Documentation
¶
Overview ¶
Package repo compiles a set of Go text templates into an immutable repository.
A code generator ships a default set of templates and lets its users override some of them. This package holds that set. It reads assets from an io/fs.FS, a directory or a byte slice, parses them into one namespace so every template can call every other, and returns them ready to execute from Repository.Get.
Usage ¶
A repository is built once, from sources declared as options:
repository, err := repo.New(
repo.FromFS(assets, ""),
repo.WithFuncMap(funcs),
)
if err != nil {
return err
}
tpl, err := repository.Get("validationPrimitive")
if err != nil {
return err
}
err = tpl.Execute(w, data)
A repository is immutable once New returns. To change the set, derive a new repository with Clone:
patched, err := repo.Clone(repository, repo.FromTemplate("validation/primitive", mine))
Clone re-parses every asset, so a template that calls the overridden one calls the new definition. The two repositories share no state.
Every error this package reports matches ErrTemplateRepo and wraps its cause, so a caller may also match a parse error or an io/fs error with errors.Is and errors.As.
Naming ¶
A template is known by three strings: the asset path it was read from, the address it was declared at, and the name it answers to. The address is the asset path with the extension trimmed, slash-separated and otherwise untouched. The name is that address recased, "/" counting as a word boundary like any other.
validation/primitive.gotmpl -> address validation/primitive, name validationPrimitive server/parameter.gotmpl -> address server/parameter, name serverParameter model.gotmpl -> address model, name model
Repository.Get takes a name and Repository.Lookup takes an address. Repository.NameOf maps an address to a name, Repository.AddressOf maps it back, and Repository.Addresses iterates over both. Call TemplateName to compute a name before any repository exists: WithRoots takes names, so a build scoped to roots needs them before it runs.
A "define" statement declares a further template, addressed under the asset that holds it:
server/fred.gotmpl holding {{define "inner-macro"}}
-> address server/fred/inner-macro, name serverFredInnerMacro
Paths are slash-separated whatever the platform, and a backslash reads as a separator too, so "server\parameter" typed on Windows reaches the same template as everywhere else.
When two assets of a single source declare one name, New returns an error: nothing settles which of the two is read last. Across sources, the last declaration wins.
Overriding ¶
Sources are read in the order they are declared, and the last declaration of a name wins. There is no other precedence rule: a source cannot mark a template as final.
Stack whole sets of templates on the file system, not in the repository. Merge them into one io/fs.FS with github.com/go-openapi/swag/fileutils.NewOverlayFS, then pass the result to FromFS.
SkipDirectories attaches to one source, not to the build. Skipping "internal" in your own assets leaves an "internal" directory in a set someone else brings fully readable.
It matches a directory by its own name, the last segment of its path, at any depth. It matches neither a path nor a template name, since it selects what a source reads before anything has a name.
Repository.Audit lists every name that two or more assets declared, with the definition that stands and the ones it replaced. Run it where a build may fail, to catch a contrib set that shadows a template by accident before it ships. It returns a github.com/go-openapi/codegen/templates-repo/reports.Audit, which also lists the unused, empty and dynamic templates.
Scoping ¶
A generator ships every template it may ever need, and one run uses a fraction of them. WithRoots keeps the named templates and everything they call, and prunes the rest:
client, err := repo.Clone(repository, repo.WithRoots("clientClient", "model"))
A root is a name, the identity Repository.Get takes, and never the address a template was declared at. WithRoots accepts names alone, unlike Repository.Lookup, so convert an address with Repository.NameOf first. Naming an address returns an error, and does not build an empty repository.
A pruned template is gone from the repository: Repository.Names does not list it, Repository.Documentation does not describe it, and Repository.Coverage does not count it. Every asset is still read and parsed, because a template only announces its name once parsed. The assets are retained whole, so a later Clone with WithExtraRoots widens the scope again.
A scope only has to bind the functions its own templates call, so a pruned template may call a function no func map provides. Pass WithFuncMap the maps of the parts the roots keep, and leave out the maps of the parts they drop.
Repository.Roots returns the current scope, and is empty when the repository kept everything it read.
Assembling ¶
A repository may itself be a source. FromRepository reads what one holds and mounts it at a chosen point, so two sets written independently are assembled without either knowing about the other:
templates, err := repo.New(
repo.FromDir("./scaffolding", ""),
repo.FromRepository(modelTemplates, "models"),
repo.FromRepository(serverTemplates, "server"),
)
Mounting moves every address under the mount point, and the references between templates move with them, so a set that resolved on its own resolves the same mounted. Two sets that each define a macro called "header" no longer collide, because their addresses now differ.
So a package that ships templates should export sources, not a repository. A scaffolding calling into the parts it assembles cannot build on its own, and those parts have to be sources of the same build:
// what the package exports, knowing nothing of where it lands
func Sources(opts ...repo.SourceOption) repo.Option {
return repo.Sources(
repo.FromFS(templates, "", opts...),
repo.FromFS(filepaths, "paths", opts...),
)
}
A template calling into another set names the address that set was mounted at, so a package whose templates do that expects a particular mount point. Document it alongside the templates the package exports and the data they are executed on. Mount a set that calls into nothing anywhere you like.
Rebase, Merge and Coalesce do the same to repositories that are already built. Rebase moves every address under a base. Merge lets the last repository to declare an address win, and Coalesce lets the first. All three need each part to build on its own.
Documentation and audit ¶
Templates are part of the interface a generator exposes, so a repository can document them. Repository.Documentation reads the assets again, comments included, and reports:
- the comments documenting each template, attached as a Go author expects, a comment group placed immediately before a "define" statement documenting that template
- the data paths each template reads, and which of them it reaches through the root
- the functions it calls, and the templates it calls, with the data passed to each
Repository.Dump renders that model as markdown:
err = repository.Dump(w)
Nothing is analysed until you call these methods, so a program that only executes templates never pays for it. The output is ordered throughout, so the same templates produce the same document every time. Commit that document and check it in CI.
The types these methods return live in a package of their own, github.com/go-openapi/codegen/templates-repo/reports. Describing a repository takes ten types and executing its templates takes none of them, so a program that only renders imports neither the documentation model nor the audit report. Call reports.Dump directly to render one document in several formats.
Coverage ¶
WithCoverage instruments a repository to count the lines of its templates that execute. Set it when the repository is built: the counters sit in the trees that run, so they go in at parse time. Clone carries the setting over, so a plain repository clones into an instrumented one:
counting, err := repo.Clone(repository, repo.WithCoverage("example.com/gen/templates"))
The prefix is prepended to the path of every asset in the profile. go tool cover resolves the file a profile names by asking go list, so every path has to read as an import path of a package that exists. That is why the prefix is required.
Repository.Coverage returns a profile in the format go test writes, which go tool cover renders as html. A line that never ran is recorded at zero, and is not left out. A line holding nothing but a define, an end or an else carries no counter, so it greys out the way a Go declaration does.
Concurrency ¶
A repository is immutable and safe for concurrent use, as are the Template values it returns. Clone only reads the repository it derives from.
The coverage counters are the one part that changes after a build. They are atomic, so a generator rendering templates in parallel needs no lock.
Example (Vocabulary) ¶
A template is known by three strings, and only the last one executes it.
package main
import (
"fmt"
repo "github.com/go-openapi/codegen/templates-repo"
)
// oneAsset declares two templates: the asset itself, and the "define" it holds.
func oneAsset() repo.Option {
return repo.FromTemplate("server/parameter.gotmpl", []byte(
`{{ define "bind" }}bound{{ end }}param[{{ template "bind" }}]`,
))
}
func main() {
repository, err := repo.New(oneAsset())
if err != nil {
panic(err)
}
for address, name := range repository.Addresses() {
asset, _ := repository.AssetOf(name)
fmt.Printf("asset %-24s address %-24s name %s\n", asset, address, name)
}
}
Output: asset server/parameter.gotmpl address server/parameter name serverParameter asset server/parameter.gotmpl address server/parameter/bind name serverParameterBind
Index ¶
- Constants
- func TemplateName(assetPath string, extensions ...string) string
- type MissingKeyBehavior
- type Option
- func FromDir(dir, mountPoint string, opts ...SourceOption) Option
- func FromFS(fsys fs.FS, mountPoint string, opts ...SourceOption) Option
- func FromRepository(source *Repository, mountPoint string, opts ...SourceOption) Option
- func FromTemplate(name string, content []byte, opts ...SourceOption) Option
- func Sources(opts ...Option) Option
- func WithCoverage(prefix string) Option
- func WithExtensions(extensions ...string) Option
- func WithExtraRoots(names ...string) Option
- func WithFuncMap(funcs template.FuncMap) Option
- func WithMissingKey(onMissingKey MissingKeyBehavior) Option
- func WithRoots(names ...string) Option
- type Repository
- func Clone(source *Repository, opts ...Option) (*Repository, error)
- func Coalesce(source *Repository, coalesced ...*Repository) (*Repository, error)
- func Merge(source *Repository, merged ...*Repository) (*Repository, error)
- func New(opts ...Option) (*Repository, error)
- func Rebase(source *Repository, base string) (*Repository, error)
- func (r *Repository) AddressOf(name string) (string, bool)
- func (r *Repository) Addresses() iter.Seq2[string, string]
- func (r *Repository) AssetOf(name string) (string, bool)
- func (r *Repository) Audit() (reports.Audit, error)
- func (r *Repository) Coverage() *cover.Profile
- func (r *Repository) Documentation() (reports.Documentation, error)
- func (r *Repository) Dump(w io.Writer, opts ...reports.DumpOption) error
- func (r *Repository) Get(name string) (Template, error)
- func (r *Repository) Has(name string) bool
- func (r *Repository) Lookup(address string) (Template, error)
- func (r *Repository) MustGet(name string) Template
- func (r *Repository) MustLookup(address string) Template
- func (r *Repository) NameOf(address string) string
- func (r *Repository) Names() iter.Seq[string]
- func (r *Repository) Roots() []string
- type SourceOption
- type Template
Examples ¶
Constants ¶
const DefaultExtension = ".gotmpl"
DefaultExtension is the file extension a repository recognizes as a template when the caller declares no other with WithExtensions.
const ErrTemplateRepo repoError = "template repository"
ErrTemplateRepo is matched by every error this package reports.
Errors wrap the cause as well, so a caller may match on a parse error or on an io/fs error with errors.Is and errors.As all the same.
Variables ¶
This section is empty.
Functions ¶
func TemplateName ¶
TemplateName returns the name a repository gives an asset at a path.
Call it before a repository exists, to name the roots a build is scoped to or to pick the sources to declare. Repository.NameOf answers the same question once the repository is built.
The extensions are those the repository recognizes, DefaultExtension when none is given.
Example:
repo.TemplateName("validation/primitive.gotmpl") -> validationPrimitive
Types ¶
type MissingKeyBehavior ¶ added in v0.0.2
type MissingKeyBehavior string
MissingKeyBehavior exposes the options provided by template.Template.
const ( MissingKeyBehaviorNone MissingKeyBehavior = "" MissingKeyBehaviorDefault MissingKeyBehavior = "missingkey=default" MissingKeyBehaviorZero MissingKeyBehavior = "missingkey=zero" MissingKeyBehaviorError MissingKeyBehavior = "missingkey=error" )
type Option ¶
type Option func(options) options
Option configures a Repository built with New or derived with Clone.
New and Clone report an option that rejected its arguments, and the option constructor does not, so a repository never silently drops a setting.
Usage ¶
Options come in two kinds. Sources declare where templates are read from, and apply in order: FromFS, FromDir and FromTemplate. Settings shape how every source is read, whatever the order: WithFuncMap, WithExtensions, WithRoots, WithExtraRoots and WithCoverage. Use a SourceOption to configure one source where it is declared.
func FromDir ¶
func FromDir(dir, mountPoint string, opts ...SourceOption) Option
FromDir reads every supported asset of a local directory, and mounts them at mountPoint.
dir is a path in the os file system, and the assets are named relative to it. FromDir is the shorthand for FromFS over an os.DirFS, and it reports an error when dir is not a readable directory.
The directory is read once, when the repository is built. Editing a template on disk afterwards has no effect until a repository is built again.
func FromFS ¶
func FromFS(fsys fs.FS, mountPoint string, opts ...SourceOption) Option
FromFS reads every supported asset of fsys, and mounts them at mountPoint.
fsys is read from its root. Use io/fs.Sub to serve a subtree. An empty mountPoint, or ".", mounts the assets at the top of the template tree, which is the usual case.
FromFS does not override. To make one set of files take precedence over another, stack them into a single io/fs.FS and pass the result.
Example:
// the assets of an embed.FS, patched by an alternate set living elsewhere in the same tree repo.New(repo.FromFS(fileutils.NewOverlayFS( fileutils.MustSub(assets, "templates"), fileutils.MustSub(assets, "templates/contrib/mine"), ), ""))
func FromRepository ¶
func FromRepository(source *Repository, mountPoint string, opts ...SourceOption) Option
FromRepository reads the templates another repository holds, and mounts them at mountPoint.
Use it to build a set out of parts in one pass. New only builds a repository when every template it refers to is there, so a scaffolding calling into the parts it assembles cannot build on its own. Declare those parts as sources of the same build, and each one is written apart and resolved together.
The templates are read as they were declared, and mounting them moves their addresses the way Rebase does. What they refer to moves with them, so a set that resolved on its own resolves the same mounted.
FromRepository reads no source of source again. It carries over the content source retained when it was built. source is left untouched.
Example:
// a scaffolding of one's own, with the sets it calls into
repo.New(
repo.FromDir("./scaffolding", ""),
repo.FromRepository(modelTemplates, "models"),
repo.FromRepository(serverTemplates, "server"),
)
func FromTemplate ¶
func FromTemplate(name string, content []byte, opts ...SourceOption) Option
FromTemplate registers a single template held in memory, at the address given.
The address locates the template exactly as written, so it may hold directories, and it is never mangled. To override a template declared elsewhere, name the address it was declared at. The name it answers to is derived from the address like any other.
FromFS and FromDir only read an asset whose name carries a recognized extension. FromTemplate registers content whatever the name.
Declare a template no file holds this way, such as one a configuration provides. For several of them, build an in-memory io/fs.FS and pass it to FromFS, so every override goes through one mechanism.
The content is retained, not copied.
func Sources ¶
Sources bundles several options into one, so that a package exports everything its templates need as a single option.
A set of templates is rarely one source: the templates themselves, the ones that place each generated section, and whatever else a package ships. Export a single Option and the caller assembling it needs to know neither how many sources there are nor what order they go in.
Example:
// the package publishes this, and nothing of what is inside it
func Sources(opts ...repo.SourceOption) repo.Option {
return repo.Sources(
repo.FromFS(templates, "", opts...),
repo.FromFS(filepaths, "paths", opts...),
)
}
func WithCoverage ¶
WithCoverage counts the lines of the templates that run.
The counters sit in the trees that execute, so a repository is instrumented when it is built or not at all. Clone carries the setting over, and a clone of a plain repository asking for it yields an instrumented twin.
prefix is prepended to the path of every asset in the profile Repository.Coverage writes. go tool cover resolves the file a profile names by asking go list, so every path has to read as an import path of a package that exists. That is why prefix is required.
Example:
repo.WithCoverage("github.com/go-swagger/go-swagger/generator/templates")
func WithExtensions ¶
WithExtensions sets the file extensions recognized as templates when reading a file system.
The default is ".gotmpl" alone. FromFS and FromDir ignore an asset whose name ends with none of them; FromTemplate registers its content whatever the name.
The extension is trimmed from the asset path before its name is derived, so "validation/primitive.gotmpl" is named validationPrimitive.
func WithExtraRoots ¶
WithExtraRoots widens the scope of a repository with roots of its own.
It adds to whatever WithRoots settled, and changes nothing on a repository that already keeps every template it reads. It takes names, as WithRoots does.
Use it to add a template to a repository you did not build. WithRoots is wrong either way: on an unscoped repository it prunes everything else away, and on a scoped one it discards the scope already set.
Example:
// one more template, reachable whether or not the repository is scoped
mine, err := repo.Clone(repository, repo.FromTemplate("mine", body), repo.WithExtraRoots("mine"))
func WithFuncMap ¶
WithFuncMap adds functions that templates may call.
By default a repository binds no function, so templates only have the builtins of text/template. The map is copied, and repeated calls merge, the last definition of a name winning.
A repository binds its functions when it parses its templates, so nothing can change them afterwards. To add a function to a repository already built, pass this option to Clone: the clone re-parses every template, so the new function reaches all of them.
A repository scoped by WithRoots only needs the functions its own templates call. Bind the maps of the parts it is scoped to, and leave out the rest.
func WithMissingKey ¶ added in v0.0.2
func WithMissingKey(onMissingKey MissingKeyBehavior) Option
WithMissingKey instructs templates to be built with the options provided by template.Template:
- MissingKeyBehaviorDefault: execution continues on missing keys on maps, the value is set to "<no value>".
- MissingKeyBehaviorZero: execution continues with the value set to the zero value of the element type.
- MissingKeyBehaviorError: execution stops with an error
func WithRoots ¶
WithRoots keeps the templates named, and the templates they reach, and prunes the rest.
A generator ships every template it may ever need, and one run uses a fraction of them: a client run and a server run execute different sets. Name the roots of a run and the repository holds what that run executes, so a template set that is incomplete for the other runs builds all the same.
Roots are names, the identity Repository.Get takes, never the address a template was declared at. Use Repository.NameOf to convert an address you already hold.
A root no source declares is an error. Nothing else would report it, since a scope naming a template that does not exist builds a repository that generates nothing.
Everything is still read and parsed, since a template only names itself once parsed, so a source that does not parse is an error whether it is pruned away or not.
The func map is the exception. A pruned template may call a function nothing binds, so a scope only has to bind what its own templates call. The check runs on whole assets, so a pruned "define" still owes its functions when the repository keeps another template of the same asset.
Pruning decides only which templates the repository holds, and therefore what Repository.Names lists, what its documentation covers, and what its coverage counts.
WithRoots replaces the scope. A Clone naming roots of its own is scoped to those alone, whatever the repository it derives from was scoped to. Use WithExtraRoots to widen a scope instead. A repository with no root at all keeps every template it reads, which is the default.
Example:
// the templates a client generation executes, and nothing else
client, err := repo.Clone(repository, repo.WithRoots("clientClient", "clientParameter", "model"))
Example ¶
Roots are names, the identity Get takes, and never addresses.
package main
import (
"fmt"
repo "github.com/go-openapi/codegen/templates-repo"
)
// oneAsset declares two templates: the asset itself, and the "define" it holds.
func oneAsset() repo.Option {
return repo.FromTemplate("server/parameter.gotmpl", []byte(
`{{ define "bind" }}bound{{ end }}param[{{ template "bind" }}]`,
))
}
func main() {
repository, err := repo.New(oneAsset(), repo.WithRoots("serverParameter"))
if err != nil {
panic(err)
}
fmt.Println(repository.Roots())
// an address is not a name, and saying so is an error rather than an empty repository
_, err = repo.New(oneAsset(), repo.WithRoots("server/parameter"))
fmt.Println(err != nil)
// a caller holding addresses converts them first
naming, err := repo.New(oneAsset())
if err != nil {
panic(err)
}
scoped, err := repo.Clone(naming, repo.WithRoots(naming.NameOf("server/parameter")))
if err != nil {
panic(err)
}
fmt.Println(scoped.Roots())
}
Output: [serverParameter] true [serverParameter]
type Repository ¶
type Repository struct {
// contains filtered or unexported fields
}
Repository is a set of compiled templates, resolved against one another and sealed.
New builds one from the sources given as options and Clone derives one from another. There is no other constructor, and no method changes a repository once it is built.
Usage ¶
A repository reads its sources once, when it is built, and retains their content. Everything else follows from that:
- templates share a single namespace, so any of them may refer to any other by name
- Clone re-reads nothing and re-parses everything, so a template added by a clone is seen by the templates that were already there
- the file system a repository was built from is not retained, and is never read again
A clone therefore costs a full rebuild. Use it for the settings of a program, decided once, and not once per operation.
Concurrency ¶
A repository is immutable, holds no lock, and is safe for concurrent use. Clone only reads its source, so it is safe to clone a repository other goroutines are executing.
func Clone ¶
func Clone(source *Repository, opts ...Option) (*Repository, error)
Clone builds a repository from the assets and settings of another one, with opts applied on top.
Clone reads no source of source again. It carries over the content retained when source was built and appends whatever opts declares. Everything is parsed and resolved afresh, so a template opts overrides reaches every template referring to it, and the two repositories share nothing.
source is left untouched, and other goroutines may execute it while Clone runs. A nil source returns an error.
Example:
// the same templates, with one of them replaced
patched, err := repo.Clone(repository, repo.FromTemplate("model", myModel))
func Coalesce ¶
func Coalesce(source *Repository, coalesced ...*Repository) (*Repository, error)
Coalesce derives a repository holding the templates of several, the first to declare an address winning.
It is Merge the other way round: a later repository declaring an address an earlier one already holds is dropped, and does not take its place. Func maps coalesce the same way, through github.com/go-openapi/codegen/funcmaps.Coalesce, which also leaves the builtins alone.
Use it when source holds the definitions that must win, and the later repositories only fill in the addresses it does not declare.
func Merge ¶
func Merge(source *Repository, merged ...*Repository) (*Repository, error)
Merge derives a repository holding the templates of several, the last to declare an address winning.
You merge in order to override, so a template declared twice is not an error. Repository.Audit reports which definition stands and which it replaced. Func maps merge the same way, through github.com/go-openapi/codegen/funcmaps.Merge.
Call Rebase first when the sets were written apart: it moves their addresses under a base of their own, so two of them never declare the same one.
Example:
templates, err := repo.Merge(scaffolding, must(repo.Rebase(modelTemplates, "models")), must(repo.Rebase(serverTemplates, "server")), )
func New ¶
func New(opts ...Option) (*Repository, error)
New builds a repository from the sources and settings given as options.
Sources are read in the order they are declared, and a template declared by several of them comes from the last one. New returns an error for an unreadable source, a template that fails to parse, a template referring to one no source declares, and an override that would silently not replace what it overrides.
Declaring no source at all builds an empty repository, and is not an error.
Example:
repository, err := repo.New(
repo.FromFS(assets, ""),
repo.FromDir("./mytemplates", ""),
repo.WithFuncMap(funcs),
)
func Rebase ¶
func Rebase(source *Repository, base string) (*Repository, error)
Rebase derives a repository holding the templates of another one, addressed under a base.
Every address moves under base, so a repository built from server/parameter.gotmpl rebased under "v2" holds v2/server/parameter, answering to v2ServerParameter.
What the templates refer to moves with them. A reference resolves outward from where it was written, and everything it could reach sits one level further in, so a set that resolved on its own resolves the same once rebased. Use this to assemble a repository out of parts written apart.
source is left untouched.
Example:
models, err := repo.Rebase(modelTemplates, "models")
func (*Repository) AddressOf ¶
func (r *Repository) AddressOf(name string) (string, bool)
AddressOf returns the address a name is declared at, and whether it is declared at all.
It reverses Repository.NameOf.
Example ¶
AddressOf goes back, and reports whether the name is declared at all.
package main
import (
"fmt"
repo "github.com/go-openapi/codegen/templates-repo"
)
// oneAsset declares two templates: the asset itself, and the "define" it holds.
func oneAsset() repo.Option {
return repo.FromTemplate("server/parameter.gotmpl", []byte(
`{{ define "bind" }}bound{{ end }}param[{{ template "bind" }}]`,
))
}
func main() {
repository, err := repo.New(oneAsset())
if err != nil {
panic(err)
}
fmt.Println(repository.AddressOf("serverParameterBind"))
fmt.Println(repository.AddressOf("nowhereAtAll"))
}
Output: server/parameter/bind true false
func (*Repository) Addresses ¶
func (r *Repository) Addresses() iter.Seq2[string, string]
Addresses iterates over what this repository declares, address first, name second, ordered by name.
func (*Repository) AssetOf ¶
func (r *Repository) AssetOf(name string) (string, bool)
AssetOf returns the path of the asset that declares a name, and whether it is declared at all.
The path is the asset's, once mounted, and the name was derived from it.
Example ¶
AssetOf names the file a template was read from, extension and all.
package main
import (
"fmt"
repo "github.com/go-openapi/codegen/templates-repo"
)
// oneAsset declares two templates: the asset itself, and the "define" it holds.
func oneAsset() repo.Option {
return repo.FromTemplate("server/parameter.gotmpl", []byte(
`{{ define "bind" }}bound{{ end }}param[{{ template "bind" }}]`,
))
}
func main() {
repository, err := repo.New(oneAsset())
if err != nil {
panic(err)
}
fmt.Println(repository.AssetOf("serverParameterBind"))
}
Output: server/parameter.gotmpl true
func (*Repository) Audit ¶
func (r *Repository) Audit() (reports.Audit, error)
Audit lists the overridden, unused, empty and dynamic templates of a repository.
It reads the assets again, as Repository.Documentation does, so nothing is computed until you call it. Run it where a build may fail: it finds a contrib set that replaced a template by accident, and a macro no other template calls any more.
Example:
report, err := repository.Audit()
if err != nil {
return err
}
for _, override := range report.Overridden {
log.Printf("%s comes from %s, replacing %v", override.Name, override.Standing, override.Replaced)
}
func (*Repository) Coverage ¶
func (r *Repository) Coverage() *cover.Profile
Coverage returns the counters of the templates, or nil when the repository was not built with WithCoverage.
The templates of a repository never change; their counters do. Each counter records how many times a run reached one line of one template.
func (*Repository) Documentation ¶
func (r *Repository) Documentation() (reports.Documentation, error)
Documentation returns the structure of the repository and the documentation of its templates.
A parse tree carries no comment, and records no data path the template reads. The repository retains its sources, so Documentation parses them again to recover both. Nothing is computed until you call it.
The result is freshly built and shared with nobody. Hold it, walk it, or render it in whatever format.
func (*Repository) Dump ¶
func (r *Repository) Dump(w io.Writer, opts ...reports.DumpOption) error
Dump writes the documentation of the repository, as markdown by default.
It calls Repository.Documentation and passes the result to reports.Dump. Call reports.Dump yourself to render one document in several formats.
func (*Repository) Get ¶
func (r *Repository) Get(name string) (Template, error)
Get returns the template registered under a name.
A template is known by three strings, and this takes the third:
asset path server/parameter.gotmpl the file it was read from address server/parameter where it was declared, never recased name serverParameter what it answers to, and what Get takes
Get returns an error when no source declares that name. The Template it returns resolves the templates it refers to in this repository.
Example ¶
Get takes a name.
package main
import (
"os"
repo "github.com/go-openapi/codegen/templates-repo"
)
// oneAsset declares two templates: the asset itself, and the "define" it holds.
func oneAsset() repo.Option {
return repo.FromTemplate("server/parameter.gotmpl", []byte(
`{{ define "bind" }}bound{{ end }}param[{{ template "bind" }}]`,
))
}
func main() {
repository, err := repo.New(oneAsset())
if err != nil {
panic(err)
}
tpl, err := repository.Get("serverParameter")
if err != nil {
panic(err)
}
_ = tpl.Execute(os.Stdout, nil)
}
Output: param[bound]
func (*Repository) Has ¶
func (r *Repository) Has(name string) bool
Has reports whether a name is declared in this repository.
func (*Repository) Lookup ¶
func (r *Repository) Lookup(address string) (Template, error)
Lookup returns the template declared at an address.
An address is the path a template was declared at, slash-separated and never recased. Lookup takes one and Repository.Get takes a name. Use whichever you already hold.
Leave the extension on or trim it: the asset path a template was read from addresses it too.
Example:
tpl, err := repository.Lookup("server/parameter")
Example ¶
Lookup takes an address, and reaches the same template as Get does by name.
package main
import (
"os"
repo "github.com/go-openapi/codegen/templates-repo"
)
// oneAsset declares two templates: the asset itself, and the "define" it holds.
func oneAsset() repo.Option {
return repo.FromTemplate("server/parameter.gotmpl", []byte(
`{{ define "bind" }}bound{{ end }}param[{{ template "bind" }}]`,
))
}
func main() {
repository, err := repo.New(oneAsset())
if err != nil {
panic(err)
}
tpl, err := repository.Lookup("server/parameter/bind")
if err != nil {
panic(err)
}
_ = tpl.Execute(os.Stdout, nil)
}
Output: bound
func (*Repository) MustGet ¶
func (r *Repository) MustGet(name string) Template
MustGet returns the template registered under a name, and panics when there is none.
Use it for a name hardcoded in the program, and Repository.Get for one a caller supplies.
func (*Repository) MustLookup ¶
func (r *Repository) MustLookup(address string) Template
MustLookup returns the template declared at an address, and panics when there is none.
Use it for an address hardcoded in the program, and Repository.Lookup for one a caller supplies.
func (*Repository) NameOf ¶
func (r *Repository) NameOf(address string) string
NameOf returns the name a template declared at an address answers to.
NameOf recases and does not look anything up, so an address nothing declares still yields the name it would have. Call Repository.Has to find out whether that name is declared. The asset path addresses a template too, since the extension is trimmed either way: NameOf("server/parameter.gotmpl") and NameOf("server/parameter") both return serverParameter.
It is a method because WithExtensions settles which extensions are trimmed. TemplateName answers the same question before a repository exists.
It reverses Repository.AddressOf and it is idempotent on the names it produces, so you may hand one back to it: NameOf("serverParameter") returns serverParameter. That does not extend to a name no address produces, since an inner "define" is addressed under the asset holding it.
Example ¶
NameOf recases an address into the name it answers to. It computes, and does not look up: an address nothing declares still yields the name it would have.
package main
import (
"fmt"
repo "github.com/go-openapi/codegen/templates-repo"
)
// oneAsset declares two templates: the asset itself, and the "define" it holds.
func oneAsset() repo.Option {
return repo.FromTemplate("server/parameter.gotmpl", []byte(
`{{ define "bind" }}bound{{ end }}param[{{ template "bind" }}]`,
))
}
func main() {
repository, err := repo.New(oneAsset())
if err != nil {
panic(err)
}
fmt.Println(repository.NameOf("server/parameter/bind"))
fmt.Println(repository.NameOf("server/parameter.gotmpl"))
fmt.Println(repository.NameOf("nowhere/at/all"), repository.Has("nowhereAtAll"))
}
Output: serverParameterBind serverParameter nowhereAtAll false
Example (Scoping) ¶
Scoping takes names alone, where Lookup takes either identity.
package main
import (
"fmt"
repo "github.com/go-openapi/codegen/templates-repo"
)
// oneAsset declares two templates: the asset itself, and the "define" it holds.
func oneAsset() repo.Option {
return repo.FromTemplate("server/parameter.gotmpl", []byte(
`{{ define "bind" }}bound{{ end }}param[{{ template "bind" }}]`,
))
}
func main() {
repository, err := repo.New(oneAsset())
if err != nil {
panic(err)
}
// a caller holding an address converts it, then scopes
scoped, err := repo.Clone(repository, repo.WithRoots(repository.NameOf("server/parameter")))
if err != nil {
panic(err)
}
for name := range scoped.Names() {
fmt.Println(name)
}
}
Output: serverParameter serverParameterBind
func (*Repository) Names ¶
func (r *Repository) Names() iter.Seq[string]
Names iterates over the names declared in this repository, in lexical order.
Example ¶
Names identify the templates a repository holds, and Get takes one.
package main
import (
"fmt"
repo "github.com/go-openapi/codegen/templates-repo"
)
// oneAsset declares two templates: the asset itself, and the "define" it holds.
func oneAsset() repo.Option {
return repo.FromTemplate("server/parameter.gotmpl", []byte(
`{{ define "bind" }}bound{{ end }}param[{{ template "bind" }}]`,
))
}
func main() {
repository, err := repo.New(oneAsset())
if err != nil {
panic(err)
}
for name := range repository.Names() {
fmt.Println(name)
}
}
Output: serverParameter serverParameterBind
func (*Repository) Roots ¶
func (r *Repository) Roots() []string
Roots returns the names this repository is scoped to, in the order they were given to WithRoots.
A build without WithRoots keeps every template it read, and Roots then returns an empty slice. Roots reports the scope and nothing acts on it. To add a template to a repository you did not build, use WithExtraRoots: it widens a scope when there is one and changes nothing when there is not.
type SourceOption ¶
type SourceOption func(sourceOptions) sourceOptions
SourceOption configures how one source is read.
A SourceOption describes the file system being walked, not the repository. SkipDirectories on one source leaves a directory of the same name fully readable in a template set someone else brings.
func Rebased ¶
func Rebased(base string) SourceOption
Rebased mounts a source under a base, on top of wherever it already mounts.
Use it to publish templates without knowing where they land. The package exports its sources, and whoever assembles them picks the mount point of each.
Example:
// the package publishes this
func Sources(opts ...repo.SourceOption) repo.Option {
return repo.FromFS(templates, "", opts...)
}
// and whoever assembles decides where it lands
repo.New(
genmodels.Sources(repo.Rebased("models")),
genclient.Sources(repo.Rebased("client")),
)
func SkipDirectories ¶
func SkipDirectories(names ...string) SourceOption
SkipDirectories walks past the directories named, wherever they are in the tree read.
Directories are matched on their name, at any depth. Nothing is skipped by default. Put it on the source that ships the alternate sets, then declare the set you want as a further source.
Example:
// the assets shipped, leaving the alternate sets to be stacked explicitly
repo.FromFS(assets, "", repo.SkipDirectories("contrib"))
Example ¶
SkipDirectories matches a directory's own name, at any depth, and never a path or a template name.
package main
import (
"fmt"
"slices"
"testing/fstest"
repo "github.com/go-openapi/codegen/templates-repo"
)
func main() {
assets := fstest.MapFS{
"model.gotmpl": {Data: []byte("model")},
"contrib/mine/model.gotmpl": {Data: []byte("mine")},
"server/legacy/contrib/x.gotmpl": {Data: []byte("legacy")},
}
skipped, err := repo.New(repo.FromFS(assets, "", repo.SkipDirectories("contrib")))
if err != nil {
panic(err)
}
fmt.Println(slices.Collect(skipped.Names()))
// a path matches no directory name, so nothing is skipped
byPath, err := repo.New(repo.FromFS(assets, "", repo.SkipDirectories("server/legacy")))
if err != nil {
panic(err)
}
fmt.Println(slices.Collect(byPath.Names()))
}
Output: [model] [contribMineModel model serverLegacyContribX]
type Template ¶
type Template struct {
// contains filtered or unexported fields
}
Template is a compiled template, resolved against every other template of its Repository.
Repository.Get and Repository.Lookup return a Template, and nothing else constructs one. The zero value reports an empty name and fails to execute.
A Template exposes Template.Execute and Template.Name, and no more. Calling Parse, Funcs or Option on the underlying text/template.Template would change what the repository serves to every other holder of it. There is no ExecuteTemplate either: use Repository.Get to resolve a name.
Concurrency ¶
A Template is immutable and may be executed concurrently. Concurrent executions sharing a single io.Writer interleave their output, as they do with text/template.Template.
func (Template) Execute ¶
Execute applies the template to data and writes the result to w.
A template that refers to another one resolves it in the repository it comes from. The zero Template returns an error wrapping ErrTemplateRepo.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
cover
Package cover measures which lines of a template a program reaches when it runs.
|
Package cover measures which lines of a template a program reaches when it runs. |
|
document
Package document analyses template sources, rather than compiling them.
|
Package document analyses template sources, rather than compiling them. |
|
Package reports holds what a templates repository says about itself.
|
Package reports holds what a templates repository says about itself. |