1"""Shared documentation inputs for Python-only build tools."""2import hashlib,json,re,posixpath3from pathlib import Path4ROOT=Path(__file__).resolve().parents[1]5def examples():6 catalog=json.loads((ROOT/'examples/gallery/catalog.json').read_text());english=json.loads((ROOT/'examples/gallery/catalog.en.json').read_text());out=[]7 for category in catalog['categories']:8 for item in category['items']:9 source=item.get('source',f'examples/gallery/{category["slug"]}/{item["slug"]}.lay')10 out.append({'id':source.removeprefix('examples/').removesuffix('.lay').replace('/','-'),'source':source,'category':'plots' if category['slug']=='plots' else 'layout','group':category['slug'],'title':[item['title'],english[category['slug']]['items'][item['slug']][0]],'description':[item['description'],english[category['slug']]['items'][item['slug']][1]]})11 seen={e['source'] for e in out}12 for p in sorted((ROOT/'examples').rglob('*.lay')):13 source=p.relative_to(ROOT).as_posix()14 if 'output' in p.relative_to(ROOT).parts:continue15 if source in seen or 'canvas(' not in p.read_text():continue16 title=p.stem.replace('-',' ').title();out.append({'id':source.removeprefix('examples/').removesuffix('.lay').replace('/','-'),'source':source,'category':'plots' if '/plot/' in source else 'notebook' if '/notebook/' in source else 'applications','group':'plot' if '/plot/' in source else 'applications','title':[title,title],'description':['完整可复现的源码与输出。','Complete reproducible source and output.']})17 return out1819def dependencies(source,seen=None):20 if seen is None:seen=set()21 if source in seen:return sorted(seen)22 seen.add(source);p=ROOT/source23 if p.suffix.lower() not in ('.lay','.lcss','.py','.ipynb','.svg'):return sorted(seen)24 text=p.read_text();text=re.sub(r'url\(\s*([^\'"()\s]+)\s*\)',r'url("\1")',text)25 for value in re.findall(r'''["']([^"'\n]+\.(?:lay|lcss|png|jpg|jpeg|tif|tiff|bmp|webp|gif|ico|pnm|pbm|pgm|ppm|pam|tga|svg|json|csv|ttf|otf|ttc|otc)(?:#[^"'\n]*)?)["']''',text):26 value=re.sub(r'(\.(?:ttf|otf|ttc|otc))#[^/]*$',r'\1',value);target=posixpath.normpath(posixpath.join(posixpath.dirname(source),value))27 if target.startswith('../') or value.startswith('/'):continue28 if (ROOT/target).is_file():dependencies(target,seen)29 return sorted(seen)30def fingerprint(files):31 h=hashlib.sha256()32 for f in sorted(set(files)):33 h.update(str(f).encode());h.update((ROOT/f).read_bytes())34 return h.hexdigest()35def engine_files():36 # Renderer provenance covers build settings and crate sources; standalone test37 # fixtures do not change rendered media when their platform paths are corrected.38 return ['Cargo.lock','Cargo.toml','rust-toolchain.toml']+[p.relative_to(ROOT).as_posix() for p in sorted((ROOT/'crates').rglob('*')) if p.is_file() and p.suffix in ('.rs','.toml','.json') and 'laymesh-wasm' not in p.parts and 'tests' not in p.relative_to(ROOT/'crates').parts]39def summary(source):40 text=(ROOT/source).read_text();m=re.search(r'# BEGIN DEMO\r?\n([\s\S]*?)\r?\n# END DEMO',text)41 if m:return m[1],m.start(1),text[:m.start(1)].count('\n')+142 return '\n'.join(text.splitlines()[:14]),0,1