# docsub
> Substitute dynamically generated content in Markdown files
[![license](https://img.shields.io/github/license/makukha/docsub.svg)](https://github.com/makukha/docsub/blob/main/LICENSE)
[![versions](https://img.shields.io/pypi/pyversions/docsub.svg)](https://pypi.org/project/docsub)
[![pypi](https://img.shields.io/pypi/v/docsub.svg#v0.8.0)](https://pypi.python.org/pypi/docsub)
[![uses docsub](https://img.shields.io/badge/uses-docsub-royalblue)
](https://github.com/makukha/docsub)
> [!WARNING]
> * With `docsub`, every documentation file may become executable.
> * Never use `docsub` to process files from untrusted sources.
> * This project is in experimental state, syntax and functionality may change significantly.
> * If still want to try it, use pinned package version `docsub==0.8.0`
# Features
* Embed **static** files
* Embed **command execution** results
* **Idempotent** substitutions
* **Invisible** non-intrusive markup using comment blocks
* **Plays nicely** with other markups
* **Extensible** with project-local commands
* **Configurable** with config files and env vars
# Use cases
* Manage partially duplicate docs for multiple destinations
* Embed CLI reference in docs
* Embed dynamically generated content:
* Models evaluation results
* Project metadata
* Test reports
> [!NOTE]
> This file uses docsub itself. Dig into raw markup if interested.
## Docsub is not...
* ...a documentation engine like [Sphinx](https://www.sphinx-doc.org) or [MkDocs](https://www.mkdocs.org)
* ...a full-featured static website generator like [Pelican](https://getpelican.com)
* ...a templating engine like [Jinja](https://jinja.palletsprojects.com)
* ...a replacement for [Bump My Version](https://callowayproject.github.io/bump-my-version)
# Usage
```shell
$ uv run docsub apply -i README.md
```
## From separate files...
<table>
<tr>
<td style="vertical-align:top">
### README.md
<!-- docsub: begin #readme -->
<!-- docsub: include tests/test_readme_showcase/__input__.md -->
<!-- docsub: lines after 1 upto -1 -->
````markdown
# Title
<!-- docsub: begin -->
<!-- docsub: include info.md -->
<!-- docsub: include features.md -->
...
<!-- docsub: end -->
## Table
<!-- docsub: begin -->
<!-- docsub: include data.md -->
<!-- docsub: lines after 2 -->
| Col 1 | Col 2 |
|-------|-------|
...
<!-- docsub: end -->
## Code
<!-- docsub: begin #code -->
<!-- docsub: include func.py -->
<!-- docsub: lines after 1 upto -1 -->
```python
...
```
<!-- docsub: end #code -->
````
<!-- docsub: end #readme -->
</td>
<td style="vertical-align:top">
### info.md
<!-- docsub: begin #readme -->
<!-- docsub: include tests/test_readme_showcase/info.md -->
<!-- docsub: lines after 1 upto -1 -->
````markdown
> Long description.
````
<!-- docsub: end #readme -->
### features.md
<!-- docsub: begin #readme -->
<!-- docsub: include tests/test_readme_showcase/features.md -->
<!-- docsub: lines after 1 upto -1 -->
````markdown
* Feature 1
* Feature 2
* Feature 3
````
<!-- docsub: end #readme -->
### data.md
<!-- docsub: begin #readme -->
<!-- docsub: include tests/test_readme_showcase/data.md -->
<!-- docsub: lines after 1 upto -1 -->
````markdown
| Key 1 | value 1 |
| Key 2 | value 2 |
| Key 3 | value 3 |
````
<!-- docsub: end #readme -->
### func.py
<!-- docsub: begin #readme -->
<!-- docsub: include tests/test_readme_showcase/func.py -->
<!-- docsub: lines after 1 upto -1 -->
````python
def func():
pass
````
<!-- docsub: end #readme -->
</td>
</tr>
</table>
## Get merged document
***and keep it updated!***
<!-- docsub: begin #readme -->
<!-- docsub: include tests/test_readme_showcase/__result__.md -->
<!-- docsub: lines after 1 upto -1 -->
````markdown
# Title
<!-- docsub: begin -->
<!-- docsub: include info.md -->
<!-- docsub: include features.md -->
> Long description.
* Feature 1
* Feature 2
* Feature 3
<!-- docsub: end -->
## Table
<!-- docsub: begin -->
<!-- docsub: include data.md -->
<!-- docsub: lines after 2 -->
| Col 1 | Col 2 |
|-------|-------|
| Key 1 | value 1 |
| Key 2 | value 2 |
| Key 3 | value 3 |
<!-- docsub: end -->
## Code
<!-- docsub: begin #code -->
<!-- docsub: include func.py -->
<!-- docsub: lines after 1 upto -1 -->
```python
def func():
pass
```
<!-- docsub: end #code -->
````
<!-- docsub: end #readme -->
# Installation
## Development dependency
Recommended. The most flexible installation option, allowing [project-local commands](#project-local-commands) to utilize project codebase.
```toml
# pyproject.toml
[dependency-groups]
dev = [
"docsub==0.8.0",
]
```
## Global installation
Works for simple cases.
```shell
uv tool install docsub==0.8.0
```
# Syntax
The syntax is purposefully verbose. This is fine, you are not supposed to edit it often. But it's searchable and sticks in eye when scrolling down large documents.
Docsub uses line-based substitution syntax based on *directives* and *substitution blocks*.
## Markdown
### Directive
*Markdown directive* is one-line comment:
```text
<!-- docsub: <directive> [directive args] -->
```
There are multiple [directive types](#directives).
### Substitution block
*Markdown substitution block* is a sequence of lines, starting with `begin` directive and ending with `end` directive.
```markdown
<!-- docsub: begin -->
<!-- docsub: help docsub -->
<!-- docsub: include CHANGELOG.md -->
Inner text will be replaced.
<!-- docsub: this whole line is treated as plain text -->
This text will be replaced too.
<!-- docsub: end -->
```
One or many other directives must come at the top of the block, otherwise they are treated as plain text. Blocks without *producing directives* are not allowed. Block's inner text will be replaced upon substitution, unless modifier directives are used, e.g. `lines`.
If docsub substitution block lies inside markdown fenced code block, it is not substituted *(example: fenced code blocks above and below this paragraph, see the raw markup)*. To put dynamic content into a fenced code block, place `begin` and `end` around it and use `lines after N upto -M` *(example: [Usage](#usage) section)*.
For nested blocks, only top level substitution is performed. Use block `#identifier` to distinguish between nesting levels.
```markdown
<!-- docsub: begin #top -->
<!-- docsub: include part.md -->
<!-- docsub: begin -->
<!-- docsub: include nested.md -->
<!-- docsub: end -->
<!-- docsub: end #top -->
```
# Directives
* *Block delimiters*: `begin`, `end`
* *Producing commands*: `exec`, `help`, `include`, `x`
* *Modifying commands*: `lines`, `strip`
## `begin`
```text
begin [#identifier]
```
Open substitution target block. To distinguish between nesting levels, use block `#identifier`, starting with `#`.
## `end`
```text
end [#identifier]
```
Close substitution target block.
## `exec`
```text
exec <shell commands>
```
Execute `<shell commands>` with `sh -c` and substitute stdout. Allows pipes and other shell functionality. If possible, avoid using this directive.
* `cmd.exec.work_dir` — shell working directory, default `'.'`
* `cmd.exec.env_vars` — dict of additional environment variables, default `{}`
## `help`
```text
help <command> [subcommand...]
help python -m <command> [subcommand...]
```
Display help for CLI utility or Python module. Use this command to document CLI instead of `exec`. Runs `command [subcommand...] --help` or `python -m command [subcommand...] --help` respectively. *Directive args* must be a space-separated sequence of characters `[-._a-zA-Z0-9]`.
* `cmd.help.env_vars` — dict of additional environment variables, default `{}`
## `include`
```text
include path/to/file
```
Literally include file specified by path relative to `base_dir` config option.
* `cmd.include.base_dir` — base directory for relative paths
## `lines`
```text
lines [after N] [upto -M]
```
Upon substitution, keep original target block lines: first `N` and/or last `M`. Only one `lines` command is allowed inside the block.
## `strip`
```text
strip
```
Strip whitespace in substitution result:
* initial and trailing blank lines
* trailing whitespace on every line
## `x`
```text
x <project-command> [args and --options]
```
Execute [project-local](#project-local-commands) command declared in `docsubfile.py` in project root. The naming is inspired by `X-` HTTP headers and `x-` convention for reusable YAML sections.
* `cmd.x.docsubfile` — path to file with project-local commands, absolute or relative to project root (default: `docsubfile.py`)
# Project-local commands
When project root contains file `docsubfile.py` with commands defined as in example below, they can be used in `docsub: x ` directive. Project commands must be defined as [click](https://click.palletsprojects.com) command and gathered under `x` group. There is no need to install `click` separately as docsub depends on it.
If docsub is installed globally and called as `uvx docsub`, project commands in `docsubfile.py` have access to docsub dependencies only: `click`, `loguru`, `rich` (see docsub's pyproject.toml for details).
If docsub is installed as project dev dependency and called as `uv run docsub`, user commands also have access to project modules and dev dependencies. This allows more flexible scenarios.
Project command author can get access to docsub `Environment` object (including command configs) from click context object (see example below). The docsub `Environment` object has some useful methods *(not documented yet)*.
## Example
```shell
$ uv run docsub apply -i sample.md
```
### sample.md
<!-- docsub: begin #readme -->
<!-- docsub: include tests/test_readme_docsubfile/__result__.md -->
<!-- docsub: lines after 1 upto -1 -->
```markdown
<!-- docsub: begin -->
<!-- docsub: x say-hello Alice Bob -->
Hi there, Alice!
Hi there, Bob!
<!-- docsub: end -->
```
<!-- docsub: end #readme -->
### docsubfile.py
<!-- docsub: begin -->
<!-- docsub: include tests/test_readme_docsubfile/docsubfile.py -->
<!-- docsub: lines after 1 upto -1 -->
```python
from docsub import Environment, click, pass_env
@click.group()
def x():
pass
@x.command()
@click.argument('users', nargs=-1)
def say_hello(users: tuple[str, ...]) -> None:
for user in users:
click.echo(f'Hi there, {user}!')
@x.command()
@click.argument('users', nargs=-1)
@pass_env
def log_hello(env: Environment, users: tuple[str, ...]) -> None:
base = env.get_temp_dir('log_hello')
(base / 'hello.log').write_text(f'said hello to {users}')
```
<!-- docsub: end -->
## Calling project-local commands
Docsub exposes `x` as CLI command, letting project commands to be executed with project settings:
<!-- docsub: begin -->
<!-- docsub: exec uv run docsub -x tests/test_readme_docsubfile/docsubfile.py x say-hello Alice Bob -->
<!-- docsub: lines after 2 upto -1 -->
```shell
$ uv run docsub x say-hello Alice Bob
Hi there, Alice!
Hi there, Bob!
```
<!-- docsub: end -->
# Configuration
Configuration resolution order
* command line options *(to be documented)*
* environment variables *(to be documented)*
* `docsub.toml` config file in current working directory
* `pyproject.toml`, section `[tool.docsub]` *(to be implemented)*
* default config values
## Root settings
* `local_dir` — internal working directory at the project root (default: `.docsub`)
## Command settings
See [Commands](#commands).
## Environment variables
*(to be documented)*
## Command line options
*(to be documented)*
## Complete config example
All config keys are optional.
```toml
local_dir = ".docsub" # default
[logging]
#level = "DEBUG" # default: missing, logging disabled
[cmd.exec]
env_vars = {} # default
work_dir = "." # default
[cmd.help.env_vars]
COLUMNS = "60" # more compact
[cmd.include]
base_dir = "." # default
[cmd.x]
docsubfile = "docsubfile.py" # default
```
> [!WARNING]
> In future releases config keys will be moved under `[tool.docsub]` root for both `pyproject.toml` and `docsub.toml`, this will be a breaking change.
# Logging
Docsub uses [loguru](https://loguru.readthedocs.io) for logging. Logging is disabled by default. To enable logging, set config option `level` to one of [logging levels](https://loguru.readthedocs.io/en/stable/api/logger.html#levels) supported by loguru.
*(logging is rudimentary at the moment)*
# CLI Reference
<!-- docsub: begin -->
<!-- docsub: help python -m docsub -->
<!-- docsub: lines after 2 upto -1 -->
<!-- docsub: strip -->
```shell
$ docsub --help
Usage: python -m docsub [OPTIONS] COMMAND [ARGS]...
╭─ Options ──────────────────────────────────────────────────────────╮
│ --config-file -c PATH │
│ --local-dir -l PATH │
│ --cmd-exec-work-dir PATH │
│ --cmd-exec-env-vars TEXT │
│ --cmd-help-env-vars TEXT │
│ --cmd-include-base-dir PATH │
│ --cmd-x-docsubfile -x PATH │
│ --version Show the version and exit. │
│ --help Show this message and exit. │
╰────────────────────────────────────────────────────────────────────╯
╭─ Commands ─────────────────────────────────────────────────────────╮
│ apply Update Markdown files with embedded content. │
│ x Project-local commands. │
╰────────────────────────────────────────────────────────────────────╯
```
<!-- docsub: end -->
## `docsub apply`
<!-- docsub: begin -->
<!-- docsub: help python -m docsub apply -->
<!-- docsub: lines after 2 upto -1 -->
<!-- docsub: strip -->
```shell
$ docsub apply --help
Usage: python -m docsub apply [OPTIONS] FILES...
Update Markdown files with embedded content.
Read FILES and perform substitutions one by one. If one file depends
on another, place it after that file.
╭─ Options ──────────────────────────────────────────────────────────╮
│ --in-place -i Process files in-place │
│ --help Show this message and exit. │
╰────────────────────────────────────────────────────────────────────╯
```
<!-- docsub: end -->
## `docsub x`
<!-- docsub: begin -->
<!-- docsub: help python -m docsub x -->
<!-- docsub: lines after 2 upto -1 -->
<!-- docsub: strip -->
```shell
$ docsub x --help
Usage: python -m docsub x [OPTIONS] COMMAND [ARGS]...
Project-local commands.
╭─ Options ──────────────────────────────────────────────────────────╮
│ --help Show this message and exit. │
╰────────────────────────────────────────────────────────────────────╯
```
<!-- docsub: end -->
# History
This project appeared to maintain docs for [multipython](https://github.com/makukha/multipython) project. You may check it up for usage examples.
# Authors
* [Michael Makukha](https://github.com/makukha)
# License
[MIT License](https://github.com/makukha/caseutil/blob/main/LICENSE)
# Changelog
[CHANGELOG.md](https://github.com/makukha/multipython/tree/main/CHANGELOG.md)
Raw data
{
"_id": null,
"home_page": null,
"name": "docsub",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"keywords": "docs, documentation, python, template-engine",
"author": null,
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/25/b9/899192110adee48bdcdd5c1f3173ca683e2ef227d40c17cbc07e523c7954/docsub-0.8.0.tar.gz",
"platform": null,
"description": "# docsub\n> Substitute dynamically generated content in Markdown files\n\n[![license](https://img.shields.io/github/license/makukha/docsub.svg)](https://github.com/makukha/docsub/blob/main/LICENSE)\n[![versions](https://img.shields.io/pypi/pyversions/docsub.svg)](https://pypi.org/project/docsub)\n[![pypi](https://img.shields.io/pypi/v/docsub.svg#v0.8.0)](https://pypi.python.org/pypi/docsub)\n[![uses docsub](https://img.shields.io/badge/uses-docsub-royalblue)\n](https://github.com/makukha/docsub)\n\n> [!WARNING]\n> * With `docsub`, every documentation file may become executable.\n> * Never use `docsub` to process files from untrusted sources.\n> * This project is in experimental state, syntax and functionality may change significantly.\n> * If still want to try it, use pinned package version `docsub==0.8.0`\n\n\n# Features\n\n* Embed **static** files\n* Embed **command execution** results\n* **Idempotent** substitutions\n* **Invisible** non-intrusive markup using comment blocks\n* **Plays nicely** with other markups\n* **Extensible** with project-local commands\n* **Configurable** with config files and env vars\n\n\n# Use cases\n\n* Manage partially duplicate docs for multiple destinations\n* Embed CLI reference in docs\n* Embed dynamically generated content:\n * Models evaluation results\n * Project metadata\n * Test reports\n\n> [!NOTE]\n> This file uses docsub itself. Dig into raw markup if interested.\n\n## Docsub is not...\n\n* ...a documentation engine like [Sphinx](https://www.sphinx-doc.org) or [MkDocs](https://www.mkdocs.org)\n* ...a full-featured static website generator like [Pelican](https://getpelican.com)\n* ...a templating engine like [Jinja](https://jinja.palletsprojects.com)\n* ...a replacement for [Bump My Version](https://callowayproject.github.io/bump-my-version)\n\n# Usage\n\n```shell\n$ uv run docsub apply -i README.md\n```\n\n## From separate files...\n\n<table>\n<tr>\n<td style=\"vertical-align:top\">\n\n### README.md\n<!-- docsub: begin #readme -->\n<!-- docsub: include tests/test_readme_showcase/__input__.md -->\n<!-- docsub: lines after 1 upto -1 -->\n````markdown\n# Title\n<!-- docsub: begin -->\n<!-- docsub: include info.md -->\n<!-- docsub: include features.md -->\n...\n<!-- docsub: end -->\n\n## Table\n<!-- docsub: begin -->\n<!-- docsub: include data.md -->\n<!-- docsub: lines after 2 -->\n| Col 1 | Col 2 |\n|-------|-------|\n...\n<!-- docsub: end -->\n\n## Code\n<!-- docsub: begin #code -->\n<!-- docsub: include func.py -->\n<!-- docsub: lines after 1 upto -1 -->\n```python\n...\n```\n<!-- docsub: end #code -->\n````\n<!-- docsub: end #readme -->\n\n</td>\n<td style=\"vertical-align:top\">\n\n### info.md\n<!-- docsub: begin #readme -->\n<!-- docsub: include tests/test_readme_showcase/info.md -->\n<!-- docsub: lines after 1 upto -1 -->\n````markdown\n> Long description.\n````\n<!-- docsub: end #readme -->\n\n### features.md\n<!-- docsub: begin #readme -->\n<!-- docsub: include tests/test_readme_showcase/features.md -->\n<!-- docsub: lines after 1 upto -1 -->\n````markdown\n* Feature 1\n* Feature 2\n* Feature 3\n````\n<!-- docsub: end #readme -->\n\n### data.md\n<!-- docsub: begin #readme -->\n<!-- docsub: include tests/test_readme_showcase/data.md -->\n<!-- docsub: lines after 1 upto -1 -->\n````markdown\n| Key 1 | value 1 |\n| Key 2 | value 2 |\n| Key 3 | value 3 |\n````\n<!-- docsub: end #readme -->\n\n### func.py\n<!-- docsub: begin #readme -->\n<!-- docsub: include tests/test_readme_showcase/func.py -->\n<!-- docsub: lines after 1 upto -1 -->\n````python\ndef func():\n pass\n````\n<!-- docsub: end #readme -->\n\n</td>\n</tr>\n</table>\n\n\n## Get merged document\n\n***and keep it updated!***\n\n<!-- docsub: begin #readme -->\n<!-- docsub: include tests/test_readme_showcase/__result__.md -->\n<!-- docsub: lines after 1 upto -1 -->\n````markdown\n# Title\n<!-- docsub: begin -->\n<!-- docsub: include info.md -->\n<!-- docsub: include features.md -->\n> Long description.\n* Feature 1\n* Feature 2\n* Feature 3\n<!-- docsub: end -->\n\n## Table\n<!-- docsub: begin -->\n<!-- docsub: include data.md -->\n<!-- docsub: lines after 2 -->\n| Col 1 | Col 2 |\n|-------|-------|\n| Key 1 | value 1 |\n| Key 2 | value 2 |\n| Key 3 | value 3 |\n<!-- docsub: end -->\n\n## Code\n<!-- docsub: begin #code -->\n<!-- docsub: include func.py -->\n<!-- docsub: lines after 1 upto -1 -->\n```python\ndef func():\n pass\n```\n<!-- docsub: end #code -->\n````\n<!-- docsub: end #readme -->\n\n\n# Installation\n\n## Development dependency\n\nRecommended. The most flexible installation option, allowing [project-local commands](#project-local-commands) to utilize project codebase.\n\n```toml\n# pyproject.toml\n[dependency-groups]\ndev = [\n \"docsub==0.8.0\",\n]\n```\n\n## Global installation\n\nWorks for simple cases.\n\n```shell\nuv tool install docsub==0.8.0\n```\n\n\n# Syntax\n\nThe syntax is purposefully verbose. This is fine, you are not supposed to edit it often. But it's searchable and sticks in eye when scrolling down large documents.\n\nDocsub uses line-based substitution syntax based on *directives* and *substitution blocks*.\n\n## Markdown\n\n### Directive\n\n*Markdown directive* is one-line comment:\n\n```text\n<!-- docsub: <directive> [directive args] -->\n```\n\nThere are multiple [directive types](#directives).\n\n### Substitution block\n\n*Markdown substitution block* is a sequence of lines, starting with `begin` directive and ending with `end` directive.\n\n```markdown\n<!-- docsub: begin -->\n<!-- docsub: help docsub -->\n<!-- docsub: include CHANGELOG.md -->\nInner text will be replaced.\n<!-- docsub: this whole line is treated as plain text -->\nThis text will be replaced too.\n<!-- docsub: end -->\n```\n\nOne or many other directives must come at the top of the block, otherwise they are treated as plain text. Blocks without *producing directives* are not allowed. Block's inner text will be replaced upon substitution, unless modifier directives are used, e.g. `lines`.\n\nIf docsub substitution block lies inside markdown fenced code block, it is not substituted *(example: fenced code blocks above and below this paragraph, see the raw markup)*. To put dynamic content into a fenced code block, place `begin` and `end` around it and use `lines after N upto -M` *(example: [Usage](#usage) section)*.\n\nFor nested blocks, only top level substitution is performed. Use block `#identifier` to distinguish between nesting levels.\n\n```markdown\n<!-- docsub: begin #top -->\n<!-- docsub: include part.md -->\n<!-- docsub: begin -->\n<!-- docsub: include nested.md -->\n<!-- docsub: end -->\n<!-- docsub: end #top -->\n```\n\n# Directives\n\n* *Block delimiters*: `begin`, `end`\n* *Producing commands*: `exec`, `help`, `include`, `x`\n* *Modifying commands*: `lines`, `strip`\n\n## `begin`\n```text\nbegin [#identifier]\n```\nOpen substitution target block. To distinguish between nesting levels, use block `#identifier`, starting with `#`.\n\n## `end`\n```text\nend [#identifier]\n```\nClose substitution target block.\n\n## `exec`\n```text\nexec <shell commands>\n```\nExecute `<shell commands>` with `sh -c` and substitute stdout. Allows pipes and other shell functionality. If possible, avoid using this directive.\n\n* `cmd.exec.work_dir` \u2014 shell working directory, default `'.'`\n* `cmd.exec.env_vars` \u2014 dict of additional environment variables, default `{}`\n\n## `help`\n\n```text\nhelp <command> [subcommand...]\nhelp python -m <command> [subcommand...]\n```\nDisplay help for CLI utility or Python module. Use this command to document CLI instead of `exec`. Runs `command [subcommand...] --help` or `python -m command [subcommand...] --help` respectively. *Directive args* must be a space-separated sequence of characters `[-._a-zA-Z0-9]`.\n\n* `cmd.help.env_vars` \u2014 dict of additional environment variables, default `{}`\n\n## `include`\n```text\ninclude path/to/file\n```\nLiterally include file specified by path relative to `base_dir` config option.\n\n* `cmd.include.base_dir` \u2014 base directory for relative paths\n\n## `lines`\n```text\nlines [after N] [upto -M]\n```\nUpon substitution, keep original target block lines: first `N` and/or last `M`. Only one `lines` command is allowed inside the block.\n\n## `strip`\n```text\nstrip\n```\nStrip whitespace in substitution result:\n* initial and trailing blank lines\n* trailing whitespace on every line\n\n## `x`\n```text\nx <project-command> [args and --options]\n```\nExecute [project-local](#project-local-commands) command declared in `docsubfile.py` in project root. The naming is inspired by `X-` HTTP headers and `x-` convention for reusable YAML sections.\n\n* `cmd.x.docsubfile` \u2014 path to file with project-local commands, absolute or relative to project root (default: `docsubfile.py`)\n\n\n# Project-local commands\n\nWhen project root contains file `docsubfile.py` with commands defined as in example below, they can be used in `docsub: x ` directive. Project commands must be defined as [click](https://click.palletsprojects.com) command and gathered under `x` group. There is no need to install `click` separately as docsub depends on it.\n\nIf docsub is installed globally and called as `uvx docsub`, project commands in `docsubfile.py` have access to docsub dependencies only: `click`, `loguru`, `rich` (see docsub's pyproject.toml for details).\n\nIf docsub is installed as project dev dependency and called as `uv run docsub`, user commands also have access to project modules and dev dependencies. This allows more flexible scenarios.\n\nProject command author can get access to docsub `Environment` object (including command configs) from click context object (see example below). The docsub `Environment` object has some useful methods *(not documented yet)*.\n\n## Example\n\n```shell\n$ uv run docsub apply -i sample.md\n```\n\n### sample.md\n<!-- docsub: begin #readme -->\n<!-- docsub: include tests/test_readme_docsubfile/__result__.md -->\n<!-- docsub: lines after 1 upto -1 -->\n```markdown\n<!-- docsub: begin -->\n<!-- docsub: x say-hello Alice Bob -->\nHi there, Alice!\nHi there, Bob!\n<!-- docsub: end -->\n```\n<!-- docsub: end #readme -->\n\n### docsubfile.py\n<!-- docsub: begin -->\n<!-- docsub: include tests/test_readme_docsubfile/docsubfile.py -->\n<!-- docsub: lines after 1 upto -1 -->\n```python\nfrom docsub import Environment, click, pass_env\n\n@click.group()\ndef x():\n pass\n\n@x.command()\n@click.argument('users', nargs=-1)\ndef say_hello(users: tuple[str, ...]) -> None:\n for user in users:\n click.echo(f'Hi there, {user}!')\n\n@x.command()\n@click.argument('users', nargs=-1)\n@pass_env\ndef log_hello(env: Environment, users: tuple[str, ...]) -> None:\n base = env.get_temp_dir('log_hello')\n (base / 'hello.log').write_text(f'said hello to {users}')\n```\n<!-- docsub: end -->\n\n## Calling project-local commands\n\nDocsub exposes `x` as CLI command, letting project commands to be executed with project settings:\n\n<!-- docsub: begin -->\n<!-- docsub: exec uv run docsub -x tests/test_readme_docsubfile/docsubfile.py x say-hello Alice Bob -->\n<!-- docsub: lines after 2 upto -1 -->\n```shell\n$ uv run docsub x say-hello Alice Bob\nHi there, Alice!\nHi there, Bob!\n```\n<!-- docsub: end -->\n\n\n# Configuration\n\nConfiguration resolution order\n\n* command line options *(to be documented)*\n* environment variables *(to be documented)*\n* `docsub.toml` config file in current working directory\n* `pyproject.toml`, section `[tool.docsub]` *(to be implemented)*\n* default config values\n\n## Root settings\n\n* `local_dir` \u2014 internal working directory at the project root (default: `.docsub`)\n\n## Command settings\n\nSee [Commands](#commands).\n\n## Environment variables\n\n*(to be documented)*\n\n## Command line options\n\n*(to be documented)*\n\n## Complete config example\n\nAll config keys are optional.\n\n\n```toml\nlocal_dir = \".docsub\" # default\n\n[logging]\n#level = \"DEBUG\" # default: missing, logging disabled\n\n[cmd.exec]\nenv_vars = {} # default\nwork_dir = \".\" # default\n\n[cmd.help.env_vars]\nCOLUMNS = \"60\" # more compact\n\n[cmd.include]\nbase_dir = \".\" # default\n\n[cmd.x]\ndocsubfile = \"docsubfile.py\" # default\n```\n\n> [!WARNING]\n> In future releases config keys will be moved under `[tool.docsub]` root for both `pyproject.toml` and `docsub.toml`, this will be a breaking change.\n\n\n# Logging\n\nDocsub uses [loguru](https://loguru.readthedocs.io) for logging. Logging is disabled by default. To enable logging, set config option `level` to one of [logging levels](https://loguru.readthedocs.io/en/stable/api/logger.html#levels) supported by loguru.\n\n*(logging is rudimentary at the moment)*\n\n\n# CLI Reference\n\n<!-- docsub: begin -->\n<!-- docsub: help python -m docsub -->\n<!-- docsub: lines after 2 upto -1 -->\n<!-- docsub: strip -->\n```shell\n$ docsub --help\nUsage: python -m docsub [OPTIONS] COMMAND [ARGS]...\n\n\u256d\u2500 Options \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 --config-file -c PATH \u2502\n\u2502 --local-dir -l PATH \u2502\n\u2502 --cmd-exec-work-dir PATH \u2502\n\u2502 --cmd-exec-env-vars TEXT \u2502\n\u2502 --cmd-help-env-vars TEXT \u2502\n\u2502 --cmd-include-base-dir PATH \u2502\n\u2502 --cmd-x-docsubfile -x PATH \u2502\n\u2502 --version Show the version and exit. \u2502\n\u2502 --help Show this message and exit. \u2502\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\n\u256d\u2500 Commands \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 apply Update Markdown files with embedded content. \u2502\n\u2502 x Project-local commands. \u2502\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\n```\n<!-- docsub: end -->\n\n## `docsub apply`\n\n<!-- docsub: begin -->\n<!-- docsub: help python -m docsub apply -->\n<!-- docsub: lines after 2 upto -1 -->\n<!-- docsub: strip -->\n```shell\n$ docsub apply --help\nUsage: python -m docsub apply [OPTIONS] FILES...\n\nUpdate Markdown files with embedded content.\nRead FILES and perform substitutions one by one. If one file depends\non another, place it after that file.\n\n\u256d\u2500 Options \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 --in-place -i Process files in-place \u2502\n\u2502 --help Show this message and exit. \u2502\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\n```\n<!-- docsub: end -->\n\n## `docsub x`\n\n<!-- docsub: begin -->\n<!-- docsub: help python -m docsub x -->\n<!-- docsub: lines after 2 upto -1 -->\n<!-- docsub: strip -->\n```shell\n$ docsub x --help\nUsage: python -m docsub x [OPTIONS] COMMAND [ARGS]...\n\nProject-local commands.\n\n\u256d\u2500 Options \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 --help Show this message and exit. \u2502\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\n```\n<!-- docsub: end -->\n\n\n# History\n\nThis project appeared to maintain docs for [multipython](https://github.com/makukha/multipython) project. You may check it up for usage examples.\n\n\n# Authors\n\n* [Michael Makukha](https://github.com/makukha)\n\n\n# License\n\n[MIT License](https://github.com/makukha/caseutil/blob/main/LICENSE)\n\n\n# Changelog\n\n[CHANGELOG.md](https://github.com/makukha/multipython/tree/main/CHANGELOG.md)\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Embed text and data into Markdown files",
"version": "0.8.0",
"project_urls": {
"Changelog": "https://github.com/makukha/docsub/releases",
"Homepage": "https://github.com/makukha/docsub",
"Issues": "https://github.com/makukha/docsub/issues",
"Repository": "https://github.com/makukha/docsub"
},
"split_keywords": [
"docs",
" documentation",
" python",
" template-engine"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "b635e896447cd761f1c61add10bfe2dada04aed4e2988e04bc03579964d26b9f",
"md5": "e91af916334789592b85e443290489d6",
"sha256": "5c1fef35d9a9bc113f37cdb80551f6c926ee7761485c47ba76cf5d532fd287ac"
},
"downloads": -1,
"filename": "docsub-0.8.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "e91af916334789592b85e443290489d6",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 19783,
"upload_time": "2025-01-18T16:17:49",
"upload_time_iso_8601": "2025-01-18T16:17:49.173813Z",
"url": "https://files.pythonhosted.org/packages/b6/35/e896447cd761f1c61add10bfe2dada04aed4e2988e04bc03579964d26b9f/docsub-0.8.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "25b9899192110adee48bdcdd5c1f3173ca683e2ef227d40c17cbc07e523c7954",
"md5": "ccf97181e016a53a481105340dedefa9",
"sha256": "5a661547cc43285d29bc895012c2daf8e9c674c3f6fcafb5146562790ce89ee0"
},
"downloads": -1,
"filename": "docsub-0.8.0.tar.gz",
"has_sig": false,
"md5_digest": "ccf97181e016a53a481105340dedefa9",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 58180,
"upload_time": "2025-01-18T16:17:50",
"upload_time_iso_8601": "2025-01-18T16:17:50.715600Z",
"url": "https://files.pythonhosted.org/packages/25/b9/899192110adee48bdcdd5c1f3173ca683e2ef227d40c17cbc07e523c7954/docsub-0.8.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-01-18 16:17:50",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "makukha",
"github_project": "docsub",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "docsub"
}