Schema



Hof has a few schemas for you to use when creating an using generators.

  • hof lets you put schemas in front of code gen because…
  • hof has schemas too, because…
  • we look at them first for an overview, used in several commands
  • we will see details, examples, and … as we go through the code gen sections

You can find their source files on GitHub

Generator

The Generator is the schema for generators. As a generator author, this is the definition you will use to define how an input is combined with templates to produce the output files. As a user, you will supply the input values to a specific geneartor to create code. Hof’s ad-hoc code gen also assembles a generator from the arguments and flags you provide.

hof/schema/gen.#Generator

package gen

import (
	"github.com/hofstadter-io/hof/schema"
	"github.com/hofstadter-io/hof/schema/common"
	"github.com/hofstadter-io/hof/schema/create"
)

// Definition for a generator
Generator: {
	schema.Hof
	#hof: gen: root: true

	// Base directory for the output
	Outdir: string | *"./"

	// Name of the generator, will default to kebab(label) where defined
	Name: common.NameLabel

	// Generator wide input value to templates.
	// Merged with any template or file level In values
	//   File.In will extend or replace any top-level fields here
	In: {...}

	// Should In be added to the input of every output file?
	applyInToAllOut: bool | *true

	// doing this in the schema crushes CUE performance
	//if applyInToAllOut == true {
	//  Out: [...{"In": In}]
	//}

	// TODO, Generator wide cue.Value for writing incomplete values
	Val: _

	// File globs to watch and trigger regen when changed
	WatchFull: [...string] // reloads & regens everything
	WatchFast: [...string] // skips CUE reload, regens everything

	// Enable Diff3
	Diff3: bool | *true

	// Formatting Control
	Formatting: {
		// default for all files, unless overridden in a file
		Disabled: bool | *false

		// Should data files also be formatted?
		// (cue,yaml,json,toml,xml)
		FormatData: bool | *true

		// Map of names to formatter config values.
		//   Supports multiple configurations for a formatter,
		//   particularly useful for prettier.
		// Hof has defaults it will use if none are specified

		// map from file extensions to formatters
		Formatters: [Extension=string]: {
			// Name of the formatter, like 'prettier' or 'black'
			Formatter: string
			// formatter specific configuration
			Config: _
		}
	}

	PreFlow?:  _ // run hof flow beforehand
	PostFlow?: _ // run hof flow afterwards

	// The final list of files for hof to generate
	Out: [...File]

	// Template (top-level) TemplateConfig (globs+config)
	"Templates": [...Templates] | *[Templates & {Globs: ["./templates/**/*"], TrimPrefix: "./templates/"}]

	// Partial (nested) TemplateConfig (globs+config)
	"Partials": [...Templates] | *[Templates & {Globs: ["./partials/**/*"], TrimPrefix: "./partials/"}]

	// Statics are copied directly into the output, bypassing the rendering
	"Statics": [...Statics] | *[Statics & {Globs: ["./statics/**/*"], TrimPrefix: "./statics/"}]

	// The following mirror their non-embedded versions
	// however they have the content as a string in CUE
	// For templates and partials, Name is the path to reference
	EmbeddedTemplates: [name=string]: Template
	EmbeddedPartials: [name=string]:  Template
	// For statics, Name is the path to write the content
	EmbeddedStatics: [name=string]: string

	// For subgenerators so a generator can leverage and design for other hofmods
	Generators: [name=string]: Generator & {Name: name}

	// Embed the creator to get creator fields
	create.Creator

	// This should be set to default to the module name
	//   (i.e. 'string | *"github.com/<org>/<repo>"')
	// Users should not have to set this.
	// 
	// Used for indexing into the cue.mod/pkg directory...
	// until embed is supported, at which point this shouldn't be needed at all
	// only needed when you have example usage in the same module the generator is in
	// set to the empty string ("") as a generator writer who is making an example in the same module
	ModuleName:  string
	PackageName: ModuleName
	ModuleName:  PackageName
	// TODO, hof, can we introspect the generator / example packages and figure this out?

	// print debug info during load & gen
	Debug: bool | *false

	// TODO, consider adding 'Override*' for templates, partials, statics

	// Note, open so you can have any extra fields
	...
}

// deprecated
#Generator:    Generator
#HofGenerator: Generator

Schema on GitHub

User Fields

These are fields that a user of a generator will typically fill in. The following fields are the default suggested user inputs You can decided to ignore these fields and make any set of exposed input fields for your generators.

Name
In

This is the primary input for users and will be used when rendering the templates. (need to check if this is provided as a root context on repeated templates, or if that is set by authors, or is it the default applied when no input is set on a per template basis)

As a generator author, you will likely want to provide a schema and set In: #MySchema. This will make it easier for users to know if they have correctly specified the required input. They are often put in a schemas directory in your generator module.

Outdir

This is the base dir where the generator output will be written.

Other

#Generator was left open so you can specify any other inputs for your users. This can be useful when you want more contextual inputs presented to the user or you want to transform the user input before passing into the template system.

Author Fields

Out

This is the primary field processed by hof. Your generator should fill in this field based on the user input. Each element will have both input and a template specified. This is where the conditional logic for what to generate comes in. More details can be found in the next section.

Templates, Partials, Statics

These are lists of templates, partials, and statics to load from disk, relative to your generator module base directory.

Embedded{Templates,Partials,Statics}

These are inline or “in-cue” templates, partials, and static fils.

Generators

