🐛 Sort need links and backlinks naturally in needs.json and HTML#1695
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1695 +/- ##
==========================================
+ Coverage 86.87% 89.33% +2.45%
==========================================
Files 56 72 +16
Lines 6532 10372 +3840
==========================================
+ Hits 5675 9266 +3591
- Misses 857 1106 +249
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
This PR is missing a performance analysis. Sorting links in big projects can be expensive. |
Address PR #1695 review comments from @chrisjsewell: - `_natural_sort_key` now uses `tok.isdigit()` and `tok.casefold()` so the sort is case-insensitive (matches the ubcode helper). - `NeedItem.sort_links()` collapses duplicate `NeedLink` entries (same id, part, condition) while sorting outgoing links, backlinks and part backlinks. Snapshot updates fall out naturally: - `test_dynamic_functions`: duplicate `CON_REQ_2` collapsed. - `test_needimport`: `filter_IMPL_01` now sorts before `IMPL_01` / `T_C3893` under case-insensitive ordering.
Need link and backlink lists are now naturally sorted (e.g. ``REQ_2`` < ``REQ_9`` < ``REQ_10``) so build outputs are reproducible regardless of need load order, including when ``needs_external_needs`` is configured. Sorting happens unconditionally — no ``needs_reproducible_json`` flag is required — and applies to both ``needs.json`` and HTML output. Implementation: ``NeedItem.sort_links()`` sorts ``_links``, ``_backlinks``, and per-part backlinks in place using a natural sort key that splits digit runs and compares them as ints. ``resolve_links()`` calls this on every need at the end of the post-processing pass, so all downstream consumers (JSON serialization, HTML rendering of outgoing/incoming refs) see sorted lists. Tests: new ``tests/test_needs_sort.py`` with YAML-driven parametric cases covering alphabetical/natural/mixed sorting, backlinks, and custom link types — checks both the JSON and rendered HTML against snapshots. Closes #1371
Adds two fixture cases with identical RST content but opposite values for ``needs_reproducible_json``. Their snapshots end up byte-identical, which documents that sorting happens regardless of the flag.
Address PR #1695 review comments from @chrisjsewell: - `_natural_sort_key` now uses `tok.isdigit()` and `tok.casefold()` so the sort is case-insensitive (matches the ubcode helper). - `NeedItem.sort_links()` collapses duplicate `NeedLink` entries (same id, part, condition) while sorting outgoing links, backlinks and part backlinks. Snapshot updates fall out naturally: - `test_dynamic_functions`: duplicate `CON_REQ_2` collapsed. - `test_needimport`: `filter_IMPL_01` now sorts before `IMPL_01` / `T_C3893` under case-insensitive ordering.
Performance impact analysisProject under test
Each outgoing-link relation also produces a computed backlink list per target need; this PR's natural-sort change applies to both outgoing-link lists and the computed backlinks, both of which are reflected in Both runs use the same input corpus and produce the same Methodology
Results
Δ (PR − baseline): −0.03 s (−0.05 %) — well inside the run-to-run noise (stdev ≈ 1.4 s, ~2.6 % of mean). No measurable regression at this corpus size. |
Summary
Closes #1371.
Need link and backlink lists are now naturally sorted (e.g.
REQ_2<REQ_9<REQ_10) so build outputs are reproducible regardless of need load order, including whenneeds_external_needsis configured. Sorting happens unconditionally — noneeds_reproducible_jsonflag is required — and applies to bothneeds.jsonand HTML output.This matches the behaviour
@ubmarcoconfirmed was desired: sort by default, in both outputs, irrespective of theneeds_reproducible_jsonsetting (which still keeps its existing role of stripping timestamps).Implementation
NeedItem.sort_links()sorts_links,_backlinksand per-partbacklinksin place using a natural sort key (digit runs are compared as ints, with the result alternatingstr/intso mixed-type comparisons are well-defined).resolve_links()callssort_links()on every need at the end of the post-processing pass, so all downstream consumers — JSON serialization inneedsfile.pyand the HTML rendering of outgoing/incoming refs inroles/need_outgoing.pyandroles/need_incoming.py— see sorted lists.Tests
New
tests/test_needs_sort.pyis YAML-driven and snapshot-based, modelled ontests/schema/test_schema.py:tests/fixtures/sort_links.ymldeclares 5 cases:alphabetical_outgoing— basic alphabetical sort of outgoing linksnatural_outgoing— natural sort with digits (provesREQ_10lands afterREQ_9)mixed_alphanumeric— mixed letter+number IDsbacklinks_sorted— backlinks naturally sorted (SPEC_1, SPEC_2, SPEC_10)custom_link_types— sort works for custom link types (implements,derives)needs.json, derives link-field names from the embeddedneeds_schema(field_type ∈ {links, backlinks}), and snapshots the link/backlink fields per need. It also readsindex.html, extracts(source need, target need)ref pairs by matchingtitle="<source>"on link anchors, and snapshots that mapping. Both snapshots clearly show the natural ordering.Existing snapshots in 8 unrelated test files were updated for the new sorted order; all changes are mechanical reorderings within link lists.
Test plan
pytest tests/test_needs_sort.py— 5 cases pass, 10 snapshots matchpytest tests/— no link-related regressionsChangelog
The bug-fix entry is in a new
Unreleasedsection above 8.0.0 (now marked released on 19.03.2026).