Skip to content

Commit fd06857

Browse files
authored
Add --standardize and --no-execute CLI flags (#44)
* Strip markdown-code-runner modifier from code fences in docs The docs site renderer (Zensical/pymdownx) doesn't understand the "python markdown-code-runner" syntax for code fence language identifiers. This causes the fenced code blocks to be parsed as inline code instead of proper code blocks, breaking the rendering. Add a regex transformation in _transform_readme_links() to strip the "markdown-code-runner" modifier when pulling content from README into the docs templates. * Add --standardize and --no-execute CLI flags This adds two new CLI flags to support standardizing code fences for compatibility with markdown processors like mkdocs and pandoc: - `--standardize` / `-s`: Post-process to standardize ALL code fences, removing 'markdown-code-runner' modifiers from language identifiers - `--no-execute` / `-n`: Skip code execution entirely These flags can be combined for different use cases: - `--standardize`: Execute code AND standardize output - `--no-execute --standardize`: Only standardize, no execution - Default: Execute code with inline standardization of executed blocks The docs_gen.py script now uses `--standardize` instead of the regex workaround, making the solution available as a CLI option for all users. Closes #26 * Update README.md * Add documentation for --standardize and --no-execute flags Document the new CLI flags and Python API parameters: - CLI: --standardize/-s and --no-execute/-n options - Python API: execute and standardize parameters for update_markdown_file - Python API: standardize_code_fences utility function - Added usage examples for all new functionality * Add test for verbose output with standardize flag
1 parent dff4f8d commit fd06857

File tree

7 files changed

+422
-7
lines changed

7 files changed

+422
-7
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ Which is rendered as:
358358
<!-- ⚠️ This content is auto-generated by `markdown-code-runner`. -->
359359
```bash
360360
usage: markdown-code-runner [-h] [-o OUTPUT] [-d] [-v]
361-
[--no-backtick-standardize]
361+
[--no-backtick-standardize] [-s] [-n]
362362
input
363363
364364
Automatically update Markdown files with code block output.
@@ -375,6 +375,10 @@ options:
375375
--no-backtick-standardize
376376
Disable backtick standardization (default: enabled for
377377
separate output files, disabled for in-place)
378+
-s, --standardize Post-process to standardize ALL code fences, removing
379+
'markdown-code-runner' modifiers
380+
-n, --no-execute Skip code execution entirely (useful with
381+
--standardize for compatibility processing only)
378382
```
379383
380384
<!-- OUTPUT:END -->

docs/docs_gen.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env python3
2-
# ruff: noqa: T201, S603, S607
2+
# ruff: noqa: S603, S607
33
"""Documentation generation utilities for Markdown Code Runner.
44
55
Provides functions to extract sections from README.md and transform
@@ -133,7 +133,7 @@ def _run_markdown_code_runner(files: list[Path], repo_root: Path) -> bool:
133133
rel_path = file.relative_to(repo_root)
134134
print(f"Updating {rel_path}...", end=" ", flush=True)
135135
result = subprocess.run(
136-
["markdown-code-runner", str(file)],
136+
["markdown-code-runner", "--standardize", str(file)],
137137
check=False,
138138
capture_output=True,
139139
text=True,

docs/usage/cli.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,44 @@ By default, when writing to a separate output file, the `markdown-code-runner` t
6161
markdown-code-runner README.md -o output.md --no-backtick-standardize
6262
```
6363

64+
### Standardize All Code Fences (`-s`, `--standardize`)
65+
66+
Post-process the output to standardize ALL code fences, removing `markdown-code-runner` modifiers from language identifiers. This is useful for compatibility with markdown processors like mkdocs and pandoc that don't understand the `python markdown-code-runner` syntax.
67+
68+
```bash
69+
markdown-code-runner README.md --standardize
70+
```
71+
72+
This transforms code fences like:
73+
74+
````markdown
75+
```python markdown-code-runner
76+
print('hello')
77+
```
78+
````
79+
80+
Into standard code fences:
81+
82+
````markdown
83+
```python
84+
print('hello')
85+
```
86+
````
87+
88+
### Skip Code Execution (`-n`, `--no-execute`)
89+
90+
Skip code execution entirely. This is useful when you only want to standardize code fences without running any code.
91+
92+
```bash
93+
markdown-code-runner README.md --no-execute --standardize
94+
```
95+
96+
This combination is particularly useful for:
97+
98+
- Preparing files for external markdown processors
99+
- Converting files without re-running code blocks
100+
- Creating compatible output from existing processed files
101+
64102
### Version (`-v`, `--version`)
65103

66104
Display the installed version.

docs/usage/python-api.md

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ def update_markdown_file(
2525
*,
2626
verbose: bool = False,
2727
backtick_standardize: bool = True,
28+
execute: bool = True,
29+
standardize: bool = False,
2830
) -> None:
2931
"""Rewrite a Markdown file by executing and updating code blocks.
3032
@@ -37,7 +39,14 @@ def update_markdown_file(
3739
verbose : bool
3840
If True, print every line that is processed.
3941
backtick_standardize : bool
40-
If True, clean up markdown-code-runner string from backtick code blocks.
42+
If True, clean up markdown-code-runner string from executed backtick code blocks.
43+
execute : bool
44+
If True, execute code blocks and update output sections.
45+
If False, skip code execution (useful with standardize=True).
46+
standardize : bool
47+
If True, post-process to standardize ALL code fences in the output,
48+
removing 'markdown-code-runner' modifiers. This is useful for
49+
compatibility with markdown processors like mkdocs and pandoc.
4150
"""
4251
```
4352

@@ -51,6 +60,7 @@ def process_markdown(
5160
*,
5261
verbose: bool = False,
5362
backtick_standardize: bool = True,
63+
execute: bool = True,
5464
) -> list[str]:
5565
"""Execute code blocks in a list of Markdown-formatted strings.
5666
@@ -61,7 +71,10 @@ def process_markdown(
6171
verbose
6272
If True, print every line that is processed.
6373
backtick_standardize
64-
If True, clean up markdown-code-runner string from backtick code blocks.
74+
If True, clean up markdown-code-runner string from executed backtick code blocks.
75+
execute
76+
If True, execute code blocks and update output sections.
77+
If False, return content unchanged.
6578
6679
Returns
6780
-------
@@ -70,6 +83,29 @@ def process_markdown(
7083
"""
7184
```
7285

86+
### `standardize_code_fences`
87+
88+
Utility function to strip `markdown-code-runner` modifiers from code fence language identifiers.
89+
90+
```python
91+
def standardize_code_fences(content: str) -> str:
92+
"""Strip markdown-code-runner modifiers from all code fence language identifiers.
93+
94+
This is useful for making markdown files compatible with standard markdown
95+
processors like mkdocs and pandoc.
96+
97+
Parameters
98+
----------
99+
content
100+
The markdown content as a string.
101+
102+
Returns
103+
-------
104+
str
105+
The content with all code fence modifiers stripped.
106+
"""
107+
```
108+
73109
## Examples
74110

75111
### Basic Usage
@@ -117,3 +153,35 @@ for md_file in docs_dir.rglob("*.md"):
117153
print(f"Processing {md_file}...")
118154
update_markdown_file(md_file)
119155
```
156+
157+
### Standardizing Code Fences for External Processors
158+
159+
```python
160+
from markdown_code_runner import update_markdown_file
161+
162+
# Execute code AND standardize all code fences
163+
update_markdown_file("README.md", "docs/README.md", standardize=True)
164+
165+
# Only standardize without executing code
166+
update_markdown_file("README.md", "docs/README.md", execute=False, standardize=True)
167+
```
168+
169+
### Using the Standardize Function Directly
170+
171+
```python
172+
from markdown_code_runner import standardize_code_fences
173+
174+
content = """
175+
```python markdown-code-runner
176+
print('hello')
177+
```
178+
"""
179+
180+
# Remove markdown-code-runner modifiers
181+
clean_content = standardize_code_fences(content)
182+
print(clean_content)
183+
# Output:
184+
# ```python
185+
# print('hello')
186+
# ```
187+
```

markdown_code_runner.py

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,42 @@ def _bold(text: str) -> str:
161161
return f"{bold}{text}{reset}"
162162

163163

164+
def standardize_code_fences(content: str) -> str:
165+
"""Strip markdown-code-runner modifiers from all code fence language identifiers.
166+
167+
This is useful for making markdown files compatible with standard markdown
168+
processors like mkdocs and pandoc, which don't understand the
169+
``python markdown-code-runner`` syntax.
170+
171+
Parameters
172+
----------
173+
content
174+
The markdown content as a string.
175+
176+
Returns
177+
-------
178+
str
179+
The content with all code fence modifiers stripped.
180+
181+
Examples
182+
--------
183+
>>> text = '''```python markdown-code-runner
184+
... print("hello")
185+
... ```'''
186+
>>> print(standardize_code_fences(text))
187+
```python
188+
print("hello")
189+
```
190+
191+
"""
192+
return re.sub(
193+
r"^(```\w+)\s+markdown-code-runner(?:\s+\S+=\S+)*\s*$",
194+
r"\1",
195+
content,
196+
flags=re.MULTILINE,
197+
)
198+
199+
164200
def _extract_backtick_options(line: str) -> dict[str, str]:
165201
"""Extract extra information from a line."""
166202
match = re.search(r"```(?P<language>\w+)", line)
@@ -310,6 +346,7 @@ def process_markdown(
310346
*,
311347
verbose: bool = False,
312348
backtick_standardize: bool = True,
349+
execute: bool = True,
313350
) -> list[str]:
314351
"""Executes code blocks in a list of Markdown-formatted strings and returns the modified list.
315352
@@ -321,6 +358,9 @@ def process_markdown(
321358
If True, print every line that is processed.
322359
backtick_standardize
323360
If True, clean up markdown-code-runner string from backtick code blocks.
361+
execute
362+
If True, execute code blocks and update output sections.
363+
If False, return content unchanged (useful with post-processing standardization).
324364
325365
Returns
326366
-------
@@ -329,6 +369,9 @@ def process_markdown(
329369
330370
"""
331371
assert isinstance(content, list), "Input must be a list"
372+
if not execute:
373+
return content
374+
332375
state = ProcessingState(backtick_standardize=backtick_standardize)
333376

334377
for i, line in enumerate(content):
@@ -339,12 +382,14 @@ def process_markdown(
339382
return state.new_lines
340383

341384

342-
def update_markdown_file(
385+
def update_markdown_file( # noqa: PLR0913
343386
input_filepath: Path | str,
344387
output_filepath: Path | str | None = None,
345388
*,
346389
verbose: bool = False,
347390
backtick_standardize: bool = True,
391+
execute: bool = True,
392+
standardize: bool = False,
348393
) -> None:
349394
"""Rewrite a Markdown file by executing and updating code blocks.
350395
@@ -357,7 +402,14 @@ def update_markdown_file(
357402
verbose : bool
358403
If True, print every line that is processed.
359404
backtick_standardize : bool
360-
If True, clean up markdown-code-runner string from backtick code blocks.
405+
If True, clean up markdown-code-runner string from executed backtick code blocks.
406+
execute : bool
407+
If True, execute code blocks and update output sections.
408+
If False, skip code execution (useful with standardize=True).
409+
standardize : bool
410+
If True, post-process to standardize ALL code fences in the output,
411+
removing ``markdown-code-runner`` modifiers. This is useful for
412+
compatibility with markdown processors like mkdocs and pandoc.
361413
362414
"""
363415
if isinstance(input_filepath, str): # pragma: no cover
@@ -370,8 +422,16 @@ def update_markdown_file(
370422
original_lines,
371423
verbose=verbose,
372424
backtick_standardize=backtick_standardize,
425+
execute=execute,
373426
)
374427
updated_content = "\n".join(new_lines).rstrip() + "\n"
428+
429+
# Post-process to standardize all code fences if requested
430+
if standardize:
431+
if verbose:
432+
print("Standardizing all code fences...")
433+
updated_content = standardize_code_fences(updated_content)
434+
375435
if verbose:
376436
print(f"Writing output to: {output_filepath}")
377437
output_filepath = (
@@ -418,6 +478,20 @@ def main() -> None:
418478
help="Disable backtick standardization (default: enabled for separate output files, disabled for in-place)",
419479
default=False,
420480
)
481+
parser.add_argument(
482+
"-s",
483+
"--standardize",
484+
action="store_true",
485+
help="Post-process to standardize ALL code fences, removing 'markdown-code-runner' modifiers",
486+
default=False,
487+
)
488+
parser.add_argument(
489+
"-n",
490+
"--no-execute",
491+
action="store_true",
492+
help="Skip code execution entirely (useful with --standardize for compatibility processing only)",
493+
default=False,
494+
)
421495

422496
args = parser.parse_args()
423497

@@ -434,6 +508,8 @@ def main() -> None:
434508
output_filepath,
435509
verbose=args.verbose,
436510
backtick_standardize=backtick_standardize,
511+
execute=not args.no_execute,
512+
standardize=args.standardize,
437513
)
438514

439515

0 commit comments

Comments
 (0)