The idea is the source code and explanatory text appear interleaved in a Markdown formatted file. Each time you edit a Markdown file, you can re-run the source code it contains using the
run_markdown tool provided by this project (it's super userful as a Sublime build system).
run_markdown will update the file in-place with the new output from running the code. If an unexpected error occurs it will be printed to the terminal instead of overwriting the file.
The example book source contained in this project has multiple chapters (directories) containing multiple subsections (files). By having these subsections in separate files, it becomes a lot easier to focus on each individual subject as you write. The example
Makefile also shows how all of the pieces can be connected together.
In the case of my book, the final step was post-processing the Markdown file and converting it into a Microsoft Word document. This was a requirement of my publisher because of the way their book printing workflow was built. You can learn more about how I wrote my book here.
example directory for the layout of a simple book. This includes an example
Makefile that ties it all together. To run the example, run the following commands from a clean check-out of this project. Make sure you have Python 3 installed.
Create a virtual environment:
Activate the virtual environment:
Install this package in your virtual environment:
pip install -e .
Go into the example directory:
Run all of the Markdown files and have them overwritten in place:
Build the whole book output:
Making the book will also output diffs of each section after the source is re-run. This allows you to see if running the latest version of the code in the Markdown files changes the output in any way.
When you're done with everything, you can delete the output files:
And finally, deactivate your virtual environment:
run_markdown tool looks for code blocks like this in Markdown files (that end with the
```python print('Hello world') ```
The tool will run the code top-to-bottom in the file. When a non-Python block like this is found (it also can be empty):
``` Output goes here ```
All of the prior Python blocks are combined together, run, and their combined output is inserted (
Hello world in this case). Not every Python block needs to have an output block, but every output block needs at least one preceeding Python block to produce output.
Python blocks do not have to be stand-alone code blocks. A single class or function definition can be interleaved with text. For example, this is legal:
```python def multiply(a, b): ``` And the body is: ```python return a * b ```
Here's a list of various detailed features that are provided by the
pprintfunction is always available without import
debugfunction will write output to stderr but not insert it into the Markdown file's output
Pdb.set_trace()will actually stop execution in the Markdown file so you can debug as the program runs
- Exception tracebacks and PDB step line numbers are all given as line numbers in the original Markdown file
- Use ```````python```` to run a code snippet as Python 3 source. This will automatically inherit all of the state of the program for any snippets that are higher up in the Markdown file
- Use ```````python2```` to run a code snippet as Python 2 source instead of Python 3. Notably, Python 2 snippets will not inherit the Python execution state from higher up in the file, they are limited to the containing ``` block
- Use ```````python-exception```` for expected exceptions for which you want to insert the exception name and error message back into the Markdown file
- Use ```````python-syntax-error```` for examples that contain syntax errors that you want to insert back into the Markdown file
- Use ```````python-include:path/to/file.py```` to include an external Python file relative to the
--root_dirflag, which defaults to the root of the book directory. This will automatically insert a comment of the source file's relative path at the top of the included source
random.seedis always set to
1234so your random functions have predictable output
- The timezone is always set to
US/Pacificso your code runs in the same timezone regardless of where your computer currently is located
- The script will only be allowed to run for
--timeout_secondsbefore being terminated (defaults to 5 seconds)
- Clean up the Python style
- Write better docstrings
- Write some tests (for things like
- Actually upload this to PyPI
- Add a Travis build for example data