diff --git a/.milpa/commands/dev/ci.sh b/.milpa/commands/dev/ci.sh index 9bfb3e1..73be738 100644 --- a/.milpa/commands/dev/ci.sh +++ b/.milpa/commands/dev/ci.sh @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright © 2022 Roberto Hidalgo -milpa dev lint || @milpa.fail "linter has errors" +milpa dev lint || @milpa.fail "linter found errors" milpa dev test unit || @milpa.fail "tests failed" diff --git a/internal/registry/registry.go b/internal/registry/registry.go index 52c5f62..abcbe45 100644 --- a/internal/registry/registry.go +++ b/internal/registry/registry.go @@ -12,7 +12,6 @@ import ( "git.rob.mx/nidito/chinampa/pkg/command" "git.rob.mx/nidito/chinampa/pkg/errors" "git.rob.mx/nidito/chinampa/pkg/logger" - "git.rob.mx/nidito/chinampa/pkg/statuscode" "github.com/fatih/color" "github.com/spf13/cobra" ) @@ -91,9 +90,6 @@ func Execute(version string) error { if idx == len(cmd.Path)-1 { leaf := ToCobra(cmd, cmdRoot.Options) container.AddCommand(leaf) - if container != ccRoot { - container.ValidArgs = append(container.ValidArgs, leaf.Name()) - } log.Tracef("cobra: %s => %s", leaf.Name(), container.CommandPath()) break } @@ -151,8 +147,8 @@ func Execute(version string) error { } return errors.NotFound{Msg: "No subcommand provided", Group: []string{}} } - os.Exit(statuscode.NotFound) - return nil + + return errors.NotFound{Msg: fmt.Sprintf("Unknown subcommand %s", args[0]), Group: []string{}} }, } diff --git a/pkg/env/env.go b/pkg/env/env.go index f9f4e5a..8d1d413 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -1,12 +1,45 @@ // Copyright © 2022 Roberto Hidalgo // SPDX-License-Identifier: Apache-2.0 + +/* +Package env holds environment variable names that are meant to be overridden by implementations. + +# example + + package main + + import "git.rob.mx/nidito/chinampa/env" + + func init() { + env.HelpUnstyled = "MY_APP_PLAIN_HELP" + env.HelpStyle = "MY_APP_HELP_STYLE" + env.Verbose = "MY_APP_VERBOSE" + env.Silent = "MY_APP_SILENT" + env.ValidationDisabled = "MY_APP_SKIP_VALIDATION" + } +*/ package env +// HelpUnstyled means help will not be colored nor formatted for a TTY. var HelpUnstyled = "HELP_STYLE_PLAIN" + +// HelpStyle identifies the theme to use for help formatting. var HelpStyle = "HELP_STYLE" + +// Verbose enables verbose printing of log entries. var Verbose = "VERBOSE" + +// Silent disables all printing of log entries, except for errors. var Silent = "SILENT" + +// NoColor disables printing of color escape codes in help and log entries. var NoColor = "NO_COLOR" + +// ForceColor enables printing of color escape codes in help and log entries. var ForceColor = "COLOR" + +// ValidationDisabled disables validation on arguments and options. var ValidationDisabled = "SKIP_VALIDATION" + +// Debug enables printing of debugging information. var Debug = "DEBUG" diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index f21013b..64c4f28 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -2,11 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 package errors +// NotFound happens when a sub-command was not found. type NotFound struct { Msg string Group []string } +// BadArguments happens when the user provided incorrect arguments or options. type BadArguments struct { Msg string } diff --git a/pkg/errors/handler.go b/pkg/errors/handler.go index cce6b3a..d63a15e 100644 --- a/pkg/errors/handler.go +++ b/pkg/errors/handler.go @@ -22,6 +22,7 @@ func showHelp(cmd *cobra.Command) { } } +// HandleCobraExit is called when a command errors out or was not found. func HandleCobraExit(cmd *cobra.Command, err error) error { if err == nil { ok, err := cmd.PersistentFlags().GetBool(_c.HelpCommandName) diff --git a/pkg/exec/exec.go b/pkg/exec/exec.go index 2e9cef2..99e5fdb 100644 --- a/pkg/exec/exec.go +++ b/pkg/exec/exec.go @@ -18,6 +18,7 @@ import ( // ExecFunc is replaced in tests. var ExecFunc = WithSubshell +// WithSubshell is the default runner of subprocesses. func WithSubshell(ctx context.Context, env []string, executable string, args ...string) (bytes.Buffer, bytes.Buffer, error) { cmd := os_exec.CommandContext(ctx, executable, args...) // #nosec G204 var stdout bytes.Buffer diff --git a/pkg/logger/formatter.go b/pkg/logger/formatter.go index 42c9ae8..58191e5 100644 --- a/pkg/logger/formatter.go +++ b/pkg/logger/formatter.go @@ -33,10 +33,10 @@ func init() { dimmed.EnableColor() } -type Formatter struct { +type ttyFormatter struct { } -func (f *Formatter) Format(entry *logrus.Entry) ([]byte, error) { +func (f *ttyFormatter) Format(entry *logrus.Entry) ([]byte, error) { prefix := "" colorEnabled := runtime.ColorEnabled() message := entry.Message diff --git a/pkg/logger/log.go b/pkg/logger/log.go index 59bc071..393e4da 100644 --- a/pkg/logger/log.go +++ b/pkg/logger/log.go @@ -12,7 +12,7 @@ import ( var componentKey = "_component" func init() { - logrus.SetFormatter(new(Formatter)) + logrus.SetFormatter(new(ttyFormatter)) } var Main = logrus.WithContext(context.Background()) @@ -21,18 +21,23 @@ func Sub(name string) *logrus.Entry { return logrus.WithField(componentKey, name) } +// Level is a log entry severity level. type Level int const ( - LevelPanic Level = iota - LevelFatal - LevelError + // LevelError is the most severe. + LevelError Level = iota + 2 + // LevelWarning happens when something is potentially off. LevelWarning + // LevelInfo is regular information relayed back to the user. LevelInfo + // LevelDebug is debugging information. LevelDebug + // LevelTrace is verbose debugging information. LevelTrace ) +// Configure sets up the Main logger. func Configure(name string, level Level) { Main = logrus.WithField(componentKey, name) if runtime.SilenceEnabled() { diff --git a/pkg/logger/log_test.go b/pkg/logger/log_test.go index c7378bf..642f77e 100644 --- a/pkg/logger/log_test.go +++ b/pkg/logger/log_test.go @@ -29,7 +29,7 @@ func withEnv(t *testing.T, env map[string]string) { } t.Cleanup(func() { - rt.ResetParsedFlags() + rt.ResetParsedFlagsCache() for k := range env { os.Unsetenv(k) diff --git a/pkg/render/render.go b/pkg/render/render.go index 29d5924..f98639f 100644 --- a/pkg/render/render.go +++ b/pkg/render/render.go @@ -20,6 +20,7 @@ func addBackticks(str []byte) []byte { return bytes.ReplaceAll(str, []byte("﹅"), []byte("`")) } +// Markdown renders markdown-formatted content to the tty. func Markdown(content []byte, withColor bool) ([]byte, error) { content = addBackticks(content) diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index 873bb61..caafc9d 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -1,5 +1,9 @@ // Copyright © 2022 Roberto Hidalgo // SPDX-License-Identifier: Apache-2.0 + +/* +Package runtime presents environment related information useful during your program's runtime. +*/ package runtime import ( @@ -9,6 +13,8 @@ import ( "git.rob.mx/nidito/chinampa/pkg/env" ) +// Executable is the name of our binary, and should be set +// using `chinampa.Execute(config chinampa.Config)`. var Executable = "chinampa" var falseIshValues = []string{ @@ -54,7 +60,8 @@ func isTrueIsh(val string) bool { var _flags map[string]bool -func ResetParsedFlags() { +// ResetParsedFlagsCache resets the cached parsed global flags. +func ResetParsedFlagsCache() { _flags = nil } @@ -85,14 +92,17 @@ func flagInArgs(name string) bool { return ok } +// DebugEnabled tells if debugging was requested. func DebugEnabled() bool { return isTrueIsh(os.Getenv(env.Debug)) } +// DebugEnabled tells if debugging was requested. func ValidationEnabled() bool { return !flagInArgs("skip-validation") && isFalseIsh(os.Getenv(env.ValidationDisabled)) } +// VerboseEnabled tells if verbose output was requested. func VerboseEnabled() bool { if flagInArgs("silent") { return false @@ -100,6 +110,7 @@ func VerboseEnabled() bool { return isTrueIsh(os.Getenv(env.Verbose)) || flagInArgs("verbose") } +// SilenceEnabled tells if silencing of output was requested. func SilenceEnabled() bool { if flagInArgs("verbose") { return false @@ -111,6 +122,7 @@ func SilenceEnabled() bool { return isTrueIsh(os.Getenv(env.Silent)) || flagInArgs("silent") } +// ColorEnabled tells if colorizing output was requested. func ColorEnabled() bool { if flagInArgs("color") { return true @@ -120,11 +132,12 @@ func ColorEnabled() bool { return !(isTrueIsh(os.Getenv(env.NoColor)) || UnstyledHelpEnabled() || flagInArgs("no-color")) } +// UnstyledHelpEnabled tells if help should be printed without formatting. func UnstyledHelpEnabled() bool { return isTrueIsh(os.Getenv(env.HelpUnstyled)) } -// EnvironmentMap returns the resolved environment map. +// EnvironmentMap returns a map of environment keys for color, debugging and verbosity and their values, ready for `os.Setenv`. func EnvironmentMap() map[string]string { res := map[string]string{} trueString := strconv.FormatBool(true) diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index f3b60f8..c322de8 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -27,7 +27,7 @@ func withEnv(t *testing.T, env map[string]string) { } t.Cleanup(func() { - ResetParsedFlags() + ResetParsedFlagsCache() for k := range env { os.Unsetenv(k) diff --git a/pkg/statuscode/statuscode.go b/pkg/statuscode/statuscode.go index b8504d2..f85011e 100644 --- a/pkg/statuscode/statuscode.go +++ b/pkg/statuscode/statuscode.go @@ -1,22 +1,25 @@ // Copyright © 2022 Roberto Hidalgo // SPDX-License-Identifier: Apache-2.0 + +/* +package statuscode manages exit codes for programs. + +See `man sysexits || grep "#define EX" /usr/include/sysexits.h` +and https://tldp.org/LDP/abs/html/exitcodes.html +*/ package statuscode -// Exit statuses -// see man sysexits || grep "#define EX" /usr/include/sysexits.h -// and https://tldp.org/LDP/abs/html/exitcodes.html const ( - // 0 means everything is fine. + // Ok means everything is fine. Ok = 0 - // 42 provides answers to life, the universe and everything; also, renders help. + // RenderHelp provides answers to life, the universe and everything; also, renders help. RenderHelp = 42 - // 64 bad arguments - // EX_USAGE The command was used incorrectly, e.g., with the wrong number of arguments, a bad flag, a bad syntax in a parameter, or whatever. + // Usage means bad arguments were provided by the user. Usage = 64 - // EX_SOFTWARE An internal software error has been detected. This should be limited to non-operating system related errors as possible. + // ProgrammerError means the developer made a mistake. ProgrammerError = 70 - // EX_CONFIG Something was found in an unconfigured or misconfigured state. + // ConfigError means configuration files or the environment is misconfigured. ConfigError = 78 - // 127 command not found. + // NotFound means a sub-command not was found. NotFound = 127 )