messageformat2 0.1.0__tar.gz → 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.
Files changed (106) hide show
  1. messageformat2-1.0.0/.github/dependabot.yml +27 -0
  2. messageformat2-1.0.0/.github/workflows/ci.yml +38 -0
  3. messageformat2-1.0.0/.github/workflows/release.yml +31 -0
  4. {messageformat2-0.1.0 → messageformat2-1.0.0}/.gitignore +1 -3
  5. {messageformat2-0.1.0 → messageformat2-1.0.0}/.readthedocs.yaml +9 -7
  6. {messageformat2-0.1.0 → messageformat2-1.0.0}/CONTRIBUTING.md +1 -1
  7. {messageformat2-0.1.0 → messageformat2-1.0.0}/PKG-INFO +25 -37
  8. {messageformat2-0.1.0 → messageformat2-1.0.0}/README.md +10 -7
  9. messageformat2-1.0.0/docs/builtins.md +142 -0
  10. messageformat2-1.0.0/docs/explanation.md +71 -0
  11. {messageformat2-0.1.0 → messageformat2-1.0.0}/docs/getting_started.md +2 -1
  12. {messageformat2-0.1.0 → messageformat2-1.0.0}/docs/howto/datamodel.md +1 -1
  13. messageformat2-1.0.0/docs/howto/errors.md +72 -0
  14. messageformat2-1.0.0/docs/howto/formatter.md +96 -0
  15. {messageformat2-0.1.0 → messageformat2-1.0.0}/docs/howto/selector.md +4 -2
  16. {messageformat2-0.1.0 → messageformat2-1.0.0}/docs/index.md +4 -14
  17. {messageformat2-0.1.0 → messageformat2-1.0.0}/messageformat2/__init__.py +1 -1
  18. messageformat2-1.0.0/messageformat2/builtins.py +592 -0
  19. {messageformat2-0.1.0 → messageformat2-1.0.0}/messageformat2/datamodel.py +70 -105
  20. {messageformat2-0.1.0 → messageformat2-1.0.0}/messageformat2/errors.py +51 -22
  21. {messageformat2-0.1.0 → messageformat2-1.0.0}/messageformat2/message.py +34 -11
  22. {messageformat2-0.1.0 → messageformat2-1.0.0}/messageformat2/parser.py +106 -159
  23. messageformat2-1.0.0/messageformat2/runtime.py +317 -0
  24. {messageformat2-0.1.0 → messageformat2-1.0.0}/pyproject.toml +23 -25
  25. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/complex_message_2.txt +7 -4
  26. messageformat2-1.0.0/tests/data/complex_message_3.txt +28 -0
  27. messageformat2-1.0.0/tests/data/simple_message_17.txt +8 -0
  28. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/simple_message_9.txt +1 -1
  29. messageformat2-1.0.0/tests/data/simple_message_escapes_3.txt +1 -0
  30. messageformat2-1.0.0/tests/spec/README.md +18 -0
  31. messageformat2-1.0.0/tests/spec/bidi.json +147 -0
  32. messageformat2-1.0.0/tests/spec/data-model-errors.json +189 -0
  33. messageformat2-1.0.0/tests/spec/fallback.json +62 -0
  34. messageformat2-1.0.0/tests/spec/functions/currency.json +56 -0
  35. messageformat2-1.0.0/tests/spec/functions/date.json +45 -0
  36. messageformat2-1.0.0/tests/spec/functions/datetime.json +64 -0
  37. messageformat2-1.0.0/tests/spec/functions/integer.json +71 -0
  38. messageformat2-1.0.0/tests/spec/functions/number.json +334 -0
  39. messageformat2-1.0.0/tests/spec/functions/offset.json +77 -0
  40. messageformat2-1.0.0/tests/spec/functions/percent.json +45 -0
  41. messageformat2-1.0.0/tests/spec/functions/string.json +79 -0
  42. messageformat2-1.0.0/tests/spec/functions/time.json +42 -0
  43. messageformat2-1.0.0/tests/spec/pattern-selection.json +120 -0
  44. messageformat2-1.0.0/tests/spec/syntax-errors.json +246 -0
  45. messageformat2-1.0.0/tests/spec/syntax.json +763 -0
  46. messageformat2-1.0.0/tests/spec/u-options.json +104 -0
  47. messageformat2-1.0.0/tests/test_builtins.py +276 -0
  48. messageformat2-1.0.0/tests/test_parser.py +412 -0
  49. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/test_regex.py +11 -84
  50. messageformat2-1.0.0/tests/test_runtime.py +458 -0
  51. messageformat2-1.0.0/tests/test_spec.py +238 -0
  52. messageformat2-1.0.0/uv.lock +950 -0
  53. messageformat2-0.1.0/.github/workflows/ci.yml +0 -34
  54. messageformat2-0.1.0/docs/builtins.md +0 -172
  55. messageformat2-0.1.0/docs/explanation.md +0 -86
  56. messageformat2-0.1.0/docs/howto/errors.md +0 -48
  57. messageformat2-0.1.0/docs/howto/formatter.md +0 -51
  58. messageformat2-0.1.0/docs/reference.md +0 -12
  59. messageformat2-0.1.0/messageformat2/builtins.py +0 -323
  60. messageformat2-0.1.0/messageformat2/runtime.py +0 -301
  61. messageformat2-0.1.0/messageformat2.egg-info/PKG-INFO +0 -148
  62. messageformat2-0.1.0/messageformat2.egg-info/SOURCES.txt +0 -70
  63. messageformat2-0.1.0/messageformat2.egg-info/dependency_links.txt +0 -1
  64. messageformat2-0.1.0/messageformat2.egg-info/entry_points.txt +0 -2
  65. messageformat2-0.1.0/messageformat2.egg-info/requires.txt +0 -20
  66. messageformat2-0.1.0/messageformat2.egg-info/top_level.txt +0 -1
  67. messageformat2-0.1.0/requirements.dev.txt +0 -176
  68. messageformat2-0.1.0/requirements.docs.txt +0 -96
  69. messageformat2-0.1.0/requirements.txt +0 -4
  70. messageformat2-0.1.0/setup.cfg +0 -4
  71. messageformat2-0.1.0/tests/data/complex_message_3.txt +0 -22
  72. messageformat2-0.1.0/tests/data/unsupported_annotations_1.txt +0 -4
  73. messageformat2-0.1.0/tests/data/unsupported_annotations_2.txt +0 -4
  74. messageformat2-0.1.0/tests/data/unsupported_annotations_3.txt +0 -3
  75. messageformat2-0.1.0/tests/data/unsupported_annotations_4.txt +0 -6
  76. messageformat2-0.1.0/tests/data/unsupported_annotations_5.txt +0 -8
  77. messageformat2-0.1.0/tests/data/unsupported_statement_1.txt +0 -15
  78. messageformat2-0.1.0/tests/data/unsupported_statement_2.txt +0 -21
  79. messageformat2-0.1.0/tests/test_parser.py +0 -235
  80. messageformat2-0.1.0/tests/test_runtime.py +0 -242
  81. {messageformat2-0.1.0 → messageformat2-1.0.0}/LICENSE +0 -0
  82. {messageformat2-0.1.0 → messageformat2-1.0.0}/docs/datamodel.md +0 -0
  83. {messageformat2-0.1.0 → messageformat2-1.0.0}/docs/errors.md +0 -0
  84. {messageformat2-0.1.0 → messageformat2-1.0.0}/docs/message.md +0 -0
  85. {messageformat2-0.1.0 → messageformat2-1.0.0}/docs/mf2.md +0 -0
  86. {messageformat2-0.1.0 → messageformat2-1.0.0}/messageformat2/cli.py +0 -0
  87. {messageformat2-0.1.0 → messageformat2-1.0.0}/mkdocs.yml +0 -0
  88. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/__init__.py +0 -0
  89. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/complex_message_1.txt +0 -0
  90. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/simple_message_1.txt +0 -0
  91. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/simple_message_10.txt +0 -0
  92. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/simple_message_11.txt +0 -0
  93. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/simple_message_12.txt +0 -0
  94. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/simple_message_13.txt +0 -0
  95. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/simple_message_14.txt +0 -0
  96. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/simple_message_15.txt +0 -0
  97. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/simple_message_16.txt +0 -0
  98. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/simple_message_2.txt +0 -0
  99. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/simple_message_3.txt +0 -0
  100. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/simple_message_4.txt +0 -0
  101. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/simple_message_5.txt +0 -0
  102. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/simple_message_6.txt +0 -0
  103. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/simple_message_7.txt +0 -0
  104. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/simple_message_8.txt +0 -0
  105. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/simple_message_escapes_1.txt +0 -0
  106. {messageformat2-0.1.0 → messageformat2-1.0.0}/tests/data/simple_message_escapes_2.txt +0 -0
