File Rendering



Code generation is central to hof. By rendering templates with data, we can generate any file for any language or framework.

hof gen has two modes for template based code generation.

  1. hof gen -T handles simple or adhoc cases
  2. hof gen -G is based on composable modules

We will only be covering hof gen -T in this section. The first example takes you through the process of creating and using a generator module with hof gen -G. The concepts and processing are the same, in fact hof gen -T creates a generator behind the scenes and can be layered on top of modular generators.

Data + Templates

hof gen -T, in the simplest form, is just rendering templates with data.

hof gen interlude.json -T interlude.template

Achelles: Did you see all them turtles down there?
Dougie: They go all the way down!

interlude.json

{
	"name": "Dougie",
	"question": "Do you see all those turtles?",
	"answer": "They look to go all the way down!"
}

interlude.template

Achelles: {{ .question }}
{{ .name }}: {{ .answer }}

You can also pipe any data into hof gen by using a “-” (hyphen). This can be helpful when you want to render an API response or process output.

pipe data into hof

# send response to hof
curl api.com  | hof gen -T template.txt -

# mix piped input with other entrypoints
cat data.json | hof gen - schema.cue -T template.txt

Template Format

hof use and extended version of Go’s text/template package. You can find the extensions in the template writing section.

Partial templates can be used from other templates, are registered with every template, and loaded with the -P flag. We’ll see these in the next section.

Alternate template delimiters are supported in hof gen.

Using CUE

hof gen arguments are CUE entrypoints, same as cue export. We can use this to enforce a schema or transform the data.

schema.cue

#Story: {
	name: string
	description: string
	state: "Open" | "InProgress" | "InReview" | "Closed"
	points: int & >0
}

[...#Story]

data.yaml

---
- name: task1
  description: "do stuff"
  state: "Open"
- name: task2
  description: "other stuff"
  state: "InReview"
  points: 3

If we run hof gen data.yaml schema.cue -T stories.md, we will get an error message:

0.points: incomplete value >0 & int

Oops, we forgot to set the points on a story.

T Flag Mappings

The -T flag for hof gen has a flexible format:

-T "<template-path>:<input-path>[@schema-path];<out-path>"

This enables you to

  1. Render multiple templates by using -T more than once
  2. Select a value with :<input-path>
  3. Select a schema with @<schema-path>
  4. Write to file with ;<out-path>
  5. Control the output filename with ;{{ .name }}.txt
  6. Render a single template multiple times with ;[]{{ .filepath }}

-T variations

hof gen input.cue ...

  # Generate multiple templates at once
  -T templateA.txt -T templateB.txt

  # Select a sub-input value by CUEpath (. for root)
  -T 'templateA.txt:foo'
  -T 'templateB.txt:sub.val'

  # Choose a schema with @
  -T 'templateA.txt:foo@#foo'
  -T 'templateB.txt:sub.val@schemas.val'

  # Writing to file with ; (semicolon)
  -T 'templateA.txt;a.txt'
  -T 'templateB.txt:sub.val@schema;b.txt'

  # Templated output path 
  -T 'templateA.txt:;{{ .name | lower }}.txt'

  # Repeated templates are used when
  # 1. the output has a '[]' prefix
  # 2. the input is a list or array
  #   The template will be processed per entry
  #   This also requires using a templated outpath
  -T 'template.txt:items;[]out/{{ .filepath }}.txt'

We will see these options used in the next section. You can also find examples in the hof render tests.

2022 Hofstadter, Inc