Hof - Polyglot Code Generation Framework
Hof is a Polyglot Code Generate Framework.
You write designs in Cue, very similar to yaml or json,
and Hofstadter validates and feeds these into
generators to create directories and files.
These directories and files can be what ever you choose,
and language, technology or combination.
Under the hood, Hofstadter is using a templating engine.
Cuelang is used to provide type safety with incredible expressiveness.
Modules can be used like other languages
to share and import functionality from small to entire applications and systems.
You can write custom code in the generated output while
still modifying design and regenerating code.
Hofstadter manages the diff process to ensure your work is not disrupted.
The Hofstadter Framework provides conventions and a specification
for writing generators. It wraps Cuelang and the templating engine
to enable you to generate code at scale.
Feed directories of data through the same generator,
the same data through multiple generators,
or any combination.
Hofstadter extends the flexibility and safety of Cue to code generation.
Alternate description:
Hof is a framework for creating
code generation libraries, modules, and tools
for any technology, system, process, or platform.
Being built on Cuelang
you get all the type safety and expressiveness
that comes with the language.
Hof extends cuelang to make
code generation a first-class concept,
optimizing for large scale code generation.
Hof also has roots in
declarative programming and DSLs,
where your write your high-level
"designs" in the "language" of a DSL.
The hof tool will read your designs
and use polyglot generators to output the implementation
in one or more computer languages and technologies.
You can modify the code in the output,
update your designs, and regenrate the output.
Hof allows you to work in both sides
of the transformation, using diff3 to
merge the files together.
Concepts in hof
Designs + Generators -> hof => All the things
Designs are written in DSLs using cuelang syntax.
They form the "source-of-truth" for your
application, data validation, or other creation.
Designs are essentially data or configuration
with a predefined structure and their own validation from the DSL.
Generators are specs and a configuration for how to generate files.
The spec creates the contract between the designs and generators, for users and writers.
The generator has input data and uses this to
hof also supports modules, packages, and imports.
Quickly assemble code generators, specs, and common pieces
into advanced, cloud native applications.
Installation
Prebuilt binaries are available in the Releases section.
There are also docker images available.
To install from source:
git clone https://github.com/hofstadter-io/hof
cd hof
git checkout vX.Y.Z
go install
You can also go get
the latest on master branch.
go get -u github.com/hofstadter-io/hof
An example
This example is taken from the test/templates
directory.
You can find the schemas for HofGenerator
and HofGeneratorFile
in the schemas directory.
To run:
# From the hof repo root
cd test/template
# Vendor modules
hof mod vendor cue
# Generate code
hof gen
# Inspect output
cat output/*
Annotated file contents:
// Declare a package
package example
// Import modules, packages, schemas, configuration, anything cuelang
import (
"github.com/hofstadter-io/hof/schema"
)
// Define some input data for our code generation
Input :: {
a: "a"
b: "b"
N: {
x: "x"
y: "y"
}
}
// Define a "HofGenName"
// The prefix is understood by hof and it will run the generator.
// Here, we are configuring an instance of a generator and giving it a name.
HofGenTest: TestGen & {
In: {
Val: A
}
...
}
// Define a generator
TestGen :: schema.HofGenerator & {
Outdir: "output"
// Generator input, this will be merged onto the Out elements
// (each element can define local input whih will be augmented by the generator input)
In: {
Val: _
...
}
// Create a list of files to generate
Out: [
// Some defaults for templates
schema.HofGeneratorFile & {
Template: "Val.a = '{{ .Val.a }}'\n"
Filepath: "\(Outdir)/default.txt"
TemplateConfig: {
TemplateSystem: "golang"
}
},
// Alternate delims
schema.HofGeneratorFile & {
Template: "Val.a = '{% .Val.a %}'\n"
Filepath: "\(Outdir)/altdelim.txt"
TemplateConfig: {
AltDelims: true
LHS2_D: "{%"
RHS2_D: "%}"
LHS3_D: "{%%"
RHS3_D: "%%}"
}
},
// Swap delims, using defaults delims for swap/temp
schema.HofGeneratorFile & {
Template: "Val.a = '{% .Val.a %}' and also this should stay {{ .Hello }}\n"
Filepath: "\(Outdir)/swapdelim.txt"
TemplateConfig: {
AltDelims: true
SwapDelims: true
LHS2_D: "{%"
RHS2_D: "%}"
LHS3_D: "{%%"
RHS3_D: "%%}"
}
},
// TODO Swap delims, using custom delims for swap/temp
// Mustache system
schema.HofGeneratorFile & {
Template: "Val.a = '{{ Val.a }}'\n"
Filepath: "\(Outdir)/mustache.txt"
TemplateConfig: {
TemplateSystem: "raymond"
}
},
// Named things
schema.HofGeneratorFile & {
TemplateName: "named"
Filepath: "\(Outdir)/named-things.txt"
},
// File based
schema.HofGeneratorFile & {
TemplateName: "template-file.txt"
Filepath: "\(Outdir)/template-file.txt"
},
]
// Templates on disk that can be referenced by name
TemplatesDir: "templates/"
// Partials on disk that can be referenced by name
PartialsDir: "partials/"
// Static files on disk to be copied into the outdir
StaticGlobs: ["static/**"]
// Templates in Cue that can be referenced by name
NamedTemplates: {
named: """
named is '{{ .Val.a }}'
"""
}
// Partials in Cue that can be referenced by name
NamedPartials: {
named: """
partial is '{{ .Val.a }}'
"""
}
// Static files in Cue to be copied into the outdir
StaticFiles: {
"static-cue.txt": """
Hello, I am a static file in cue
"""
}
}
Output:
$ tree output
output/
├── altdelim.txt
├── default.txt
├── mustache.txt
├── named-things.txt
├── static-cue.txt
├── static-file.txt
├── swapdelim.txt
└── template-file.txt
$ find output -type f -exec echo "{}" \; -exec cat "{}" \; -exec echo \;
output/swapdelim.txt
Val.a = 'a' and also this should stay {{ .Hello }}
output/named-things.txt
named is 'a'
output/template-file.txt
Hi, I'm a template file on disk 'a'
... and I'm a partial Hi, I'm a partial file on disk 'b'
---
HellWorld
---
output/static-file.txt
Hello, I am a static file from the filesystem
output/mustache.txt
Val.a = 'a'
output/default.txt
Val.a = 'a'
output/static-cue.txt
Hello, I am a static file in cue
output/altdelim.txt
Val.a = 'a'
Modules and Examples
Projects:
- This project uses itself to generate various pieces like the cli structure and the release process.
- MVS is a dependency management system based on
go mod
. It uses the hofmod-cli
generator and is imported into hof
here.
Modules:
- hofmod-model - A module for representing types and their relations. Batteries are being included.
- hofmod-cli - Create CLI infrastructure based on the Golang Cobra library.
- hofmod-releaser - Release code or binaries to GitHub and Docker with minimal configuration. Based on GoReleaser.
- hofmod-config - Cloud native config and secret files using the Golang Viper library and adding dynamic reload in Kubernetes.
- hofmod-rest - Generate Golang REST servers that are ready for production. This makes use of many of the other modules here.
- hofmod-hugo - Create documenation sites with Hugo and Docsy