@@ -0,0 +1,27 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ day: "monday"
8
+ time: "09:00"
9
+ timezone: "Europe/Zurich"
10
+ cooldown:
11
+ default-days: 7
12
+ groups:
13
+ github-actions:
14
+ patterns: ["*"]
15
+
16
+ - package-ecosystem: "pip"
17
+ directory: "/"
18
+ schedule:
19
+ interval: "weekly"
20
+ day: "monday"
21
+ time: "09:00"
22
+ timezone: "Europe/Zurich"
23
+ cooldown:
24
+ default-days: 7
25
+ groups:
26
+ python-dependencies:
27
+ patterns: ["*"]
@@ -0,0 +1,38 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+
9
+ jobs:
10
+ ci:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.12", "3.13", "3.14", "3.15"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v7.0.1
18
+
19
+ - name: Install the latest version of uv and set the python version
20
+ uses: astral-sh/setup-uv@v7
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Lint code 🧹
25
+ run: |
26
+ uv run ruff check tests/ messageformat2/
27
+
28
+ - name: Format code 📃
29
+ run: |
30
+ uv run ruff format --check --output-format=github messageformat2/ tests/
31
+
32
+ - name: Type check 🏷
33
+ run: |
34
+ uv run ty check messageformat2/ tests/
35
+
36
+ - name: Run tests 🧪
37
+ run: |
38
+ uv run pytest --no-cov --doctest-modules
@@ -0,0 +1,31 @@
1
+ name: Upload To PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ pypi-publish:
13
+
14
+ runs-on: ubuntu-latest
15
+ environment: release
16
+ permissions:
17
+ id-token: write
18
+
19
+ steps:
20
+ - uses: actions/checkout@v7.0.1
21
+
22
+ - name: Install the latest version of uv and set the python version
23
+ uses: astral-sh/setup-uv@v7
24
+ with:
25
+ python-version: '3.14'
26
+
27
+ - name: Build package 📦
28
+ run: uv build
29
+
30
+ - name: Publish package distributions to PyPI
31
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -1,5 +1,4 @@
1
- venv/
2
- .python-version
1
+ .venv/
3
2
  __pycache__/
