# vienna ## a tiny, tasty ssg `vienna` is my (current) Platonic Ideal of a simple and extensible static site generator. I've written a couple of these over the years, and I think I've finally got something that I really like. Anyway, lemme tell you how it works. ## `vienna` invocation `vienna` has a similar command-line interface to `make`; that is, it runs commands on the files in a given folder and produces output. It has command-line switches as well as subcommands. By default, `vienna` simply builds the pages in the current directory into the output directory. ### command-line arguments - `-r URL`: Use URL as the root url for the site. The default is the useless https://example.com/, since you really should set this either on the command line, in the config file, or in an environment variable. - `-C DIRECTORY`: Build the site from pages in DIRECTORY, instead of the current directory. - `-o DIRECTORY`: Build the site to DIRECTORY, instead of the default `out`. - `-c FILE`: Use FILE as the configuration file, instead of `.vienna.sh`. - `-h`: Show a usage note and exit. - `-q`: Disable any logging. ### subcommands - `init`: Initialize a site by creating a `.vienna.sh` in the site directory. - `clean`: Delete the output directory and temporary files before building. - `preview`: Preview the website on your local machine. By default, `vienna` doesn't define a `preview` function, so you'll want to write your own. I've included one in the `.vienna.sh` made with `vienna init` to get you started. - `publish`: Publish the website to your production server. By default, `vienna` doesn't define a `publish` function, though I do include a sample in the default `.vienna.sh` made with `vienna init`. ## file layout Pretty much any SSG is a mapping from input files to output files, with various amounts of processing along the way. Usually, the input files are in some text-ish format like markdown, the output files are in html, and processing includes templating, static file copying, and other such mixins. `vienna` is similar, except it uses a mostly-html input language and uses POSIX shell as its templating and extension language. I chose these because they require the smallest number of dependencies, and because shell scripting is fun! [citation required] But more about the markup later. Let's talk about the structure `vienna` expects your files to be in: ``` / /some-page.htm /some-other-page.htm [...] /.vienna.sh /.page.tmpl.htm /.index.tmpl.htm /.feed.tmpl.htm /.plugins/[...] ``` The content files of your site are all of the ones *not* beginning with a dot, while all of `vienna`'s are the ones that *are*. In UNIX-like environments, this makes them "hidden," so you can focus on your content by default. However, your content is not something I can talk about in this README, because you still have to write it! Let's talk about `vienna`'s files next. ### .vienna.sh `vienna` is a POSIX shell script. Before it builds any pages, it reads a configuration file. By default, this file is `.vienna.sh` in your site folder, but you can make it anything you want by passing `-c ` on the command line or by setting the `$VIENNA_CONFIG` environment variable (more on the command line and environment variables below). Because the configuration file is written in the same language as `vienna` itself, you can redefine any variable *or function* that's in `vienna`, fully customizing your experience. The main knobs you'll want to turn include #### variables - `$DOMAIN`: The domain (and, well, protocol) of your published website. Used when building indexes and feeds. *Default: `https://www.example.com`* - `$OUTDIR`: The output directory where `vienna` will put the built site. *Default: `out`* - `$PLUGINDIR`: The directory where plugins can be found and sourced by `vienna`. *Default: `.plugins`* - `$PAGE_TEMPLATE`: The template to use for pages. *Default: `.page.tmpl.htm`* - `$INDEX_TEMPLATE`: The template to use for an index. *Default: `.index.tmpl.htm`* - `$FEED_TEMPLATE`: The template to use for an RSS feed. *Default: `.feed.tmpl.htm`* #### functions - `publish`: How to publish a finished site. By default, this function does nothing. `vienna init` defines a basic `publish` function in `.vienna.sh`. - `preview`: How to preview a site locally. By default, this function does nothing. `vienna init` defines a basic `preview` function in `.vienna.sh`. - `sort_items`: How to sort items when generating a list. By default, don't sort files at all. - `index_item`: How to build each item in an index. The default outputs `
  • TITLE
  • `, where TITLE is the page's title and LINK is a link to the published page. - `feed_item`: How to build each item in a feed. The default outputs an RSS `` tag suitable for inclusion in an RSS 2.0 feed. - `filters`: UNIX pipes that each page is run through as it's being built. The default just uses `phtml`, but in my personal site I also pipe it through `expand` so I can use shell expansion as well. ### templates - `.page.tmpl.htm`: The template for individual pages. - `.index.tmpl.htm`: The template for the root index.html. - `.feed.tmpl.htm`: The template for the RSS feed. ### plugins - `.plugins/*.sh`: Files matching this pattern will be sourced before building the site. They can define new functions, export variables, or do whatever, so make sure you know what's in these files! ## content Now for the stuff *you're* in charge of: your content. `vienna` takes a flat- or no-hierarchy approach to sites. Your site will just be one flat folder full of pages, though each page will be its own folder with an `index.html` for nicer urls. This might not be for you, and that's okay! You can change it (complicated) or use another ssg (probably easier). It's up to you. ### pages By default, files matching the pattern `*.htm` will be processed by `vienna` and turned into finished pages. `.htm` was chosen because it'll be picked up as html in most text editors (for the format they're written in, see the **phtml** section below), but it's also *unfinished html* (get it?!). You can change this—you can change anything with `vienna`—but I'll leave that as an exercise for you to figure out. ### static files Every other non-hidden file in the `vienna` folder will be copied as-is to the output folder. ## `phtml`: *pretty much* html While lightweight markup languages like Markdown are nice (I'm using it to write this README, for example), I realized that for my blogging needs it's not really necessary. html is *pretty much* good enough for authoring, if I'm being honest—it's just a little too verbose for fully-fluent drafting. Thus, `phtml` was born. It's a function in the `vienna` source that consists of one sed call: ``` sed -E ' /./ {H;$!d}; x # Hold lines til empty, then exchange to pattern s#^[ \n\t]+([^<].*)#\1# # Replace non-HTML paragraph with itself t par; b end # If successful, branch to :par; else :end :par s#([^\\])&#\1\&#g # Replace & with & s#([^\\])<#\1\<#g # Replace < with < s#([^\\])>#\1\>#g # Replace > with > s#\\([&<>])#\1#g # Replace \-escaped &,<,> with raw s#.*#

    &

    # # Wrap the pattern space with

    tags :end # [:par falls through to :end] s#^[ \n\t]+## # Remove leading whitespace $!a # Add a final newline unless last line ' ``` To clarify the comments above: `phtml` allows a writer to leave out `

    ` tags, which I consider are the most annoying parts of html. Paragraphs that don't begin with `<` are wrapped in `

    ` tags, and html reserved characters `<`, `>`, and `&` are turned into entities. You can still write html, of course—either by backslash-escaping the reserved characters or by starting a paragraph with an html tag (really, the character `<`). Those paragraphs are passed through unprocessed. This rule may seem as though it negates the benefits of leaving out `

    ` tags. *After all*, you might think, *if I want to add a link or even emphasize text, I'll have to escape the tags or wrap the whole thing in html!* While you'd be right about that, I use plaintext paragraphs enough that it's worth it. ## `expand`: templating with here-docs Here-docs are some of the most useful structures in any programming language, and in shell they can be especially powerful. I first came across using here-docs for templating on some Github repo I've since lost, but there are many other projects that do something similar. `expand` is pretty minimal, and uses here-docs and a little bit of escaping to provide lots of expressive power. A template that looks like this: ``` Donuts now cost $$${CURRENT_DONUT_COST}, a $$(donut_perc_delta)% $$(if [ $$(donut_perc_delta) -ge 0 ] then echo increase else echo decrease ) from last week. ``` will turn into this, given that donuts currently cost $1.99 and $1.89 last week (`donut_perc_delta` truncates percentages to two digits): ``` Donuts now cost $1.99, a 5% increase from last week. ``` Single `$` and \` characters are escaped with backslashes so they won't be expanded by the shell. Two `$$` are converted to one `$`, and three `$$$` in a row are converted to `\$$$`, since usually you'll want the second pair of dollar signs to introduce a variable or function, not the first pair. Other than that, `expand` simply `eval`s the templates given on the command line as here-docs. Pages to be templated are passed into the function's standard input, and everything just works. It's pretty cool! ### page metadata `vienna` also supports colon-separated metadata in html comments in source files. I put something like the following at the tops of my pages: ``` ``` `vienna` processes these as simple key-value pairs, accessible with the `meta` function. So in templates, you can access the title with `$$(meta title)`, or the value of a key called `foo` with `$$(meta foo)`. `vienna` provides convenience functions `title` for the page `title` and `pubdate` for its `date`. ## tweaking the behavior of phtml and expand (without re-writing the functions) Of course, you can always rewrite `phtml` or `expand` to suit your needs, or rewrite `filters` to use other processers like `markdown`, `asciidoc`, or whatever you desire. However, for the "vanilla" experience, `vienna` includes a variable that tweaks the behavior of `phtml`: `$PHTML_OPTIONS`. `$PHTML_OPTIONS` can be one or more of the following values, separated by spaces: - `expand`: Run `expand` on the content after it's processed by `phtml`. - `entities`: Convert `&`, `<`, and `>` into html entities. The default is `expand entities`. ## using plugins `vienna` also supports the use of plugins, which are shell scripts sourced before building the site. Plugins are placed in `$VIENNA_PLUGINDIR`, or `.plugins` by default, and must end in `.sh` to be sourced. You can see the `plugins` directory of this repo for some example plugins that I found useful. ## installing `vienna` Clone this repository and copy or link `vienna` to somewhere in your `$PATH`. That's what I do anyway. ## contributing Comments, bug reports, and merge requests are welcome! [Send me an email](mailto:vienna@code.acdw.net) or [contact me on Mastodon](https://tilde.zone/@acdw). ## license `vienna` is licensed under the Good Choices License. See COPYING for details.