polytypo 1.0.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.
- polytypo-1.0.0/.gitignore +13 -0
- polytypo-1.0.0/LICENSE +21 -0
- polytypo-1.0.0/PKG-INFO +117 -0
- polytypo-1.0.0/README.md +85 -0
- polytypo-1.0.0/pyproject.toml +78 -0
- polytypo-1.0.0/src/polytypo/__init__.py +46 -0
- polytypo-1.0.0/src/polytypo/_data/locales/de-CH.json +77 -0
- polytypo-1.0.0/src/polytypo/_data/locales/de-DE.json +76 -0
- polytypo-1.0.0/src/polytypo/_data/locales/el.json +90 -0
- polytypo-1.0.0/src/polytypo/_data/locales/en-GB.json +115 -0
- polytypo-1.0.0/src/polytypo/_data/locales/en-US.json +133 -0
- polytypo-1.0.0/src/polytypo/_data/locales/fi.json +136 -0
- polytypo-1.0.0/src/polytypo/_data/locales/fr-CA.json +78 -0
- polytypo-1.0.0/src/polytypo/_data/locales/fr.json +84 -0
- polytypo-1.0.0/src/polytypo/_data/locales/registry.json +9 -0
- polytypo-1.0.0/src/polytypo/_data/locales/ru.json +112 -0
- polytypo-1.0.0/src/polytypo/_data/locales/sv.json +124 -0
- polytypo-1.0.0/src/polytypo/_data/rules/order.json +78 -0
- polytypo-1.0.0/src/polytypo/_engine/__init__.py +0 -0
- polytypo-1.0.0/src/polytypo/_engine/codepoints.py +20 -0
- polytypo-1.0.0/src/polytypo/_engine/edits.py +66 -0
- polytypo-1.0.0/src/polytypo/_engine/locale.py +114 -0
- polytypo-1.0.0/src/polytypo/_engine/mode_pipelines.py +45 -0
- polytypo-1.0.0/src/polytypo/_engine/pipeline.py +66 -0
- polytypo-1.0.0/src/polytypo/_engine/registry.py +61 -0
- polytypo-1.0.0/src/polytypo/_engine/rules/__init__.py +27 -0
- polytypo-1.0.0/src/polytypo/_engine/rules/_dash_shared.py +216 -0
- polytypo-1.0.0/src/polytypo/_engine/rules/_quote_ambiguity.py +143 -0
- polytypo-1.0.0/src/polytypo/_engine/rules/apostrophe.py +103 -0
- polytypo-1.0.0/src/polytypo/_engine/rules/dashes.py +106 -0
- polytypo-1.0.0/src/polytypo/_engine/rules/ellipsis.py +87 -0
- polytypo-1.0.0/src/polytypo/_engine/rules/hyphen.py +140 -0
- polytypo-1.0.0/src/polytypo/_engine/rules/nbsp.py +557 -0
- polytypo-1.0.0/src/polytypo/_engine/rules/quotes.py +294 -0
- polytypo-1.0.0/src/polytypo/_engine/rules/ranges.py +104 -0
- polytypo-1.0.0/src/polytypo/_engine/rules/spaces.py +124 -0
- polytypo-1.0.0/src/polytypo/_engine/rules/symbols.py +196 -0
- polytypo-1.0.0/src/polytypo/_engine/sentinels.py +27 -0
- polytypo-1.0.0/src/polytypo/_engine/span_runner.py +67 -0
- polytypo-1.0.0/src/polytypo/_engine/unicode.py +59 -0
- polytypo-1.0.0/src/polytypo/_modes/__init__.py +0 -0
- polytypo-1.0.0/src/polytypo/_modes/html.py +107 -0
- polytypo-1.0.0/src/polytypo/_modes/markdown.py +250 -0
- polytypo-1.0.0/src/polytypo/_modes/parse_error.py +25 -0
- polytypo-1.0.0/src/polytypo/_modes/spans.py +138 -0
- polytypo-1.0.0/src/polytypo/errors.py +22 -0
- polytypo-1.0.0/src/polytypo/html.py +18 -0
- polytypo-1.0.0/src/polytypo/markdown.py +23 -0
- polytypo-1.0.0/src/polytypo/py.typed +0 -0
- polytypo-1.0.0/src/polytypo/text.py +18 -0
- polytypo-1.0.0/tests/conformance/test_fixtures.py +114 -0
- polytypo-1.0.0/tests/engine/test_idempotency.py +186 -0
- polytypo-1.0.0/tests/modes/test_html.py +67 -0
- polytypo-1.0.0/tests/modes/test_markdown.py +94 -0
- polytypo-1.0.0/tests/packaging/test_packaging.py +85 -0
- polytypo-1.0.0/tests/test_rules/test_apostrophe.py +25 -0
- polytypo-1.0.0/tests/test_rules/test_dashes.py +29 -0
- polytypo-1.0.0/tests/test_rules/test_ellipsis.py +18 -0
- polytypo-1.0.0/tests/test_rules/test_hyphen.py +17 -0
- polytypo-1.0.0/tests/test_rules/test_nbsp.py +29 -0
- polytypo-1.0.0/tests/test_rules/test_quotes.py +20 -0
- polytypo-1.0.0/tests/test_rules/test_ranges.py +31 -0
- polytypo-1.0.0/tests/test_rules/test_spaces.py +44 -0
- polytypo-1.0.0/tests/test_rules/test_symbols.py +25 -0
- polytypo-1.0.0/vendor/polytypo-spec/README.md +15 -0
- polytypo-1.0.0/vendor/polytypo-spec/UNICODE +1 -0
- polytypo-1.0.0/vendor/polytypo-spec/VERSION +1 -0
- polytypo-1.0.0/vendor/polytypo-spec/fixtures/de-CH.json +501 -0
- polytypo-1.0.0/vendor/polytypo-spec/fixtures/de-DE.json +547 -0
- polytypo-1.0.0/vendor/polytypo-spec/fixtures/el.json +239 -0
- polytypo-1.0.0/vendor/polytypo-spec/fixtures/en-GB.json +1274 -0
- polytypo-1.0.0/vendor/polytypo-spec/fixtures/en-US.json +1807 -0
- polytypo-1.0.0/vendor/polytypo-spec/fixtures/fi.json +1306 -0
- polytypo-1.0.0/vendor/polytypo-spec/fixtures/fr-CA.json +268 -0
- polytypo-1.0.0/vendor/polytypo-spec/fixtures/fr.json +603 -0
- polytypo-1.0.0/vendor/polytypo-spec/fixtures/locale-resolution.json +209 -0
- polytypo-1.0.0/vendor/polytypo-spec/fixtures/ru.json +688 -0
- polytypo-1.0.0/vendor/polytypo-spec/fixtures/sv.json +1290 -0
- polytypo-1.0.0/vendor/polytypo-spec/locales/de-CH.json +77 -0
- polytypo-1.0.0/vendor/polytypo-spec/locales/de-DE.json +76 -0
- polytypo-1.0.0/vendor/polytypo-spec/locales/el.json +90 -0
- polytypo-1.0.0/vendor/polytypo-spec/locales/en-GB.json +115 -0
- polytypo-1.0.0/vendor/polytypo-spec/locales/en-US.json +133 -0
- polytypo-1.0.0/vendor/polytypo-spec/locales/fi.json +136 -0
- polytypo-1.0.0/vendor/polytypo-spec/locales/fr-CA.json +78 -0
- polytypo-1.0.0/vendor/polytypo-spec/locales/fr.json +84 -0
- polytypo-1.0.0/vendor/polytypo-spec/locales/registry.json +9 -0
- polytypo-1.0.0/vendor/polytypo-spec/locales/ru.json +112 -0
- polytypo-1.0.0/vendor/polytypo-spec/locales/sv.json +124 -0
- polytypo-1.0.0/vendor/polytypo-spec/rules/dashes.md +1238 -0
- polytypo-1.0.0/vendor/polytypo-spec/rules/order.json +78 -0
- polytypo-1.0.0/vendor/polytypo-spec/schema/fixtures.schema.json +79 -0
- polytypo-1.0.0/vendor/polytypo-spec/schema/locale.schema.json +235 -0
- polytypo-1.0.0/vendor/polytypo-spec/schema/registry.schema.json +29 -0
- polytypo-1.0.0/vendor/polytypo-spec/schema/resolution.schema.json +50 -0
polytypo-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Iurii Rogulia
|
|
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 deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
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 all
|
|
13
|
+
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 FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
polytypo-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: polytypo
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Multilingual microtypography: locale-correct quotes, dashes, ellipses and no-break spaces.
|
|
5
|
+
Project-URL: Homepage, https://polytypo.dev/
|
|
6
|
+
Project-URL: Repository, https://github.com/polytypo/polytypo-python
|
|
7
|
+
Project-URL: Spec / canonical, https://github.com/polytypo/polytypo
|
|
8
|
+
Project-URL: Issues, https://github.com/polytypo/polytypo-python/issues
|
|
9
|
+
Author: Iurii Rogulia
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: apostrophe,dashes,ellipsis,em-dash,html,i18n,l10n,locale,markdown,microtypography,punctuation,quotes,typography,unicode
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: tree-sitter-markdown>=0.5.1
|
|
23
|
+
Requires-Dist: tree-sitter>=0.23
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
26
|
+
Requires-Dist: hypothesis>=6.115; extra == 'dev'
|
|
27
|
+
Requires-Dist: jsonschema>=4.23; extra == 'dev'
|
|
28
|
+
Requires-Dist: mypy>=1.13; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8.3; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
<p align="center">
|
|
34
|
+
<img src="https://raw.githubusercontent.com/polytypo/polytypo/main/brand/logo/polytypo-lockup-stacked.svg" alt="polytypo" width="260">
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
<h1 align="center">polytypo</h1>
|
|
38
|
+
|
|
39
|
+
<p align="center">
|
|
40
|
+
<a href="https://pypi.org/project/polytypo/"><img src="https://img.shields.io/pypi/v/polytypo.svg" alt="PyPI version"></a>
|
|
41
|
+
<a href="https://github.com/polytypo/polytypo-python/actions/workflows/ci.yml"><img src="https://github.com/polytypo/polytypo-python/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
42
|
+
<a href="https://pypi.org/project/polytypo/"><img src="https://img.shields.io/pypi/dm/polytypo.svg" alt="PyPI downloads"></a>
|
|
43
|
+
<a href="LICENSE"><img src="https://img.shields.io/pypi/l/polytypo.svg" alt="License: MIT"></a>
|
|
44
|
+
</p>
|
|
45
|
+
|
|
46
|
+
<p align="center">
|
|
47
|
+
Locale-correct quotes, dashes, ellipses, apostrophes, symbols and no-break spaces —<br>
|
|
48
|
+
one portable spec, designed for byte-identical output across runtimes.
|
|
49
|
+
</p>
|
|
50
|
+
|
|
51
|
+
<p align="center">
|
|
52
|
+
<strong>Try it live, no install: <a href="https://polytypo.dev/">polytypo.dev</a></strong>
|
|
53
|
+
</p>
|
|
54
|
+
|
|
55
|
+
This is the Python implementation. The full spec — all locales, all rules, worked examples in
|
|
56
|
+
each — lives in [polytypo/polytypo](https://github.com/polytypo/polytypo). This runtime supports
|
|
57
|
+
the `text` and `html` modes fully, and `markdown` for the `commonmark` dialect only — `mdx`
|
|
58
|
+
raises `POLYTYPO_INVALID_DIALECT` (no MDX/JSX parser is available for Python; see
|
|
59
|
+
[Supported dialects](#supported-dialects)).
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
pip install polytypo
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from polytypo import transform
|
|
71
|
+
|
|
72
|
+
transform("She said, \"it's fine\" -- but I wasn't sure...", locale="en-US")
|
|
73
|
+
# She said, “it’s fine”—but I wasn’t sure…
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Same input, one locale changed — quotes, dash spacing and all follow the target locale, not a
|
|
77
|
+
single hardcoded style:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
transform('Sie sagte: "Alles gut" -- aber ich war mir nicht sicher...', locale="de-DE")
|
|
81
|
+
# Sie sagte: „Alles gut“ – aber ich war mir nicht sicher…
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
HTML and Markdown are first-class modes, not an afterthought — tags, attributes and fenced code
|
|
85
|
+
are left alone; only text content is touched:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from polytypo.html import transform
|
|
89
|
+
|
|
90
|
+
transform('<a title="test... wait">Wait... she said "go on."</a>', locale="en-US")
|
|
91
|
+
# <a title="test... wait">Wait… she said “go on.”</a>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`polytypo.text`, `polytypo.html` and `polytypo.markdown` each exclude the parser dependency the
|
|
95
|
+
other modes don't need (importing `polytypo.text` never imports `html.parser`-based span
|
|
96
|
+
extraction or tree-sitter). The aggregate `polytypo` module supports every mode via a `mode`
|
|
97
|
+
keyword, defaulting to `"text"`. `locale` has no default anywhere and must always be passed
|
|
98
|
+
explicitly — there is no silent fallback to English.
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
import polytypo
|
|
102
|
+
|
|
103
|
+
polytypo.transform("...", locale="fr", mode="markdown", dialect="commonmark")
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Supported dialects
|
|
107
|
+
|
|
108
|
+
`markdown` mode requires a `dialect` keyword, exactly as the spec requires (no default,
|
|
109
|
+
detection is forbidden). This runtime supports `dialect="commonmark"` (CommonMark plus GFM —
|
|
110
|
+
tables, strikethrough, task lists, autolink literals). `dialect="mdx"` is a real dialect the spec
|
|
111
|
+
names, but this runtime has no MDX/JSX parser for it and raises `POLYTYPO_INVALID_DIALECT`
|
|
112
|
+
immediately rather than silently mishandling it — a narrower, honest conformance claim, not a
|
|
113
|
+
port defect.
|
|
114
|
+
|
|
115
|
+
## Licence
|
|
116
|
+
|
|
117
|
+
MIT. See [LICENSE](LICENSE).
|
polytypo-1.0.0/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/polytypo/polytypo/main/brand/logo/polytypo-lockup-stacked.svg" alt="polytypo" width="260">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">polytypo</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://pypi.org/project/polytypo/"><img src="https://img.shields.io/pypi/v/polytypo.svg" alt="PyPI version"></a>
|
|
9
|
+
<a href="https://github.com/polytypo/polytypo-python/actions/workflows/ci.yml"><img src="https://github.com/polytypo/polytypo-python/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
10
|
+
<a href="https://pypi.org/project/polytypo/"><img src="https://img.shields.io/pypi/dm/polytypo.svg" alt="PyPI downloads"></a>
|
|
11
|
+
<a href="LICENSE"><img src="https://img.shields.io/pypi/l/polytypo.svg" alt="License: MIT"></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
Locale-correct quotes, dashes, ellipses, apostrophes, symbols and no-break spaces —<br>
|
|
16
|
+
one portable spec, designed for byte-identical output across runtimes.
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<strong>Try it live, no install: <a href="https://polytypo.dev/">polytypo.dev</a></strong>
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
This is the Python implementation. The full spec — all locales, all rules, worked examples in
|
|
24
|
+
each — lives in [polytypo/polytypo](https://github.com/polytypo/polytypo). This runtime supports
|
|
25
|
+
the `text` and `html` modes fully, and `markdown` for the `commonmark` dialect only — `mdx`
|
|
26
|
+
raises `POLYTYPO_INVALID_DIALECT` (no MDX/JSX parser is available for Python; see
|
|
27
|
+
[Supported dialects](#supported-dialects)).
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
pip install polytypo
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from polytypo import transform
|
|
39
|
+
|
|
40
|
+
transform("She said, \"it's fine\" -- but I wasn't sure...", locale="en-US")
|
|
41
|
+
# She said, “it’s fine”—but I wasn’t sure…
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Same input, one locale changed — quotes, dash spacing and all follow the target locale, not a
|
|
45
|
+
single hardcoded style:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
transform('Sie sagte: "Alles gut" -- aber ich war mir nicht sicher...', locale="de-DE")
|
|
49
|
+
# Sie sagte: „Alles gut“ – aber ich war mir nicht sicher…
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
HTML and Markdown are first-class modes, not an afterthought — tags, attributes and fenced code
|
|
53
|
+
are left alone; only text content is touched:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from polytypo.html import transform
|
|
57
|
+
|
|
58
|
+
transform('<a title="test... wait">Wait... she said "go on."</a>', locale="en-US")
|
|
59
|
+
# <a title="test... wait">Wait… she said “go on.”</a>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`polytypo.text`, `polytypo.html` and `polytypo.markdown` each exclude the parser dependency the
|
|
63
|
+
other modes don't need (importing `polytypo.text` never imports `html.parser`-based span
|
|
64
|
+
extraction or tree-sitter). The aggregate `polytypo` module supports every mode via a `mode`
|
|
65
|
+
keyword, defaulting to `"text"`. `locale` has no default anywhere and must always be passed
|
|
66
|
+
explicitly — there is no silent fallback to English.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import polytypo
|
|
70
|
+
|
|
71
|
+
polytypo.transform("...", locale="fr", mode="markdown", dialect="commonmark")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Supported dialects
|
|
75
|
+
|
|
76
|
+
`markdown` mode requires a `dialect` keyword, exactly as the spec requires (no default,
|
|
77
|
+
detection is forbidden). This runtime supports `dialect="commonmark"` (CommonMark plus GFM —
|
|
78
|
+
tables, strikethrough, task lists, autolink literals). `dialect="mdx"` is a real dialect the spec
|
|
79
|
+
names, but this runtime has no MDX/JSX parser for it and raises `POLYTYPO_INVALID_DIALECT`
|
|
80
|
+
immediately rather than silently mishandling it — a narrower, honest conformance claim, not a
|
|
81
|
+
port defect.
|
|
82
|
+
|
|
83
|
+
## Licence
|
|
84
|
+
|
|
85
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "polytypo"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Multilingual microtypography: locale-correct quotes, dashes, ellipses and no-break spaces."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{ name = "Iurii Rogulia" }]
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
keywords = [
|
|
15
|
+
"typography",
|
|
16
|
+
"microtypography",
|
|
17
|
+
"i18n",
|
|
18
|
+
"l10n",
|
|
19
|
+
"locale",
|
|
20
|
+
"quotes",
|
|
21
|
+
"dashes",
|
|
22
|
+
"em-dash",
|
|
23
|
+
"ellipsis",
|
|
24
|
+
"apostrophe",
|
|
25
|
+
"punctuation",
|
|
26
|
+
"unicode",
|
|
27
|
+
"markdown",
|
|
28
|
+
"html",
|
|
29
|
+
]
|
|
30
|
+
classifiers = [
|
|
31
|
+
"Development Status :: 5 - Production/Stable",
|
|
32
|
+
"Intended Audience :: Developers",
|
|
33
|
+
"Programming Language :: Python :: 3",
|
|
34
|
+
"Programming Language :: Python :: 3.10",
|
|
35
|
+
"Programming Language :: Python :: 3.11",
|
|
36
|
+
"Programming Language :: Python :: 3.12",
|
|
37
|
+
"Programming Language :: Python :: 3.13",
|
|
38
|
+
"Typing :: Typed",
|
|
39
|
+
]
|
|
40
|
+
dependencies = ["tree-sitter>=0.23", "tree-sitter-markdown>=0.5.1"]
|
|
41
|
+
|
|
42
|
+
[project.optional-dependencies]
|
|
43
|
+
dev = ["ruff>=0.8", "mypy>=1.13", "pytest>=8.3", "hypothesis>=6.115", "jsonschema>=4.23", "build>=1.2"]
|
|
44
|
+
|
|
45
|
+
[project.urls]
|
|
46
|
+
Homepage = "https://polytypo.dev/"
|
|
47
|
+
Repository = "https://github.com/polytypo/polytypo-python"
|
|
48
|
+
"Spec / canonical" = "https://github.com/polytypo/polytypo"
|
|
49
|
+
Issues = "https://github.com/polytypo/polytypo-python/issues"
|
|
50
|
+
|
|
51
|
+
[tool.hatch.build]
|
|
52
|
+
# src/polytypo/_data/ is generated by scripts/gen_data.py from vendor/polytypo-spec/ and is
|
|
53
|
+
# gitignored (never hand-edited, never committed) -- hatchling's default file selection is
|
|
54
|
+
# VCS-aware and silently excludes gitignored files from both the wheel and the sdist, so it must
|
|
55
|
+
# be force-included here as a build artifact. Caught by tests/packaging/test_packaging.py, which
|
|
56
|
+
# installs the real built wheel into a scratch venv: without this, `import polytypo` succeeds but
|
|
57
|
+
# every `transform()` call fails with `ModuleNotFoundError: No module named 'polytypo._data'`.
|
|
58
|
+
artifacts = ["src/polytypo/_data/**/*.json"]
|
|
59
|
+
|
|
60
|
+
[tool.hatch.build.targets.wheel]
|
|
61
|
+
packages = ["src/polytypo"]
|
|
62
|
+
|
|
63
|
+
[tool.hatch.build.targets.sdist]
|
|
64
|
+
include = ["src/polytypo", "vendor/polytypo-spec", "tests", "README.md", "LICENSE"]
|
|
65
|
+
|
|
66
|
+
[tool.ruff]
|
|
67
|
+
line-length = 100
|
|
68
|
+
target-version = "py310"
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint]
|
|
71
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
72
|
+
|
|
73
|
+
[tool.mypy]
|
|
74
|
+
strict = true
|
|
75
|
+
python_version = "3.10"
|
|
76
|
+
|
|
77
|
+
[tool.pytest.ini_options]
|
|
78
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""polytypo -- locale-correct quotes, dashes, ellipses and no-break spaces.
|
|
2
|
+
|
|
3
|
+
The aggregate entry point. Its module graph includes every mode's dependencies (`html.parser`
|
|
4
|
+
and tree-sitter): it is the only pipeline module that imports all three mode-specific ones.
|
|
5
|
+
`polytypo.text`, `polytypo.html` and `polytypo.markdown` each call their own mode-specific
|
|
6
|
+
pipeline directly and never import this module (mirrors polytypo-js's `polytypo`/`polytypo/text`
|
|
7
|
+
/`polytypo/html`/`polytypo/markdown` split)."""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from polytypo._engine.mode_pipelines import (
|
|
12
|
+
run_html_pipeline,
|
|
13
|
+
run_markdown_pipeline,
|
|
14
|
+
run_text_pipeline,
|
|
15
|
+
)
|
|
16
|
+
from polytypo.errors import POLYTYPO_INVALID_MODE, PolytypoError
|
|
17
|
+
|
|
18
|
+
__all__ = ["PolytypoError", "transform"]
|
|
19
|
+
|
|
20
|
+
__version__ = "0.0.0"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _resolve_mode(mode: str | None) -> str:
|
|
24
|
+
if mode is None or mode == "text":
|
|
25
|
+
return "text"
|
|
26
|
+
if mode in ("html", "markdown"):
|
|
27
|
+
return mode
|
|
28
|
+
raise PolytypoError(
|
|
29
|
+
POLYTYPO_INVALID_MODE, f'Unknown mode "{mode}". Expected "text", "html" or "markdown".'
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def transform(
|
|
34
|
+
input: str,
|
|
35
|
+
*,
|
|
36
|
+
locale: str,
|
|
37
|
+
mode: str | None = None,
|
|
38
|
+
dialect: str | None = None,
|
|
39
|
+
rules: dict[str, bool] | None = None,
|
|
40
|
+
) -> str:
|
|
41
|
+
resolved_mode = _resolve_mode(mode)
|
|
42
|
+
if resolved_mode == "text":
|
|
43
|
+
return run_text_pipeline(input, locale=locale, rules=rules)
|
|
44
|
+
if resolved_mode == "html":
|
|
45
|
+
return run_html_pipeline(input, locale=locale, rules=rules)
|
|
46
|
+
return run_markdown_pipeline(input, locale=locale, dialect=dialect, rules=rules)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"locale": "de-CH",
|
|
3
|
+
"name": "German (Switzerland)",
|
|
4
|
+
"quotes": {
|
|
5
|
+
"primary": {
|
|
6
|
+
"open": "«",
|
|
7
|
+
"close": "»",
|
|
8
|
+
"innerSpace": "none"
|
|
9
|
+
},
|
|
10
|
+
"secondary": {
|
|
11
|
+
"open": "‹",
|
|
12
|
+
"close": "›",
|
|
13
|
+
"innerSpace": "none"
|
|
14
|
+
},
|
|
15
|
+
"elisionIdioms": []
|
|
16
|
+
},
|
|
17
|
+
"dash": {
|
|
18
|
+
"parenthetical": "en-spaced",
|
|
19
|
+
"range": "en-tight"
|
|
20
|
+
},
|
|
21
|
+
"ellipsis": {
|
|
22
|
+
"abbreviatedAfterTerminal": false
|
|
23
|
+
},
|
|
24
|
+
"hyphen": {
|
|
25
|
+
"prefixes": [],
|
|
26
|
+
"suffixes": [],
|
|
27
|
+
"compounds": []
|
|
28
|
+
},
|
|
29
|
+
"nbsp": {
|
|
30
|
+
"beforePunctuation": [],
|
|
31
|
+
"narrowBeforePunctuation": [],
|
|
32
|
+
"afterShortWords": [],
|
|
33
|
+
"abbreviations": ["z. B.", "d. h.", "u. a.", "u. U.", "m. a. W.", "m. w. H."],
|
|
34
|
+
"beforeUnits": ["%", "‰", "°C", "km", "cm", "mm", "kg", "km/h", "kWh"],
|
|
35
|
+
"beforeNumber": ["Art.", "Abs.", "Ziff.", "Kap.", "S."],
|
|
36
|
+
"beforeWord": ["St."],
|
|
37
|
+
"afterSymbols": ["§", "§§"],
|
|
38
|
+
"initialBinding": "chain"
|
|
39
|
+
},
|
|
40
|
+
"sources": [
|
|
41
|
+
{
|
|
42
|
+
"rule": "quotes",
|
|
43
|
+
"cite": "Schweizerische Bundeskanzlei, Schreibweisungen, Rz. 201–203: zu schreiben sind die Guillemets « » und, für eine Anführung innerhalb einer Anführung, die halben Anführungszeichen ‹ ›; die in Deutschland übliche Schreibung » « ist für amtliche Texte nicht zulässig",
|
|
44
|
+
"url": "https://www.bk.admin.ch/dam/bk/de/dokumente/sprachdienste/sprachdienst_de/schreibweisungen.pdf.download.pdf/schreibweisungen.pdf",
|
|
45
|
+
"note": "Rz. 202 regelt ausdrücklich nur das Leerzeichen VOR dem Anführungszeichen und NACH dem Schlusszeichen; ein Zwischenraum innerhalb der Guillemets wird weder gefordert noch in einem der Beispiele gesetzt («Zukunft für Schweizer Fahrende»). Daher innerSpace = none."
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"rule": "dashes",
|
|
49
|
+
"cite": "Schweizerische Bundeskanzlei, Schreibweisungen, Rz. 231, 237, 238: der Gedankenstrich ist der Halbgeviertstrich –; vor und nach dem Gedankenstrich bei Nachträgen und paarigen Einschüben steht ein Leerzeichen",
|
|
50
|
+
"url": "https://www.bk.admin.ch/dam/bk/de/dokumente/sprachdienste/sprachdienst_de/schreibweisungen.pdf.download.pdf/schreibweisungen.pdf",
|
|
51
|
+
"note": "Rz. 236 hält ausdrücklich fest, dass der Geviertstrich (—) nicht zulässig ist."
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"rule": "dashes",
|
|
55
|
+
"cite": "Schweizerische Bundeskanzlei, Schreibweisungen, Rz. 234: der Gedankenstrich als Begriffszeichen für „bis“ steht ohne Leerzeichen — „16–17 Uhr“, „die Artikel 10–12“, „die Jahre 1939–1945“",
|
|
56
|
+
"url": "https://www.bk.admin.ch/dam/bk/de/dokumente/sprachdienste/sprachdienst_de/schreibweisungen.pdf.download.pdf/schreibweisungen.pdf"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"rule": "nbsp",
|
|
60
|
+
"cite": "Schweizerische Bundeskanzlei, Schreibweisungen, Rz. 251 (Festabstand hält Zusammengehöriges zusammen: Ziffer und Masseinheit, Abkürzung und Ortsname, Teile mehrgliedriger Abkürzungen, Gliederungseinheit und Ziffer — „20 km“, „St. Gallen“, „Artikel 35“), Rz. 438 (mehrgliedrige Abkürzungen: d. h. / z. B. / u. a. / u. U. / m. a. W. / m. w. H.), Rz. 549–550 (Festabstand zwischen Zahl und Einheit), Rz. 554 (Festabstand vor % und ‰)",
|
|
61
|
+
"url": "https://www.bk.admin.ch/dam/bk/de/dokumente/sprachdienste/sprachdienst_de/schreibweisungen.pdf.download.pdf/schreibweisungen.pdf",
|
|
62
|
+
"note": "initialBinding: \"chain\" (spec 0.6.0: das Feld hieß zuvor das boolesche bindInitials) ist eine Ableitung, keine wörtliche Weisung: Rz. 251 nennt „eine Abkürzung und ein Ortsname“ (St. Gallen) und Rz. 438 die Teile mehrgliedriger Abkürzungen; die Bindung Initiale–Nachname ist dieselbe Konstruktion, wird aber in den Schreibweisungen nicht ausdrücklich erwähnt. Keine Quelle belegt eine einzelne Initiale (\"single\"); \"chain\" folgt derselben Zwei-oder-mehr-Logik wie de-DE/ru. Die Einheitenliste ist bewusst kurz und enthält keine einbuchstabigen Einheitenzeichen. Der Schweizer Verzicht auf ß (ss statt ß, Amtliches Regelwerk § 25 E2) lässt sich im Schema nicht ausdrücken und ist deshalb hier nicht abgebildet."
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"rule": "nbsp",
|
|
66
|
+
"cite": "Schweizerische Bundeskanzlei, Schreibweisungen, Rz. 251 (Festabstand zwischen einer Gliederungseinheit eines Erlasses und der dazugehörigen Ziffer — Beispiele „Artikel 35“, „S. 17 f.“, „St. Gallen“), Rz. 438 (Entsprechendes gilt für die eingliedrige Abkürzung „St.“ für „Sankt“ in Ortsnamen: „St. Gallen“, „St. Moritz“), Rz. 439 (abschliessende Aufzählung der Begriffszeichen: Ziffern, %, ‰, Gedankenstrich, Schrägstrich, §, Währungszeichen, mathematische Zeichen, Einheitenzeichen), Rz. 440 (zwischen einem Begriffszeichen und der dazugehörigen Zahl steht ein Festabstand), Rz. 727 (in verknapptem Text werden die Gliederungseinheiten abgekürzt: „Kap., Art., Abs., Bst. (falsch: Buchst., lit.), Ziff.“), Rz. 730 (Nummerierung der Gliederungseinheiten)",
|
|
67
|
+
"url": "https://www.bk.admin.ch/dam/de/sd-web/YVHazXZRkqKn/schreibweisungen.pdf",
|
|
68
|
+
"note": "Wortlaut im PDF selbst geprüft. Rz. 439 und Rz. 727 entscheiden die Zuordnung von „Art.“: die Aufzählung der Begriffszeichen in Rz. 439 ist abschliessend und enthält „Art.“ nicht, Rz. 727 führt „Art.“ ausdrücklich als Abkürzung einer Gliederungseinheit. „Art.“ gehört damit zu beforeNumber und nicht zu afterSymbols, wo es zuvor stand; die Bindung an die folgende Zahl folgt aus Rz. 251. Aus derselben Liste fehlt nur „Bst.“, weil ihm ein Buchstabe folgt, keine Zahl („Bst. a“). „S.“ ist mit „S. 17 f.“ in Rz. 251 wörtlich belegt. „Nr.“ wurde ersatzlos entfernt: es steht weder in Rz. 439 noch überhaupt im Sachregister (unter N nur „NGO“, „Normen“, „Null“); die einzige Fundstelle ist Rz. 431, wo „Nr. (Nummer), Tarif-Nrn.“ als Beispiel für Deklinationsendungen dient und keine Abstandsregel ausspricht. afterSymbols war damit nachweislich falsch, und für beforeNumber fehlt der Beleg — nach docs/PLAN.md §6.1 zieht das die Streichung nach sich. Zurückkommen kann „Nr.“ mit einer Lesung der DIN 5008. beforeWord enthält nur „St.“: Rz. 438 nennt genau diesen Fall wörtlich, weitere Abkürzung-plus-Wort-Bindungen sind in den Schreibweisungen nicht als geschlossene Liste geregelt („Küssnacht a. R.“ ist eine mehrgliedrige Abkürzung, kein Präfix)."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"rule": "hyphen",
|
|
72
|
+
"cite": "Schweizerische Bundeskanzlei, Schreibweisungen, Rz. 223 und 224: der Bindestrich wird als Ergänzungsstrich für einen eingesparten Wortteil verwendet („Papierproduktion und -handel“); mit dem geschützten Ergänzungsstrich wird verhindert, dass der Ergänzungsstrich am Zeilenende auf der oberen Zeile bleibt, während der zugehörige zweite Wortbestandteil auf die nächste Zeile rutscht",
|
|
73
|
+
"url": "https://www.bk.admin.ch/dam/de/sd-web/YVHazXZRkqKn/schreibweisungen.pdf",
|
|
74
|
+
"note": "Beleg dafür, dass die drei Listen leer bleiben. Das Deutsche kennt keine geschlossene Liste von Morphemen mit unteilbarem Bindestrich; die Trennung am Bindestrich ist zulässig (Rz. 269–271 behandeln nur sinnentstellende Trennungen). Der einzige belegte Fall eines geschützten Bindestrichs ist der Ergänzungsstrich (Rz. 224), und der ist offen: er betrifft jedes beliebige Zweitglied nach „und -“ und lässt sich als literale Wortliste nicht abbilden."
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"locale": "de-DE",
|
|
3
|
+
"name": "German (Germany)",
|
|
4
|
+
"quotes": {
|
|
5
|
+
"primary": {
|
|
6
|
+
"open": "„",
|
|
7
|
+
"close": "“",
|
|
8
|
+
"innerSpace": "none"
|
|
9
|
+
},
|
|
10
|
+
"secondary": {
|
|
11
|
+
"open": "‚",
|
|
12
|
+
"close": "‘",
|
|
13
|
+
"innerSpace": "none"
|
|
14
|
+
},
|
|
15
|
+
"elisionIdioms": []
|
|
16
|
+
},
|
|
17
|
+
"dash": {
|
|
18
|
+
"parenthetical": "en-spaced",
|
|
19
|
+
"range": "en-tight"
|
|
20
|
+
},
|
|
21
|
+
"ellipsis": {
|
|
22
|
+
"abbreviatedAfterTerminal": false
|
|
23
|
+
},
|
|
24
|
+
"hyphen": {
|
|
25
|
+
"prefixes": [],
|
|
26
|
+
"suffixes": [],
|
|
27
|
+
"compounds": []
|
|
28
|
+
},
|
|
29
|
+
"nbsp": {
|
|
30
|
+
"beforePunctuation": [],
|
|
31
|
+
"narrowBeforePunctuation": [],
|
|
32
|
+
"afterShortWords": [],
|
|
33
|
+
"abbreviations": ["z. B.", "d. h.", "u. a.", "u. Ä.", "z. T.", "i. d. R."],
|
|
34
|
+
"beforeUnits": ["%", "‰", "€", "°C", "km", "cm", "mm", "kg", "km/h", "kWh"],
|
|
35
|
+
"beforeNumber": ["S."],
|
|
36
|
+
"beforeWord": ["St."],
|
|
37
|
+
"afterSymbols": ["§", "§§"],
|
|
38
|
+
"initialBinding": "chain"
|
|
39
|
+
},
|
|
40
|
+
"sources": [
|
|
41
|
+
{
|
|
42
|
+
"rule": "quotes",
|
|
43
|
+
"cite": "Duden, Rechtschreibregeln, „Anführungszeichen“, Regeln D 5 und D 12: Gänsefüßchen „…“ als Anführungszeichen, halbe Anführungszeichen ‚…‘ für eine Anführung innerhalb einer Anführung",
|
|
44
|
+
"url": "https://www.duden.de/sprachwissen/rechtschreibregeln/anfuehrungszeichen",
|
|
45
|
+
"note": "Deckungsgleich mit dem Amtlichen Regelwerk der deutschen Rechtschreibung, § 79 E2 (Anführung innerhalb einer Anführung durch halbe Anführungszeichen)."
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"rule": "dashes",
|
|
49
|
+
"cite": "Duden, Rechtschreibregeln, „Gedankenstrich“, Regel D 45: der Gedankenstrich (Halbgeviertstrich) steht mit Leerzeichen auf beiden Seiten beim Einschieben eines Zusatzes",
|
|
50
|
+
"url": "https://www.duden.de/sprachwissen/rechtschreibregeln/gedankenstrich"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"rule": "dashes",
|
|
54
|
+
"cite": "Schweizerische Bundeskanzlei, Schreibweisungen (Ausgabe 2013, aktualisiert), Rz. 234: der Gedankenstrich als Begriffszeichen für „bis“ steht ohne Leerzeichen — „die Jahre 1939–1945“",
|
|
55
|
+
"url": "https://www.bk.admin.ch/dam/bk/de/dokumente/sprachdienste/sprachdienst_de/schreibweisungen.pdf.download.pdf/schreibweisungen.pdf",
|
|
56
|
+
"note": "Für den Bis-Strich konnte keine gleichwertig präzise Duden-Onlineregel gefunden werden; die schweizerische Weisung formuliert dieselbe im gesamten deutschen Sprachraum übliche Regel und wird hier als überprüfbare Quelle angegeben."
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"rule": "nbsp",
|
|
60
|
+
"cite": "DIN 5008 (Schreib- und Gestaltungsregeln für die Text- und Informationsverarbeitung): geschütztes Leerzeichen zwischen den Teilen mehrgliedriger Abkürzungen („z. B.“, „d. h.“), bei Initialen („J. K. Rowling“) sowie zwischen Zahl und Einheit, Prozentzeichen und Paragrafenzeichen",
|
|
61
|
+
"note": "Die Norm selbst ist kostenpflichtig; die Regelinhalte wurden über die Duden-Regeln zu Abkürzungen (D 1) und über die von Duden selbst verwendete Schreibung „z. B.“ mit geschütztem Leerzeichen gegengeprüft. Der Einheitenliste liegt keine Normliste zugrunde: sie ist bewusst kurz gehalten und enthält keine einbuchstabigen Einheitenzeichen (m, g, l, s), weil diese ohne Kontextprüfung zu Falschtreffern führen."
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"rule": "nbsp",
|
|
65
|
+
"cite": "Schweizerische Bundeskanzlei, Schreibweisungen, Rz. 251: der Festabstand verhindert, dass Zusammengehöriges beim Zeilensprung auseinandergerissen wird — Beispiele „20 km“, „St. Gallen“, „Artikel 35“, „S. 17 f.“, „20. November“; Rz. 438: Entsprechendes gilt für die eingliedrige Abkürzung „St.“ für „Sankt“ in Ortsnamen („St. Gallen“, „St. Moritz“); Rz. 439: abschliessende Aufzählung der Begriffszeichen (Ziffern, %, ‰, Gedankenstrich, Schrägstrich, §, Währungszeichen, mathematische Zeichen, Einheitenzeichen)",
|
|
66
|
+
"url": "https://www.bk.admin.ch/dam/de/sd-web/YVHazXZRkqKn/schreibweisungen.pdf",
|
|
67
|
+
"note": "Wortlaut im PDF selbst geprüft. Für Deutschland regelt dieselbe Konstruktion die DIN 5008 (Schreib- und Gestaltungsregeln), die kostenpflichtig ist und deren Wortlaut hier nicht geprüft werden konnte; deshalb steht die schweizerische Weisung als überprüfbarer Beleg — dasselbe Vorgehen wie bei der Quelle zu Rz. 234 in diesem File. Die Übertragung auf de-DE ist insofern eine Ableitung, keine deutschlandspezifische Weisung. „S.“ ist mit „S. 17 f.“ wörtlich belegt und deckt die von docs/PLAN.md §7 geforderte Schreibung „S.“ + Zahl. „Nr.“ ist aus afterSymbols ersatzlos gestrichen und wurde NICHT nach beforeNumber übernommen: die Aufzählung der Begriffszeichen in Rz. 439 ist abschliessend und enthält „Nr.“ nicht (im Sachregister unter N stehen nur „NGO“, „Normen“, „Null“), der Duden führt „Nr.“ als Abkürzung, spricht aber weder im Wörterbucheintrag noch im Sprachratgeber zu Abkürzungen eine Abstandsregel aus. Damit ist afterSymbols nachweislich falsch und beforeNumber unbelegt; nach docs/PLAN.md §6.1 fährt ein Eintrag ohne deckende Quelle nicht mit. Zurückholen lässt sich „Nr.“ durch eine Lesung der DIN 5008 — die Zugehörigkeit zur Klasse allein genügt nicht, das wäre eine als Zitat ausgegebene Ableitung. Die von docs/PLAN.md §7 geforderte Schreibung „Nr.“ + Zahl bleibt damit vorerst offen."
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"rule": "hyphen",
|
|
71
|
+
"cite": "Schweizerische Bundeskanzlei, Schreibweisungen, Rz. 223 und 224: der Bindestrich wird als Ergänzungsstrich für einen eingesparten Wortteil verwendet („Papierproduktion und -handel“); mit dem geschützten Ergänzungsstrich wird verhindert, dass der Ergänzungsstrich am Zeilenende auf der oberen Zeile bleibt, während der zugehörige zweite Wortbestandteil auf die nächste Zeile rutscht",
|
|
72
|
+
"url": "https://www.bk.admin.ch/dam/de/sd-web/YVHazXZRkqKn/schreibweisungen.pdf",
|
|
73
|
+
"note": "Beleg dafür, dass die drei Listen leer bleiben. Das Deutsche kennt keine geschlossene Liste von Morphemen mit unteilbarem Bindestrich; die Trennung am Bindestrich ist zulässig. Der einzige belegte Fall eines geschützten Bindestrichs ist der Ergänzungsstrich (Rz. 224), und der ist offen: er betrifft jedes beliebige Zweitglied nach „und -“ und lässt sich als literale Wortliste nicht abbilden."
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"locale": "el",
|
|
3
|
+
"name": "Greek",
|
|
4
|
+
"quotes": {
|
|
5
|
+
"primary": {
|
|
6
|
+
"open": "«",
|
|
7
|
+
"close": "»",
|
|
8
|
+
"innerSpace": "none"
|
|
9
|
+
},
|
|
10
|
+
"secondary": {
|
|
11
|
+
"open": "“",
|
|
12
|
+
"close": "”",
|
|
13
|
+
"innerSpace": "none"
|
|
14
|
+
},
|
|
15
|
+
"elisionIdioms": []
|
|
16
|
+
},
|
|
17
|
+
"dash": {
|
|
18
|
+
"parenthetical": "none",
|
|
19
|
+
"range": "none"
|
|
20
|
+
},
|
|
21
|
+
"ellipsis": {
|
|
22
|
+
"abbreviatedAfterTerminal": false
|
|
23
|
+
},
|
|
24
|
+
"hyphen": {
|
|
25
|
+
"prefixes": [],
|
|
26
|
+
"suffixes": [],
|
|
27
|
+
"compounds": []
|
|
28
|
+
},
|
|
29
|
+
"nbsp": {
|
|
30
|
+
"beforePunctuation": [],
|
|
31
|
+
"narrowBeforePunctuation": [],
|
|
32
|
+
"afterShortWords": [],
|
|
33
|
+
"abbreviations": [],
|
|
34
|
+
"beforeUnits": [],
|
|
35
|
+
"beforeNumber": [],
|
|
36
|
+
"beforeWord": [],
|
|
37
|
+
"afterSymbols": [],
|
|
38
|
+
"initialBinding": "none"
|
|
39
|
+
},
|
|
40
|
+
"sources": [
|
|
41
|
+
{
|
|
42
|
+
"rule": "quotes",
|
|
43
|
+
"cite": "Υπηρεσία Εκδόσεων της Ευρωπαϊκής Ένωσης, Διοργανικό εγχειρίδιο σύνταξης κειμένων (ελληνική έκδοση 2011, τελευταία ενημέρωση 30.4.2012), Μέρος Τέταρτο «Συμβατικοί κανόνες για την ελληνική γλώσσα», §10.1.7 «Εισαγωγικά»: «Σε εισαγωγικά (στο ελληνικό κείμενο προτιμώνται τα διπλά γωνιώδη εισαγωγικά: « ») κλείνονται κυρίως λόγια ή παραθέματα που αναφέρονται αυτολεξεί. […] Στην περίπτωση που χρειάζονται εισαγωγικά μέσα σε κείμενο που είναι ήδη σε εισαγωγικά, τότε για τα εισαγωγικά αυτά χρησιμοποιούνται τα διπλά ανωφερή εισαγωγικά (“ ”), ενώ σε τρίτο επίπεδο εσωτερικά χρησιμοποιούνται τα μονά ανωφερή εισαγωγικά (‘ ’)»",
|
|
44
|
+
"url": "http://publications.europa.eu/resource/cellar/e774ea2a-ef84-4bf6-be92-c9ebebf91c1b.0015.03/DOC_2",
|
|
45
|
+
"note": "Primary U+00AB/U+00BB, secondary U+201C/U+201D. Part Four of this guide is a Greek-specific punctuation chapter, not the multilingual part — the distinction matters and is the reason this source is cited for Greek at all. innerSpace is \"none\": the §6.4 spacing table sets «xx» with an ordinary space only outside the guillemets, and the Greek chapter never asks for one inside. The third nesting level the source describes (U+2018/U+2019) is not expressible in locale.schema.json, which has primary and secondary only; that is a schema limit, not a gap in the source. Corroborated by ΥΠΕΠΘ/ΙΤΥΕ, Γραμματική Νέας Ελληνικής Γλώσσας Α΄–Γ΄ Γυμνασίου, §3.3 («Τα εισαγωγικά ( « » ) σημειώνονται…»), whose examples likewise carry no inner space. Retrieved 2026-08-15."
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"rule": "dashes",
|
|
49
|
+
"cite": "Διοργανικό εγχειρίδιο σύνταξης κειμένων (EL, 2011), §10.1.8 «Ενωτικό — Παύλα μεσαίου μεγέθους — Μεγάλη παύλα»: για τα αριθμητικά διαστήματα, «Παύλα μεσαίου μεγέθους ή μείον (–) […] γ) για να δηλώσει το διάστημα μεταξύ δύο ορίων (π.χ. άτομα ηλικίας 25–45 ετών) […] δ) στην αναφορά σε περιόδους τουλάχιστον δύο πλήρων ετών (π.χ. 1989–1991) […] Ωστόσο, και στην περίπτωση αυτή μπορεί να χρησιμοποιηθεί και ενωτικό». Ο ίδιος κανόνας για τα αριθμητικά διαστήματα εμφανίζεται ΔΥΟ ΦΟΡΕΣ στην §10.1.8, μία στην υποενότητα «Ενωτικό (-)» και μία στην υποενότητα «Παύλα μεσαίου μεγέθους (–)», και κάθε φορά η υποενότητα παραχωρεί ρητά το άλλο σημείο (paraphrase of the «Ενωτικό» occurrence — its verbatim text was not captured at review time; the «Παύλα» occurrence is quoted above verbatim)· για την παρενθετική χρήση, «Όπως η παρένθεση, η διπλή παύλα δεν χωρίζεται με κενά διαστήματα από τη λέξη, φράση ή πρόταση που περικλείει· αντίθετα, μπαίνουν διαστήματα πριν από την πρώτη και μετά τη δεύτερη παύλα»",
|
|
50
|
+
"url": "http://publications.europa.eu/resource/cellar/e774ea2a-ef84-4bf6-be92-c9ebebf91c1b.0015.03/DOC_2",
|
|
51
|
+
"note": "Justifies \"none\" on BOTH fields, which no other locale has — but for TWO DIFFERENT REASONS, and conflating them would misrepresent the source. Range: genuinely ambivalent. The rule appears twice in §10.1.8, once under «Ενωτικό (-)» and once under «Παύλα μεσαίου μεγέθους (–)», and each occurrence explicitly concedes the other mark. That mutual concession is the proof — a source that names both forms in both places is not being vague, it is declining to rank them, and substituting either would express a preference the citation does not carry. Parenthetical: NOT ambivalent. The guide prescribes a U+2014 pair (its own header gives «Alt 0151») with ordinary spaces outside the pair and none on the inner edges. \"none\" here is forced by a SCHEMA LIMIT, not by the source: locale.schema.json's dash enum has no value for asymmetric spacing — \"em-spaced\" puts a space on each side of each dash, which is precisely what this sentence forbids. See spec/rules/dashes.md §6 «el», which states the two justifications separately. SOURCE CONFLICT, recorded and deliberately not settled: this guide prescribes U+2014 for the parenthetical dash, while ΥΠΕΠΘ/ΙΤΥΕ Γραμματική Α΄–Γ΄ Γυμνασίου §3.3 appears to set U+2013 in the same role. The sources array has no way to represent a conflict — it models agreement, not disagreement — so it is recorded here in prose. Resolving it is with the operator and needs a source that ranks the two, not a third that adds a form. Retrieved 2026-08-15."
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"rule": "ellipsis",
|
|
55
|
+
"cite": "Διοργανικό εγχειρίδιο σύνταξης κειμένων (EL, 2011), §10.1.9 «Αποσιωπητικά»: «Τα αποσιωπητικά, που είναι πάντοτε τρεις (και όχι περισσότερες) τελείες, χρησιμοποιούνται κυρίως: […]»· παρατήρηση ii): «Μεταξύ των αποσιωπητικών και της λέξης που προηγείται δεν αφήνουμε διάστημα»· παρατήρηση iii): «Όταν τα αποσιωπητικά βρίσκονται στο τέλος της περιόδου δεν προσθέτουμε τελεία»",
|
|
56
|
+
"url": "http://publications.europa.eu/resource/cellar/e774ea2a-ef84-4bf6-be92-c9ebebf91c1b.0015.03/DOC_2",
|
|
57
|
+
"note": "Justifies abbreviatedAfterTerminal = false. The source requires exactly three dots always and nowhere provides for the two-dot form after «?» or «!» that Russian uses, so the Russian branch of spec/rules/ellipsis.md must stay off for Greek. Corroborated by Γραμματική Α΄–Γ΄ Γυμνασίου §3.3: «Οι σημειούμενες τελείες είναι πάντα τρεις». Neither source gives any rule for αποσιωπητικά meeting the ερωτηματικό or the θαυμαστικό — checked specifically, in the Ministry of Education grammar and in the Κέντρο Ελληνικής Γλώσσας materials — so the engine's TERMINAL class is deliberately left as {U+0021, U+003F} and the Greek U+003B is not added to it (spaces.md §7.8; adding it would also regress Russian, where Лопатин §154 ties the two-dot form to «?» and «!» by name). Neither source addresses U+2026 versus three U+002E; that choice is the engine's, made identically in every locale, and is not claimed here. KNOWN DIVERGENCE, recorded because a verified source and the engine disagree and neither may stand unremarked: the same §10.1.9, παρατήρηση ii), states «Μεταξύ των αποσιωπητικών και της λέξης που προηγείται δεν αφήνουμε διάστημα» - no space between the ellipsis and the preceding word - and polytypo does NOT honour it. spec/rules/spaces.md §3.4 preserves a space before a dot run in EVERY locale, so «Πράγματι …» is returned as typed. The divergence is deliberate and argued in spaces.md §7.9 and ellipsis.md §6: honouring it needs either locale data in a rule that has none by design, or an ellipsis rule that deletes rather than replaces, which changes that rule's kind and forces a new composition argument. It would be revisited if a second locale wanted the same behaviour, at which point the shape is an ellipsis.noSpaceBefore flag consumed by the ellipsis rule. The source is right about Greek; the engine is declining to act on it, not disputing it. Retrieved 2026-08-15."
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"rule": "spaces",
|
|
61
|
+
"cite": "Διοργανικό εγχειρίδιο σύνταξης κειμένων (EL, 2011), §10.1.4 «Διπλή τελεία», παρατήρηση iv): «Στα ελληνικά, πριν από τη διπλή τελεία δεν πρέπει να υπάρχει διάστημα (πράγμα που συμβαίνει, π.χ., στα γαλλικά)»· §10.1.3 «Άνω τελεία», παρατηρήσεις ii) και iii): «Στα ελληνικά, πριν από την άνω τελεία δεν πρέπει να υπάρχει διάστημα»· «Μετά την άνω τελεία αρχίζουμε με μικρό γράμμα»",
|
|
62
|
+
"url": "http://publications.europa.eu/resource/cellar/e774ea2a-ef84-4bf6-be92-c9ebebf91c1b.0015.03/DOC_2",
|
|
63
|
+
"note": "Note the exact standing of this citation, because it is weaker than the claim it is often read as supporting. Neither passage is about the ερωτηματικό: they are about the colon and the άνω τελεία. What they establish is that Greek denies the French space-before-punctuation pattern, by name, for the marks they do cover. No source examined addresses spacing before the Greek question mark specifically, so the position is \"no source contradicts it\", NOT \"a source requires it\". Nothing rests on the difference: the Greek question mark is written U+003B, which spec/rules/spaces.md lists in STRIP-BEFORE for the Latin semicolon on its own merits, so the space is stripped under the Latin reading alone and no Greek-specific decision is being made (spaces.md §3.5 part 2). The §6.4 spacing table is deliberately NOT offered as corroboration here — the nbsp source in this same file rejects §6.4 as publisher house style rather than evidence about Greek, and it cannot be house style there and evidence here. Retrieved 2026-08-15."
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"rule": "nbsp",
|
|
67
|
+
"cite": "Διοργανικό εγχειρίδιο σύνταξης κειμένων (EL, 2011), §10.1.4 παρατήρηση iv) και §10.1.3 παρατήρηση ii) (ό.π., ρητή αντιπαραβολή με τα γαλλικά)· §10.6 «Συντομογραφίες», γενικός κανόνας γ): «Στις ελληνικές συντομογραφίες με τις οποίες συντέμνονται φράσεις που αποτελούνται από περισσότερες της μίας λέξεις μπαίνει κατά κανόνα τελεία έπειτα από κάθε συντεμνόμενη λέξη» (π.χ. κ.λπ., π.χ., πρβλ., κ.ο.κ.)",
|
|
68
|
+
"url": "http://publications.europa.eu/resource/cellar/e774ea2a-ef84-4bf6-be92-c9ebebf91c1b.0015.03/DOC_2",
|
|
69
|
+
"note": "Two empty lists closed by citation rather than by absence of one. beforePunctuation and narrowBeforePunctuation are empty because Greek takes neither U+00A0 nor U+202F before «;» «:» «!» «·» — the source denies the French practice by name. abbreviations is empty because Greek multi-word abbreviations appear to be written with no internal space at all (κ.λπ., not «κ. λπ.»), so there is no U+0020 for the rule to promote. INFERENCE, NOT CITATION: §10.6 γ) states only that a full stop follows each abbreviated word; it says nothing about spacing between them. The no-space form is visible in the guide's own printed examples and in no normative sentence examined. The list would be empty on the absence of a citation alone (docs/PLAN.md §6.1), so nothing rests on the inference — it is labelled because an unlabelled inference in a cite field is the failure mode this field exists to prevent. The remaining lists are empty because no Greek normative source examined states a binding: the one candidate, the §6.4 table binding a number to «%» and «°C», sits in Part Three — «Συμβατικοί κανόνες κοινοί για ΟΛΕΣ τις γλώσσες» — whose own preamble says it replaced diverging national rules for uniform presentation, which makes it publisher house style rather than evidence about Greek. A missing citation is never a licence to guess (docs/PLAN.md §6.1). Observed usage does not contradict that reading, which is worth recording because it was checked rather than assumed: a byte-level scan on 2026-08-15 of the Greek-language home pages of kathimerini.gr, kathimerini.gr/economy, tovima.gr, tanea.gr, efsyn.gr, in.gr, naftemporiki.gr, protagon.gr, lifo.gr, meteo.gr, kedros.gr, onassis.org/el and emst.gr found 183 number-plus-unit occurrences — 140 «%», 41 «°C», 2 «€» — and NOT ONE of them carried a space of any kind, no U+0020, no U+00A0. Greek web practice sets «50%» and «20°C» tight, so beforeUnits would have nothing to promote even if it were populated. Method, so the figure can be re-derived rather than taken on trust: fetch each page, strip script, style and noscript elements and then all tags, decode HTML entities (so that becomes the U+00A0 it denotes rather than disappearing), and count matches of a digit followed by an optional single space character from {U+0020, U+00A0, U+202F, U+2009} followed by the unit. Pages carrying under 200 Greek letters were excluded as not being Greek-language content. That scan is an observation of usage, is not offered as a normative claim, and nothing in this file depends on it: the lists would be empty on the absence of a citation alone. Retrieved 2026-08-15."
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"rule": "hyphen",
|
|
73
|
+
"cite": "Διοργανικό εγχειρίδιο σύνταξης κειμένων (EL, 2011), §10.1.8, υποενότητα «Ενωτικό (-)»: «Η μικρή οριζόντια παύλα, το βραχύτερο σε μήκος από τα τρία σημεία […] σημειώνεται χωρίς κενά σε σχέση με ό,τι προηγείται ή έπεται»",
|
|
74
|
+
"url": "http://publications.europa.eu/resource/cellar/e774ea2a-ef84-4bf6-be92-c9ebebf91c1b.0015.03/DOC_2",
|
|
75
|
+
"note": "Justifies all three lists being empty. The source describes what the hyphen does — syllable division, numeric bounds, appositional compounds such as «απόφαση-πλαίσιο» — without defining any closed list of morphological forms whose hyphen must resist a line break, which is the only thing this rule consumes. No Greek source examined defines one. Empty lists make the rule a provable total no-op for Greek (spec/rules/hyphen.md §2), which is the normal case, not a deficiency. Retrieved 2026-08-15."
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"rule": "apostrophe",
|
|
79
|
+
"cite": "ΥΠΕΠΘ / Ινστιτούτο Τεχνολογίας Υπολογιστών και Εκδόσεων, Γραμματική Νέας Ελληνικής Γλώσσας Α΄, Β΄, Γ΄ Γυμνασίου, §3.3 «Η στίξη — Τα σημεία στίξης»: «Η απόστροφος ( ’ ) χρησιμοποιείται για να δηλώσει ότι ένα φωνήεν έχει παραλειφθεί στη γραφή λόγω της προφοράς»· και, για την ταυτότητα του κωδικού σημείου, The Unicode Standard 17.0, Core Specification, §6.2.7 «Apostrophes»: «When text is set, U+2019 RIGHT SINGLE QUOTATION MARK is preferred as apostrophe, but only U+0027 is present on most keyboards»",
|
|
80
|
+
"url": "https://ebooks.edu.gr/ebooks/v/html/8547/2334/Grammatiki-Neas-Ellinikis-Glossas_A-B-G-Gymnasiou_html-apli/index_B_03.html",
|
|
81
|
+
"note": "Two sources for two separate facts. The Greek grammar establishes that elision and apocope (γι’ αυτό, απ’ την, σ’ αυτό) are marked with an apostrophe; the code point comes only from Unicode, because no Greek normative source examined names one. U+02BC is refused: Unicode §6.2.7 reserves it for use as a modifier letter, e.g. a glottal stop in transliteration. U+0384 GREEK TONOS is refused: no source proposes a diacritic in this role. The rule reads no locale data, so this citation certifies rather than configures — but Greek elision is the shape the rule most often meets in Greek text, and it earns a fixture. Retrieved 2026-08-15."
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"rule": "symbols",
|
|
85
|
+
"cite": "Unicode Consortium, The Unicode Standard, Version 17.0, Core Specification, ch. 7 «Europe-I», §7.2.1 Greek, «Compatibility Punctuation», verbatim: «Therefore, use of U+037E and U+0387 is not necessary for interoperating with legacy Greek data, and their use is not generally encouraged for representation of Greek punctuation.» The preceding sentence of that subsection — to the effect that the two characters have canonical equivalences to U+003B and U+00B7, so that normalised Greek text loses the distinction — is given here as PARAPHRASE, not as quotation: it could not be confirmed word for word at review time. Its substance does not rest on the prose in any case; it is entailed directly by UnicodeData.txt, where U+037E carries the canonical decomposition mapping 003B and U+0387 carries 00B7, each as a singleton",
|
|
86
|
+
"url": "https://www.unicode.org/versions/Unicode17.0.0/core-spec/chapter-7/",
|
|
87
|
+
"note": "Code-point identity only, which is the one thing this source is authoritative for. Note the split standing of the citation: one sentence is verbatim, one is paraphrase backed by UnicodeData.txt rather than by the Core Specification's wording, and the file says which is which. Everything the rules do rests on the machine-readable field, not on the prose. The ερωτηματικό is written U+003B, not U+037E; the άνω τελεία is written U+00B7, not U+0387. Consequence for the rules: no rule may emit U+037E or U+0387, and none may rewrite one to the character it decomposes to — that is normalisation, forbidden by docs/ARCHITECTURE.md §4.3 and performed anyway by any downstream NFC pass. The full argument, including why U+00B7 must not join the spaces rule's STRIP-BEFORE set, is in spec/rules/spaces.md §3.5. Retrieved 2026-08-15."
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}
|