paulrun 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.
Files changed (89) hide show
  1. paulrun-0.1.0/.github/workflows/ci.yml +59 -0
  2. paulrun-0.1.0/.gitignore +20 -0
  3. paulrun-0.1.0/.python-version +1 -0
  4. paulrun-0.1.0/CHANGELOG.md +18 -0
  5. paulrun-0.1.0/CODE_OF_CONDUCT.md +32 -0
  6. paulrun-0.1.0/CONTRIBUTING.md +150 -0
  7. paulrun-0.1.0/CONTRIBUTORS.md +3 -0
  8. paulrun-0.1.0/LICENSE +21 -0
  9. paulrun-0.1.0/Makefile +42 -0
  10. paulrun-0.1.0/PKG-INFO +137 -0
  11. paulrun-0.1.0/PUBLISHING.md +134 -0
  12. paulrun-0.1.0/README.md +120 -0
  13. paulrun-0.1.0/SECURITY.md +11 -0
  14. paulrun-0.1.0/docs/CHANGELOG.md +18 -0
  15. paulrun-0.1.0/docs/backends.md +258 -0
  16. paulrun-0.1.0/docs/cli.md +228 -0
  17. paulrun-0.1.0/docs/index.md +110 -0
  18. paulrun-0.1.0/docs/install.md +42 -0
  19. paulrun-0.1.0/docs/runbook-format.md +267 -0
  20. paulrun-0.1.0/examples/clientele-publishing.md +160 -0
  21. paulrun-0.1.0/examples/hello.md +30 -0
  22. paulrun-0.1.0/mkdocs.yml +47 -0
  23. paulrun-0.1.0/paulrun/__init__.py +0 -0
  24. paulrun-0.1.0/paulrun/backends/__init__.py +178 -0
  25. paulrun-0.1.0/paulrun/backends/python.py +51 -0
  26. paulrun-0.1.0/paulrun/backends/shell.py +80 -0
  27. paulrun-0.1.0/paulrun/cli.py +233 -0
  28. paulrun-0.1.0/paulrun/inputs.py +74 -0
  29. paulrun-0.1.0/paulrun/runbook.py +199 -0
  30. paulrun-0.1.0/paulrun/runner.py +295 -0
  31. paulrun-0.1.0/paulrun/settings.py +3 -0
  32. paulrun-0.1.0/paulrun/transcript.py +136 -0
  33. paulrun-0.1.0/paulrun/validate.py +257 -0
  34. paulrun-0.1.0/pyproject.toml +78 -0
  35. paulrun-0.1.0/specs/SPEC.md +370 -0
  36. paulrun-0.1.0/tasks/plan.md +121 -0
  37. paulrun-0.1.0/tasks/todo.md +488 -0
  38. paulrun-0.1.0/tests/__init__.py +0 -0
  39. paulrun-0.1.0/tests/backends/__init__.py +0 -0
  40. paulrun-0.1.0/tests/backends/test_python.py +205 -0
  41. paulrun-0.1.0/tests/backends/test_shell.py +336 -0
  42. paulrun-0.1.0/tests/conftest.py +14 -0
  43. paulrun-0.1.0/tests/runbooks/all_features.md +72 -0
  44. paulrun-0.1.0/tests/runbooks/broken/block-before-first-step.md +15 -0
  45. paulrun-0.1.0/tests/runbooks/broken/duplicate-input-name.md +14 -0
  46. paulrun-0.1.0/tests/runbooks/broken/input-block-before-first-step.md +14 -0
  47. paulrun-0.1.0/tests/runbooks/broken/input-block-env-used-before.md +20 -0
  48. paulrun-0.1.0/tests/runbooks/broken/input-block-in-output-path.md +15 -0
  49. paulrun-0.1.0/tests/runbooks/broken/input-block-invalid-yaml.md +10 -0
  50. paulrun-0.1.0/tests/runbooks/broken/input-block-missing-description.md +13 -0
  51. paulrun-0.1.0/tests/runbooks/broken/input-block-name-already-declared.md +17 -0
  52. paulrun-0.1.0/tests/runbooks/broken/input-block-not-a-mapping.md +10 -0
  53. paulrun-0.1.0/tests/runbooks/broken/input-block-placeholder-used-before.md +20 -0
  54. paulrun-0.1.0/tests/runbooks/broken/input-missing-description.md +11 -0
  55. paulrun-0.1.0/tests/runbooks/broken/input-missing-name.md +11 -0
  56. paulrun-0.1.0/tests/runbooks/broken/input-name-not-upper-snake-case.md +12 -0
  57. paulrun-0.1.0/tests/runbooks/broken/invalid-pattern.md +13 -0
  58. paulrun-0.1.0/tests/runbooks/broken/missing-title.md +9 -0
  59. paulrun-0.1.0/tests/runbooks/broken/python-not-on-path.md +10 -0
  60. paulrun-0.1.0/tests/runbooks/broken/python-syntax-error.md +11 -0
  61. paulrun-0.1.0/tests/runbooks/broken/run-block-with-no-backend.md +9 -0
  62. paulrun-0.1.0/tests/runbooks/broken/secret-as-placeholder.md +13 -0
  63. paulrun-0.1.0/tests/runbooks/broken/shell-syntax-error.md +11 -0
  64. paulrun-0.1.0/tests/runbooks/broken/title-not-a-string.md +11 -0
  65. paulrun-0.1.0/tests/runbooks/broken/undeclared-placeholder-in-confirm.md +9 -0
  66. paulrun-0.1.0/tests/runbooks/broken/undeclared-placeholder-in-docstring.md +9 -0
  67. paulrun-0.1.0/tests/runbooks/broken/undeclared-placeholder-in-output-path.md +10 -0
  68. paulrun-0.1.0/tests/runbooks/broken/undeclared-placeholder.md +10 -0
  69. paulrun-0.1.0/tests/runbooks/broken-body.md +21 -0
  70. paulrun-0.1.0/tests/runbooks/docstring-and-confirm-example.md +42 -0
  71. paulrun-0.1.0/tests/runbooks/dry-run-example.md +43 -0
  72. paulrun-0.1.0/tests/runbooks/input-block-example.md +53 -0
  73. paulrun-0.1.0/tests/runbooks/inputs-example.md +42 -0
  74. paulrun-0.1.0/tests/runbooks/minimal.md +9 -0
  75. paulrun-0.1.0/tests/runbooks/python-example.md +45 -0
  76. paulrun-0.1.0/tests/runbooks/step-example.md +37 -0
  77. paulrun-0.1.0/tests/runbooks/transcript-example.md +44 -0
  78. paulrun-0.1.0/tests/runbooks/two_steps.md +19 -0
  79. paulrun-0.1.0/tests/test_backend_loading.py +132 -0
  80. paulrun-0.1.0/tests/test_cli.py +1173 -0
  81. paulrun-0.1.0/tests/test_docs.py +165 -0
  82. paulrun-0.1.0/tests/test_examples.py +84 -0
  83. paulrun-0.1.0/tests/test_inputs.py +147 -0
  84. paulrun-0.1.0/tests/test_interrupt.py +292 -0
  85. paulrun-0.1.0/tests/test_runbook.py +666 -0
  86. paulrun-0.1.0/tests/test_runner.py +1430 -0
  87. paulrun-0.1.0/tests/test_transcript.py +459 -0
  88. paulrun-0.1.0/tests/test_validate.py +619 -0
  89. paulrun-0.1.0/uv.lock +1086 -0