4
3
  .coverage
5
4
  htmlcov/
@@ -7,4 +6,3 @@ htmlcov/
7
6
  .pytest_cache/
8
7
  *.egg-info/
9
8
 
10
- node_modules/
@@ -6,14 +6,16 @@ version: 2
6
6
 
7
7
  # Set the version of Python and other tools you might need
8
8
  build:
9
- os: ubuntu-22.04
9
+ os: ubuntu-26.04
10
10
  tools:
11
- python: "3.12"
11
+ python: "3.14"
12
12
 
13
- mkdocs:
14
- configuration: mkdocs.yml
15
-
16
- # Optionally declare the Python requirements required to build your docs
17
13
  python:
18
14
  install:
19
- - requirements: requirements.docs.txt
15
+ - method: uv
16
+ command: sync
17
+ groups:
18
+ - docs
19
+
20
+ mkdocs:
21
+ configuration: mkdocs.yml
@@ -24,6 +24,6 @@ pytest --doctest-modules
24
24
  ### Code quality
25
25
 
26
26
  ```sh
27
- pyright tests/ messageformat2/
27
+ ty tests/ messageformat2/
28
28
  ruff check tests/ messageformat2/
29
29
  ```
@@ -1,7 +1,10 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.5
2
2
  Name: messageformat2
3
- Version: 0.1.0
3
+ Version: 1.0.0
4
4
  Summary: Python implementation of Unicode MessageFormat 2.0
5
+ Project-URL: Homepage, https://github.com/tomasr8/messageformat2
6
+ Project-URL: Github, https://github.com/tomasr8/messageformat2
7
+ Project-URL: Documentation, https://messageformat2.readthedocs.io/en/latest/
5
8
  Author-email: Tomas Roun <tomas.roun8@gmail.com>
6
9
  License: MIT License
7
10
 
