Repository: jagerber48/sciform

Issue:
add `a` and `p` precision values in FSML to select `round_mode="all"` and `round_mode="pdg"`.
It should be possible to select "all" and "pdg" rounding modes via the FSML.
E.g.

```
from sciform import SciNum

print(format(SciNum(123.456, 0.789), ".pf"))
# 123.5 ± 0.8
print(format(SciNum(123.456, 0.789), ".af"))
# 123.456 ± 0.789
```

Discussion:

Solution patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 3b28c241..46626400 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,6 +14,9 @@ Added
 * Digits-past-the-decimal formatting (``round_mode="dec_place"``) is now supported for
   value/uncertainty pairs.
   [`#170 <https://github.com/jagerber48/sciform/issues/170>`_]
+* ``round_mode="all"`` and ``round_mode="pdg"`` options can now be accessed in the FSML
+  using ``"A"`` and ``"P"`` flags.
+  [`#192 <https://github.com/jagerber48/sciform/issues/192>`_]
 * Added a dark theme option (default on) for readthedocs documentation.
 * Added code coverage breakdown for unit and feature tests to codecov CI.
 
diff --git a/docs/source/examples.rst b/docs/source/examples.rst
index f314beec..f40d5d00 100644
--- a/docs/source/examples.rst
+++ b/docs/source/examples.rst
@@ -131,6 +131,18 @@ The ``.-1`` indicates the number should be rounded to one
 digit *before* the decimal point and the ``f`` indicates that fixed
 point mode should be used.
 
+>>> print(f"{SciNum(123.456, 0.123):Pf}")
+123.46 ± 0.12
+>>> print(f"{SciNum(123.456, 0.523):Pf}")
+123.5 ± 0.5
+>>> print(f"{SciNum(123.456, 0.973):Pf}")
+123.5 ± 1.0
+>>> print(f"{SciNum(123.456, 0.973):Af}")
+123.456 ± 0.973
+
+In these examples the ``"P"`` selects the ``"PDG"`` rounding rule and
+``"A"`` selects the ``"all"`` rounding rule.
+
 SciNum, and Global Options
 =====================================
 
diff --git a/docs/source/fsml.rst b/docs/source/fsml.rst
index 99cfe6ec..b4b8f4d6 100644
--- a/docs/source/fsml.rst
+++ b/docs/source/fsml.rst
@@ -18,17 +18,18 @@ This is analogous to how python :class:`int`, :class:`float`, and
 `format specification mini-language <https://docs.python.org/3/library/string.html#format-specification-mini-language>`_.
 The :mod:`sciform` format specification mini-language is given by::
 
-    format_spec        ::=  [left_pad_char "="][sign]["#"][left_pad_dec_place][round_mode ndigits][exp_mode]["x" exp_val]["p"]["()"]
+    format_spec        ::=  [left_pad_char "="][sign]["#"][left_pad_dec_place][round_mode ndigits | special_round_mode][exp_mode]["x" exp_val]["p"]["()"]
 
     left_pad_char      ::=  "0" | " "
     sign               ::=  "+" | "-" | " "
     left_pad_dec_place ::=  digit+
     round_mode         ::=  "!" | "."
+    special_round_mode ::=  "A" | "P"
     ndigits            ::=  [+-]?digit+
     exp_mode           ::=  "f" | "F" | "%" | "e" | "E" | "r" | "R" | "b" | "B" |
     exp_val            ::=  [+-]?digit+
 
-Below is are two simple FSML usage examples.
+Below are two simple FSML usage examples.
 See :ref:`FSML examples <fsml_examples>` for more complicated FSML
 usage examples.
 
@@ -87,6 +88,10 @@ Further details about the options can be found at
        | (``[+-]?\d+``)
      - Sets ``ndigits`` to an integer to control rounding.
        See :ref:`rounding`.
+   * - | special_round_mode
+       | (``'A'``, ``'P'``)
+     - Set ``round_mode`` to ``'all'`` or ``'pdg'``.
+       See :ref:`rounding`.
    * - | exp_mode
        | (``'f'``, ``'F'``, ``'%'``, ``'e'``, ``'E'``, ``'r'``, ``'R'``,
          ``'b'``, ``'B'``)