@@ -0,0 +1,59 @@
1
+ name: CI - Lint and Test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.12", "3.13", "3.14", "3.15-dev"]
15
+ steps:
16
+ #----------------------------------------------
17
+ # check-out repo and set-up python
18
+ #----------------------------------------------
19
+ - name: Check out repository
20
+ uses: actions/checkout@v7
21
+ - name: Set up python
22
+ id: setup-python
23
+ uses: actions/setup-python@v7
24
+ with:
25
+ python-version: ${{matrix.python-version}}
26
+ #----------------------------------------------
27
+ # ----- install & configure uv -----
28
+ #----------------------------------------------
29
+ # Pointing uv at the interpreter setup-python installed overrides .python-version,
30
+ # which would otherwise make every matrix entry run on 3.14.
31
+ - name: Install UV
32
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
33
+ with:
34
+ python-version: ${{ steps.setup-python.outputs.python-path }}
35
+ enable-cache: true
36
+ cache-dependency-glob: "uv.lock"
37
+
38
+ #----------------------------------------------
39
+ # install dependencies
40
+ #----------------------------------------------
41
+ - name: Install dependencies
42
+ run: uv sync --frozen
43
+ - name: Show Python version
44
+ run: uv run python --version
45
+ #----------------------------------------------
46
+ # Run linting and tests
47
+ #----------------------------------------------
48
+ - name: Ruff format check
49
+ run: |
50
+ uv run ruff format --check .
51
+ - name: Ruff linting
52
+ run: |
53
+ uv run ruff check .
54
+ - name: Type check with ty
55
+ run: |
56
+ uv run ty check
57
+ - name: Test with pytest
58
+ run: |
59
+ uv run pytest -vv --cov=paulrun --cov-branch --cov-report=term-missing --cov-config=pyproject.toml
@@ -0,0 +1,20 @@
1
+ .venv/
2
+ .ty_cache/
3
+ .ruff_cache/
4
+ .pytest_cache/
5
+ .idea/
6
+ __pycache__
7
+ .DS_Store
8
+ dist/
9
+ site/
10
+ htmlcov/
11
+ *.coverage*
12
+ coverage.xml
13
+ .cache/
14
+
15
+ # Transcripts written by running the fixture runbooks by hand
16
+ tests/runbooks/logs/
17
+
18
+ # Transcripts written by releasing paulrun with PUBLISHING.md, or running the examples by hand
19
+ /logs/
20
+ examples/logs/
@@ -0,0 +1 @@
1
+ 3.14
@@ -0,0 +1,18 @@
1
+ # Change log
2
+
3
+ ## 0.1.0
4
+
5
+ Initial release. paulrun runs hand-written markdown runbooks.
6
+
7
+ - `paulrun go` runs a runbook's steps in order, streaming each block's output as it's printed and stopping at the first block that fails.
8
+ - `run` blocks for shell (`sh`, `bash`, `shell`) and Python (`python`, `py`), with the `python` frontmatter key to choose the interpreter.
9
+ - `docstring` blocks to show notes as the runbook runs, and `confirm` blocks to pause for steps done by hand.
10
+ - `input` blocks to ask for a value part way through a run, once it exists: a pull request number from a step done by hand, or a one-time code that expires in seconds.
11
+ - Inputs, collected from the environment or by asking, with an optional `pattern` to check them against. Plain inputs fill in `<NAME>` placeholders; secrets are hidden as you type them and replaced with `****` everywhere paulrun shows or records them.
12
+ - `paulrun go --dry` to see every block with its inputs filled in, without running anything.
13
+ - `paulrun go --step` to run, skip or quit at each block.
14
+ - Transcripts: with `output_path` set, every run is added to a plain record of what ran, what it printed and how it finished.
15
+ - Ctrl-C stops a run cleanly, letting a running block stop and recording how far the run got.
16
+ - `paulrun check` to find problems before running anything: missing or malformed frontmatter, undeclared placeholders, secrets used as placeholders, blocks outside a step, and shell and Python syntax errors, with shellcheck warnings when it's installed.
17
+ - `paulrun inputs` to list the inputs a runbook needs, and `paulrun backends` to list the languages paulrun can run.
18
+ - Backends for other languages can be added by any package, through the `paulrun.backends` entry point group.
@@ -0,0 +1,32 @@
1
+ # paulrun Code of Conduct
2
+
3
+ Like the technical community as a whole, the paulrun team and community is made up of a mixture of professionals and volunteers from all over the world, working on every aspect of the mission - including mentorship, teaching, and connecting people.
4
+
5
+ Diversity is one of our huge strengths, but it can also lead to communication issues and unhappiness. To that end, we have a few ground rules that we ask people to adhere to. This code applies equally to founders, mentors and those seeking help and guidance.
6
+
7
+ This isn’t an exhaustive list of things that you can’t do. Rather, take it in the spirit in which it’s intended - a guide to make it easier to enrich all of us and the technical communities in which we participate.
8
+
9
+ This code of conduct applies to all spaces managed by the paulrun project or Paul Hallett. This includes the issue tracker, pull requests, discussions, and any other forums created by the project team which the community uses for communication. In addition, violations of this code outside these spaces may affect a person's ability to participate within them.
10
+
11
+ If you believe someone is violating the code of conduct, we ask that you report it by emailing [paulandrewhallett@gmail.com](mailto:paulandrewhallett@gmail.com).
12
+
13
+ - **Be friendly and patient.**
14
+ - **Be welcoming.** We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.
15
+ - **Be considerate.** Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language.
16
+ - **Be respectful.** Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one. Members of the paulrun community should be respectful when dealing with other members as well as with people outside the paulrun community.
17
+ - **Be careful in the words that you choose.** We are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable. This includes, but is not limited to:
18
+ - Violent threats or language directed against another person.
19
+ - Discriminatory jokes and language.
20
+ - Posting sexually explicit or violent material.
21
+ - Posting (or threatening to post) other people's personally identifying information ("doxing").
22
+ - Personal insults, especially those using racist or sexist terms.
23
+ - Unwelcome sexual attention.
24
+ - Advocating for, or encouraging, any of the above behavior.
25
+ - Repeated harassment of others. In general, if someone asks you to stop, then stop.
26
+ - **When we disagree, try to understand why.** Disagreements, both social and technical, happen all the time and paulrun is no exception. It is important that we resolve disagreements and differing views constructively. Remember that we’re different. The strength of paulrun comes from its varied community, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes.
27
+
28
+ Original text courtesy of the [Speak Up! project](http://web.archive.org/web/20141109123859/http://speakup.io/coc.html).
29
+
30
+ ## Questions?
31
+
32
+ If you have questions, please see [CONTRIBUTING.md](CONTRIBUTING.md). If that doesn't answer your questions, feel free to [contact us](mailto:paulandrewhallett@gmail.com).
@@ -0,0 +1,150 @@
1
+ # Contributing
2
+
3
+ First things first: thank you for contributing! This project will be successful thanks to everyone who contributes, and we're happy to have you.
4
+
5
+ ## AI disclosure
6
+
7
+ Within paulrun we use coding agents to prototype and build out ideas, and then take the idea to fruition using human reviewed and approved code.
8
+
9
+ You, the human and the author of changes are responsible for the contributions, and if you cannot confidently explain or demonstrate understanding of the changes you are contributing, then they will not be accepted.
10
+
11
+ ## Bug or issue?
12
+
13
+ To raise a bug or issue please use [our GitHub](https://github.com/phalt/paulrun/issues).
14
+
15
+ Please check the issue has not been raised before by using the search feature.
16
+
17
+ When submitting an issue or bug, please make sure you provide thorough detail on:
18
+
19
+ 1. The version of paulrun you are using (`paulrun --version`)
20
+ 2. Your operating system and Python version
21
+ 3. Any errors or outputs you see in your terminal
22
+ 4. The smallest runbook that shows the problem (this is particularly important). Take out any secrets first!
23
+
24
+ ## Contribution
25
+
26
+ If you want to directly contribute you can do so in two ways:
27
+
28
+ 1. Documentation
29
+ 2. Code
30
+
31
+ ### Documentation
32
+
33
+ We use [mkdocs](https://www.mkdocs.org/) and [GitHub pages](https://pages.github.com/) to deploy our docs.
34
+
35
+ Fixing grammar, spelling mistakes, or expanding the documentation to cover features that are not yet documented, are all valuable contributions.
36
+
37
+ Please see the **Set up** instructions below to run the docs locally on your computer, then:
38
+
39
+ ```sh
40
+ make docs-serve
41
+ ```
42
+
43
+ The examples in the docs and the README are tested, so if you change one, run `make test` too.
44
+
45
+ ### Code
46
+
47
+ Contribution by writing code for new features, or fixing bugs, is a great way to contribute to the project.
48
+
49
+ Please [open an issue](https://github.com/phalt/paulrun/issues) to talk it through before changing the runbook format (frontmatter keys, what a fence does, placeholders or the transcript), adding a command or flag, or adding a dependency. These are hard to change once people's runbooks rely on them.
50
+
51
+ #### Set up
52
+
53
+ Clone the repo:
54
+
55
+ ```sh
56
+ git clone git@github.com:phalt/paulrun.git
57
+ cd paulrun
58
+ ```
59
+
60
+ Move to a feature branch:
61
+
62
+ ```sh
63
+ git switch -c my-branch-name
64
+ ```
65
+
66
+ Install UV (if not already installed):
67
+
68
+ ```sh
69
+ # On macOS and Linux:
70
+ curl -LsSf https://astral.sh/uv/install.sh | sh
71
+
72
+ # Or using pip:
73
+ pip install uv
74
+ ```
75
+
76
+ Install all the dependencies:
77
+
78
+ ```sh
79
+ make install
80
+ ```
81
+
82
+ This will use UV to create a virtual environment and install all dependencies, including paulrun itself, so its built-in backends are registered. UV handles the virtual environment automatically, so you don't need to manually activate it.
83
+
84
+ To make sure you have things set up correctly, please run the tests:
85
+
86
+ ```sh
87
+ make test
88
+ ```
89
+
90
+ You can run your copy of paulrun with `uv run paulrun`, e.g. on one of the example runbooks in `tests/runbooks/`:
91
+
92
+ ```sh
93
+ uv run paulrun go tests/runbooks/two_steps.md
94
+ ```
95
+
96
+ Run `make help` to see every command.
97
+
98
+ ### Adding a backend
99
+
100
+ paulrun runs shell and Python blocks out of the box. To add another language to paulrun itself:
101
+
102
+ 1. Write `paulrun/backends/<name>.py`, implementing the backend protocol
103
+ 2. Add one entry point line for it in `pyproject.toml`, then run `make install`
104
+ 3. Write `tests/backends/test_<name>.py`, which really runs blocks (skipped if the language isn't installed)
105
+ 4. Add a section about it to `docs/backends.md`
106
+ 5. Add a CHANGELOG entry
107
+
108
+ [docs/backends.md](https://phalt.github.io/paulrun/backends/) explains each step, and how backends work.
109
+
110
+ A backend doesn't have to be part of paulrun: the same page shows how to ship one in your own package.
111
+
112
+ ### Preparing changes for review
113
+
114
+ Once you've made changes, here's a good checklist to run through before publishing for review:
115
+
116
+ Format and lint the code:
117
+
118
+ ```sh
119
+ make format
120
+ ```
121
+
122
+ Check the types:
123
+
124
+ ```sh
125
+ make ty
126
+ ```
127
+
128
+ Run the tests:
129
+
130
+ ```sh
131
+ make test
132
+ ```
133
+
134
+ New behaviour needs tests, and each new runbook feature gets an example runbook in `tests/runbooks/` that shows it off.
135
+
136
+ If you've changed how runbooks are written or run, update `docs/runbook-format.md` or `docs/cli.md` to match.
137
+
138
+ Make sure you add to `CHANGELOG.md` and `docs/CHANGELOG.md` what changes you have made. The two must be identical, and a test checks they are.
139
+
140
+ Make sure you add your name to `CONTRIBUTORS.md` as well!
141
+
142
+ ### Making a pull request
143
+
144
+ Please push your changes up to a feature branch and make a new [pull request](https://github.com/phalt/paulrun/compare) on GitHub.
145
+
146
+ Please add a description to the PR and some information about why the change is being made.
147
+
148
+ After a review, you might need to make more changes.
149
+
150
+ Once accepted, a core contributor will merge your changes!
@@ -0,0 +1,3 @@
1
+ # Contributors
2
+
3
+ - [Paul Hallett](https://github.com/phalt)
paulrun-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Paul Hallett
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.
paulrun-0.1.0/Makefile ADDED
@@ -0,0 +1,42 @@
1
+ help:
2
+ @echo Developer commands for paulrun
3
+ @echo
4
+ @grep -E '^[ .a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
5
+ @echo
6
+
7
+ install: ## Install requirements ready for development
8
+ uv sync
9
+
10
+ deploy-docs: ## Build and deploy the documentation
11
+ uv run mkdocs build
12
+ uv run mkdocs gh-deploy
13
+
14
+ docs-serve: ## Run a local documentation server
15
+ uv run mkdocs serve
16
+
17
+ release: ## Build a new version and release it
18
+ uv build
19
+ uv publish
20
+
21
+ ty: ## Run a static syntax check
22
+ uv run ty check .
23
+
24
+ format: ## Format the code correctly
25
+ uv run ruff format .
26
+ uv run ruff check --fix .
27
+
28
+ clean: ## Clear any cache files and test files
29
+ rm -rf .ty_cache
30
+ rm -rf .pytest_cache
31
+ rm -rf .ruff_cache
32
+ rm -rf site/
33
+ rm -rf dist/
34
+ find paulrun tests -name __pycache__ -prune -exec rm -rf {} +
35
+ rm -rf htmlcov/
36
+ rm -rf .coverage
37
+
38
+ test: ## Run tests
39
+ uv run pytest -vvv -x --cov=paulrun --cov-report=term-missing --cov-report=html --cov-config=pyproject.toml
40
+
41
+ shell: ## Run an ipython shell
42
+ uv run ipython
paulrun-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.5
2
+ Name: paulrun
3
+ Version: 0.1.0
4
+ Summary: Run hand-written markdown runbooks
5
+ Project-URL: Homepage, https://github.com/phalt/paulrun
6
+ Project-URL: Documentation, https://phalt.github.io/paulrun/
7
+ Project-URL: Issues, https://github.com/phalt/paulrun/issues
8
+ Author-email: Paul Hallett <paulandrewhallett@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Requires-Python: >=3.12
12
+ Requires-Dist: click>=8.5.0
13
+ Requires-Dist: markdown-it-py>=4.2.0
14
+ Requires-Dist: pyyaml>=6.0.3
15
+ Requires-Dist: rich>=15.0.0
16
+ Description-Content-Type: text/markdown
17
+
18
+ # 🏃‍♂️‍➡️📕 paulrun
19
+
20
+ ## What is paulrun?
21
+
22
+ paulrun runs hand-written markdown runbooks.
23
+
24
+ A runbook is a normal markdown file that a person can read and follow without paulrun. Mark a code block `run`, and paulrun runs it for you. Everything else stays documentation.
25
+
26
+ [![Package version](https://img.shields.io/pypi/v/paulrun?color=%2334D058&label=latest%20version)](https://pypi.org/project/paulrun)
27
+ ![Python versions](https://img.shields.io/badge/python-3.12+-blue)
28
+ ![PyPI - License](https://img.shields.io/pypi/l/paulrun)
29
+
30
+ ## Example runbook
31
+
32
+ Save this as `hello.md`:
33
+
34
+ ````markdown
35
+ ---
36
+ title: Say hello
37
+ inputs:
38
+ - name: NAME
39
+ description: Who to say hello to
40
+ ---
41
+
42
+ # Say hello
43
+
44
+ A runbook is just markdown. paulrun runs the blocks marked `run`, shows the
45
+ `docstring` ones, and leaves everything else alone, like this paragraph.
46
+
47
+ ## Greet
48
+
49
+ ```docstring
50
+ Saying hello to <NAME>.
51
+ ```
52
+
53
+ ```sh run
54
+ echo "Hello, <NAME>!"
55
+ ```
56
+
57
+ ## Count the letters
58
+
59
+ ```python run
60
+ import os
61
+
62
+ name = os.environ["NAME"]
63
+ print(f"{name} has {len(name)} letters")
64
+ ```
65
+ ````
66
+
67
+ Check it, then run it:
68
+
69
+ ```sh
70
+ paulrun check hello.md
71
+ paulrun go hello.md
72
+ ```
73
+
74
+ ```
75
+ hello.md: ok
76
+ NAME (Who to say hello to): Paul
77
+ Say hello
78
+ inputs: NAME=Paul
79
+ 1. Greet
80
+ 2. Count the letters
81
+ 2 run blocks
82
+ Start? [y/N] y
83
+
84
+ === Greet ===
85
+ Saying hello to Paul.
86
+ echo "Hello, Paul!"
87
+ Hello, Paul!
88
+ exit 0 (0.0s)
89
+
90
+ === Count the letters ===
91
+ import os
92
+
93
+ name = os.environ["NAME"]
94
+ print(f"{name} has {len(name)} letters")
95
+ Paul has 4 letters
96
+ exit 0 (0.1s)
97
+
98
+ finished: ok (0.1s)
99
+ ```
100
+
101
+ `check` finds problems before anything runs. `go` asks for the inputs, shows you the plan, then runs each step in order, streaming every command's output as it goes. It stops at the first command that fails.
102
+
103
+ ## Why use paulrun?
104
+
105
+ Release steps get forgotten. Writing them down helps; making the written-down version run itself fixes it.
106
+
107
+ - **It's just markdown** - A runbook reads like documentation, renders on GitHub, and works fine without paulrun.
108
+ - **Check before you run** - `paulrun check` catches undeclared placeholders, broken frontmatter and shell or Python syntax errors before anything runs.
109
+ - **See what would happen** - `paulrun go --dry` shows every command with its inputs filled in, and runs nothing.
110
+ - **Inputs and secrets** - Ask for values once, check them against a pattern, and keep secrets off the screen and out of the log.
111
+ - **Manual steps too** - `confirm` blocks pause for the things only a person can do.
112
+ - **A record of every run** - An optional transcript keeps every command, its output and how the run finished.
113
+ - **More than shell** - Shell and Python blocks are built in, and other languages can be added as backends.
114
+
115
+ ## Install
116
+
117
+ paulrun is a command-line tool, so install it on its own rather than into a project:
118
+
119
+ ```sh
120
+ uv tool install paulrun
121
+ ```
122
+
123
+ Or with pip:
124
+
125
+ ```sh
126
+ pip install paulrun
127
+ ```
128
+
129
+ paulrun needs Python 3.12 or later and `bash`, and runs on **macOS and Linux**. Windows isn't supported. It's for runbooks you run yourself, at a terminal; there's no CI mode.
130
+
131
+ ## Getting started
132
+
133
+ 👉 Read the [full documentation](https://phalt.github.io/paulrun/) for the runbook format, every command, and writing your own backend.
134
+
135
+ See [`examples/`](https://github.com/phalt/paulrun/tree/main/examples) for more runbooks, including a real release, and [`PUBLISHING.md`](https://github.com/phalt/paulrun/blob/main/PUBLISHING.md) for the runbook paulrun releases itself with.
136
+
137
+ Want to help? See [CONTRIBUTING.md](https://github.com/phalt/paulrun/blob/main/CONTRIBUTING.md).
@@ -0,0 +1,134 @@
1
+ ---
2
+ title: Release paulrun
3
+ description: Release a new version of paulrun to GitHub, the docs site and PyPI.
4
+ output_path: logs/release-<VERSION>.md
5
+ inputs:
6
+ - name: VERSION
7
+ description: The version to release, e.g. 0.2.0
8
+ pattern: '^\d+\.\d+\.\d+$'
9
+ - name: UV_PUBLISH_TOKEN
10
+ description: PyPI API token for paulrun
11
+ secret: true
12
+ ---
13
+
14
+ # 🏃‍♂️‍➡️📕 Releasing paulrun
15
+
16
+ paulrun releases itself with this runbook:
17
+
18
+ ```sh
19
+ paulrun check PUBLISHING.md
20
+ paulrun go PUBLISHING.md --dry
21
+ paulrun go PUBLISHING.md
22
+ ```
23
+
24
+ You can also follow it by hand: each step's commands are in the order they run.
25
+
26
+ Before you start, add a `## <version>` section for the release to the top of
27
+ `CHANGELOG.md`, and copy it to `docs/CHANGELOG.md`. That change gets committed
28
+ with the version bump.
29
+
30
+ Each run is recorded in `logs/release-<version>.md`, which git ignores.
31
+
32
+ ## Check before releasing
33
+
34
+ Releases are made from an up-to-date `main`, with nothing left uncommitted
35
+ except the changelog, for a version that hasn't been released yet.
36
+
37
+ ```sh run
38
+ git switch main
39
+ git pull origin main
40
+ git fetch --tags
41
+
42
+ if [ -n "$(git status --porcelain -- . ':!CHANGELOG.md' ':!docs/CHANGELOG.md' ':!logs')" ]; then
43
+ echo "Commit or stash these changes first:"
44
+ git status --short
45
+ exit 1
46
+ fi
47
+ if git rev-parse --quiet --verify "refs/tags/<VERSION>" > /dev/null; then
48
+ echo "Tag <VERSION> already exists"
49
+ exit 1
50
+ fi
51
+ if ! grep -qx "## <VERSION>" CHANGELOG.md; then
52
+ echo "CHANGELOG.md has no '## <VERSION>' section"
53
+ exit 1
54
+ fi
55
+ ```
56
+
57
+ ## Run the checks
58
+
59
+ ```sh run
60
+ make ty
61
+ make test
62
+ ```
63
+
64
+ ## Update the version
65
+
66
+ `pyproject.toml` holds paulrun's only copy of its version. `uv version` sets
67
+ it and updates `uv.lock` to match.
68
+
69
+ ```sh run
70
+ uv version <VERSION>
71
+ uv run paulrun --version
72
+ ```
73
+
74
+ ## Commit and push
75
+
76
+ There's nothing to commit if the version and changelog were already
77
+ committed, e.g. for the first release, or when rerunning one that failed
78
+ after this step.
79
+
80
+ ```sh run
81
+ if git diff --quiet HEAD; then
82
+ echo "Nothing to commit"
83
+ else
84
+ git commit -am "version <VERSION>"
85
+ fi
86
+ git push origin main
87
+ ```
88
+
89
+ ## Tag the release
90
+
91
+ ```sh run
92
+ git tag <VERSION>
93
+ git push origin <VERSION>
94
+ ```
95
+
96
+ ## Create the GitHub release
97
+
98
+ These are the release notes, from `CHANGELOG.md`:
99
+
100
+ ```python run
101
+ import os
102
+ import re
103
+ from pathlib import Path
104
+
105
+ version = re.escape(os.environ["VERSION"])
106
+ changelog = Path("CHANGELOG.md").read_text()
107
+ notes = re.search(rf"^## {version}\n(.*?)(?=^## |\Z)", changelog, re.MULTILINE | re.DOTALL)
108
+ print(notes.group(1).strip())
109
+ ```
110
+
111
+ ```confirm
112
+ Create a release on GitHub from tag <VERSION>:
113
+ https://github.com/phalt/paulrun/releases/new?tag=<VERSION>
114
+
115
+ Paste in the release notes above, and mark it as the latest release.
116
+ ```
117
+
118
+ ## Publish the documentation
119
+
120
+ Takes about 5-10 minutes to deploy fully.
121
+
122
+ ```sh run
123
+ make deploy-docs
124
+ ```
125
+
126
+ ## Release to PyPI
127
+
128
+ `uv publish` uploads everything in `dist/`, so clear it out first.
129
+
130
+ ```sh run
131
+ rm -rf dist
132
+ # uv publish reads the token from $UV_PUBLISH_TOKEN.
133
+ make release
134
+ ```