-
Notifications
You must be signed in to change notification settings - Fork 34
Generate
./godelw generate
runs "go generate" tasks in a project based on configuration. This task is not a built-in task, so
we will add it as a plugin task.
-
${GOPATH}/src/${PROJECT_PATH}
exists, is the working directory and is initialized as a Git repository - Project contains
godel
andgodelw
- Project contains
main.go
- Project contains
.gitignore
that ignores GoLand files - Project contains
echo/echo.go
,echo/echo_test.go
andecho/echoer.go
-
godel/config/dist-plugin.yml
is configured to buildechgo2
- Project is tagged as 0.0.1
-
godel/config/dist-plugin.yml
is configured to create distributions forechgo
- Project is tagged as 0.0.2
-
dockerctx
directory exists andgodel/config/dist-plugin.yml
is configured to build Docker images for the product - Go files have license headers
The go-generate tool provides a gödel plugin that allows go generate
tasks
to be defined, run and verified. The plugin identifier is "com.palantir.godel-generate-plugin:generate-plugin:1.0.0", and it
is available on Bintray.
Add the plugin definition to godel/config/godel.yml
:
➜ echo 'plugins:
resolvers:
- "https://palantir.bintray.com/releases/{{GroupPath}}/{{Product}}/{{Version}}/{{Product}}-{{Version}}-{{OS}}-{{Arch}}.tgz"
plugins:
- locator:
id: "com.palantir.godel-generate-plugin:generate-plugin:1.0.0"
exclude:
names:
- "\\\\..+"
- "vendor"
paths:
- "godel"' > godel/config/godel.yml
Any ./godelw
invocation resolves all plugins and assets, downloading them if needed. Running ./godelw
will download
the plugin:
➜ ./godelw
Getting package from https://palantir.bintray.com/releases/com/palantir/godel-generate-plugin/generate-plugin/1.0.0/generate-plugin-1.0.0-linux-amd64.tgz...
0 B / 3.29 MiB 0.00%
1.44 MiB / 3.29 MiB 43.74%
2.47 MiB / 3.29 MiB 75.15%
3.29 MiB / 3.29 MiB 100.00% 0s
Usage:
godel [command]
Available Commands:
artifacts Print the artifacts for products
build Build the executables for products
check Run checks (runs all checks if none are specified)
check-path Verify that the Go environment is set up properly and that the project is in the proper location
clean Remove the build and dist outputs for products
dist Create distributions for products
docker Create or push Docker images for products
format Format files
generate Run generate task
git-hooks Install git commit hooks that verify that Go files are properly formatted before commit
github-wiki Push contents of a documents directory to a GitHub Wiki repository
goland GoLand project commands
idea Create IntelliJ project files for this project
info Print information regarding gödel
install Install gödel from a local tgz file
license Run license task
packages Lists all of the packages in the project except those excluded by configuration
products Print the IDs of the products in this project
project-version Print the version of the project
publish Publish products
run Run product
run-check Runs a specific check
tasks-config Prints the full YAML configuration used to load tasks and assets
test Test packages
test-tags Print the test packages that match the provided test tags
update Update gödel for project
upgrade-config Upgrade configuration
verify Run verify tasks for project
version Print godel version
Flags:
--debug run in debug mode (print full stack traces on failures and include other debugging output)
-h, --help help for godel
--version print godel version
--wrapper string path to the wrapper script for this invocation
Use "godel [command] --help" for more information about a command.
All projects share the same plugin and asset files, so once a plugin or asset has been resolved, subsequent runs will not re-download them.
We will extend echgo
by creating some different echo implementations. The different types will be defined as enums,
and we will use go generate
to invoke stringer
to create the string representation of these enum values.
Run the following to update the echo
implementation:
➜ echo '// Copyright (c) 2018 Author Name. All rights reserved.
// Use of this source code is governed by the Apache License, Version 2.0
// that can be found in the LICENSE file.
package echo
import (
"fmt"
"strings"
)
type Type int
func (t Type) String() string {
switch t {
case Simple:
return "Simple"
case Reverse:
return "Reverse"
default:
panic(fmt.Sprintf("unrecognized type: %d", t))
}
}
const (
Simple Type = iota
Reverse
end
)
var echoers = []Echoer{
Simple: &simpleEchoer{},
Reverse: &reverseEchoer{},
}
func NewEchoer(typ Type) Echoer {
return echoers[typ]
}
func TypeFrom(typ string) (Type, error) {
for curr := Simple; curr < end; curr++ {
if strings.ToLower(typ) == strings.ToLower(curr.String()) {
return curr, nil
}
}
return end, fmt.Errorf("unrecognized type: %s", typ)
}
type simpleEchoer struct{}
func (e *simpleEchoer) Echo(in string) string {
return in
}
type reverseEchoer struct{}
func (e *reverseEchoer) Echo(in string) string {
out := make([]byte, len(in))
for i := 0; i < len(out); i++ {
out[i] = in[len(in)-1-i]
}
return string(out)
}' > echo/echo.go
➜ SRC='// Copyright (c) 2018 Author Name. All rights reserved.
// Use of this source code is governed by the Apache License, Version 2.0
// that can be found in the LICENSE file.
package echo_test
import (
"testing"
"PROJECT_PATH/echo"
)
func TestEcho(t *testing.T) {
echoer := echo.NewEchoer(echo.Simple)
for i, tc := range []struct {
in string
want string
}{
{"foo", "foo"},
{"foo bar", "foo bar"},
} {
if got := echoer.Echo(tc.in); got != tc.want {
t.Errorf("case %d failed: want %q, got %q", i, tc.want, got)
}
}
}' && SRC=${SRC//PROJECT_PATH/$PROJECT_PATH} && echo "$SRC" > echo/echo_test.go
➜ SRC='// Copyright (c) 2018 Author Name. All rights reserved.
// Use of this source code is governed by the Apache License, Version 2.0
// that can be found in the LICENSE file.
package main
import (
"flag"
"fmt"
"strings"
"PROJECT_PATH/echo"
)
var version = "none"
func main() {
versionVar := flag.Bool("version", false, "print version")
typeVar := flag.String("type", echo.Simple.String(), "type of echo")
flag.Parse()
if *versionVar {
fmt.Println("echgo2 version:", version)
return
}
typ, err := echo.TypeFrom(*typeVar)
if err != nil {
fmt.Println("invalid echo type:", *typeVar)
return
}
echoer := echo.NewEchoer(typ)
fmt.Println(echoer.Echo(strings.Join(flag.Args(), " ")))
}' && SRC=${SRC//PROJECT_PATH/$PROJECT_PATH} && echo "$SRC" > main.go
At a high level, this code introduces a new type named Type
that represents the different types of echo
implementations. The code maintains a mapping from the types to the implementations and provides a function that returns
the Type for a given string. Run the code to verify that this works for the "simple" and "reverse" types that were
defined:
➜ go run main.go -type simple foo
foo
➜ go run main.go -type reverse foo
oof
The code relies on Type
having a String
function that returns its string representation. The current implementation
works, but it is a bit redundant since the string value is always the name of the constant. It is also a maintenance
burden: whenever a new type is added or an existing type is renamed, the String
function must also be updated.
Furthermore, because the string definitions are simply part of the switch statement, if someone forgets to add or update
the definitions, this will not be caught at compile-time, so it's also a likely source of future bugs.
We can address this by using go generate
and the stringer
tool to generate this code automatically.
Run the following to ensure that you have the stringer
tool:
➜ go get -u golang.org/x/tools/cmd/stringer
Now, update echo.go
to have a go generate
line that invokes stringer
:
➜ echo '// Copyright (c) 2018 Author Name. All rights reserved.
// Use of this source code is governed by the Apache License, Version 2.0
// that can be found in the LICENSE file.
//go:generate stringer -type=Type
package echo
import (
"fmt"
"strings"
)
type Type int
func (t Type) String() string {
switch t {
case Simple:
return "Simple"
case Reverse:
return "Reverse"
default:
panic(fmt.Sprintf("unrecognized type: %v", t))
}
}
const (
Simple Type = iota
Reverse
end
)
var echoers = []Echoer{
Simple: &simpleEchoer{},
Reverse: &reverseEchoer{},
}
func NewEchoer(typ Type) Echoer {
return echoers[typ]
}
func TypeFrom(typ string) (Type, error) {
for curr := Simple; curr < end; curr++ {
if strings.ToLower(typ) == strings.ToLower(curr.String()) {
return curr, nil
}
}
return end, fmt.Errorf("unrecognized type: %s", typ)
}
type simpleEchoer struct{}
func (e *simpleEchoer) Echo(in string) string {
return in
}
type reverseEchoer struct{}
func (e *reverseEchoer) Echo(in string) string {
out := make([]byte, len(in))
for i := 0; i < len(out); i++ {
out[i] = in[len(in)-1-i]
}
return string(out)
}' > echo/echo.go
Now that the //go:generate
directive exists, the standard Go approach would be to run go generate
to run the
generation task. However, this approach depends on developers knowing/remembering to run go generate
when they update
the definitions. Projects typically address this my noting it in their documentation or in comments, but this is
obviously quite fragile, and correctly calling all of the required generators can be especially challenging for larger
projects that may have several go generate
tasks.
We can address this by defining the generate
tasks as part of the declarative configuration for our project. Define a
"generate" task in godel/config/generate-plugin.yml
by running the following:
➜ echo 'generators:
stringer:
go-generate-dir: echo
gen-paths:
paths:
- echo/type_string.go' > godel/config/generate-plugin.yml
This specifies that we have a generator task named "stringer" (this name is specified by the user and can be anything).
The go-generate-dir
specifies the directory (relative to the project root) in which go generate
should be run. The
gen-paths
parameter specifies paths to the files or directories that are generated or modified by the go generate
task.
Run the generator task and verify that it generates the expected code:
➜ ./godelw generate
➜ cat ./echo/type_string.go
// Code generated by "stringer -type=Type"; DO NOT EDIT.
package echo
import "strconv"
const _Type_name = "SimpleReverseend"
var _Type_index = [...]uint8{0, 6, 13, 16}
func (i Type) String() string {
if i < 0 || i >= Type(len(_Type_index)-1) {
return "Type(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Type_name[_Type_index[i]:_Type_index[i+1]]
}
We can see that echo/type_string.go
was generated and provides an implementation of the String
function for Type
.
Now that this exists, we can remove the one we wrote manually in echo.go
:
➜ echo '// Copyright (c) 2018 Author Name. All rights reserved.
// Use of this source code is governed by the Apache License, Version 2.0
// that can be found in the LICENSE file.
//go:generate stringer -type=Type
package echo
import (
"fmt"
"strings"
)
type Type int
const (
Simple Type = iota
Reverse
end
)
var echoers = []Echoer{
Simple: &simpleEchoer{},
Reverse: &reverseEchoer{},
}
func NewEchoer(typ Type) Echoer {
return echoers[typ]
}
func TypeFrom(typ string) (Type, error) {
for curr := Simple; curr < end; curr++ {
if strings.ToLower(typ) == strings.ToLower(curr.String()) {
return curr, nil
}
}
return end, fmt.Errorf("unrecognized type: %s", typ)
}
type simpleEchoer struct{}
func (e *simpleEchoer) Echo(in string) string {
return in
}
type reverseEchoer struct{}
func (e *reverseEchoer) Echo(in string) string {
out := make([]byte, len(in))
for i := 0; i < len(out); i++ {
out[i] = in[len(in)-1-i]
}
return string(out)
}' > echo/echo.go
With this setup, ./godelw generate
can be called on a project to invoke all of its generate
tasks.
We will now attempt to commit these changes. If you have followed the tutorial up to this point, the git hook that enforces formatting for files will reject the commit:
➜ git add echo godel main.go
➜ git commit -m "Add support for echo types"
Unformatted files exist -- run ./godelw format to format these files:
echo/type_string.go
This is because the generated Go file does not match the formatting enforced by ptimports
. However, because this code
is generated, we do not want to modify it after the fact. In general, we want to simply exclude generated code from all
gödel tasks -- we don't want to add license headers to it, format it, run linting checks on it, etc. We will update the
exclude block of godel/config/godel.yml
to reflect this and specify that the file should be ignored:
➜ echo 'plugins:
resolvers:
- "https://palantir.bintray.com/releases/{{GroupPath}}/{{Product}}/{{Version}}/{{Product}}-{{Version}}-{{OS}}-{{Arch}}.tgz"
plugins:
- locator:
id: "com.palantir.godel-generate-plugin:generate-plugin:1.0.0"
exclude:
names:
- "\\\\..+"
- "vendor"
paths:
- "godel"
- "echo/type_string.go"' > godel/config/godel.yml
We will go through this file in more detail in the next portion of the tutorial, but for now it is sufficient to know
that this excludes the echo/type_string.go
file from checks and other tasks (we will make this more generic later).
We can now commit the changes:
➜ git add echo godel main.go
➜ git commit -m "Add support for echo types"
[master 346da9e] Add support for echo types
6 files changed, 78 insertions(+), 4 deletions(-)
create mode 100644 echo/type_string.go
create mode 100644 godel/config/generate-plugin.yml
Many Go projects would consider this sufficient -- they would document the requirement that developers must run
go get golang.org/x/tools/cmd/stringer
locally to in order to run "generate" and also ensure that this same action is
performed in their CI environment. However, this introduces an external dependency on the ability to get and install
stringer
. Furthermore, the version of stringer
is not defined/locked in anywhere -- the go get
action will fetch
whatever version is the latest at that time. This may not be an issue for tools that have a completely mature API, but
if there are behavior changes between versions of the tools it can lead to the generation tasks creating inconsistent
output.
For that reason, if the generate
task is running a Go program, we have found it helpful to vendor the entire program
within the project and to run it using go run
to ensure that the generate
task does not have any external
dependencies. We will use this construction for this project.
Run the following to create a new directory for the generator and create a vendor
directory within that directory:
➜ mkdir -p generator/vendor
Putting the vendor
directory within generator
ensures that the code we vendor will only be accessible within the
generator
directory. We will now vendor the stringer
program. In a real workflow, you would use the vendoring tool
of your choice to do so. For the purposes of this tutorial, we will handle our vendoring manually by copying the code we
need to the expected location:
➜ mkdir -p generator/vendor/golang.org/x/tools/cmd/stringer
➜ cp $(find $GOPATH/src/golang.org/x/tools/cmd/stringer -name '*.go' -not -name '*_test.go' -maxdepth 1 -type f) generator/vendor/golang.org/x/tools/cmd/stringer/
Note: the find
command above performs some pruning to copy only the buildable Go files that will be used -- if you
don't care about pulling in extra unneeded files (such as tests and testdata files), you can run
cp -r $GOPATH/src/golang.org/x/tools/cmd/stringer/* generator/vendor/golang.org/x/tools/cmd/stringer/
instead.
We will now define a generator that invokes this:
➜ echo '// Copyright (c) 2018 Author Name. All rights reserved.
// Use of this source code is governed by the Apache License, Version 2.0
// that can be found in the LICENSE file.
//go:generate -command runstringer go run vendor/golang.org/x/tools/cmd/stringer/stringer.go vendor/golang.org/x/tools/cmd/stringer/importer18.go
//go:generate runstringer -type=Type ../echo
package generator' > generator/generate.go
This generator now runs stringer
directly from the vendor directory. If we had other packages on which we wanted to
invoke stringer
, we could simply update this file to do so.
Update the previous code to remove its generation logic:
➜ echo '// Copyright (c) 2018 Author Name. All rights reserved.
// Use of this source code is governed by the Apache License, Version 2.0
// that can be found in the LICENSE file.
package echo
import (
"fmt"
"strings"
)
type Type int
const (
Simple Type = iota
Reverse
end
)
var echoers = []Echoer{
Simple: &simpleEchoer{},
Reverse: &reverseEchoer{},
}
func NewEchoer(typ Type) Echoer {
return echoers[typ]
}
func TypeFrom(typ string) (Type, error) {
for curr := Simple; curr < end; curr++ {
if strings.ToLower(typ) == strings.ToLower(curr.String()) {
return curr, nil
}
}
return end, fmt.Errorf("unrecognized type: %s", typ)
}
type simpleEchoer struct{}
func (e *simpleEchoer) Echo(in string) string {
return in
}
type reverseEchoer struct{}
func (e *reverseEchoer) Echo(in string) string {
out := make([]byte, len(in))
for i := 0; i < len(out); i++ {
out[i] = in[len(in)-1-i]
}
return string(out)
}' > echo/echo.go
Update the generate-plugin.yml
configuration:
➜ echo 'generators:
stringer:
go-generate-dir: generator
gen-paths:
paths:
- echo/type_string.go' > godel/config/generate-plugin.yml
Run the generate
task to verify that it still succeeds:
➜ ./godelw generate
Run the check
command to verify that the project is still valid:
➜ ./godelw check
[compiles] Running compiles...
[errcheck] Running errcheck...
[extimport] Running extimport...
[deadcode] Running deadcode...
[extimport] Finished extimport
[golint] Running golint...
[golint] Finished golint
[govet] Running govet...
[govet] Finished govet
[importalias] Running importalias...
[importalias] Finished importalias
[ineffassign] Running ineffassign...
[ineffassign] Finished ineffassign
[novendor] Running novendor...
[novendor] golang.org/x/tools
[novendor] Finished novendor
[outparamcheck] Running outparamcheck...
[compiles] Finished compiles
[unconvert] Running unconvert...
[deadcode] Finished deadcode
[varcheck] Running varcheck...
[errcheck] Finished errcheck
[outparamcheck] Finished outparamcheck
[unconvert] Finished unconvert
[varcheck] Finished varcheck
Check(s) produced output: [novendor]
You can see that the novendor
check now fails. This is because the golang.org/x/tools
package is present in the
vendor
directory, but no packages in the project are importing its packages and the novendor
check has identified
it as an unused vendored project. However, in this instance we know that this is valid because we call the code directly
from go generate
. Update the godel/config/check.yml
configuration to reflect this:
➜ echo 'checks:
golint:
filters:
- value: "should have comment or be unexported"
- value: "or a comment on this block"
novendor:
config:
ignore-pkgs:
- "./generator/vendor/golang.org/x/tools"' > godel/config/check-plugin.yml
Run check novendor
to verify that the check now passes:
➜ ./godelw check novendor
Running novendor...
Finished novendor
Commit these changes by running the following:
➜ git add echo generator godel
➜ git commit -m "Update generator code"
[master 05b7c34] Update generator code
8 files changed, 702 insertions(+), 4 deletions(-)
create mode 100644 generator/generate.go
create mode 100644 generator/vendor/golang.org/x/tools/cmd/stringer/importer18.go
create mode 100644 generator/vendor/golang.org/x/tools/cmd/stringer/importer19.go
create mode 100644 generator/vendor/golang.org/x/tools/cmd/stringer/stringer.go
Although vendoring the program and running it directly is an approach that works, it is heavyweight and can be a pain to maintain -- for example, updating the generator program requires updating the contents of the vendor directory. If a generator task is used often across projects, consider implementing a gödel plugin to perform the task instead: this allows the logic to be versioned and maintained in a separate plugin instead and provides the power of the abstractions provided by gödel.
-
${GOPATH}/src/${PROJECT_PATH}
exists, is the working directory and is initialized as a Git repository - Project contains
godel
andgodelw
- Project contains
main.go
- Project contains
.gitignore
that ignores GoLand files - Project contains
echo/echo.go
,echo/echo_test.go
andecho/echoer.go
-
godel/config/dist-plugin.yml
is configured to buildechgo2
- Project is tagged as 0.0.1
-
godel/config/dist-plugin.yml
is configured to create distributions forechgo
- Project is tagged as 0.0.2
-
dockerctx
directory exists andgodel/config/dist-plugin.yml
is configured to build Docker images for the product - Go files have license headers
-
godel/config/godel.yml
is configured to add the go-generate plugin -
godel/config/generate-plugin.yml
is configured to generate string function
The generate
task also supports a verification mode that ensures that the code that is generated by the
./godelw generate
task does not change the contents of the generated target paths. This is useful for use in CI to
verify that developers properly ran generate
.
To demonstrate this, update echo/echo.go
to add another echo type:
➜ echo '// Copyright (c) 2018 Author Name. All rights reserved.
// Use of this source code is governed by the Apache License, Version 2.0
// that can be found in the LICENSE file.
//go:generate stringer -type=Type
package echo
import (
"fmt"
"math/rand"
"strings"
)
type Type int
const (
Simple Type = iota
Reverse
Random
end
)
var echoers = []Echoer{
Simple: &simpleEchoer{},
Reverse: &reverseEchoer{},
Random: &randomEchoer{},
}
func NewEchoer(typ Type) Echoer {
return echoers[typ]
}
func TypeFrom(typ string) (Type, error) {
for curr := Simple; curr < end; curr++ {
if strings.ToLower(typ) == strings.ToLower(curr.String()) {
return curr, nil
}
}
return end, fmt.Errorf("unrecognized type: %s", typ)
}
type simpleEchoer struct{}
func (e *simpleEchoer) Echo(in string) string {
return in
}
type reverseEchoer struct{}
func (e *reverseEchoer) Echo(in string) string {
out := make([]byte, len(in))
for i := 0; i < len(out); i++ {
out[i] = in[len(in)-1-i]
}
return string(out)
}
type randomEchoer struct{}
func (e *randomEchoer) Echo(in string) string {
inBytes := []byte(in)
out := make([]byte, len(in))
for i := 0; i < len(out); i++ {
randIdx := rand.Intn(len(inBytes))
out[i] = inBytes[randIdx]
inBytes = append(inBytes[:randIdx], inBytes[randIdx+1:]...)
}
return string(out)
}' > echo/echo.go
Now, run the generate task with the --verify
flag:
➜ ./godelw generate --verify
Generators produced output that differed from what already exists: [stringer]
stringer:
echo/type_string.go: previously had checksum 3029c612cac515bcfee843ab766d550cee29fb89995a0ac143db2de9fc8b5eec, now has checksum b7d6ccbb07cb0267f6af7cdfa2d2a038b71fcaa65261e9e9472f45e1427dc303
As you can see, the task determined that the generate
task changed a file that was specified in the gen-paths
configuration for the task and prints a warning that specifies this.
The gen-paths
configuration consists of a paths
and names
list that specify regular expressions that match the
paths or names of files created by the generator. When a generate
task is run in --verify
mode, the task determines
all of the paths in the project that match the gen-paths
configuration, computes the checksums of all of the files,
runs the generation task, computes all of the paths that match after the task is run, and then compares both the file
list and the checksums. If either the file list or checksums differ, the differences are echoed and the verification
fails. Note that the --verify
mode still runs the generate
task -- because go generate
itself doesn't have a
notion of a dry run or verification, the only way to determine the effects of a generate
task is to run it and to
compare the state before and after the run. Although this same kind of check can be approximated by something like a
git status
, this configuration mechanism is more explicit/declarative and more robust.
Revert the changes by running the following:
➜ git checkout -- echo
The generate
configuration supports configuring generate tasks organized in any manner. However, for projects that
have multiple different kinds of go generate
tasks, we have found that the following can be an effective way to
organize the generators:
- Have a
generators
directory at the top level of the project - Have a subdirectory for each generator type within the
generators
directory - The subdirectory contains a
generate.go
file withgo generate
directive and avendor
directory that vendors the program being called by the generator
Here is an example of such a directory structure:
➜ tree generators
generators [error opening dir]
0 directories, 0 files
The following are examples of tasks that can be set up and configured to run as a go generate
task:
-
protoc
for generating protobufs -
mockery
for generating mocks for testing -
stringer
for generating String() functions for types -
gopherjs
to generate Javascript from Go code -
go-bindata
to generate Go code that embeds static resources -
amalgomate
for creating libraries out of Gomain
packages
Generators that leverage all of these tools (as well as other custom/proprietary internal tools) have been successfully configured and integrated with gödel projects.
Generators work most effectively if there is a guarantee that, for a given input, the generated outputs is always the same (this is required if output files are verified).
Generators can be very helpful, and their flexibility makes it appealing to use them to generate output for projects,
especially on a one-off basis. However, if you find that you are commonly using the same generator across projects,
consider creating a gödel plugin for the same task instead -- although generators offer nice flexibility and the
generate
plugin offers a way for such tasks to be integrated into a gödel project, gödel plugins are much easier to
work with. As an example, implementing a plugin for stringer
would be easy to do, and using that plugin would prevent
having to vendor the stringer
program.
- Home
-
Tutorial
- Add gödel to a project
- Add Git hooks to enforce formatting
- Generate IDE project for GoLand
- Format Go files
- Run static checks on code
- Run tests
- Build
- Run
- Dist
- Publish
- Build and push Docker images
- Generate license headers
- Go generate tasks
- Define excludes
- Write integration tests
- Sync a documentation directory with GitHub wiki
- Verify project
- Set up CI to run tasks
- Update gödel
- Update legacy gödel
- Other commands
- Conclusion
- Name
- Philosophy
- Architecture
- Plugins
- Configuration