mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-08-28 05:25:04 +00:00
* feat: simplify local links and support line anchors * fix: align line anchor tests with CI lint * fix: preserve local links across file moves * fix: encode markdown paths when rewriting links * refactor(read): keep explicit line range parameters * fix: simplify legacy link predicate compatibility * docs: align local link behavior with implementation * fix: skip unsupported markdown destination escapes * fix: normalize workspace link paths across platforms * fix: bound markdown link scanning * fix: keep local link processing linear * docs: clarify permissive markdown link parsing * fix: handle local link processing failures * refactor: limit file links to wikilink syntax * docs: align wikilink contract with implementation * fix: normalize dream and neighbor paths on Windows * fix: resolve workspace path for neighbor expansion
43 lines
1.7 KiB
Python
43 lines
1.7 KiB
Python
"""Parse and format GitHub-style 1-based line-anchor strings."""
|
|
|
|
import re
|
|
|
|
_LINE_ANCHOR_RE = re.compile(r"L[0-9]+(?:-L[0-9]+)?(?:,L[0-9]+(?:-L[0-9]+)?)*")
|
|
|
|
|
|
def parse_line_anchor(anchor: str | None) -> list[tuple[int, int]] | None:
|
|
"""Return normalized inclusive ranges, or ``None`` for a non-line anchor.
|
|
|
|
Supported forms are ``L9``, ``L9-L10`` and
|
|
``L9-L10,L15-L20``. Overlapping and adjacent ranges are merged.
|
|
Anchors beginning with ``L<digit>`` are treated as line anchors and raise
|
|
``ValueError`` when malformed, zero-based, or reversed.
|
|
"""
|
|
if not anchor or not re.match(r"L[0-9]", anchor):
|
|
return None
|
|
if not _LINE_ANCHOR_RE.fullmatch(anchor):
|
|
raise ValueError(f"invalid line anchor: #{anchor}")
|
|
|
|
ranges: list[tuple[int, int]] = []
|
|
for item in anchor.split(","):
|
|
start_text, separator, end_text = item.partition("-L")
|
|
start = int(start_text[1:])
|
|
end = int(end_text) if separator else start
|
|
if start < 1 or end < 1:
|
|
raise ValueError("line numbers must be at least 1")
|
|
if start > end:
|
|
raise ValueError(f"line range start ({start}) exceeds end ({end})")
|
|
ranges.append((start, end))
|
|
|
|
merged: list[tuple[int, int]] = []
|
|
for start, end in sorted(ranges):
|
|
if merged and start <= merged[-1][1] + 1:
|
|
merged[-1] = (merged[-1][0], max(merged[-1][1], end))
|
|
else:
|
|
merged.append((start, end))
|
|
return merged
|
|
|
|
|
|
def format_line_anchor(ranges: list[tuple[int, int]]) -> str:
|
|
"""Render normalized ranges without the leading ``#``."""
|
|
return ",".join(f"L{start}" if start == end else f"L{start}-L{end}" for start, end in ranges)
|