mirror of
https://github.com/shareAI-lab/analysis_claude_code.git
synced 2026-10-31 02:31:28 +08:00
fix: an empty REPL line no longer quits the chapter silently (#569)
Every chapter REPL treated an empty line as a quit command and exited with status 0 without a word, while the banner only promises "Type q to quit". Under VS Code's debugger the Python extension's environment-activation line is often injected into stdin after the program started, so the first thing the REPL read was a blank line and the chapter vanished right after its banner. An empty line is now ignored and the prompt comes back; q and exit still quit, and EOF/Ctrl-C still end the loop. s17 already behaved this way. A static test keeps every chapter's quit check free of the empty string. Fixes #567
This commit is contained in:
@@ -138,7 +138,9 @@ if __name__ == "__main__":
|
||||
query = input("\001\033[36m\002s01 >> \001\033[0m\002")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
if query.strip().lower() in ("q", "exit", ""):
|
||||
if not query.strip():
|
||||
continue
|
||||
if query.strip().lower() in ("q", "exit"):
|
||||
break
|
||||
history.append({"role": "user", "content": query})
|
||||
agent_loop(history)
|
||||
|
||||
@@ -196,7 +196,9 @@ if __name__ == "__main__":
|
||||
query = input("\001\033[36m\002s02 >> \001\033[0m\002")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
if query.strip().lower() in ("q", "exit", ""):
|
||||
if not query.strip():
|
||||
continue
|
||||
if query.strip().lower() in ("q", "exit"):
|
||||
break
|
||||
history.append({"role": "user", "content": query})
|
||||
agent_loop(history)
|
||||
|
||||
@@ -257,7 +257,9 @@ if __name__ == "__main__":
|
||||
query = input("\001\033[36m\002s03 >> \001\033[0m\002")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
if query.strip().lower() in ("q", "exit", ""):
|
||||
if not query.strip():
|
||||
continue
|
||||
if query.strip().lower() in ("q", "exit"):
|
||||
break
|
||||
history.append({"role": "user", "content": query})
|
||||
agent_loop(history)
|
||||
|
||||
+3
-1
@@ -269,7 +269,9 @@ if __name__ == "__main__":
|
||||
query = input("\001\033[36m\002s04 >> \001\033[0m\002")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
if query.strip().lower() in ("q", "exit", ""):
|
||||
if not query.strip():
|
||||
continue
|
||||
if query.strip().lower() in ("q", "exit"):
|
||||
break
|
||||
trigger_hooks("UserPromptSubmit", query)
|
||||
history.append({"role": "user", "content": query})
|
||||
|
||||
@@ -357,7 +357,9 @@ if __name__ == "__main__":
|
||||
query = input("\001\033[36m\002s05 >> \001\033[0m\002")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
if query.strip().lower() in ("q", "exit", ""):
|
||||
if not query.strip():
|
||||
continue
|
||||
if query.strip().lower() in ("q", "exit"):
|
||||
break
|
||||
trigger_hooks("UserPromptSubmit", query)
|
||||
history.append({"role": "user", "content": query})
|
||||
|
||||
@@ -372,7 +372,9 @@ if __name__ == "__main__":
|
||||
query = input("\001\033[36m\002s06 >> \001\033[0m\002")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
if query.strip().lower() in ("q", "exit", ""):
|
||||
if not query.strip():
|
||||
continue
|
||||
if query.strip().lower() in ("q", "exit"):
|
||||
break
|
||||
trigger_hooks("UserPromptSubmit", query)
|
||||
history.append({"role": "user", "content": query})
|
||||
|
||||
@@ -388,7 +388,9 @@ if __name__ == "__main__":
|
||||
query = input("\001\033[36m\002s07 >> \001\033[0m\002")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
if query.strip().lower() in ("q", "exit", ""):
|
||||
if not query.strip():
|
||||
continue
|
||||
if query.strip().lower() in ("q", "exit"):
|
||||
break
|
||||
trigger_hooks("UserPromptSubmit", query)
|
||||
history.append({"role": "user", "content": query})
|
||||
|
||||
@@ -591,7 +591,9 @@ if __name__ == "__main__":
|
||||
query = input("\001\033[36m\002s08 >> \001\033[0m\002")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
if query.strip().lower() in ("q", "exit", ""):
|
||||
if not query.strip():
|
||||
continue
|
||||
if query.strip().lower() in ("q", "exit"):
|
||||
break
|
||||
trigger_hooks("UserPromptSubmit", query)
|
||||
history.append({"role": "user", "content": query})
|
||||
|
||||
+3
-1
@@ -773,7 +773,9 @@ if __name__ == "__main__":
|
||||
query = input("\001\033[36m\002s09 >> \001\033[0m\002")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
if query.strip().lower() in ("q", "exit", ""):
|
||||
if not query.strip():
|
||||
continue
|
||||
if query.strip().lower() in ("q", "exit"):
|
||||
break
|
||||
trigger_hooks("UserPromptSubmit", query)
|
||||
history.append({"role": "user", "content": query})
|
||||
|
||||
@@ -589,7 +589,9 @@ if __name__ == "__main__":
|
||||
query = input("\001\033[36m\002s10 >> \001\033[0m\002")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
if query.strip().lower() in ("q", "exit", ""):
|
||||
if not query.strip():
|
||||
continue
|
||||
if query.strip().lower() in ("q", "exit"):
|
||||
break
|
||||
trigger_hooks("UserPromptSubmit", query)
|
||||
history.append({"role": "user", "content": query})
|
||||
|
||||
@@ -531,7 +531,9 @@ if __name__ == "__main__":
|
||||
query = input("\001\033[36m\002s11 >> \001\033[0m\002")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
if query.strip().lower() in ("q", "exit", ""):
|
||||
if not query.strip():
|
||||
continue
|
||||
if query.strip().lower() in ("q", "exit"):
|
||||
break
|
||||
trigger_hooks("UserPromptSubmit", query)
|
||||
history.append({"role": "user", "content": query})
|
||||
|
||||
@@ -782,7 +782,9 @@ if __name__ == "__main__":
|
||||
query = input("\001\033[36m\002s12 >> \001\033[0m\002")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
if query.strip().lower() in ("q", "exit", ""):
|
||||
if not query.strip():
|
||||
continue
|
||||
if query.strip().lower() in ("q", "exit"):
|
||||
break
|
||||
with agent_lock:
|
||||
run_agent_turn_locked(query)
|
||||
|
||||
@@ -1862,7 +1862,11 @@ if __name__ == "__main__":
|
||||
if kind == "quit":
|
||||
break
|
||||
if kind == "user":
|
||||
if payload is None or payload.strip().lower() in {"q", "exit", ""}:
|
||||
if payload is None:
|
||||
break
|
||||
if not payload.strip():
|
||||
continue
|
||||
if payload.strip().lower() in {"q", "exit"}:
|
||||
break
|
||||
trigger_hooks("UserPromptSubmit", payload)
|
||||
history.append({"role": "user", "content": payload})
|
||||
|
||||
@@ -536,7 +536,9 @@ if __name__ == "__main__":
|
||||
query = input("s14 >> ")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
if query.strip().lower() in {"q", "exit", ""}:
|
||||
if not query.strip():
|
||||
continue
|
||||
if query.strip().lower() in {"q", "exit"}:
|
||||
break
|
||||
trigger_hooks("UserPromptSubmit", query)
|
||||
history.append({"role": "user", "content": query})
|
||||
|
||||
@@ -3313,7 +3313,9 @@ if __name__ == "__main__":
|
||||
query = CONSOLE.ask()
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
if query.strip().lower() in ("q", "exit", ""):
|
||||
if not query.strip():
|
||||
continue
|
||||
if query.strip().lower() in ("q", "exit"):
|
||||
break
|
||||
with agent_lock:
|
||||
trigger_hooks("UserPromptSubmit", query)
|
||||
|
||||
@@ -868,7 +868,9 @@ def run_cli():
|
||||
query = host.CONSOLE.ask()
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
if query.strip().lower() in ("q", "exit", ""):
|
||||
if not query.strip():
|
||||
continue
|
||||
if query.strip().lower() in ("q", "exit"):
|
||||
break
|
||||
with host.agent_lock:
|
||||
host.trigger_hooks("UserPromptSubmit", query)
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
"""Every chapter REPL quits only on an explicit command (issue #567).
|
||||
|
||||
An empty line used to be a silent exit with status 0, which contradicted the
|
||||
"Type q to quit" banner and, combined with VS Code's environment-activation
|
||||
line being injected into stdin, made a debugged chapter vanish right after its
|
||||
banner. An empty line is now ignored and the prompt comes back.
|
||||
"""
|
||||
|
||||
import ast
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
CHAPTER_FILES = sorted(ROOT.glob("s*/code.py"))
|
||||
|
||||
|
||||
def _quit_checks(source: str) -> list[ast.Compare]:
|
||||
"""Return every `X.strip().lower() in (...)` / `in {...}` comparison."""
|
||||
found = []
|
||||
for node in ast.walk(ast.parse(source)):
|
||||
if not isinstance(node, ast.Compare) or len(node.ops) != 1 or not isinstance(node.ops[0], ast.In):
|
||||
continue
|
||||
left = node.left
|
||||
if (
|
||||
isinstance(left, ast.Call)
|
||||
and isinstance(left.func, ast.Attribute)
|
||||
and left.func.attr == "lower"
|
||||
and isinstance(left.func.value, ast.Call)
|
||||
and isinstance(left.func.value.func, ast.Attribute)
|
||||
and left.func.value.func.attr == "strip"
|
||||
):
|
||||
found.append(node)
|
||||
return found
|
||||
|
||||
|
||||
def test_every_chapter_has_a_repl_quit_check():
|
||||
assert len(CHAPTER_FILES) >= 17
|
||||
for path in CHAPTER_FILES:
|
||||
assert _quit_checks(path.read_text(encoding="utf-8")), f"{path}: no quit command check found"
|
||||
|
||||
|
||||
def test_empty_line_is_never_a_quit_command():
|
||||
for path in CHAPTER_FILES:
|
||||
for check in _quit_checks(path.read_text(encoding="utf-8")):
|
||||
container = check.comparators[0]
|
||||
assert isinstance(container, (ast.Tuple, ast.Set)), f"{path}:{check.lineno}"
|
||||
values = [c.value for c in container.elts if isinstance(c, ast.Constant)]
|
||||
assert "" not in values, f"{path}:{check.lineno}: empty input still quits the REPL"
|
||||
assert "q" in values, f"{path}:{check.lineno}: q must still quit"
|
||||
|
||||
|
||||
def test_blank_input_is_skipped_before_the_quit_check():
|
||||
pattern = re.compile(r"if not (\w+)\.strip\(\):\n\s+continue\n\s+if \1\.strip\(\)\.lower\(\) in", re.M)
|
||||
for path in CHAPTER_FILES:
|
||||
if path.parent.name.startswith("s17"):
|
||||
continue # s17 already handled blank input before this change
|
||||
assert pattern.search(path.read_text(encoding="utf-8")), f"{path}: blank input is not skipped"
|
||||
Reference in New Issue
Block a user