Skip to content

Correct the stale README examples and fill the documentation gaps - #146

Merged
ESultanik merged 8 commits into
masterfrom
docs-readme-filetypes
Sep 9, 2026
Merged

ESultanik merged 8 commits into
masterfrom
docs-readme-filetypes

Conversation

@ESultanik

Copy link
Copy Markdown
Collaborator

Corrects the stale and missing documentation in README.md, docs/filetypes.rst, docs/index.rst, and the PyPI description in pyproject.toml. No source file changes.

What was stale

Every JSON output example in the README printed something other than what it claimed. Two changes had invalidated them:

  • DictNode.from_dict (graphtage/graphtage.py:548-550) sorts keys when it builds a tree, so output orders keys alphabetically regardless of input order.
  • PR Select optimal Levenshtein paths in EditDistance #132 changed the edit tie-break order to emit removals before insertions, so "bar" renaming to "zab" renders as "b̶z̟ar̶b̟", not "z̟b̶ab̟r̶".

Regenerated: the headline example, all four Output Formatting examples, and the marked-up key in the git diff example. The YAML block in the Git Integration section still reproduces byte for byte and is unchanged.

Two factual errors. The Matching Options section opened by describing --dict-strategy match while the list three lines below correctly named auto as the default (graphtage/__main__.py:293-304). The copyright notice read 2023 while docs/conf.py read 2026.

docs/filetypes.rst documented code that no longer exists. The MIME registration snippet was moved out of graphtage/pickle.py into the centralized register_mimetypes() in graphtage/__main__.py. Worse, an out-of-tree Filetype that followed the old snippet failed silently: register_mimetypes() opens with mimetypes.init(), which rebuilds the global types_map and discards any add_type() call made earlier at import time. The page now says where registration has to happen, and gives the working sequence for a filetype defined outside the package.

The Filetype sketch on that page was not valid Python: build_tree and get_default_formatter had comment-only bodies (IndentationError), and os and PickleDecodeError were used without imports. It is now the real graphtage.pickle implementation with absolute imports, verified to build a tree.

What was missing

README:

  • The --from-<type>/--to-<type> overrides (20 generated flags), --from-mime/--to-mime, -ds, --no-key-edits/-k, --debug, --version/-v, and -dumpversion, none of which appeared anywhere in the README or the docs.
  • A "Match Constraints" section for --match-if and --match-unless, the only user-facing surface of graphtage.expressions and graphtage.constraints, with a worked example.
  • The exit status contract for graphtage itself. The README stated it for the diff driver but not for the command a script or CI job calls, where 1 means "the inputs differ", not "an error occurred".
  • The Python floor (3.10, tested through 3.14), the two console scripts installed, and the dev extra. The Git Integration section previously assumed graphtage-git-diff existed without saying where it came from.

docs/filetypes.rst documented only the Filetype subclass. It now also covers importing the module in graphtage/__init__.py, adding the extension to register_mimetypes(), and the four rules governing formatters: routing sequence printing through SequenceFormatter.print_SequenceNode, aliasing print_UnorderedListNode to print_ListNode, marking helper formatters is_partial = True, and defining each print_<NodeType> once per formatter tree.

pyproject.toml's description, which is what shows on PyPI, listed seven of the ten registered formats, dropping JSON5, HTML, and Python pickle.

Verification

  • Every example in the README was executed and its real output pasted in. A script confirms each block appears verbatim in the file.
  • The docs/filetypes.rst sketch runs: with its identifiers renamed to avoid colliding with the shipped graphtage.pickle, it registers, builds a tree from a real .pkl file, and returns its formatter.
  • sphinx-build -W --keep-going -E -b html docs docs/_build/html produces the same two warnings as master (both in graphtage/__init__.py docstrings, owned by another PR in this series) and no new ones.
  • ruff check graphtage test docs bindist passes.

Defects found, not fixed

