Cue really shines with configuration management and
solves a lot of fundamental problems we have with current options.
And while this is going to be Cue’s primary use case for most people,
you can actually generate pretty much anything.
How? By using text/templates to render any file we wish.
Generating with Cue
first.cue
packagegenimport"text/template"data: {// meta info name: *"tasks"|string @tag(name) msg: "Hello \(name), welcome to Cuetorials"// task list tasks: [ {name: "t1", effort: 1, complete: true}, {name: "t2", effort: 4, complete: true}, {name: "t3", effort: 3, complete: false}, {name: "t4", effort: 2, complete: true}, {name: "t5", effort: 3, complete: false}, ]// grouped tasks complete: [ for t in tasks if t.complete ==true {t}] incomplete: [ for t in tasks if t.complete ==false {t}]}// Templates which use Go's template enginetemplates: [ { filename: "\(data.name)-todo.txt" contents: """ {{ .msg }} --- TODO --- {{ range $T := .incomplete -}} {{ printf "%-4s%v" $T.name $T.effort }} {{ end }} """ }, { filename: "\(data.name)-done.txt" contents: """ Here's what you have finished \(data.name). Good job! --- DONE --- {{ range $T := .complete -}} {{ $T.name }} {{ end }} """ },]// The rendered output from data+templatesrendered: [ for T in templates { filename: T.filename contents: template.Execute(T.contents, data)}]
first_tool.cue
packagegenimport ("tool/file")command: "gen": {for i, R in rendered {// make a unique name when comprehending"write-\(i)": file.Create & { filename: R.filename contents: R.contents } }}
cue cmd -t "name=bob" gen
Cue’s template system is Go’s template system,
so all of the capabilities and rules are the same.
We’ve used both list and field comprehension
to define render and write our templates respectively.
With sophisticated Cue definitions, values, and templates
you can generate any output that Cue does not map onto natively.
Generating with Hof
At Hofstadter, we built hof as a custom tool for sophisticated code generation.
We wanted a single source of truth for our models which we could turn into
the source code for databases, servers, and frontend which implement a full stack app.
We think of it as a “high code” (low code) solution for developers.
Cue was chosen as the UX/DX for writing the input (designs)
and the generators which hof processes.
The two problem with previous systems are:
You always end up writing custom code in the generated output
Your designs evolve but you’ve already generated the boilerplate
The solution is actually pretty simple, keep a shadow copy of the generated code
and use a 3-way diff to merge design updates and custom code.
With the following two files in a directory, run hof mod vendor cue and hof gen
packagegenimport (// import hof's generator schema"github.com/hofstadter-io/hof/schema/gen")// A schema for our generator's input#Input: { name: string todos: [...{ name: string effort: int complete: bool }]}// create a generator#Gen: gen.#HofGenerator & {// We often have some input values for the user to provide.// Use a Cue definition to enforce a schema Input: #Input// Required filed for generator definitions, details can be found in the hof docs PackageName: "dummy"// Required field for a generator to work, the list of files to generate Out: [...gen.#HofGeneratorFile] & [ todo, done, debug, ]// In is supplied as the root data object to every template// pass user inputs to the tempaltes here, possibly modified, enhanced, or transformed In: { INPUT: Input Completed: _C Incomplete: _I }// calculate some internal data from the input _C: [ for t in Input.todos if t.complete ==true {t}] _I: [ for t in Input.todos if t.complete ==false {t}]// the template files todo: { Template: """ Hello {{ .INPUT.name }}. The items still on your todo list: {{ range $T := .Incomplete -}} {{ printf "%-4s%v" $T.name $T.effort }} {{ end }} """// The output filename, using string interpolation Filepath: "\(Input.name)-todo.txt" } done: { Template: """ Here's what you have finished {{ .INPUT.name }}. Good job! {{ range $T := .Completed -}} {{ $T.name }} {{ end }} """ Filepath: "\(Input.name)-done.txt" }// useful helper debug: { Template: """ {{ yaml . }} """ Filepath: "debug.yaml" }}// Add the @gen(<name>,<name>,...) to denote usage of a generatorGen: _ @gen(todos)// Construct the generatorGen: #Gen & { Input: { name: "tasks" todos: [ {name: "t1", effort: 1, complete: true}, {name: "t2", effort: 4, complete: true}, {name: "t3", effort: 3, complete: false}, {name: "t4", effort: 2, complete: true}, {name: "t5", effort: 3, complete: false}, ] }}
There are also a number of “hofmod” repos for generating CLIs, APIs, and more.
The separation of hofmod’s from the hof tool means that you can create your own without needing to change our code.
Because they are also Cue modules, you have all the power of Cue and
can import them for reusing or extending your own generators and projects.