From 8cb375f5c753f42b743090cfc52ab4151e7345c6 Mon Sep 17 00:00:00 2001 From: Case Duckworth Date: Mon, 9 Jan 2023 00:01:08 -0600 Subject: Add README.md, COPYING --- COPYING | 8 ++ README.md | 276 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 284 insertions(+) create mode 100644 COPYING create mode 100644 README.md diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..3be757f --- /dev/null +++ b/COPYING @@ -0,0 +1,8 @@ +Copyright (C) 2023 Case Duckworth + +Everyone is permitted to do whatever with this software, without +limitation. This software comes without any warranty whatsoever, +but with two pieces of advice: + +- Don't hurt yourself. +- Make good choices. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2e53c51 --- /dev/null +++ b/README.md @@ -0,0 +1,276 @@ +# 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.n + +## `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. + +## contributing + +Comments, bug reports, and merge requests are welcome! [Send me an +email](mailto:vienna@code.acdw.net) or [@ me on Mastodon](https://tilde.zone/@acdw). + +## license + +`vienna` is licensed under the Good Choices License. See COPYING for details. -- cgit 1.4.1-21-gabe81