OPEN SOURCE
tools/package-site-source.py
Read on this site and download the files you need.
Nichijoyaku was created using AI. A complete review by human biblical-language and biblical-studies specialists has not been established. Read alongside established translations and original-language resources.
← Back to the open-source guide
1"""Build the website's deterministic, allowlisted source ZIP and text browser.23Run from a complete monorepo checkout. The source archive deliberately excludes4private runtime archives and separately licensed reference corpora.5"""6import argparse7import hashlib8import importlib.util9import json10from pathlib import Path11import shutil12import tempfile13import zipfile1415SITE = Path(__file__).resolve().parents[1]16LIMIT = 25 * 1024 * 102417ARCHIVE = 'nichijoyaku-source.zip'18PREFIX = 'nichijoyaku-source'19BROWSER_SUFFIXES = {'.py': 'python', '.json': 'json', '.yaml': 'yaml', '.yml': 'yaml',20'.toml': 'toml', '.md': 'markdown'}212223def digest(data):24return hashlib.sha256(data).hexdigest()252627def browser_language(name):28path = Path(name)29if name in ('README.md', 'CONTRIBUTING.md', 'LICENSE'):30return 'markdown' if path.suffix == '.md' else 'text'31if path.parts[0] not in ('software', 'tools'):32return None33if path.name == 'LICENSE':34return 'text'35return BROWSER_SUFFIXES.get(path.suffix)363738def archive(source, destination):39"""Freeze an already allowlisted directory; no unreviewed tree is read here."""40destination.mkdir(parents=True, exist_ok=True)41code = destination / 'code'42if code.exists():43shutil.rmtree(code)44files = {}45browser = []46with zipfile.ZipFile(destination / ARCHIVE, 'w', compression=zipfile.ZIP_DEFLATED, compresslevel=9) as bundle:47for path in sorted(source.rglob('*')):48if path.is_symlink():49raise ValueError('Source package cannot contain symlinks')50if not path.is_file():51continue52name = path.relative_to(source).as_posix()53data = path.read_bytes()54files[name] = {'sha256': digest(data), 'bytes': len(data)}55info = zipfile.ZipInfo(f'{PREFIX}/{name}', date_time=(2026, 10, 5, 0, 0, 0))56info.create_system = 357info.external_attr = 0o100644 << 1658info.compress_type = zipfile.ZIP_DEFLATED59bundle.writestr(info, data, compresslevel=9)60language = browser_language(name)61if language:62data.decode('utf-8') # Fail rather than silently corrupt binary text.63if len(data) >= LIMIT:64raise ValueError('Browser source exceeds hosting file limit')65raw = code / (name + '.txt')66raw.parent.mkdir(parents=True, exist_ok=True)67raw.write_bytes(data)68browser.append({'file': name, 'url': '/downloads/source-v1/code/' + name + '.txt',69'language': language, **files[name]})70data = (destination / ARCHIVE).read_bytes()71if len(data) >= LIMIT:72raise ValueError('Source archive exceeds hosting file limit')73record = {'schema_version': 1, 'archive': {'file': ARCHIVE,74'url': '/downloads/source-v1/' + ARCHIVE, 'sha256': digest(data),75'bytes': len(data), 'file_count': len(files)}, 'files': files,76'browser_files': browser,77'scope': 'Reviewed source allowlist, edition 1, and retained public provenance; no private prompts or reference Bible corpora.'}78(destination / 'index.json').write_text(json.dumps(record, ensure_ascii=False, indent=2) + '\n')79return record808182def build(destination):83spec = importlib.util.spec_from_file_location('source_package', SITE / 'scripts/package-source.py')84package = importlib.util.module_from_spec(spec)85spec.loader.exec_module(package)86with tempfile.TemporaryDirectory() as temporary:87source = Path(temporary) / 'source'88package.package(source)89return archive(source, destination)909192if __name__ == '__main__':93parser = argparse.ArgumentParser(description=__doc__)94parser.add_argument('--destination', type=Path, default=SITE / 'public/downloads/source-v1')95record = build(parser.parse_args().destination)96print(json.dumps(record['archive']))97print('Browsable files:', len(record['browser_files']))98