From 505913a1c4ef784cf9414b38e711f35781803501 Mon Sep 17 00:00:00 2001 From: switchupcb Date: Mon, 27 Mar 2023 02:42:46 -0500 Subject: [PATCH 1/8] add cast outline add cast config add cast examples clarify automatcher disable is per function fix UI mismatch by replacing -xm option --- CONTRIBUTING.md | 39 ++++++----- README.md | 75 ++++++++++++--------- cli/cli.go | 15 ++--- cli/config/models.go | 21 ++++++ cli/config/yml.go | 8 +++ cli/models/generator.go | 15 ++++- examples/_tests/README.md | 1 - examples/_tests/cast/domain/domain.go | 13 ---- examples/_tests/cast/models/models.go | 14 ---- examples/_tests/cast/setup/setup.go | 14 ---- examples/_tests/cast/setup/setup.yml | 4 -- examples/_tests/examples_test.go | 39 +++++++++-- examples/_tests/multi/copygen.go | 5 ++ examples/_tests/multi/setup/setup.go | 1 + examples/_tests/option/setup/setup.yml | 8 +++ examples/cast/README.md | 93 ++++++++++++++++++++++++++ examples/cast/assert/assert.go | 11 +++ examples/cast/assert/setup.go | 9 +++ examples/cast/assert/setup.yml | 22 ++++++ examples/cast/convert/convert.go | 15 +++++ examples/cast/convert/setup.go | 11 +++ examples/cast/convert/setup.yml | 18 +++++ examples/cast/depth/depth.go | 4 ++ examples/cast/depth/setup.go | 15 +++++ examples/cast/depth/setup.yml | 18 +++++ examples/cast/expression/setup.go | 10 +++ examples/cast/expression/setup.yml | 18 +++++ examples/cast/function/function.go | 13 ++++ examples/cast/function/setup.go | 10 +++ examples/cast/function/setup.yml | 18 +++++ examples/cast/property/property.go | 6 ++ examples/cast/property/setup.go | 7 ++ examples/cast/property/setup.yml | 18 +++++ examples/program/README.md | 2 +- examples/program/main.go | 1 + 35 files changed, 476 insertions(+), 115 deletions(-) delete mode 100644 examples/_tests/cast/domain/domain.go delete mode 100644 examples/_tests/cast/models/models.go delete mode 100644 examples/_tests/cast/setup/setup.go delete mode 100644 examples/_tests/cast/setup/setup.yml create mode 100644 examples/cast/README.md create mode 100644 examples/cast/assert/assert.go create mode 100644 examples/cast/assert/setup.go create mode 100644 examples/cast/assert/setup.yml create mode 100644 examples/cast/convert/convert.go create mode 100644 examples/cast/convert/setup.go create mode 100644 examples/cast/convert/setup.yml create mode 100644 examples/cast/depth/depth.go create mode 100644 examples/cast/depth/setup.go create mode 100644 examples/cast/depth/setup.yml create mode 100644 examples/cast/expression/setup.go create mode 100644 examples/cast/expression/setup.yml create mode 100644 examples/cast/function/function.go create mode 100644 examples/cast/function/setup.go create mode 100644 examples/cast/function/setup.yml create mode 100644 examples/cast/property/property.go create mode 100644 examples/cast/property/setup.go create mode 100644 examples/cast/property/setup.yml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0b53461..6de78fd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,7 +44,7 @@ The `setup` file is parsed using an Abstract Syntax Tree. This tree contains the **Convert** options are defined **outside** of the `type Copygen Interface` and may apply to multiple functions. As a result, all `ast.Comments` must be parsed before `models.Function` and `models.Field` objects can be created. In order to do this, the `type Copygen Interface` is stored, but **NOT** analyzed until the `setup` file is traversed. -There are multiple ways to parse `ast.Comments` into `Options`, but **convert** options require the name of their respective **convert** functions _(which can't be parsed from comments)_. As a result, the most readable, efficient, and least error prone method of parsing `ast.Comments` into `Options` is to parse them when discovered; and assign them from a `CommentOptionMap` later. In addition, regex compilation is expensive — [especially in Go](https://github.com/mariomka/regex-benchmark#performance) — and avoided by only compiling unique comments once. +There are multiple ways to parse `ast.Comments` into `Options`, but **convert** options require the name of their respective **convert** functions _(which can't be parsed from comments)_. As a result, the most readable, efficient, and least error prone method of parsing `ast.Comments` into `Options` is to parse them when discovered and assign them from a `CommentOptionMap` later. In addition, regex compilation is expensive — [especially in Go](https://github.com/mariomka/regex-benchmark#performance) — and avoided by only compiling unique comments once. #### Copygen Interface @@ -64,7 +64,9 @@ Copygen supports three methods of generation for end-users _(developers)_: `.go` #### .go -`.go` code generation allows users to generate code using the programming language they are familiar with. `.go` code generation works by allowing the end-user to specify **where** _the `.go` file containing the code generation algorithm_ is, then running the file _at runtime_. In order to do this, we must use an **interpreter**. Templates are interpreted by our [temporary yaegi fork](https://github.com/switchupcb/yaegi). `models` objects are extracted via reflection and loaded into the interpreter. Then, the interpreter interprets the provided `.go` template file _(specified by the user)_ to run the `Generate()` function. +`.go` code generation allows users to generate code using the programming language they are familiar with. `.go` code generation works by allowing the end-user to specify **where** _the `.go` file containing the code generation algorithm_ is, then running the file _at runtime_. In order to do this, we must use an **interpreter**. + +Templates are interpreted by our [yaegi fork](https://github.com/switchupcb/yaegi). `models` objects are extracted via reflection and loaded into the interpreter. Then, the interpreter interprets the provided `.go` template file _(specified by the user)_ to run the `Generate()` function. #### .tmpl @@ -72,20 +74,20 @@ Copygen supports three methods of generation for end-users _(developers)_: `.go` #### programmatic -`programmatic` code generation allows users to generate code by using `copygen` as a third-party module. For more information, read the [program example README](/examples/program/README.md). +`programmatic` code generation allows users to generate code by using `copygen` as a third-party module. For more information, read the [program example](/examples/program/README.md). ## Specification ### From vs. To -From and To is used to denote the direction of a type or field. A from-field is assigned **to** a to-field. In contrast, one from-field can match many to-fields. As a result, **"From" comes before "To" when parsing** while **"To" comes before "From" in comparison**. +From and To is used to denote the direction of a type or field. A from-field is assigned **to** a to-field. In contrast, one from-field can match many to-fields. As a result, **"From" comes before "To" when parsing** while **"To" comes before "From" when matching**. ### Variable Names -| Variable | Description | -| :------- | :----------------------------------------------------------------------------------- | -| from.* | Variables preceded by from indicate from-functionality. | -| to.* | Variables preceded by to indicate to-functionality. | +| Variable | Description | +| :------- | :------------------------------------------------------ | +| from.* | Variables preceded by from indicate from-functionality. | +| to.* | Variables preceded by to indicate to-functionality. | ### Comments @@ -93,14 +95,14 @@ Comments follow [Effective Go](https://golang.org/doc/effective_go#commentary) a ### Why Pointers -Contrary to the README, pointers aren't used — on Fields — as a performance optimization. Using pointers with Fields makes it less likely for a mistake to occur during the comparison of them. For example, using a for-copy loop on a `[]models.Field`: +Contrary to the README, pointers aren't used — on `models.Fields` — as a performance optimization. Using pointers with `models.Fields` makes it less likely for a mistake to occur during their comparison. For example, using a for-copy loop on a `[]models.Field`: ```go -// A copy of field is created with a separate pointer. +// A copy of field is created with a distinct memory address. for _, field := range fields { - // fromField.To still points to the original field. - // fromField.From points to a field which is NOT the copied field (but has the same values). - if field == fromField.To { + // field.To still points to the original field's .To memory address. + // field.To.From points to the original field's memory address, which is NOT the copied field's memory address, even though both fields' fields have the same values. + if field == field.To.From { // never happens ... } @@ -109,9 +111,9 @@ for _, field := range fields { ### Anti-patterns -Using the `*models.Field` definition for a `models.Field`'s `Parent` field can be considered an anti-pattern. In the program, a `models.Type` specifically refers to the types in a function signature _(i.e `func(models.Account, models.User) *domain.Account`)_. While these types **are** fields _(which may contain other fields)_ , their actual `Type` properties are not relevant to `models.Field`. As a result, `models.Field` objects are pointed directly to maintain simplicity. +Using the `*models.Field` definition for a `models.Field`'s `Parent` field can be considered an anti-pattern. In the program, a `models.Type` specifically refers to the types in a function signature _(i.e `func(models.Account, models.User) *domain.Account`)_. While these types **are** fields _(which may contain other fields)_ , their actual `Type` properties are not relevant to `models.Field`. As a result, `models.Field` objects are pointed directly to each other for simplicity. -Using the `*models.Field` definition for a `models.Field`'s `From` and `To` fields can be placed into a `type FieldRelation`: `From` and `To` is only assigned in the matcher. While either method allows you to reference a `models.Field`'s respective `models.Field`, directly pointing `models.Field` objects adds more customizability to the program. +Using the `*models.Field` definition for a `models.Field`'s `From` and `To` fields can be placed into a `type FieldRelation` since `From` and `To` is only assigned in the matcher. While either method allows you to reference a `models.Field`'s respective `models.Field`, directly pointing `models.Field` objects adds more customizability to the program for the end user. ## CI/CD @@ -124,7 +126,9 @@ If you receive `File is not ... with -...`, use `golangci-lint run --disable-all #### Fieldalignment **Struct padding** aligns the fields of a struct to addresses in memory. The compiler does this to improve performance and prevent numerous issues on a system's architecture _(32-bit, 64-bit)_. As a result, misaligned fields add more memory-usage to a program, which can effect performance in a numerous amount of ways. For a simple explanation, view [Golang Struct Size and Memory Optimization](https://medium.com/techverito/golang-struct-size-and-memory-optimisation-b46b124f008d -). Fieldalignment can be fixed using the [fieldalignment tool](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/fieldalignment) which is installed using `go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest`. +). + +Fieldalignment can be fixed using the [fieldalignment tool](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/fieldalignment) which is installed using `go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest`. **ALWAYS COMMIT BEFORE USING `fieldalignment -fix ./cli/...`** as it may remove comments. @@ -134,7 +138,6 @@ For information on testing, read [Tests](examples/_tests/). # Roadmap -Focus on these features: - - Options, Matcher, Generator: `cast` option for [direct type conversions](https://go.dev/ref/spec#Conversions) _(as opposed to a convert function)_ +Implement the following features. - Generator: deepcopy - Parser: Fix Free-floating comments _(add structs in [`multi`](examples/_tests/multi/copygen.go) to test)_ diff --git a/README.md b/README.md index e97a6cb..e09b194 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Each example has a **README**. | [automatch](examples/automatch/) | Uses the automatch feature with depth. | | [map](examples/map/) | Uses the manual map feature. | | [tag](examples/tag/) | Uses the manual tag feature. | +| [cast](examples/cast/) | Uses the cast option modifier. | | deepcopy _(Roadmap)_ | Uses the deepcopy option. | | [error](examples/error/) | Uses `.go` templates to return an error. | | [tmpl](examples/tmpl/) | Uses `.tmpl` templates. | @@ -94,18 +95,16 @@ generated: output: ../copygen.go # Define the optional custom templates used to generate the file (.go, .tmpl supported). - template: ./generate.go + # template: ./generate.go # Define custom options (which are passed to generator options) for customization. custom: option: The possibilities are endless. ``` -_The main example ignores the template fields._ - #### setup.go -Create an interface in the specified setup file with a `type Copygen interface`. In each function, specify _the types you want to copy from_ as parameters, and _the type you want to copy to_ as return values. _This interface is inspired by **goverter**._ +Define a `type Copygen interface` in the specified setup file. In each function, specify _the types you want to copy from_ as parameters, and _the types you want to copy to_ as return values. _This interface is inspired by **goverter**._ ```go /* Specify the name of the generated file's package. */ @@ -122,22 +121,22 @@ _Copygen uses no allocation **with pointers** because Go is pass-by-value. Speci #### options -You can specify options for your Copygen functions using comments: Do **NOT** put empty lines between comments that pertain to one function. **Options are evaluated in order of declaration.** +Use comments to specify options for Copygen functions: Do **NOT** add empty lines between comments that pertain to one function. **Options are evaluated in order of declaration.** -| Option | Use | Description | Example | -| :------------------ | :------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------- | -| `map from to` | Map fields manually. | Copygen uses its [automatcher](#automatch) by default.
Override this to `map` fields to and from each other.
Regex is supported for from-fields. | `map .* package.Type.Field`
`map models.Account.ID domain.Account.ID` | -| `tag field key` | Map fields manually using tags. | Copygen uses its [automatcher](#automatch) by default.
Override this using `tag` with _regex_ and a tag key. | `tag package.Type.Field key`
`tag .* api` _(all fields)_ | -| `depth field level` | Use a specific field depth. | Copygen uses full-field [depth](#depth) by default.
Override this using `depth` with _regex_ and a [depth-level](#depth) integer. | `depth .* 2`
`depth models.Account.* 1` | -| `deepcopy field` | Deepcopy from-fields. | Copygen shallow copies fields by default.
Override this using `deepcopy` with _regex_.
For more info, view [Shallow Copy vs. Deep Copy](#shallow-copy-vs-deep-copy). | `deepcopy package.Type.Field`
`deepcopy .*` _(all fields)_ | -| `automatch field` | Use the automatcher selectively or with `map` and `tag`. | Using `map` or `tag` disables the default automatcher.
Enable it using `automatch` with _regex_.
| `automatch package.Type.Field`
`automatch models.User.*` | -| `custom option` | Specify custom function options. | Use custom options with [templates](#templates).
Returns `map[string][]string` _(trim-spaced)_. | `ignore true`
`swap false` | +| Option | Use | Description | Example | +| :------------------ | :--------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------- | +| `automatch field` | Use the automatcher selectively or with `map` and `tag` options. | Using `map` or `tag` disables the default automatcher.
Enable it again using `automatch` with _regex_. | `automatch package.Type.Field`
`automatch models.User.*` | +| `map from to` | Map fields manually. | `map` fields to and from each other.
Regex is supported for from-fields. | `map .* package.Type.Field`
`map models.Account.ID domain.Account.ID` | +| `tag field key` | Map fields manually using struct tags. | Use `tag` with _regex_ and a tag key. | `tag package.Type.Field key`
`tag .* api` _(all fields)_ | +| `depth field level` | Use a specific field depth. | Copygen uses full-field [depth](#depth) by default.
Override this using `depth` with _regex_ and a [depth-level](#depth) integer. | `depth .* 2`
`depth models.Account.* 1` | +| `deepcopy field` | Deepcopy from-fields. | Copygen shallow copies fields by default.
Override this using `deepcopy` with _regex_.
For more info, view [Shallow Copy vs. Deep Copy](#shallow-copy-vs-deep-copy). | `deepcopy package.Type.Field`
`deepcopy .*` _(all fields)_ | +| `custom option` | Specify custom function options. | Use custom options with [templates](#templates).
Returns `map[string][]string` _(trim-spaced)_. | `ignore true`
`swap false` | _[View a reference on Regex.](https://cheatography.com/davechild/cheat-sheets/regular-expressions/)_ #### Convert -In certain cases, you may want to specify a how a specific type or field is copied with a function. This can be done by defining a function with a `convert` option. +Use the `convert function field` option to control how a type or field is copied within a function. ```go /* Define the function and field this converter is applied to using regex. */ // convert .* models.User.UserID @@ -147,9 +146,15 @@ func Itoa(i int) string { } ``` +_This example converts the `models.User.UserID` value using `Itoa` within all functions (`.*`) when the `models.User.UserID` field is matched._ + +#### Cast + +Use the `matcher: cast` generator option to enable automatic casting. Use the `cast from to modifier` option to perform direct type assertion, conversion, expressions, function usage, and property usage with a matched field. For more information, read the [`cast` example](/examples/cast/). + ### Command Line -Install the command line utility: Copygen is an executable and not a dependency, so use `go install`. +Install the command line utility: Copygen is an executable, not a dependency, so use `go install`. ``` go install github.com/switchupcb/copygen@latest @@ -172,7 +177,7 @@ Run the executable with given options. copygen -yml path/to/yml ``` -_The path to the YML file is specified in reference to the current working directory._ +_The path to the YML file must be specified in reference to the current working directory._ ### Output @@ -208,11 +213,11 @@ func ModelsToDomain(tA *domain.Account, fA *models.Account, fU *models.User) { ## Customization -Copygen's method of input and output allows you to generate code not limited to copying fields. +Copygen's method of input and output lets you generate code that isn't limited to copying fields. ### Custom Objects -Custom types external to your application can be created for use in the `setup.go` file. When a file is generated, all types _(structs, interfaces, funcs)_ are copied **EXCEPT** the `type Copygen interface`. +Custom types external to your application can be defined in the setup file (`.go`): When an output file is generated, all types _(structs, interfaces, funcs)_ defined in the setup file (`.go`) are copied **EXCEPT** the `type Copygen interface`. ```go type DataTransferObject struct { @@ -230,7 +235,7 @@ func New() { ### Shallow Copy vs. Deep Copy -The library generates a [shallow copy](https://en.m.wikipedia.org/wiki/Object_copying#Shallow_copy) function by default. An easy way to deep-copy fields with the same return type is by using `new()` as or in a converter function **or** by using a custom template. +The library generates [shallow copy](https://en.m.wikipedia.org/wiki/Object_copying#Shallow_copy) functions by default. Need to deepcopy instead? Use `new()` within a `convert` or `cast` function **or** use a customized generator template. ### Templates @@ -238,7 +243,9 @@ Copygen supports three methods of code generation: `.go`, `.tmpl`, and `programm #### .go -Use `.go` files to customize the code generation algorithm. The `copygen` generator uses the [`package template Generate(*models.Generator) (string, error)`](cli/generator/template/generate.go) to generate code. As a result, **this function is required** for your `.go` templates to work. The [error example](examples/error/) modifies the `.yml` in order to use **custom `.go` template functions** that `return error`. The [`template/generate.go`](/cli//generator/template/generate.go) file provides the default code generation algorithm for generating code. +Use `.go` files to customize the code generation algorithm: The `copygen` generator uses the [`package template Generate(*models.Generator) (string, error)`](cli/generator/template/generate.go) to generate code. As a result, **this function is required** for your `.go` templates to work. + +The [error example](examples/error/) modifies the configuration file (`.yml`) to use **custom `.go` template functions** that `return error`. The [`template/generate.go`](/cli//generator/template/generate.go) file provides the default code generation algorithm for generating code. _Use of non-extracted Go Module Imports in [`generate.go` template files](cli/generator/template/generate.go) are unsupported at the current time._ @@ -254,22 +261,22 @@ Use `copygen` as a third-party module in your application. For more information, Copygen provides two methods of field-matching: `automatch` and `manual`. -_Disable the matcher from the command-line using `-xm` or programmatically by setting `Enviroment.DisableMatcher` = true._ +_You can disable the matcher using the `matcher: skip: true` option in the setup file._ ### Automatch -When fields aren't specified using [options](#options), Copygen will attempt to automatch type-fields by name and definition. Automatch will match one from-field to many to-fields. **Automatch supports field-depth** (where types are located within fields) **and recursive types** (where the same type is in another type). Automatch loads types from Go modules _(in GOPATH)_: Ensure your modules are up to date by using `go get -u `. +When a [matching option _(`automatch`,`map`,`tag`)_](#options) isn't specified on a function, Copygen automatically matches the function's fields by field name and definition. Automatch matches one from-field to many to-fields. **Automatch supports field-depth** (when fields contain fields) **and recursive types** (when the field contains itself). Automatch loads types from Go modules _(in the `GOPATH`)_: Ensure your modules are up to date by using `go get -u `. ### Manual -Using the `map` or `tag` option disables the automatcher, which allows you to manually match fields. In order to re-enable the automatcher, use the `automatch` option. Options are evaluated in order of declaration, so using `automatch .*` **after** declaring `map` and `tag` options provides an easy way to re-enable the _automatcher_ for remaining fields. +Using the `map` or `tag` option disables the automatcher, which lets you manually match fields. In order to re-enable the automatcher, use the `automatch` option. Options are evaluated in order of declaration, so using `automatch .*` **after** declaring `map` and `tag` options is an easy way to re-enable the _automatcher_ for remaining fields. #### Depth -The automatcher uses a field-based depth system. A field with a depth-level of 0 will only match itself. Increasing the depth-level allows its sub-fields to be matched. This system allows you to specify the depth-level for whole types **and** specific fields. +The automatcher uses a field-based depth system. A field with a depth-level of 0 will only match itself. Increasing the depth-level allows the field's sub-fields to be matched. This system lets you specify the depth-level for specific types and fields. ```go -// depth-level in relation to the first-level field (0). +// Depth-level in relation to the first-level field (0). type Account // 1 ID int @@ -299,17 +306,21 @@ type Account ## Usecase -#### When to Use Copygen +### When to Use Copygen Copygen's main purpose is to save you time by generating boilerplate code to map objects together. -#### Why would I do that? +### Why would I do that? + +In order to keep a program adaptable _(to new features)_, a program may contain two types of models. The first type of model is the **domain model**, which is **used throughout your application** to model business logic. For example, the [domain models of Copygen](cli/models/) focus on field relations and manipulation. + +The ideal way to store your data _(such as in a database)_ may not match your domain model. In order to amend this problem, you create a **data model**. The [data models of Copygen](cli/config/models.go) are located in its configuration loader. -In order to keep a program adaptable _(to new features)_, a program may contain two types of models. The first type of model is the **domain model** which is **used throughout your application** to model its business logic. For example, the [domain models of Copygen](cli/models/) focus on field relations and manipulation. In contrast, the ideal way to store your data _(such as in a database)_ may not match your domain model. In order to amend this problem, you create a **data model**. The [data models of Copygen](cli/config/models.go) are located in its configuration loader. In many cases, you will need a way to map these models together to exchange information from your data-model to your domain _(and vice-versa)_. It's tedious to repeatedly do this in the application _(through assignment or function definitions)_. Copygen solves this problem. +In many cases, you must map these models together to exchange information from your data-model to your domain _(and vice-versa)_. It's tedious to do this manually — via assignment or function definition — for each update in the application: Copygen solves this problem. -#### Custom Generation +### Custom Generation -Copygen's customizability with templates allows you to generate any code **based on types** _(and their respective fields, tags, etc)_. +Copygen's customizability with templates lets you generate any code **based on types** _(and their respective fields, tags, etc)_. | Example | Description | | :------ | :-------------------------------------------------------------------------------------- | @@ -324,11 +335,11 @@ Copygen uses a [GPLv3 License](https://www.gnu.org/licenses/gpl-3.0.en.html). An ### What can I do? -**Code generated by Copygen can be used without restriction (including proprietary and commercial usage)** since generated code is not considered a derivative work. In contrast, **modifications** to the _Copygen Software Source Code_ and/or implementing Copygen in a larger work **programmatically** requires you to [adhere to the GPLv3 License](https://www.gnu.org/licenses/gpl-faq.html). These restrictions do **NOT** apply to template or example files, as long as those files don't generate Copygen itself. +**Code generated by Copygen can be used without restriction (including proprietary and commercial usage)** since generated code is not considered a derivative work. In contrast, **modifications** to the _Copygen Software Source Code_ or implementing Copygen in a larger work **programmatically** requires you to [adhere to the GPLv3 License](https://www.gnu.org/licenses/gpl-faq.html). These restrictions do **NOT** apply to template or example files, as long as those files don't generate Copygen itself. ### License Exception -A license exception allows you to modify and/or use Copygen programmatically **without restriction**. In order to purchase a license exception, please contact SwitchUpCB using the [Copygen License Exception Inquiry Form](https://switchupcb.com/copygen-license-exception/). +A license exception lets you modify and use Copygen programmatically **without restriction**. In order to purchase a license exception, please contact SwitchUpCB using the [Copygen License Exception Inquiry Form](https://switchupcb.com/copygen-license-exception/). ## Contributing diff --git a/cli/cli.go b/cli/cli.go index 26e7976..52ac573 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -14,10 +14,9 @@ import ( // Environment represents the copygen environment. type Environment struct { - YMLPath string // The .yml file path used as a configuration file. - Output bool // Whether to print the generated code to stdout. - Write bool // Whether to write the generated code to a file. - DisableMatcher bool // Whether to disable the matcher. + YMLPath string // The .yml file path used as a configuration file. + Output bool // Whether to print the generated code to stdout. + Write bool // Whether to write the generated code to a file. } // CLI runs copygen from a Command Line Interface and returns the exit status. @@ -41,9 +40,8 @@ func CLI() int { func (e *Environment) parseArgs() error { // define the command line arguments. var ( - ymlpath = flag.String("yml", "", "The path to the .yml flag used for code generation (from the current working directory).") - output = flag.Bool("o", false, "Use -o to print generated code to the screen.") - disablematcher = flag.Bool("xm", false, "Use -xm to disable the matcher.") + ymlpath = flag.String("yml", "", "The path to the .yml flag used for code generation (from the current working directory).") + output = flag.Bool("o", false, "Use -o to print generated code to the screen.") ) // parse the command line arguments. @@ -56,7 +54,6 @@ func (e *Environment) parseArgs() error { e.YMLPath = *ymlpath e.Output = *output e.Write = true - e.DisableMatcher = *disablematcher return nil } @@ -75,7 +72,7 @@ func (e *Environment) Run() (string, error) { } // The matcher is run on the parsed data (to create the objects used during generation). - if !e.DisableMatcher { + if !gen.Options.Matcher.Skip { if err = matcher.Match(gen); err != nil { return "", fmt.Errorf("%w", err) } diff --git a/cli/config/models.go b/cli/config/models.go index 498ef6e..dae8e4d 100644 --- a/cli/config/models.go +++ b/cli/config/models.go @@ -5,6 +5,7 @@ package config type YML struct { Options map[string]interface{} `yaml:"custom"` Generated Generated `yaml:"generated"` + Matcher Matcher `yaml:"matcher"` } // Generated represents generated properties of the YML file. @@ -13,3 +14,23 @@ type Generated struct { Output string `yaml:"output"` Template string `yaml:"template"` } + +// Matcher represents matcher properties of the YML file. +type Matcher struct { + Skip bool `yaml:"skip"` + Cast Cast `yaml:"cast"` +} + +// Cast represents matcher cast properties of the YML file. +type Cast struct { + Depth int `yaml:"depth"` + Enabled bool `yaml:"enabled"` + Disabled Disabled `yaml:"disabled"` +} + +// Disabled represents matcher cast feature flags of the YML file. +type Disabled struct { + AssignObjectInterface bool `yaml:"assignObjectInterface"` + AssertInterfaceObject bool `yaml:"assertInterfaceObject"` + Convert bool `yaml:"convert"` +} diff --git a/cli/config/yml.go b/cli/config/yml.go index 6dc8a65..b5d2b8a 100644 --- a/cli/config/yml.go +++ b/cli/config/yml.go @@ -51,6 +51,14 @@ func ParseYML(yml YML) *models.Generator { Outpath: yml.Generated.Output, Tempath: yml.Generated.Template, Options: models.GeneratorOptions{ + Matcher: models.MatcherOptions{ + Skip: yml.Matcher.Skip, + AutoCast: yml.Matcher.Cast.Enabled, + CastDepth: yml.Matcher.Cast.Depth, + DisableAssignObjectInterface: yml.Matcher.Cast.Disabled.AssignObjectInterface, + DisableAssertInterfaceObject: yml.Matcher.Cast.Disabled.AssertInterfaceObject, + DisableConvert: yml.Matcher.Cast.Disabled.Convert, + }, Custom: yml.Options, }, } diff --git a/cli/models/generator.go b/cli/models/generator.go index c8792b4..92fdeae 100644 --- a/cli/models/generator.go +++ b/cli/models/generator.go @@ -10,7 +10,18 @@ type Generator struct { Keep []byte // The code that is kept from the setup file. } -// GeneratorOptions represent options for a Generator. +// GeneratorOptions represents options for a Generator. type GeneratorOptions struct { - Custom map[string]interface{} // The custom options of a generator. + Custom map[string]interface{} // The custom options of a generator. + Matcher MatcherOptions // The options for the matcher of a generator. +} + +// MatcherOptions represents options for the Generator's matcher. +type MatcherOptions struct { + CastDepth int // The option that sets the maximum depth for automatic casting. + Skip bool // The option that skips the matcher. + AutoCast bool // The option that enables automatic casting. + DisableAssignObjectInterface bool // The cast option feature flag that disables assignment of objects to interfaces. + DisableAssertInterfaceObject bool // The cast option feature flag that disables assignment of interfaces to objects. + DisableConvert bool // The cast option feature flag that disables type conversion. } diff --git a/examples/_tests/README.md b/examples/_tests/README.md index bf0f962..c22aa06 100644 --- a/examples/_tests/README.md +++ b/examples/_tests/README.md @@ -10,7 +10,6 @@ The command line interface is straightforward. The loader uses a tested library. | :-------- | :------------------------------------------------------------------- | | Alias | Uses an alias import (for a copied struct). | | Automap | Uses the `automatch` option with a manual matcher option (`map`). | -| Cast | Uses the `cast` option for direct type conversion. | | Cyclic | Uses a nested struct (containing a field of the same type). | | Duplicate | Defines two structs with duplicate definitions, but not names. | | Import | Imports a package in the setup file, that the output file exists in. | diff --git a/examples/_tests/cast/domain/domain.go b/examples/_tests/cast/domain/domain.go deleted file mode 100644 index f93424a..0000000 --- a/examples/_tests/cast/domain/domain.go +++ /dev/null @@ -1,13 +0,0 @@ -// Package domain contains business logic models. -package domain - -type ReversedString string - -// Account represents a user account. -type Account struct { - ID int - Name string - Email string - SuperString string - ReversedString ReversedString -} diff --git a/examples/_tests/cast/models/models.go b/examples/_tests/cast/models/models.go deleted file mode 100644 index 8f3b2e5..0000000 --- a/examples/_tests/cast/models/models.go +++ /dev/null @@ -1,14 +0,0 @@ -// Package models contains data storage models (i.e database). -package models - -type SuperString string - -// Account represents the data model for account. -type Account struct { - ID int - Name string - Password string - Email string - SuperString SuperString - ReversedString string -} diff --git a/examples/_tests/cast/setup/setup.go b/examples/_tests/cast/setup/setup.go deleted file mode 100644 index f30dbf4..0000000 --- a/examples/_tests/cast/setup/setup.go +++ /dev/null @@ -1,14 +0,0 @@ -// Package copygen contains the setup information for copygen generated code. -package copygen - -import ( - "github.com/switchupcb/copygen/examples/_tests/cast/domain" - "github.com/switchupcb/copygen/examples/_tests/cast/models" -) - -// Copygen defines the functions that will be generated. -type Copygen interface { - // cast domain.ReversedString .* - // cast string SuperString - ModelsToDomain(*models.Account) *domain.Account -} diff --git a/examples/_tests/cast/setup/setup.yml b/examples/_tests/cast/setup/setup.yml deleted file mode 100644 index d8770db..0000000 --- a/examples/_tests/cast/setup/setup.yml +++ /dev/null @@ -1,4 +0,0 @@ -# Define where the code will be generated. -generated: - setup: ./setup.go - output: ../copygen.go diff --git a/examples/_tests/examples_test.go b/examples/_tests/examples_test.go index 8245f4d..9ce956c 100644 --- a/examples/_tests/examples_test.go +++ b/examples/_tests/examples_test.go @@ -36,6 +36,38 @@ var ( ymlpath: "basic/setup/setup.yml", wantpath: "basic/copygen.go", }, + /* + { + name: "cast-assert", + ymlpath: "cast/assert/setup.yml", + wantpath: "cast/assert/copygen.go", + }, + { + name: "cast-convert", + ymlpath: "cast/convert/setup.yml", + wantpath: "cast/convert/copygen.go", + }, + { + name: "cast-depth", + ymlpath: "cast/depth/setup.yml", + wantpath: "cast/depth/copygen.go", + }, + { + name: "cast-expression", + ymlpath: "cast/expression/setup.yml", + wantpath: "cast/expression/copygen.go", + }, + { + name: "cast-function", + ymlpath: "cast/function/setup.yml", + wantpath: "cast/function/copygen.go", + }, + { + name: "cast-property", + ymlpath: "cast/property/setup.yml", + wantpath: "cast/property/copygen.go", + }, + */ /* { name: "deepcopy", @@ -68,13 +100,6 @@ var ( ymlpath: "_tests/automap/setup/setup.yml", wantpath: "_tests/automap/copygen.go", }, - /* - { - name: "cast", - ymlpath: "_tests/cast/setup/setup.yml", - wantpath: "_tests/cast/copygen.go", - }, - */ { name: "cyclic", ymlpath: "_tests/cyclic/setup/setup.yml", diff --git a/examples/_tests/multi/copygen.go b/examples/_tests/multi/copygen.go index 5d68991..17ce17b 100644 --- a/examples/_tests/multi/copygen.go +++ b/examples/_tests/multi/copygen.go @@ -39,6 +39,11 @@ func NoMatchBasic(tP Placeholder, fP Placeholder) { // Placeholder fields } +// NoMatchBasicAlias copies a bool to a Placeholder. +func NoMatchBasicAlias(tP Placeholder, fb bool) { + // Placeholder fields +} + // NoMatchBasicExternal copies a *Placeholder to a external.Placeholder, *external.Placeholder, bool. func NoMatchBasicExternal(tP external.Placeholder, tP1 *external.Placeholder, tb bool, fP *Placeholder) { // external.Placeholder fields diff --git a/examples/_tests/multi/setup/setup.go b/examples/_tests/multi/setup/setup.go index 5d40ce2..e10f28d 100644 --- a/examples/_tests/multi/setup/setup.go +++ b/examples/_tests/multi/setup/setup.go @@ -12,6 +12,7 @@ type Placeholder bool // Copygen defines the functions that will be generated. type Copygen interface { NoMatchBasic(A Placeholder) (B Placeholder) + NoMatchBasicAlias(B bool) (B Placeholder) NoMatchBasicExternal(A *Placeholder) (A external.Placeholder, B *external.Placeholder, C bool) NoMatchArraySimple([16]byte) Collection NoMatchSliceSimple([]string) Collection diff --git a/examples/_tests/option/setup/setup.yml b/examples/_tests/option/setup/setup.yml index 6dab1e6..d04c5a6 100644 --- a/examples/_tests/option/setup/setup.yml +++ b/examples/_tests/option/setup/setup.yml @@ -6,6 +6,14 @@ generated: # Define the optional custom templates used to generate the file (.go, .tmpl supported). template: ../template/generate.go + +# Define how the matcher will work. +matcher: + skip: false # Determine whether the matcher should be skipped (default: false). + cast: + enabled: true # Determine whether automatic casting is enabled (default: false). + depth: 1 # Set the maximum depth to attempt automatic casting (default: 1) + # Define custom options (which are passed to generator options) for customization. custom: option: The possibilities are endless. \ No newline at end of file diff --git a/examples/cast/README.md b/examples/cast/README.md new file mode 100644 index 0000000..31d1a82 --- /dev/null +++ b/examples/cast/README.md @@ -0,0 +1,93 @@ +# Example: Cast + +These examples use the `cast from to modifier` option to modify fields before assignment: This lets you perform direct type assertion, conversion, expressions, function usage, and property usage with a matched field. + +| Example | Description | +| :-------------------------------------- | :--------------------------------------------------------------------- | +| [Assert](examples/cast/assert/) | Use `cast` generator options to enable automatic type assertion. | +| [Convert](examples/convert/) | Use `cast` function option modifiers to enable direct type conversion. | +| [Depth](examples/cast/depth/) | Use `cast` option modifier `depth` to change autocasting behavior. | +| [Expression](examples/cast/expression/) | Use `cast` option modifier `expression` to evaluate an expression. | +| [Function](examples/cast/function/) | Use `cast` option modifier `func()` to call a function. | +| [Property](examples/cast/property/) | Use `cast` option modifier `.Property` to reference a type property. | + +## Modifiers + +Copygen handles assignability of fields with identical field names and definitions by default. When you must match fields using identical field names, but different definitions, enable Copygen type casting: This option modifies the matcher to match fields using an automatic or provided modifier. + +_Typecasting is not a feature in the Go programming language._ + +### Assertion + +[Type assertion](https://go.dev/ref/spec#Type_assertions) provides access to an interface's underlying concrete values: This lets you use the underlying concrete type's fields and functions. Enabling **type assertion** in Copygen applies to the following cases. +- Assignment of objects to interfaces: `interface = type`. +- Assertion of interfaces into objects: `type = interface.(type)`. + +### Conversion + +[Type conversion](https://go.dev/ref/spec#Conversions) changes the type of an expression to the type specified by the conversion: This lets you convert types into other types. Enabling **type conversion** in Copygen enables type conversion at the specified **depth**. + +_For example, `custom = custom(bool)` represents a type conversion at a depth of one._ + +### Expressions + +An expression is a value: Multiple expressions can be used to create statements which perform operations. Copygen `cast` modifiers are copied directly from the option. So using literal expressions _(i.e `* 2`)_, type functions _(i.e `.String()`)_, and type properties _(i.e `.Property`)_ is allowed. + +_For example, `cast model.Type.Field domain.Type.Field * 2` results in the assignment `model.Type.Field = domain.Type.Field * 2` upon a match of the fields._ + +## Usage + +There are multiple ways to enable Copygen type casting. + +### Generator Options + +```yml +# Define how the matcher will work. +matcher: + # Skip the matcher (default: false). + skip: false + + # Control the matcher type cast behavior. + cast: + # Enable automatic casting (default: false). + enabled: true + + # Set the maximum depth for automatic casting (default: 1) + depth: 1 + + # Disable certain features of casting. + disabled: + + # Disable assignment of objects to interfaces (default: false). + assignObjectInterface: false + + # Disable assertion of interfaces to objects (default: false). + assertInterfaceObject: false + + # Disable type conversion (default: false). + convert: false +``` + +### Function Option + +Use the `cast from to modifier` function option to enable casting for the respective function. Regex is supported for from-fields. The **modifier** flag is optional. +- `cast .* package.Type.Field` +- `cast models.Account.ID domain.Account.ID .String()` + +### Option Modifier + +Use the `-cast modifier` function option modifier to enable casting for the respective function option. The **modifier** flag is optional. +- `automatch package.Type.Field -cast` +- `automatch models.User.* -cast 1` +- `map .* package.Type.Field -cast .String()` +- `map .* package.Type.Field -cast Convert()` +- `tag package.Type.Field key -cast .Property` +- `tag .* api -cast + 100` + +## Behavior + +The `cast` option is a **modifier**: It modifies the matching algorithm or assignment of fields. It can't be used to match fields in a direct manner _(unlike `automatch`, `map`, and `tag`)_. + +When a `modifier` is **NOT** provided, Copygen will perform **automatic typecasting** using type assertion or conversion at the specified depth level. Otherwise, the definition of the **provided modifier** will be evaluated to match fields. + +_For example, `map .* package.Type.Field -cast .String()` matches from-fields with the name `Field` (from `package.Type.Field`) and definition `string` (since `.String()` returns `string`) when depth is greater than 0._ \ No newline at end of file diff --git a/examples/cast/assert/assert.go b/examples/cast/assert/assert.go new file mode 100644 index 0000000..86e9a7f --- /dev/null +++ b/examples/cast/assert/assert.go @@ -0,0 +1,11 @@ +package assert + +// Animal represents an abstraction of an animal. +type Animal interface { + Survive() +} + +// Human represents an animal that belongs to the species Homo sapiens. +type Human struct{} + +func (Human) Survive() {} diff --git a/examples/cast/assert/setup.go b/examples/cast/assert/setup.go new file mode 100644 index 0000000..ae6b010 --- /dev/null +++ b/examples/cast/assert/setup.go @@ -0,0 +1,9 @@ +package assert + +// Copygen defines the functions that will be generated. +type Copygen interface { + AssertHuman(Human) Animal + AssertPointer(*Human) Animal + AssertInterface(Animal) Human + AssertInterfacePointer(*Animal) Human +} diff --git a/examples/cast/assert/setup.yml b/examples/cast/assert/setup.yml new file mode 100644 index 0000000..e9cf6fe --- /dev/null +++ b/examples/cast/assert/setup.yml @@ -0,0 +1,22 @@ +# Define where the code will be generated. +generated: + setup: ./setup.go + output: ./copygen.go + + # Define the optional custom templates used to generate the file (.go, .tmpl supported). + # template: ./generate.go + +# Define how the matcher will work. +matcher: + skip: false # Skip the matcher (default: false). + cast: + enabled: true # Enable automatic casting (default: false). + depth: 1 # Set the maximum depth for automatic casting (default: 1) + disabled: + assignObjectInterface: false # Disable assignment of objects to interfaces (default: false). + assertInterfaceObject: false # Disable assertion of interfaces to objects (default: false). + convert: false # Disable type conversion (default: false). + +# Define custom options (which are passed to generator options) for customization. +# custom: +# option: The possibilities are endless. \ No newline at end of file diff --git a/examples/cast/convert/convert.go b/examples/cast/convert/convert.go new file mode 100644 index 0000000..1020004 --- /dev/null +++ b/examples/cast/convert/convert.go @@ -0,0 +1,15 @@ +package convert + +// Placeholder represents a custom type alias for a boolean. +// +// A constant boolean can be assigned to a Placeholder. +// var placeholder Placeholder +// placeholder = true +// +// To assign a Placeholder variable to a boolean variable, type conversion must occur. +// var b boolean +// boolean = bool(placeholder) +// +// To assign a boolean variable to a Placeholder variable, type conversion must occur. +// placeholder = Placeholder(boolean) +type Placeholder bool diff --git a/examples/cast/convert/setup.go b/examples/cast/convert/setup.go new file mode 100644 index 0000000..006012f --- /dev/null +++ b/examples/cast/convert/setup.go @@ -0,0 +1,11 @@ +package convert + +// Copygen defines the functions that will be generated. +type Copygen interface { + // cast bool Placeholder + ConvertBool(bool) Placeholder + // cast Placeholder bool + ConvertPlaceholder(Placeholder) bool + // map Placeholder bool -cast + MapConvertPlaceholder(Placeholder) bool +} diff --git a/examples/cast/convert/setup.yml b/examples/cast/convert/setup.yml new file mode 100644 index 0000000..50b6074 --- /dev/null +++ b/examples/cast/convert/setup.yml @@ -0,0 +1,18 @@ +# Define where the code will be generated. +generated: + setup: ./setup.go + output: ./copygen.go + + # Define the optional custom templates used to generate the file (.go, .tmpl supported). + # template: ./generate.go + +# Define how the matcher will work. +matcher: + skip: false # Determine whether the matcher should be skipped (default: false). + cast: + enabled: false # Determine whether automatic casting is enabled (default: false). + depth: 1 # Set the maximum depth to attempt automatic casting (default: 1) + +# Define custom options (which are passed to generator options) for customization. +# custom: +# option: The possibilities are endless. \ No newline at end of file diff --git a/examples/cast/depth/depth.go b/examples/cast/depth/depth.go new file mode 100644 index 0000000..8d2961e --- /dev/null +++ b/examples/cast/depth/depth.go @@ -0,0 +1,4 @@ +package depth + +type One []string +type Two One diff --git a/examples/cast/depth/setup.go b/examples/cast/depth/setup.go new file mode 100644 index 0000000..7f766c9 --- /dev/null +++ b/examples/cast/depth/setup.go @@ -0,0 +1,15 @@ +package depth + +type Copygen interface { + ZeroDepth([]string) []string + DefaultDepth([]string) One + + // cast .* .* 2 + CustomDepth([]string) Two + + // cast .* .* 2 + ReverseDepth(Two) []string + + // cast .* .* 0 + DisableCast([]string) []string +} diff --git a/examples/cast/depth/setup.yml b/examples/cast/depth/setup.yml new file mode 100644 index 0000000..7b1855f --- /dev/null +++ b/examples/cast/depth/setup.yml @@ -0,0 +1,18 @@ +# Define where the code will be generated. +generated: + setup: ./setup.go + output: ./copygen.go + + # Define the optional custom templates used to generate the file (.go, .tmpl supported). + # template: ./generate.go + +# Define how the matcher will work. +matcher: + skip: false # Determine whether the matcher should be skipped (default: false). + cast: + enabled: true # Determine whether automatic casting is enabled (default: false). + depth: 1 # Set the maximum depth to attempt automatic casting (default: 1) + +# Define custom options (which are passed to generator options) for customization. +# custom: +# option: The possibilities are endless. \ No newline at end of file diff --git a/examples/cast/expression/setup.go b/examples/cast/expression/setup.go new file mode 100644 index 0000000..f0ef401 --- /dev/null +++ b/examples/cast/expression/setup.go @@ -0,0 +1,10 @@ +package expression + +// Copygen defines the functions that will be generated. +type Copygen interface { + // cast int int * 2 + ExprDouble(int) int + + // map bool bool -cast ^ true + MapExprXOR(bool) bool +} diff --git a/examples/cast/expression/setup.yml b/examples/cast/expression/setup.yml new file mode 100644 index 0000000..8fb6e56 --- /dev/null +++ b/examples/cast/expression/setup.yml @@ -0,0 +1,18 @@ +# Define where the code will be generated. +generated: + setup: ./setup.go + output: ./copygen.go + + # Define the optional custom templates used to generate the file (.go, .tmpl supported). + # template: ./generate.go + +# Define how the matcher will work. +matcher: + skip: false # Determine whether the matcher should be skipped (default: false). + cast: + enabled: true # Determine whether automatic casting is enabled (default: false). + depth: 1 # Set the maximum depth to attempt automatic casting (default: 1) + +# Define custom options (which are passed to generator options) for customization. +# custom: +# option: The possibilities are endless. \ No newline at end of file diff --git a/examples/cast/function/function.go b/examples/cast/function/function.go new file mode 100644 index 0000000..cf2fd7f --- /dev/null +++ b/examples/cast/function/function.go @@ -0,0 +1,13 @@ +package function + +// Custom represents a custom type. +type Custom struct{} + +func (Custom) String() string { + return "" +} + +// Convert converts a Custom struct into a string. +func Convert(Custom) string { + return "" +} diff --git a/examples/cast/function/setup.go b/examples/cast/function/setup.go new file mode 100644 index 0000000..5fb5786 --- /dev/null +++ b/examples/cast/function/setup.go @@ -0,0 +1,10 @@ +package function + +// Copygen defines the functions that will be generated. +type Copygen interface { + // cast Custom string .String() + TypeFuncString(Custom) string + + // cast Custom string Convert() + FuncString(Custom) string +} diff --git a/examples/cast/function/setup.yml b/examples/cast/function/setup.yml new file mode 100644 index 0000000..8fb6e56 --- /dev/null +++ b/examples/cast/function/setup.yml @@ -0,0 +1,18 @@ +# Define where the code will be generated. +generated: + setup: ./setup.go + output: ./copygen.go + + # Define the optional custom templates used to generate the file (.go, .tmpl supported). + # template: ./generate.go + +# Define how the matcher will work. +matcher: + skip: false # Determine whether the matcher should be skipped (default: false). + cast: + enabled: true # Determine whether automatic casting is enabled (default: false). + depth: 1 # Set the maximum depth to attempt automatic casting (default: 1) + +# Define custom options (which are passed to generator options) for customization. +# custom: +# option: The possibilities are endless. \ No newline at end of file diff --git a/examples/cast/property/property.go b/examples/cast/property/property.go new file mode 100644 index 0000000..aa59847 --- /dev/null +++ b/examples/cast/property/property.go @@ -0,0 +1,6 @@ +package property + +// Circle represents a shape. +type Circle struct { + Radius float32 +} diff --git a/examples/cast/property/setup.go b/examples/cast/property/setup.go new file mode 100644 index 0000000..bebedd4 --- /dev/null +++ b/examples/cast/property/setup.go @@ -0,0 +1,7 @@ +package property + +// Copygen defines the functions that will be generated. +type Copygen interface { + // cast .* float32 .Radius + PropertyFloat(Circle) float32 +} diff --git a/examples/cast/property/setup.yml b/examples/cast/property/setup.yml new file mode 100644 index 0000000..8fb6e56 --- /dev/null +++ b/examples/cast/property/setup.yml @@ -0,0 +1,18 @@ +# Define where the code will be generated. +generated: + setup: ./setup.go + output: ./copygen.go + + # Define the optional custom templates used to generate the file (.go, .tmpl supported). + # template: ./generate.go + +# Define how the matcher will work. +matcher: + skip: false # Determine whether the matcher should be skipped (default: false). + cast: + enabled: true # Determine whether automatic casting is enabled (default: false). + depth: 1 # Set the maximum depth to attempt automatic casting (default: 1) + +# Define custom options (which are passed to generator options) for customization. +# custom: +# option: The possibilities are endless. \ No newline at end of file diff --git a/examples/program/README.md b/examples/program/README.md index c3680c4..6c7c1ed 100644 --- a/examples/program/README.md +++ b/examples/program/README.md @@ -4,7 +4,7 @@ Using Copygen programmatically allows you to implement custom loaders, matchers, ### Disclaimer -Copygen uses a [GPLv3 License](../../README.md#license). An exception is provided for template and example files, which are licensed under the [MIT License](cli/generator/template/LICENSE.md). While this allows you to use the generator tool without restriction, **proprietary programmatic usage** of Copygen requires the purchase of a license exception. In order to purchase a license exception, please contact SwitchUpCB using the [Copygen License Exception Inquiry Form](https://switchupcb.com/copygen-license-exception/). For more information, please read [What Can I do?](../../README.md#what-can-i-do) +Copygen uses a [GPLv3 License](../../README.md#license). An exception is provided for template and example files, which are licensed under the [MIT License](cli/generator/template/LICENSE.md). While this allows you to use the generator tool without restriction, **proprietary programmatic usage** of Copygen requires the purchase of a license exception. In order to purchase a license exception, please contact SwitchUpCB using the [Copygen License Exception Inquiry Form](https://switchupcb.com/copygen-license-exception/). For more information, read [What Can I do?](../../README.md#what-can-i-do) ## Standard diff --git a/examples/program/main.go b/examples/program/main.go index b1a7f78..70d53a1 100644 --- a/examples/program/main.go +++ b/examples/program/main.go @@ -17,6 +17,7 @@ func main() { code, err := env.Run() if err != nil { fmt.Printf("%v", err) + return } From 3e257fb281bfe3e68585954bdb2084caf92029ff Mon Sep 17 00:00:00 2001 From: switchupcb Date: Wed, 29 Mar 2023 17:49:23 -0500 Subject: [PATCH 2/8] update convert option help msg --- cli/parser/options/convert.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/parser/options/convert.go b/cli/parser/options/convert.go index a3bb81f..b9558d0 100644 --- a/cli/parser/options/convert.go +++ b/cli/parser/options/convert.go @@ -27,12 +27,12 @@ func ParseConvert(option, value string) (*Option, error) { funcRe, err := regexp.Compile("^" + splitoption[0] + "$") if err != nil { - return nil, fmt.Errorf("an error occurred compiling the regex for the first field in the %s option: %q\n%w", CategoryConvert, option, err) + return nil, fmt.Errorf("an error occurred compiling the regex for the function in the %s option: %q\n%w", CategoryConvert, option, err) } fieldRe, err := regexp.Compile("^" + splitoption[1] + "$") if err != nil { - return nil, fmt.Errorf("an error occurred compiling the regex for the second field in the %s option: %q\n%w", CategoryConvert, option, err) + return nil, fmt.Errorf("an error occurred compiling the regex for the from-field in the %s option: %q\n%w", CategoryConvert, option, err) } return &Option{ From 762612536b9cb8a4e0b677e3235faf77c7e0eb0f Mon Sep 17 00:00:00 2001 From: switchupcb <81384235+switchupcb@users.noreply.github.com> Date: Thu, 20 Feb 2025 13:55:07 -0600 Subject: [PATCH 3/8] code dump --- cli/parser/function.go | 2 +- cli/parser/options/cast.go | 63 ++++++++++++++++++++++++++++++++++ cli/parser/options/convert.go | 4 +-- cli/parser/options/deepcopy.go | 2 +- cli/parser/options/depth.go | 4 +-- cli/parser/options/matcher.go | 14 ++++---- cli/parser/options/options.go | 12 +++++-- 7 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 cli/parser/options/cast.go diff --git a/cli/parser/function.go b/cli/parser/function.go index 2a5c6e8..acffc4e 100644 --- a/cli/parser/function.go +++ b/cli/parser/function.go @@ -10,7 +10,7 @@ import ( ) // parseFunctions parses the AST for functions in the setup file. -// astcopygen is used to assign options from *ast.Comments. +// The copygen *ast.InterfaceType is used to assign options from *ast.Comments. func (p *Parser) parseFunctions(copygen *ast.InterfaceType) ([]models.Function, error) { numMethods := len(copygen.Methods.List) if numMethods == 0 { diff --git a/cli/parser/options/cast.go b/cli/parser/options/cast.go new file mode 100644 index 0000000..f9facee --- /dev/null +++ b/cli/parser/options/cast.go @@ -0,0 +1,63 @@ +package options + +import ( + "fmt" + "regexp" + "strings" + + "github.com/switchupcb/copygen/cli/models" +) + +const ( + CategoryCast = "cast" + + // FormatCast represents an end-user facing format for a cast option. + //