codejen

package module
v0.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 16, 2022 License: Apache-2.0 Imports: 16 Imported by: 17

README

Codejen

Codejen is a modular code generation framework that integrates writing generated files to disk, as well as the common CI task of verifying that generator output and current disk state in sync.

Documentation

Index

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 NewFS

func NewFS() *FS

NewFS creates a new FS, ready for use.

func (*FS) Add

func (fs *FS) Add(flist ...File) error

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

func (fs *FS) AsFiles() []File

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) Glob

func (fsys FS) Glob(pattern string) ([]string, error)

func (*FS) Len

func (fs *FS) Len() int

Len returns the number of items in the FS.

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

func (fs *FS) Merge(wd2 *FS) error

Merge combines all the entries from the provided FS into the receiver FS. Duplicate paths result in an error.

func (FS) Open

func (fsys FS) Open(name string) (fs.File, error)

Open opens the named file.

func (FS) ReadDir

func (fsys FS) ReadDir(name string) ([]fs.DirEntry, error)

func (FS) ReadFile

func (fsys FS) ReadFile(name string) ([]byte, error)

func (FS) Stat

func (fsys FS) Stat(name string) (fs.FileInfo, error)

func (FS) Sub

func (fsys FS) Sub(dir string) (fs.FS, error)

func (*FS) Verify

func (fs *FS) Verify(ctx context.Context, prefix string) error

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

func (fs *FS) Write(ctx context.Context, prefix string) error

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) Exists

func (f File) Exists() bool

Exists indicates whether the File should be considered to exist.

func (File) FromString

func (f File) FromString() string

FromString converts the stack of jennies in File.From to a string by joining them with a colon.

func (File) ToFS

func (f File) ToFS() (*FS, error)

ToFS turns a single File into a FS containing only that file.

An error is only possible if the File does not Validate.

func (File) Validate

func (f File) Validate() error

Validate checks that the File is valid - has a relative path, and at least one jenny in its From.

type FileMapper

type FileMapper func(File) (File, error)

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.

func (Files) Validate

func (fsl Files) Validate() error

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:

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

func JennyListWithNamer[Input any](namer func(t Input) string) *JennyList[Input]

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

func (jl *JennyList[Input]) Append(jennies ...Jenny[Input])

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

func (jl *JennyList[Input]) AppendManyToOne(jennies ...ManyToOne[Input])

AppendManyToOne is like JennyList.Append, but typesafe for ManyToOne jennies.

func (*JennyList[Input]) AppendOneToMany

func (jl *JennyList[Input]) AppendOneToMany(jennies ...OneToMany[Input])

AppendOneToMany is like JennyList.Append, but typesafe for OneToMany jennies.

func (*JennyList[Input]) AppendOneToOne

func (jl *JennyList[Input]) AppendOneToOne(jennies ...OneToOne[Input])

AppendOneToOne is like JennyList.Append, but typesafe for OneToOne jennies.

func (*JennyList[Input]) Generate

func (jl *JennyList[Input]) Generate(objs ...Input) (Files, error)

func (*JennyList[Input]) GenerateFS

func (jl *JennyList[Input]) GenerateFS(objs ...Input) (*FS, error)

func (*JennyList[Input]) JennyName

func (jl *JennyList[Input]) JennyName() string

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

func AdaptManyToOne[InI, OutI Input](g ManyToOne[InI], fn func(OutI) InI) ManyToOne[OutI]

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

func AdaptOneToMany[InI, OutI Input](j OneToMany[InI], fn func(OutI) InI) OneToMany[OutI]

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

func AdaptOneToOne[InI, OutI Input](j OneToOne[InI], fn func(OutI) InI) OneToOne[OutI]

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL