106 lines
3.6 KiB
Python
106 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
||
"""Run the xdfx flow one step at a time.
|
||
|
||
The heavy business logic intentionally stays in xdfx_flow.py. This wrapper only
|
||
keeps "full flow" orchestration readable and preserves per-step feedback: every
|
||
subprocess line is flushed immediately, so OP/SAVE/ERROR/STEP feedback remains
|
||
visible to the calling agent.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import os
|
||
import subprocess
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
if hasattr(sys.stdout, "reconfigure"):
|
||
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||
if hasattr(sys.stderr, "reconfigure"):
|
||
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
||
|
||
STEPS = ("step1", "step2", "step3", "step4", "step5", "step6")
|
||
|
||
STEP_LABELS = {
|
||
"step1": "受众目标分析与入库",
|
||
"step2": "关键传播节点分析与入库",
|
||
"step3": "受众脆弱点分析与入库",
|
||
"step4": "传播节点脆弱点分析与入库",
|
||
"step5": "受众策略分析与入库",
|
||
"step6": "传播节点策略分析与入库",
|
||
}
|
||
|
||
|
||
def build_parser() -> argparse.ArgumentParser:
|
||
"""Parse only wrapper-owned flags; pass all other flags to xdfx_flow.py."""
|
||
parser = argparse.ArgumentParser(
|
||
description="按六个步骤运行 xdfx_flow.py,并实时转发中文反馈日志。",
|
||
add_help=True,
|
||
)
|
||
parser.add_argument("--from-step", choices=STEPS, default="step1")
|
||
parser.add_argument("--to-step", choices=STEPS, default="step6")
|
||
parser.add_argument(
|
||
"--continue-on-error",
|
||
action="store_true",
|
||
help="某一步返回非零退出码时仍继续执行后续步骤。",
|
||
)
|
||
return parser
|
||
|
||
|
||
def selected_steps(first: str, last: str) -> list[str]:
|
||
"""Return an inclusive step slice for resume/partial reruns."""
|
||
start = STEPS.index(first)
|
||
end = STEPS.index(last)
|
||
if start > end:
|
||
raise SystemExit("起始步骤必须小于或等于结束步骤")
|
||
return list(STEPS[start : end + 1])
|
||
|
||
|
||
def stream_command(step: str, cmd: list[str]) -> tuple[int, bool]:
|
||
"""Run one step and forward stdout/stderr lines as they are produced."""
|
||
print(f"运行步骤:{STEP_LABELS.get(step, step)};执行命令:" + " ".join(cmd), flush=True)
|
||
env = os.environ.copy()
|
||
env["PYTHONIOENCODING"] = "utf-8"
|
||
process = subprocess.Popen(
|
||
cmd,
|
||
stdout=subprocess.PIPE,
|
||
stderr=subprocess.STDOUT,
|
||
text=True,
|
||
encoding="utf-8",
|
||
errors="replace",
|
||
env=env,
|
||
)
|
||
assert process.stdout is not None
|
||
waiting_user = False
|
||
for line in process.stdout:
|
||
if line.startswith("用户协助 "):
|
||
waiting_user = True
|
||
sys.stdout.buffer.write(line.encode("utf-8", errors="replace"))
|
||
sys.stdout.buffer.flush()
|
||
return process.wait(), waiting_user
|
||
|
||
|
||
def main(argv: list[str] | None = None) -> int:
|
||
parser = build_parser()
|
||
args, passthrough = parser.parse_known_args(argv)
|
||
flow_script = Path(__file__).with_name("xdfx_flow.py")
|
||
exit_code = 0
|
||
for step in selected_steps(args.from_step, args.to_step):
|
||
# Keep all business flags unchanged so the wrapper behaves like a thin
|
||
# streaming version of repeated xdfx_flow.py --mode stepN calls.
|
||
cmd = [sys.executable, str(flow_script), "--mode", step, *passthrough]
|
||
code, waiting_user = stream_command(step, cmd)
|
||
if waiting_user:
|
||
print("已发起用户协助,暂停后续步骤;请根据用户选择重跑当前步骤。", flush=True)
|
||
return code
|
||
if code:
|
||
exit_code = code
|
||
if not args.continue_on_error:
|
||
return code
|
||
return exit_code
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|