#!/usr/bin/env python3 """ Match Figma cloud Drafts files (from figma-files.json) back to W{N} entries in manifest.json by fuzzy name match, then update web/data.json with figma_key+url. """ import json, re, sys, difflib from pathlib import Path ROOT = Path(__file__).resolve().parent.parent def normalize(s): s = s.lower() s = re.sub(r'[^a-z0-9]+', ' ', s).strip() return s def best_match(target, candidates): """Return (score, candidate) best match from candidates list of dicts with .name""" nt = normalize(target) best = (0, None) for c in candidates: nc = normalize(c['name']) # exact / prefix / contains / sequence if nt == nc: s = 1.0 elif nt in nc or nc in nt: s = 0.85 else: s = difflib.SequenceMatcher(None, nt, nc).ratio() if s > best[0]: best = (s, c) return best def main(): manifest = json.loads((ROOT/'manifest.json').read_text()) figma_files_path = ROOT/'figma-files.json' if not figma_files_path.exists(): print(f"missing {figma_files_path}", file=sys.stderr) sys.exit(1) figma_files = json.loads(figma_files_path.read_text()) # list of {key, name, ...} # Match each template source file to ALL matching cloud files (supports multi-variant) matches = [] used_keys = set() for t in manifest['templates']: source_files = t['fig'] if t['fig'] else t['sketch'] if not source_files: continue kind = 'fig' if t['fig'] else 'sketch' variants = [] for src in source_files: stem = Path(src).stem # Match this specific source stem to best unused cloud file score, cand = best_match(stem, [f for f in figma_files if f['key'] not in used_keys]) if cand and score >= 0.6: used_keys.add(cand['key']) variants.append({ 'source_stem': stem, 'matched': cand['name'], 'key': cand['key'], 'score': round(score, 3) }) matches.append({ 'W': t['id'], 'name': t['name'], 'kind': kind, 'source_count': len(source_files), 'variants': variants, }) # Update web/data.json data_path = ROOT/'web'/'data.json' data = json.loads(data_path.read_text()) by_W = {m['W']: m for m in matches} for t in data['templates']: m = by_W.get(t['id']) if m and m['variants']: first = m['variants'][0] t['figma_key'] = first['key'] t['figma_url'] = f"https://www.figma.com/file/{first['key']}" t['figma_variants'] = [ {'name': v['source_stem'], 'matched': v['matched'], 'key': v['key'], 'url': f"https://www.figma.com/file/{v['key']}"} for v in m['variants'] ] else: t['figma_key'] = None t['figma_url'] = None t['figma_variants'] = [] # update banner with imported count + variant total imported = sum(1 for t in data['templates'] if t['figma_key']) total_variants = sum(len(t.get('figma_variants', [])) for t in data['templates']) fig_cnt = sum(1 for t in manifest['templates'] if t['fig']) sketch_only_cnt = sum(1 for t in manifest['templates'] if not t['fig'] and t['sketch']) data['imported_summary'] = ( f"✅ {imported}/56 套(共 {total_variants} 个文件)已云端就位在 " f"Figma Drafts" f"({fig_cnt} .fig + {sketch_only_cnt} .sketch 转换)。" f"点卡片 → modal → 多变体 tab 切换 + iframe 实时投射。" ) data_path.write_text(json.dumps(data, ensure_ascii=False, indent=2)) # write match report report = ROOT/'figma-match-report.json' report.write_text(json.dumps(matches, ensure_ascii=False, indent=2)) total_matchable = sum(1 for t in manifest['templates'] if t['fig'] or t['sketch']) multi_variant = sum(1 for m in matches if len(m['variants']) > 1) print(f"Matched {imported}/{total_matchable} templates, {total_variants} total file keys, {multi_variant} with multi variants") print(f"Report: {report.relative_to(ROOT)}") unmatched = [m for m in matches if not m['variants']] if unmatched: print(f"\n⚠️ {len(unmatched)} unmatched:") for m in unmatched: print(f" {m['W']:4s} {m['name'][:50]:50s}") if __name__ == '__main__': main()