Architecture

Parser
At the start of lets application, parser tries to find lets.yaml file starting from current directory up to the /.
When config file is found, parser tries to read/parse and validate yaml config.
How parsing works ?
config.go:Config struct implements UnmarshalYAML function, so when yaml.Unmarshal called with Config instance passed in,
custom unmarshalling code is executed.
Its common to make some normalization of commands and its data during parsing phase so the rest of the code does not have to do any kind of normalization on its own.
To add a new field you probably must implement UnmarshalYAML somehow.
Mixins
Lets has feature called mixins. When parser meets mixins directive,
it basically repeats all read/parse logic on minix files.
Since mixin config files have some limitations, although they are parsed the same way, validation is a bit different.
Validation
There are two validation phases.
First validation phase happens during unmarshalling and checks if:
- directives names valid
- directives types valid (array, map, string, number, etc.)
- references to command in dependsdirective points to existing commands
Second phase happens after we ensured that config is syntactically and semantically correct.
Int the second phase we are checking:
- config version
- circular dependencies in commands
Env
TODO
Cobra CLI Framework
We are using Cobra CLI framework and delegating to it most of the work related to parsing
command line arguments, help messages etc.
Binding our config with Cobra
Now we have to bind our config to Cobra.
Cobra has a concept of cobra.Command. It is a representation of command in CLI application, for example:
git commit
git pull
git is a CLI applications and
commit and pull are commands.
In a traditional lets application commands will be what is declared in lets.yaml commands section.
To achieve this we are creating so-called root command and subcommands from config.
Root command
Root command is responsible for:
- letsown command line flags such as- --version,- --upgrade,- --helpand so on.
- letscommands autocompletion in terminal
Subcommands
Subcommand is created from our Config.Commands (see initSubCommands function).
In subcommand's RunE callback we are parsing/validation/normalizing command line arguments for this subcommand
and then finally executing command with Executor.
Since we are using docopt as an argument parser for subcommands, we don't let Cobra parse and interpret args,
and instead we are passing raw arguments as is to Executor.
Executor
Executor is responsible for:
- parsing and preparing args using docopt
- calculating and storing command's checksums
- executing other commands from dependssection
- preparing environment
- running command in OS using exec.Command