diff --git a/config/README.md b/config/README.md new file mode 100644 index 00000000..73d0a247 --- /dev/null +++ b/config/README.md @@ -0,0 +1,4 @@ +Config +------ + +The `config` package provides mockery configuration semantics and behaviors. The [`RootConfig`](https://pkg.go.dev/github.com/vektra/mockery/v3/config#RootConfig) type defines the schema for the top-level `.mockery.yml` file. diff --git a/template/config.go b/config/config.go similarity index 98% rename from template/config.go rename to config/config.go index a5d48ef9..46715fda 100644 --- a/template/config.go +++ b/config/config.go @@ -1,4 +1,4 @@ -package template +package config import ( "bufio" @@ -7,12 +7,12 @@ import ( "errors" "fmt" "go/ast" - "html/template" "os" "path/filepath" "reflect" "regexp" "strings" + "text/template" "github.com/brunoga/deep" "github.com/chigopher/pathlib" @@ -25,6 +25,7 @@ import ( "github.com/spf13/pflag" "github.com/vektra/mockery/v3/internal/logging" "github.com/vektra/mockery/v3/internal/stackerr" + "github.com/vektra/mockery/v3/shared" "golang.org/x/tools/go/packages" ) @@ -36,8 +37,18 @@ type Interface struct { Config *Config } -// ConfigData is the data sent to the template for the config file. -type ConfigData struct { +func NewInterface(name string, filename string, file *ast.File, pkg *packages.Package, config *Config) *Interface { + return &Interface{ + Name: name, + FileName: filename, + File: file, + Pkg: pkg, + Config: config, + } +} + +// Data is the data sent to the template for the config file. +type Data struct { // ConfigDir is the directory of where the mockery config file is located. ConfigDir string // InterfaceDir is the directory of the interface being mocked. @@ -58,16 +69,6 @@ type ConfigData struct { SrcPackagePath string } -func NewInterface(name string, filename string, file *ast.File, pkg *packages.Package, config *Config) *Interface { - return &Interface{ - Name: name, - FileName: filename, - File: file, - Pkg: pkg, - Config: config, - } -} - type RootConfig struct { *Config `koanf:",squash"` Packages map[string]*PackageConfig `koanf:"packages"` @@ -592,7 +593,7 @@ func (c *Config) ParseTemplates(ctx context.Context, iface *Interface, srcPkg *p } } // data is the struct sent to the template parser - data := ConfigData{ + data := Data{ ConfigDir: filepath.Dir(*c.ConfigFile), InterfaceDir: interfaceDir, InterfaceDirRelative: interfaceDirRelative, @@ -631,7 +632,7 @@ func (c *Config) ParseTemplates(ctx context.Context, iface *Interface, srcPkg *p for name, attributePointer := range templateMap { oldVal := *attributePointer - attributeTempl, err := template.New("config-template").Funcs(StringManipulationFuncs).Parse(*attributePointer) + attributeTempl, err := template.New("config-template").Funcs(shared.StringManipulationFuncs).Parse(*attributePointer) if err != nil { return fmt.Errorf("failed to parse %s template: %w", name, err) } diff --git a/docs/configuration.md b/docs/configuration.md index 51ede9fd..4232a1e8 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,6 +1,9 @@ Configuration ============== +Example +------- + All configuration is specified in a `.mockery.yml` file. An example config file may look like this: ```yaml @@ -41,7 +44,7 @@ These are the highlights of the config scheme: 1. The parameters are merged hierarchically 2. There are a number of template variables available to generalize config values. -3. The style of mock to be generated is specified using the [`template`](templates.md) parameter. +3. The style of mock to be generated is specified using the [`template`](templates/index.md) parameter. An output file may contain multiple mocks, but the only rule is that all the mocks in the file must come from the same package. Because of this, mocks for different packages must go in different files. @@ -72,29 +75,29 @@ Parameter Descriptions | `template-data` | :fontawesome-solid-x: | `#!yaml {}` | A `map[string]any` that provides arbitrary options to the template. Each template will have a different set of accepted keys. Refer to each template's documentation for more details. | -Config Templates ----------------- +Templates +--------- -Parameters marked as being templated have access to a number of template variables and functions. +Parameters marked as being templated have access to a number of template variables and functions through the Go [`text/template`](https://pkg.go.dev/text/template#hdr-Examples) system. ### Variables -The variables provided are specified in the [`ConfigData`](https://pkg.go.dev/github.com/vektra/mockery/v3/template#ConfigData) struct. +The variables provided are specified in the [`config.Data`](https://pkg.go.dev/github.com/vektra/mockery/v3/config#Data) struct. ### Functions -All of the functions defined in [`StringManipulationFuncs`](https://pkg.go.dev/github.com/vektra/mockery/v3/template#pkg-variables) are available to templated parameters. +All of the functions defined in [`StringManipulationFuncs`](https://pkg.go.dev/github.com/vektra/mockery/v3/shared#pkg-variables) are available to templated parameters. Merging Precedence ------------------ -The configuration applied to a specific mocked interface is merged according to the following precedence (in decreasing priority): +The configuration applied to a specific mocked interface is merged according to the following precedence (in increasing priority): -1. Interface-specific config in `.mockery.yaml` -2. Package-specific config in `.mockery.yaml` +1. Top-level defaults in `.mockery.yaml` +2. Environment variables 3. Command-line options -4. Environment variables -5. Top-level defaults in `.mockery.yaml` +4. Package-specific config in `.mockery.yaml` +5. Interface-specific config in `.mockery.yaml` Formatting ---------- @@ -105,5 +108,5 @@ If a parameter is named `enable-feature` and we want a value of `True`, then the |----------------------|------------------------------| | command line | `--enable-feature=true` | | Environment variable | `MOCKERY_ENABLE_FEATURE=True` | -| yaml | `enable-feature: True` | +| yaml | `#!yaml enable-feature: True` | diff --git a/docs/templates.md b/docs/template/index.md similarity index 70% rename from docs/templates.md rename to docs/template/index.md index 3ed74359..e71869eb 100644 --- a/docs/templates.md +++ b/docs/template/index.md @@ -59,29 +59,12 @@ You can see examples of how the mockery project utilizes the template system to - [`moq.templ`](https://github.com/vektra/mockery/blob/v3/internal/moq.templ) - [`mockery.templ`](https://github.com/vektra/mockery/blob/v3/internal/mockery.templ) -## Data Provided To Templates +## Template Data -Mockery has two separate template instances: one for the `.mockery.yml` file, and one for the mock templates. Each instance has a different set of variables and functions available to it. All functions are [pipeline-compatible](https://pkg.go.dev/text/template#hdr-Pipelines). +### Functions -### `.mockery.yml` +Template files have both [`StringManipulationFuncs`](https://pkg.go.dev/github.com/vektra/mockery/v3/shared#pkg-variables) and [`TemplateMockFuncs`](https://pkg.go.dev/github.com/vektra/mockery/v3/template#pkg-variables) available as functions. -As seen in the [configuration](configuration.md) section, mockery configuration has template variables and methods available to it. - -#### Functions - -Functions provided are in the [`StringManipulationFuncs`](https://pkg.go.dev/github.com/vektra/mockery/v3/template#pkg-variables) variable. - -#### Variables - -The variables available are defined in the [`template.ConfigData`](https://pkg.go.dev/github.com/vektra/mockery/v3/template#ConfigData) struct. - - -### Template Files - -#### Functions - -Template files have both [`StringManipulationFuncs`](https://pkg.go.dev/github.com/vektra/mockery/v3/template#pkg-variables) and [`TemplateMockFuncs`](https://pkg.go.dev/github.com/vektra/mockery/v3@v3.0.0-alpha.10/template#pkg-variables) available as functions. - -#### Variables +### Variables The template is supplied with the [`template.Data`](https://pkg.go.dev/github.com/vektra/mockery/v3/template#Data) struct. Some attributes return types such as [`template.MockData`](https://pkg.go.dev/github.com/vektra/mockery/v3@v3.0.0-alpha.10/template#MockData) and [`template.Package`](https://pkg.go.dev/github.com/vektra/mockery/v3/template#Package) which themselves contain methods that may also be called. diff --git a/docs/template-matryer.md b/docs/template/matryer.md similarity index 100% rename from docs/template-matryer.md rename to docs/template/matryer.md diff --git a/docs/template-testify.md b/docs/template/testify.md similarity index 100% rename from docs/template-testify.md rename to docs/template/testify.md diff --git a/docs/v3.md b/docs/v3.md index e726ce2d..c1347685 100644 --- a/docs/v3.md +++ b/docs/v3.md @@ -3,7 +3,7 @@ v3 Release Mockery releases version 3 of the project that provides a number of high-profile benefits over v2: -1. Allows generation of [`moq`](template-moq.md)-style templates. The https://github.com/matryer/moq project is being folded into mockery to combine the speed and configuration flexibility of mockery with the simplicity of moq-style mocks. +1. Allows generation of [`matryer`](templates/matryer.md)-style templates. The https://github.com/matryer/moq project is being folded into mockery to combine the speed and configuration flexibility of mockery with the simplicity of moq-style mocks. 2. Changes the generation scheme to be entirely driven off of Go templates. This means that the data provided to templates is considered as part of the public API. 3. Mockery now allows users to specify their own templates to make code generation far easier. Mockery handles the problem of parsing source code and enables you to focus on creating [your own interface implementations](templates.md#template-file). 4. Shedding all deprecated variables and simplifying the way in which mocks are configured. diff --git a/internal/cmd/mockery.go b/internal/cmd/mockery.go index fcc6e25a..eac911c7 100644 --- a/internal/cmd/mockery.go +++ b/internal/cmd/mockery.go @@ -11,10 +11,10 @@ import ( "github.com/rs/zerolog" "github.com/spf13/cobra" "github.com/spf13/pflag" + "github.com/vektra/mockery/v3/config" pkg "github.com/vektra/mockery/v3/internal" "github.com/vektra/mockery/v3/internal/logging" "github.com/vektra/mockery/v3/internal/stackerr" - "github.com/vektra/mockery/v3/template" "golang.org/x/tools/go/packages" ) @@ -86,12 +86,12 @@ func Execute() { } type RootApp struct { - Config template.RootConfig + Config config.RootConfig } func GetRootApp(ctx context.Context, flags *pflag.FlagSet) (*RootApp, error) { r := &RootApp{} - config, _, err := template.NewRootConfig(ctx, flags) + config, _, err := config.NewRootConfig(ctx, flags) if err != nil { return nil, stackerr.NewStackErrf(err, "failed to get config") } @@ -112,7 +112,7 @@ type InterfaceCollection struct { outFilePath *pathlib.Path srcPkg *packages.Package outPkgName string - interfaces []*template.Interface + interfaces []*config.Interface template string } @@ -128,12 +128,12 @@ func NewInterfaceCollection( outFilePath: outFilePath, srcPkg: srcPkg, outPkgName: outPkgName, - interfaces: make([]*template.Interface, 0), + interfaces: make([]*config.Interface, 0), template: templ, } } -func (i *InterfaceCollection) Append(ctx context.Context, iface *template.Interface) error { +func (i *InterfaceCollection) Append(ctx context.Context, iface *config.Interface) error { collectionFilepath := i.outFilePath.String() interfaceFilepath := iface.Config.FilePath().String() log := zerolog.Ctx(ctx).With(). @@ -257,7 +257,7 @@ func (r *RootApp) Run() error { } if err := mockFileToInterfaces[filePath.String()].Append( ctx, - template.NewInterface( + config.NewInterface( iface.Name, iface.FileName, iface.File, diff --git a/internal/cmd/showconfig.go b/internal/cmd/showconfig.go index 5b7b21bd..d81e2904 100644 --- a/internal/cmd/showconfig.go +++ b/internal/cmd/showconfig.go @@ -8,8 +8,8 @@ import ( "github.com/knadh/koanf/providers/structs" "github.com/knadh/koanf/v2" "github.com/spf13/cobra" + "github.com/vektra/mockery/v3/config" "github.com/vektra/mockery/v3/internal/logging" - "github.com/vektra/mockery/v3/template" ) func NewShowConfigCmd() *cobra.Command { @@ -24,7 +24,7 @@ func NewShowConfigCmd() *cobra.Command { } ctx := log.WithContext(context.Background()) - conf, _, err := template.NewRootConfig(ctx, cmd.Parent().PersistentFlags()) + conf, _, err := config.NewRootConfig(ctx, cmd.Parent().PersistentFlags()) if err != nil { return err } diff --git a/internal/parse.go b/internal/parse.go index c0f380ce..07af3f43 100644 --- a/internal/parse.go +++ b/internal/parse.go @@ -8,7 +8,7 @@ import ( "strings" "github.com/rs/zerolog" - "github.com/vektra/mockery/v3/template" + "github.com/vektra/mockery/v3/config" "golang.org/x/tools/go/packages" ) @@ -38,9 +38,9 @@ func NewParser(buildTags []string) *Parser { return p } -func (p *Parser) ParsePackages(ctx context.Context, packageNames []string) ([]*template.Interface, error) { +func (p *Parser) ParsePackages(ctx context.Context, packageNames []string) ([]*config.Interface, error) { log := zerolog.Ctx(ctx) - interfaces := []*template.Interface{} + interfaces := []*config.Interface{} packages, err := packages.Load(&p.conf, packageNames...) if err != nil { @@ -91,7 +91,7 @@ func (p *Parser) ParsePackages(ctx context.Context, packageNames []string) ([]*t continue } - interfaces = append(interfaces, template.NewInterface( + interfaces = append(interfaces, config.NewInterface( name, file, fileSyntax, diff --git a/internal/template_generator.go b/internal/template_generator.go index 69ba172c..9858deac 100644 --- a/internal/template_generator.go +++ b/internal/template_generator.go @@ -15,6 +15,7 @@ import ( "github.com/chigopher/pathlib" "github.com/rs/zerolog" + "github.com/vektra/mockery/v3/config" "github.com/vektra/mockery/v3/internal/stackerr" "github.com/vektra/mockery/v3/template" "golang.org/x/tools/go/packages" @@ -103,7 +104,7 @@ type TemplateGenerator struct { registry *template.Registry formatter Formatter inPackage bool - pkgConfig *template.Config + pkgConfig *config.Config pkgName string } @@ -113,7 +114,7 @@ func NewTemplateGenerator( outPkgFSPath *pathlib.Path, templateName string, formatter Formatter, - pkgConfig *template.Config, + pkgConfig *config.Config, pkgName string, ) (*TemplateGenerator, error) { srcPkgFSPath := pathlib.NewPath(srcPkg.GoFiles[0]).Parent() @@ -177,7 +178,7 @@ func (g *TemplateGenerator) format(src []byte) ([]byte, error) { return nil, fmt.Errorf("unknown formatter type: %s", g.formatter) } -func (g *TemplateGenerator) methodData(ctx context.Context, method *types.Func, ifaceConfig *template.Config) (template.MethodData, error) { +func (g *TemplateGenerator) methodData(ctx context.Context, method *types.Func, ifaceConfig *config.Config) (template.MethodData, error) { log := zerolog.Ctx(ctx) methodScope := g.registry.MethodScope() @@ -306,7 +307,7 @@ func (g *TemplateGenerator) typeParams(ctx context.Context, tparams *types.TypeP func (g *TemplateGenerator) Generate( ctx context.Context, - interfaces []*template.Interface, + interfaces []*config.Interface, ) ([]byte, error) { log := zerolog.Ctx(ctx) mockData := []template.MockData{} diff --git a/main.go b/main.go index 728e89ab..c59884d1 100644 --- a/main.go +++ b/main.go @@ -1,3 +1,4 @@ +// Package main implements shit package main import ( diff --git a/mkdocs.yml b/mkdocs.yml index 3ee42fb3..89f64e72 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -54,17 +54,16 @@ markdown_extensions: - toc: permalink: true - nav: - Home: index.md - Getting Started: - Installation: installation.md - Configuration: configuration.md - - Templates: templates.md - Running: running.md - Templates: - - template-testify.md - - template-matryer.md + - template/index.md + - template/testify.md + - template/matryer.md - Features: - replace-type.md - Notes: diff --git a/mockery-tools.env b/mockery-tools.env index 38295009..2cd4891e 100644 --- a/mockery-tools.env +++ b/mockery-tools.env @@ -1 +1 @@ -VERSION=v3.0.0-alpha.20 +VERSION=v3.0.0-alpha.21 diff --git a/shared/template_funcs.go b/shared/template_funcs.go new file mode 100644 index 00000000..0b6efb19 --- /dev/null +++ b/shared/template_funcs.go @@ -0,0 +1,59 @@ +// Package shared provides variables/objects that need to be shared +// across multiple packages. The main purpose is to resolve cyclical imports +// arising from multiple packages needing to share common utilies. +package shared + +import ( + "os" + "path/filepath" + "regexp" + "strings" + "text/template" + + "github.com/huandu/xstrings" +) + +//nolint:predeclared +var StringManipulationFuncs = template.FuncMap{ + // String inspection and manipulation. Note that the first argument is replaced + // as the last argument in some functions in order to support chained + // template pipelines. + "contains": func(substr string, s string) bool { return strings.Contains(s, substr) }, + "hasPrefix": func(prefix string, s string) bool { return strings.HasPrefix(s, prefix) }, + "hasSuffix": func(suffix string, s string) bool { return strings.HasSuffix(s, suffix) }, + "join": func(sep string, elems []string) string { return strings.Join(elems, sep) }, + "replace": func(old string, new string, n int, s string) string { return strings.Replace(s, old, new, n) }, + "replaceAll": func(old string, new string, s string) string { return strings.ReplaceAll(s, old, new) }, + "split": func(sep string, s string) []string { return strings.Split(s, sep) }, + "splitAfter": func(sep string, s string) []string { return strings.SplitAfter(s, sep) }, + "splitAfterN": func(sep string, n int, s string) []string { return strings.SplitAfterN(s, sep, n) }, + "trim": func(cutset string, s string) string { return strings.Trim(s, cutset) }, + "trimLeft": func(cutset string, s string) string { return strings.TrimLeft(s, cutset) }, + "trimPrefix": func(prefix string, s string) string { return strings.TrimPrefix(s, prefix) }, + "trimRight": func(cutset string, s string) string { return strings.TrimRight(s, cutset) }, + "trimSpace": strings.TrimSpace, + "trimSuffix": func(suffix string, s string) string { return strings.TrimSuffix(s, suffix) }, + "lower": strings.ToLower, + "upper": strings.ToUpper, + "camelcase": xstrings.ToCamelCase, + "snakecase": xstrings.ToSnakeCase, + "kebabcase": xstrings.ToKebabCase, + "firstLower": xstrings.FirstRuneToLower, + "firstUpper": xstrings.FirstRuneToUpper, + + // Regular expression matching + "matchString": regexp.MatchString, + "quoteMeta": regexp.QuoteMeta, + + // Filepath manipulation + "base": filepath.Base, + "clean": filepath.Clean, + "dir": filepath.Dir, + + // Basic access to reading environment variables + "expandEnv": os.ExpandEnv, + "getenv": os.Getenv, + + // Arithmetic + "add": func(i1, i2 int) int { return i1 + i2 }, +} diff --git a/template/README.md b/template/README.md index fcf857b0..f739111e 100644 --- a/template/README.md +++ b/template/README.md @@ -1,4 +1,12 @@ -Mockery Templates ------------------ +Template +-------- -Further documentation for this project can be found on https://vektra.github.io/mockery/v3/. \ No newline at end of file +This package contains all of the data passed to mockery templates. The top-most variable provided +to the templates is [MockData](github.com/vektra/mockery/v3/template#MockData). Examples of how to +use the data provided to mockery templates can be found in the pre-curated mocks, such as: + +- [matryer](https://github.com/vektra/mockery/blob/v3/internal/mock_matryer.templ) +- [testify](https://github.com/vektra/mockery/blob/v3/internal/mock_testify.templ) + + +Full documentation is provided at: https://vektra.github.io/mockery/v3/ \ No newline at end of file diff --git a/template/method_scope.go b/template/method_scope.go index 669fd1b9..6fff486c 100644 --- a/template/method_scope.go +++ b/template/method_scope.go @@ -6,6 +6,7 @@ import ( "go/types" "github.com/rs/zerolog" + "github.com/vektra/mockery/v3/config" "github.com/vektra/mockery/v3/internal/stackerr" "golang.org/x/tools/go/packages" ) @@ -99,7 +100,7 @@ var _ TypesPackage = fakePackage{} // Variables names are generated if required and are ensured to be // without conflict with other variables and imported packages. It also // adds the relevant imports to the registry for each added variable. -func (m *MethodScope) AddVar(ctx context.Context, vr *types.Var, prefix string, replacement *ReplaceType) (*Var, error) { +func (m *MethodScope) AddVar(ctx context.Context, vr *types.Var, prefix string, replacement *config.ReplaceType) (*Var, error) { var ( imports map[string]*Package = map[string]*Package{} v Var diff --git a/template/template.go b/template/template.go index 863f4c73..093cde99 100644 --- a/template/template.go +++ b/template/template.go @@ -3,12 +3,10 @@ package template import ( "io" "os" - "path/filepath" - "regexp" "strings" "text/template" - "github.com/huandu/xstrings" + "github.com/vektra/mockery/v3/shared" ) // Template is the Moq template. It is capable of generating the Moq @@ -20,7 +18,7 @@ type Template struct { // New returns a new instance of Template. func New(templateString string, name string) (Template, error) { mergedFuncMap := template.FuncMap{} - for key, val := range StringManipulationFuncs { + for key, val := range shared.StringManipulationFuncs { mergedFuncMap[key] = val } for key, val := range TemplateMockFuncs { @@ -115,48 +113,3 @@ var TemplateMockFuncs = template.FuncMap{ return string(fileBytes) }, } - -//nolint:predeclared -var StringManipulationFuncs = template.FuncMap{ - // String inspection and manipulation. Note that the first argument is replaced - // as the last argument in some functions in order to support chained - // template pipelines. - "contains": func(substr string, s string) bool { return strings.Contains(s, substr) }, - "hasPrefix": func(prefix string, s string) bool { return strings.HasPrefix(s, prefix) }, - "hasSuffix": func(suffix string, s string) bool { return strings.HasSuffix(s, suffix) }, - "join": func(sep string, elems []string) string { return strings.Join(elems, sep) }, - "replace": func(old string, new string, n int, s string) string { return strings.Replace(s, old, new, n) }, - "replaceAll": func(old string, new string, s string) string { return strings.ReplaceAll(s, old, new) }, - "split": func(sep string, s string) []string { return strings.Split(s, sep) }, - "splitAfter": func(sep string, s string) []string { return strings.SplitAfter(s, sep) }, - "splitAfterN": func(sep string, n int, s string) []string { return strings.SplitAfterN(s, sep, n) }, - "trim": func(cutset string, s string) string { return strings.Trim(s, cutset) }, - "trimLeft": func(cutset string, s string) string { return strings.TrimLeft(s, cutset) }, - "trimPrefix": func(prefix string, s string) string { return strings.TrimPrefix(s, prefix) }, - "trimRight": func(cutset string, s string) string { return strings.TrimRight(s, cutset) }, - "trimSpace": strings.TrimSpace, - "trimSuffix": func(suffix string, s string) string { return strings.TrimSuffix(s, suffix) }, - "lower": strings.ToLower, - "upper": strings.ToUpper, - "camelcase": xstrings.ToCamelCase, - "snakecase": xstrings.ToSnakeCase, - "kebabcase": xstrings.ToKebabCase, - "firstLower": xstrings.FirstRuneToLower, - "firstUpper": xstrings.FirstRuneToUpper, - - // Regular expression matching - "matchString": regexp.MatchString, - "quoteMeta": regexp.QuoteMeta, - - // Filepath manipulation - "base": filepath.Base, - "clean": filepath.Clean, - "dir": filepath.Dir, - - // Basic access to reading environment variables - "expandEnv": os.ExpandEnv, - "getenv": os.Getenv, - - // Arithmetic - "add": func(i1, i2 int) int { return i1 + i2 }, -}