diff --git a/src/sciform/formatting/fsml.py b/src/sciform/formatting/fsml.py
index a6d328b6..38b49556 100644
--- a/src/sciform/formatting/fsml.py
+++ b/src/sciform/formatting/fsml.py
@@ -5,6 +5,7 @@
 import re
 
 from sciform.options.input_options import InputOptions
+from sciform.options.option_types import RoundModeEnum
 
 pattern = re.compile(
     r"""^
@@ -12,7 +13,11 @@
                          (?P<sign_mode>[-+ ])?
                          (?P<alternate_mode>\#)?
                          (?P<left_pad_dec_place>\d+)?
-                         (?:(?P<round_mode>[.!])(?P<ndigits>[+-]?\d+))?
+                         (?:
+                           (?P<round_mode>[.!])(?P<ndigits>([+-]?\d+))
+                           |
+                           (?P<round_mode_special>[AP])
+                         )?
                          (?P<exp_mode>[fF%eErRbB])?
                          (?:x(?P<exp_val>[+-]?\d+))?
                          (?P<prefix_mode>p)?
@@ -51,6 +56,15 @@ def parse_exp_mode(
     return exp_mode, capitalize
 
 
+ROUND_MODE_MAPPING = {
+    "!": "sig_fig",
+    ".": "dec_place",
+    "A": RoundModeEnum.ALL,
+    "P": RoundModeEnum.PDG,
+    None: None,
+}
+
+
 def format_options_from_fmt_spec(fmt_spec: str) -> InputOptions:
     """Resolve InputOptions from format specification string."""
     match = pattern.match(fmt_spec)
@@ -68,16 +82,17 @@ def format_options_from_fmt_spec(fmt_spec: str) -> InputOptions:
     if left_pad_dec_place is not None:
         left_pad_dec_place = int(left_pad_dec_place)
 
-    round_mode_mapping = {"!": "sig_fig", ".": "dec_place", None: None}
-
     round_mode_flag = match.group("round_mode")
-    round_mode = round_mode_mapping[round_mode_flag]
-
     ndigits = match.group("ndigits")
-    if ndigits is not None:
-        ndigits = int(ndigits)
+    if not round_mode_flag:
+        special_round_mode_flag = match.group("round_mode_special")
+        if not special_round_mode_flag:
+            round_mode = None
+        else:
+            round_mode = ROUND_MODE_MAPPING[special_round_mode_flag]
     else:
-        ndigits = None
+        round_mode = ROUND_MODE_MAPPING[round_mode_flag]
+        ndigits = int(ndigits)
 
     exp_mode = match.group("exp_mode")
     exp_mode, capitalize = parse_exp_mode(


Test patch:
diff --git a/tests/feature/test_val_unc_fsml.py b/tests/feature/test_val_unc_fsml.py
index 55f15764..1b9ddc70 100644
--- a/tests/feature/test_val_unc_fsml.py
+++ b/tests/feature/test_val_unc_fsml.py
@@ -404,6 +404,51 @@ def test_percent(self):
 
         self.run_val_unc_fsml_cases(cases_list)
 
+    def test_all_pdg(self):
+        cases_list = [
+            (
+                (123.456, 0.789),
+                [
+                    ("Af", "123.456 ± 0.789"),
+                    ("Af", "123.456 ± 0.789"),
+                    ("Pf", "123.5 ± 0.8"),
+                    ("Pf", "123.5 ± 0.8"),
+                ],
+            ),
+            (
+                (0.789, 123.456),
+                [
+                    ("Af", "0.789 ± 123.456"),
+                    ("Af", "0.789 ± 123.456"),
+                    ("Pf", "0 ± 120"),
+                    ("Pf", "0 ± 120"),
+                ],
+            ),
+            (
+                (3.1415, 0.01),
+                [
+                    ("Af", "3.14 ± 0.01"),
+                    ("Pf", "3.142 ± 0.010"),
+                ],
+            ),
+            (
+                (3.1415, 0.05),
+                [
+                    ("Af", "3.14 ± 0.05"),
+                    ("Pf", "3.14 ± 0.05"),
+                ],
+            ),
+            (
+                (3.1415, 0.095),
+                [
+                    ("Af", "3.142 ± 0.095"),
+                    ("Pf", "3.14 ± 0.10"),
+                ],
+            ),
+        ]
+
+        self.run_val_unc_fsml_cases(cases_list)
+
     def test_paren_uncertainty(self):
         cases_list = [
             (