A GO program to loop structured data through the template engine. This is a command-line program that pipes a CSV or JSON file through the "text/template" package and prints the results to Stdout. If you need to make a full blown website or blog, and have stumbled onto this page, I highly recommend using Hugo instead. I often have to make small snippets of data-based code, portions of a page, or single pages in multiple environments. With these type of projects, the file structure required by Hugo sometimes prevents me from using it.
Example usage:
loopit [options] template.html [...template.html]
The data flag will accept a file path on the system or a url to a CSV, JSON or RSS file. Loopit will make a map of the file data and pass it to the template.
In the case of a CSV file, loopit will create an object for each row using the first row as the keys for each object. Therefore, the file needs to have a header row with column names that can be used in the dot notation of the "text/template" pacakge. As a general rule stick to single words without punctuation and you should be fine.
In the case of a JSON file, loopit will pass the object or array straight through.
In the case of an RSS file, loopit will convert most standard elements.
If you do not specify a template file, loopit will print the parsed data to sdout. It will also accept data piped in through stdin if -data is ommitted.
The markdown flag passes the output through the russross/blackfriday package.
The minify flag will pass the output through the tdewolff/minify package. Currently this only works for HTML output.
Write the markup to a file instead of stdout. This uses ioutil.WriteFile()
and replaces the file each time.
After the initial render, loopit will enter watch mode and re-run the render function any time a template is written. It also recursively walks the current directory and adds any .js
and .css
files it finds to the list.
The shim flag is a boolean option. When added, loopit will use goquery to move DOM around after the template has been parsed. This is useful when HTML is provided in a JSON object, but needs to be enhanced with ads or any other content.
Here is an example that will inject an ad before the 3rd paragraph:
<div shim="body p:nth-child(3)">[Ad code goes here]</div>
Prints the version.
The Go template package is very basic, but also extendable. Below are custom functions that are available in the template based solely on need so far. Several of these are borrowed from Hugo.
Adds an int to an int.
{{ add 1 $index }}
This function parses a date using the time
package and returns a formatted version of your choosing. The Golang time package is kind of odd, but also very flexible. Make sure to read the documentation fully before working with this.
The first parameter is the date string to parse, the second is a representation of that date mapped to 'Mon Jan 2 15:04:05 MST 2006' and the third is the desired output format mapped to 'Mon Jan 2 15:04:05 MST 2006'.
The fourth parameter is optional, and allows you to alter the Timezone using the time.LoadLocation() function.
{{ dateFormat "2017/11/2" "2006/01/02" "Jan 2" "America/Chicago" }}
This function returns the first string match using the regexp package.
{{ findRe "^foo" "foobar" }}
This is similar to findRe, but it returns a slice of strings that match sub-patterns.
{{ findSubRe "^foo(.*)$" "foobar" }}
This would return: ["foobar", "bar"]
Straightforward but sometimes necessary when comparing data types from JSON feeds that are casted with json.UnMarshal()
{{ floatToInt .floatPropertyInFeed }}
Simple function that will convert 68 inches to the string 5'8".
{{ inchesToFeet .player.height }}
Returns a string converted to lower case.
{{ lower "John Smith" }}
Returns a JSON string representation of an object. Like the JavaScript JSON.stringify()
function.
{{ marshal .tags }}
The minify functions sends a string through the tdewolff/minify package. The following mimetypes are supported:
- "text/css"
- "text/html"
- "text/javascript"
<style>{{ file "./css/styles.css" | minify "text/css" }}</style>
This function sends the string through the blackfriday.MarkdownCommon()
function and returns parsed HTML as a string.
{{ file "./contents.md" | markdown }}
Just like findRe but returns a boolean instead of the matching string
{{ findRe "^foo" "foobar" }}
Passes a string through the strings.Replace
function swapping the first match.
{{ replace "http" "https" .url }}
More advanced replacing using the regexp package. This replaces all matching strings in the pattern.
{{ replaceRe "^<div xmlns.*?>" "" .body }}
Subtracts an int from an int.
{{ subtract 1 $index }}
Trims characters and space from both sides of the string.
{{ trim "/" .link }}
Runs a string through the url.QueryUnescape() function, which converts each 3-byte encoded substring of the form "%AB" into the hex-decoded byte 0xAB. Useful for urls in feeds.
Note: escape is already available in the Go Template package.
{{ unescape .link }}
Converts a string to uppercase.
{{ upper "huge" }}
This function is based on the Array.find
function in javascript. It returns the first value of a sequence that matches (or doesn't match) the comparator.
{{ $josh := find .employees "name" "Josh" }}
or
{{ $awayTeam := find .teams "venue.location" "!=" "home" }}
Joins a slice using a separator and returns the string.
{{ join "," .authors }}
A port of Underscore's pluck function. Uses dot notation to grab nested objects and returns a slice.
{{ pluck "album.title" .relatedWork }}
Pulls a subset of data by index. For example, to range over the first five rows of a csv file use the following code.
{{ range slice . 0 5 }} ... {{ end }}
Sorts a sequence by a path. Ascending and desending order are supported.
{{ range sort .people "name.last" }} ... {{ end }}
or
{{ range sort .people "age" "desc" }} ... {{ end }}
A port of Hugo's where function, this returns a filtered slice and is desinged to work with a range. Dot notation is possible.
The default comparator is ==
, but you can use any of the following: =, ==, eq, !=, <>, ne, <, lt, >, gt, <=, lte, >=, gte, matches
.
matches
runs a pattern through the regexp.MatchString() function.
{{ range (where .friends ".name.first" "ne" "John") }}
This is a copy of a concept from Hugo. The Go template package is intentionally dumb by design, but that doesn't always work when a JSON feed is created by a third party and time to publication is critical. Variable scope is very tight, so to loosen it up a bit you can use scratch. This lets you maintain global variables and alter them from within loops and conditionals.
It works very much like local storage in the browser, with key/string pairings. There are three functions to get, set and delete a pairing. Unlike Hugo, only strings are supported.
{{ $scratch := scratch }}
{{ $scratch.Set("name", "Jane") }}
{{ if [condition] }}
{{ $scratch.Set("name", "Gloria") }}
{{ end }}
<h2>Hello, {{ $scratch.Get("name") }}</h2>
Loopit is designed to make small snippets quickly, but often those snippets can turn into full blown pages that need some DRY tools. Partials allows you to run data through an external template and get a string, which can be further manipulated by the functions in this template.
Simply returns file contents as a string. Relative paths are compared to the working directory when the loopit command is run, not relative to the template. This is a tad confusing but allows for maximum flexibility, like common atomic structures for multiple projects. For consistency, I personally use a Makefile.
{{ file "./css/atoms.css" | minify "text/css" }}
Returns contents of a file as a string, but runs data through loopit and includes all functions (except nested partials I haven't figured that one out yet). Just like file
, relative paths are compared to the working directory.
{{ partial "./amp-components/amp-img.tmpl" .photo }}