Skip to content

fix(parsers): normalize CRLF/CR to LF in text & markdown parsers#59

Merged
officialCodeWork merged 1 commit into
mainfrom
fix/parsers-windows-crlf
May 25, 2026
Merged

fix(parsers): normalize CRLF/CR to LF in text & markdown parsers#59
officialCodeWork merged 1 commit into
mainfrom
fix/parsers-windows-crlf

Conversation

@officialCodeWork

Copy link
Copy Markdown
Owner

Summary

PR #57 (Step 1.3) merged with a failing windows-latest job. Root cause: the PlainTextParser paragraph regex \n[ \t]*\n+ can't see across the \r between two CRLF-terminated lines. The ragctl test was using Path.write_text, which emits CRLF on Windows, so Windows saw one paragraph instead of two.

Fixing the parsers (not just the test) keeps blocks platform-stable for any CRLF / bare-CR input — including content fetched from connectors on a Windows host or files authored on Windows. This is the standing cross-platform requirement (CLAUDE.md: "every deliverable must work on Windows, macOS, and Linux without WSL").

What changed

  • PlainTextParser._decode normalises \r\n and bare \r to \n after UTF-8 / latin-1 decode, before the paragraph regex runs.
  • MarkdownParser.parse does the same after decode so markdown-it's line-based Token.map offsets stay in sync with the text we return.
  • Other parsers (HTML, JSON, YAML, CSV, PDF, DOCX, PPTX, XLSX) use libraries that handle CRLF natively — no changes there.

Tests

  • tests/parsers/test_text_family.py — 3 new platform-independent tests via write_bytes:
    • test_plain_text_crlf_paragraph_split — CRLF input → 2 paragraphs.
    • test_plain_text_bare_cr_normalized — classic-Mac bare-CR input → 2 paragraphs.
    • test_markdown_crlf_input — CRLF markdown → heading + paragraph + list_item.
  • packages/ragctl/tests/test_parse.py — three text fixtures switched to write_bytes(b\"...\\n...\") (defence in depth against platform newline translation in CLI-level tests).

Verified locally

  • uv run pytest tests/ packages/ -x -q — full suite passes (no regressions).
  • uv run ruff check / ruff format --check — clean.
  • uv run mypy packages/parsers/ — clean.

Documentation

No public-API surface change — internal normalisation only, transparent to callers. No docs/ update needed; the existing docs/reference/parsers.md and docs/architecture/parsers.md continue to apply.

Test plan

  • CI passes on windows-latest (specifically the previously-failing Lint & Test (windows-latest) job + the CI passed rollup).
  • CI still passes on ubuntu-22.04 and macos-14.

🤖 Generated with Claude Code

PR #57 (Step 1.3) merged with a failing windows-latest job: test_parse_plain_text
expected 2 paragraphs but saw 1 because the PlainTextParser's blank-line splitter
(\n[ \t]*\n+) can't see across the \r between two CRLF-terminated lines, and the
test wrote the sample with Path.write_text which emits CRLF on Windows.

Root-cause fix in the parsers (not just the test) so any CRLF or bare-CR input —
including content fetched from connectors on a Windows host or downloaded from
Windows-authored sources — produces the same blocks on every platform.

- PlainTextParser._decode now normalises \r\n and bare \r to \n after UTF-8/latin-1
  decode, before the paragraph regex runs.
- MarkdownParser.parse does the same after decode so markdown-it's line-based
  Token.map offsets stay in sync with the text we hand back.
- Two new tests in tests/parsers/test_text_family.py exercise CRLF + bare-CR
  inputs via write_bytes so they're platform-independent.
- packages/ragctl/tests/test_parse.py switched to write_bytes(b"...\n...") for
  the three text fixtures — defence in depth against platform newline translation
  in the integration-style CLI tests.

Other parsers (HTML / JSON / YAML / CSV / PDF / DOCX / PPTX / XLSX) use libraries
that handle CRLF natively, so no changes there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@officialCodeWork
officialCodeWork merged commit 8083570 into main May 25, 2026
11 checks passed
officialCodeWork added a commit that referenced this pull request May 25, 2026
chore(tracker): sync Step 1.4 → ✅ and log PRs #58, #59, #60
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