Skip to content

Commit dd35dd2

Browse files
refactor: Migrate Python tools from bin/ to modern curation/ package (openvanilla#698)
* refactor: Migrate Python tools from bin/ to curation/ package structure This commit reorganizes 13+ years of Python dictionary tools (2012-2025) from flat bin/ directory into a modern package structure with centralized path configuration. Changes: - Rename bin/ → bin_legacy/ (preserve all historical tools) - Create curation/ package with organized submodules: - builders/: frequency_builder, phrase_deriver - compilers/: main_compiler, plain_bpmf_compiler, compiler_utils - validators/: score_validator - utils/: text_filter - Move CLI-only scripts to scripts/ directory - Add centralized path configuration via PROJECT_ROOT, CONFIG_FILE exports - Move textpool.rc to Data/ root - Update all documentation (AGENTS.md, README, algorithm.md) Benefits: - Modern Python package structure with proper organization - PEP-8 compliant naming conventions - Centralized path configuration (no more relative path calculations) - Installable via pip (pyproject.toml with console script entry points) - Clear separation: library modules vs CLI scripts - Zero code loss: all tools preserved in bin_legacy/ Historical Context: - 2012-08-06: cook.py created (replaced Ruby) - 2012-09-16: buildFreq.py created (replaced bash) - 2013-01-21: C version moved to subdirectory - 2024-03-15: derive_associated_phrases.py added (v2 system) - 2024-2025: Modern Python practices (argparse, Black, pathlib) Breaking Changes: - bin/ paths no longer work (use curation.* imports instead) - Old: bin/cook.py → New: curation.compilers.main_compiler - Configuration moved: bin/textpool.rc → textpool.rc Migration Guide: See Source/Data/bin_legacy/DEPRECATED.md for complete migration map, tool history, and usage instructions for legacy tools. Contributors: - Mengjuei Hsieh: Original Python implementation (2012-2013) - Lukhnos Liu: Modernization and v2 features (2024-2025) - zonble: Encoding audit tool (2024) Files changed: ~70 files - Moved: 30+ bin/ files → bin_legacy/ - Created: 12 curation/*.py modules, 3 scripts/*.py, bin_legacy/DEPRECATED.md - Updated: 5 documentation files, pyproject.toml, Makefile - Deleted: 1 duplicate file Build verified: ✅ - data.txt: 158,967 lines - data-plain-bpmf.txt: 15,189 lines - associated-phrases-v2.txt: 93,474 lines * fix: add main() wrapper to frequency_builder Wrap module-level code in main() function to fix mcbpmf-build-freq console script entry point defined in pyproject.toml. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: add main() wrapper to plain_bpmf_compiler Wrap module-level code in main() function to fix mcbpmf-compile-plain console script entry point defined in pyproject.toml. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: correct main() signature in phrase_deriver Change main() signature from accepting parameters to no arguments, moving argument parsing inside the function. This fixes the mcbpmf-derive-phrases console script entry point compatibility with setuptools entry point mechanism. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: add main() wrapper to main_compiler Add main() function wrapper to fix mcbpmf-compile console script entry point defined in pyproject.toml. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: fix console script names in README Update console script names to match pyproject.toml entry points and remove non-entry-point scripts from the list. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: update __init__ docstring to match structure Remove references to moved scripts (occurrence_counter, data_analyzer, bpmf_mapper) that are now in scripts/ directory. Update docstring to reflect actual package organization. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: fix code example in AGENTS.md Make code example valid and syntactically correct by providing a complete, working implementation without undefined references. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: simplify DEPRECATED.md maintenance info Remove timestamp columns (Created, Last Updated) to reduce maintenance burden. Keep only essential migration mapping information as suggested by maintainer review. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: remove emojis from markdown files Remove all emojis from AGENTS.md and DEPRECATED.md to comply with coding convention defined in AGENTS.md:128 which prohibits emoji use in documentation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7e10804 commit dd35dd2

67 files changed

Lines changed: 2720 additions & 941 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Source/Data/AGENTS.md

Lines changed: 242 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -161,20 +161,252 @@ Emojis/symbols are allowed but **MUST NOT be the default candidate**. Add to `Sy
161161
- Frequencies must be non-negative integers (0 acceptable, negatives NOT allowed)
162162
- All files must be C locale sorted for binary search compatibility
163163

164-
## Python Tools in bin/
164+
## Python Package Structure
165+
166+
The `curation/` package contains library modules organized into submodules:
167+
168+
| Submodule | Purpose |
169+
|-----------|---------|
170+
| `curation.builders` | Data building and processing tools (frequency_builder, phrase_deriver) |
171+
| `curation.compilers` | Data compilation tools (main_compiler, plain_bpmf_compiler) |
172+
| `curation.validators` | Validation and analysis tools (score_validator) |
173+
| `curation.utils` | General utilities (text_filter) |
174+
175+
Scripts with side effects are located in `scripts/`:
165176

166177
| Script | Purpose |
167178
|--------|---------|
168-
| `cook.py` | Main build script: combines all source files into `data.txt` |
169-
| `cook-plain-bpmf.py` | Builds `data-plain-bpmf.txt` for traditional Bopomofo mode |
170-
| `buildFreq.py` | Generates `PhraseFreq.txt` from `phrase.occ` and `exclusion.txt` |
171-
| `derive_associated_phrases.py` | Generates associated phrase suggestions from `data.txt` |
172-
| `bpmfmap.py` | Helper for automatic Bopomofo mapping |
173-
| `count.occurrence.py` | Counts phrase occurrences in text corpus |
174-
| `self-score-test.py` | Validates and scores dictionary data quality |
175-
| `nonCJK_filter.py` | Filters out non-CJK characters |
176-
| `audit_encoding.swift` | Checks file encoding issues |
179+
| `count_occurrences.py` | Counts phrase occurrences in text corpus |
180+
| `analyze_data.py` | Analyzes dictionary data and generates reports |
181+
| `map_bpmf.py` | Helper for automatic Bopomofo mapping |
182+
183+
### Python Development Guidelines
184+
185+
**CRITICAL RULES for AI coding assistants:**
186+
187+
#### 1. Module Organization Rules
188+
189+
**Library modules** (in `curation/` package) **MUST NOT** have side effects at module level:
190+
- **PROHIBITED**: Opening files, reading/writing data, printing output at module level
191+
- **PROHIBITED**: Executing code immediately when module is imported
192+
- **REQUIRED**: All initialization must be in functions
193+
- **REQUIRED**: Module must be importable without executing code
194+
195+
**Example of BAD module (violates rules):**
196+
```python
197+
# BAD: Has side effects at import time
198+
import configparser
199+
config = configparser.ConfigParser()
200+
config.read('config.ini') # WRONG: Reads file at import!
201+
corpus = open('corpus.txt').read() # WRONG: Opens file at import!
202+
```
203+
204+
**Example of GOOD module:**
205+
```python
206+
# GOOD: No side effects, importable as library
207+
import configparser
208+
209+
def load_config(config_path='config.ini'):
210+
"""Load configuration from file."""
211+
config = configparser.ConfigParser()
212+
config.read(config_path)
213+
return config
214+
215+
def main():
216+
"""Main entry point for CLI usage."""
217+
config = load_config()
218+
# ... rest of logic
219+
220+
if __name__ == '__main__':
221+
main()
222+
```
223+
224+
#### 2. Import Guidelines
225+
226+
- **REQUIRED**: All imports at top of file
227+
- **NEVER** use inline imports (unless explicitly necessary for specific technical reasons)
228+
- Use relative imports within package (e.g., `from .compiler_utils import HEADER`)
229+
- Use absolute imports for external packages (e.g., `import argparse`)
230+
231+
**Example of BAD imports:**
232+
```python
233+
# BAD: Inline import
234+
def process_data():
235+
import pandas as pd # WRONG: NEVER do this!
236+
return pd.DataFrame(data)
237+
```
238+
239+
**Example of GOOD imports:**
240+
```python
241+
# GOOD: All imports at top
242+
import argparse
243+
import sys
244+
from typing import List, Dict
245+
from .compiler_utils import HEADER
246+
247+
def process_data(input_data):
248+
result = []
249+
for item in input_data:
250+
result.append(item.upper())
251+
return result
252+
```
253+
254+
#### 3. Side Effect Management
255+
256+
**Scripts with side effects belong in `scripts/` directory, NOT in `curation/` package.**
257+
258+
Scripts that do any of the following must be in `scripts/`:
259+
- Read configuration files at module level
260+
- Open and process data files at module level
261+
- Print output or generate reports at module level
262+
- Execute analysis immediately when imported
263+
264+
**When to use `scripts/` vs `curation/`:**
265+
266+
| Location | Purpose | Characteristics |
267+
|----------|---------|-----------------|
268+
| `scripts/` | Pure CLI tools | Has side effects; not importable as library; immediate execution |
269+
| `curation/` | Library modules | No side effects; importable; reusable functions |
270+
271+
#### 4. Script vs Library Separation
272+
273+
**Library modules** in `curation/` should:
274+
- Provide reusable functions and classes
275+
- Have a `main()` function for CLI usage
276+
- Be declared in `pyproject.toml` `[project.scripts]`
277+
- Follow pattern: `mcbpmf-tool-name = "curation.module:main"`
278+
279+
**Scripts** in `scripts/` should:
280+
- Be standalone executables
281+
- Have all logic in `if __name__ == '__main__':` block
282+
- Be called directly: `python3 scripts/script_name.py`
283+
- NOT be imported by other modules
284+
285+
#### 5. PEP-8 Naming Conventions
286+
287+
- Module names: `lowercase_with_underscores.py`
288+
- Function names: `lowercase_with_underscores()`
289+
- Class names: `CapitalizedWords`
290+
- Constants: `UPPERCASE_WITH_UNDERSCORES`
291+
292+
**Examples:**
293+
- GOOD: `frequency_builder.py`, `main_compiler.py`, `text_filter.py`
294+
- BAD: `buildFreq.py`, `nonCJK_filter.py`, `cook-plain-bpmf.py`
295+
296+
#### 6. Package Installation
297+
298+
Install as editable package for development:
299+
```bash
300+
pip install -e . # Install package
301+
pip install -e ".[dev]" # Install with dev dependencies
302+
pip install -e ".[notebook]" # Install with notebook dependencies
303+
```
304+
305+
After installation, use console scripts:
306+
```bash
307+
mcbpmf-build-freq # Instead of: python3 -m curation.builders.frequency_builder
308+
mcbpmf-compile # Instead of: python3 -m curation.compilers.main_compiler
309+
mcbpmf-validate-scores # Instead of: python3 -m curation.validators.score_validator
310+
```
311+
312+
## Project Path Configuration
313+
314+
All scripts and modules use centralized path constants from the `curation` package:
315+
316+
```python
317+
from curation import PROJECT_ROOT, CONFIG_FILE
318+
319+
# PROJECT_ROOT = Source/Data/ directory (where pyproject.toml lives)
320+
# CONFIG_FILE = Source/Data/textpool.rc
321+
322+
# Example usage in scripts
323+
config = configparser.ConfigParser()
324+
config.read(CONFIG_FILE)
325+
corpus_path = Path(config.get('data', 'corpus_path')).expanduser()
326+
```
327+
328+
**Do NOT** compute paths relatively in individual scripts:
329+
- BAD: `Path(__file__).parent.parent`
330+
- BAD: `os.path.abspath(sys.argv[0]).split('/')`
331+
- GOOD: `from curation import PROJECT_ROOT`
332+
333+
This ensures:
334+
- Single source of truth for project structure
335+
- Easy refactoring if directory structure changes
336+
- Consistent behavior across all tools
337+
338+
## Historical Context: Tool Evolution (2012-2025)
339+
340+
### Migration from bin/ to curation/ (October 2024)
341+
342+
Prior to October 2024, all Python tools were located in the `bin/` directory (now renamed to `bin_legacy/`). This directory accumulated tools over 13+ years with contributions from multiple developers.
343+
344+
#### Tool Creation Timeline
345+
346+
- **2012-08-06**: `cook.py` created by Mengjuei Hsieh, replacing Ruby implementation
347+
- **2012-09-16**: `buildFreq.py` created, replacing bash version
348+
- **2013-01-02**: `self-score-test.py` added for quality validation
349+
- **2013-01-21**: C version moved to `C_Version/` subdirectory ("phasing out")
350+
- **2024-03-15**: `derive_associated_phrases.py` added by Lukhnos Liu (v2 system)
351+
- **2024-08-25**: `audit_encoding.swift` added by zonble
352+
- **2025-03-08**: `cook.py` modernized with Black formatting and argparse
353+
354+
#### Why Migration Was Needed
355+
356+
The bin/ structure had accumulated issues:
357+
1. Flat organization (~30 files, no logical grouping)
358+
2. Mixed concerns (library code, CLI scripts, config files, legacy tools)
359+
3. Inconsistent naming conventions
360+
4. Some modules had side effects at import time
361+
5. Not installable as proper Python package
362+
6. Each script calculated paths differently
363+
364+
#### What Was Migrated vs Preserved
365+
366+
**Migrated to `curation/` package** (actively used):
367+
- All compilation and build tools
368+
- Frequency calculation
369+
- Data validation
370+
- Text processing utilities
371+
372+
**Moved to `scripts/`** (CLI-only, with side effects):
373+
- Corpus occurrence counting
374+
- Data analysis reports
375+
- BPMF mapping helpers
376+
377+
**Preserved in `bin_legacy/`** (historical reference):
378+
- `audit_encoding.swift` - Still usable standalone tool (2024)
379+
- `C_Version/` - Fast C implementation, phased out in 2013
380+
- `Sample_Prep/` - Corpus preparation methodology
381+
- `disabled/` - Legacy Perl/Ruby/Bash implementations
382+
383+
#### Path Configuration Changed
384+
385+
- **Before**: Each script calculated paths relatively
386+
- **After**: Import from `curation` package: `from curation import PROJECT_ROOT, CONFIG_FILE`
387+
388+
#### Using Legacy Tools
389+
390+
The `audit_encoding.swift` tool is still functional:
391+
```bash
392+
cd bin_legacy
393+
swift audit_encoding.swift # Validates BPMFBase.txt encoding categories
394+
```
395+
396+
C version (for performance comparison):
397+
```bash
398+
cd bin_legacy/C_Version
399+
export TEXTPOOL=/path/to/corpus
400+
./count.bash 測試詞彙
401+
```
402+
403+
For complete migration history and tool details, see `bin_legacy/DEPRECATED.md`.
404+
405+
#### Key Contributors
177406

407+
- **Mengjuei Hsieh**: Original Python implementation (2012-2013)
408+
- **Lukhnos Liu**: Modernization and associated phrases v2 (2024-2025)
409+
- **zonble**: Encoding audit tool (2024)
178410

179411
## Data Generation Pipeline
180412

Source/Data/Makefile

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ all: data.txt data-plain-bpmf.txt associated-phrases-v2.txt
44

55
install: all
66

7-
data-plain-bpmf.txt: bin/cook-plain-bpmf.py BPMFBase.txt BPMFPunctuations.txt
8-
bin/cook-plain-bpmf.py BPMFBase.txt BPMFPunctuations.txt data-plain-bpmf.txt
7+
data-plain-bpmf.txt: curation/compilers/plain_bpmf_compiler.py BPMFBase.txt BPMFPunctuations.txt
8+
python3 -m curation.compilers.plain_bpmf_compiler BPMFBase.txt BPMFPunctuations.txt data-plain-bpmf.txt
99

10-
data.txt: bin/cook.py BPMFBase.txt BPMFMappings.txt BPMFPunctuations.txt \
10+
data.txt: curation/compilers/main_compiler.py BPMFBase.txt BPMFMappings.txt BPMFPunctuations.txt \
1111
PhraseFreq.txt phrase.occ Symbols.txt Macros.txt\
1212
heterophony1.list heterophony2.list heterophony3.list
13-
bin/cook.py \
13+
python3 -m curation.compilers.main_compiler \
1414
--heterophony1 heterophony1.list \
1515
--heterophony2 heterophony2.list \
1616
--heterophony3 heterophony3.list \
@@ -22,11 +22,11 @@ data.txt: bin/cook.py BPMFBase.txt BPMFMappings.txt BPMFPunctuations.txt \
2222
--macros Macros.txt \
2323
--output data.txt
2424

25-
associated-phrases-v2.txt: data.txt bin/derive_associated_phrases.py associated-punctuation.txt
26-
bin/derive_associated_phrases.py $< $@ associated-punctuation.txt
25+
associated-phrases-v2.txt: data.txt curation/builders/phrase_deriver.py associated-punctuation.txt
26+
python3 -m curation.builders.phrase_deriver $< $@ associated-punctuation.txt
2727

28-
PhraseFreq.txt: bin/buildFreq.py phrase.occ exclusion.txt
29-
bin/buildFreq.py
28+
PhraseFreq.txt: curation/builders/frequency_builder.py phrase.occ exclusion.txt
29+
python3 -m curation.builders.frequency_builder
3030

3131
clean:
3232
rm -f data.txt data-plain-bpmf.txt phrase.list
@@ -68,7 +68,7 @@ tidy:
6868
@sed -i '' -e 's/1/˙/g;s/2/ˊ/g;s/3/ˇ/g;s/4/ˋ/g' BPMFMappings.txt
6969

7070
_phrase.occ: phrase.list
71-
@bin/count.occurrence.py phrase.list > tmp && mv tmp phrase.occ
71+
@python3 scripts/count_occurrences.py phrase.list > tmp && mv tmp phrase.occ
7272

7373
phrase.list: BPMFBase.txt BPMFMappings.txt
7474
awk 'length($$1)<4{print $$1}' BPMFBase.txt > tmp
@@ -91,6 +91,6 @@ _mydebugbuild:
9191
-derivedDataPath build \
9292
build
9393
_mycodecheck:
94-
@for myfile in bin/*.py; do \
94+
@for myfile in curation/*/*.py scripts/*.py; do \
9595
pycodestyle --ignore=E501,E722,E701,E221,W605 $$myfile; \
9696
done

Source/Data/README

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,54 @@
1-
├── BPMFBase.txt Bopomofo Table for chars
2-
├── BPMFMappings.txt Bopomofo Table for 2-char words to 6-char words
3-
| Originally simplified from tsi.src of libtabe
4-
| (BSD Licensed) with my own and zonble's
5-
| modification.
6-
├── BPMFPunctuations.txt Punctuation table
7-
├── heterophony1.list table for char heterophony, first order
8-
├── heterophony2.list table for char heterophony, second order
9-
├── heterophony3.list table for char heterophony, third order
10-
├── phrase.occ table for appearance of a word in a text pool
11-
├── Makefile for conveniences for developers
12-
└── bin
13-
   ├── cook.py The tool to build the data.txt for McBopomofo
14-
   ├── buildFreq.bash The awk script for counting occurance
15-
   ├── bpmfmap.py mapping bpmf automatically and primitively
16-
   ├── cook-plain-bpmf.py build data file for tranditional bpmf ime
17-
   ├── count.bash a wrapper for a C-based counting facility
18-
   ├── count.occurrence.c a C-based counting facility for command line
19-
   └── count.occurrence.py a python-based counter for the whole list
1+
Dictionary Data Structure
2+
=========================
3+
4+
├── BPMFBase.txt Single character Bopomofo mappings
5+
├── BPMFMappings.txt Multi-character phrases (2-6 chars)
6+
│ Originally simplified from tsi.src of libtabe
7+
│ (BSD Licensed) with modifications
8+
├── BPMFPunctuations.txt Punctuation marks
9+
├── heterophony1.list Heterophony reading priorities (1st order)
10+
├── heterophony2.list Heterophony reading priorities (2nd order)
11+
├── heterophony3.list Heterophony reading priorities (3rd order)
12+
├── phrase.occ Phrase frequency/occurrence data
13+
├── exclusion.txt Phrase frequency exclusions
14+
├── Symbols.txt Special symbols (era names, etc.)
15+
├── Macros.txt Text macros (date/time)
16+
├── Makefile Build automation
17+
├── pyproject.toml Python package configuration
18+
├── textpool.rc Corpus counting configuration
19+
20+
├── curation/ Python package for data processing
21+
│ │ (exports PROJECT_ROOT, CONFIG_FILE for path resolution)
22+
│ ├── builders/ frequency_builder, phrase_deriver
23+
│ ├── compilers/ main_compiler, plain_bpmf_compiler, compiler_utils
24+
│ ├── utils/ text_filter
25+
│ ├── validators/ score_validator
26+
│ ├── notebooks/ Jupyter analysis notebooks
27+
│ └── requirements.txt Package dependencies
28+
29+
├── scripts/ Standalone CLI tools (with side effects)
30+
│ │ (imports paths from curation package)
31+
│ ├── count_occurrences.py Count phrase occurrences in corpus
32+
│ ├── analyze_data.py Analyze dictionary data quality
33+
│ └── map_bpmf.py Primitive BPMF mapping helper
34+
35+
├── tests/ Unit tests for curation package
36+
37+
└── bin_legacy/ DEPRECATED (Oct 2024): Historical tools (2012-2025)
38+
├── DEPRECATED.md Complete migration guide & tool history
39+
├── audit_encoding.swift Still usable encoding validator
40+
├── C_Version/ Fast C implementation (phased out 2013)
41+
├── Sample_Prep/ Historical corpus preparation methods
42+
└── disabled/ Legacy Perl/Ruby/Bash implementations
43+
44+
----- Build System -----
45+
Use Makefile targets (automatically uses new curation/ structure):
46+
make all # Build all data files
47+
make sort # Sort all data files
48+
make check # Validate data integrity
49+
make clean # Remove generated files
50+
51+
For detailed build pipeline, see AGENTS.md and ../algorithm.md
2052

2153
----- Editorial Rule -----
2254
* when in doubt, use google/yahoo search to confirm the rarity of the phrases
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)