mdka 2.2.2__tar.gz → 2.2.3__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.
Files changed (155) hide show
  1. {mdka-2.2.2 → mdka-2.2.3}/.github/workflows/scripts/check-docs-examples.py +124 -10
  2. {mdka-2.2.2 → mdka-2.2.3}/CHANGELOG.md +53 -0
  3. {mdka-2.2.2 → mdka-2.2.3}/Cargo.lock +7 -7
  4. {mdka-2.2.2 → mdka-2.2.3}/Cargo.toml +2 -2
  5. {mdka-2.2.2 → mdka-2.2.3}/PKG-INFO +39 -15
  6. {mdka-2.2.2/python → mdka-2.2.3}/README.md +38 -14
  7. {mdka-2.2.2 → mdka-2.2.3}/ROADMAP.md +47 -3
  8. mdka-2.2.3/docs/src/getting-started/installation.md +77 -0
  9. {mdka-2.2.2 → mdka-2.2.3}/docs/src/getting-started/usage-cli.md +6 -0
  10. {mdka-2.2.2 → mdka-2.2.3}/docs/src/getting-started/usage-nodejs.md +14 -3
  11. {mdka-2.2.2 → mdka-2.2.3}/docs/src/getting-started/usage-python.md +15 -4
  12. {mdka-2.2.2 → mdka-2.2.3}/pyproject.toml +1 -1
  13. {mdka-2.2.2 → mdka-2.2.3}/python/Cargo.toml +1 -1
  14. {mdka-2.2.2 → mdka-2.2.3/python}/README.md +38 -14
  15. {mdka-2.2.2 → mdka-2.2.3}/rfcs/README.md +1 -0
  16. {mdka-2.2.2 → mdka-2.2.3}/rfcs/accepted/024-inline-composition-output-sink.md +27 -0
  17. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/026-consumer-artifact-gates.md +43 -0
  18. mdka-2.2.3/rfcs/done/029-published-surface-documentation-repair.md +126 -0
  19. mdka-2.2.3/rfcs/handoffs/029-published-surface-documentation-repair/implementation-handoff.md +195 -0
  20. mdka-2.2.2/docs/src/getting-started/installation.md +0 -53
  21. {mdka-2.2.2 → mdka-2.2.3}/.gitattributes +0 -0
  22. {mdka-2.2.2 → mdka-2.2.3}/.github/CODE_OF_CONDUCT.md +0 -0
  23. {mdka-2.2.2 → mdka-2.2.3}/.github/CONTRIBUTING.md +0 -0
  24. {mdka-2.2.2 → mdka-2.2.3}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  25. {mdka-2.2.2 → mdka-2.2.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  26. {mdka-2.2.2 → mdka-2.2.3}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  27. {mdka-2.2.2 → mdka-2.2.3}/.github/ISSUE_TEMPLATE/question.yml +0 -0
  28. {mdka-2.2.2 → mdka-2.2.3}/.github/SECURITY.md +0 -0
  29. {mdka-2.2.2 → mdka-2.2.3}/.github/workflows/ci.yaml +0 -0
  30. {mdka-2.2.2 → mdka-2.2.3}/.github/workflows/crates-package-gate.yaml +0 -0
  31. {mdka-2.2.2 → mdka-2.2.3}/.github/workflows/create-release.yaml +0 -0
  32. {mdka-2.2.2 → mdka-2.2.3}/.github/workflows/docs-example-gate.yaml +0 -0
  33. {mdka-2.2.2 → mdka-2.2.3}/.github/workflows/docs.yaml +0 -0
  34. {mdka-2.2.2 → mdka-2.2.3}/.github/workflows/npm-install-gate.yaml +0 -0
  35. {mdka-2.2.2 → mdka-2.2.3}/.github/workflows/pypi-wheel-gate.yaml +0 -0
  36. {mdka-2.2.2 → mdka-2.2.3}/.github/workflows/release-crates.yaml +0 -0
  37. {mdka-2.2.2 → mdka-2.2.3}/.github/workflows/release-executable.yaml +0 -0
  38. {mdka-2.2.2 → mdka-2.2.3}/.github/workflows/release-npm.yaml +0 -0
  39. {mdka-2.2.2 → mdka-2.2.3}/.github/workflows/release-pypi.yaml +0 -0
  40. {mdka-2.2.2 → mdka-2.2.3}/.github/workflows/scripts/install-rust.sh +0 -0
  41. {mdka-2.2.2 → mdka-2.2.3}/.gitignore +0 -0
  42. {mdka-2.2.2 → mdka-2.2.3}/.vscode/extensions.json +0 -0
  43. {mdka-2.2.2 → mdka-2.2.3}/.vscode/settings.json +0 -0
  44. {mdka-2.2.2 → mdka-2.2.3}/LICENSE +0 -0
  45. {mdka-2.2.2 → mdka-2.2.3}/NOTICE +0 -0
  46. {mdka-2.2.2 → mdka-2.2.3}/benches/bench_common.rs +0 -0
  47. {mdka-2.2.2 → mdka-2.2.3}/benches/benchdata/deep_nest.html +0 -0
  48. {mdka-2.2.2 → mdka-2.2.3}/benches/benchdata/flat.html +0 -0
  49. {mdka-2.2.2 → mdka-2.2.3}/benches/benchdata/large.html +0 -0
  50. {mdka-2.2.2 → mdka-2.2.3}/benches/benchdata/malformed.html +0 -0
  51. {mdka-2.2.2 → mdka-2.2.3}/benches/benchdata/medium.html +0 -0
  52. {mdka-2.2.2 → mdka-2.2.3}/benches/benchdata/scale_500k.html +0 -0
  53. {mdka-2.2.2 → mdka-2.2.3}/benches/benchdata/scale_50k.html +0 -0
  54. {mdka-2.2.2 → mdka-2.2.3}/benches/benchdata/scale_5m.html +0 -0
  55. {mdka-2.2.2 → mdka-2.2.3}/benches/benchdata/small.html +0 -0
  56. {mdka-2.2.2 → mdka-2.2.3}/benches/convert.rs +0 -0
  57. {mdka-2.2.2 → mdka-2.2.3}/benches/memory.rs +0 -0
  58. {mdka-2.2.2 → mdka-2.2.3}/benches/parallel.rs +0 -0
  59. {mdka-2.2.2 → mdka-2.2.3}/benches/scaling.rs +0 -0
  60. {mdka-2.2.2 → mdka-2.2.3}/cargo-publish.sh +0 -0
  61. {mdka-2.2.2 → mdka-2.2.3}/docs/book.toml +0 -0
  62. {mdka-2.2.2 → mdka-2.2.3}/docs/src/SUMMARY.md +0 -0
  63. {mdka-2.2.2 → mdka-2.2.3}/docs/src/api/core.md +0 -0
  64. {mdka-2.2.2 → mdka-2.2.3}/docs/src/api/elements.md +0 -0
  65. {mdka-2.2.2 → mdka-2.2.3}/docs/src/api/errors.md +0 -0
  66. {mdka-2.2.2 → mdka-2.2.3}/docs/src/api/index.md +0 -0
  67. {mdka-2.2.2 → mdka-2.2.3}/docs/src/api/modes.md +0 -0
  68. {mdka-2.2.2 → mdka-2.2.3}/docs/src/api/options.md +0 -0
  69. {mdka-2.2.2 → mdka-2.2.3}/docs/src/api/text-processing.md +0 -0
  70. {mdka-2.2.2 → mdka-2.2.3}/docs/src/assets/logo.png +0 -0
  71. {mdka-2.2.2 → mdka-2.2.3}/docs/src/design/architecture.md +0 -0
  72. {mdka-2.2.2 → mdka-2.2.3}/docs/src/design/features.md +0 -0
  73. {mdka-2.2.2 → mdka-2.2.3}/docs/src/design/performance-characteristics.md +0 -0
  74. {mdka-2.2.2 → mdka-2.2.3}/docs/src/design/philosophy.md +0 -0
  75. {mdka-2.2.2 → mdka-2.2.3}/docs/src/getting-started/usage-rust.md +0 -0
  76. {mdka-2.2.2 → mdka-2.2.3}/docs/src/getting-started/usage.md +0 -0
  77. {mdka-2.2.2 → mdka-2.2.3}/docs/src/introduction.md +0 -0
  78. {mdka-2.2.2 → mdka-2.2.3}/examples/measure_mem.rs +0 -0
  79. {mdka-2.2.2 → mdka-2.2.3}/examples/quick_bench.rs +0 -0
  80. {mdka-2.2.2 → mdka-2.2.3}/examples/quick_compare.rs +0 -0
  81. {mdka-2.2.2 → mdka-2.2.3}/examples/quick_mem.rs +0 -0
  82. {mdka-2.2.2 → mdka-2.2.3}/mdka/__init__.py +0 -0
  83. {mdka-2.2.2 → mdka-2.2.3}/python/example.py +0 -0
  84. {mdka-2.2.2 → mdka-2.2.3}/python/src/lib.rs +0 -0
  85. {mdka-2.2.2 → mdka-2.2.3}/python/test_mdka.py +0 -0
  86. {mdka-2.2.2 → mdka-2.2.3}/rfcs/accepted/025-output-validity-harness.md +0 -0
  87. {mdka-2.2.2 → mdka-2.2.3}/rfcs/accepted/028-emphasis-around-block-content.md +0 -0
  88. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/000-rfc-lifecycle-policy.md +0 -0
  89. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/001-ci-quality-gates.md +0 -0
  90. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/002-governance-artifacts.md +0 -0
  91. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/003-architecture-doc-reconciliation.md +0 -0
  92. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/004-preprocessor-disposition.md +0 -0
  93. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/005-conversion-options-semantics.md +0 -0
  94. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/006-option-docs-and-binding-parity.md +0 -0
  95. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/007-english-only-public-surface.md +0 -0
  96. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/014-release-time-ci-verification.md +0 -0
  97. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/015-release-tooling-completion.md +0 -0
  98. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/016-hr-newline-reset.md +0 -0
  99. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/017-pre-fence-newline-reset.md +0 -0
  100. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/018-readme-prebuilt-binaries.md +0 -0
  101. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/019-release-creation-via-dispatch.md +0 -0
  102. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/020-npm-distribution-repair.md +0 -0
  103. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/021-bulk-output-collision-safety.md +0 -0
  104. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/022-cli-allocator-and-jemalloc.md +0 -0
  105. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/023-getting-started-doc-reconciliation.md +0 -0
  106. {mdka-2.2.2 → mdka-2.2.3}/rfcs/done/027-verification-discipline.md +0 -0
  107. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/001-ci-quality-gates/implementation-handoff.md +0 -0
  108. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/002-governance-artifacts/implementation-handoff.md +0 -0
  109. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/003-architecture-doc-reconciliation/implementation-handoff.md +0 -0
  110. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/004-preprocessor-disposition/implementation-handoff.md +0 -0
  111. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/005-conversion-options-semantics/implementation-handoff.md +0 -0
  112. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/005-conversion-options-semantics/slice-b1-placement-correction-handoff.md +0 -0
  113. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/005-conversion-options-semantics/slices-bc-handoff.md +0 -0
  114. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/006-option-docs-and-binding-parity/implementation-handoff.md +0 -0
  115. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/007-english-only-public-surface/implementation-handoff.md +0 -0
  116. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/014-release-time-ci-verification/implementation-handoff.md +0 -0
  117. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/015-release-tooling-completion/amendment-handoff.md +0 -0
  118. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/015-release-tooling-completion/implementation-handoff.md +0 -0
  119. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/015-release-tooling-completion/restore-handoff.md +0 -0
  120. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/016-hr-newline-reset/implementation-handoff.md +0 -0
  121. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/017-pre-fence-newline-reset/implementation-handoff.md +0 -0
  122. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/018-readme-prebuilt-binaries/implementation-handoff.md +0 -0
  123. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/019-release-creation-via-dispatch/implementation-handoff.md +0 -0
  124. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/020-npm-distribution-repair/implementation-handoff.md +0 -0
  125. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/021-bulk-output-collision-safety/implementation-handoff.md +0 -0
  126. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/022-cli-allocator-and-jemalloc/alloc-counter-deprecation-handoff.md +0 -0
  127. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/022-cli-allocator-and-jemalloc/implementation-handoff.md +0 -0
  128. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/023-getting-started-doc-reconciliation/implementation-handoff.md +0 -0
  129. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/024-inline-composition-output-sink/implementation-handoff.md +0 -0
  130. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/025-output-validity-harness/implementation-handoff.md +0 -0
  131. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/026-consumer-artifact-gates/implementation-handoff.md +0 -0
  132. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/027-verification-discipline/implementation-handoff.md +0 -0
  133. {mdka-2.2.2 → mdka-2.2.3}/rfcs/handoffs/028-emphasis-around-block-content/implementation-handoff.md +0 -0
  134. {mdka-2.2.2 → mdka-2.2.3}/src/alloc_counter.rs +0 -0
  135. {mdka-2.2.2 → mdka-2.2.3}/src/lib.rs +0 -0
  136. {mdka-2.2.2 → mdka-2.2.3}/src/options.rs +0 -0
  137. {mdka-2.2.2 → mdka-2.2.3}/src/renderer.rs +0 -0
  138. {mdka-2.2.2 → mdka-2.2.3}/src/traversal/tests.rs +0 -0
  139. {mdka-2.2.2 → mdka-2.2.3}/src/traversal.rs +0 -0
  140. {mdka-2.2.2 → mdka-2.2.3}/src/utils/tests.rs +0 -0
  141. {mdka-2.2.2 → mdka-2.2.3}/src/utils.rs +0 -0
  142. {mdka-2.2.2 → mdka-2.2.3}/tests/anchor_drift_guard.rs +0 -0
  143. {mdka-2.2.2 → mdka-2.2.3}/tests/block_elements.rs +0 -0
  144. {mdka-2.2.2 → mdka-2.2.3}/tests/characterisation_attributes.rs +0 -0
  145. {mdka-2.2.2 → mdka-2.2.3}/tests/characterisation_elements.rs +0 -0
  146. {mdka-2.2.2 → mdka-2.2.3}/tests/characterisation_structural.rs +0 -0
  147. {mdka-2.2.2 → mdka-2.2.3}/tests/common.rs +0 -0
  148. {mdka-2.2.2 → mdka-2.2.3}/tests/compat.rs +0 -0
  149. {mdka-2.2.2 → mdka-2.2.3}/tests/file_conversion.rs +0 -0
  150. {mdka-2.2.2 → mdka-2.2.3}/tests/hr_newline_reset.rs +0 -0
  151. {mdka-2.2.2 → mdka-2.2.3}/tests/inline_elements.rs +0 -0
  152. {mdka-2.2.2 → mdka-2.2.3}/tests/pre_fence_newline_reset.rs +0 -0
  153. {mdka-2.2.2 → mdka-2.2.3}/tests/preserve_ids_anchors.rs +0 -0
  154. {mdka-2.2.2 → mdka-2.2.3}/tests/robustness.rs +0 -0
  155. {mdka-2.2.2 → mdka-2.2.3}/version.sh +0 -0
@@ -38,10 +38,30 @@ class Block:
38
38
  return f"{self.path}:{self.line}"
39
39
 
40
40
 
41
- def extract(root):
42
- """Yield every fenced block under `root`, in document order."""
41
+ def markdown_files(roots):
42
+ """Every markdown file under each root. A root may be a file or a directory.
43
+
44
+ README.md is a root in its own right, not something reachable from
45
+ `docs/src`. It renders on GitHub, crates.io, npmjs.com and PyPI, and it was
46
+ invisible to this gate until RFC 029 -- the gate built to catch broken
47
+ examples was pointed at a directory that excluded the most-read file in the
48
+ project.
49
+ """
50
+ seen, out = set(), []
51
+ for root in roots:
52
+ p = Path(root)
53
+ found = sorted(p.rglob("*.md")) if p.is_dir() else [p]
54
+ for md in found:
55
+ if md.resolve() not in seen:
56
+ seen.add(md.resolve())
57
+ out.append(md)
58
+ return out
59
+
60
+
61
+ def extract(roots):
62
+ """Yield every fenced block under `roots`, in document order."""
43
63
  blocks = []
44
- for md in sorted(Path(root).rglob("*.md")):
64
+ for md in markdown_files(roots):
45
65
  lines = md.read_text(encoding="utf-8").splitlines()
46
66
  i = 0
47
67
  while i < len(lines):
@@ -67,6 +87,42 @@ def extract(root):
67
87
  return blocks
68
88
 
69
89
 
90
+ LINK = re.compile(r"!?\[[^\]]*\]\(([^)\s]+)")
91
+
92
+
93
+ def check_readme_links(path):
94
+ """Assert README.md carries no relative or root-absolute Markdown link.
95
+
96
+ Deliberately a grep, not a link checker: nothing is resolved, followed or
97
+ crawled. README.md is published inside the npm tarball (four files) and
98
+ rendered on npmjs.com and the PyPI project page, where a relative path
99
+ resolves against the registry rather than the repository. `./CHANGELOG.md`,
100
+ `./docs/` and a root-absolute logo path were all live and broken on both
101
+ registries at 2.2.2.
102
+
103
+ Scoped to README.md alone. `docs/src/` is rendered by mdbook, where
104
+ relative links are correct and expected.
105
+ """
106
+ p = Path(path)
107
+ if not p.exists():
108
+ return []
109
+ # Strip fenced blocks: a `](` inside a code sample is not a link.
110
+ text, fenced = [], False
111
+ for line in p.read_text(encoding="utf-8").splitlines():
112
+ if line.lstrip().startswith("```"):
113
+ fenced = not fenced
114
+ continue
115
+ text.append("" if fenced else line)
116
+
117
+ bad = []
118
+ for n, line in enumerate(text, 1):
119
+ for target in LINK.findall(line):
120
+ if target.startswith("#") or "://" in target or target.startswith("mailto:"):
121
+ continue
122
+ bad.append(f"{p}:{n}: relative or root-absolute link: {target}")
123
+ return bad
124
+
125
+
70
126
  def runnable(blocks):
71
127
  out = []
72
128
  for b in blocks:
@@ -172,21 +228,63 @@ def check_python(blocks, workdir, python):
172
228
  except SyntaxError:
173
229
  continue
174
230
  names = set()
231
+ imported = set()
175
232
  for node in ast.walk(tree):
176
233
  if isinstance(node, ast.ImportFrom) and (node.module or "").split(".")[0] == "mdka":
177
234
  names.update(a.name for a in node.names)
235
+ imported.update(a.asname or a.name for a in node.names)
178
236
  elif (
179
237
  isinstance(node, ast.Attribute)
180
238
  and isinstance(node.value, ast.Name)
181
239
  and node.value.id == "mdka"
182
240
  ):
183
241
  names.add(node.attr)
184
- if not names:
242
+
243
+ # Keyword arguments, not just symbols. `html_to_markdown_with` resolves
244
+ # whether or not `preserve_unknown_attrs=True` is a real parameter --
245
+ # and it is not; it raises TypeError. Documenting a call that raises is
246
+ # worse than documenting nothing, so bind the documented kwargs against
247
+ # the installed signature (RFC 029 §4.2).
248
+ calls = []
249
+ for node in ast.walk(tree):
250
+ if not isinstance(node, ast.Call) or not node.keywords:
251
+ continue
252
+ fn = node.func
253
+ if isinstance(fn, ast.Attribute) and isinstance(fn.value, ast.Name) and fn.value.id == "mdka":
254
+ target = fn.attr
255
+ elif isinstance(fn, ast.Name) and fn.id in imported:
256
+ target = fn.id
257
+ else:
258
+ continue
259
+ kws = [k.arg for k in node.keywords if k.arg]
260
+ if kws:
261
+ calls.append((target, kws))
262
+
263
+ if not names and not calls:
185
264
  continue
186
265
  probe = (
187
- "import mdka, sys\n"
266
+ "import inspect, mdka, sys\n"
188
267
  f"missing = [n for n in {sorted(names)!r} if not hasattr(mdka, n)]\n"
189
- "sys.exit('missing from installed mdka: ' + ', '.join(missing)) if missing else None\n"
268
+ "if missing:\n"
269
+ " sys.exit('missing from installed mdka: ' + ', '.join(missing))\n"
270
+ f"problems = []\n"
271
+ f"for fname, kws in {calls!r}:\n"
272
+ " fn = getattr(mdka, fname, None)\n"
273
+ " if fn is None:\n"
274
+ " problems.append(fname + ': not in installed mdka'); continue\n"
275
+ " try:\n"
276
+ " sig = inspect.signature(fn)\n"
277
+ " except (TypeError, ValueError):\n"
278
+ " continue\n"
279
+ " params = sig.parameters\n"
280
+ " if any(p.kind is inspect.Parameter.VAR_KEYWORD for p in params.values()):\n"
281
+ " continue\n"
282
+ " bad = [k for k in kws if k not in params]\n"
283
+ " if bad:\n"
284
+ " problems.append(fname + '() rejects: ' + ', '.join(bad))\n"
285
+ "if problems:\n"
286
+ " sys.exit('documented call does not match the installed signature -- '\n"
287
+ " + '; '.join(problems))\n"
190
288
  )
191
289
  pf = workdir / f"probe{n}.py"
192
290
  pf.write_text(probe, encoding="utf-8")
@@ -252,7 +350,9 @@ def check_ts(blocks, workdir, types_dir):
252
350
 
253
351
  def main():
254
352
  ap = argparse.ArgumentParser()
255
- ap.add_argument("--root", default="docs/src")
353
+ ap.add_argument("--root", action="append", default=None,
354
+ help="file or directory to scan; repeatable "
355
+ "(default: docs/src and README.md)")
256
356
  ap.add_argument("--types", default="node", help="dir holding the generated index.d.ts")
257
357
  ap.add_argument("--python", default="", help="interpreter with mdka installed; enables symbol resolution")
258
358
  ap.add_argument("--list", action="store_true", help="list blocks and exit")
@@ -265,7 +365,8 @@ def main():
265
365
  print(f"error: --python {args.python} does not exist", file=sys.stderr)
266
366
  return 2
267
367
 
268
- blocks = extract(args.root)
368
+ roots = args.root or ["docs/src", "README.md"]
369
+ blocks = extract(roots)
269
370
  run = runnable(blocks)
270
371
 
271
372
  if args.list:
@@ -291,9 +392,22 @@ def main():
291
392
  failures += check_js(by.get("js", []), wd)
292
393
  failures += check_ts(by.get("ts", []), wd, args.types)
293
394
 
294
- if not failures:
295
- print("\nAll runnable examples OK.")
395
+ link_problems = check_readme_links("README.md")
396
+ if link_problems:
397
+ print(f"\n{len(link_problems)} README link problem(s):\n")
398
+ for msg in link_problems:
399
+ print(" " + msg)
400
+ print(
401
+ "\nREADME.md ships inside the npm tarball and renders on npmjs.com\n"
402
+ "and the PyPI project page, where a relative path resolves against the\n"
403
+ "registry rather than the repository. Use an absolute URL."
404
+ )
405
+
406
+ if not failures and not link_problems:
407
+ print("\nAll runnable examples OK. README links OK.")
296
408
  return 0
409
+ if not failures:
410
+ return 1
297
411
 
298
412
  print(f"\n{len(failures)} failing example(s):\n")
299
413
  for b, err in failures:
@@ -11,6 +11,59 @@ confidence, that is stated explicitly rather than guessed.
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [2.2.3] - 2026-09-16
15
+
16
+ ### Added
17
+
18
+ - **`mdka --version`.** It previously failed with
19
+ `error: IO error: No such file or directory`, because an unrecognised
20
+ `-`-prefixed argument was treated as a filename. `-V` works too.
21
+
22
+ ### Changed
23
+
24
+ - **The CLI now rejects unknown options instead of treating them as
25
+ filenames.** `mdka --drop-shel page.html` used to exit 0 having converted
26
+ nothing; it now prints `error: unknown option '--drop-shel'` with the usage
27
+ text and exits 1.
28
+
29
+ **This can break an existing invocation.** A file whose name begins with `-`
30
+ was previously converted and is now rejected. Put `--` before it:
31
+
32
+ ```
33
+ mdka -- -weird.html
34
+ ```
35
+
36
+ A bare `-` is still passed through as a path, unchanged.
37
+
38
+ ### Fixed
39
+
40
+ - **The README's Node.js Quick Start did not parse.** It declared `const md`
41
+ twice, called `htmlToMarkdownWithAsync` without importing it, referenced an
42
+ undefined `html`, and used top-level `await` in a CommonJS example. Its Rust
43
+ and Python examples also used an undefined `html`. All of them now run as
44
+ written. This is the file rendered on GitHub, crates.io, npmjs.com and PyPI.
45
+ - **README links and the logo now resolve outside GitHub.** The logo path was
46
+ root-absolute and `./docs/`, `./CHANGELOG.md` and `./ROADMAP.md` are not in
47
+ the published npm tarball, so all four were broken on npmjs.com and the PyPI
48
+ project page. They are absolute URLs now, and CI fails if a relative link is
49
+ reintroduced.
50
+ - **The Python and Node.js guides documented two options that do not exist.**
51
+ `preserve_unknown_attrs` and `drop_presentation_attrs` are fields of the Rust
52
+ `ConversionOptions` but are not exposed by either binding: Python raises
53
+ `TypeError`, TypeScript reports `TS2353`. Three of the five deprecated
54
+ attribute options are reachable from the bindings, and the pages now say
55
+ which.
56
+ - **`mdka --help` no longer shows a multi-file example that fails.**
57
+ `mdka --mode minimal --drop-shell *.html` needs `-o` for more than one input,
58
+ two lines below the rule saying so. Fixed in `--help` and in the README.
59
+ - **`installation.md` no longer tells you to run `npm run build` on
60
+ unsupported platforms.** The published npm package contains four files and no
61
+ Rust source, so that could never work. It now names the three platforms with
62
+ prebuilt bindings and what is actually possible elsewhere.
63
+ - **The README now carries two caveats it previously omitted**: that
64
+ `Balanced`, `Strict` and `Preserve` currently produce identical output, and
65
+ that tables are not yet converted.
66
+
14
67
  ## [2.2.2] - 2026-09-16
15
68
 
16
69
  ### Fixed
@@ -1179,7 +1179,7 @@ dependencies = [
1179
1179
 
1180
1180
  [[package]]
1181
1181
  name = "mdka"
1182
- version = "2.2.2"
1182
+ version = "2.2.3"
1183
1183
  dependencies = [
1184
1184
  "criterion",
1185
1185
  "dom_smoothie",
@@ -1198,16 +1198,16 @@ dependencies = [
1198
1198
 
1199
1199
  [[package]]
1200
1200
  name = "mdka-cli"
1201
- version = "2.2.2"
1201
+ version = "2.2.3"
1202
1202
  dependencies = [
1203
- "mdka 2.2.2",
1203
+ "mdka 2.2.3",
1204
1204
  ]
1205
1205
 
1206
1206
  [[package]]
1207
1207
  name = "mdka-node"
1208
- version = "2.2.2"
1208
+ version = "2.2.3"
1209
1209
  dependencies = [
1210
- "mdka 2.2.2",
1210
+ "mdka 2.2.3",
1211
1211
  "napi",
1212
1212
  "napi-build",
1213
1213
  "napi-derive",
@@ -1216,9 +1216,9 @@ dependencies = [
1216
1216
 
1217
1217
  [[package]]
1218
1218
  name = "mdka-python"
1219
- version = "2.2.2"
1219
+ version = "2.2.3"
1220
1220
  dependencies = [
1221
- "mdka 2.2.2",
1221
+ "mdka 2.2.3",
1222
1222
  "pyo3",
1223
1223
  "rayon",
1224
1224
  ]
@@ -3,11 +3,11 @@ members = [".", "python"]
3
3
  resolver = "2"
4
4
 
5
5
  [workspace.dependencies]
6
- mdka = { version = "2.2.2", path = "." }
6
+ mdka = { version = "2.2.3", path = "." }
7
7
 
8
8
  [package]
9
9
  name = "mdka"
10
- version = "2.2.2"
10
+ version = "2.2.3"
11
11
  edition = "2024"
12
12
  rust-version = "1.88"
13
13
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mdka
3
- Version: 2.2.2
3
+ Version: 2.2.3
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: License :: OSI Approved :: Apache Software License
@@ -25,7 +25,7 @@ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
25
25
  [![npm](https://github.com/nabbisen/mdka-rs/actions/workflows/release-npm.yaml/badge.svg)](https://github.com/nabbisen/mdka-rs/actions/workflows/release-npm.yaml)
26
26
  [![PyPi](https://github.com/nabbisen/mdka-rs/actions/workflows/release-pypi.yaml/badge.svg)](https://github.com/nabbisen/mdka-rs/actions/workflows/release-pypi.yaml)
27
27
 
28
- ![logo](/docs/src/assets/logo.png)
28
+ ![logo](https://raw.githubusercontent.com/nabbisen/mdka-rs/main/docs/src/assets/logo.png)
29
29
 
30
30
  mdka balances conversion quality with runtime efficiency —
31
31
  readable output from real-world HTML, without sacrificing speed or memory.
@@ -49,7 +49,8 @@ CMS output, and SPA-rendered DOM without special-casing.
49
49
  no matter the nesting depth.
50
50
  - **Configurable pre-processing.**
51
51
  Five [conversion modes](#conversion-modes) let you tune what gets kept or
52
- stripped — from noise-free LLM input to lossless archiving.
52
+ stripped, from noise-free LLM input to maximum retention. Three of the five
53
+ currently produce identical output — see [Conversion Modes](#conversion-modes).
53
54
  - **Multi-language.**
54
55
  The same Rust implementation is accessible from Node.js (napi-rs) and
55
56
  Python (PyO3).
@@ -98,7 +99,7 @@ echo '<h1>Hello</h1><p><strong>world</strong></p>' | mdka
98
99
 
99
100
  ```bash
100
101
  mdka page.html # → page.md (same directory)
101
- mdka --mode minimal --drop-shell *.html # strip nav/header/footer
102
+ mdka --mode minimal --drop-shell -o out/ *.html # strip nav/header/footer
102
103
  mdka --help # full option list
103
104
  ```
104
105
 
@@ -120,12 +121,14 @@ let md = html_to_markdown("<h1>Hello</h1><p><em>world</em></p>");
120
121
  With options:
121
122
 
122
123
  ```rust
123
- use mdka::{html_to_markdown_with};
124
+ use mdka::html_to_markdown_with;
124
125
  use mdka::options::{ConversionMode, ConversionOptions};
125
126
 
127
+ let html = "<nav>menu</nav><h1>Hello</h1>";
126
128
  let mut opts = ConversionOptions::for_mode(ConversionMode::Minimal);
127
129
  opts.drop_interactive_shell = true;
128
130
  let md = html_to_markdown_with(html, &opts);
131
+ // "# Hello\n"
129
132
  ```
130
133
 
131
134
  ### Add to a Node.js project
@@ -135,14 +138,20 @@ npm install mdka
135
138
  ```
136
139
 
137
140
  ```js
138
- const { htmlToMarkdown, htmlToMarkdownWith } = require('mdka')
141
+ const { htmlToMarkdown, htmlToMarkdownWithAsync } = require('mdka')
139
142
 
140
143
  const md = htmlToMarkdown('<h1>Hello</h1>')
141
-
142
- const md = await htmlToMarkdownWithAsync(html, {
143
- mode: 'minimal',
144
- dropInteractiveShell: true,
145
- })
144
+ // "# Hello\n"
145
+
146
+ async function main() {
147
+ const html = '<nav>menu</nav><h1>Hello</h1>'
148
+ const minimal = await htmlToMarkdownWithAsync(html, {
149
+ mode: 'minimal',
150
+ dropInteractiveShell: true,
151
+ })
152
+ console.log(minimal)
153
+ }
154
+ main()
146
155
  ```
147
156
 
148
157
  ### Add to a Python project
@@ -155,8 +164,10 @@ pip install mdka
155
164
  import mdka
156
165
 
157
166
  md = mdka.html_to_markdown('<h1>Hello</h1>')
167
+ # "# Hello\n"
158
168
 
159
- md = mdka.html_to_markdown_with(
169
+ html = '<nav>menu</nav><h1>Hello</h1>'
170
+ minimal = mdka.html_to_markdown_with(
160
171
  html,
161
172
  mode=mdka.ConversionMode.Minimal,
162
173
  drop_interactive_shell=True,
@@ -175,11 +186,24 @@ md = mdka.html_to_markdown_with(
175
186
  | `Semantic` | SPA content, ARIA-aware pipelines |
176
187
  | `Preserve` | Archiving, audit trails |
177
188
 
189
+ **`Balanced`, `Strict` and `Preserve` currently produce identical output.** They
190
+ differ only in the defaults of five fields that have no effect, so choosing
191
+ between them changes nothing today. They remain distinct API and may diverge
192
+ again — see
193
+ [Conversion Modes](https://nabbisen.github.io/mdka-rs/api/modes), which explains
194
+ why in full.
195
+
196
+ **Tables are not yet converted.** `<table>` cell text is emitted without
197
+ structure or separators, so a table becomes a run of joined text. See
198
+ [Supported Elements](https://nabbisen.github.io/mdka-rs/api/elements) for the
199
+ full list of what is and is not supported.
200
+
178
201
  ---
179
202
 
180
203
  ## Learn More
181
204
 
182
- Full documentation lives in the [`docs/`](./docs/) folder, published as GitHub Pages.
205
+ Full documentation is published as GitHub Pages, and its source lives in
206
+ [`docs/`](https://github.com/nabbisen/mdka-rs/tree/main/docs).
183
207
 
184
208
  https://nabbisen.github.io/mdka-rs/
185
209
 
@@ -198,8 +222,8 @@ https://nabbisen.github.io/mdka-rs/
198
222
  | Performance Characteristics | [/design/performance-characteristics](https://nabbisen.github.io/mdka-rs/design/performance-characteristics) |
199
223
  | Architecture | [/design/architecture](https://nabbisen.github.io/mdka-rs/design/architecture) |
200
224
  | Features | [/design/features](https://nabbisen.github.io/mdka-rs/design/features) |
201
- | Changelog | [CHANGELOG.md](./CHANGELOG.md) |
202
- | Roadmap | [ROADMAP.md](./ROADMAP.md) |
225
+ | Changelog | [CHANGELOG.md](https://github.com/nabbisen/mdka-rs/blob/main/CHANGELOG.md) |
226
+ | Roadmap | [ROADMAP.md](https://github.com/nabbisen/mdka-rs/blob/main/ROADMAP.md) |
203
227
 
204
228
  ---
205
229
 
@@ -13,7 +13,7 @@
13
13
  [![npm](https://github.com/nabbisen/mdka-rs/actions/workflows/release-npm.yaml/badge.svg)](https://github.com/nabbisen/mdka-rs/actions/workflows/release-npm.yaml)
14
14
  [![PyPi](https://github.com/nabbisen/mdka-rs/actions/workflows/release-pypi.yaml/badge.svg)](https://github.com/nabbisen/mdka-rs/actions/workflows/release-pypi.yaml)
15
15
 
16
- ![logo](/docs/src/assets/logo.png)
16
+ ![logo](https://raw.githubusercontent.com/nabbisen/mdka-rs/main/docs/src/assets/logo.png)
17
17
 
18
18
  mdka balances conversion quality with runtime efficiency —
19
19
  readable output from real-world HTML, without sacrificing speed or memory.
@@ -37,7 +37,8 @@ CMS output, and SPA-rendered DOM without special-casing.
37
37
  no matter the nesting depth.
38
38
  - **Configurable pre-processing.**
39
39
  Five [conversion modes](#conversion-modes) let you tune what gets kept or
40
- stripped — from noise-free LLM input to lossless archiving.
40
+ stripped, from noise-free LLM input to maximum retention. Three of the five
41
+ currently produce identical output — see [Conversion Modes](#conversion-modes).
41
42
  - **Multi-language.**
42
43
  The same Rust implementation is accessible from Node.js (napi-rs) and
43
44
  Python (PyO3).
@@ -86,7 +87,7 @@ echo '<h1>Hello</h1><p><strong>world</strong></p>' | mdka
86
87
 
87
88
  ```bash
88
89
  mdka page.html # → page.md (same directory)
89
- mdka --mode minimal --drop-shell *.html # strip nav/header/footer
90
+ mdka --mode minimal --drop-shell -o out/ *.html # strip nav/header/footer
90
91
  mdka --help # full option list
91
92
  ```
92
93
 
@@ -108,12 +109,14 @@ let md = html_to_markdown("<h1>Hello</h1><p><em>world</em></p>");
108
109
  With options:
109
110
 
110
111
  ```rust
111
- use mdka::{html_to_markdown_with};
112
+ use mdka::html_to_markdown_with;
112
113
  use mdka::options::{ConversionMode, ConversionOptions};
113
114
 
115
+ let html = "<nav>menu</nav><h1>Hello</h1>";
114
116
  let mut opts = ConversionOptions::for_mode(ConversionMode::Minimal);
115
117
  opts.drop_interactive_shell = true;
116
118
  let md = html_to_markdown_with(html, &opts);
119
+ // "# Hello\n"
117
120
  ```
118
121
 
119
122
  ### Add to a Node.js project
@@ -123,14 +126,20 @@ npm install mdka
123
126
  ```
124
127
 
125
128
  ```js
126
- const { htmlToMarkdown, htmlToMarkdownWith } = require('mdka')
129
+ const { htmlToMarkdown, htmlToMarkdownWithAsync } = require('mdka')
127
130
 
128
131
  const md = htmlToMarkdown('<h1>Hello</h1>')
129
-
130
- const md = await htmlToMarkdownWithAsync(html, {
131
- mode: 'minimal',
132
- dropInteractiveShell: true,
133
- })
132
+ // "# Hello\n"
133
+
134
+ async function main() {
135
+ const html = '<nav>menu</nav><h1>Hello</h1>'
136
+ const minimal = await htmlToMarkdownWithAsync(html, {
137
+ mode: 'minimal',
138
+ dropInteractiveShell: true,
139
+ })
140
+ console.log(minimal)
141
+ }
142
+ main()
134
143
  ```
135
144
 
136
145
  ### Add to a Python project
@@ -143,8 +152,10 @@ pip install mdka
143
152
  import mdka
144
153
 
145
154
  md = mdka.html_to_markdown('<h1>Hello</h1>')
155
+ # "# Hello\n"
146
156
 
147
- md = mdka.html_to_markdown_with(
157
+ html = '<nav>menu</nav><h1>Hello</h1>'
158
+ minimal = mdka.html_to_markdown_with(
148
159
  html,
149
160
  mode=mdka.ConversionMode.Minimal,
150
161
  drop_interactive_shell=True,
@@ -163,11 +174,24 @@ md = mdka.html_to_markdown_with(
163
174
  | `Semantic` | SPA content, ARIA-aware pipelines |
164
175
  | `Preserve` | Archiving, audit trails |
165
176
 
177
+ **`Balanced`, `Strict` and `Preserve` currently produce identical output.** They
178
+ differ only in the defaults of five fields that have no effect, so choosing
179
+ between them changes nothing today. They remain distinct API and may diverge
180
+ again — see
181
+ [Conversion Modes](https://nabbisen.github.io/mdka-rs/api/modes), which explains
182
+ why in full.
183
+
184
+ **Tables are not yet converted.** `<table>` cell text is emitted without
185
+ structure or separators, so a table becomes a run of joined text. See
186
+ [Supported Elements](https://nabbisen.github.io/mdka-rs/api/elements) for the
187
+ full list of what is and is not supported.
188
+
166
189
  ---
167
190
 
168
191
  ## Learn More
169
192
 
170
- Full documentation lives in the [`docs/`](./docs/) folder, published as GitHub Pages.
193
+ Full documentation is published as GitHub Pages, and its source lives in
194
+ [`docs/`](https://github.com/nabbisen/mdka-rs/tree/main/docs).
171
195
 
172
196
  https://nabbisen.github.io/mdka-rs/
173
197
 
@@ -186,8 +210,8 @@ https://nabbisen.github.io/mdka-rs/
186
210
  | Performance Characteristics | [/design/performance-characteristics](https://nabbisen.github.io/mdka-rs/design/performance-characteristics) |
187
211
  | Architecture | [/design/architecture](https://nabbisen.github.io/mdka-rs/design/architecture) |
188
212
  | Features | [/design/features](https://nabbisen.github.io/mdka-rs/design/features) |
189
- | Changelog | [CHANGELOG.md](./CHANGELOG.md) |
190
- | Roadmap | [ROADMAP.md](./ROADMAP.md) |
213
+ | Changelog | [CHANGELOG.md](https://github.com/nabbisen/mdka-rs/blob/main/CHANGELOG.md) |
214
+ | Roadmap | [ROADMAP.md](https://github.com/nabbisen/mdka-rs/blob/main/ROADMAP.md) |
191
215
 
192
216
  ---
193
217
 
@@ -1,9 +1,10 @@
1
1
  # mdka — Roadmap
2
2
 
3
3
  **Status.** Active — planning baseline approved by the project owner on 2026-08-02.
4
- **Current version.** 2.2.2 (released 2026-09-16)
5
- **Milestone progress.** M1, M1b, M2 and **M2b complete**. `2.2.1` shipped RFC 020;
6
- `2.2.2` shipped RFC 007, 021, 022, 023, 026 and 027. **M3 is next.**
4
+ **Current version.** 2.2.3 (released 2026-09-16)
5
+ **Current version note.** `2.2.1` shipped RFC 020; `2.2.2` shipped RFC 007, 021,
6
+ 022, 023, 026 and 027; `2.2.3` shipped RFC 029.
7
+ **Milestone progress.** M1, M1b, M2, M2b and **M2c complete**. **M3 is next.**
7
8
  **Governance.** RFC lifecycle follows [RFC 000](./rfcs/done/000-rfc-lifecycle-policy.md).
8
9
 
9
10
  This document is the planning baseline from which the RFC portfolio is derived.
@@ -382,6 +383,49 @@ These gates narrow what the consumer pass has to catch; they do not replace it.
382
383
  **Do not describe this set as complete coverage.** Four green checkmarks mean
383
384
  the Linux artifacts install and the documented examples resolve — nothing more.
384
385
 
386
+ ### M2c · Published-surface repair → `2.2.3` (patch) — ✅ COMPLETE
387
+
388
+ From the **first consumer pass** (RFC 027 Rule 1), run against published `2.2.2`
389
+ by a session with no history of this project. Disposition:
390
+ `.git-exclude/reviewed/2.2.2-consumer-pass/README.md`.
391
+
392
+ | RFC | Title | Priority | Size |
393
+ |---|---|---|---|
394
+ | 029 | Published-surface documentation repair | **P0** | S |
395
+
396
+ **Why a patch of its own.** The README's Node Quick Start does not parse, and it
397
+ renders on GitHub, crates.io, npm and PyPI. `usage-python.md` documents a keyword
398
+ argument that raises `TypeError`. Both are live; neither should wait behind M3's
399
+ renderer work.
400
+
401
+ #### What the consumer pass exposed about our controls
402
+
403
+ **`README.md` has never been in scope for any documentation RFC.** RFC 023's
404
+ boundary was `docs/src/getting-started/`; RFC 007's was six source files. The
405
+ most-read file in the project sat outside every one — **the third scope-boundary
406
+ miss this milestone**, after RFC 022's `examples/` consumers and RFC 007's count
407
+ table.
408
+
409
+ RFC 027 Rule 2 now requires boundaries be derived from a search. **That is
410
+ necessary and was not sufficient**: a search only covers what you think to
411
+ search for.
412
+
413
+ **The docs-example gate could not see it either** — `--root` defaults to
414
+ `docs/src`. And it checks symbol resolution, not keyword arguments, so the
415
+ Python defect was invisible twice over. Both recorded as corrections on RFC 026;
416
+ both fixed by RFC 029.
417
+
418
+ **One finding went to M3, not here.** F-04 — link text silently losing spaces
419
+ inside `<a>` — shares RFC 024's root cause but has a worse symptom: `****[b](/x)`
420
+ is visibly wrong, `[Readmore now]` reads as prose with a word gone. Added to
421
+ RFC 024 as an explicit acceptance criterion so it cannot be fixed by accident.
422
+
423
+ **Recorded, not scheduled:** F-24, deep nesting is quadratic — 25k depth 0.72s,
424
+ 100k 10.5s, 300k 255s. The README's "no stack overflow at any depth" claim holds;
425
+ the failure mode moved from crash to hang. M4. And F-23, default `Balanced`
426
+ emitting 1,190 `<a id>` anchors on a real Wikipedia page — a design question
427
+ shared with bekoedit's item 8, not a defect.
428
+
385
429
  ### M3 · Conversion fidelity → `2.3.0` (minor)
386
430
 
387
431
  Purely additive element coverage. Tables are the largest known gap against the