Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 28 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ python -m rrparser -o rules.csv
python -m rrparser --release 3.1.0 --dataset rhea --diameters 0-8 -o rules.csv

# Equivalent selection using radii.
python -m rrparser --dataset rhea --radii 0,1,2,3,4 -o rules.csv
python -m rrparser --dataset rhea --radii 0-4 -o rules.csv

# Aggregate datasets within one release (comma or + separator).
python -m rrparser --release 3.1.0 --dataset metanetx,rhea -d 0-8 -o rules.csv
Expand Down Expand Up @@ -106,14 +106,17 @@ python -m rrparser --dataset rhea --diameters '<=8' -o rules.csv
python -m rrparser --dataset rhea --diameters '>=8' -o rules.csv
```

Alternatively, `--radii` accepts comma-separated integers from 0 to 10:
`--radii 0,1,2` is equivalent to `--diameters 0,2,4`. The two options are
mutually exclusive. Without either option, all diameters 0,2,…,20 are selected.
`--radii` supports the same expressions over integer radii 0–10:
`0,1,2`, `0-4`, `<4`, `<=4`, `>4`, `>=4`, and combinations such as `0-2,>=8`.
For example, `--radii '<=4'` is equivalent to `--diameters '<=8'`.
The two options are mutually exclusive. Both are expanded into normalized
integer radii by the argument parser, before calling `parse_rules()`.
Without either option, all radii 0–10 (diameters 0,2,…,20) are selected.

For templates, a row is retained when at least one source radius matches
`diameter / 2`, once per template. The output `RADII` column retains all source
radii, separated by semicolons. Explicit odd diameters or values above 20 are
rejected for templates.
rejected before parsing, including for rr02.

rr02 supplies diameters 2,4,…,16. The extended default therefore selects all
its rules. A selection containing no available diameter, such as `-d 20`,
Expand Down Expand Up @@ -158,7 +161,7 @@ archive cache.
| `--list-releases` | Print available release/dataset pairs and exit. |
| `--cache-dir`, `--rules-dir` | Source cache directory; default package-local `rrparser/cache`. |
| `-d`, `--diameters` | List, range, or comparison; default all even diameters 0–20. |
| `--radii` | Comma-separated radii 0–10, as an alternative to `--diameters`. |
| `--radii` | List, range, or comparison over radii 0–10, as an alternative to `--diameters`. |
| `-rt`, `--rule-type` | `all` (default), `retro`, or `forward`; templates require `all`. |
| `-ec`, `--ec` | File of comma-separated EC prefixes to keep. |
| `-ecx`, `--ecx` | File of comma-separated EC prefixes to exclude; mutually exclusive with `--ec`. |
Expand All @@ -176,7 +179,7 @@ Run `python -m rrparser --help` for the full CLI help.
Use keyword arguments to select the source, filters and output:

```python
from rrparser import list_retrorules, parse_rules
from rrparser import list_retrorules, parse_diameters, parse_radii, parse_rules

# Mapping: (release, dataset) -> download URL.
choices = list_retrorules()
Expand All @@ -186,40 +189,49 @@ parse_rules(
outfile="rules.csv",
release="3.1.0",
dataset="rhea",
diameters="<=8",
radii=parse_radii("<=4"),
)

# Return CSV text instead of writing a file.
csv_text = parse_rules(
outfile=None,
dataset="rhea",
radii="0,1,2",
radii=[0, 1, 2],
)

# Aggregate datasets; dataset="all" is also supported.
parse_rules(
outfile="combined.csv",
release="3.1.0",
dataset="metanetx+rhea",
diameters="<=8",
radii=parse_diameters("<=8"),
)

