Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ Which is rendered as:
<!-- ⚠️ This content is auto-generated by `markdown-code-runner`. -->
```bash
usage: markdown-code-runner [-h] [-o OUTPUT] [-d] [-v]
[--no-backtick-standardize]
[--no-backtick-standardize] [-s] [-n]
input

Automatically update Markdown files with code block output.
Expand All @@ -375,6 +375,10 @@ options:
--no-backtick-standardize
Disable backtick standardization (default: enabled for
separate output files, disabled for in-place)
-s, --standardize Post-process to standardize ALL code fences, removing
'markdown-code-runner' modifiers
-n, --no-execute Skip code execution entirely (useful with
--standardize for compatibility processing only)
```

<!-- OUTPUT:END -->
Expand Down
4 changes: 2 additions & 2 deletions docs/docs_gen.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# ruff: noqa: T201, S603, S607
# ruff: noqa: S603, S607
"""Documentation generation utilities for Markdown Code Runner.

Provides functions to extract sections from README.md and transform
Expand Down Expand Up @@ -133,7 +133,7 @@ def _run_markdown_code_runner(files: list[Path], repo_root: Path) -> bool:
rel_path = file.relative_to(repo_root)
print(f"Updating {rel_path}...", end=" ", flush=True)
result = subprocess.run(
["markdown-code-runner", str(file)],
["markdown-code-runner", "--standardize", str(file)],
check=False,
capture_output=True,
text=True,
Expand Down
38 changes: 38 additions & 0 deletions docs/usage/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,44 @@ By default, when writing to a separate output file, the `markdown-code-runner` t
markdown-code-runner README.md -o output.md --no-backtick-standardize
```

### Standardize All Code Fences (`-s`, `--standardize`)

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.

```bash
markdown-code-runner README.md --standardize
```

This transforms code fences like:

````markdown
```python markdown-code-runner
print('hello')
```
````

Into standard code fences:

````markdown
```python
print('hello')
```
````

### Skip Code Execution (`-n`, `--no-execute`)

Skip code execution entirely. This is useful when you only want to standardize code fences without running any code.

```bash
markdown-code-runner README.md --no-execute --standardize
```

This combination is particularly useful for:

- Preparing files for external markdown processors
- Converting files without re-running code blocks
- Creating compatible output from existing processed files

### Version (`-v`, `--version`)

Display the installed version.
Expand Down
72 changes: 70 additions & 2 deletions docs/usage/python-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ def update_markdown_file(
*,
verbose: bool = False,
backtick_standardize: bool = True,
execute: bool = True,
standardize: bool = False,
) -> None:
"""Rewrite a Markdown file by executing and updating code blocks.

