feat(learn-theme): add CLI with argparse and terminal report

Replace the smoke-test main() with a proper argparse CLI that accepts
a URL and --name, validates the name, fetches + extracts + analyzes the
article, calls generate_theme_yaml(), and writes the YAML to
toolkit/themes/. Prints a human-readable theme report with color values
and typography. Adds `learn-theme` subcommand to toolkit/cli.py
(delegates to subprocess call of scripts/learn_theme.py).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangzhuc 2026-04-01 11:45:36 +08:00
parent 1cd9b4409f
commit d6900fe85d
2 changed files with 64 additions and 22 deletions

View file

@ -588,33 +588,59 @@ def _load_from_file(path: str):
def main(): def main():
args = sys.argv[1:] parser = argparse.ArgumentParser(
if not args: description="Learn a WeChat formatting theme from an article URL.",
print(__doc__) )
sys.exit(0) parser.add_argument("url", help="WeChat article URL (https://mp.weixin.qq.com/s/...)")
parser.add_argument("--name", required=True, help="Theme name (used as filename and reference)")
parser.add_argument("--output-dir", default=None, help="Output directory (default: toolkit/themes/)")
args = parser.parse_args()
if args[0] == "--file" and len(args) >= 2: # Validate name: only letters, digits, hyphens, underscores
content = _load_from_file(args[1]) if not re.match(r"^[a-zA-Z0-9_-]+$", args.name):
else: print("Error: --name must contain only letters, digits, hyphens, and underscores.", file=sys.stderr)
content = fetch_article(args[0]) raise SystemExit(1)
print(f"Title: {content._wewrite_title}") output_dir = Path(args.output_dir) if args.output_dir else THEMES_DIR
output_path = output_dir / f"{args.name}.yaml"
if output_path.exists():
print(f"Warning: {output_path} already exists, will be overwritten.", file=sys.stderr)
# Fetch
print("Fetching article...")
content = fetch_article(args.url)
title = getattr(content, "_wewrite_title", "")
if title:
print(f"Title: {title}")
# Extract
grouped = extract_styles(content) grouped = extract_styles(content)
print("Elements with styles:") styled_count = sum(len(v) for v in grouped.values())
for tag, styles in grouped.items(): print(f"Extracted {styled_count} styled elements.")
if styles:
print(f" <{tag}>: {len(styles)} elements")
theme = analyze_styles(grouped) # Analyze
print("\nInferred theme:") analyzed = analyze_styles(grouped)
for key, val in theme.items():
print(f" {key}: {val}")
# Dark mode # Generate & write
dm = derive_darkmode(theme) theme_yaml = generate_theme_yaml(args.name, title, analyzed)
print("\nDerived dark mode:") output_dir.mkdir(parents=True, exist_ok=True)
for key, val in dm.items(): output_path.write_text(theme_yaml, encoding="utf-8")
print(f" {key}: {val}")
# Report
print()
print(f"Learned theme from: {title or args.url}")
print(f" text: {analyzed['text']}")
print(f" text_light: {analyzed['text_light']}")
print(f" primary: {analyzed['primary']}")
print(f" secondary: {analyzed['secondary']}")
print(f" background: {analyzed['background']}")
print(f" font: {analyzed['font_family'][:50]}")
print(f" size: {analyzed['font_size']} / line-height {analyzed['line_height']} / spacing {analyzed['letter_spacing']}")
print()
print(f"Theme saved → {output_path}")
print(f"Use it: python3 toolkit/cli.py preview article.md --theme {args.name}")
print(f"Or set: theme: {args.name} in style.yaml")
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -232,6 +232,15 @@ def cmd_gallery(args):
webbrowser.open(f"file://{Path(output).absolute()}") webbrowser.open(f"file://{Path(output).absolute()}")
def cmd_learn_theme(args):
"""Learn a theme from a WeChat article URL."""
import subprocess
script = Path(__file__).parent.parent / "scripts" / "learn_theme.py"
cmd = [sys.executable, str(script), args.url, "--name", args.name]
result = subprocess.run(cmd)
sys.exit(result.returncode)
def _gallery_sample_markdown(): def _gallery_sample_markdown():
return """# 示例文章标题 return """# 示例文章标题
@ -397,6 +406,11 @@ def main():
p_gallery.add_argument("-o", "--output", help="Output HTML file path") p_gallery.add_argument("-o", "--output", help="Output HTML file path")
p_gallery.add_argument("--no-open", action="store_true", help="Don't open browser") p_gallery.add_argument("--no-open", action="store_true", help="Don't open browser")
# learn-theme
p_learn = sub.add_parser("learn-theme", help="Learn formatting theme from a WeChat article URL")
p_learn.add_argument("url", help="WeChat article URL")
p_learn.add_argument("--name", required=True, help="Theme name")
args = parser.parse_args() args = parser.parse_args()
try: try:
@ -410,6 +424,8 @@ def main():
cmd_image_post(args) cmd_image_post(args)
elif args.command == "gallery": elif args.command == "gallery":
cmd_gallery(args) cmd_gallery(args)
elif args.command == "learn-theme":
cmd_learn_theme(args)
except Exception as e: except Exception as e:
print(f"Error: {e}", file=sys.stderr) print(f"Error: {e}", file=sys.stderr)
sys.exit(1) sys.exit(1)