Contributions to this project must be accompanied by a Contributor License Agreement. You or your employer retain the copyright to your contribution. This simply gives us permission to use and redistribute your contributions as part of the project.
Pull requests must pass all CI/CD measures and follow the code specification.
The domain of Copygen lies in field manipulation. The program uses provided types to determine the fields we must assign. In this context, a Type
refers to the types used in a function (as parameters or results) rather than a type used to define variables.
The repository consists of a detailed README, examples, and command line interface.
The command-line interface (cli) consists of 5 packages.
Package | Description |
---|---|
cli | Contains the primary logic used to parse arguments and run the copygen command-line application. |
models | Contains models based on the application's functionality (business logic). |
config | Contains external loaders used to configure the file settings and command line options. |
parser | Uses an Abstract Syntax Tree (AST) and go/types to parse a setup file for fields. |
matcher | Contains application logic to match fields to each other. |
generator | Contains the generator logic used to generate code (and interpret templates). |
Read program for an overview of the application's code.
A setup
file's abstract syntax tree is traversed once, but involves four processes.
The setup
file is parsed using an Abstract Syntax Tree. This tree contains the type Copygen Interface
but also code that must be kept in the generated output
file. For example, the package declaration, file imports, convert functions, and custom types all exist outside of the type Copygen Interface
. Instead of storing these declarations and attempting to regenerate them, we simply discard declarations — from the setup
file's AST — that won't be kept: In this case, the type Copygen Interface
and ast.Comments
(that refer to Options
).
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 — and avoided by only compiling unique comments once.
The type Copygen interface
is parsed to setup the models.Function
and models.Field
objects used in the Matcher
and Generator
.
- go/types Contents (Types, A -> B)
- go/packages Package Object
- go/types Func (Signature)
- go/types Types
The go/types
package provides all of the other important information except for alias import names. In order to assign aliased or non-aliased import names to models.Field
, the imports of the setup
file are mapped to a package path, then assigned to fields prior to matching.
Copygen supports three methods of generation for end-users (developers): .go
, .tmpl
, and programmatic
.
.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. 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
code generation allows users to generate code using text/templates
. .tmpl
code generation works by allowing the end-user to specify where the .tmpl
file containing the code generation algorithm is, then parsing and executing the file at runtime.
programmatic
code generation allows users to generate code by using copygen
as a third-party module. For more information, read the program example README.
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.
Variable | Description |
---|---|
from.* | Variables preceded by from indicate from-functionality. |
to.* | Variables preceded by to indicate to-functionality. |
Comments follow Effective Go and explain why more than what (unless the "what" isn't intuitive).
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
:
// A copy of field is created with a separate pointer.
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 {
// never happens
...
}
}
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 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.
Copygen uses golangci-lint in order to statically analyze code. You can install golangci-lint with go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.52.1
and run it using golangci-lint run
. If you receive a diff
error, you must add a diff
tool in your PATH. There is one located in the Git
bin.
If you receive File is not ... with -...
, use golangci-lint run --disable-all --no-config -Egofmt --fix
.
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. Fieldalignment can be fixed using the fieldalignment tool 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.
For information on testing, read Tests.
Focus on these features:
- Options, Matcher, Generator:
cast
option for direct type conversions (as opposed to a convert function) - Generator: deepcopy
- Parser: Fix Free-floating comments (add structs in
multi
to test)