Documentation ¶
Index ¶
- type ContentsDifferErr
- type FS
- func (fs *FS) Add(flist ...File) error
- func (fs *FS) AsFiles() []File
- func (fsys FS) Glob(pattern string) ([]string, error)
- func (fs *FS) Len() int
- func (fs *FS) Map(fn FileMapper) (*FS, error)
- func (fs *FS) Merge(wd2 *FS) error
- func (fsys FS) Open(name string) (fs.File, error)
- func (fsys FS) ReadDir(name string) ([]fs.DirEntry, error)
- func (fsys FS) ReadFile(name string) ([]byte, error)
- func (fsys FS) Stat(name string) (fs.FileInfo, error)
- func (fsys FS) Sub(dir string) (fs.FS, error)
- func (fs *FS) Verify(ctx context.Context, prefix string) error
- func (fs *FS) Write(ctx context.Context, prefix string) error
- type File
- type FileMapper
- type Files
- type Input
- type Jenny
- type JennyList
- func (jl *JennyList[Input]) AddPostprocessors(fn ...FileMapper)
- func (jl *JennyList[Input]) Append(jennies ...Jenny[Input])
- func (jl *JennyList[Input]) AppendManyToMany(jennies ...ManyToMany[Input])
- func (jl *JennyList[Input]) AppendManyToOne(jennies ...ManyToOne[Input])
- func (jl *JennyList[Input]) AppendOneToMany(jennies ...OneToMany[Input])
- func (jl *JennyList[Input]) AppendOneToOne(jennies ...OneToOne[Input])
- func (jl *JennyList[Input]) Generate(objs ...Input) (Files, error)
- func (jl *JennyList[Input]) GenerateFS(objs ...Input) (*FS, error)
- func (jl *JennyList[Input]) JennyName() string
- type ManyToMany
- type ManyToOne
- type NamedJenny
- type OneToMany
- type OneToOne
- type ShouldExistErr
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ContentsDifferErr ¶
type ContentsDifferErr struct { }
ContentsDifferErr is an error that indicates the contents of a file on disk are different than those in the FS.
type FS ¶
type FS struct {
// contains filtered or unexported fields
}
FS is a pseudo-filesystem that supports batch-writing its contents to the real filesystem, or batch-comparing its contents to the real filesystem. Its intended use is for idiomatic `go generate`-style code generators, where it is expected that the results of codegen are committed to version control.
In such cases, the normal behavior of a generator is to write files to disk, but in CI, that behavior should change to verify that what is already on disk is identical to the results of code generation. This allows CI to ensure that the results of code generation are always up to date. FS supports these related behaviors through its FS.Write and FS.Verify methods, respectively.
FS behaves like an immutable append-only data structure - [File]s may not be removed once [FS.Add]ed. If a path conflict occurs when adding a new file or merging another FS, an error is returned.
Every File added to FS must have a relative path. An absolute path may be provided as a universal prefix on calls to FS.Write or FS.Verify.
FS implements io/fs.FS, backed by [fstest.MapFS]. Added mutexes make FS safe for concurrent use, but it has the same scaling limitations as [fstest.MapFS] for large numbers of files.
Note that the statelessness of FS entails that if a particular Jenny Input goes away, FS.Verify cannot know what orphaned generated files should be removed.
func (*FS) Add ¶
Add adds one or more files to the FS. An error is returned if the RelativePath of any provided files already exists in the FS.
func (*FS) AsFiles ¶
AsFiles returns a Files representing the contents of the FS.
The contents are sorted lexicographically, and it is guaranteed that the invariants enforced by Files.Validate are met.
func (*FS) Map ¶
func (fs *FS) Map(fn FileMapper) (*FS, error)
Map creates a new FS by passing each File element in the receiver FS through the provided FileMapper.
func (*FS) Merge ¶
Merge combines all the entries from the provided FS into the receiver FS. Duplicate paths result in an error.
func (*FS) Verify ¶
Verify checks the contents of each file against the filesystem. It emits an error if any of its contained files differ.
If the provided prefix path is non-empty, it will be prepended to all file entries in the map for writing. prefix may be an absolute path.
func (*FS) Write ¶
Write writes all of the files to their indicated paths.
If the provided prefix path is non-empty, it will be prepended to all file entries in the map for writing. prefix may be an absolute path. TODO try to undo already-written files on error (only best effort, it's impossible to guarantee)
type File ¶
type File struct { // The relative path to which the file should be written. An empty // RelativePath indicates a File that does not [File.Exists]. RelativePath string // Data is the contents of the file. Data []byte // From is the stack of jennies responsible for producing this File. // Wrapper jennies should precede the jennies they wrap. From []NamedJenny }
File is a single file, intended to be written or compared against existing files on disk through an FS.
codejen treats a File with an empty RelativePath as not existing, regardless of whether Data is empty. Thus, the zero value of File is considered not to exist.
func NewFile ¶
func NewFile(path string, data []byte, from ...NamedJenny) *File
NewFile makes it slightly more ergonomic to create a new File than with a raw struct declaration.
func (File) FromString ¶
FromString converts the stack of jennies in File.From to a string by joining them with a colon.
type FileMapper ¶
FileMapper takes a File and transforms it into a new File.
codejen generally assumes that FileMappers will reuse an unmodified byte slice.
type Files ¶
type Files []File
Files is a set of File objects.
A Files is [Files.Invalid] if it contains a File that does not File.Exists, or if it contains more than one File having the same [File.RelativePath].
These invariants are internally enforced by FS.
type Input ¶ added in v0.0.3
type Input = any
Input is used in generic type parameters solely to indicate to human eyes that that type parameter is used to govern the type passed as input to a jenny's Generate method.
Input is an alias for any, because the codejen framework takes no stance on what can be accepted as jenny inputs.
type Jenny ¶
type Jenny[I Input] interface { // JennyName returns the name of the generator. JennyName() string }
A Jenny is a single codejen code generator.
Each Jenny works with exactly one type of input to its code generation, as indicated by its I type parameter, which may be any. The type Input is used as an indicator to humans of the purpose of such type parameters.
Each Jenny takes either one or many Inputs, and produces one or many output files. Jennies may also return nils to indicate zero outputs.
It is a design tenet of codejen that, in code generation, good separation of concerns starts with keeping a single file to a single responsibility. Thus, where possible, most Jennies should aim for one input to one output.
Unfortunately, Go's generic system does not (yet?) allow expression of the necessary abstraction over individual kinds of Jennies as part of the Jenny interface itself. As such, the actual, functional interface is split into four:
- OneToOne: one Input in, one File out
- OneToMany: one Input in, many [File]s out
- ManyToOne: many [Input]s in, one File out
- ManyToMany: many [Input]s in, many [File]s out
All jennies will follow exactly one of these four interfaces.
type JennyList ¶
type JennyList[Input any] struct { // contains filtered or unexported fields }
JennyList is an ordered collection of jennies. JennyList itself implements ManyToMany, and when called, will construct an FS by calling each of its contained jennies in order.
The primary purpose of JennyList is to make it easy to create complex, case-specific code generators by composing sets of small, reusable jennies that each have clear, narrow responsibilities.
The File outputs of all member jennies in a JennyList exist in the same relative path namespace. JennyList does not modify emitted paths. Path uniqueness (per Files.Validate) is internally enforced across the aggregate set of Files.
JennyList's Input type parameter is used to enforce that every Jenny in the JennyList takes the same type parameter.
func JennyListWithNamer ¶
JennyListWithNamer creates a new JennyList that decorates errors using the provided namer func, which can derive a meaningful identifier string from the Input type for the JennyList.
func (*JennyList[Input]) AddPostprocessors ¶
func (jl *JennyList[Input]) AddPostprocessors(fn ...FileMapper)
AddPostprocessors appends a slice of FileMapper to its internal list of postprocessors.
Postprocessors are run (FIFO) on every File produced by the JennyList.
func (*JennyList[Input]) Append ¶
Append adds Jennies to the end of the JennyList. In Generate, Jennies are called in the order they were appended.
All provided jennies must also implement one of OneToOne, OneToMany, ManyToOne, ManyToMany, or this method will panic. For proper type safety, use the Append* methods.
func (*JennyList[Input]) AppendManyToMany ¶
func (jl *JennyList[Input]) AppendManyToMany(jennies ...ManyToMany[Input])
AppendManyToMany is like JennyList.Append, but typesafe for ManyToMany jennies.
func (*JennyList[Input]) AppendManyToOne ¶
AppendManyToOne is like JennyList.Append, but typesafe for ManyToOne jennies.
func (*JennyList[Input]) AppendOneToMany ¶
AppendOneToMany is like JennyList.Append, but typesafe for OneToMany jennies.
func (*JennyList[Input]) AppendOneToOne ¶
AppendOneToOne is like JennyList.Append, but typesafe for OneToOne jennies.
func (*JennyList[Input]) GenerateFS ¶
type ManyToMany ¶
type ManyToMany[I Input] interface { Jenny[I] // Generate takes a slice of Input and generates many [File]s, or none // (nil) if the j was a no-op for the provided Input. // // A nil, nil return is used to indicate the generator had nothing to do for the // provided Input. Generate(...I) (Files, error) }
ManyToMany is a Jenny that accepts many inputs, and produces 0 to N files as output.
func AdaptManyToMany ¶
func AdaptManyToMany[InI, OutI Input](j ManyToMany[InI], fn func(OutI) InI) ManyToMany[OutI]
AdaptManyToMany takes a ManyToMany jenny that accepts a particular type as input (InI), and transforms it into a jenny that accepts a different type as input (OutI), given a function that can transform an InI to an OutI.
Use this to make jennies reusable in other Input type contexts.
type ManyToOne ¶
type ManyToOne[I Input] interface { Jenny[I] // Generate takes a slice of Input and generates one File, The zero value of a // File may be returned to indicate the jenny was a no-op for the provided // Inputs. Generate(...I) (*File, error) }
func AdaptManyToOne ¶
AdaptManyToOne takes a ManyToOne jenny that accepts a particular type as input (InI), and transforms it into a jenny that accepts a different type as input (OutI), given a function that can transform an InI to an OutI.
Use this to make jennies reusable in other Input type contexts.
type NamedJenny ¶
type NamedJenny interface {
JennyName() string
}
NamedJenny includes just the JennyName method. We have to have this interface due to the limits on Go's type system.
type OneToMany ¶
type OneToMany[I Input] interface { Jenny[I] // Generate takes an Input and generates many [File]s, or none (nil) if the j // was a no-op for the provided Input. Generate(I) (Files, error) }
func AdaptOneToMany ¶
AdaptOneToMany takes a OneToMany jenny that accepts a particular type as input (InI), and transforms it into a jenny that accepts a different type as input (OutI), given a function that can transform an InI to an OutI.
Use this to make jennies reusable in other Input type contexts.
type OneToOne ¶
type OneToOne[I Input] interface { Jenny[I] // Generate takes an Input and generates one [File]. The zero value of a File // may be returned to indicate the jenny was a no-op for the provided Input. Generate(I) (*File, error) }
func AdaptOneToOne ¶
AdaptOneToOne takes a OneToOne jenny that accepts a particular type as input (InI), and transforms it into a jenny that accepts a different type as input (OutI), given a function that can transform an InI to an OutI.
Use this to make jennies reusable in other Input type contexts.
type ShouldExistErr ¶
type ShouldExistErr struct { }
ShouldExistErr is an error that indicates a file should exist, but does not.