galaxy-tool-refactor-rules 0.2.0__py3-none-any.whl

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.
@@ -0,0 +1,11 @@
1
+ """Shared rule-metadata vocabulary for the galaxy-tool-refactor tiers.
2
+
3
+ Tier 0.5 of the refactoring framework: a dependency-free home for the
4
+ ``RuleMeta`` descriptor shared by the formatter (tier 3) and the codemod
5
+ framework (tier 2), plus a pure markdown render helper for rule glossaries.
6
+
7
+ Following the project's dignified-python conventions there are no re-exports;
8
+ callers import ``RuleMeta`` from ``galaxy_tool_refactor_rules.meta`` and
9
+ ``render_rule_reference_table`` from ``galaxy_tool_refactor_rules.reference``
10
+ directly.
11
+ """
@@ -0,0 +1,86 @@
1
+ """The ``RuleMeta`` descriptor shared across the refactor tiers.
2
+
3
+ Both a tier-3 formatter rule (``galaxy_tool_fmt.rules.Rule``) and a tier-2
4
+ codemod (``galaxy_tool_codemod.codemod.CodemodCommand``) carry a
5
+ ``meta: ClassVar[RuleMeta]`` so the two tiers expose one uniform vocabulary for
6
+ the GTR rule registry. The descriptor is pure data — it deliberately knows
7
+ nothing about lxml, edits, or the cursor walk, which keeps this package
8
+ dependency-free and the tiers independent.
9
+
10
+ Versioning convention: stability for consumers comes from pinning the owning
11
+ tier's package version in their lockfile, not from this metadata. ``since`` /
12
+ ``until`` are documentary only — ``until`` stays ``None`` while a rule is active
13
+ and is stamped (for the changelog) in the same commit that retires the rule.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from dataclasses import dataclass
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class RuleMeta:
23
+ """Metadata descriptor for a refactor rule (a fmt rule or a codemod).
24
+
25
+ Attributes:
26
+ code: Short unique rule identifier (e.g. ``"GTR001"``).
27
+ summary: One-line human-readable description.
28
+ since: Version in which this rule was introduced.
29
+ until: Version in which this rule was removed, or ``None`` if active.
30
+ cite: Optional reference URL or citation.
31
+ order: Application order; lower values run first. Each family is ordered
32
+ independently by this value — the formatter tier sequences its
33
+ cosmetic rules, and the codemod tier sequences its canonical codemods
34
+ (the registry's apply phase sorts each family by ``order``). An
35
+ upgrade-only or report-only rule leaves it at the default.
36
+ detect_only: Whether the rule only *reports* (a lint with no automatic
37
+ fix), as opposed to the fixable fmt rules and codemods. The advisory
38
+ check tier (``galaxy-tool-lint``) sets this ``True``; a
39
+ report-only consumer like the ``check`` CLI uses it to treat such
40
+ findings as informational rather than as a failing gate.
41
+ applies_to: The document kinds the rule operates on — a subset of
42
+ ``{"tool", "macro"}``. A generic XML rule (canonical indentation,
43
+ empty-element shorthand) applies to both; a tool-structural rule
44
+ (``<tool>`` child order, a blank line between ``<tool>`` sections,
45
+ attribute order, profile upgrades) applies only to ``"tool"``; a
46
+ macro-library rule applies only to ``"macro"``. The default
47
+ ``{"tool"}`` is the conservative choice — a rule runs on a macro
48
+ file only when it explicitly opts in. Consumers run a rule against a
49
+ document only when the document's kind is in this set.
50
+ parent: The code of the **partition parent** this rule is a sub-rule of,
51
+ or ``None`` for a standalone rule. A best-practice that splits into a
52
+ provably-fixable part and an advisory residual is modelled as a parent
53
+ practice code (e.g. ``"GTR020"``) with two sub-rules whose own ``code``
54
+ is dotted: ``"GTR020.1"`` (fixable) and ``"GTR020.2"`` (advisory). The
55
+ parent is a registry-level grouping (selectable, expands to its
56
+ children), not itself a rule; this field is what the registry derives
57
+ the groups from. See registry ``docs/decisions.md`` D10.
58
+ rulesets: The names of the rule-sets this rule belongs to (the catalog
59
+ lives in ``rulesets.py``). This is the maintainer-facing "mark which
60
+ rules belong to which set" mechanism: the registry groups rules by
61
+ these names into selectable sets, and the CLI ``--ruleset`` flag
62
+ selects the **union** of the named sets. The default empty set means
63
+ the rule is never independently selectable — e.g. an upgrade-only
64
+ codemod driven internally by ``UpgradeToLatest``. Every name used
65
+ here must appear in the ``rulesets.py`` catalog (guarded by a test).
66
+ planemo_linters: The names of the planemo (``galaxy.tool_util.lint``)
67
+ linter classes this rule covers, e.g. ``{"HelpMissing", "HelpEmpty"}``.
68
+ One GTR rule may cover several planemo linters (planemo splits some
69
+ single practices across linter classes). The registry derives a
70
+ ``planemo name → GTR code`` index from this so a planemo user can
71
+ select/find a rule by its planemo name (``--select HelpMissing``) and
72
+ so the parity table can be generated. Empty for our own rules with no
73
+ planemo equivalent (the cosmetic fmt rules, the XSD-restoring repairs).
74
+ """
75
+
76
+ code: str
77
+ summary: str
78
+ since: str
79
+ until: str | None = None
80
+ cite: str | None = None
81
+ order: int = 100
82
+ detect_only: bool = False
83
+ applies_to: frozenset[str] = frozenset({"tool"})
84
+ parent: str | None = None
85
+ rulesets: frozenset[str] = frozenset()
86
+ planemo_linters: frozenset[str] = frozenset()
File without changes
@@ -0,0 +1,48 @@
1
+ """Render a cross-tier GTR rule glossary as GitHub-flavored markdown.
2
+
3
+ A pure, dependency-free helper: it turns ``(RuleMeta, tier)`` pairs into the
4
+ rows of a ``| Rule | Tier | What it does |`` table, sorted by code. It emits the
5
+ table only — the caller owns any surrounding heading and intro prose, which is
6
+ context-specific (the fmt stat page frames it differently than a standalone rule
7
+ registry would).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import re
13
+ from collections.abc import Iterable
14
+
15
+ from galaxy_tool_refactor_rules.meta import RuleMeta
16
+
17
+ # Element names in a summary (``<param>``, ``<foo/>``) would be parsed as HTML
18
+ # tags inside a markdown table cell and rendered as nothing; wrap them in
19
+ # backticks so they survive as literal text.
20
+ _ANGLE_TOKEN = re.compile(r"(<[^>]+>)")
21
+
22
+
23
+ def _backtick_xml_tokens(text: str) -> str:
24
+ """Backtick-wrap angle-bracket tokens so GitHub renders them literally."""
25
+ return _ANGLE_TOKEN.sub(r"`\1`", text)
26
+
27
+
28
+ def render_rule_reference_table(entries: Iterable[tuple[RuleMeta, str]]) -> list[str]:
29
+ """Render ``(meta, tier)`` pairs as a markdown reference table.
30
+
31
+ Args:
32
+ entries: ``(RuleMeta, tier_label)`` pairs, where ``tier_label`` names the
33
+ owning tier (e.g. ``"fmt"`` or ``"codemod"``).
34
+
35
+ Returns:
36
+ Markdown lines: a header row, the separator, then one row per rule
37
+ ordered by ``meta.code``.
38
+ """
39
+ rows = sorted(entries, key=lambda entry: entry[0].code)
40
+ lines = [
41
+ "| Rule | Tier | What it does |",
42
+ "|---|---|---|",
43
+ ]
44
+ lines.extend(
45
+ f"| {meta.code} | {tier} | {_backtick_xml_tokens(meta.summary)} |"
46
+ for meta, tier in rows
47
+ )
48
+ return lines
@@ -0,0 +1,84 @@
1
+ """The named rule-sets — the maintainer-facing vocabulary for grouping rules.
2
+
3
+ A *ruleset* is a named, described bucket of rules. **Membership is declared per
4
+ rule** (``RuleMeta.rulesets``): a maintainer marks a rule's set(s) right on the
5
+ rule. This module is the authoritative catalog of the ruleset *names and
6
+ descriptions* — the one property that belongs to the set itself, not to any
7
+ member. The registry tier (3.6) derives ``name -> {codes}`` by grouping rules by
8
+ their declared membership, and the CLI ``--ruleset`` flag selects the **union** of
9
+ the named sets.
10
+
11
+ Dependency-free, like the rest of tier 0.5 — ruleset names are plain strings, so a
12
+ rule in any tier can declare membership (``RuleMeta(..., rulesets=frozenset({...}))``)
13
+ without importing anything heavier. Adding a ruleset is a developer task (a new
14
+ ``Ruleset`` here + tagging the member rules); there are no user-defined rulesets.
15
+
16
+ The catalog also defines the subset relationships informally via the seeded
17
+ membership: ``cosmetic`` ⊆ ``default`` == ``iuc`` ⊆ ``strict`` today. ``default``
18
+ is the set applied when the user names no ruleset; it reproduces the historical
19
+ default ``format`` behaviour.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from dataclasses import dataclass
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class Ruleset:
29
+ """A named rule-set: a selectable bucket of rules.
30
+
31
+ Attributes:
32
+ name: The selection key (e.g. ``"default"``) — exactly as it appears in
33
+ ``RuleMeta.rulesets`` and on the CLI ``--ruleset`` flag.
34
+ description: A one-line human-readable summary.
35
+ """
36
+
37
+ name: str
38
+ description: str
39
+
40
+
41
+ DEFAULT_RULESET = "default"
42
+ """The ruleset selected when the user names none (the no-argument ``format`` set)."""
43
+
44
+
45
+ _CATALOG: tuple[Ruleset, ...] = (
46
+ Ruleset(
47
+ name="cosmetic",
48
+ description="Cosmetic whitespace only (indent, blank lines, shorthand).",
49
+ ),
50
+ Ruleset(
51
+ name="default",
52
+ description="The opinionated canonical formatter: structural repair + "
53
+ "attribute/element order + CDATA/quoting/help-RST fixes + cosmetic "
54
+ "formatting (the default).",
55
+ ),
56
+ Ruleset(
57
+ name="iuc",
58
+ description="Mirrors 'default' today; reserved for IUC-specific "
59
+ "divergence.",
60
+ ),
61
+ Ruleset(
62
+ name="strict",
63
+ description="Everything in 'default' plus the advisory best-practice "
64
+ "checks (report-only).",
65
+ ),
66
+ )
67
+
68
+
69
+ def rulesets_catalog() -> tuple[Ruleset, ...]:
70
+ """Return every defined ruleset, in display order."""
71
+ return _CATALOG
72
+
73
+
74
+ def ruleset_names() -> tuple[str, ...]:
75
+ """Return the defined ruleset names, in display order."""
76
+ return tuple(ruleset.name for ruleset in _CATALOG)
77
+
78
+
79
+ def ruleset_description(name: str, /) -> str | None:
80
+ """Return the one-line description for *name*, or ``None`` if undefined."""
81
+ for ruleset in _CATALOG:
82
+ if ruleset.name == name:
83
+ return ruleset.description
84
+ return None
@@ -0,0 +1,39 @@
1
+ """The ``Violation`` diagnostic descriptor shared across the refactor tiers.
2
+
3
+ A ``Violation`` is the per-occurrence report a detect (lint) phase produces:
4
+ which rule fired (``code``), where (``sourceline`` + ``xpath``), and a
5
+ human-readable ``message``. It is the read-only counterpart to the mutating
6
+ ``Edit`` (tier 3) / ``Change`` (tier 2) types — those carry the fix; this carries
7
+ the finding. Both the formatter (tier 3) and codemod (tier 2) detect phases, the
8
+ ``check`` CLI (tier 4), and the advisory check library surface diagnostics as
9
+ ``Violation``s, so the type lives here next to ``RuleMeta`` where every tier can
10
+ reach it without depending on one another.
11
+
12
+ Like ``RuleMeta`` this is pure data — the location is a plain ``int`` line plus a
13
+ ``str`` xpath, never an lxml handle — which keeps this package dependency-free
14
+ (no lxml, no tier 1/2/3 imports). See ``docs/decisions.md`` § D1 for the
15
+ shared-vocabulary rationale.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from dataclasses import dataclass
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class Violation:
25
+ """A single detected rule occurrence in a tool XML document.
26
+
27
+ Attributes:
28
+ code: The rule's identifier (e.g. ``"GTR002"``); matches ``RuleMeta.code``.
29
+ sourceline: 1-based line of the offending element, or ``0`` when the
30
+ element was synthesised and has no source position.
31
+ xpath: Absolute xpath to the offending element (e.g.
32
+ ``"/tool/inputs/param[1]"``).
33
+ message: One-line human-readable description of the finding.
34
+ """
35
+
36
+ code: str
37
+ sourceline: int
38
+ xpath: str
39
+ message: str
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.4
2
+ Name: galaxy-tool-refactor-rules
3
+ Version: 0.2.0
4
+ Summary: Shared rule-metadata vocabulary for the galaxy-tool-refactor tiers (tier 0.5).
5
+ Author: Richard Burhans
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+
10
+ # galaxy-tool-refactor-rules
11
+
12
+ Shared **rule-metadata vocabulary** for the Galaxy tool refactoring framework —
13
+ a small, dependency-free "tier 0.5" package consumed by both higher tiers:
14
+
15
+ | Tier | Layer | Package |
16
+ |---|---|---|
17
+ | 0.5 | **rule metadata** | `galaxy-tool-refactor-rules` *(this package)* |
18
+ | 1 | parsing & validation | `galaxy-tool-source` |
19
+ | 2 | structure | `galaxy-tool-codemod` |
20
+ | 3 | formatting | `galaxy-tool-fmt` |
21
+ | 3.5 | advisory checks | `galaxy-tool-lint` |
22
+ | 3.6 | rule registry / rulesets | `galaxy-tool-refactor-registry` |
23
+ | 4 | app / CLI | `galaxy-tool-refactor-cli` |
24
+
25
+ It provides:
26
+
27
+ - **`galaxy_tool_refactor_rules.meta.RuleMeta`** — a frozen dataclass describing
28
+ one GTR rule (`code`, `summary`, `since`, `until`, `cite`, `order`,
29
+ `detect_only`, `applies_to`). A tier-3 formatter `Rule`, a tier-2
30
+ `CodemodCommand`, and a tier-3.5 `CheckRule` each carry a
31
+ `meta: ClassVar[RuleMeta]`, so the tiers share one registry vocabulary.
32
+ - **`galaxy_tool_refactor_rules.violation.Violation`** — a frozen dataclass for a
33
+ detect-phase finding (`code`, `sourceline`, `xpath`, `message`); the pure,
34
+ lxml-free, read-only counterpart to the mutating tier-2 `Change` / tier-3 `Edit`.
35
+ - **`galaxy_tool_refactor_rules.reference.render_rule_reference_table`** — a pure
36
+ helper that renders `(RuleMeta, tier)` pairs as a GitHub-flavored markdown
37
+ glossary table.
38
+
39
+ ## Why a separate package
40
+
41
+ The descriptor is the only thing the two tiers genuinely share — their
42
+ *behavioral* bases differ (fmt yields lxml edits; codemod walks a cursor), so
43
+ those stay in their own packages. Keeping `RuleMeta` here, with **zero runtime
44
+ dependencies**, lets both fmt and codemod depend on it without depending on each
45
+ other — preserving the tier independence documented in
46
+ `galaxy-tool-fmt/docs/decisions.md` §D10. The extraction was anticipated in
47
+ that package's §D1 ("a shared rule-engine package will be extracted only when a
48
+ second consumer materialises"); the codemod tier is that consumer.
49
+
50
+ ## Install / test
51
+
52
+ ```bash
53
+ uv sync # from the workspace root
54
+ uv run --package galaxy-tool-refactor-rules pytest galaxy-tool-refactor-rules/tests/
55
+ ```
@@ -0,0 +1,9 @@
1
+ galaxy_tool_refactor_rules/__init__.py,sha256=Q0rYk_ovDHfo8JCPwqW1NK3gH599PSc6ArOfIITj2Go,535
2
+ galaxy_tool_refactor_rules/meta.py,sha256=IQ9dvG2bnqk4sanBopvPsZnLoXq8g6ZmNfmpRJRVqIQ,5015
3
+ galaxy_tool_refactor_rules/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ galaxy_tool_refactor_rules/reference.py,sha256=kfnBf1uYStiucwZZsqakMpnwjlCVnczRWmqwedlKcKA,1691
5
+ galaxy_tool_refactor_rules/rulesets.py,sha256=TzsBGU6pMxSeh9pYrEiYWvfj19o_pHyTtnqj1FxhmXY,2968
6
+ galaxy_tool_refactor_rules/violation.py,sha256=2HYnrxByc_6_8qSVwwo-EqIPzeJ8Y_rER-GjC6a0GFo,1610
7
+ galaxy_tool_refactor_rules-0.2.0.dist-info/METADATA,sha256=urxww9kbIu0D6U3Mu9tSGeGjZbiS1ho1BiVssOqfZvU,2440
8
+ galaxy_tool_refactor_rules-0.2.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
9
+ galaxy_tool_refactor_rules-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any