This is where you set sub-generators that your generator builds on. We have used this for

  • Using one generator in another, for example to provide a more advanced CLI for our REST server binary.
  • Building higher level generators, for example an APP which has Client, Server, and Database subgenerators with a single input.
ModuleName

This is the CUE module name of your generator. It is used for indexing into the cue.mod folder to find your templates and partials from disk.

(this will go away once CUE supports the @embed() for this purpose, and likely structural sharing will be needed as well)

File

File is the schema for a generated output file. The generator Out field is a list of these and what hof iterates over and processes.


hof/schema/gen.#File

package gen

// A file which should be generated by hof
File: {

	// The local input data, any struct
	// The Generator.In will be merged here
	//   but will not replace any values set locally
	In?: {...} // for templates

	// input value for data files, always remains a CUE value
	Val?: _ // for datafiles

	// The full path under the output location
	// empty implies don't generate, even though it may end up in the out list
	Filepath?: string

	//
	// One and only one of these next three may be set
	//

	// The template contents
	TemplateContent?: string

	// Path into the loaded templates
	TemplatePath?: string

	// Writes a datafile, bypassing template rendering
	// Supports infering DatafileFormat by matching extensions
	// You only have to set this when hof cannot infer from the file extension
	DatafileFormat?: "cue" | "json" | "yaml" | "xml" | "toml"

	// TODO, we would like to make the above a disjunction (multi-field)
	// but it results in a significant slowdown 50-100% for hof self-gen
	// Most likely need to wait for structural sharing to land in cue

	// CUE settings
	// for data files which need a package or namespace at the beginning
	Package:        string | *""
	Raw:            bool | *false
	Final:          bool | *false
	Concrete:       bool | *true
	Definitions:    bool | *true
	Optional:       bool | *true
	Hidden:         bool | *true
	Attributes:     bool | *true
	Docs:           bool | *true
	InlineImports:  bool | *false
	ErrorsAsValues: bool | *false

	// Alternative Template Delimiters
	Delims:          #TemplateDelims
	TemplateDelims?: Delims

	// Formatting Control
	Formatting?: {
		Disabled?: bool
		// Name of the formatter, like 'prettier' or 'black'
		Formatter: string
		// formatter specific configuration
		Config: _
	}

	// note, how In gets combined may be opaque, and non-CUEish
	// we should think about applying it at the schema level

	// local override if the generator is set the opposite way
	applyGenInToOut: bool | *true

	// Note, intentionally closed to prevent user error when creating GenFiles
}

// deprecated
#File:             File
#HofGeneratorFile: File

Source on GitHub

Author Fields

#File is normally only used by generator authors.

In

The input data used when rendering the template.

Filepath

The full filepath within the outdir to generate.

TemplateContent, TemplatePath

You must specify one or the other. TemplateContent is the listeral content as a string whereas TemplatePath references one of the predefined templates.

TemplateDelims

Only needed when you need alternative delimiters. The default is {{ and }}.

Templates

The template config schemas are the parameters for the different available rendering engines.

hof/schmea/gen.#Template

package gen

#EmptyTemplates: EmptyTemplates
EmptyTemplates: {
	Templates: []
	Partials: []
	Statics: []
	...
}

#SubdirTemplates: SubdirTemplates
SubdirTemplates: {
	#subdir: string | *"."
	Templates: [{
		Globs: ["\(#subdir)/templates/**/*"]
		TrimPrefix: "\(#subdir)/templates/"
	}]
	Partials: [{
		Globs: ["\(#subdir)/partials/**/*"]
		TrimPrefix: "\(#subdir)/partials/"
	}]
	Statics: [{
		Globs: ["\(#subdir)/statics/**/*"]
		TrimPrefix: "\(#subdir)/statics/"
	}]
	...
}

#TemplateSubdirs: TemplateSubdirs
TemplateSubdirs: {
	#subdir: string | *"."
	Templates: [{
		Globs: ["./templates/\(#subdir)/**/*"]
		TrimPrefix: "./templates/\(#subdir)/"
	}]
	Partials: [{
		Globs: ["./partials/\(#subdir)/**/*"]
		TrimPrefix: "./partials/\(#subdir)/"
	}]
	Statics: [{
		Globs: ["./statics/\(#subdir)/**/*"]
		TrimPrefix: "./statics/\(#subdir)/"
	}]
	...
}

// #Statics is used for static files copied over, bypassing the template engine
#Statics: Statics
Statics: {
	Globs: [...string]
	TrimPrefix?: string
	OutPrefix?:  string
}

// #Template is used for embedded or named templates or partials
#Template: Template
Template: {
	Content: string
	Delims?: TemplateDelims
}

// #Templates is used for templates or partials loaded from a filesystem
#Templates: Templates
Templates: {
	Globs: [...string]
	TrimPrefix?: string
	Delims?:     TemplateDelims

	// apply delims to a subset of templates, by glob
	DelimGlobs: [glob=string]: TemplateDelims
}

#TemplateDelims: TemplateDelims
TemplateDelims: {
	LHS: string | *""
	RHS: string | *""
}

Source on GitHub

#Statics

Represents a list of Globs to copy into the output, bypassing the template rendering engine. You can specify TrimPrefix to remove leading directories and OutPrefix to write to subdirectories relative to the output dir.

#Template

Represents an inline Template and content.

#Templates

Represents Globs to load into the template system. Used for both templates and partials. Use TrimPrefix to remove leading directories and Delims to specify alternative template delimiters for all Globs.

#Delims

The schema for template delimiters when you need to override the defaults ({{ and }}).