Expand All @@ -37,7 +39,14 @@ def update_markdown_file(
verbose : bool
If True, print every line that is processed.
backtick_standardize : bool
If True, clean up markdown-code-runner string from backtick code blocks.
If True, clean up markdown-code-runner string from executed backtick code blocks.
execute : bool
If True, execute code blocks and update output sections.
If False, skip code execution (useful with standardize=True).
standardize : bool
If True, post-process to standardize ALL code fences in the output,
removing 'markdown-code-runner' modifiers. This is useful for
compatibility with markdown processors like mkdocs and pandoc.
"""
```

Expand All @@ -51,6 +60,7 @@ def process_markdown(
*,
verbose: bool = False,
backtick_standardize: bool = True,
execute: bool = True,
) -> list[str]:
"""Execute code blocks in a list of Markdown-formatted strings.

Expand All @@ -61,7 +71,10 @@ def process_markdown(
verbose
If True, print every line that is processed.
backtick_standardize
If True, clean up markdown-code-runner string from backtick code blocks.
If True, clean up markdown-code-runner string from executed backtick code blocks.
execute
If True, execute code blocks and update output sections.
If False, return content unchanged.

Returns
-------
Expand All @@ -70,6 +83,29 @@ def process_markdown(
"""
```

### `standardize_code_fences`

Utility function to strip `markdown-code-runner` modifiers from code fence language identifiers.

```python
def standardize_code_fences(content: str) -> str:
"""Strip markdown-code-runner modifiers from all code fence language identifiers.

This is useful for making markdown files compatible with standard markdown
processors like mkdocs and pandoc.

Parameters
----------
content
The markdown content as a string.

Returns
-------
str
The content with all code fence modifiers stripped.
"""
```

## Examples

### Basic Usage
Expand Down Expand Up @@ -117,3 +153,35 @@ for md_file in docs_dir.rglob("*.md"):
print(f"Processing {md_file}...")
update_markdown_file(md_file)
```

### Standardizing Code Fences for External Processors

```python
from markdown_code_runner import update_markdown_file

# Execute code AND standardize all code fences
update_markdown_file("README.md", "docs/README.md", standardize=True)

# Only standardize without executing code
update_markdown_file("README.md", "docs/README.md", execute=False, standardize=True)
```

### Using the Standardize Function Directly

```python
from markdown_code_runner import standardize_code_fences

content = """
```python markdown-code-runner
print('hello')
```
"""

# Remove markdown-code-runner modifiers
clean_content = standardize_code_fences(content)
print(clean_content)
# Output:
# ```python
# print('hello')
# ```
```
80 changes: 78 additions & 2 deletions markdown_code_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,42 @@ def _bold(text: str) -> str:
return f"{bold}{text}{reset}"


def standardize_code_fences(content: str) -> str:
"""Strip markdown-code-runner modifiers from all code fence language identifiers.

This is useful for making markdown files compatible with standard markdown
processors like mkdocs and pandoc, which don't understand the
``python markdown-code-runner`` syntax.

Parameters
----------
content
The markdown content as a string.

Returns
-------
str
The content with all code fence modifiers stripped.

Examples
--------
>>> text = '''```python markdown-code-runner
... print("hello")
... ```'''
>>> print(standardize_code_fences(text))
```python
print("hello")
```

"""
return re.sub(
r"^(```\w+)\s+markdown-code-runner(?:\s+\S+=\S+)*\s*$",
r"\1",
content,
flags=re.MULTILINE,
)


def _extract_backtick_options(line: str) -> dict[str, str]:
"""Extract extra information from a line."""
match = re.search(r"```(?P<language>\w+)", line)
Expand Down Expand Up @@ -310,6 +346,7 @@ def process_markdown(
*,
verbose: bool = False,
backtick_standardize: bool = True,
execute: bool = True,
) -> list[str]:
"""Executes code blocks in a list of Markdown-formatted strings and returns the modified list.

Expand All @@ -321,6 +358,9 @@ def process_markdown(
If True, print every line that is processed.
backtick_standardize
If True, clean up markdown-code-runner string from backtick code blocks.
execute
If True, execute code blocks and update output sections.
If False, return content unchanged (useful with post-processing standardization).

Returns
-------
Expand All @@ -329,6 +369,9 @@ def process_markdown(

"""
assert isinstance(content, list), "Input must be a list"
if not execute:
return content

state = ProcessingState(backtick_standardize=backtick_standardize)

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


def update_markdown_file(
def update_markdown_file( # noqa: PLR0913
input_filepath: Path | str,
output_filepath: Path | str | None = None,
*,
verbose: bool = False,
backtick_standardize: bool = True,
execute: bool = True,
standardize: bool = False,
) -> None:
"""Rewrite a Markdown file by executing and updating code blocks.

Expand All @@ -357,7 +402,14 @@ def update_markdown_file(
verbose : bool
If True, print every line that is processed.
backtick_standardize : bool
If True, clean up markdown-code-runner string from backtick code blocks.
If True, clean up markdown-code-runner string from executed backtick code blocks.
execute : bool
If True, execute code blocks and update output sections.
If False, skip code execution (useful with standardize=True).
standardize : bool
If True, post-process to standardize ALL code fences in the output,
removing ``markdown-code-runner`` modifiers. This is useful for
compatibility with markdown processors like mkdocs and pandoc.

"""
if isinstance(input_filepath, str): # pragma: no cover
Expand All @@ -370,8 +422,16 @@ def update_markdown_file(
original_lines,
verbose=verbose,
backtick_standardize=backtick_standardize,
execute=execute,
)
updated_content = "\n".join(new_lines).rstrip() + "\n"

# Post-process to standardize all code fences if requested
if standardize:
if verbose:
print("Standardizing all code fences...")
updated_content = standardize_code_fences(updated_content)

if verbose:
print(f"Writing output to: {output_filepath}")
output_filepath = (
Expand Down Expand Up @@ -418,6 +478,20 @@ def main() -> None:
help="Disable backtick standardization (default: enabled for separate output files, disabled for in-place)",
default=False,
)
parser.add_argument(
"-s",
"--standardize",
action="store_true",
help="Post-process to standardize ALL code fences, removing 'markdown-code-runner' modifiers",
default=False,
)
parser.add_argument(
"-n",
"--no-execute",
action="store_true",
help="Skip code execution entirely (useful with --standardize for compatibility processing only)",
default=False,
)

args = parser.parse_args()

Expand All @@ -434,6 +508,8 @@ def main() -> None:
output_filepath,
verbose=args.verbose,
backtick_standardize=backtick_standardize,
execute=not args.no_execute,
standardize=args.standardize,
)


Expand Down
Loading