These are behaviors observed while verifying the examples. No source file was changed.

  1. --match-if cannot be used as its --help text describes. MatchIf binds from and to to raw TreeNode objects, while MatchUnless binds them to to_obj() results (graphtage/constraints.py:24,35). The constraint is applied to every node in the tree (__main__.py:338-342), and MatchIf returns a Replace when the expression raises, so the help text's own example, --match-if "from['foo'] == to['bar']", refuses every pair including the two roots and collapses the whole diff into one replacement. Confirmed with from['id'] == to['id'], from['id'].value == to['id'].value, and from.to_obj()['id'] == to.to_obj()['id'], all of which replace the entire tree. --match-unless works because its failure mode is "leave the pair unconstrained". The README documents the working option and warns about the other; the underlying inconsistency is worth fixing separately.

  2. A Replace edit inside a list prints its replacement twice. Diffing [1, {"a": 1}] against [1, [2, 3]] prints {...} -> [2, 3] -> [2, 3]. The same Replace as a dictionary value ({"x": {"a": 1}} against {"x": [2, 3]}) prints correctly, so this is specific to sequence rendering.

  3. -dumpversion prints 0 . 3 . 1. __main__.py:219 is ' '.join(map(str, version.__version__)), but version.__version__ is now the string "0.3.1" (graphtage/version.py:7), so the join iterates characters. It looks like a leftover from when __version__ was a tuple; __version_tuple__ still exists for that.

  4. --join-dict-items indents oddly. With -jd, a nested list is printed with an 8-space item indent and a 4-space closing bracket even though the enclosing dictionary is on one line: {"bar": "baz","foo": [\n 1,\n 2,\n 3\n ]}. The README now documents what the tool prints rather than what it ought to print.

  5. A closed stdout raises rather than exiting quietly. Piping output to a command that exits early (graphtage a.json b.json | head -3) produces three chained BrokenPipeError tracebacks instead of a clean exit.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GypKU5KdLfs2Cf8kS2TzJa

ESultanik and others added 8 commits September 9, 2026 08:41
The PyPI description listed seven of the ten formats Graphtage registers,
omitting JSON5, HTML, and Python pickle. Match README.md and
docs/index.rst, which both already list all ten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GypKU5KdLfs2Cf8kS2TzJa
Pull request #132 changed the edit tie-break order to emit removals
before insertions, and DictNode.from_dict sorts keys when it builds a
tree, so every JSON example printed something other than what it
claimed. Each block is now the verbatim output of the command above it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GypKU5KdLfs2Cf8kS2TzJa
The Matching Options section opened by describing `--dict-strategy
match`, contradicting the list below it and graphtage/__main__.py, where
`auto` is the default. The copyright notice trailed docs/conf.py by
three years.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GypKU5KdLfs2Cf8kS2TzJa
The Installation section was a bare pip command. It now states the
supported Python versions from pyproject.toml, names both console
scripts the package installs, and points at the dev extra.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GypKU5KdLfs2Cf8kS2TzJa
Adds the file type overrides, the short forms of --dict-strategy and
--no-key-edits, --debug, and a section on --match-if and --match-unless,
which were the only user-facing surface of graphtage.expressions and
graphtage.constraints and appeared in neither the README nor the docs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GypKU5KdLfs2Cf8kS2TzJa
The README documented the exit statuses of the diff driver but never
those of graphtage itself, which is what a script or CI job needs. Also
documents --version and -dumpversion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GypKU5KdLfs2Cf8kS2TzJa
The MIME registration snippet no longer matched the code: the pickle
module dropped it when register_mimetypes() centralized the registrations
in graphtage/__main__.py. Following the old snippet from an out-of-tree
filetype silently failed, because register_mimetypes() opens with
mimetypes.init(), which discards any earlier add_type() call.

The Filetype sketch raised IndentationError as written, since two methods
had comment-only bodies, and used os and PickleDecodeError without
importing them. It is now the real implementation, verified to build a
tree.

The page also described only the Filetype subclass, omitting the module
import, the MIME entry, and the four formatter rules that a working
format needs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GypKU5KdLfs2Cf8kS2TzJa
@ESultanik

Copy link
Copy Markdown
Collaborator Author

The code defects noted in the PR description are now tracked:

@ESultanik
ESultanik merged commit c4b331a into master Sep 9, 2026
12 checks passed
@ESultanik
ESultanik deleted the docs-readme-filetypes branch September 9, 2026 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant