TITLE = 'Introducing nrstatic' TAGS = [] CATEGORIES = [] DEPENDENCIES = [] HEADER = 'blog-banner.html' FOOTER = None TEMPLATE = 'blog.html' STYLE = 'resume.css' IMAGE = None SUMMARY = "An introduction to nrstatic—the static webpage creator that I wrote to create this website. It is specifically designed with technical communication in mind." MAKE_INDEX = False DATE = "June 6, 2025" --- # <[TITLE]> After years of using WordPress on my websites, I became frustrated with the overhead of maintaining WordPress and its plugins. The process of writing technical articles in WordPress was also a bit annoying. I looked at some of the alternative tools, including static webpage generators that already exist, but none of them were quite what I wanted. So I developed my own, which I call `nrstatic`. It's the tool that created all of the pages on the website you are currently viewing. The pages are written as Markdown files with a bit of metadata added to the header. Mathematical expressions are written in $\rm \LaTeX$ and rendered on your browser using ${\boldsymbol{\mathsf{\color{green}{Math}\color{black}{Ja\mathcal{x}}}}}$. Code syntax highlighting is accomplished with [highlight.js](https://highlightjs.org/). There are also several built-in tools for making plots, graphs, UML diagrams, and image galleries. Python expressions can be evaluated inline and variables can be defined and then used later in the document. Bash commands can also be executed to add content to the document. It is easy to hide elements and then reveal them when the user clicks a link. For example, you can view the source code of the `build` utility by clicking `here`{.show #build}.
```python #! /usr/bin/env python3 import glob from nrstatic.filedb import FileDB from nrstatic.rendering import render_page if __name__ == '__main__': files = glob.glob("**/*.md", recursive=True) fdb = FileDB() for filename in files: render_page(filename, fdb) fdb.update_templates() fdb.close() ```
If you read through the source above, you may have noticed that the code recursively searches the entire directory tree below the current directory, searching for `.md` files to render to HTML. Only pages that have changed since the last build are re-generated, which means that the build process is usually quite fast (less than 1 second) on modern hardware. If an HTML template file changes, then all files that use that template are re-generated. If you want to view the Markdown file that was used to generate any page, simply load the `index.md` file in that directory. The file that generated *this* page is [here](index.md){target="_blank"}. There's also a nice little feature which allows you to display only one `
` element at a time, in a particular region of the page. For example, `Show Ex1`{.show-group #div-one} | `Show Ex2`{.show-group #div-two} | `Show Ex3`{.show-group #div-three}.
<< markdown_py example1
<< markdown_py example2
<< markdown_py example3
The code isn't quite ready to release for others to use, but I expect that it will be ready to share before the end of 2025 (a few more features need to be added and detailed documentation needs to be written). ## Math typesetting example Here is an un-numbered, un-labeled displaystyle equation: $$\frac{\partial L}{\partial q_i}(t,\mathbf{q}(t),\dot{\mathbf{q}}(t))-\frac{\d}{\d t} \frac{\partial L}{\partial \dot q_i}(t,\mathbf{q}(t),\dot{\mathbf{q}}(t)) = 0.$$ And this is a labeled equation using the `equation` environment: \begin{equation} \int_0^\infty e^{-x^2}\d x = \frac{\sqrt{\pi}}{2} \label{eq:gauss} \end{equation} This can be referenced as \eqref{eq:gauss} or as Eq. \ref{eq:gauss}. And here are some inline mathematics: $A=\pi r^2$. ## Extended Markdown Standard Markdown and extended Markdown features are implemented using the `markdown` module for Python[^1]. For example, the the ability to generate tables is enabled, as in: | Column 1 | Column 2 | | :---------: | :---------: | | row 1 | Text for row 1 | | row 2 | Text for row 2 | | row 3 | Text for row 3 | ### Definition Lists term 1 : definition of term 1 term 2 : definition of term 2 term 3 : definition of term 3 : another definition of term 3 ### Task Lists - [x] write blog article about `nrstatic`. - [x] add the ability to search the website. - [ ] implement the example / note / warning syntax. - [ ] consider implementing dark mode (automatically produce light and dark versions of all figures). ### Quotation blocks Quotation blocks are supported. > My passion for social justice has often brought me into conflict with people, as has my aversion to any obligation and dependence I did not regard as absolutely necessary. > — A. Einstein [^1]: This is an example of a footnote. The Markdown module that I mentioned is: [here](https://pypi.org/project/Markdown/). ## The `simplot` environment The `simplot` environment is an environment for making `sim`{.uline .text}ple `plot`{.uline .text}s of functions. The author simply writes the functions that will be plotted directly into the Markdown source file, as demonstrated below. The functions are then plotted as an SVG image and put into an HTML `figure`. <[simplot_example := """```{class="simplot center" caption="This is an automatically-generated plot."} f = ["2 * x^3 - 4 * x", "3 * x*x + 2 * cos(4*x)", "2 * cos(4*x)"] linestyle = ['--', '-', ':'] linecolor = ['m', 'b', 'g'] linewidth = [3, 1, 2] label = ["series 1", 'series 2', '2 * cos(4x)'] grid = True # turn on the grid lines x = [-2, 2] # lower and upper values of the x-axis y = [-4, 4] # lower and upper values of the y-axis title = "Three Functions" xlabel = "x-axis-label" ylabel = "y-axis-label" ```"""]> <[simplot_example]> To use `simplot`, simply create a fenced code block and add `class="simplot"` to the attributes. A caption can optionally be added as well. The source that generated the plot above is here: ```` <[simplot_example]> ```` The function (or functions) to be plotted must be entered in the array `f = []`. ## The `genplot` environment The `genplot` environment is the environment for making more `gen`{.uline .text}eral `plot`{.uline .text}s by writing the code for the plot directly into the Markdown source file. The code within the block is expected to write an output image with the basename given by the `fname` variable. It adds a file extension (like `.svg` or `.png`) and sets the final file name in the variable `filename`. The code within the block could be a Python script or you can use the python `subprocess` module to call literally any other executable code that you have installed on your machine. An example is shown below: <[genplot_example := """```{class="genplot center" caption='A non-trivial plot made within the genplot environment.'} import numpy as np import matplotlib.pylab as plt from scipy.stats.kde import gaussian_kde n_points = 10000 xvals = np.random.randn(n_points) yvals = np.random.randn(n_points) pdf = gaussian_kde([xvals, yvals], bw_method=0.14) grid_x, grid_y = np.mgrid[-5:5:0.075, -5:5:0.075] density = pdf(np.vstack([grid_x.flatten(), grid_y.flatten()])).reshape(grid_x.shape) v = plt.pcolormesh(grid_x, grid_y, density, shading='gouraud', cmap=plt.cm.YlOrBr) levels = [0.05, 0.1, 0.15] colors = ['k', 'w', 'w'] contours = plt.contour(grid_x, grid_y, density, levels, linestyles=':', linewidths=0.6, colors=colors) plt.clabel(contours, inline=True, inline_spacing=0, fontsize=8, fmt='%1.2f') plt.colorbar(v, pad=0.015) plt.xlim((-4, 4)) plt.ylim((-4, 4)) plt.xlabel('x') plt.ylabel('y') plt.title('2D Probability Distribution', fontsize=14, fontname='Liberation Serif'); # the plotting code must define filename: filename = f"{fname}.png" # here's where you use `fname` and define `filename` plt.savefig(filename) ```"""]> <[genplot_example]> To use `genplot`, simply create a fenced code block and add `class="genplot"` to the attributes. A caption can optionally be added as well. The source that generated the plot is here: `click to show`{.show #genplot-example}
```` <[genplot_example]> ````
The first time that a plot is generated, the processing may take a while if the plotting code is computationally expensive. On subsequent builds, *the plot will not need to be generated again unless the code that was used to create it changes.* Each output image file name contains a hash of the code that created the image. A side effect of this is that the directory containing the Markdown file will accumulate many different versions of the same plot as the plot evolves over time, unless the images are manually deleted. ## A Graphviz graph example Graphviz graphs can automatically be generated by putting the code for the graph in a code block and adding the attribute `class="graph"`. Optionally, a caption can be added. Here's an example: <[graph_example := """```{class="graph center" caption="This is a tree."} digraph G { bgcolor="#ffffff00" node [shape=circle, width=0.5, fixedsize=true]; 0 -> 1 0 -> 2 1 -> 3 1 -> 5 2 -> 4 2 -> 6 3 -> 7 3 -> 9 5 -> 11 5 -> 13 4 -> 8 4 -> 10 6 -> 12 6 -> 14 } ```"""]> <[graph_example]> `View source`{.show #graph-source}
```` <[graph_example]> ````
## A Mermaid UML example Similarly, Mermaid UML syntax is also enabled. Simply add `class="mermaid"` to a fenced code block containing Mermaid UML syntax and it will be rendered. For example: <[mermaid_example := """```{class="mermaid center"} flowchart TB A e1@--> C A e2@--> D B --> C B --> D e1@{ animate: true } e2@{ animate: true } ```"""]> <[mermaid_example]> `View source`{.show #mermaid-source}
```` <[mermaid_example]> ````
## Evaluation of Python expressions Python expressions, enclosed in `<["<" + "[]" + ">"]>` brackets, are executed and the value returned by the statement is inserted into the document. Putting a format specifier after the brackets in curly braces applies the Python formatting rules to the output number or string. Including `variable_name := value` inside of the brackets defines a variable, which can be used later in the document (inside of the evaluation brackets, of course). <[angle := pi / 3]> For example, `<["<" + "[10 * 4]" + ">"]>` becomes <[10 * 4]>. `<["<" + "[cos(4.1)]" + ">{:0.3f}"]>` becomes <[cos(4.1)]>{:0.3f}. The `cos()` works because everything from the `math` module is already imported. If we define a variable, `angle`, as in: ```text <["<" + "[angle :" + "= pi / 3 ]" + ">"]> ``` then we could use the variable like this `<["<" + "[tan(angle)]" + ">{:0.3f}"]>`, which becomes <[tan(angle)]>{:0.3f}. The `<["<" + "[]" + ">"]>` bracket evaluations are performed before any other processing, as a pre-processing step, which means that they can be used within any other environment within the document; everything can be parameterized. Using this feature inside of equations and plotting code can be very handy, particularly if the same complicated string shows up multiple times within the document; you only need to write it once, rather than copying and pasting multiple times. Note that multi-line strings can also be set to a variable; just use Python's triple-quote syntax for multi-line strings: `""" multi-line string """`. In fact, this is how the source code of the plots and graphs above was displayed in this document; the variable was used once to create the plot and a second time to display the source code for the plot. ### Accessing document metadata The `<["<" + "[]" + ">"]>` brackets can also be used to access the document's metadata attributes. For example, the title of this post is *<[TITLE]>*. It uses the <[TEMPLATE]> template. It was originally posted on <[DATE]>, and the summary of the post is: > <[SUMMARY]> Check out [`index.md`](index.md){target="_blank"} to see how these were accessed. ## Bash command output inclusion The standard output stream from any Bash command string can be inserted into the page by simply putting `<< command` at the beginning of a line (with no whitespace before it). This allows for essentially endless possibilities. To include the contents of another file on the local system: ```markdown << cat /path/to/file ``` To include something downloaded from a web server: ```markdown << wget -q -O - URL ``` To run a command on a remote system on which you have set up passwordless SSH access and include the output of that command: ```markdown << ssh username@remote "command" ``` A concrete example: ```markdown << echo "This was last built $(date -u) on a system named '$(hostname).'" ``` becomes: << echo "This was last built $(date -u) on a system named '$(hostname).'" ## Automatic index creation Setting the metadata parameter `MAKE_INDEX = True` causes `nrstatic` to create a summary page containing links and summaries of all of the pages in the directory tree under that page. For example, the [blog]($ROOT/blog/) page is created using this feature. ![An example of an automatically-generated index page.](blog-index.png){.center} ## Automatic image gallery creation Image galleries are displayed using [nanogallery2](https://nanogallery2.nanostudio.org/). `nrstatic` can search for the presence of sub-directories named `gallery*/`, containing images. A gallery is inserted by simply including ``` << mkgallery ``` at the beginning of the line where you want the gallery to appear. Optionally, a directory name containing the gallery images can be specified, as in ``` << mkgallery gal2 ``` In this case, the gallery directory does not need to match the pattern `gallery*/`. There is a helper script, called `mkthumbs` which must first be called in order to generate the thumbnail images in the gallery directory, but once the thumbnail images exist, everything else about gallery creation is fully automatic. An example gallery is shown below. << mkgallery ## nrstatic sub-commands Currently, there are several sub-commands available. Some of these were already mentioned above. All of these follow the command `nrs`: | Command | Summary | | -------: | :--------- | | `build` | Build pages that were recently added or modified. | | `rebuild` | Delete the file database and rebuild all pages. | | `publish` | Publish the website to a remote server (this is configurable). | | `server` | Launch an HTTP server for local development / testing. | | `mksearch` | Create the search index (this is configurable; I use [pagefind](https://pagefind.app/) for indexing & searching.)| | `mkgallery` | Create the HTML for a gallery in the current directory.| | `mkthumbs` | Make thumbnail images for the files in the current directory.| | `shrink-images` | Resize large images in the current directory because very large files make the galleries very slow.|