Architecture
#
ParserAt 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.
#
MixinsLets 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.
#
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.
#
ValidationThere 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
depends
directive 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
#
Cobra CLI FrameworkWe 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 CobraNow 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
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 commandRoot command is responsible for:
lets
own command line flags such as--version
,--upgrade
,--help
and so on.lets
commands autocompletion in terminal
#
SubcommandsSubcommand 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 Runner
.
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 Runner
.
#
RunnerRunner
is responsible for:
- parsing and preparing args using
docopt
- calculating and storing command's checksums
- executing other commands from
depends
section - preparing environment
- running command in OS using
exec.Command