|
|
@@ -0,0 +1,102 @@
|
|
|
+#!/usr/bin/env python3
|
|
|
+"""Generate a Less and Cosmos double-sided employee business card."""
|
|
|
+
|
|
|
+from __future__ import annotations
|
|
|
+
|
|
|
+import argparse
|
|
|
+import html
|
|
|
+import json
|
|
|
+import re
|
|
|
+from pathlib import Path
|
|
|
+
|
|
|
+
|
|
|
+SKILL_DIR = Path(__file__).resolve().parent.parent
|
|
|
+ASSETS_DIR = SKILL_DIR / "assets"
|
|
|
+
|
|
|
+
|
|
|
+def clean(value: str, field: str) -> str:
|
|
|
+ value = " ".join(value.strip().split())
|
|
|
+ if not value:
|
|
|
+ raise ValueError(f"{field} must not be empty")
|
|
|
+ return value
|
|
|
+
|
|
|
+
|
|
|
+def normalize_phone(value: str) -> str:
|
|
|
+ value = clean(value, "phone")
|
|
|
+ value = re.sub(r"\s+", " ", value)
|
|
|
+ if value.startswith("+86"):
|
|
|
+ return value
|
|
|
+ if value.startswith("86 "):
|
|
|
+ return "+" + value
|
|
|
+ return "+86 " + value
|
|
|
+
|
|
|
+
|
|
|
+def render_template(template: str, values: dict[str, str]) -> str:
|
|
|
+ for key, value in values.items():
|
|
|
+ template = template.replace("{{" + key + "}}", html.escape(value))
|
|
|
+ unresolved = sorted(set(re.findall(r"\{\{([A-Z_]+)\}\}", template)))
|
|
|
+ if unresolved:
|
|
|
+ raise ValueError("Unresolved template fields: " + ", ".join(unresolved))
|
|
|
+ return template
|
|
|
+
|
|
|
+
|
|
|
+def build_preview(name: str) -> str:
|
|
|
+ safe_name = html.escape(name)
|
|
|
+ return f"""<!doctype html>
|
|
|
+<html lang=\"zh-CN\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">
|
|
|
+<title>{safe_name} · 纯粹星辰名片</title><style>
|
|
|
+*{{box-sizing:border-box}}body{{margin:0;background:#e9e9e7;font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif}}
|
|
|
+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}}
|
|
|
+.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)}}
|
|
|
+figcaption{{margin-top:12px;color:#8d9297;font-size:11px;letter-spacing:2px;text-transform:uppercase}}@media(max-width:800px){{.pair{{grid-template-columns:1fr}}}}
|
|
|
+</style></head><body><main><h1>{safe_name} · 纯粹星辰</h1><div class=\"pair\">
|
|
|
+<figure><img src=\"front.svg\" alt=\"名片正面\"><figcaption>Front</figcaption></figure>
|
|
|
+<figure><img src=\"back.svg\" alt=\"名片背面\"><figcaption>Back</figcaption></figure>
|
|
|
+</div></main></body></html>"""
|
|
|
+
|
|
|
+
|
|
|
+def main() -> None:
|
|
|
+ parser = argparse.ArgumentParser(description=__doc__)
|
|
|
+ parser.add_argument("--name", required=True, help="Chinese name")
|
|
|
+ parser.add_argument("--title", required=True, help="Role, e.g. 联合创始人 / CMO")
|
|
|
+ parser.add_argument("--email", required=True)
|
|
|
+ parser.add_argument("--phone", required=True)
|
|
|
+ parser.add_argument("--output-dir", required=True, type=Path)
|
|
|
+ args = parser.parse_args()
|
|
|
+
|
|
|
+ name = clean(args.name, "name")
|
|
|
+ title = clean(args.title, "title")
|
|
|
+ email = clean(args.email, "email")
|
|
|
+ if "@" not in email or email.startswith("@") or email.endswith("@"):
|
|
|
+ raise ValueError("email does not look valid")
|
|
|
+ phone = normalize_phone(args.phone)
|
|
|
+
|
|
|
+ data = {
|
|
|
+ "name": name,
|
|
|
+ "title": title,
|
|
|
+ "email": email,
|
|
|
+ "phone": phone,
|
|
|
+ "company": "杭州纯粹星辰人工智能科技有限公司",
|
|
|
+ "address": "浙江省杭州市西湖区转塘街道云腾丝路中心5号楼商业208室",
|
|
|
+ "website": "lessncosmos.com",
|
|
|
+ "slogan": "构建能够持续学习与进化的企业级人工智能系统。",
|
|
|
+ }
|
|
|
+
|
|
|
+ args.output_dir.mkdir(parents=True, exist_ok=True)
|
|
|
+ (args.output_dir / "front.svg").write_text(
|
|
|
+ (ASSETS_DIR / "common-front.svg").read_text(encoding="utf-8"), encoding="utf-8"
|
|
|
+ )
|
|
|
+ template = (ASSETS_DIR / "back-template.svg").read_text(encoding="utf-8")
|
|
|
+ replacements = {key.upper(): value for key, value in data.items()}
|
|
|
+ (args.output_dir / "back.svg").write_text(
|
|
|
+ render_template(template, replacements), encoding="utf-8"
|
|
|
+ )
|
|
|
+ (args.output_dir / "preview.html").write_text(build_preview(name), encoding="utf-8")
|
|
|
+ (args.output_dir / "card-data.json").write_text(
|
|
|
+ json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
|
|
|
+ )
|
|
|
+ print(f"Created business card in {args.output_dir.resolve()}")
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ main()
|