| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110 |
- #!/usr/bin/env python3
- """
- Build an inlined variant of mnemonicode.py with the wordlist embedded as a
- Python literal instead of loaded from wordlist.json at runtime.
- Usage:
- python3 build_inline.py [source.py] [wordlist.json] [output.py]
- Defaults:
- source mnemonicode.py
- wordlist wordlist.json
- output mnemonicode_inline.py
- """
- import json
- import sys
- import textwrap
- from pathlib import Path
- COLS = 79 # target line length for the word list
- # Markers that delimit the section to replace in the source file.
- IMPORT_JSON = "import json\n"
- IMPORT_PATH = "from pathlib import Path\n"
- WORDLIST_LINE = "_WORDLIST_JSON = Path(__file__).parent / \"wordlist.json\"\n"
- LOAD_OLD = '''\
- @lru_cache(maxsize=1)
- def _load() -> tuple[list[str], dict[str, int], str]:
- data = json.loads(_WORDLIST_JSON.read_text())
- words: list[str] = data["words"]
- index: dict[str, int] = {w: i for i, w in enumerate(words)}
- return words, index, data["version"]
- '''
- LOAD_NEW_TMPL = '''\
- _WORDLIST: tuple[list[str], str] = (
- {words_literal},
- {version_literal},
- )
- @lru_cache(maxsize=1)
- def _load() -> tuple[list[str], dict[str, int], str]:
- words, version = _WORDLIST
- index: dict[str, int] = {{w: i for i, w in enumerate(words)}}
- return words, index, version
- '''
- def format_words(words: list[str]) -> str:
- """Format a list of words as a Python list literal, wrapped at COLS."""
- items = [repr(w) for w in words]
- joined = ", ".join(items)
- # Fits on one line?
- one_line = f" [{joined}]"
- if len(one_line) <= COLS:
- return one_line
- # Wrap into multiple lines indented by 4 spaces inside the outer 2-space indent.
- lines = []
- current = ""
- for item in items:
- candidate = f"{current}, {item}" if current else item
- if len(candidate) + 4 > COLS - 2: # account for " [" prefix on first line
- if current:
- lines.append(current)
- current = item
- else:
- current = candidate
- if current:
- lines.append(current)
- inner = ",\n ".join(lines)
- return f" [\n {inner},\n ]"
- def build(source: Path, wordlist: Path, output: Path) -> None:
- src = source.read_text()
- data = json.loads(wordlist.read_text())
- words: list[str] = data["words"]
- version: str = data["version"]
- # Remove imports that are only needed for file-based loading.
- src = src.replace(IMPORT_JSON, "")
- src = src.replace(IMPORT_PATH, "")
- src = src.replace(WORDLIST_LINE + "\n", "") # remove trailing blank line too
- # Replace _load().
- words_lit = format_words(words)
- version_lit = repr(version)
- load_new = LOAD_NEW_TMPL.format(
- words_literal=words_lit,
- version_literal=version_lit,
- )
- if LOAD_OLD not in src:
- sys.exit(f"error: could not find _load() definition in {source}")
- src = src.replace(LOAD_OLD, load_new)
- output.write_text(src)
- print(f"wrote {output} ({output.stat().st_size:,} bytes)")
- if __name__ == "__main__":
- args = sys.argv[1:]
- source = Path(args[0]) if len(args) > 0 else Path("mnemonicode.py")
- wordlist = Path(args[1]) if len(args) > 1 else Path("wordlist.json")
- output = Path(args[2]) if len(args) > 2 else Path("mnemonicode_inline.py")
- for p in (source, wordlist):
- if not p.exists():
- sys.exit(f"error: {p} not found")
- build(source, wordlist, output)
|