reglem 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- reglem-0.1.0/.github/workflows/ci.yml +29 -0
- reglem-0.1.0/.github/workflows/publish.yml +43 -0
- reglem-0.1.0/.gitignore +14 -0
- reglem-0.1.0/.pre-commit-config.yaml +25 -0
- reglem-0.1.0/CHANGELOG.md +38 -0
- reglem-0.1.0/LICENSE +21 -0
- reglem-0.1.0/PKG-INFO +154 -0
- reglem-0.1.0/README.md +128 -0
- reglem-0.1.0/docs/greek.md +119 -0
- reglem-0.1.0/pyproject.toml +85 -0
- reglem-0.1.0/src/reglem/__init__.py +40 -0
- reglem-0.1.0/src/reglem/anki.py +23 -0
- reglem-0.1.0/src/reglem/build.py +103 -0
- reglem-0.1.0/src/reglem/cli.py +146 -0
- reglem-0.1.0/src/reglem/errors.py +23 -0
- reglem-0.1.0/src/reglem/languages/__init__.py +29 -0
- reglem-0.1.0/src/reglem/languages/_base.py +34 -0
- reglem-0.1.0/src/reglem/languages/greek.py +144 -0
- reglem-0.1.0/src/reglem/normalize.py +48 -0
- reglem-0.1.0/src/reglem/options.py +71 -0
- reglem-0.1.0/src/reglem/py.typed +0 -0
- reglem-0.1.0/src/reglem/variants.py +32 -0
- reglem-0.1.0/tests/test_anki.py +60 -0
- reglem-0.1.0/tests/test_build.py +141 -0
- reglem-0.1.0/tests/test_cli.py +124 -0
- reglem-0.1.0/tests/test_greek.py +133 -0
- reglem-0.1.0/tests/test_languages_registry.py +22 -0
- reglem-0.1.0/tests/test_normalize.py +47 -0
- reglem-0.1.0/tests/test_options.py +49 -0
- reglem-0.1.0/tests/test_properties.py +44 -0
- reglem-0.1.0/tests/test_variants.py +41 -0
- reglem-0.1.0/uv.lock +787 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Install uv
|
|
18
|
+
uses: astral-sh/setup-uv@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python-version }}
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: uv sync --all-extras --dev
|
|
24
|
+
|
|
25
|
+
- name: Run pre-commit
|
|
26
|
+
run: uv run pre-commit run --all-files
|
|
27
|
+
|
|
28
|
+
- name: Run tests
|
|
29
|
+
run: uv run pytest
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- name: Install uv
|
|
15
|
+
uses: astral-sh/setup-uv@v5
|
|
16
|
+
|
|
17
|
+
- name: Build
|
|
18
|
+
run: uv build
|
|
19
|
+
|
|
20
|
+
- name: Check package
|
|
21
|
+
run: uvx twine check dist/*
|
|
22
|
+
|
|
23
|
+
- name: Upload build artifacts
|
|
24
|
+
uses: actions/upload-artifact@v4
|
|
25
|
+
with:
|
|
26
|
+
name: dist
|
|
27
|
+
path: dist/
|
|
28
|
+
|
|
29
|
+
publish:
|
|
30
|
+
needs: build
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
environment: pypi
|
|
33
|
+
permissions:
|
|
34
|
+
id-token: write # PyPI Trusted Publishing (OIDC) -- no stored token needed
|
|
35
|
+
steps:
|
|
36
|
+
- name: Download build artifacts
|
|
37
|
+
uses: actions/download-artifact@v4
|
|
38
|
+
with:
|
|
39
|
+
name: dist
|
|
40
|
+
path: dist/
|
|
41
|
+
|
|
42
|
+
- name: Publish to PyPI
|
|
43
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
reglem-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v5.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: check-yaml
|
|
6
|
+
- id: check-toml
|
|
7
|
+
- id: check-added-large-files
|
|
8
|
+
- id: end-of-file-fixer
|
|
9
|
+
- id: trailing-whitespace
|
|
10
|
+
|
|
11
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
12
|
+
rev: v0.15.0
|
|
13
|
+
hooks:
|
|
14
|
+
- id: ruff-check
|
|
15
|
+
args: [--fix]
|
|
16
|
+
- id: ruff-format
|
|
17
|
+
|
|
18
|
+
- repo: local
|
|
19
|
+
hooks:
|
|
20
|
+
- id: basedpyright
|
|
21
|
+
name: basedpyright
|
|
22
|
+
entry: uv run basedpyright
|
|
23
|
+
language: system
|
|
24
|
+
types: [python]
|
|
25
|
+
pass_filenames: false
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
4
|
+
Versioning: [Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-08-31
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Core:
|
|
13
|
+
`build_pattern` / `build_anki_search`,
|
|
14
|
+
lemma normalization,
|
|
15
|
+
per-language spelling-variant expansion,
|
|
16
|
+
Greek macron table.
|
|
17
|
+
- Core:
|
|
18
|
+
per-lemma terminator exceptions (`Language.excluded_terminators`),
|
|
19
|
+
and a Greek exception denying space/NBSP right after any of the 19
|
|
20
|
+
definite-article forms, so `ὁ` no longer false-matches entries like
|
|
21
|
+
`ὁ σοφός, -ή, -όν` or `ἡ ἀρίστη`.
|
|
22
|
+
- `/` (slash) added to `DEFAULT_TERMINATORS`,
|
|
23
|
+
so slash-separated citations (`ὁ/ἡ/τό`, `ἀγαθός/ή/όν`) still match.
|
|
24
|
+
- CLI:
|
|
25
|
+
`reglem` console script
|
|
26
|
+
(file / stdin / `-w` input, `--macrons`, `--raw`, `--list-languages`).
|
|
27
|
+
- Packaging:
|
|
28
|
+
strict ruff + basedpyright,
|
|
29
|
+
pytest with coverage gate,
|
|
30
|
+
pre-commit,
|
|
31
|
+
CI,
|
|
32
|
+
PyPI Trusted Publishing workflow.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- A Greek definite-article lemma (`ὁ`, `ἡ`, `τό`, and the rest of the
|
|
37
|
+
paradigm) followed by a non-breaking space no longer matches —
|
|
38
|
+
NBSP is now excluded the same as a plain space for these lemmas.
|
reglem-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jaycrick
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to
|
|
7
|
+
deal in the Software without restriction, including without limitation the
|
|
8
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
9
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
20
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
21
|
+
IN THE SOFTWARE.
|
reglem-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: reglem
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Build anchored regex alternations over word lemmas, with optional per-language spelling variants.
|
|
5
|
+
Project-URL: Homepage, https://github.com/jaycrick/reglem
|
|
6
|
+
Project-URL: Issues, https://github.com/jaycrick/reglem/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/jaycrick/reglem/blob/main/CHANGELOG.md
|
|
8
|
+
Author-email: jaycrick <114450568+jaycrick@users.noreply.github.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: anki,greek,lemma,linguistics,regex
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: pydantic>=2.7
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# reglem
|
|
28
|
+
|
|
29
|
+
Turn list of word lemmas into one anchored regex alternation.
|
|
30
|
+
Optional per-language spelling variants (Greek macrons, for now).
|
|
31
|
+
Optional Anki `field:re:...` search-string wrapper.
|
|
32
|
+
|
|
33
|
+
Greek only today.
|
|
34
|
+
Language layer built so more languages drop in later without touching core.
|
|
35
|
+
|
|
36
|
+
## Why
|
|
37
|
+
|
|
38
|
+
Building `re:` search for Anki (or any regex-search tool) by hand from a word list is fiddly:
|
|
39
|
+
prefix leaks (`ox` matching `oxen`),
|
|
40
|
+
Rust `regex` crate has no lookahead so `(?=...)` breaks in Anki,
|
|
41
|
+
spelling variants (accents, long-vowel marks) multiply the work.
|
|
42
|
+
This package does that once, correctly, tested.
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uv add reglem
|
|
48
|
+
# or
|
|
49
|
+
pip install reglem
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Quickstart — library
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from reglem import build_anki_search, build_pattern, SearchOptions
|
|
56
|
+
|
|
57
|
+
# bare regex
|
|
58
|
+
build_pattern(["cat", "dog"])
|
|
59
|
+
# '^(dog|cat)([ ,. ]|$)'
|
|
60
|
+
|
|
61
|
+
# Anki search string
|
|
62
|
+
build_anki_search(["ὁ", "καί", "ἀγαθός"], SearchOptions(field="Greek", with_macrons=True))
|
|
63
|
+
# '"Greek:re:^(...)([ ,. ]|$)"'
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Paste that string into Anki's Browse search bar.
|
|
67
|
+
It matches notes whose field *starts* with one of the given lemmas,
|
|
68
|
+
followed by space, comma, period, or end of field.
|
|
69
|
+
|
|
70
|
+
## Quickstart — CLI
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
reglem lemmas.txt # Anki search string, one lemma per line in file
|
|
74
|
+
cat lemmas.txt | reglem - # same, from stdin
|
|
75
|
+
reglem -w ὁ -w καί -w ἀγαθός --macrons
|
|
76
|
+
reglem lemmas.txt --raw # bare regex, no Anki wrapper
|
|
77
|
+
reglem --list-languages
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`lemmas.txt`: one lemma per line, blank lines and `#` comments skipped.
|
|
81
|
+
|
|
82
|
+
## Flags
|
|
83
|
+
|
|
84
|
+
| flag | default | does |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `-w/--word LEMMA` | — | add one lemma; repeatable; overrides file/stdin |
|
|
87
|
+
| `-f/--field NAME` | `Greek` | Anki field name in output search string |
|
|
88
|
+
| `--macrons` | off | expand ambiguous-length vowels into macron alternatives |
|
|
89
|
+
| `--strip-trailing-digits` | off | strip trailing homograph digit (`lead2` → `lead`) before matching |
|
|
90
|
+
| `--terminators CHARS` | `" ,. /"` (space, comma, period, NBSP, slash) | chars allowed right after a matched lemma |
|
|
91
|
+
| `--raw` | off | print bare regex, skip the `"field:re:..."` wrapper |
|
|
92
|
+
| `--language NAME` | `greek` | which variant tables to use for `--macrons` |
|
|
93
|
+
| `--list-languages` | — | print known language names, exit |
|
|
94
|
+
|
|
95
|
+
## Why prefix-anchored, no lookahead
|
|
96
|
+
|
|
97
|
+
Anki's `re:` search uses the Rust `regex` crate (no lookaround support),
|
|
98
|
+
and field regexes are unanchored by default.
|
|
99
|
+
So a naive `word(?=[ ,])` breaks two ways:
|
|
100
|
+
it's invalid syntax in Anki,
|
|
101
|
+
and without `^` it'd match `word` inside `password` too.
|
|
102
|
+
reglem builds `^(alt1|alt2|...)([terminators]|$)` instead —
|
|
103
|
+
one alternation, explicit anchor, ordinary terminator class.
|
|
104
|
+
When a language excludes a terminator for a specific lemma (see Greek article,
|
|
105
|
+
below), that lemma gets its own terminator group in a separate branch instead —
|
|
106
|
+
still no lookahead.
|
|
107
|
+
|
|
108
|
+
## Greek macrons
|
|
109
|
+
|
|
110
|
+
See `docs/greek.md` for the full story —
|
|
111
|
+
why unmarked Greek text is vowel-length-ambiguous,
|
|
112
|
+
and how the macron expansion table is built.
|
|
113
|
+
|
|
114
|
+
## Greek article
|
|
115
|
+
|
|
116
|
+
A plain space isn't allowed right after any of the 19 forms of the Greek
|
|
117
|
+
definite article (`ὁ`, `ἡ`, `τό`, `τῶν`, `τούς`, ...): unlike an ordinary
|
|
118
|
+
headword, the article is never immediately followed by running Greek text in
|
|
119
|
+
its own dictionary entry — it's cited with punctuation instead, `ὁ, ἡ, τό` or
|
|
120
|
+
`ὁ/ἡ/τό`. Without the exception, `ὁ` as a lemma would match the start of
|
|
121
|
+
every entry that *begins* with the article, like `ὁ σοφός, -ή, -όν wise` or
|
|
122
|
+
`ἡ ἀρίστη`, which are entries for other words entirely.
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
build_pattern(["ὁ"])
|
|
126
|
+
# matches: "ὁ, ἡ, τό the" "ὁ/ἡ/τό" "ὁ" (end of field)
|
|
127
|
+
# doesn't match: "ὁ σοφός, -ή, -όν wise" "ἡ ἀρίστη"
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
This is why `/` is in `DEFAULT_TERMINATORS`: it's how a slash-separated
|
|
131
|
+
article citation (`ὁ/ἡ/τό`) still matches. See `docs/greek.md` for the full
|
|
132
|
+
list of forms and why grave-accented running-text forms (`τὸν`, `τὰς`, ...)
|
|
133
|
+
are intentionally excluded.
|
|
134
|
+
|
|
135
|
+
## Development
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
uv sync
|
|
139
|
+
uv run pytest # coverage gate: 95%
|
|
140
|
+
uv run ruff check .
|
|
141
|
+
uv run ruff format --check .
|
|
142
|
+
uv run basedpyright
|
|
143
|
+
uv run pre-commit run --all-files
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Publish
|
|
147
|
+
|
|
148
|
+
Tag `vX.Y.Z`, push tag.
|
|
149
|
+
CI builds, checks with `twine`, publishes via PyPI Trusted Publishing (OIDC) —
|
|
150
|
+
no stored token.
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT.
|
reglem-0.1.0/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# reglem
|
|
2
|
+
|
|
3
|
+
Turn list of word lemmas into one anchored regex alternation.
|
|
4
|
+
Optional per-language spelling variants (Greek macrons, for now).
|
|
5
|
+
Optional Anki `field:re:...` search-string wrapper.
|
|
6
|
+
|
|
7
|
+
Greek only today.
|
|
8
|
+
Language layer built so more languages drop in later without touching core.
|
|
9
|
+
|
|
10
|
+
## Why
|
|
11
|
+
|
|
12
|
+
Building `re:` search for Anki (or any regex-search tool) by hand from a word list is fiddly:
|
|
13
|
+
prefix leaks (`ox` matching `oxen`),
|
|
14
|
+
Rust `regex` crate has no lookahead so `(?=...)` breaks in Anki,
|
|
15
|
+
spelling variants (accents, long-vowel marks) multiply the work.
|
|
16
|
+
This package does that once, correctly, tested.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
uv add reglem
|
|
22
|
+
# or
|
|
23
|
+
pip install reglem
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quickstart — library
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from reglem import build_anki_search, build_pattern, SearchOptions
|
|
30
|
+
|
|
31
|
+
# bare regex
|
|
32
|
+
build_pattern(["cat", "dog"])
|
|
33
|
+
# '^(dog|cat)([ ,. ]|$)'
|
|
34
|
+
|
|
35
|
+
# Anki search string
|
|
36
|
+
build_anki_search(["ὁ", "καί", "ἀγαθός"], SearchOptions(field="Greek", with_macrons=True))
|
|
37
|
+
# '"Greek:re:^(...)([ ,. ]|$)"'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Paste that string into Anki's Browse search bar.
|
|
41
|
+
It matches notes whose field *starts* with one of the given lemmas,
|
|
42
|
+
followed by space, comma, period, or end of field.
|
|
43
|
+
|
|
44
|
+
## Quickstart — CLI
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
reglem lemmas.txt # Anki search string, one lemma per line in file
|
|
48
|
+
cat lemmas.txt | reglem - # same, from stdin
|
|
49
|
+
reglem -w ὁ -w καί -w ἀγαθός --macrons
|
|
50
|
+
reglem lemmas.txt --raw # bare regex, no Anki wrapper
|
|
51
|
+
reglem --list-languages
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`lemmas.txt`: one lemma per line, blank lines and `#` comments skipped.
|
|
55
|
+
|
|
56
|
+
## Flags
|
|
57
|
+
|
|
58
|
+
| flag | default | does |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| `-w/--word LEMMA` | — | add one lemma; repeatable; overrides file/stdin |
|
|
61
|
+
| `-f/--field NAME` | `Greek` | Anki field name in output search string |
|
|
62
|
+
| `--macrons` | off | expand ambiguous-length vowels into macron alternatives |
|
|
63
|
+
| `--strip-trailing-digits` | off | strip trailing homograph digit (`lead2` → `lead`) before matching |
|
|
64
|
+
| `--terminators CHARS` | `" ,. /"` (space, comma, period, NBSP, slash) | chars allowed right after a matched lemma |
|
|
65
|
+
| `--raw` | off | print bare regex, skip the `"field:re:..."` wrapper |
|
|
66
|
+
| `--language NAME` | `greek` | which variant tables to use for `--macrons` |
|
|
67
|
+
| `--list-languages` | — | print known language names, exit |
|
|
68
|
+
|
|
69
|
+
## Why prefix-anchored, no lookahead
|
|
70
|
+
|
|
71
|
+
Anki's `re:` search uses the Rust `regex` crate (no lookaround support),
|
|
72
|
+
and field regexes are unanchored by default.
|
|
73
|
+
So a naive `word(?=[ ,])` breaks two ways:
|
|
74
|
+
it's invalid syntax in Anki,
|
|
75
|
+
and without `^` it'd match `word` inside `password` too.
|
|
76
|
+
reglem builds `^(alt1|alt2|...)([terminators]|$)` instead —
|
|
77
|
+
one alternation, explicit anchor, ordinary terminator class.
|
|
78
|
+
When a language excludes a terminator for a specific lemma (see Greek article,
|
|
79
|
+
below), that lemma gets its own terminator group in a separate branch instead —
|
|
80
|
+
still no lookahead.
|
|
81
|
+
|
|
82
|
+
## Greek macrons
|
|
83
|
+
|
|
84
|
+
See `docs/greek.md` for the full story —
|
|
85
|
+
why unmarked Greek text is vowel-length-ambiguous,
|
|
86
|
+
and how the macron expansion table is built.
|
|
87
|
+
|
|
88
|
+
## Greek article
|
|
89
|
+
|
|
90
|
+
A plain space isn't allowed right after any of the 19 forms of the Greek
|
|
91
|
+
definite article (`ὁ`, `ἡ`, `τό`, `τῶν`, `τούς`, ...): unlike an ordinary
|
|
92
|
+
headword, the article is never immediately followed by running Greek text in
|
|
93
|
+
its own dictionary entry — it's cited with punctuation instead, `ὁ, ἡ, τό` or
|
|
94
|
+
`ὁ/ἡ/τό`. Without the exception, `ὁ` as a lemma would match the start of
|
|
95
|
+
every entry that *begins* with the article, like `ὁ σοφός, -ή, -όν wise` or
|
|
96
|
+
`ἡ ἀρίστη`, which are entries for other words entirely.
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
build_pattern(["ὁ"])
|
|
100
|
+
# matches: "ὁ, ἡ, τό the" "ὁ/ἡ/τό" "ὁ" (end of field)
|
|
101
|
+
# doesn't match: "ὁ σοφός, -ή, -όν wise" "ἡ ἀρίστη"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This is why `/` is in `DEFAULT_TERMINATORS`: it's how a slash-separated
|
|
105
|
+
article citation (`ὁ/ἡ/τό`) still matches. See `docs/greek.md` for the full
|
|
106
|
+
list of forms and why grave-accented running-text forms (`τὸν`, `τὰς`, ...)
|
|
107
|
+
are intentionally excluded.
|
|
108
|
+
|
|
109
|
+
## Development
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
uv sync
|
|
113
|
+
uv run pytest # coverage gate: 95%
|
|
114
|
+
uv run ruff check .
|
|
115
|
+
uv run ruff format --check .
|
|
116
|
+
uv run basedpyright
|
|
117
|
+
uv run pre-commit run --all-files
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Publish
|
|
121
|
+
|
|
122
|
+
Tag `vX.Y.Z`, push tag.
|
|
123
|
+
CI builds, checks with `twine`, publishes via PyPI Trusted Publishing (OIDC) —
|
|
124
|
+
no stored token.
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Greek macron expansion
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
Word lists that mark accent/breathing but not vowel length leave α, ι, υ ambiguous:
|
|
6
|
+
short or long, no way to tell from the text alone.
|
|
7
|
+
Pedagogical texts, by contrast,
|
|
8
|
+
often mark long instances of these three vowels with a macron
|
|
9
|
+
layered on top of whatever accent/breathing the vowel already carries.
|
|
10
|
+
Example:
|
|
11
|
+
a long alpha with smooth breathing appears as ᾱ̓ in a macron-marked text,
|
|
12
|
+
but as plain ἀ in a macron-less source.
|
|
13
|
+
|
|
14
|
+
`reglem`'s `--macrons` flag (`SearchOptions(with_macrons=True)` in the library)
|
|
15
|
+
expands each ambiguous vowel in a lemma into `(?:plain|macron)`,
|
|
16
|
+
so one search matches both spellings.
|
|
17
|
+
|
|
18
|
+
## Unicode detail
|
|
19
|
+
|
|
20
|
+
Precomposed macron-vowel characters exist: ᾱ ῑ ῡ.
|
|
21
|
+
Precomposed macron+breathing or macron+accent characters do NOT exist —
|
|
22
|
+
those are the macron-vowel codepoint followed by ordinary *combining* marks:
|
|
23
|
+
|
|
24
|
+
- U+0313 combining comma above (smooth breathing, psili)
|
|
25
|
+
- U+0314 combining reversed comma above (rough breathing, dasia)
|
|
26
|
+
- U+0301 combining acute accent (oxia)
|
|
27
|
+
- U+0300 combining grave accent (varia)
|
|
28
|
+
|
|
29
|
+
Order: breathing before accent.
|
|
30
|
+
Verified against Python `unicodedata`, both directions:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
>>> import unicodedata
|
|
34
|
+
>>> unicodedata.normalize("NFC", "ᾱ" + "̓" + "́") == "ᾱ" + "̓" + "́"
|
|
35
|
+
True # already canonical, NFC doesn't reorder/recompose further
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Also verified:
|
|
39
|
+
NFC always resolves the alpha/iota/upsilon + acute double-encoding
|
|
40
|
+
(polytonic U+1F71 vs. monotonic U+03AC "tonos") down to one codepoint.
|
|
41
|
+
Most Greek lemma sources give NFC text already,
|
|
42
|
+
so no dual-encoding to handle on the plain side.
|
|
43
|
+
|
|
44
|
+
## Circumflex excluded on purpose
|
|
45
|
+
|
|
46
|
+
Circumflex forms (ᾶ, ἆ, ἇ, ι/υ equivalents) are NOT in the macron table.
|
|
47
|
+
A circumflex accent can only sit on a long vowel —
|
|
48
|
+
already unambiguous, no separate macron form needed.
|
|
49
|
+
|
|
50
|
+
## Known simplification
|
|
51
|
+
|
|
52
|
+
The table maps every bare/accented/breathed α, ι, υ,
|
|
53
|
+
including ones that are actually the first or second vowel of a diphthong
|
|
54
|
+
(αι, αυ, ει, ευ, οι, ου, υι) —
|
|
55
|
+
positions where a macron would never really be written.
|
|
56
|
+
Effect:
|
|
57
|
+
a generated pattern gets a few extra alternation branches that can never match anything real.
|
|
58
|
+
Not incorrect, just slightly bigger than the minimum.
|
|
59
|
+
Reliable diphthong detection would be much more machinery
|
|
60
|
+
than the false-positive cost justifies.
|
|
61
|
+
|
|
62
|
+
## Table size
|
|
63
|
+
|
|
64
|
+
27 entries:
|
|
65
|
+
9 each for α, ι, υ
|
|
66
|
+
(bare, acute, grave, smooth, rough, smooth+acute, rough+acute, smooth+grave, rough+grave).
|
|
67
|
+
|
|
68
|
+
## Greek article: no space allowed
|
|
69
|
+
|
|
70
|
+
### Problem
|
|
71
|
+
|
|
72
|
+
A lemma pattern normally allows a plain space right after a matched lemma —
|
|
73
|
+
that's how `καί and, also` matches the lemma `καί`.
|
|
74
|
+
The Greek definite article breaks that assumption.
|
|
75
|
+
It's never immediately followed by running Greek text in its own dictionary entry;
|
|
76
|
+
it's cited on its own, joined to its other forms by punctuation:
|
|
77
|
+
`ὁ, ἡ, τό the` or `ὁ/ἡ/τό`.
|
|
78
|
+
In running text, though, it's immediately followed by the word it modifies:
|
|
79
|
+
`ὁ σοφός, -ή, -όν` (the wise man), `ἡ ἀρίστη` (the best woman).
|
|
80
|
+
|
|
81
|
+
Without an exception,
|
|
82
|
+
the lemma `ὁ` matches the start of every one of those unrelated entries too —
|
|
83
|
+
a large, silent source of false positives for a single-letter lemma.
|
|
84
|
+
|
|
85
|
+
### Fix
|
|
86
|
+
|
|
87
|
+
`reglem` denies a plain space, and a non-breaking space (U+00A0),
|
|
88
|
+
right after any of the 19 forms of the Greek definite article.
|
|
89
|
+
Comma, period, slash, and end-of-field are still allowed.
|
|
90
|
+
That's implemented as `Language.excluded_terminators`,
|
|
91
|
+
a per-lemma map of terminator characters to exclude —
|
|
92
|
+
see `build.py`'s module docstring for how the pattern splits into per-terminator
|
|
93
|
+
branches when this map applies.
|
|
94
|
+
|
|
95
|
+
NBSP is excluded for the same reason it's a default terminator at all:
|
|
96
|
+
it renders as an invisible space in the Anki editor
|
|
97
|
+
while still separating words in field HTML.
|
|
98
|
+
|
|
99
|
+
### The 19 forms
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
ὁ ἡ τό
|
|
103
|
+
τοῦ τῆς τῷ τῇ τόν τήν
|
|
104
|
+
τώ τοῖν
|
|
105
|
+
οἱ αἱ τά τῶν τοῖς ταῖς τούς τάς
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Masculine, feminine, neuter; nominative, genitive, dative, accusative;
|
|
109
|
+
singular, dual, plural.
|
|
110
|
+
|
|
111
|
+
### Grave-accented forms excluded on purpose
|
|
112
|
+
|
|
113
|
+
Running text writes the article with a grave accent before another word —
|
|
114
|
+
`τὸν`, `τὴν`, `τοὺς`, `τὰς` —
|
|
115
|
+
where the citation form carries an acute (`τόν`, `τήν`, `τούς`, `τάς`).
|
|
116
|
+
A lemma list cites the acute form,
|
|
117
|
+
so there's nothing to exclude a terminator from for the grave spellings —
|
|
118
|
+
they're not in `ARTICLE_FORMS`.
|
|
119
|
+
Adding them would be a one-line extension if a source ever needs it.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "reglem"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Build anchored regex alternations over word lemmas, with optional per-language spelling variants."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [{ name = "jaycrick", email = "114450568+jaycrick@users.noreply.github.com" }]
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
keywords = ["regex", "lemma", "anki", "greek", "linguistics"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 3 - Alpha",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Topic :: Text Processing :: Linguistic",
|
|
22
|
+
"Typing :: Typed",
|
|
23
|
+
]
|
|
24
|
+
dependencies = ["pydantic>=2.7"]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/jaycrick/reglem"
|
|
28
|
+
Issues = "https://github.com/jaycrick/reglem/issues"
|
|
29
|
+
Changelog = "https://github.com/jaycrick/reglem/blob/main/CHANGELOG.md"
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
reglem = "reglem.cli:main"
|
|
33
|
+
|
|
34
|
+
[dependency-groups]
|
|
35
|
+
dev = [
|
|
36
|
+
"pytest>=8.0",
|
|
37
|
+
"pytest-cov>=5.0",
|
|
38
|
+
"hypothesis>=6.100",
|
|
39
|
+
"ruff>=0.15",
|
|
40
|
+
"basedpyright>=1.18",
|
|
41
|
+
"pre-commit>=3.7",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[build-system]
|
|
45
|
+
requires = ["hatchling"]
|
|
46
|
+
build-backend = "hatchling.build"
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.wheel]
|
|
49
|
+
packages = ["src/reglem"]
|
|
50
|
+
|
|
51
|
+
[tool.ruff]
|
|
52
|
+
line-length = 96
|
|
53
|
+
target-version = "py310"
|
|
54
|
+
|
|
55
|
+
[tool.ruff.lint]
|
|
56
|
+
select = ["ALL"]
|
|
57
|
+
ignore = [
|
|
58
|
+
"D203", # one-blank-line-before-class -- conflicts with D211
|
|
59
|
+
"D213", # multi-line-summary-second-line -- conflicts with D212
|
|
60
|
+
"COM812", # trailing comma -- formatter handles this
|
|
61
|
+
"CPY001", # copyright header -- not used in this project
|
|
62
|
+
"RUF001", # ambiguous-unicode-character-string -- constant false positive on Greek text
|
|
63
|
+
"RUF002", # same, in docstrings
|
|
64
|
+
"RUF003", # same, in comments
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
[tool.ruff.lint.per-file-ignores]
|
|
68
|
+
"tests/**" = ["S101", "PLR2004", "D", "INP001", "FBT001"]
|
|
69
|
+
"src/reglem/cli.py" = ["T201"] # CLI output is meant to print to stdout
|
|
70
|
+
|
|
71
|
+
[tool.ruff.lint.pydocstyle]
|
|
72
|
+
convention = "google"
|
|
73
|
+
|
|
74
|
+
[tool.basedpyright]
|
|
75
|
+
include = ["src", "tests"]
|
|
76
|
+
typeCheckingMode = "strict"
|
|
77
|
+
pythonVersion = "3.10"
|
|
78
|
+
reportMissingTypeStubs = true
|
|
79
|
+
|
|
80
|
+
[tool.pytest.ini_options]
|
|
81
|
+
addopts = "--strict-markers --strict-config --cov=reglem --cov-report=term-missing --cov-fail-under=95"
|
|
82
|
+
testpaths = ["tests"]
|
|
83
|
+
|
|
84
|
+
[tool.coverage.run]
|
|
85
|
+
source = ["src/reglem"]
|