@@ -24,52 +27,34 @@ License: MIT License
24
27
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
28
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
29
  SOFTWARE.
27
-
28
- Project-URL: Homepage, https://github.com/tomasr8/messageformat2
29
- Project-URL: Github, https://github.com/tomasr8/messageformat2
30
- Keywords: i18n,internationalization,localization,messageformat,messageformat 2,cldr,gettext
31
- Classifier: License :: OSI Approved :: MIT License
30
+ License-File: LICENSE
31
+ Keywords: cldr,gettext,i18n,internationalization,localization,messageformat,messageformat 2
32
32
  Classifier: Operating System :: OS Independent
33
33
  Classifier: Programming Language :: Python :: 3
34
34
  Classifier: Programming Language :: Python :: 3.12
35
- Classifier: Topic :: Scientific/Engineering
35
+ Classifier: Programming Language :: Python :: 3.13
36
+ Classifier: Programming Language :: Python :: 3.14
37
+ Classifier: Programming Language :: Python :: 3.15
36
38
  Classifier: Topic :: Software Development :: Internationalization
37
39
  Classifier: Topic :: Software Development :: Localization
38
40
  Requires-Python: >=3.12
41
+ Requires-Dist: babel>=2
42
+ Requires-Dist: click>=8
39
43
  Description-Content-Type: text/markdown
40
- License-File: LICENSE
41
- Requires-Dist: Babel
42
- Requires-Dist: click
43
- Provides-Extra: dev
44
- Requires-Dist: build; extra == "dev"
45
- Requires-Dist: twine; extra == "dev"
46
- Requires-Dist: uv; extra == "dev"
47
- Requires-Dist: pytest; extra == "dev"
48
- Requires-Dist: pytest-snapshot; extra == "dev"
49
- Requires-Dist: pytest-cov; extra == "dev"
50
- Requires-Dist: ruff; extra == "dev"
51
- Requires-Dist: pyright; extra == "dev"
52
- Requires-Dist: mkdocs; extra == "dev"
53
- Requires-Dist: mkdocstrings[python]; extra == "dev"
54
- Requires-Dist: mkdocs-material; extra == "dev"
55
- Provides-Extra: docs
56
- Requires-Dist: mkdocs; extra == "docs"
57
- Requires-Dist: mkdocstrings[python]; extra == "docs"
58
- Requires-Dist: mkdocs-material; extra == "docs"
59
-
60
-
61
-
62
- # MessageFormat2 [![Documentation Status](https://readthedocs.org/projects/messageformat2/badge/?version=latest)](https://messageformat2.readthedocs.io/en/latest/?badge=latest) [![CI](https://github.com/tomasr8/messageformat2/workflows/CI/badge.svg)](https://github.com/tomasr8/messageformat2/workflows/CI/badge.svg) [![Checked with pyright](https://microsoft.github.io/pyright/img/pyright_badge.svg)](https://microsoft.github.io/pyright/)
44
+
45
+
46
+
47
+ # MessageFormat2 [![Documentation Status](https://readthedocs.org/projects/messageformat2/badge/?version=latest)](https://messageformat2.readthedocs.io/en/latest/?badge=latest) [![CI](https://github.com/tomasr8/messageformat2/workflows/CI/badge.svg)](https://github.com/tomasr8/messageformat2/workflows/CI/badge.svg)
63
48
 
