reference.py (10368B)
1 #!/usr/bin/env python3 2 """Build static agent references and picker metadata from published interfaces.""" 3 import argparse 4 import hashlib 5 import json 6 from pathlib import Path 7 import re 8 import sys 9 10 11 # These paths belong to immutable releases predating canonical IO naming. 12 # Current implementation context is published only when its source exists. 13 HISTORICAL_SOURCE_PATHS = { 14 'lib/io-v2.tri', 'lib/io-v2/artifacts.tri', 'lib/io-v2/http-transport.tri', 15 'lib/io-v2/objects.tri', 'lib/io-v2/storage.tri', 'lib/io-v2/text.tri', 16 'lib/source/io-v2.tri', 'lib/arboricx/streaming-v2.tri', 'lib/packages/io-v2.tri', 17 } 18 19 20 def source_reference(root, title): 21 source = (root / title).resolve() 22 if not source.is_relative_to(root / 'lib'): 23 raise ValueError('library source escapes the source root') 24 if not source.is_file(): 25 if title not in HISTORICAL_SOURCE_PATHS: 26 raise ValueError('missing current library source: ' + title) 27 return None, f'Historical source path: `{title}`. Its source is retained in Git history. ' 28 relative = source.relative_to(root).as_posix() 29 return source, (f'[Repository source](../source/{relative}.txt) from this site build. ' 30 f'Source SHA-256: `{hashlib.sha256(source.read_bytes()).hexdigest()}`. ') 31 32 33 def example_instructions(example): 34 name = example['expression'] 35 if not re.fullmatch(r'[A-Za-z_][A-Za-z0-9_]*', name): 36 raise ValueError('Playground examples require a named source definition: ' + name) 37 interpretation = dict(number='Natural number', string='Text')[example['output']] 38 if example['mode'] == 'pure': 39 return f'Click the underlined `{name}` definition. Its card displays the result; choose **{interpretation}** to request that interpretation explicitly.' 40 return (f'Click the underlined `{name}` definition to open its IO card. ' 41 f'In **IO details**, set **Returned value (next run)** to **{interpretation}**, ' 42 'then click **Run IO**. The card shows terminal output and the returned value.') 43 44 45 def generate(root, output): 46 site = root / 'ext/demo-site' 47 sys.path.insert(0, str(root / 'test')) 48 from bundle_dag import read 49 reference = output / 'reference' 50 reference.mkdir(parents=True, exist_ok=True) 51 def write(name, text): 52 path = reference / name 53 path.parent.mkdir(parents=True, exist_ok=True) 54 path.write_text(text) 55 write('LICENSE.txt', (root / 'LICENSE').read_text()) 56 notes = json.loads((site / 'reference-input/libraries.json').read_text()) 57 releases = json.loads((site / 'packages.json').read_text())['releases'] 58 rows = [] 59 # Keep all releases. Browser-side SemVer ordering chooses the initial view. 60 for release in releases: 61 if not release['name'].startswith('tricu-lib-'): 62 continue 63 short = release['name'].removeprefix('tricu-lib-') 64 bundle = site / 'bundles' / (release['hash'] + '.arboricx') 65 assert hashlib.sha256(bundle.read_bytes()).hexdigest() == release['hash'] 66 names = list(read(bundle)[3]) 67 note = notes.get(short + '@' + release['version'], notes.get(short, {})) 68 note = note if release['version'] == note.get('version', '0.1.0') else {} 69 alias = note.get('alias', ''.join(word.capitalize() for word in short.split('-'))) 70 description = note.get('description', f'{short.replace("-", " ").capitalize()} library; inspect its complete interface and source reference below.') 71 filename = release['name'] + '-' + release['version'] + '.md' 72 rows.append(dict(release, title=short, description=description, alias=alias, 73 names=names, reference='libraries/' + filename, **({'usage': note['usage']} if 'usage' in note else {}))) 74 source, source_note = source_reference(root, release['title']) 75 # Export membership comes from the immutable published bundle; current 76 # checkout source is separately labelled implementation context. 77 if source is not None: 78 write('source/' + source.relative_to(root).as_posix() + '.txt', source.read_text()) 79 write('libraries/' + filename, 80 f'# {short} · {release["version"]}\n\n{description}\n\n' 81 f'```tricu\n!import {release["manifest"]} as {alias}\n```\n\n' 82 f'Package: `{release["name"]}`\n\nManifest: `{release["manifest"]}`\n\n' 83 f'Bundle SHA-256: `{release["hash"]}`\n\n' 84 f'[Download bundle](https://arbori.cx/_arboricx/bundle/hash/{release["hash"]})\n\n' 85 '## Calling conventions\n\n' + note.get('usage', 'Consult the source and protocol guides for argument order, encoding and effects.') + '\n\n' 86 '[Complete examples](../examples.md) · [Language features](../language-features.md) · [Contracts](../contracts.md) · [Host ABI](../host-abi.md) · [Parser protocol](../parser.md)\n\n' 87 f'## Complete published interface ({len(names)} exports)\n\n```text\n' + '\n'.join(names) + '\n```\n\n' 88 '## Source reference\n\n' + source_note + 89 'The export list above describes the selected published bundle; repository source is provided for implementation context. ' 90 'Workspace import names in source resolve through the [library directory](../libraries.md) when adapting code for the playground.\n') 91 write('libraries.md', '# Published tricu libraries\n\nChoose a release to get its complete exports, manifest import and calling conventions. ' 92 'The playground picker initially shows the newest version of each library; all releases are listed here. ' 93 '[Start with the playground guide](getting-started.md).\n\n' + '\n'.join( 94 f'- [{r["title"]} {r["version"]}]({r["reference"]}) — {r["description"]}' for r in rows) + '\n') 95 # Publish readable source and language/protocol contracts at ordinary HTTP URLs. 96 for path in (root / 'lib').rglob('*.tri'): 97 write('source/' + path.relative_to(root).as_posix() + '.txt', path.read_text()) 98 for path in (root / 'docs').glob('*.md'): 99 text = path.read_text().replace('.md.html', '.md') 100 text = re.sub(r'\]\(\.\./lib/([^)]*?\.tri)(#[^)]*)?\)', lambda m: '](source/lib/' + m[1] + '.txt' + (m[2] or '') + ')', text) 101 text = re.sub(r'\]\(\.\./([^)]*)\)', r'](https://git.eversole.co/James/tricu/file/\1)', text) 102 write(path.name, text) 103 write('getting-started.md', (site / 'reference-input/getting-started.md').read_text()) 104 # Examples pin explicit release versions; adding a publication cannot silently 105 # change their program meaning or independent expected results. 106 pins = {r['name'].removeprefix('tricu-lib-'): r['manifest'] for r in rows if r['version'] == '0.11.0'} 107 pins.update({r['name'].removeprefix('tricu-lib-') + '@' + r['version']: r['manifest'] for r in rows}) 108 examples = json.loads((site / 'reference-input/examples.json').read_text()) 109 for example in examples: 110 example['source'] = re.sub(r'\{\{([^}]+)\}\}', lambda m: pins[m[1]], example['source']) 111 for row in rows: 112 example = next((e for e in examples if row['manifest'] in e['source']), None) 113 if example: 114 row['example'] = example['source'] + '\n' + example_instructions(example) + '\nExpected result: ' + example['expected'] 115 write('libraries.json', json.dumps(dict(schema=1, releases=rows), indent=2) + '\n') 116 write('examples.json', json.dumps(examples, indent=2) + '\n') 117 write('examples.md', '# Playground examples\n\nReplace Source with a complete module below and wait for **Compiled**. ' 118 'Click the underlined definition name to open its card. Pure examples display their result there; ' 119 'IO examples require an explicit **Run IO** click. Write a new source definition to try another application. ' 120 'The interpretation selector belongs to the card; IO returned-value interpretation is in **IO details**.\n\n' + '\n'.join( 121 f'## {e["title"]}\n\n```tricu\n{e["source"]}```\n\n' + example_instructions(e) + '\n\n' 122 f'Expected result: `{e["expected"]}`.\n\n' + (f'Terminal output:\n\n```text\n{e["terminal"]}```\n\n' if e['terminal'] else '') for e in examples)) 123 (output / 'llms.txt').write_text('''# Arboricx and tricu 124 125 > tricu is a programming language built on Tree Calculus. Arboricx carries compiled programs between hosts. This site is the language homepage and browser playground. 126 127 ## Write programs 128 129 - [Playground guide](https://arbori.cx/reference/getting-started.md): edit source, click definition names, inspect cards, run IO, import libraries and handle diagnostics. 130 - [Library directory](https://arbori.cx/reference/libraries.md): complete published interfaces, versions, exact manifest imports and calling conventions. 131 - [Machine-readable library index](https://arbori.cx/reference/libraries.json): the same metadata used by Explore libraries. 132 - [Language features](https://arbori.cx/reference/language-features.md): patterns, recursion, local bindings, sequencing and library protocols. 133 - [Language contract](https://arbori.cx/reference/language.md): source syntax and semantics. 134 - [Worked examples](https://arbori.cx/reference/examples.md): complete source, definition-card instructions and expected results. 135 - [Modules and packages](https://arbori.cx/reference/modules.md): source modules and portable bundle identities. 136 137 ## Protocols and boundaries 138 139 - [Contracts](https://arbori.cx/reference/contracts.md): input validation and Result protocols. 140 - [Parser combinators](https://arbori.cx/reference/parser.md): replies, progress and rollback. 141 - [Host ABI](https://arbori.cx/reference/host-abi.md): IO actions and host capabilities. 142 - [Native CLI](https://arbori.cx/reference/build-and-run.md): run and package programs outside the browser. 143 - [License](https://arbori.cx/reference/LICENSE.txt): source and documentation licensing. 144 145 Start with the playground guide, then fetch the relevant library release pages. Static references are available without executing WebAssembly. 146 ''') 147 148 149 if __name__ == '__main__': 150 parser = argparse.ArgumentParser(description=__doc__) 151 parser.add_argument('--root', type=Path, required=True) 152 parser.add_argument('--output', type=Path, required=True) 153 args = parser.parse_args() 154 generate(args.root.resolve(), args.output.resolve())