any2md 0.5.0__tar.gz → 1.0.0__tar.gz
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.
- {any2md-0.5.0 → any2md-1.0.0}/.gitignore +5 -0
- any2md-1.0.0/CHANGELOG.md +233 -0
- any2md-1.0.0/CONTRIBUTING.md +148 -0
- any2md-1.0.0/PKG-INFO +529 -0
- any2md-1.0.0/README.md +499 -0
- {any2md-0.5.0 → any2md-1.0.0}/any2md/__init__.py +1 -1
- {any2md-0.5.0 → any2md-1.0.0}/any2md/__main__.py +1 -0
- any2md-1.0.0/any2md/_docling.py +37 -0
- any2md-1.0.0/any2md/cli.py +403 -0
- any2md-1.0.0/any2md/config.py +81 -0
- any2md-1.0.0/any2md/converters/__init__.py +86 -0
- any2md-1.0.0/any2md/converters/docx.py +168 -0
- any2md-1.0.0/any2md/converters/html.py +261 -0
- any2md-1.0.0/any2md/converters/pdf.py +253 -0
- {any2md-0.5.0 → any2md-1.0.0}/any2md/converters/txt.py +68 -74
- any2md-1.0.0/any2md/frontmatter.py +304 -0
- any2md-1.0.0/any2md/pipeline/__init__.py +73 -0
- any2md-1.0.0/any2md/pipeline/cleanup.py +129 -0
- any2md-1.0.0/any2md/pipeline/structured.py +142 -0
- any2md-1.0.0/any2md/pipeline/text.py +291 -0
- any2md-1.0.0/any2md/utils.py +70 -0
- any2md-1.0.0/any2md/validators.py +26 -0
- any2md-1.0.0/docs/architecture.md +578 -0
- any2md-1.0.0/docs/cli-reference.md +541 -0
- any2md-1.0.0/docs/output-format.md +774 -0
- any2md-1.0.0/docs/superpowers/plans/2026-04-26-any2md-v1-phase1-foundation.md +3766 -0
- any2md-1.0.0/docs/superpowers/specs/2026-04-26-any2md-v1-design.md +928 -0
- any2md-1.0.0/docs/troubleshooting.md +473 -0
- any2md-1.0.0/docs/upgrading-from-0.7.md +236 -0
- {any2md-0.5.0 → any2md-1.0.0}/pyproject.toml +19 -7
- any2md-1.0.0/requirements.txt +7 -0
- any2md-1.0.0/scripts/validate-phase1.sh +32 -0
- any2md-1.0.0/tests/__init__.py +0 -0
- any2md-1.0.0/tests/cli/__init__.py +0 -0
- any2md-1.0.0/tests/cli/test_cli_existing_flags.py +52 -0
- any2md-1.0.0/tests/cli/test_cli_exit_codes.py +71 -0
- any2md-1.0.0/tests/cli/test_cli_high_fidelity.py +39 -0
- any2md-1.0.0/tests/cli/test_cli_meta.py +36 -0
- any2md-1.0.0/tests/cli/test_cli_ocr_save_images.py +34 -0
- any2md-1.0.0/tests/cli/test_cli_quiet_verbose.py +34 -0
- any2md-1.0.0/tests/cli/test_cli_strict.py +47 -0
- any2md-1.0.0/tests/conftest.py +25 -0
- any2md-1.0.0/tests/fixtures/__init__.py +0 -0
- any2md-1.0.0/tests/fixtures/docs/ligatures_and_softhyphens.txt +5 -0
- any2md-1.0.0/tests/fixtures/docs/multi_column.pdf +93 -0
- any2md-1.0.0/tests/fixtures/docs/table_heavy.docx +0 -0
- any2md-1.0.0/tests/fixtures/docs/web_page.html +35 -0
- any2md-1.0.0/tests/fixtures/make_fixtures.py +140 -0
- any2md-1.0.0/tests/fixtures/snapshots/ligatures_and_softhyphens.md +25 -0
- any2md-1.0.0/tests/fixtures/snapshots/multi_column.docling.md +28 -0
- any2md-1.0.0/tests/fixtures/snapshots/multi_column.fallback.md +31 -0
- any2md-1.0.0/tests/fixtures/snapshots/table_heavy.docling.md +31 -0
- any2md-1.0.0/tests/fixtures/snapshots/table_heavy.fallback.md +33 -0
- any2md-1.0.0/tests/fixtures/snapshots/web_page.md +38 -0
- any2md-1.0.0/tests/integration/__init__.py +0 -0
- any2md-1.0.0/tests/integration/test_docx_docling.py +30 -0
- any2md-1.0.0/tests/integration/test_docx_mammoth_fallback.py +42 -0
- any2md-1.0.0/tests/integration/test_html_trafilatura.py +29 -0
- any2md-1.0.0/tests/integration/test_pdf_docling.py +48 -0
- any2md-1.0.0/tests/integration/test_pdf_pymupdf_fallback.py +37 -0
- any2md-1.0.0/tests/integration/test_save_images.py +24 -0
- any2md-1.0.0/tests/integration/test_snapshots.py +87 -0
- any2md-1.0.0/tests/integration/test_txt.py +32 -0
- any2md-1.0.0/tests/unit/__init__.py +0 -0
- any2md-1.0.0/tests/unit/pipeline/__init__.py +0 -0
- any2md-1.0.0/tests/unit/pipeline/test_cleanup_footnote_markers.py +40 -0
- any2md-1.0.0/tests/unit/pipeline/test_cleanup_ligatures.py +31 -0
- any2md-1.0.0/tests/unit/pipeline/test_cleanup_nfc.py +22 -0
- any2md-1.0.0/tests/unit/pipeline/test_cleanup_quotes_dashes.py +29 -0
- any2md-1.0.0/tests/unit/pipeline/test_cleanup_soft_hyphens.py +19 -0
- any2md-1.0.0/tests/unit/pipeline/test_cleanup_validate.py +32 -0
- any2md-1.0.0/tests/unit/pipeline/test_cleanup_whitespace.py +38 -0
- any2md-1.0.0/tests/unit/pipeline/test_runner.py +44 -0
- any2md-1.0.0/tests/unit/pipeline/test_structured_compact_tables.py +36 -0
- any2md-1.0.0/tests/unit/pipeline/test_structured_heading_hierarchy.py +34 -0
- any2md-1.0.0/tests/unit/pipeline/test_structured_lift_figures.py +40 -0
- any2md-1.0.0/tests/unit/pipeline/test_structured_normalize_cites.py +25 -0
- any2md-1.0.0/tests/unit/pipeline/test_text_dedupe_paragraphs.py +27 -0
- any2md-1.0.0/tests/unit/pipeline/test_text_dedupe_toc.py +72 -0
- any2md-1.0.0/tests/unit/pipeline/test_text_dehyphenate.py +32 -0
- any2md-1.0.0/tests/unit/pipeline/test_text_repair_line_wraps.py +47 -0
- any2md-1.0.0/tests/unit/pipeline/test_text_restore_lists_code.py +29 -0
- any2md-1.0.0/tests/unit/pipeline/test_text_strip_headers_footers.py +36 -0
- any2md-1.0.0/tests/unit/test_auto_id.py +64 -0
- any2md-1.0.0/tests/unit/test_config.py +52 -0
- any2md-1.0.0/tests/unit/test_content_hash.py +40 -0
- any2md-1.0.0/tests/unit/test_docling_helper.py +28 -0
- any2md-1.0.0/tests/unit/test_frontmatter_compose.py +138 -0
- any2md-1.0.0/tests/unit/test_frontmatter_helpers.py +124 -0
- any2md-1.0.0/tests/unit/test_meta_overrides.py +81 -0
- any2md-1.0.0/tests/unit/test_pdf_complexity.py +47 -0
- any2md-1.0.0/tests/unit/test_validators.py +34 -0
- any2md-0.5.0/CHANGELOG.md +0 -27
- any2md-0.5.0/PKG-INFO +0 -272
- any2md-0.5.0/README.md +0 -250
- any2md-0.5.0/any2md/cli.py +0 -145
- any2md-0.5.0/any2md/converters/__init__.py +0 -34
- any2md-0.5.0/any2md/converters/docx.py +0 -72
- any2md-0.5.0/any2md/converters/html.py +0 -162
- any2md-0.5.0/any2md/converters/pdf.py +0 -73
- any2md-0.5.0/any2md/utils.py +0 -86
- any2md-0.5.0/requirements.txt +0 -6
- {any2md-0.5.0 → any2md-1.0.0}/LICENSE +0 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
|
|
7
|
+
## [1.0.0] — 2026-04-26
|
|
8
|
+
|
|
9
|
+
First stable release of any2md v1.0. Validated end-to-end against
|
|
10
|
+
real-world documents (a 164-page PDF technical standard via Docling,
|
|
11
|
+
a multi-page academic DOCX, and a Wikipedia article via trafilatura).
|
|
12
|
+
The output contract — SSRM-compatible frontmatter, deterministic
|
|
13
|
+
`content_hash`, NFC + LF body normalization — is now stable. Downstream
|
|
14
|
+
consumers can rely on this shape.
|
|
15
|
+
|
|
16
|
+
Functional contents are identical to 1.0.0rc1; only the version is bumped.
|
|
17
|
+
|
|
18
|
+
## [1.0.0rc1] — 2026-04-26
|
|
19
|
+
|
|
20
|
+
Phase 4: configuration, polish, and the v1.0 documentation set.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- `--profile {conservative,aggressive,maximum}` flag — tunes minimization
|
|
24
|
+
aggressiveness. `conservative` skips TOC dedupe and footnote-marker
|
|
25
|
+
stripping; `aggressive` (default) runs the full pipeline; `maximum`
|
|
26
|
+
additionally implies `--strip-links`.
|
|
27
|
+
- `--meta KEY=VAL` repeatable flag for frontmatter overrides. Dotted keys
|
|
28
|
+
set nested fields (e.g. `--meta generation_metadata.authored_by=human`);
|
|
29
|
+
comma-separated values become arrays
|
|
30
|
+
(e.g. `--meta authors="Alice, Bob"`).
|
|
31
|
+
- `--meta-file PATH` flag plus auto-discovery of `.any2md.toml` (walks up
|
|
32
|
+
from cwd). The `[meta]` table supplies frontmatter overrides; the
|
|
33
|
+
`[document_id]` table supplies `--auto-id` prefix and type code.
|
|
34
|
+
- `--auto-id` flag — generates an SSRM-conformant `document_id` as
|
|
35
|
+
`{PREFIX}-{YYYY}-{TYPE}-{SHA8}`. Defaults to `LOCAL`/`DOC`; override via
|
|
36
|
+
the `[document_id]` table in `.any2md.toml`.
|
|
37
|
+
- `--strict` flag — promotes pipeline validation warnings (heading
|
|
38
|
+
hierarchy auto-fixes, missing H1, `content_hash` round-trip mismatches)
|
|
39
|
+
to a non-zero exit.
|
|
40
|
+
- `--quiet` / `-q` flag — suppresses the per-file `OK:` lines. Errors and
|
|
41
|
+
the final summary still print.
|
|
42
|
+
- `--verbose` / `-v` flag — adds per-file pipeline stage timings.
|
|
43
|
+
- New exit code contract: `0` success, `1` usage or install error, `2` at
|
|
44
|
+
least one file failed entirely, `3` at least one file produced warnings
|
|
45
|
+
AND `--strict` was set.
|
|
46
|
+
- Per-file summary line now includes backend, lane, token estimate, and
|
|
47
|
+
warning count when present.
|
|
48
|
+
- New module `any2md/config.py` for `.any2md.toml` discovery and parsing
|
|
49
|
+
(uses stdlib `tomllib` on 3.11+, `tomli` on 3.10).
|
|
50
|
+
- Comprehensive documentation set:
|
|
51
|
+
- `README.md` rewritten with educational tone — every command preceded
|
|
52
|
+
by why you'd run it and followed by what you'll see.
|
|
53
|
+
- `docs/output-format.md` — SSRM-compatible field reference and the
|
|
54
|
+
`content_hash` recomputation recipe.
|
|
55
|
+
- `docs/cli-reference.md` — flag-by-flag reference with use cases and a
|
|
56
|
+
worked-example matrix.
|
|
57
|
+
- `docs/architecture.md` — pipeline internals, stage catalog, and
|
|
58
|
+
contributor guide.
|
|
59
|
+
- `docs/troubleshooting.md` — symptom-cause-fix triage guide.
|
|
60
|
+
- `docs/upgrading-from-0.7.md` — migration guide.
|
|
61
|
+
- `CONTRIBUTING.md` — dev setup, test commands, coding standards,
|
|
62
|
+
release flow, PR process.
|
|
63
|
+
- GitHub issue templates: generic bug report, conversion quality.
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
- `frontmatter.compose()` now accepts an `overrides: dict | None` argument
|
|
67
|
+
that deep-merges over derived fields. Used by `--meta`, `--meta-file`,
|
|
68
|
+
and `.any2md.toml`.
|
|
69
|
+
- `cli.main()` exit-code logic refactored to track failures and
|
|
70
|
+
strict-mode warnings separately, via the new
|
|
71
|
+
`any2md.converters.collected_warnings()` helper.
|
|
72
|
+
- `--profile maximum` implies `--strip-links` automatically.
|
|
73
|
+
|
|
74
|
+
## [1.0.0a3] — 2026-04-26
|
|
75
|
+
|
|
76
|
+
Phase 3: text-lane post-processing stages and image/OCR flag wiring.
|
|
77
|
+
|
|
78
|
+
### Added
|
|
79
|
+
- Text-lane stages T1–T6 now active for all non-Docling output paths
|
|
80
|
+
(TXT, trafilatura HTML/URL, mammoth DOCX fallback, pymupdf4llm PDF
|
|
81
|
+
fallback):
|
|
82
|
+
- **T1 `repair_line_wraps`** — joins soft-wrapped lines inside
|
|
83
|
+
paragraphs while preserving lists, tables, code blocks, headings.
|
|
84
|
+
- **T2 `dehyphenate`** — merges `co-\noperation` → `cooperation`
|
|
85
|
+
when the joined word appears elsewhere in the document
|
|
86
|
+
(same-doc corroboration; conservative).
|
|
87
|
+
- **T3 `dedupe_paragraphs`** — drops a paragraph if it's identical to
|
|
88
|
+
the immediately previous one (PDF over-extraction artifact).
|
|
89
|
+
- **T4 `dedupe_toc_block`** — strips leading TOC blocks when ≥70% of
|
|
90
|
+
their entries reappear as H2/H3 headings later. Aggressive/maximum
|
|
91
|
+
profiles only.
|
|
92
|
+
- **T5 `strip_running_headers_footers`** — removes lines that repeat
|
|
93
|
+
≥3 times across page boundaries (form-feed `\f`-aware).
|
|
94
|
+
- **T6 `restore_lists_and_code`** — wraps ≥4-line indented blocks
|
|
95
|
+
in fenced code.
|
|
96
|
+
- New CLI flags `--ocr-figures` and `--save-images`. Both imply
|
|
97
|
+
`--high-fidelity`. With `--save-images`, the PDF Docling path writes
|
|
98
|
+
extracted images to `<output_dir>/images/<source_stem>/imgN.png`.
|
|
99
|
+
|
|
100
|
+
### Changed
|
|
101
|
+
- `repair_line_wraps` runs before `dehyphenate` so hyphens that span
|
|
102
|
+
joined lines are merged correctly.
|
|
103
|
+
- Snapshot fixtures regenerated to reflect text-lane stage output.
|
|
104
|
+
|
|
105
|
+
## [1.0.0a2] — 2026-04-26
|
|
106
|
+
|
|
107
|
+
Phase 2: Docling backend integration. PDFs and DOCX files can now be
|
|
108
|
+
extracted via Docling for substantially higher fidelity on multi-column
|
|
109
|
+
layouts and complex tables. Docling is an optional install — without it,
|
|
110
|
+
any2md transparently falls back to pymupdf4llm (PDF) and mammoth (DOCX).
|
|
111
|
+
|
|
112
|
+
### Added
|
|
113
|
+
- New optional dependency: `pip install "any2md[high-fidelity]"` installs
|
|
114
|
+
Docling, which becomes the primary extraction backend for PDF and DOCX.
|
|
115
|
+
- `any2md/_docling.py` — detection helper plus a rate-limited install hint
|
|
116
|
+
that surfaces on the stderr when an artifact-prone PDF is being converted
|
|
117
|
+
without Docling installed.
|
|
118
|
+
- New CLI flag: `--high-fidelity` / `-H`. Forces Docling. Exits with code 1
|
|
119
|
+
and prints the install hint when Docling is not present.
|
|
120
|
+
- `pdf_looks_complex(pdf_path)` heuristic — fast (< 50 ms) check used to
|
|
121
|
+
decide whether to print the install hint when Docling is missing.
|
|
122
|
+
- New `PipelineOptions.high_fidelity` field (forwarded by the CLI).
|
|
123
|
+
- Structured-lane post-processing stages now active for Docling-emitted
|
|
124
|
+
markdown:
|
|
125
|
+
- **S1 `lift_figure_captions`** — converts image markdown and HTML
|
|
126
|
+
`<figure>` blocks to italic `*Figure: caption*` lines. Drops image
|
|
127
|
+
references (preserved with `--save-images`, planned for Phase 3).
|
|
128
|
+
- **S2 `compact_tables`** — strips per-cell padding spaces in GFM tables;
|
|
129
|
+
preserves alignment row intact.
|
|
130
|
+
- **S3 `normalize_citations`** — coalesces `[1] [2] [3]` → `[1][2][3]`.
|
|
131
|
+
- **S4 `enforce_heading_hierarchy`** — guarantees a single H1 (promotes
|
|
132
|
+
first heading if needed; demotes subsequent H1s); flattens skipped
|
|
133
|
+
levels (H2 → H4 becomes H2 → H3).
|
|
134
|
+
- Per-backend snapshot tests: PDF and DOCX now have `.docling.md` and
|
|
135
|
+
`.fallback.md` golden outputs.
|
|
136
|
+
|
|
137
|
+
### Changed
|
|
138
|
+
- PDF and DOCX converters select backend at runtime: Docling if importable,
|
|
139
|
+
pymupdf4llm/mammoth otherwise. Fallback also fires if Docling raises
|
|
140
|
+
during extraction (with stderr warning).
|
|
141
|
+
- `extracted_via` frontmatter field reports `docling` for the new path.
|
|
142
|
+
- `lane` is `"structured"` for Docling output, `"text"` for fallback —
|
|
143
|
+
the structured lane runs S1–S4 before shared cleanup; the text lane
|
|
144
|
+
runs only shared cleanup in Phase 2 (T1–T6 land in Phase 3).
|
|
145
|
+
|
|
146
|
+
### Carried forward
|
|
147
|
+
- All v0.7 + v1.0.0a1 CLI flags.
|
|
148
|
+
- HTML/URL → trafilatura (Docling not used for web — purpose-built
|
|
149
|
+
boilerplate stripping is more important than structured layout).
|
|
150
|
+
- TXT → heuristic `structurize()`.
|
|
151
|
+
|
|
152
|
+
## [1.0.0a1] — 2026-04-26
|
|
153
|
+
|
|
154
|
+
First prerelease of any2md v1.0. Phase 1 of 5: foundation only — no Docling
|
|
155
|
+
backend yet, no new CLI flags. Output frontmatter has been rewritten to be
|
|
156
|
+
SSRM-compatible (Structured Security Reasoning Markdown) — this is a
|
|
157
|
+
breaking change for downstream consumers parsing v0.7 output. See
|
|
158
|
+
`docs/superpowers/specs/2026-04-26-any2md-v1-design.md` for the full
|
|
159
|
+
v1.0 design and `docs/superpowers/plans/2026-04-26-any2md-v1-phase1-foundation.md`
|
|
160
|
+
for this phase's task plan.
|
|
161
|
+
|
|
162
|
+
### Added
|
|
163
|
+
- New `any2md/frontmatter.py` module — SSRM-compatible YAML emitter with the
|
|
164
|
+
`SourceMeta` dataclass, deterministic `compute_content_hash`,
|
|
165
|
+
`estimate_tokens`, `recommend_chunk_level`, `extract_abstract`, and
|
|
166
|
+
`derive_title` helpers, plus the central `compose()` function (the only
|
|
167
|
+
producer of the YAML block — converters never touch YAML directly).
|
|
168
|
+
- New `any2md/pipeline/` package with the two-lane post-processing runner
|
|
169
|
+
(`structured` and `text` lanes both empty in Phase 1; filled in Phase 2/3)
|
|
170
|
+
and seven shared cleanup stages C1–C7 in `pipeline/cleanup.py`:
|
|
171
|
+
NFC normalization, soft-hyphen strip, ligature normalization (whitelist
|
|
172
|
+
driven, NOT blanket NFKC), smart-quote/dash normalization, whitespace
|
|
173
|
+
collapse, footnote-marker strip (aggressive/maximum profiles only), and
|
|
174
|
+
read-only validation that emits non-fatal warnings via a contextvar.
|
|
175
|
+
- New `any2md/validators.py` — heading-hierarchy and `content_hash` round-trip
|
|
176
|
+
checks. Used by C7 and exposed for programmatic consumers.
|
|
177
|
+
- pytest test suite: 86 tests covering pipeline stages, frontmatter helpers,
|
|
178
|
+
content_hash determinism (including known empty-string SHA-256 vector),
|
|
179
|
+
validators, all four converters end-to-end, CLI smoke tests, and
|
|
180
|
+
snapshot tests for golden output.
|
|
181
|
+
- Synthetic test fixtures (HTML, TXT, multi-column PDF, table-heavy DOCX)
|
|
182
|
+
under `tests/fixtures/docs/` — all owned, deterministic, and small.
|
|
183
|
+
Generator script `tests/fixtures/make_fixtures.py` regenerates the binary
|
|
184
|
+
fixtures (PDF via reportlab, DOCX via direct zip+XML — no python-docx dep).
|
|
185
|
+
- `[dev]` extras in `pyproject.toml`: `pytest`, `pytest-snapshot`,
|
|
186
|
+
`reportlab`, `ruff`.
|
|
187
|
+
|
|
188
|
+
### Changed
|
|
189
|
+
- **BREAKING:** Output frontmatter shape is now SSRM-compatible. New required
|
|
190
|
+
fields: `document_id` (empty string by default — opt-in via future
|
|
191
|
+
`--auto-id`), `version`, `date`, `status: "draft"`, `document_type` (empty
|
|
192
|
+
for non-security documents), `content_domain` (empty array),
|
|
193
|
+
`authors`, `organization`, `generation_metadata.authored_by: "unknown"`,
|
|
194
|
+
and `content_hash` (SHA-256 of NFC + LF body). Optional fields auto-filled
|
|
195
|
+
when derivable: `keywords`, `token_estimate`, `recommended_chunk_level`,
|
|
196
|
+
`abstract_for_rag` (only for documents ≥ 500 estimated tokens).
|
|
197
|
+
- v0.7 fields `source_file`, `source_url`, `pages`, `word_count`, and `type`
|
|
198
|
+
are retained as any2md extension fields for traceability. A new
|
|
199
|
+
`extracted_via` extension field records which library produced the
|
|
200
|
+
markdown (`pymupdf4llm` / `mammoth+markdownify` / `trafilatura` /
|
|
201
|
+
`trafilatura+bs4_fallback` / `heuristic`).
|
|
202
|
+
- Body is NFC-normalized with LF line endings before write — outputs are
|
|
203
|
+
byte-deterministic for a given input + flag set.
|
|
204
|
+
- All four converters now route through the shared post-processing pipeline
|
|
205
|
+
via `pipeline.run(text, lane, options)` and emit through
|
|
206
|
+
`frontmatter.compose(body, meta, options)`. Converters return
|
|
207
|
+
`(markdown, SourceMeta)` shape internally — they no longer touch YAML.
|
|
208
|
+
- DOCX metadata extraction: now reads `docProps/core.xml` and
|
|
209
|
+
`docProps/app.xml` directly via `zipfile + xml.etree.ElementTree`. No
|
|
210
|
+
python-docx dependency added.
|
|
211
|
+
- HTML converter: uses `trafilatura.bare_extraction(...)` for metadata
|
|
212
|
+
(title/author/sitename/date/categories). For URL inputs, a single
|
|
213
|
+
best-effort HEAD request captures HTTP `Last-Modified`.
|
|
214
|
+
- `.gitignore` now excludes `template/`, `test_docs/`, and `.worktrees/`.
|
|
215
|
+
|
|
216
|
+
### Carried forward unchanged
|
|
217
|
+
- All v0.7 CLI flags (`--input-dir`, `--output-dir`, `--force`,
|
|
218
|
+
`--strip-links`, `--recursive`, `--max-file-size`, positional file/URL/dir
|
|
219
|
+
arguments). The `--strip-links` flag is now wired through `PipelineOptions`.
|
|
220
|
+
- SSRF protection on URL fetching (private/reserved/loopback IP rejection).
|
|
221
|
+
- File size limits (default 100 MB).
|
|
222
|
+
- Filename sanitization rules.
|
|
223
|
+
- Skip-if-exists unless `--force`.
|
|
224
|
+
- pymupdf4llm, mammoth, markdownify, trafilatura, BeautifulSoup, lxml
|
|
225
|
+
backends.
|
|
226
|
+
- Python 3.10+ floor.
|
|
227
|
+
|
|
228
|
+
### Out of scope for Phase 1
|
|
229
|
+
Phase 2 (Docling backend, `--high-fidelity`, structured-lane stages S1–S4),
|
|
230
|
+
Phase 3 (figure/OCR handling, text-lane stages T1–T6, full metadata
|
|
231
|
+
extraction), Phase 4 (`--meta`, `.any2md.toml`, `--auto-id`, `--strict`,
|
|
232
|
+
`--quiet`, `--verbose`, full documentation rewrite), Phase 5 (release
|
|
233
|
+
validation, 1.0.0).
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Contributing to any2md
|
|
2
|
+
|
|
3
|
+
This guide covers everything you need to make a change to any2md and get it
|
|
4
|
+
merged: development setup, the test commands you'll run, where the code
|
|
5
|
+
lives, the coding standards, and the release flow.
|
|
6
|
+
|
|
7
|
+
For a user-facing orientation, see the [README](README.md). For pipeline
|
|
8
|
+
internals and the contracts that converters and pipeline stages have to
|
|
9
|
+
honor, see [docs/architecture.md](docs/architecture.md). For the output
|
|
10
|
+
shape any change must continue to produce, see
|
|
11
|
+
[docs/output-format.md](docs/output-format.md).
|
|
12
|
+
|
|
13
|
+
## Development setup
|
|
14
|
+
|
|
15
|
+
Clone the repo and install the editable package with both extras. `dev`
|
|
16
|
+
brings in `pytest`, `pytest-snapshot`, `reportlab` (used by the fixture
|
|
17
|
+
generator), and `ruff`. `high-fidelity` brings in Docling and roughly 2 GB
|
|
18
|
+
of ML models on first use:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
git clone https://github.com/rocklambros/any2md.git
|
|
22
|
+
cd any2md
|
|
23
|
+
pip install -e ".[dev,high-fidelity]"
|
|
24
|
+
pytest
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
You can skip `high-fidelity` if you're working on a non-Docling code path —
|
|
28
|
+
the test suite gates Docling-dependent tests with
|
|
29
|
+
`@pytest.mark.skipif(not has_docling(), …)` so they're transparently skipped
|
|
30
|
+
without it. Coverage of fallback paths is unaffected.
|
|
31
|
+
|
|
32
|
+
Python 3.10 or later is required.
|
|
33
|
+
|
|
34
|
+
## Running tests
|
|
35
|
+
|
|
36
|
+
The default invocation runs the whole suite (currently 172 tests):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pytest -v
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Tests are organized by scope:
|
|
43
|
+
|
|
44
|
+
- `tests/unit/` — pure-function tests for pipeline stages, frontmatter
|
|
45
|
+
helpers, validators, and config parsing.
|
|
46
|
+
- `tests/cli/` — `subprocess`-driven tests that exercise the CLI end to end.
|
|
47
|
+
- `tests/integration/` — full converter runs over real fixtures, including
|
|
48
|
+
Docling-vs-fallback parity checks.
|
|
49
|
+
|
|
50
|
+
The integration suite includes snapshot tests that compare converter output
|
|
51
|
+
against committed golden files under `tests/fixtures/snapshots/`. When you
|
|
52
|
+
make an intentional output change (new pipeline stage, fixed bug that
|
|
53
|
+
shifted a hash, etc.), regenerate the snapshots:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
UPDATE_SNAPSHOTS=1 pytest tests/integration/test_snapshots.py
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Then inspect the diff (`git diff tests/fixtures/snapshots/`) before
|
|
60
|
+
committing — the snapshots are review material, not just bytes.
|
|
61
|
+
|
|
62
|
+
A few tests are network-dependent (the URL converter integration suite)
|
|
63
|
+
and opt-in. Run them explicitly when touching `converters/html.py` or the
|
|
64
|
+
SSRF protection:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pytest tests/integration/test_url_wikipedia.py -v
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Adding a new converter
|
|
71
|
+
|
|
72
|
+
The full step-by-step is in
|
|
73
|
+
[docs/architecture.md#adding-a-new-converter](docs/architecture.md#adding-a-new-converter).
|
|
74
|
+
The short version: write a `convert_<format>` function in
|
|
75
|
+
`any2md/converters/<format>.py` that returns `(markdown, SourceMeta)` and
|
|
76
|
+
declares its lane (`"structured"` or `"text"`). Register it in
|
|
77
|
+
`any2md/cli.py`'s dispatch logic. The frontmatter emitter and the cleanup
|
|
78
|
+
pipeline are not the converter's job — keep them centralized.
|
|
79
|
+
|
|
80
|
+
## Adding a new pipeline stage
|
|
81
|
+
|
|
82
|
+
The full step-by-step is in
|
|
83
|
+
[docs/architecture.md#adding-a-new-pipeline-stage](docs/architecture.md#adding-a-new-pipeline-stage).
|
|
84
|
+
The short version: pick the lane (`structured`, `text`, or shared
|
|
85
|
+
`cleanup`), write a function with the signature
|
|
86
|
+
`(text: str, options: PipelineOptions) -> str`, register it in the lane's
|
|
87
|
+
`STAGES` list in dependency order, and add a unit test. One test file per
|
|
88
|
+
stage; follow the red-then-green TDD rhythm and commit when green.
|
|
89
|
+
|
|
90
|
+
## Coding standards
|
|
91
|
+
|
|
92
|
+
- **Lazy imports for optional dependencies.** Anything pulled in by an
|
|
93
|
+
extra (Docling, reportlab) is imported inside the function that uses it,
|
|
94
|
+
not at module top, so the lightweight install path doesn't pay an import
|
|
95
|
+
cost or crash on missing packages.
|
|
96
|
+
- **No class hierarchies for simple operations.** Pipeline stages are
|
|
97
|
+
module-level functions, not subclasses of an abstract `Stage` base.
|
|
98
|
+
Frontmatter helpers are functions, not methods on a `Frontmatter` class.
|
|
99
|
+
This is a deliberate choice — the operations are stateless and the
|
|
100
|
+
resulting code is easier to read.
|
|
101
|
+
- **`ruff check` and `ruff format` clean.** The repo runs ruff in CI; PRs
|
|
102
|
+
with lint errors won't merge.
|
|
103
|
+
- **Docstrings.** One-line summary, blank line, optional sections (Args,
|
|
104
|
+
Returns, Raises, Examples). Match the style already in
|
|
105
|
+
`any2md/frontmatter.py` and `any2md/pipeline/cleanup.py`.
|
|
106
|
+
- **Tests.** One file per stage. Red-then-green TDD: write the failing
|
|
107
|
+
test first, then the minimal implementation. Commit when green. Snapshot
|
|
108
|
+
tests cover whole-converter behavior; unit tests cover individual stages.
|
|
109
|
+
|
|
110
|
+
## Release flow
|
|
111
|
+
|
|
112
|
+
Releases are driven by GitHub Releases. The
|
|
113
|
+
[`publish.yml`](.github/workflows/publish.yml) workflow inspects the
|
|
114
|
+
release's `prerelease` flag: when `prerelease == true`, the artifacts go
|
|
115
|
+
to TestPyPI; when `false`, they go to PyPI.
|
|
116
|
+
|
|
117
|
+
The flow:
|
|
118
|
+
|
|
119
|
+
1. Bump `__version__` in `any2md/__init__.py` (e.g., `1.0.0rc2` for a
|
|
120
|
+
prerelease, `1.0.0` for a stable).
|
|
121
|
+
2. Update `CHANGELOG.md` with a new section dated to the release.
|
|
122
|
+
3. Open a PR with the version bump and the changelog entry. Land it.
|
|
123
|
+
4. Tag the release commit on `main` (e.g., `git tag -a v1.0.0rc2 -m "any2md 1.0.0rc2"`)
|
|
124
|
+
and push the tag.
|
|
125
|
+
5. Create a GitHub Release pointing at the tag. **Mark it as a prerelease**
|
|
126
|
+
if the version contains `a`, `b`, or `rc` — that's what routes the
|
|
127
|
+
workflow to TestPyPI rather than PyPI.
|
|
128
|
+
6. Watch the workflow. The version-tag check will fail loudly if the tag
|
|
129
|
+
and `__version__` disagree.
|
|
130
|
+
|
|
131
|
+
Don't tag and release directly without the PR — CI gates exist for a
|
|
132
|
+
reason.
|
|
133
|
+
|
|
134
|
+
## Pull request process
|
|
135
|
+
|
|
136
|
+
- Branch off `main` (or off a phase branch like `v1.0` if one is open for
|
|
137
|
+
in-progress work — check the recent activity before branching).
|
|
138
|
+
- One logical change per PR. A PR that adds a converter and rewrites the
|
|
139
|
+
HTML lane is two PRs.
|
|
140
|
+
- Tests green and `ruff check` clean before requesting review.
|
|
141
|
+
- Descriptive PR title — same shape as the commit messages in
|
|
142
|
+
`git log --oneline` (`feat(cli): ...`, `fix(pipeline): ...`,
|
|
143
|
+
`docs: ...`). The title becomes the squash-merge commit message.
|
|
144
|
+
- Conversion-quality bug reports are the highest-value issues. If you're
|
|
145
|
+
filing one, please use the `Conversion quality` issue template — it asks
|
|
146
|
+
for the source format, file size, Docling version, full command, and a
|
|
147
|
+
5-line snippet of the bad output, all of which are typically required to
|
|
148
|
+
reproduce the artifact.
|