generate_card.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. #!/usr/bin/env python3
  2. """Generate a Less and Cosmos double-sided employee business card."""
  3. from __future__ import annotations
  4. import argparse
  5. import html
  6. import json
  7. import re
  8. from pathlib import Path
  9. SKILL_DIR = Path(__file__).resolve().parent.parent
  10. ASSETS_DIR = SKILL_DIR / "assets"
  11. def clean(value: str, field: str) -> str:
  12. value = " ".join(value.strip().split())
  13. if not value:
  14. raise ValueError(f"{field} must not be empty")
  15. return value
  16. def normalize_phone(value: str) -> str:
  17. value = clean(value, "phone")
  18. value = re.sub(r"\s+", " ", value)
  19. if value.startswith("+86"):
  20. return value
  21. if value.startswith("86 "):
  22. return "+" + value
  23. return "+86 " + value
  24. def render_template(template: str, values: dict[str, str]) -> str:
  25. for key, value in values.items():
  26. template = template.replace("{{" + key + "}}", html.escape(value))
  27. unresolved = sorted(set(re.findall(r"\{\{([A-Z_]+)\}\}", template)))
  28. if unresolved:
  29. raise ValueError("Unresolved template fields: " + ", ".join(unresolved))
  30. return template
  31. def build_preview(name: str) -> str:
  32. safe_name = html.escape(name)
  33. return f"""<!doctype html>
  34. <html lang=\"zh-CN\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">
  35. <title>{safe_name} · 纯粹星辰名片</title><style>
  36. *{{box-sizing:border-box}}body{{margin:0;background:#e9e9e7;font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif}}
  37. main{{width:min(1480px,94vw);margin:0 auto;padding:5vw 0}}h1{{margin:0 0 28px;color:#363a3f;font-size:18px;font-weight:500;letter-spacing:2px}}
  38. .pair{{display:grid;grid-template-columns:1fr 1fr;gap:30px}}figure{{margin:0}}img{{display:block;width:100%;box-shadow:0 16px 46px rgba(25,28,31,.12)}}
  39. figcaption{{margin-top:12px;color:#8d9297;font-size:11px;letter-spacing:2px;text-transform:uppercase}}@media(max-width:800px){{.pair{{grid-template-columns:1fr}}}}
  40. </style></head><body><main><h1>{safe_name} · 纯粹星辰</h1><div class=\"pair\">
  41. <figure><img src=\"front.svg\" alt=\"名片正面\"><figcaption>Front</figcaption></figure>
  42. <figure><img src=\"back.svg\" alt=\"名片背面\"><figcaption>Back</figcaption></figure>
  43. </div></main></body></html>"""
  44. def main() -> None:
  45. parser = argparse.ArgumentParser(description=__doc__)
  46. parser.add_argument("--name", required=True, help="Chinese name")
  47. parser.add_argument("--title", required=True, help="Role, e.g. 联合创始人 / CMO")
  48. parser.add_argument("--email", required=True)
  49. parser.add_argument("--phone", required=True)
  50. parser.add_argument("--output-dir", required=True, type=Path)
  51. args = parser.parse_args()
  52. name = clean(args.name, "name")
  53. title = clean(args.title, "title")
  54. email = clean(args.email, "email")
  55. if "@" not in email or email.startswith("@") or email.endswith("@"):
  56. raise ValueError("email does not look valid")
  57. phone = normalize_phone(args.phone)
  58. data = {
  59. "name": name,
  60. "title": title,
  61. "email": email,
  62. "phone": phone,
  63. "company": "杭州纯粹星辰人工智能科技有限公司",
  64. "address": "浙江省杭州市西湖区转塘街道云腾丝路中心5号楼商业208室",
  65. "website": "lessncosmos.com",
  66. "slogan": "构建能够持续学习与进化的企业级人工智能系统。",
  67. }
  68. args.output_dir.mkdir(parents=True, exist_ok=True)
  69. (args.output_dir / "front.svg").write_text(
  70. (ASSETS_DIR / "common-front.svg").read_text(encoding="utf-8"), encoding="utf-8"
  71. )
  72. template = (ASSETS_DIR / "back-template.svg").read_text(encoding="utf-8")
  73. replacements = {key.upper(): value for key, value in data.items()}
  74. (args.output_dir / "back.svg").write_text(
  75. render_template(template, replacements), encoding="utf-8"
  76. )
  77. (args.output_dir / "preview.html").write_text(build_preview(name), encoding="utf-8")
  78. (args.output_dir / "card-data.json").write_text(
  79. json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
  80. )
  81. print(f"Created business card in {args.output_dir.resolve()}")
  82. if __name__ == "__main__":
  83. main()