# Parse a local file with EC filtering and replacement scores.
parse_rules(
outfile="filtered.csv",
rules_file="input.csv",
diameters="2,4",
radii=[1, 2],
ec=["1.1"],
scores_file="scores.csv",
)
```

`rules_dir=` overrides the cache directory. `release=`, `dataset=`, and cache
lookup apply when `rules_file='retrorules'`, the default. Diameter expressions
have the same meaning in Python and on the CLI. Pass `radii=` as an alternative
to `diameters=`. `ec=` and `ecx=` take lists of prefixes; `read_ecnumbers()`
lookup apply when `rules_file='retrorules'`, the default.

`parse_rules()` accepts only normalized integer radii via `radii=` (a list or
tuple, default 0–10); it no longer accepts `diameters=` or expression strings.
Use the exported `parse_radii(expression)` to expand radius expressions, or
`parse_diameters(expression)` to expand diameter expressions and convert them
to radii before calling `parse_rules()`. Existing Python callers using
`diameters=expression` should migrate to `radii=parse_diameters(expression)`;
callers using `radii=expression` should use `radii=parse_radii(expression)`.
`ec=` and `ecx=` take lists of prefixes; `read_ecnumbers()`
can load these from a comma-separated file. Invalid selections raise
`ValueError`; the CLI reports these errors and exits with status 1.
`ValueError`; invalid radius sequence types raise `TypeError`. Invalid CLI
expressions are rejected by the argument parser with status 2 before any
download; runtime selection errors are reported with status 1.

The CLI argument parser is also available:

Expand All @@ -228,6 +240,7 @@ from rrparser import build_args_parser

parser = build_args_parser()
params = parser.parse_args(["--dataset", "rhea", "--diameters", "<=8"])
assert params.radii == [0, 1, 2, 3, 4] # No separate diameters attribute.
```

## Output compatibility
Expand Down
59 changes: 54 additions & 5 deletions rrparser/Args.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,60 @@

from argparse import ArgumentParser
from os import path as os_path
from re import fullmatch

from rrparser._version import __version__

DEFAULT_RULES_DIR = os_path.join(os_path.dirname(os_path.realpath(__file__)), "cache")
DEFAULT_RULES_FILE = "retrorules"
DEFAULT_RELEASE = "3.1.0"
DEFAULT_DIAMETERS = "0,2,4,6,8,10,12,14,16,18,20"
DEFAULT_RADII = tuple(range(11))


def _parse_sizes(expression, maximum, step):
values = set()
for term in expression.split(","):
term = term.strip()
interval = fullmatch(r"(\d+)\s*-\s*(\d+)", term)
comparison = fullmatch(r"([<>])(=?)\s*(\d+)", term)
if term.isdigit():
value = int(term)
if value > maximum or value % step:
raise ValueError(
f"Values must be multiples of {step} from 0 to {maximum}"
)
values.add(value)
elif interval:
lower, upper = map(int, interval.groups())
if lower > upper:
raise ValueError("Range must have its lower bound first")
values.update(
value
for value in range(0, maximum + 1, step)
if lower <= value <= upper
)
elif comparison:
operator, inclusive, bound = comparison.groups()
bound = int(bound)
values.update(
value
for value in range(0, maximum + 1, step)
if (value < bound if operator == "<" else value > bound)
or (inclusive and value == bound)
)
else:
raise ValueError("Use a list (0,2,4), range (0-8), <8, <=8, >8 or >=8")
return sorted(values)


def parse_radii(expression):
"""Expand a radius expression into integer radii 0–10."""
return _parse_sizes(expression, 10, 1)


def parse_diameters(expression):
"""Expand a diameter expression and convert it to integer radii."""
return [diameter // 2 for diameter in _parse_sizes(expression, 20, 2)]


def build_args_parser():
Expand Down Expand Up @@ -87,16 +134,18 @@ def _add_arguments(parser):
sizes.add_argument(
"-d",
"--diameters",
type=str,
default=DEFAULT_DIAMETERS,
type=parse_diameters,
dest="radii",
default=DEFAULT_RADII,
help='Diameters: list (0,2,4), inclusive range (0-8), "<8", "<=8", ">8" or ">=8". '
"Ranges and comparisons select even values from 0 to 20 (default: all). "
"For templates, diameter = 2 * radius; rr02 provides diameters 2 to 16",
)
sizes.add_argument(
"--radii",
type=str,
help="Comma-separated radii from 0 to 10 (alternative to --diameters)",
type=parse_radii,
help='Radii: list (0,1,2), inclusive range (0-4), "<4", "<=4", ">4" or ">=4". '
"Values range from 0 to 10 (default: all); mutually exclusive with --diameters.",
)
parser.add_argument(
"-of",
Expand Down
Loading
Loading