README
¶
Trash
Trash - a stupid, simple website compiler.
[!CAUTION] Nothing is stabilized yet, so existing pages may break from time to time, do not use this for anything serious
Features
- $LaTeX$ expressions (no client-side JS!)
- D2 diagram rendering (no JS still!)
- Mermaid diagram rendering (yeah, still no client-side JS)
- Pikchr diagram rendering (you guessed it)
- Painless embedding of YouTube videos, HTML5 audio, and more in native Markdown
- Syntax highlighting
- Various Markdown extensions such as image
<figure>s, image sizing, callouts, Pandoc-style fences,:emojis:, and more - YAML and TOML frontmatter parsing support
- Automatic anchor placement
- Automatially minifies output HTML, CSS, JS, JSON, SVG and XML for smallest builds
- Lots of built-in template functions including an integration with the Expr expression language
- Built-in webserver with live-reloading (
trash serve) - Under 1400 lines of Go code in a single file
Installation
Install Go if you haven't yet.
$ go install github.com/zeozeozeo/trash@latest
Usage
$ trash help
Usage: trash <command> [directory]
A stupid, simple website compiler.
Commands:
init Initialize a new site in the directory (default: current).
build Build the site.
watch Watch for changes and rebuild.
serve Serve the site with live reload.
help Show this help message.
Template cheatsheet
Trash uses the Go template syntax for templates, extending it with some handy built-in functions. Below is a reference of all extra functions defined by Trash.
You should still refer to the source code instead of this if possible, this mostly serves as a general overview.
File system
-
readDir "path": Get all pages within a directory{{ $posts := readDir "posts" }} -
listDir "path": List directory entries, use.Nameand.IsDiron returned valuesused in personal example
-
readFile "path": Read a file from the project root<style>{{ readFile "static/style.css" }}</style>
Dict operations
dict "key1" val1 "key2" val2: Create a dict, usually paired with other functionssortBy "key" "order" $dict: Sort by a key path.ordercan be"asc"or"desc"{{ $posts := readDir "posts" | sortBy "date" "desc" }} {{ $users := $data.users | sortBy "age" "asc" }}where "key" value $dict: Filter where a key path matches a value{{ $featured := where "featured" true .Site.Pages }} {{ $activeUsers := where "active" true $data.users }}groupBy "key" $dict: Group by a key path{{ $postsByYear := groupBy "date.year" .Site.Pages }} {{ $usersByDept := groupBy "department" $data.users }}select "key" $dict: Extract values from a key path across many dicts{{ $allTags := select "tags" .Site.Pages }} {{ $allNames := select "name" $data.users }}has "key" $dict: Check if a dict has a certain key path{{ if has "image" .Page }} ... {{ end }} {{ if has "email" $user }} ... {{ end }}
All collection functions support dot notation for nested keys:
{{ where "author.name" "Alice" .Site.Pages }}
{{ groupBy "metadata.tags.primary" .Site.Pages }}
{{ select "contact.email.work" $users }}
Passing a .Page will decay into its frontmatter (.Page.Metadata):
{{/* These are equivalent - both access the page's frontmatter */}}
{{ if has "title" .Page.Metadata }} ... {{ end }}
{{ if has "title" .Page }} ... {{ end }}
Datetime
-
now: Return the current UTC time -
formatTime "format" date: Format time{{ .Metadata.date | formatTime "DateOnly" }}Supported formats:
Format Output (example) Layout01/02 03:04:05PM '06 -0700 ANSICMon Jan _2 15:04:05 2006 UnixDateMon Jan _2 15:04:05 MST 2006 RubyDateMon Jan 02 15:04:05 -0700 2006 RFC82202 Jan 06 15:04 MST RFC822Z02 Jan 06 15:04 -0700 RFC850Monday, 02-Jan-06 15:04:05 MST RFC1123Mon, 02 Jan 2006 15:04:05 MST RFC1123ZMon, 02 Jan 2006 15:04:05 -0700 RFC33392006-01-02T15:04:05Z07:00 RFC3339Nano2006-01-02T15:04:05.999999999Z07:00 Kitchen3:04PM StampJan _2 15:04:05 StampMilliJan _2 15:04:05.000 StampMicroJan _2 15:04:05.000000 StampNanoJan _2 15:04:05.000000000 DateTime2006-01-02 15:04:05 DateOnly2006-01-02 TimeOnly15:04:05 Or vice-versa (passing
"15:04:05"will have the same effect as passing"TimeOnly")
Strings and URLs
concatURL "base" "path1" "path2" ...: Join URL parts together<img src="{{ concatURL .Config.site.url .Page.Metadata.image }}">truncate "string" length: Shorten a string to a max length by adding…<p>{{ .Content | truncate 150 }}</p>pluralize count "singular" "plural": Return the singular or plural form based on the count{{ len $posts | pluralize "post" "posts" }}markdownify "string": Render a string of Markdown as HTML{{ .Page.Metadata.bio | markdownify }}
Conditionals
default "fallback" value: Return the fallback if the value is empty<img alt="{{ default "A cool image" .Page.Metadata.altText }}">ternary condition trueVal falseVal: if/else<body class="{{ ternary (.Page.Metadata.isHome) "home" "page" }}">
Math and random
add,subtract,multiply,divide,max,min: Operations on integersrandint min max: A random integer in a rangerandfloat min max: A random float in a rangechoice item1 item2 ...: Randomly select one item from the list of arguments provided<p>Today's greeting: {{ choice "Hello" "Welcome" "Greetings" "Howdy" }}!</p>shuffle $list: Randomly shuffle a list (returns a copy)
Slice utilities
first $list: Get the first item of a slicelast $list: Get the last item of a slicereverse $list: Return a new slice with the order of elements reversedcontains $list item: Check if a slice contains an item{{ if contains .Page.Metadata.tags "featured" }} <span class="featured-badge">Featured</span> {{ end }}
Casts
toString value: Convert any value to a stringtoInt value: Convert any value (e.g. float, string) to an integertoFloat value: Convert any value (e.g. int, string) to a float
Debugging
print value: Print a value during the build
Utility
toJSON $data: Convert a value to a JSON stringfromJSON $data: Parse a JSON stringsprint "format" values...: Return a formatted string, similar toprintf{{ $message := sprint "Processed page %s" .Page.Permalink }}expr "code" environ: Execute an Expr block, see the blog example for more
Config format
Running trash init will create a Trash.toml as one of the files in your current directory. The structure of this file is not forced upon you whatsoever, however there are a few optional settings you can toggle:
[mermaid]
theme = "dark" # see available themes at https://mermaid.js.org/config/theming.html
[d2]
sketch = true # see https://d2lang.com/tour/sketch/
theme = 200 # see available themes at https://d2lang.com/tour/themes/
[pikchr]
dark = true # change pikchr colors to assume a dark-mode theme
[anchor]
text = "#" # change the ¶ character in auto-anchors to something else
position = "before" # default is "after", where to place the anchor
Aside from this, you can add your own fields, and access them in templates:
[site]
url = "https://example.com/"
Let's say you're making an RSS feed for your blog:
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>My Blog</title>
<link>{{ .Config.site.url }}</link> <!-- this is the `url` from Trash.toml! -->
...
<item>
...
</item>
</channel>
Template context
All templates under the layouts directory are created in the same context, so you can include other templates within a template:
<!-- layouts/base.html (this is the default layout) --->
<!DOCTYPE html>
<html lang="en">
{{ template "boilerplate.html" }}
<body>
{{ .Page.Content }}
</body>
</html>
<!-- layouts/boilerplate.html --->
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Blog</title>
</head>
This is not the case for .md filles inside the pages directory.