Contributions to this project must be accompanied by a Contributor License Agreement.
You or your employer retain the copyright to your contribution: Accepting this agreement 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 the context of this domain, a Type refers to the types used in a function (as parameters or results) [e.g., func example(x TypeX) y TypeY], and not a type used to define variables (e.g., type example string).
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. So, 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). So, 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. We must use an interpreter to provide this functionality.
.go templates are interpreted by a yaegi fork.
modelsobjects are extracted via reflection and loaded into the interpreter.- Then, the interpreter interprets the provided
.gotemplate file (specified by the user) to run theGenerate()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 lets users generate code by using copygen as a third-party module. For more information, read the program example.
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. So, "From" comes before "To" when parsing while "To" comes before "From" when matching.
| 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 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:
// A copy of field is created with a distinct memory address.
for _, field := range fields {
// 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
...
}
}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. So, 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 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.
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.64.5 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). So, 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.
Implement the following features.
- Generator: deepcopy
- Parser: Fix Free-floating comments (add structs in
multito test)