64
49
  This is a Python implementation of the [Unicode Message Format 2.0
65
- specification](https://www.unicode.org/reports/tr35/tr35-72/tr35-messageFormat.html).
50
+ specification](https://www.unicode.org/reports/tr35/tr35-messageFormat.html).
66
51
 
67
52
  Message Format 2.0 (MF2) is a new iteration of Message Format which aims to be
68
53
  more expressive and flexible than both Message Format 1.0 and, in the context of
69
54
  Python, gettext.
70
55
 
71
56
  If you would like to learn more about MF2 itself head over to the [Unicode technical
72
- specification](https://www.unicode.org/reports/tr35/tr35-72/tr35-messageFormat.html).
57
+ specification](https://www.unicode.org/reports/tr35/tr35-messageFormat.html).
73
58
 
74
59
  ## Features
75
60
 
@@ -77,12 +62,14 @@ The library includes:
77
62
 
78
63
  - A message parser and a formatter
79
64
  - Several builtin formatters and selectors described by the
80
- [spec](https://www.unicode.org/reports/tr35/tr35-72/tr35-messageFormat.html#function-registry)
65
+ [spec](https://github.com/unicode-org/message-format-wg/blob/main/spec/functions/README.md)
81
66
  and the possibility to write custom formatters/selectors
82
67
  - Helper classes to inspect and transform the message [data
83
- model](https://www.unicode.org/reports/tr35/tr35-72/tr35-messageFormat.html#interchange-data-model)
68
+ model](https://github.com/unicode-org/message-format-wg/blob/main/spec/data-model/README.md)
69
+
70
+ ## Documentation
84
71
 
85
- ## [Documentation](https://messageformat2.readthedocs.io/en/latest/)
72
+ Documentation is available here: [https://messageformat2.readthedocs.io/en/latest/](https://messageformat2.readthedocs.io/en/latest/)
86
73
 
87
74
  ## Installation
88
75
 
@@ -136,7 +123,8 @@ MF2 supports pluralization using built-in or custom selectors.
136
123
  from messageformat2 import Message
137
124
 
138
125
  msg = Message ("""\
139
- .match {$count :number}
126
+ .input {$count :number}
127
+ .match $count
140
128
  one {{You have {$count} notification.}}
141
129
  * {{You have {$count} notifications.}}
142
130
  """)
@@ -1,16 +1,16 @@
1
1
 
2
2
 
3
- # MessageFormat2 [![Documentation Status](https://readthedocs.org/projects/messageformat2/badge/?version=latest)](https://messageformat2.readthedocs.io/en/latest/?badge=latest) [![CI](https://github.com/tomasr8/messageformat2/workflows/CI/badge.svg)](https://github.com/tomasr8/messageformat2/workflows/CI/badge.svg) [![Checked with pyright](https://microsoft.github.io/pyright/img/pyright_badge.svg)](https://microsoft.github.io/pyright/)
3
+ # MessageFormat2 [![Documentation Status](https://readthedocs.org/projects/messageformat2/badge/?version=latest)](https://messageformat2.readthedocs.io/en/latest/?badge=latest) [![CI](https://github.com/tomasr8/messageformat2/workflows/CI/badge.svg)](https://github.com/tomasr8/messageformat2/workflows/CI/badge.svg)
4
4
 
5
5
  This is a Python implementation of the [Unicode Message Format 2.0
6
- specification](https://www.unicode.org/reports/tr35/tr35-72/tr35-messageFormat.html).
6
+ specification](https://www.unicode.org/reports/tr35/tr35-messageFormat.html).
7
7
 
8
8
  Message Format 2.0 (MF2) is a new iteration of Message Format which aims to be
9
9
  more expressive and flexible than both Message Format 1.0 and, in the context of
10
10
  Python, gettext.
11
11
 
12
12
  If you would like to learn more about MF2 itself head over to the [Unicode technical
13
- specification](https://www.unicode.org/reports/tr35/tr35-72/tr35-messageFormat.html).
13
+ specification](https://www.unicode.org/reports/tr35/tr35-messageFormat.html).
14
14
 
15
15
  ## Features
16
16
 
@@ -18,12 +18,14 @@ The library includes:
18
18
 
19
19
  - A message parser and a formatter
20
20
  - Several builtin formatters and selectors described by the
21
- [spec](https://www.unicode.org/reports/tr35/tr35-72/tr35-messageFormat.html#function-registry)
21
+ [spec](https://github.com/unicode-org/message-format-wg/blob/main/spec/functions/README.md)
22
22
  and the possibility to write custom formatters/selectors
23
23
  - Helper classes to inspect and transform the message [data
24
- model](https://www.unicode.org/reports/tr35/tr35-72/tr35-messageFormat.html#interchange-data-model)
24
+ model](https://github.com/unicode-org/message-format-wg/blob/main/spec/data-model/README.md)
25
25
 
26
- ## [Documentation](https://messageformat2.readthedocs.io/en/latest/)
26
+ ## Documentation
27
+
28
+ Documentation is available here: [https://messageformat2.readthedocs.io/en/latest/](https://messageformat2.readthedocs.io/en/latest/)
27
29
 
28
30
  ## Installation
29
31
 
@@ -77,7 +79,8 @@ MF2 supports pluralization using built-in or custom selectors.
77
79
  from messageformat2 import Message
78
80
 
79
81
  msg = Message ("""\
80
- .match {$count :number}
82
+ .input {$count :number}
83
+ .match $count
81
84
  one {{You have {$count} notification.}}
82
85
  * {{You have {$count} notifications.}}
83
86
  """)
@@ -0,0 +1,142 @@
1
+ The default function registry includes these functions
2
+
3
+ in line with the [specification](https://github.com/unicode-org/message-format-wg/tree/main/spec/functions)
4
+
5
+ ### `:string`
6
+
7
+ #### Formatter
8
+
9
+ As a formatter, this function simply calls `str(value)`.
10
+
11
+ #### Selector
12
+
13
+ As a selector, this function selects the exact match among the variant keys
14
+ (after NFC normalization of the value).
15
+
16
+ ```python
17
+ format_message("""\
18
+ .input {$pet :string}
19
+ .match $pet
20
+ dog {{I have a dog}}
21
+ cat {{I have a cat}}
22
+ * {{I have a {$pet}}}
23
+ """, {"pet": "cat"})
24
+ # -> "I have a cat"
25
+ ```
26
+
27
+ ### `:number`
28
+
29
+ The operand must be an `int`, `float` or `Decimal` (but not a `bool`), or a string matching the
30
+ `number-literal` production of the spec (e.g. `|-1234.5|`, `|1e3|`; `|01|` or `|.5|` are not numbers).
31
+ The resolved value of another numeric function (`:number`, `:integer`, `:offset`) is also accepted, in which
32
+ case its options are inherited, with the options of the expression taking priority.
33
+
34
+ #### Formatter
35
+
36
+ - `signDisplay`
37
+ - auto (default)
38
+ - always
39
+ - exceptZero
40
+ - negative
41
+ - never
42
+ - `useGrouping`
43
+ - auto (default)
44
+ - always
45
+ - never
46
+ - min2 (treated as auto)
47
+ - `minimumIntegerDigits`
48
+ - `minimumFractionDigits`
49
+ - `maximumFractionDigits`
50
+ - `minimumSignificantDigits`
51
+ - `maximumSignificantDigits`
52
+ - `numberingSystem` (implementation extension): a Unicode Number System Identifier
53
+
54
+ The digit options are *digit size options*: small non-negative integers (`0`-`99`).
55
+ Not implemented: `trailingZeroDisplay`, `roundingPriority`, `roundingIncrement`, `roundingMode` (these are ignored).
56
+
57
+ #### Selector
58
+
59
+ The selector takes the same options as the formatter in addition to:
60
+
61
+ - `select`
62
+ - plural (default)
63
+ - ordinal
64
+ - exact
65
+
66
+ `select` must be set by a literal. The selector first tries an exact match between the number and
67
+ numeric keys (e.g. `1`), then (unless `select=exact`) matches the CLDR plural or ordinal category of the number
68
+ (`zero`, `one`, `two`, `few`, `many`, `other`). Any other key is a `BadVariantKey` error.
69
+
70
+ ### `:integer`
71
+
72
+ Same as `:number`, but the operand is truncated to an integer. The `minimumFractionDigits`, `maximumFractionDigits`
73
+ and `minimumSignificantDigits` options of an annotated operand are discarded.
74
+
75
+ ### `:offset`
76
+
77
+ Adds or subtracts a small integer from a numeric operand, e.g. to count "other" users:
78
+
79
+ ```python
80
+ .input {$likes :integer}
81
+ .local $others = {$likes :offset subtract=1}
82
+ ```
83
+
84
+ Exactly one of the options must be given:
85
+
86
+ - `add`
87
+ - `subtract`
88
+
89
+ The formatting options of an annotated operand (e.g. `signDisplay` of `{$likes :integer signDisplay=always}`)
90
+ are inherited. `:offset` can also be used as a selector, in which case it behaves like `:number`.
91
+
92
+ ### `:datetime`, `:date`, `:time`
93
+
94
+ The operand must be a `datetime` (or a `date`/`time` for `:date`/`:time` respectively) or an ISO 8601 date or
95
+ datetime string that can be parsed with `datetime.fromisoformat`, e.g. `|2006-01-02|` or `|2006-01-02T15:04:06+01:00|`.
96
+ The resolved value of another date/time function is also accepted, in which case its override options
97
+ (`timeZone`, `hour12`) are inherited.
98
+
99
+ The option values below must be set by literals (an option set by a variable is a `BadOption` error).
100
+
101
+ #### `:datetime`
102
+
103
+ - `dateFields`
104
+ - weekday
105
+ - day-weekday
106
+ - month-day
107
+ - month-day-weekday
108
+ - year-month-day (default)
109
+ - year-month-day-weekday
110
+ - `dateLength`
111
+ - long
112
+ - medium (default)
113
+ - short
114
+ - `timePrecision`
115
+ - hour
116
+ - minute (default)
117
+ - second
118
+ - `timeZoneStyle`: no time zone is shown unless set
119
+ - long
120
+ - short
121
+
122
+ #### `:date`
123
+
124
+ - `fields`: same values as `dateFields` above
125
+ - `length`: same values as `dateLength` above
126
+
127
+ #### `:time`
128
+
129
+ - `precision`: same values as `timePrecision` above
130
+ - `timeZoneStyle`: same values as above
131
+
132
+ #### Override options
133
+
134
+ - `timeZone`: `UTC`, `input` (the time zone of the operand, which must then have one), an IANA time zone
135
+ name (`|Europe/Zurich|`) or an offset (`|+01:00|`). A naive `datetime` is interpreted in this time zone,
136
+ an aware one is converted to it.
137
+ - `hour12`: `true` or `false` (`:datetime` and `:time` only); the default is locale-dependent.
138
+ - `calendar`: not implemented (ignored).
139
+
140
+ #### Selector
141
+
142
+ The date/time functions do not support selection, as it is not required by MF2.
@@ -0,0 +1,71 @@
1
+ ## Runtime model
2
+
3
+ All expressions are lazily evaluated at the time of use.
4
+ If the same variable is referenced more than once, it will also be evaluated more than once.
5
+ Pay attention when providing custom formatters/selectors with side effects:
6
+
7
+ ```python
8
+ counter = 0
9
+
10
+ def formatter_with_side_effect(value, locale, options):
11
+ nonlocal counter
12
+ counter +=1
13
+ return str(value)
14
+
15
+ msg = Message("\
16
+ .local $foo = {|foo| :side_effect}
17
+ {{{$foo} {$foo}}}
18
+ ")
19
+ msg.format(formatters={'side_effect': formatter_with_side_effect})
20
+ print(counter) # -> 2
21
+ ```
22
+
23
+ ## Unannotated variables
24
+
25
+ A placeholder consisting only of a variable (`{$x}`) is formatted according to the type of its value:
26
+ numbers (`int`, `float`, `Decimal`) as if annotated with `:number`, `datetime`, `date` and `time` values with
27
+ `:datetime`, `:date` and `:time`. Anything else is formatted with `str()`.
28
+ Literals are always strings, so `{|1234.5|}` is formatted verbatim.
29
+
30
+ ## Function composition
31
+
32
+ A variable which was annotated with a function can be annotated again, for example in a placeholder.
33
+ The function handler then receives the resolved value of the earlier annotation
34
+ (a `messageformat2.builtins.LazyValue` carrying the earlier function's name, operand and options) as its operand,
35
+ and decides what to do with it. The built-in numeric functions accept each other's resolved values
36
+ and inherit their options, with the options of the later expression taking priority:
37
+
38
+ ```python
39
+ .local $x = {42 :number minimumFractionDigits=2 signDisplay=always}
40
+ {{x = {$x :number minimumFractionDigits=1}}} # -> "x = +42.0"
41
+
42
+ .local $x = {1.25 :integer}
43
+ {{x = {$x :number}}} # -> "x = 1"
44
+ ```
45
+
46
+ whereas e.g. `{$x :date}` would raise `BadOperand`. See [Custom formatters](howto/formatter.md) for how to
47
+ handle such operands in your own functions.
48
+
49
+ ## Whitespace
50
+
51
+ This library implements the [grammar](https://github.com/unicode-org/message-format-wg/blob/main/spec/message.abnf) exactly as specified which includes whitespace.
52
+
53
+ Whitespace around a complex message (a message containing declarations and/or a match block) is not significant,
54
+ so complex messages can be freely indented, e.g. in multiline strings:
55
+
56
+ ```python
57
+ Message("""
58
+ .input {$count :integer}
59
+ .match $count
60
+ * {{count is {$count}}}
61
+ """)
62
+ ```
63
+
64
+ Whitespace in a simple message (a message consisting of a single pattern) is always significant
65
+ and is part of the message text. Note that the first non-whitespace character of a simple message
66
+ cannot be a `.`, so a misspelled keyword (`.imput`) is a syntax error rather than silently becoming text.
67
+ To get a pattern which starts with a `.`, use a quoted pattern:
68
+
69
+ ```python
70
+ Message("{{.input is not a keyword here}}")
71
+ ```
@@ -48,7 +48,8 @@ MF2 supports pluralization using built-in or custom selectors.
48
48
  from messageformat2 import Message
49
49
 
50
50
  msg = Message ("""\
51
- .match {$count :number}
51
+ .input {$count :number}
52
+ .match $count
52
53
  one {{You have {$count} notification.}}
53
54
  * {{You have {$count} notifications.}}
54
55
  """)
@@ -1,6 +1,6 @@
1
1
  The `Message` class has a `datamodel` property which gives you access to the
2
2
  message data model which is based on the [MF2
3
- spec](https://www.unicode.org/reports/tr35/tr35-72/tr35-messageFormat.html#interchange-data-model).
3
+ spec](https://github.com/unicode-org/message-format-wg/blob/main/spec/data-model/README.md).
4
4
  The data model nodes are documented [here](/datamodel).
5
5
 
6
6
  There are two classes to work with the datamodel - `DataModelVisitor` &
@@ -0,0 +1,72 @@
1
+ # Error handling
2
+
3
+ Errors might be raised during parsing or during formatting. You can learn more
4
+ about MF2 errors
5
+ [here](https://github.com/unicode-org/message-format-wg/blob/main/spec/errors.md).
6
+
7
+ During parsing the following exceptions (or their subclasses) might be raised:
8
+
9
+ - `messageformat2.errors.ParseError` - for syntax errors
10
+ - `messageformat2.errors.DataModelError` - for semantic errors
11
+
12
+ During formatting the following exception (or its subclasses) might be raised:
13
+
14
+ - `messageformat2.errors.FormatError`
15
+
16
+ ## Handling parsing errors
17
+
18
+ ```python
19
+ from messageformat2.message import Message
20
+ from messageformat2.errors import ParseError, DataModelError
21
+
22
+ try:
23
+ Message("}} invalid syntax")
24
+ except ParseError as e:
25
+ print(e) # -> Invalid syntax
26
+
27
+
28
+ try:
29
+ Message("""\
30
+ .input {$count :integer}
31
+ .match $count
32
+ one {{The count is {$count}}}
33
+ """)
34
+ except DataModelError as e:
35
+ print(e) # -> Missing fallback variant
36
+ ```
37
+
38
+ ## Handling formatting errors
39
+
40
+ ```python
41
+ from messageformat2.message import Message
42
+ from messageformat2.errors import FormatError
43
+
44
+ msg = Message("Hello, {$name}!")
45
+ try:
46
+ msg.format()
47
+ except FormatError as e:
48
+ print(e) # -> Unresolved variable
49
+ ```
50
+
51
+ ## Formatting with fallback values
52
+
53
+ By default the first formatting error is raised and no output is produced. The spec requires
54
+ that a valid message can always be formatted, with
55
+ [fallback values](https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#fallback-resolution)
56
+ standing in for the parts that failed. Pass an `on_error` callback to get this behaviour: every error is
57
+ passed to the callback instead of being raised, and formatting continues.
58
+
59
+ ```python
60
+ from messageformat2.message import Message
61
+
62
+ errors = []
63
+ msg = Message("Hello, {$name}! You have {$count :number} messages.")
64
+ msg.format({"count": "many"}, on_error=errors.append)
65
+ # -> "Hello, {$name}! You have {$count} messages."
66
+ errors
67
+ # -> [UnresolvedVariable('Unresolved variable: name'), BadOperand("Bad operand: 'many' is not a number")]
68
+ ```
69
+
70
+ The fallback value is `{$name}` for a variable, `{|literal|}` for a literal operand and `{:function}` for a
71
+ function without an operand. An option whose value cannot be resolved is omitted, and a selector which fails
72
+ only matches the catch-all key `*`. Syntax and data model errors are still raised by `Message()`.