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 in a single pass so that 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. TemplateName computes a name before any repository exists, which a caller needs when the templates it is about to declare are themselves chosen by name.
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, so a caller may write "server\parameter" on Windows and reach the same template as everyone else.
Two assets of the same source declaring one name is an error, since no source is read after the other. 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.
Stacking whole sets of templates is a file system concern rather than a repository one. Merge the sets 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: it decides what a source reads, before anything is named.
Repository.Audit lists every name that more than one asset declared, with the definition that stands and the ones it replaced. Run it where a build can fail, so that a contrib set which shadows a template by accident is caught before it ships. It returns a github.com/go-openapi/codegen/templates-repo/reports.Audit, which covers more than overrides.
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. Scoping is the one place that accepts names alone: Repository.Lookup takes either, so a caller holding addresses converts them with Repository.NameOf first. Naming an address reports an error rather than building 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.
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.
A package that ships templates should therefore export sources rather than a repository. A scaffolding that calls into the parts it is assembled with cannot be built on its own, so 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...),
)
}
One caveat is worth stating. A template that calls into another set names the address that set was mounted at, so a package whose templates do that has an expected mount point. Document it alongside the templates the package exports and the data they are executed on. A set that calls into nothing may be mounted anywhere.
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)
The analysis runs on demand rather than when the repository is built, so a caller that only executes templates does not pay for it. The output is ordered throughout, so the same templates produce the same document every time, and that document can be committed and checked 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; executing its templates takes none of them, so a program that only renders imports neither the documentation model nor the audit report. That package also renders a document on its own, through reports.Dump, which suits a document built once and laid out several ways.
Coverage ¶
WithCoverage instruments a repository to count the lines of its templates that execute. It has to be set when the repository is built, because the templates that run must be the ones holding the counters. 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 the paths have 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 appears at zero rather than being absent. A line holding nothing but a define, an end or an else is left out, 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.
It computes the name before a repository exists, for a caller that has to choose its sources by name: the name decides what to build, so it cannot wait for the build. Repository.NameOf answers the same question for a repository already 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 ( 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.
An option that cannot be honoured reports an error from New or Clone, rather than at the point where it is constructed: a repository built from settings the caller did not ask for is worse than one that fails to build.
Usage ¶
Options come in two kinds. Sources declare where templates are read from, and are applied in order: FromFS, FromDir and FromTemplate. Settings shape how they are read, whatever the order: WithFuncMap, WithExtensions, WithRoots, WithExtraRoots and WithCoverage. How one source is read is settled where it is declared, with a SourceOption.
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. It 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, so a caller serving a subtree re-roots it beforehand with io/fs.Sub. An empty mountPoint, or ".", mounts the assets at the top of the template tree, which is the usual case.
Overriding is not this option's business: a caller that wants one set of files to take precedence over another stacks them into a single io/fs.FS first, and passes 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.
This is how a set assembled out of parts is built in one pass. A repository is only built when everything it refers to is there, so a scaffolding that calls into the parts it is assembled with cannot stand on its own. Declare the parts as sources of the same build, and each may be written apart and resolved together.
The templates of the repository are read as they were declared, and mounting them somewhere 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.
Nothing of the repository is read again: it retained the content of its own sources, and that is what is carried over. It 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: overriding a template declared elsewhere means naming the address it was declared at. The key it answers to is derived from the address like any other.
Unlike an asset read from a file system, it is registered whatever its extension.
This is the way to declare a template that no file holds, such as one a configuration provides. A caller holding several of them is better served by an in-memory io/fs.FS passed to FromFS, which keeps every override going through the same 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. The caller assembling them does not have to know how many there are, nor in what order they go.
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.
Counting is decided here rather than later: the templates that execute have to be the ones holding the counters, so a repository either is instrumented or is not. 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 the paths have to read as an import path of a package that exists, so 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. An asset whose name ends with none of them is ignored by FromFS and FromDir, while FromTemplate registers its content whatever its 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 keeps every template it reads, since that scope already holds them. It takes names, as WithRoots does.
Use it to add a template to a repository the caller did not build. WithRoots would be 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.
Functions are bound when templates are parsed, which is why they cannot be changed afterwards. Adding a function to an existing repository is Clone with this option: the clone re-parses its templates, so the new function reaches all of them.
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 a single run needs a part of them: the templates a client needs are not those a server needs. Naming the roots of a run keeps the repository to what that run executes, and lets a template set that is incomplete for the other runs build all the same.
Roots are names, the identity Repository.Get takes, never the address a template was declared at. Repository.NameOf converts one to the other for a caller holding addresses.
A root no source declares is an error: a filter naming a template that does not exist builds a repository that generates nothing, which is worse than a build that fails.
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. Pruning decides only which templates the repository holds, and therefore what Repository.Names lists, what its documentation covers, and what its coverage counts.
This sets the scope rather than adding to it: a Clone naming roots of its own is scoped to those alone, whatever the repository it derives from was scoped to. WithExtraRoots is the one that widens a scope. 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.
A repository is built by New from the sources given as options, and derived by Clone. It has no other constructor, and nothing alters it once 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. This is meant for the settings of a program, decided once, and not for a per-operation derivation.
Concurrency ¶
A repository is immutable, holds no lock, and is safe for concurrent use. Clone only reads its source, so cloning a repository other goroutines are using is safe as well.
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.
The sources of source are not read again: their content, retained when source was built, is carried over and the sources declared by opts are appended to it. Everything is parsed and resolved afresh, so a template that opts overrides is picked up by every template referring to it, and the two repositories share nothing.
source is left untouched, and may be used by other goroutines while Clone runs. A nil source reports 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: what a later repository declares at an address another one already holds is dropped rather than taking its place. Func maps are coalesced the same way, by github.com/go-openapi/codegen/funcmaps.Coalesce, which also leaves the builtins alone.
This is for assembling a set out of parts where the first one named is the one in charge, and a later one only fills what is missing.
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.
A merge exists 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 are merged the same way, by github.com/go-openapi/codegen/funcmaps.Merge.
Assembling sets that were written apart usually means Rebase first, which is what keeps their addresses from meeting at all.
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. Reporting an error rather than a repository covers an unreadable source, a template that fails to parse, a template that refers to one no source declares, and an override that would silently not replace what it overrides.
Building a repository with no source at all yields an empty one, which 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 is resolved outward from where it was written, and everything it could reach is still there, one level further in, so a set that resolved on its own resolves the same rebased, so a repository may be assembled rather than only built.
The repository it derives from is 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 reports what a repository holds that is worth a second look.
It reads the assets again, as Repository.Documentation does, so a caller pays for it only by asking. Run it where a build can fail: it reveals a contrib set that replaced a template by accident, or a macro that has outlived its callers.
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 are frozen, their counters are not. The counters record what a run reaches, and are the one part of a repository that changes.
func (*Repository) Documentation ¶
func (r *Repository) Documentation() (reports.Documentation, error)
Documentation returns the structure of the repository and the documentation of its templates.
The analysis runs here rather than while the repository is built: comments are dropped from the trees a template executes, and the data a template reads is of no use to executing it. The repository holds its sources, so both are recovered by reading them again, which only a caller asking for documentation pays for.
The result is built on demand and shared with nobody, so a caller may 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 is reports.Dump over what Repository.Documentation returns, which is the common way to ask. Use reports.Dump directly to lay out a document built once and rendered several ways.
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
It reports an error when no source declares that name. The returned Template 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. This method takes one, Repository.Get takes a name. Use whichever a caller already holds.
The extension may be left on, so 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 coming from the outside.
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 coming from the outside.
func (*Repository) NameOf ¶
func (r *Repository) NameOf(address string) string
NameOf returns the name a template declared at an address answers to.
It recases rather than looks up, so an address nothing declares still yields the name it would have. Ask Repository.Has whether that name is declared. The asset path addresses a template too, the extension being trimmed either way: NameOf("server/parameter.gotmpl") and NameOf("server/parameter") are both serverParameter.
Which extensions are trimmed is a setting of the repository, which is why this is a method. TemplateName answers the same question before a repository exists.
It reverses Repository.AddressOf, and it is idempotent on the names it produces, so a name may be handed back to it: NameOf("serverParameter") is serverParameter. A name an address never produced is not covered by that, an inner "define" being addressed under the asset that holds 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.
It is empty when the repository holds every template it read, which is a repository built without WithRoots. This reports a scope rather than deciding anything with it: a caller adding a template to a repository it did not build wants WithExtraRoots, which does the right thing whether there is a scope to widen or not.
type SourceOption ¶
type SourceOption func(sourceOptions) sourceOptions
SourceOption configures how one source is read.
Which directories to skip describes the file system being walked, not the repository. Skipping a directory of the assets one source ships 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 sources, and the caller assembling them chooses 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. Use this to stack a set of alternate templates without reading all of it, on the source that holds them.
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.
It is obtained from Repository.Get and cannot be built otherwise. The zero value reports an empty name and fails to execute.
A Template exposes execution and nothing else, on purpose: the methods of a text/template.Template that alter a template would alter what the repository serves, for every holder of it. There is no ExecuteTemplate either, since resolving a name is the job of Repository.Get.
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.
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. |