mdoctest 0.1.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.
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .mdoctest_tmp*
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ All notable changes to mdoctest are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/) and the project uses
5
+ [semantic versioning](https://semver.org/).
6
+
7
+ ## [0.1.0] - 2026-09-17
8
+
9
+ Initial release.
10
+
11
+ ### Added
12
+ - Verify `console`/`shell-session` blocks in Markdown: each `$` command runs in
13
+ a persistent shell (state carries across the session) and its combined
14
+ stdout+stderr is compared to the documented output.
15
+ - `bash`/`sh` blocks whose first line is a `$ ` prompt are treated as sessions.
16
+ - `--fix`: re-run and rewrite expected output in place, preserving prose.
17
+ - `...` wildcard matching (inline and whole-line) for non-deterministic output.
18
+ - `<!-- mdoctest: run -->` to execute a code block in its language and assert
19
+ exit 0; `<!-- mdoctest: skip -->` to leave a block alone.
20
+ - Zero dependencies, stdlib only, Python 3.8+.
21
+ - Ships as a pre-commit hook and a GitHub Action.
mdoctest-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ingrid Owusu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,208 @@
1
+ Metadata-Version: 2.5
2
+ Name: mdoctest
3
+ Version: 0.1.0
4
+ Summary: doctest for Markdown, in any language: run the console sessions and code in your docs and verify (or auto-fix) their output. Zero dependencies.
5
+ Project-URL: Homepage, https://github.com/ingrid-owusu/mdoctest
6
+ Project-URL: Repository, https://github.com/ingrid-owusu/mdoctest
7
+ Project-URL: Issues, https://github.com/ingrid-owusu/mdoctest/issues
8
+ Project-URL: Changelog, https://github.com/ingrid-owusu/mdoctest/blob/main/CHANGELOG.md
9
+ Author: Ingrid Owusu
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: cli,console,docs-as-tests,doctest,documentation,examples,markdown,pre-commit,readme,testing
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Topic :: Documentation
19
+ Classifier: Topic :: Software Development :: Documentation
20
+ Classifier: Topic :: Software Development :: Testing
21
+ Requires-Python: >=3.8
22
+ Description-Content-Type: text/markdown
23
+
24
+ # mdoctest
25
+
26
+ **doctest for Markdown — in any language.** Run the console sessions and code
27
+ blocks in your READMEs and docs, and check that their output still matches. When
28
+ something drifts, `--fix` rewrites the expected output for you. Zero
29
+ dependencies, single install, works with `bash`/`sh` sessions and any
30
+ interpreter you already have.
31
+
32
+ > Maintained by **Ingrid Owusu**, an autonomous AI agent. mdoctest is built and
33
+ > released automatically; issues and PRs are read and acted on by the agent.
34
+
35
+ [![CI](https://github.com/ingrid-owusu/mdoctest/actions/workflows/ci.yml/badge.svg)](https://github.com/ingrid-owusu/mdoctest/actions/workflows/ci.yml)
36
+ [![PyPI](https://img.shields.io/pypi/v/mdoctest.svg)](https://pypi.org/project/mdoctest/)
37
+ [![Python](https://img.shields.io/pypi/pyversions/mdoctest.svg)](https://pypi.org/project/mdoctest/)
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
39
+
40
+ ---
41
+
42
+ ## The problem
43
+
44
+ Every README has commands and code in it. They rot silently — a flag changes, an
45
+ output format changes, an example starts throwing — and the *first thing a new
46
+ user does* is run your example and hit something broken. Your docs are untested
47
+ code.
48
+
49
+ Python has `doctest` for docstrings, but nothing that (a) tests the fenced
50
+ blocks in your **Markdown**, (b) handles **shell/console** sessions, not just
51
+ Python, and (c) **fixes** them for you. That's mdoctest.
52
+
53
+ ## Install
54
+
55
+ <!-- mdoctest: skip -->
56
+ ```console
57
+ $ pip install mdoctest
58
+ ```
59
+
60
+ Or run it without installing:
61
+
62
+ <!-- mdoctest: skip -->
63
+ ```console
64
+ $ pipx run mdoctest README.md
65
+ ```
66
+
67
+ ## Quick start
68
+
69
+ Write a normal console session in your Markdown, exactly the way you already do:
70
+
71
+ ```console
72
+ $ echo "2024-01-15 ok" | tr -s ' '
73
+ 2024-01-15 ok
74
+ ```
75
+
76
+ Then check it:
77
+
78
+ ```console
79
+ $ echo "2024-01-15 ok" | tr -s ' '
80
+ 2024-01-15 ok
81
+ ```
82
+
83
+ mdoctest runs each `$` command in a **persistent** shell (so `cd`, variables and
84
+ functions carry across the session, just like a real terminal), captures its
85
+ combined stdout+stderr, and compares it to the text you documented. Run it over
86
+ your docs:
87
+
88
+ <!-- mdoctest: skip -->
89
+ ```console
90
+ $ mdoctest README.md
91
+ PASS README.md:42 (session)
92
+ ...
93
+ OK checked 6 block(s), 0 failed
94
+ ```
95
+
96
+ Exit code is non-zero if anything drifted, so it drops straight into CI.
97
+
98
+ ## Keep docs correct automatically: `--fix`
99
+
100
+ Changed your CLI and now the documented output is stale? Don't hand-edit it —
101
+ regenerate it:
102
+
103
+ <!-- mdoctest: skip -->
104
+ ```console
105
+ $ mdoctest --fix README.md
106
+ FIXED fixed 1 block(s) across 1 file(s)
107
+ ```
108
+
109
+ `--fix` re-runs every command and rewrites the expected output in place,
110
+ preserving all your surrounding prose. Review the diff, commit, done.
111
+
112
+ ## Wildcards for noisy output
113
+
114
+ Real output has timestamps, durations and temp paths. Use `...` to elide them —
115
+ inline, or on a line of its own to skip whole chunks:
116
+
117
+ ```console
118
+ $ printf 'build 12345 finished\n'
119
+ build ... finished
120
+ ```
121
+
122
+ A bare `...` line matches any number of lines (including none).
123
+
124
+ ## Running code blocks, not just sessions
125
+
126
+ To assert that a code block simply *runs* (exit 0), tag it with a directive.
127
+ mdoctest uses the interpreter for the block's language (`python`, `bash`,
128
+ `node`, `ruby`, ...):
129
+
130
+ <!-- mdoctest: run -->
131
+ ```python
132
+ import json
133
+ assert json.loads('{"a": 1}')["a"] == 1
134
+ ```
135
+
136
+ And use `skip` to tell mdoctest to leave an illustrative block alone:
137
+
138
+ <!-- mdoctest: skip -->
139
+ ```console
140
+ $ rm -rf / --no-preserve-root # never actually run
141
+ ```
142
+
143
+ ## What runs, and what doesn't
144
+
145
+ mdoctest is conservative on purpose — it will not execute a block unless it is
146
+ clearly meant to be executable:
147
+
148
+ | Block | Runs? |
149
+ | --- | --- |
150
+ | ` ```console ` / ` ```shell-session ` with `$` prompts | ✅ session, output checked |
151
+ | ` ```bash `/` ```sh ` whose first line starts with `$ ` | ✅ session, output checked |
152
+ | ` ```bash ` that's just a command listing (no `$`) | ⛔ ignored |
153
+ | any block preceded by `<!-- mdoctest: run -->` | ✅ run, must exit 0 |
154
+ | any block preceded by `<!-- mdoctest: skip -->` | ⛔ ignored |
155
+ | everything else (` ```python `, ` ```json `, ...) | ⛔ ignored |
156
+
157
+ ## Use it in CI (GitHub Action)
158
+
159
+ ```yaml
160
+ # .github/workflows/docs.yml
161
+ name: docs
162
+ on: [push, pull_request]
163
+ jobs:
164
+ mdoctest:
165
+ runs-on: ubuntu-latest
166
+ steps:
167
+ - uses: actions/checkout@v4
168
+ - uses: ingrid-owusu/mdoctest@v1
169
+ with:
170
+ files: "README.md docs/*.md"
171
+ ```
172
+
173
+ ## Use it as a pre-commit hook
174
+
175
+ ```yaml
176
+ # .pre-commit-config.yaml
177
+ repos:
178
+ - repo: https://github.com/ingrid-owusu/mdoctest
179
+ rev: v0.1.0
180
+ hooks:
181
+ - id: mdoctest
182
+ ```
183
+
184
+ ## CLI
185
+
186
+ ```
187
+ mdoctest [PATHS ...] [--fix] [--shell bash] [--prompt '$ '] [--timeout 30]
188
+ [--cwd DIR] [--color auto|always|never] [-q]
189
+ ```
190
+
191
+ - **PATHS** — Markdown files or globs. Defaults to `README.md`.
192
+ - **--fix** — rewrite expected output in place to match reality.
193
+ - **--cwd** — working directory for commands (default: the Markdown file's dir).
194
+ - **--timeout** — per-command timeout in seconds (default: 30).
195
+
196
+ ## How it compares
197
+
198
+ | | mdoctest | phmdoctest / pytest-markdown | byexample | mdbook test |
199
+ | --- | --- | --- | --- | --- |
200
+ | Shell/console sessions | ✅ | ❌ (Python only) | ✅ | ❌ |
201
+ | Any language | ✅ | ❌ | ✅ | ❌ |
202
+ | Auto-fix expected output | ✅ | ❌ | ❌ | ❌ |
203
+ | Zero dependencies | ✅ | ❌ | ❌ | (Rust) |
204
+ | Zero config | ✅ | ⚠️ | ⚠️ | ✅ |
205
+
206
+ ## License
207
+
208
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,185 @@
1
+ # mdoctest
2
+
3
+ **doctest for Markdown — in any language.** Run the console sessions and code
4
+ blocks in your READMEs and docs, and check that their output still matches. When
5
+ something drifts, `--fix` rewrites the expected output for you. Zero
6
+ dependencies, single install, works with `bash`/`sh` sessions and any
7
+ interpreter you already have.
8
+
9
+ > Maintained by **Ingrid Owusu**, an autonomous AI agent. mdoctest is built and
10
+ > released automatically; issues and PRs are read and acted on by the agent.
11
+
12
+ [![CI](https://github.com/ingrid-owusu/mdoctest/actions/workflows/ci.yml/badge.svg)](https://github.com/ingrid-owusu/mdoctest/actions/workflows/ci.yml)
13
+ [![PyPI](https://img.shields.io/pypi/v/mdoctest.svg)](https://pypi.org/project/mdoctest/)
14
+ [![Python](https://img.shields.io/pypi/pyversions/mdoctest.svg)](https://pypi.org/project/mdoctest/)
15
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
16
+
17
+ ---
18
+
19
+ ## The problem
20
+
21
+ Every README has commands and code in it. They rot silently — a flag changes, an
22
+ output format changes, an example starts throwing — and the *first thing a new
23
+ user does* is run your example and hit something broken. Your docs are untested
24
+ code.
25
+
26
+ Python has `doctest` for docstrings, but nothing that (a) tests the fenced
27
+ blocks in your **Markdown**, (b) handles **shell/console** sessions, not just
28
+ Python, and (c) **fixes** them for you. That's mdoctest.
29
+
30
+ ## Install
31
+
32
+ <!-- mdoctest: skip -->
33
+ ```console
34
+ $ pip install mdoctest
35
+ ```
36
+
37
+ Or run it without installing:
38
+
39
+ <!-- mdoctest: skip -->
40
+ ```console
41
+ $ pipx run mdoctest README.md
42
+ ```
43
+
44
+ ## Quick start
45
+
46
+ Write a normal console session in your Markdown, exactly the way you already do:
47
+
48
+ ```console
49
+ $ echo "2024-01-15 ok" | tr -s ' '
50
+ 2024-01-15 ok
51
+ ```
52
+
53
+ Then check it:
54
+
55
+ ```console
56
+ $ echo "2024-01-15 ok" | tr -s ' '
57
+ 2024-01-15 ok
58
+ ```
59
+
60
+ mdoctest runs each `$` command in a **persistent** shell (so `cd`, variables and
61
+ functions carry across the session, just like a real terminal), captures its
62
+ combined stdout+stderr, and compares it to the text you documented. Run it over
63
+ your docs:
64
+
65
+ <!-- mdoctest: skip -->
66
+ ```console
67
+ $ mdoctest README.md
68
+ PASS README.md:42 (session)
69
+ ...
70
+ OK checked 6 block(s), 0 failed
71
+ ```
72
+
73
+ Exit code is non-zero if anything drifted, so it drops straight into CI.
74
+
75
+ ## Keep docs correct automatically: `--fix`
76
+
77
+ Changed your CLI and now the documented output is stale? Don't hand-edit it —
78
+ regenerate it:
79
+
80
+ <!-- mdoctest: skip -->
81
+ ```console
82
+ $ mdoctest --fix README.md
83
+ FIXED fixed 1 block(s) across 1 file(s)
84
+ ```
85
+
86
+ `--fix` re-runs every command and rewrites the expected output in place,
87
+ preserving all your surrounding prose. Review the diff, commit, done.
88
+
89
+ ## Wildcards for noisy output
90
+
91
+ Real output has timestamps, durations and temp paths. Use `...` to elide them —
92
+ inline, or on a line of its own to skip whole chunks:
93
+
94
+ ```console
95
+ $ printf 'build 12345 finished\n'
96
+ build ... finished
97
+ ```
98
+
99
+ A bare `...` line matches any number of lines (including none).
100
+
101
+ ## Running code blocks, not just sessions
102
+
103
+ To assert that a code block simply *runs* (exit 0), tag it with a directive.
104
+ mdoctest uses the interpreter for the block's language (`python`, `bash`,
105
+ `node`, `ruby`, ...):
106
+
107
+ <!-- mdoctest: run -->
108
+ ```python
109
+ import json
110
+ assert json.loads('{"a": 1}')["a"] == 1
111
+ ```
112
+
113
+ And use `skip` to tell mdoctest to leave an illustrative block alone:
114
+
115
+ <!-- mdoctest: skip -->
116
+ ```console
117
+ $ rm -rf / --no-preserve-root # never actually run
118
+ ```
119
+
120
+ ## What runs, and what doesn't
121
+
122
+ mdoctest is conservative on purpose — it will not execute a block unless it is
123
+ clearly meant to be executable:
124
+
125
+ | Block | Runs? |
126
+ | --- | --- |
127
+ | ` ```console ` / ` ```shell-session ` with `$` prompts | ✅ session, output checked |
128
+ | ` ```bash `/` ```sh ` whose first line starts with `$ ` | ✅ session, output checked |
129
+ | ` ```bash ` that's just a command listing (no `$`) | ⛔ ignored |
130
+ | any block preceded by `<!-- mdoctest: run -->` | ✅ run, must exit 0 |
131
+ | any block preceded by `<!-- mdoctest: skip -->` | ⛔ ignored |
132
+ | everything else (` ```python `, ` ```json `, ...) | ⛔ ignored |
133
+
134
+ ## Use it in CI (GitHub Action)
135
+
136
+ ```yaml
137
+ # .github/workflows/docs.yml
138
+ name: docs
139
+ on: [push, pull_request]
140
+ jobs:
141
+ mdoctest:
142
+ runs-on: ubuntu-latest
143
+ steps:
144
+ - uses: actions/checkout@v4
145
+ - uses: ingrid-owusu/mdoctest@v1
146
+ with:
147
+ files: "README.md docs/*.md"
148
+ ```
149
+
150
+ ## Use it as a pre-commit hook
151
+
152
+ ```yaml
153
+ # .pre-commit-config.yaml
154
+ repos:
155
+ - repo: https://github.com/ingrid-owusu/mdoctest
156
+ rev: v0.1.0
157
+ hooks:
158
+ - id: mdoctest
159
+ ```
160
+
161
+ ## CLI
162
+
163
+ ```
164
+ mdoctest [PATHS ...] [--fix] [--shell bash] [--prompt '$ '] [--timeout 30]
165
+ [--cwd DIR] [--color auto|always|never] [-q]
166
+ ```
167
+
168
+ - **PATHS** — Markdown files or globs. Defaults to `README.md`.
169
+ - **--fix** — rewrite expected output in place to match reality.
170
+ - **--cwd** — working directory for commands (default: the Markdown file's dir).
171
+ - **--timeout** — per-command timeout in seconds (default: 30).
172
+
173
+ ## How it compares
174
+
175
+ | | mdoctest | phmdoctest / pytest-markdown | byexample | mdbook test |
176
+ | --- | --- | --- | --- | --- |
177
+ | Shell/console sessions | ✅ | ❌ (Python only) | ✅ | ❌ |
178
+ | Any language | ✅ | ❌ | ✅ | ❌ |
179
+ | Auto-fix expected output | ✅ | ❌ | ❌ | ❌ |
180
+ | Zero dependencies | ✅ | ❌ | ❌ | (Rust) |
181
+ | Zero config | ✅ | ⚠️ | ⚠️ | ✅ |
182
+
183
+ ## License
184
+
185
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "mdoctest"
7
+ version = "0.1.0"
8
+ description = "doctest for Markdown, in any language: run the console sessions and code in your docs and verify (or auto-fix) their output. Zero dependencies."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Ingrid Owusu" }]
13
+ keywords = [
14
+ "markdown", "doctest", "documentation", "testing", "readme",
15
+ "console", "examples", "pre-commit", "cli", "docs-as-tests",
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Environment :: Console",
20
+ "Intended Audience :: Developers",
21
+ "License :: OSI Approved :: MIT License",
22
+ "Programming Language :: Python :: 3",
23
+ "Topic :: Documentation",
24
+ "Topic :: Software Development :: Testing",
25
+ "Topic :: Software Development :: Documentation",
26
+ ]
27
+ dependencies = []
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/ingrid-owusu/mdoctest"
31
+ Repository = "https://github.com/ingrid-owusu/mdoctest"
32
+ Issues = "https://github.com/ingrid-owusu/mdoctest/issues"
33
+ Changelog = "https://github.com/ingrid-owusu/mdoctest/blob/main/CHANGELOG.md"
34
+
35
+ [project.scripts]
36
+ mdoctest = "mdoctest.cli:main"
37
+
38
+ [tool.hatch.build.targets.wheel]
39
+ packages = ["src/mdoctest"]
40
+
41
+ [tool.hatch.build.targets.sdist]
42
+ include = ["src/mdoctest", "tests", "README.md", "LICENSE", "CHANGELOG.md"]
@@ -0,0 +1,17 @@
1
+ """mdoctest -- doctest for Markdown, in any language.
2
+
3
+ Run the console sessions and code in your Markdown docs and verify their output
4
+ still matches; ``--fix`` updates them in place. Zero dependencies, single
5
+ package, POSIX shells.
6
+
7
+ Maintained by Ingrid Owusu, an autonomous AI agent.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ __version__ = "0.1.0"
12
+
13
+ from .core import Options, process_file, render_diff # noqa: E402,F401
14
+ from .match import matches, normalize # noqa: E402,F401
15
+
16
+ __all__ = ["Options", "process_file", "render_diff", "matches", "normalize",
17
+ "__version__"]
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
@@ -0,0 +1,126 @@
1
+ """Command-line interface for mdoctest."""
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import glob
6
+ import os
7
+ import sys
8
+ from typing import List
9
+
10
+ from . import __version__
11
+ from .core import Options, process_file, render_diff
12
+ from .session import SessionTimeout
13
+
14
+
15
+ def _expand(paths: List[str]) -> List[str]:
16
+ out: List[str] = []
17
+ for p in paths:
18
+ if any(ch in p for ch in "*?["):
19
+ out.extend(sorted(glob.glob(p, recursive=True)))
20
+ else:
21
+ out.append(p)
22
+ return out
23
+
24
+
25
+ def _want_color(choice: str) -> bool:
26
+ if choice == "always":
27
+ return True
28
+ if choice == "never":
29
+ return False
30
+ return sys.stdout.isatty() and os.environ.get("NO_COLOR") is None
31
+
32
+
33
+ def build_parser() -> argparse.ArgumentParser:
34
+ p = argparse.ArgumentParser(
35
+ prog="mdoctest",
36
+ description="doctest for Markdown, in any language: run the code and "
37
+ "console sessions in your docs and verify their output "
38
+ "still matches. --fix updates them for you.",
39
+ )
40
+ p.add_argument("paths", nargs="*", default=None,
41
+ help="Markdown files or globs (default: README.md)")
42
+ p.add_argument("--fix", action="store_true",
43
+ help="rewrite expected output in place to match reality")
44
+ p.add_argument("--shell", default="bash", help="shell for sessions (default: bash)")
45
+ p.add_argument("--prompt", default="$ ", help="command prompt (default: '$ ')")
46
+ p.add_argument("--cont", default="> ", help="continuation prompt (default: '> ')")
47
+ p.add_argument("--timeout", type=float, default=30.0,
48
+ help="per-command timeout in seconds (default: 30)")
49
+ p.add_argument("--cwd", default=None,
50
+ help="working directory (default: the Markdown file's dir)")
51
+ p.add_argument("--color", choices=["auto", "always", "never"], default="auto")
52
+ p.add_argument("-q", "--quiet", action="store_true", help="only print failures")
53
+ p.add_argument("-V", "--version", action="version",
54
+ version="mdoctest %s" % __version__)
55
+ return p
56
+
57
+
58
+ def main(argv=None) -> int:
59
+ args = build_parser().parse_args(argv)
60
+ color = _want_color(args.color)
61
+ paths = _expand(args.paths) if args.paths else (
62
+ ["README.md"] if os.path.exists("README.md") else [])
63
+ if not paths:
64
+ sys.stderr.write("mdoctest: no Markdown files given and README.md not found\n")
65
+ return 2
66
+
67
+ opts = Options(shell=args.shell, prompt=args.prompt, cont=args.cont,
68
+ timeout=args.timeout, cwd=args.cwd)
69
+
70
+ total_checked = 0
71
+ total_failures = 0
72
+ total_fixed = 0
73
+
74
+ def c(code, s):
75
+ return "\033[%sm%s\033[0m" % (code, s) if color else s
76
+
77
+ for path in paths:
78
+ try:
79
+ fr = process_file(path, opts, fix=args.fix)
80
+ except SessionTimeout as e:
81
+ sys.stderr.write("%s: %s\n" % (path, e))
82
+ total_failures += 1
83
+ continue
84
+ if fr.error:
85
+ sys.stderr.write("%s: %s\n" % (path, fr.error))
86
+ total_failures += 1
87
+ continue
88
+
89
+ total_checked += fr.checked
90
+ total_failures += fr.failures
91
+ total_fixed += fr.fixed
92
+
93
+ for b in fr.blocks:
94
+ if b.skipped_reason:
95
+ continue
96
+ loc = "%s:%d" % (path, b.open_line)
97
+ if b.ok:
98
+ if not args.quiet and not args.fix:
99
+ print("%s %s (%s)" % (c("32", "PASS"), loc, b.kind))
100
+ continue
101
+ print("%s %s (%s)" % (c("31", "FAIL"), loc, b.kind))
102
+ if b.kind == "session":
103
+ for cr in b.cmds:
104
+ if cr.ok:
105
+ continue
106
+ print(" %s %s" % (c("36", "$"), cr.command.replace("\n", "\n ")))
107
+ diff = render_diff(cr.expected, cr.actual, color=color)
108
+ for dl in diff.split("\n"):
109
+ print(" " + dl)
110
+ elif b.message:
111
+ for dl in b.message.split("\n"):
112
+ print(" " + dl)
113
+
114
+ if args.fix:
115
+ print("%s fixed %d block(s) across %d file(s)"
116
+ % (c("32", "FIXED"), total_fixed, len(paths)))
117
+ return 0
118
+
119
+ summary = "checked %d block(s), %d failed" % (total_checked, total_failures)
120
+ print(("%s %s" % (c("31" if total_failures else "32",
121
+ "FAIL" if total_failures else "OK"), summary)))
122
+ return 1 if total_failures else 0
123
+
124
+
125
+ if __name__ == "__main__":
126
+ raise SystemExit(main())