error on unexpected arguments, test out logger, add .milpa
courtesy of the department of departamental recursiveness
This commit is contained in:
parent
7fab6d66d8
commit
725347ec48
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
coverage/*
|
6
.milpa/commands/dev/ci.sh
Normal file
6
.milpa/commands/dev/ci.sh
Normal file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright © 2022 Roberto Hidalgo <chinampa@un.rob.mx>
|
||||
|
||||
milpa dev lint || @milpa.fail "linter has errors"
|
||||
milpa dev test unit || @milpa.fail "tests failed"
|
3
.milpa/commands/dev/ci.yaml
Normal file
3
.milpa/commands/dev/ci.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
summary: Lints and tests milpa
|
||||
description: |
|
||||
runs `milpa dev lint {shell,go}` and `milpa dev test {unit,integration}`
|
10
.milpa/commands/dev/lint.sh
Normal file
10
.milpa/commands/dev/lint.sh
Normal file
@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright © 2022 Roberto Hidalgo <chinampa@un.rob.mx>
|
||||
|
||||
cd "$(dirname "$MILPA_COMMAND_REPO")" || @milpa.fail "could not cd into base dir"
|
||||
|
||||
@milpa.log info "Linting go files"
|
||||
golangci-lint run || exit 2
|
||||
@milpa.log complete "Go files are up to spec"
|
||||
|
3
.milpa/commands/dev/lint.yaml
Normal file
3
.milpa/commands/dev/lint.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
summary: Runs linter on go files
|
||||
description: |
|
||||
basically golangci-lint
|
12
.milpa/commands/dev/test.sh
Normal file
12
.milpa/commands/dev/test.sh
Normal file
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright © 2022 Roberto Hidalgo <chinampa@un.rob.mx>
|
||||
|
||||
set -e
|
||||
if [[ "$MILPA_OPT_COVERAGE" ]]; then
|
||||
rm -rf coverage
|
||||
milpa dev test unit --coverage
|
||||
milpa dev test coverage-report
|
||||
else
|
||||
milpa dev test unit
|
||||
fi
|
7
.milpa/commands/dev/test.yaml
Normal file
7
.milpa/commands/dev/test.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
summary: Runs unit and integration tests
|
||||
description: |
|
||||
a wrapper for milpa dev test *
|
||||
options:
|
||||
coverage:
|
||||
type: bool
|
||||
description: if provided, will output coverage reports
|
18
.milpa/commands/dev/test/coverage-report.sh
Normal file
18
.milpa/commands/dev/test/coverage-report.sh
Normal file
@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright © 2022 Roberto Hidalgo <chinampa@un.rob.mx>
|
||||
|
||||
runs=()
|
||||
while IFS= read -r -d $'\0'; do
|
||||
runs+=("$REPLY")
|
||||
done < <(find coverage -type d -maxdepth 1 -mindepth 1 -print0)
|
||||
packages="$(IFS=, ; echo "${runs[*]}")"
|
||||
|
||||
|
||||
@milpa.log info "Building coverage report from runs: ${runs[*]}"
|
||||
go tool covdata textfmt -i="$packages" -o coverage/coverage.cov || @milpa.fail "could not merge runs"
|
||||
go tool cover -html=coverage/coverage.cov -o coverage/coverage.html || @milpa.fail "could not build reports"
|
||||
|
||||
@milpa.log complete "Coverage report built"
|
||||
go tool covdata percent -i="$packages"
|
||||
go tool cover -func=coverage/coverage.cov | tail -n 1
|
5
.milpa/commands/dev/test/coverage-report.yaml
Normal file
5
.milpa/commands/dev/test/coverage-report.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
summary: Creates a coverage report from previous test runs
|
||||
description: |
|
||||
looks at test/coverage/* for output
|
||||
|
||||
see: https://go.dev/testing/coverage/
|
17
.milpa/commands/dev/test/unit.sh
Normal file
17
.milpa/commands/dev/test/unit.sh
Normal file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright © 2022 Roberto Hidalgo <chinampa@un.rob.mx>
|
||||
|
||||
root="$(dirname "$MILPA_COMMAND_REPO")"
|
||||
cd "$root" || @milpa.fail "could not cd into $root"
|
||||
@milpa.log info "Running unit tests"
|
||||
args=()
|
||||
|
||||
if [[ "${MILPA_OPT_COVERAGE}" ]]; then
|
||||
cover_dir="$root/coverage/unit"
|
||||
rm -rf "$cover_dir"
|
||||
mkdir -p "$cover_dir"
|
||||
args=( -test.gocoverdir="$cover_dir" --coverpkg=./... )
|
||||
fi
|
||||
gotestsum --format short -- ./... "${args[@]}" || exit 2
|
||||
@milpa.log complete "Unit tests passed"
|
7
.milpa/commands/dev/test/unit.yaml
Normal file
7
.milpa/commands/dev/test/unit.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
summary: Runs unit tests
|
||||
description: |
|
||||
Runs unit tests using gotestsum
|
||||
options:
|
||||
coverage:
|
||||
type: bool
|
||||
description: if provided, will output coverage reports
|
46
.milpa/commands/release/create.sh
Normal file
46
.milpa/commands/release/create.sh
Normal file
@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright © 2022 Roberto Hidalgo <chinampa@un.rob.mx>
|
||||
@milpa.load_util user-input
|
||||
|
||||
current_branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
[[ "$current_branch" != "main" ]] && @milpa.fail "Refusing to release on branch <$current_branch>"
|
||||
[[ -n "$(git status --porcelain)" ]] && @milpa.fail "Git tree is messy, won't continue"
|
||||
|
||||
function next_semver() {
|
||||
local components
|
||||
IFS="." read -r -a components <<< "${2}"
|
||||
following=""
|
||||
case "$1" in
|
||||
major ) following="$((components[0]+1)).0.0" ;;
|
||||
minor ) following="${components[0]}.$((components[1]+1)).0" ;;
|
||||
patch ) following="${components[0]}.${components[1]}.$((components[2]+1))" ;;
|
||||
*) @milpa.fail "unknown increment type: <$1>"
|
||||
esac
|
||||
|
||||
echo "$following"
|
||||
}
|
||||
|
||||
increment="$MILPA_ARG_INCREMENT"
|
||||
# get the latest tag, ignoring any pre-releases
|
||||
# by default current version is 0.0.-1, and must initially release a patch
|
||||
current="$(git describe --abbrev=0 --exclude='*-*' --tags 2>/dev/null || echo "0.0.-1")"
|
||||
|
||||
next=$(next_semver "$increment" "$current")
|
||||
|
||||
if [[ "$MILPA_OPT_PRE" ]]; then
|
||||
# pre releases might update previous ones, look for them
|
||||
pre_current=$(git describe --abbrev=0 --match="$next-$MILPA_OPT_PRE.*" --tags 2>/dev/null || echo "$current-$MILPA_OPT_PRE.-1")
|
||||
build=${pre_current##*.}
|
||||
next="$next-$MILPA_OPT_PRE.$(( build + 1 ))"
|
||||
fi
|
||||
|
||||
@milpa.log info "Creating release with version $(@milpa.fmt inverted "$next")"
|
||||
@milpa.confirm "Proceed with release?" || @milpa.fail "Refusing to continue, got <$REPLY>"
|
||||
@milpa.log success "Continuing with release"
|
||||
|
||||
@milpa.log info "Creating tag and pushing"
|
||||
git tag "$next" || @milpa.fail "Could not create tag $next"
|
||||
git push origin "$next" || @milpa.fail "Could not push tag $next"
|
||||
|
||||
@milpa.log complete "Release created and pushed to origin!"
|
15
.milpa/commands/release/create.yaml
Normal file
15
.milpa/commands/release/create.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
summary: Creates a new tag and updates the changelog
|
||||
description: |
|
||||
Automation might trigger a release if github is in a good mood
|
||||
arguments:
|
||||
- name: increment
|
||||
description: "The kind of semver increment"
|
||||
default: patch
|
||||
values:
|
||||
static: [major, minor, patch]
|
||||
required: true
|
||||
options:
|
||||
pre:
|
||||
values:
|
||||
static: [alpha, beta, rc]
|
||||
description: create a pre-release
|
@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright © 2021 Roberto Hidalgo <chinampa@un.rob.mx>
|
||||
// Copyright © 2022 Roberto Hidalgo <chinampa@un.rob.mx>
|
||||
package commands
|
||||
|
||||
import (
|
||||
@ -14,7 +14,8 @@ var GenerateCompletions = &cobra.Command{
|
||||
Hidden: true,
|
||||
DisableAutoGenTag: true,
|
||||
SilenceUsage: true,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgs: []string{"bash", "fish", "zsh"},
|
||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||
switch args[0] {
|
||||
case "bash":
|
||||
|
@ -24,7 +24,8 @@ func newCobraRoot(root *command.Command) *cobra.Command {
|
||||
DisableAutoGenTag: true,
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
ValidArgs: []string{""},
|
||||
// This tricks cobra into erroring without a subcommand
|
||||
ValidArgs: []string{""},
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if err := cobra.OnlyValidArgs(cmd, args); err != nil {
|
||||
suggestions := []string{}
|
||||
@ -73,7 +74,9 @@ func ToCobra(cmd *command.Command, globalOptions command.Options) *cobra.Command
|
||||
Args: func(cc *cobra.Command, supplied []string) error {
|
||||
skipValidation, _ := cc.Flags().GetBool("skip-validation")
|
||||
if !skipValidation && runtime.ValidationEnabled() {
|
||||
cmd.Arguments.Parse(supplied)
|
||||
if err := cmd.Arguments.Parse(supplied); err != nil {
|
||||
return err
|
||||
}
|
||||
return cmd.Arguments.AreValid()
|
||||
}
|
||||
return nil
|
||||
|
@ -91,7 +91,9 @@ func Execute(version string) error {
|
||||
if idx == len(cmd.Path)-1 {
|
||||
leaf := ToCobra(cmd, cmdRoot.Options)
|
||||
container.AddCommand(leaf)
|
||||
container.ValidArgs = append(container.ValidArgs, leaf.Name())
|
||||
if container != ccRoot {
|
||||
container.ValidArgs = append(container.ValidArgs, leaf.Name())
|
||||
}
|
||||
log.Tracef("cobra: %s => %s", leaf.Name(), container.CommandPath())
|
||||
break
|
||||
}
|
||||
|
@ -51,7 +51,8 @@ func anySliceToStringSlice(src any) []string {
|
||||
return res
|
||||
}
|
||||
|
||||
func (args *Arguments) Parse(supplied []string) {
|
||||
func (args *Arguments) Parse(supplied []string) error {
|
||||
parsed := []string{}
|
||||
for idx, arg := range *args {
|
||||
argumentProvided := idx < len(supplied)
|
||||
|
||||
@ -76,7 +77,13 @@ func (args *Arguments) Parse(supplied []string) {
|
||||
} else {
|
||||
arg.SetValue([]string{supplied[idx]})
|
||||
}
|
||||
parsed = append(parsed, *arg.provided...)
|
||||
}
|
||||
|
||||
if len(parsed) != len(supplied) {
|
||||
return errors.BadArguments{Msg: fmt.Sprintf("Unexpected arguments provided: %s", supplied)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (args *Arguments) AreValid() error {
|
||||
@ -106,7 +113,9 @@ func (args *Arguments) CompletionFunction(cc *cobra.Command, provided []string,
|
||||
lastArg := (*args)[len(*args)-1]
|
||||
hasVariadicArg := expectedArgLen > 0 && lastArg.Variadic
|
||||
lastArg.Command.Options.Parse(cc.Flags())
|
||||
args.Parse(provided)
|
||||
if err := args.Parse(provided); err != nil {
|
||||
return []string{err.Error()}, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
|
||||
directive = cobra.ShellCompDirectiveDefault
|
||||
if argsCompleted < expectedArgLen || hasVariadicArg {
|
||||
|
@ -39,7 +39,7 @@ func testCommand() *Command {
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
cmd := testCommand()
|
||||
cmd.Arguments.Parse([]string{"asdf", "one", "two", "three"})
|
||||
cmd.Arguments.Parse([]string{"asdf", "one", "two", "three"}) // nolint: errcheck
|
||||
known := cmd.Arguments.AllKnown()
|
||||
|
||||
if !cmd.Arguments[0].IsKnown() {
|
||||
@ -67,7 +67,7 @@ func TestParse(t *testing.T) {
|
||||
}
|
||||
|
||||
cmd = testCommand()
|
||||
cmd.Arguments.Parse([]string{"asdf"})
|
||||
cmd.Arguments.Parse([]string{"asdf"}) // nolint: errcheck
|
||||
known = cmd.Arguments.AllKnown()
|
||||
|
||||
if !cmd.Arguments[0].IsKnown() {
|
||||
@ -209,7 +209,7 @@ func TestArgumentsValidate(t *testing.T) {
|
||||
cmd.Arguments[1] = staticArgument("second", "", []string{"one", "two", "three"}, true)
|
||||
cmd.SetBindings()
|
||||
|
||||
cmd.Arguments.Parse([]string{"first", "one", "three", "two"})
|
||||
cmd.Arguments.Parse([]string{"first", "one", "three", "two"}) // nolint: errcheck
|
||||
|
||||
err := cmd.Arguments.AreValid()
|
||||
if err == nil {
|
||||
@ -219,7 +219,7 @@ func TestArgumentsValidate(t *testing.T) {
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.Command.FullName(), func(t *testing.T) {
|
||||
c.Command.Arguments.Parse(c.Args)
|
||||
c.Command.Arguments.Parse(c.Args) // nolint: errcheck
|
||||
|
||||
err := c.Command.Arguments.AreValid()
|
||||
if err == nil {
|
||||
@ -232,144 +232,6 @@ func TestArgumentsValidate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// func TestArgumentsToEnv(t *testing.T) {
|
||||
// cases := []struct {
|
||||
// Command *Command
|
||||
// Args []string
|
||||
// Expect []string
|
||||
// Env []string
|
||||
// }{
|
||||
// {
|
||||
// Args: []string{"something"},
|
||||
// Expect: []string{"export MILPA_ARG_FIRST=something"},
|
||||
// Command: &Command{
|
||||
// // Name: []string{"test", "required", "present"},
|
||||
// Arguments: []*Argument{
|
||||
// {
|
||||
// Name: "first",
|
||||
// Required: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// Args: []string{},
|
||||
// Expect: []string{"export MILPA_ARG_FIRST=default"},
|
||||
// Command: &Command{
|
||||
// // Name: []string{"test", "default", "present"},
|
||||
// Arguments: []*Argument{
|
||||
// {
|
||||
// Name: "first",
|
||||
// Default: "default",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// Args: []string{"zero", "one", "two", "three"},
|
||||
// Expect: []string{
|
||||
// "export MILPA_ARG_FIRST=zero",
|
||||
// "declare -a MILPA_ARG_VARIADIC='( one two three )'",
|
||||
// },
|
||||
// Command: &Command{
|
||||
// // Name: []string{"test", "variadic"},
|
||||
// Arguments: []*Argument{
|
||||
// {
|
||||
// Name: "first",
|
||||
// Default: "default",
|
||||
// },
|
||||
// {
|
||||
// Name: "variadic",
|
||||
// Variadic: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// Args: []string{},
|
||||
// Expect: []string{"export MILPA_ARG_FIRST=default"},
|
||||
// Command: &Command{
|
||||
// // Name: []string{"test", "static", "default"},
|
||||
// Arguments: []*Argument{
|
||||
// {
|
||||
// Name: "first",
|
||||
// Default: "default",
|
||||
// Values: &ValueSource{
|
||||
// Static: &[]string{
|
||||
// "default",
|
||||
// "good",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// Args: []string{"good"},
|
||||
// Expect: []string{"export MILPA_ARG_FIRST=good"},
|
||||
// Command: &Command{
|
||||
// // Name: []string{"test", "static", "good"},
|
||||
// Arguments: []*Argument{
|
||||
// {
|
||||
// Name: "first",
|
||||
// Default: "default",
|
||||
// Values: &ValueSource{
|
||||
// Static: &[]string{
|
||||
// "default",
|
||||
// "good",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// Args: []string{"good"},
|
||||
// Expect: []string{"export MILPA_ARG_FIRST=good"},
|
||||
// Command: &Command{
|
||||
// // Name: []string{"test", "script", "good"},
|
||||
// Arguments: []*Argument{
|
||||
// {
|
||||
// Name: "first",
|
||||
// Default: "default",
|
||||
// Values: &ValueSource{
|
||||
// Script: "echo good; echo default",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
|
||||
// for _, c := range cases {
|
||||
// t.Run(c.Command.FullName(), func(t *testing.T) {
|
||||
// dst := []string{}
|
||||
// c.Command.SetBindings()
|
||||
// c.Command.Arguments.Parse(c.Args)
|
||||
// c.Command.Arguments.ToEnv(c.Command, &dst, "export ")
|
||||
|
||||
// err := c.Command.Arguments.AreValid()
|
||||
// if err != nil {
|
||||
// t.Fatalf("Unexpected failure validating: %s", err)
|
||||
// }
|
||||
|
||||
// for _, expected := range c.Expect {
|
||||
// found := false
|
||||
// for _, actual := range dst {
|
||||
// if strings.HasPrefix(actual, expected) {
|
||||
// found = true
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
// if !found {
|
||||
// t.Fatalf("Expected line %v not found in %v", expected, dst)
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
func TestArgumentToDesc(t *testing.T) {
|
||||
cases := []struct {
|
||||
Arg *Argument
|
||||
|
@ -119,7 +119,9 @@ func (cmd *Command) FlagSet() *pflag.FlagSet {
|
||||
}
|
||||
|
||||
func (cmd *Command) ParseInput(cc *cobra.Command, args []string) error {
|
||||
cmd.Arguments.Parse(args)
|
||||
if err := cmd.Arguments.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
skipValidation, _ := cc.Flags().GetBool("skip-validation")
|
||||
cmd.Options.Parse(cc.Flags())
|
||||
if !skipValidation {
|
||||
|
@ -165,7 +165,9 @@ func (opt *Option) CompletionFunction(cmd *cobra.Command, args []string, toCompl
|
||||
return
|
||||
}
|
||||
|
||||
opt.Command.Arguments.Parse(args)
|
||||
if err := opt.Command.Arguments.Parse(args); err != nil {
|
||||
return []string{err.Error()}, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
opt.Command.Options.Parse(cmd.Flags())
|
||||
|
||||
var err error
|
||||
|
@ -311,7 +311,7 @@ func (vs *ValueSource) UnmarshalYAML(node *yaml.Node) error {
|
||||
|
||||
var customCompleters = map[string]CompletionFunc{}
|
||||
|
||||
// Registers a completion function for the given command.ValueType key name
|
||||
// Registers a completion function for the given command.ValueType key name.
|
||||
func RegisterValueSource(key string, completion CompletionFunc) {
|
||||
customCompleters[key] = completion
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ func TestResolveTemplate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}).SetBindings()
|
||||
cmd.Arguments.Parse(test.Args)
|
||||
cmd.Arguments.Parse(test.Args) // nolint: errcheck
|
||||
cmd.Options.Parse(test.Flags)
|
||||
res, err := cmd.ResolveTemplate(test.Tpl, "")
|
||||
|
||||
|
@ -1,14 +1,38 @@
|
||||
// Copyright © 2022 Roberto Hidalgo <chinampa@un.rob.mx>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.rob.mx/nidito/chinampa/pkg/runtime"
|
||||
"github.com/fatih/color"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var bold *color.Color
|
||||
var boldRedBG *color.Color
|
||||
var boldRed *color.Color
|
||||
var boldYellowBG *color.Color
|
||||
var boldYellow *color.Color
|
||||
var dimmed *color.Color
|
||||
|
||||
func init() {
|
||||
bold = color.New(color.Bold)
|
||||
bold.EnableColor()
|
||||
boldRedBG = color.New(color.Bold, color.BgRed)
|
||||
boldRedBG.EnableColor()
|
||||
boldRed = color.New(color.Bold, color.FgHiRed)
|
||||
boldRed.EnableColor()
|
||||
boldYellowBG = color.New(color.Bold, color.BgYellow, color.FgBlack)
|
||||
boldYellowBG.EnableColor()
|
||||
boldYellow = color.New(color.Bold, color.FgHiYellow)
|
||||
boldYellow.EnableColor()
|
||||
dimmed = color.New(color.Faint)
|
||||
dimmed.EnableColor()
|
||||
}
|
||||
|
||||
type Formatter struct {
|
||||
}
|
||||
|
||||
@ -16,7 +40,8 @@ func (f *Formatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||
prefix := ""
|
||||
colorEnabled := runtime.ColorEnabled()
|
||||
message := entry.Message
|
||||
if runtime.VerboseEnabled() {
|
||||
switch {
|
||||
case runtime.VerboseEnabled():
|
||||
date := strings.Replace(entry.Time.Local().Format(time.DateTime), " ", "T", 1)
|
||||
component := ""
|
||||
if c, ok := entry.Data[componentKey]; ok {
|
||||
@ -24,36 +49,40 @@ func (f *Formatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||
}
|
||||
level := entry.Level.String()
|
||||
if colorEnabled {
|
||||
if entry.Level <= logrus.ErrorLevel {
|
||||
level = "\033[31m\033[1m" + level + "\033[0m"
|
||||
} else if entry.Level == logrus.WarnLevel {
|
||||
level = "\033[33m\033[1m" + level + "\033[0m"
|
||||
} else if entry.Level >= logrus.DebugLevel && colorEnabled {
|
||||
message = "\033[2m" + message + "\033[0m"
|
||||
switch {
|
||||
case entry.Level <= logrus.ErrorLevel:
|
||||
level = boldRed.Sprint(level)
|
||||
case entry.Level == logrus.WarnLevel:
|
||||
level = boldYellow.Sprint(level)
|
||||
case entry.Level >= logrus.DebugLevel:
|
||||
level = dimmed.Sprint(level)
|
||||
message = dimmed.Sprint(message)
|
||||
default:
|
||||
level = dimmed.Sprint(level)
|
||||
}
|
||||
}
|
||||
|
||||
prefix = fmt.Sprintf("\033[2m%s %s%s\033[0m\t", date, level, component)
|
||||
} else if entry.Level == logrus.ErrorLevel {
|
||||
prefix = dimmed.Sprint(date) + " " + level + dimmed.Sprint(component) + "\t"
|
||||
case entry.Level == logrus.ErrorLevel:
|
||||
if colorEnabled {
|
||||
prefix = "\033[41m\033[1m ERROR \033[0m "
|
||||
prefix = boldRedBG.Sprint(" ERROR ") + " "
|
||||
} else {
|
||||
prefix = "ERROR: "
|
||||
}
|
||||
} else if entry.Level == logrus.WarnLevel {
|
||||
case entry.Level == logrus.WarnLevel:
|
||||
if colorEnabled {
|
||||
prefix = "\033[43m\033[31m warning \033[0m "
|
||||
message = "\033[33m" + message + "\033[0m"
|
||||
prefix = boldYellowBG.Sprint(" WARNING ") + " "
|
||||
} else {
|
||||
prefix = "WARNING: "
|
||||
}
|
||||
} else if entry.Level >= logrus.DebugLevel {
|
||||
case entry.Level >= logrus.DebugLevel:
|
||||
if colorEnabled {
|
||||
prefix = "\033[2m" + entry.Level.String() + ":\033[0m "
|
||||
message = "\033[2m" + message + "\033[0m"
|
||||
prefix = dimmed.Sprintf("%s: ", strings.ToUpper(entry.Level.String()))
|
||||
message = dimmed.Sprint(message)
|
||||
} else {
|
||||
prefix = strings.ToUpper(entry.Level.String()) + ": "
|
||||
}
|
||||
}
|
||||
|
||||
return []byte(prefix + message + "\n"), nil
|
||||
}
|
||||
|
187
pkg/logger/log_test.go
Normal file
187
pkg/logger/log_test.go
Normal file
@ -0,0 +1,187 @@
|
||||
// Copyright © 2022 Roberto Hidalgo <chinampa@un.rob.mx>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package logger_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "git.rob.mx/nidito/chinampa/pkg/logger"
|
||||
rt "git.rob.mx/nidito/chinampa/pkg/runtime"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func withEnv(t *testing.T, env map[string]string) {
|
||||
prevEnv := os.Environ()
|
||||
for _, entry := range prevEnv {
|
||||
parts := strings.SplitN(entry, "=", 2)
|
||||
os.Unsetenv(parts[0])
|
||||
}
|
||||
|
||||
for k, v := range env {
|
||||
os.Setenv(k, v)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
rt.ResetParsedFlags()
|
||||
|
||||
for k := range env {
|
||||
os.Unsetenv(k)
|
||||
}
|
||||
for _, entry := range prevEnv {
|
||||
parts := strings.SplitN(entry, "=", 2)
|
||||
os.Setenv(parts[0], parts[1])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestFormatter(t *testing.T) {
|
||||
now := strings.Replace(time.Now().Local().Format(time.DateTime), " ", "T", 1)
|
||||
cases := []struct {
|
||||
Color bool
|
||||
Verbose bool
|
||||
Call func(args ...any)
|
||||
Expects string
|
||||
Level logrus.Level
|
||||
}{
|
||||
{
|
||||
Color: true,
|
||||
Call: Info,
|
||||
Expects: "message",
|
||||
Level: logrus.InfoLevel,
|
||||
},
|
||||
{
|
||||
Color: true,
|
||||
Verbose: true,
|
||||
Call: Info,
|
||||
Expects: fmt.Sprintf("\033[2m%s\033[0m \033[2minfo\033[0m\033[2m\033[0m message", now),
|
||||
Level: logrus.InfoLevel,
|
||||
},
|
||||
{
|
||||
Color: true,
|
||||
Call: Debug,
|
||||
Expects: "",
|
||||
Level: logrus.InfoLevel,
|
||||
},
|
||||
{
|
||||
Call: Debug,
|
||||
Expects: "DEBUG: message",
|
||||
Level: logrus.DebugLevel,
|
||||
},
|
||||
{
|
||||
Color: true,
|
||||
Call: Debug,
|
||||
Expects: "\033[2mDEBUG: \033[0m\033[2mmessage\033[0m",
|
||||
Level: logrus.DebugLevel,
|
||||
},
|
||||
{
|
||||
Color: true,
|
||||
Verbose: true,
|
||||
Call: Debug,
|
||||
Expects: fmt.Sprintf("\033[2m%s\033[0m \033[2mdebug\033[0m\033[2m\033[0m\t\033[2mmessage\033[0m",
|
||||
now),
|
||||
Level: logrus.DebugLevel,
|
||||
},
|
||||
{
|
||||
Call: Trace,
|
||||
Expects: "",
|
||||
Level: logrus.DebugLevel,
|
||||
},
|
||||
{
|
||||
Call: Trace,
|
||||
Expects: "TRACE: message",
|
||||
Level: logrus.TraceLevel,
|
||||
},
|
||||
{
|
||||
Call: Warn,
|
||||
Expects: "",
|
||||
Level: logrus.ErrorLevel,
|
||||
},
|
||||
{
|
||||
Call: Warn,
|
||||
Expects: "WARNING: message",
|
||||
Level: logrus.InfoLevel,
|
||||
},
|
||||
{
|
||||
Call: Warn,
|
||||
Level: logrus.InfoLevel,
|
||||
Color: true,
|
||||
Verbose: true,
|
||||
Expects: fmt.Sprintf("\033[2m%s\033[0m \033[1;93mwarning\033[0m\033[2m\033[0m\tmessage", now),
|
||||
},
|
||||
{
|
||||
Call: Warn,
|
||||
Level: logrus.InfoLevel,
|
||||
Color: true,
|
||||
Expects: "\033[1;43;30m WARNING \033[0m message",
|
||||
},
|
||||
{
|
||||
Call: Error,
|
||||
Expects: "ERROR: message",
|
||||
Level: logrus.ErrorLevel,
|
||||
},
|
||||
{
|
||||
Call: Error,
|
||||
Expects: "ERROR: message",
|
||||
Level: logrus.InfoLevel,
|
||||
},
|
||||
{
|
||||
Call: Error,
|
||||
Level: logrus.InfoLevel,
|
||||
Color: true,
|
||||
Verbose: true,
|
||||
Expects: fmt.Sprintf("\033[2m%s\033[0m \033[1;91merror\033[0m\033[2m\033[0m\tmessage", now),
|
||||
},
|
||||
{
|
||||
Call: Error,
|
||||
Level: logrus.InfoLevel,
|
||||
Color: true,
|
||||
Expects: "\033[1;41m ERROR \033[0m message",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
fname := runtime.FuncForPC(reflect.ValueOf(c.Call).Pointer()).Name()
|
||||
comps := []string{fname, c.Level.String()}
|
||||
if c.Color {
|
||||
comps = append(comps, "color")
|
||||
}
|
||||
if c.Verbose {
|
||||
comps = append(comps, "verbose")
|
||||
}
|
||||
name := strings.Join(comps, "/")
|
||||
t.Run(name, func(t *testing.T) {
|
||||
env := map[string]string{
|
||||
"COLOR": "",
|
||||
"VERBOSE": "",
|
||||
}
|
||||
if c.Color {
|
||||
env["COLOR"] = "always"
|
||||
} else {
|
||||
env["NO_COLOR"] = "1"
|
||||
}
|
||||
if c.Verbose {
|
||||
env["VERBOSE"] = "1"
|
||||
}
|
||||
withEnv(t, env)
|
||||
data := bytes.Buffer{}
|
||||
logrus.SetLevel(c.Level)
|
||||
logrus.SetOutput(&data)
|
||||
c.Call("message")
|
||||
expected := c.Expects
|
||||
if c.Expects != "" {
|
||||
expected = c.Expects + "\n"
|
||||
}
|
||||
|
||||
if res := data.String(); res != expected {
|
||||
t.Fatalf("%s:\ngot : %s\nwanted: %v", name, res, expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -52,39 +52,72 @@ func isTrueIsh(val string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var _flags map[string]bool
|
||||
|
||||
func ResetParsedFlags() {
|
||||
_flags = nil
|
||||
}
|
||||
|
||||
func flagInArgs(name string) bool {
|
||||
if _flags == nil {
|
||||
_flags = map[string]bool{}
|
||||
for _, arg := range os.Args {
|
||||
switch arg {
|
||||
case "--verbose":
|
||||
_flags["verbose"] = true
|
||||
delete(_flags, "silent")
|
||||
case "--silent":
|
||||
_flags["silent"] = true
|
||||
delete(_flags, "verbose")
|
||||
case "--color":
|
||||
_flags["color"] = true
|
||||
delete(_flags, "no-color")
|
||||
case "--no-color":
|
||||
_flags["no-color"] = true
|
||||
delete(_flags, "color")
|
||||
case "--skip-validation":
|
||||
_flags["skip-validation"] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, ok := _flags[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func DebugEnabled() bool {
|
||||
return isTrueIsh(os.Getenv(env.Debug))
|
||||
}
|
||||
|
||||
func ValidationEnabled() bool {
|
||||
return isFalseIsh(os.Getenv(env.ValidationDisabled))
|
||||
return !flagInArgs("skip-validation") && isFalseIsh(os.Getenv(env.ValidationDisabled))
|
||||
}
|
||||
|
||||
func VerboseEnabled() bool {
|
||||
for _, arg := range os.Args {
|
||||
if arg == "--verbose" {
|
||||
return true
|
||||
}
|
||||
if flagInArgs("silent") {
|
||||
return false
|
||||
}
|
||||
return isTrueIsh(os.Getenv(env.Verbose))
|
||||
return isTrueIsh(os.Getenv(env.Verbose)) || flagInArgs("verbose")
|
||||
}
|
||||
|
||||
func SilenceEnabled() bool {
|
||||
for _, arg := range os.Args {
|
||||
if arg == "--silent" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if VerboseEnabled() {
|
||||
if flagInArgs("verbose") {
|
||||
return false
|
||||
}
|
||||
if flagInArgs("silent") {
|
||||
return true
|
||||
}
|
||||
|
||||
return isTrueIsh(os.Getenv(env.Silent))
|
||||
return isTrueIsh(os.Getenv(env.Silent)) || flagInArgs("silent")
|
||||
}
|
||||
|
||||
func ColorEnabled() bool {
|
||||
return isFalseIsh(os.Getenv(env.NoColor)) && !UnstyledHelpEnabled()
|
||||
if flagInArgs("color") {
|
||||
return true
|
||||
}
|
||||
|
||||
// we're talking to ttys, we want color unless NO_COLOR/--no-color
|
||||
return !(isTrueIsh(os.Getenv(env.NoColor)) || UnstyledHelpEnabled() || flagInArgs("no-color"))
|
||||
}
|
||||
|
||||
func UnstyledHelpEnabled() bool {
|
||||
|
@ -8,15 +8,146 @@ import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.rob.mx/nidito/chinampa/pkg/env"
|
||||
. "git.rob.mx/nidito/chinampa/pkg/runtime"
|
||||
)
|
||||
|
||||
func TestEnabled(t *testing.T) {
|
||||
defer func() { os.Setenv(env.Verbose, "") }()
|
||||
func withEnv(t *testing.T, env map[string]string) {
|
||||
prevEnv := os.Environ()
|
||||
for _, entry := range prevEnv {
|
||||
parts := strings.SplitN(entry, "=", 2)
|
||||
os.Unsetenv(parts[0])
|
||||
}
|
||||
|
||||
for k, v := range env {
|
||||
os.Setenv(k, v)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
ResetParsedFlags()
|
||||
|
||||
for k := range env {
|
||||
os.Unsetenv(k)
|
||||
}
|
||||
for _, entry := range prevEnv {
|
||||
parts := strings.SplitN(entry, "=", 2)
|
||||
os.Setenv(parts[0], parts[1])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCombinations(t *testing.T) {
|
||||
args := append([]string{}, os.Args...)
|
||||
t.Cleanup(func() { os.Args = args })
|
||||
cases := []struct {
|
||||
Env map[string]string
|
||||
Args []string
|
||||
Func func() bool
|
||||
Expects bool
|
||||
}{
|
||||
{
|
||||
Env: map[string]string{},
|
||||
Args: []string{},
|
||||
Func: VerboseEnabled,
|
||||
Expects: false,
|
||||
},
|
||||
{
|
||||
Env: map[string]string{env.Verbose: "1"},
|
||||
Args: []string{"--silent"},
|
||||
Func: VerboseEnabled,
|
||||
Expects: false,
|
||||
},
|
||||
{
|
||||
Env: map[string]string{env.Verbose: "1"},
|
||||
Args: []string{},
|
||||
Func: VerboseEnabled,
|
||||
Expects: true,
|
||||
},
|
||||
{
|
||||
Env: map[string]string{env.Silent: "1"},
|
||||
Args: []string{},
|
||||
Func: VerboseEnabled,
|
||||
Expects: false,
|
||||
},
|
||||
{
|
||||
Env: map[string]string{},
|
||||
Args: []string{},
|
||||
Func: SilenceEnabled,
|
||||
Expects: false,
|
||||
},
|
||||
{
|
||||
Env: map[string]string{env.Silent: "1"},
|
||||
Args: []string{},
|
||||
Func: SilenceEnabled,
|
||||
Expects: true,
|
||||
},
|
||||
{
|
||||
Env: map[string]string{env.Silent: "1"},
|
||||
Args: []string{"--verbose"},
|
||||
Func: SilenceEnabled,
|
||||
Expects: false,
|
||||
},
|
||||
{
|
||||
Env: map[string]string{env.Verbose: "1"},
|
||||
Args: []string{"--silent"},
|
||||
Func: SilenceEnabled,
|
||||
Expects: true,
|
||||
},
|
||||
{
|
||||
Env: map[string]string{env.ForceColor: "1"},
|
||||
Args: []string{"--no-color"},
|
||||
Func: ColorEnabled,
|
||||
Expects: false,
|
||||
},
|
||||
{
|
||||
Env: map[string]string{},
|
||||
Args: []string{},
|
||||
Func: ColorEnabled,
|
||||
Expects: true,
|
||||
},
|
||||
{
|
||||
Env: map[string]string{env.ForceColor: "1"},
|
||||
Args: []string{},
|
||||
Func: ColorEnabled,
|
||||
Expects: true,
|
||||
},
|
||||
{
|
||||
Env: map[string]string{env.ForceColor: "1"},
|
||||
Args: []string{"--no-color"},
|
||||
Func: ColorEnabled,
|
||||
Expects: false,
|
||||
},
|
||||
{
|
||||
Env: map[string]string{env.NoColor: "1"},
|
||||
Args: []string{},
|
||||
Func: ColorEnabled,
|
||||
Expects: false,
|
||||
},
|
||||
{
|
||||
Env: map[string]string{env.NoColor: "1"},
|
||||
Args: []string{"--color"},
|
||||
Func: ColorEnabled,
|
||||
Expects: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
fname := runtime.FuncForPC(reflect.ValueOf(c.Func).Pointer()).Name()
|
||||
name := fmt.Sprintf("%v/%v/%s", fname, c.Env, c.Args)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
withEnv(t, c.Env)
|
||||
os.Args = c.Args
|
||||
if res := c.Func(); res != c.Expects {
|
||||
t.Fatalf("%s got %v wanted: %v", name, res, c.Expects)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnabled(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
Func func() bool
|
||||
@ -27,6 +158,7 @@ func TestEnabled(t *testing.T) {
|
||||
Func: VerboseEnabled,
|
||||
Expects: true,
|
||||
},
|
||||
|
||||
{
|
||||
Name: env.Silent,
|
||||
Func: SilenceEnabled,
|
||||
@ -64,7 +196,7 @@ func TestEnabled(t *testing.T) {
|
||||
}
|
||||
for _, val := range enabled {
|
||||
t.Run("enabled-"+val, func(t *testing.T) {
|
||||
os.Setenv(c.Name, val)
|
||||
withEnv(t, map[string]string{c.Name: val})
|
||||
if c.Func() != c.Expects {
|
||||
t.Fatalf("%s wasn't enabled with a valid value: %s", name, val)
|
||||
}
|
||||
@ -74,7 +206,7 @@ func TestEnabled(t *testing.T) {
|
||||
disabled := []string{"", "no", "false", "0", "disabled"}
|
||||
for _, val := range disabled {
|
||||
t.Run("disabled-"+val, func(t *testing.T) {
|
||||
os.Setenv(c.Name, val)
|
||||
withEnv(t, map[string]string{c.Name: val})
|
||||
if c.Func() == c.Expects {
|
||||
t.Fatalf("%s was enabled with falsy value: %s", name, val)
|
||||
}
|
||||
@ -84,40 +216,46 @@ func TestEnabled(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSilent(t *testing.T) {
|
||||
origArgs := os.Args
|
||||
t.Cleanup(func() {
|
||||
os.Args = origArgs
|
||||
})
|
||||
args := append([]string{}, os.Args...)
|
||||
t.Cleanup(func() { os.Args = args })
|
||||
t.Run("SILENT = silence", func(t *testing.T) {
|
||||
t.Setenv(env.Silent, "1")
|
||||
t.Setenv(env.Verbose, "")
|
||||
withEnv(t, map[string]string{
|
||||
env.Silent: "1",
|
||||
env.Verbose: "",
|
||||
})
|
||||
os.Args = []string{}
|
||||
if !SilenceEnabled() {
|
||||
t.Fail()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("SILENT + VERBOSE = silence", func(t *testing.T) {
|
||||
t.Setenv(env.Silent, "1")
|
||||
t.Setenv(env.Verbose, "1")
|
||||
t.Run("SILENT+VERBOSE=silence", func(t *testing.T) {
|
||||
withEnv(t, map[string]string{
|
||||
env.Silent: "1",
|
||||
env.Verbose: "1",
|
||||
})
|
||||
os.Args = []string{}
|
||||
if SilenceEnabled() {
|
||||
if !SilenceEnabled() {
|
||||
t.Fail()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("VERBOSE + --silent = silent", func(t *testing.T) {
|
||||
t.Setenv(env.Silent, "")
|
||||
t.Setenv(env.Verbose, "1")
|
||||
t.Run("VERBOSE+--silent=silent", func(t *testing.T) {
|
||||
withEnv(t, map[string]string{
|
||||
env.Silent: "0",
|
||||
env.Verbose: "1",
|
||||
})
|
||||
os.Args = []string{"some", "random", "--silent", "args"}
|
||||
if !SilenceEnabled() {
|
||||
t.Fail()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("--silent = silent", func(t *testing.T) {
|
||||
t.Setenv(env.Silent, "")
|
||||
t.Setenv(env.Verbose, "")
|
||||
t.Run("--silent=silent", func(t *testing.T) {
|
||||
withEnv(t, map[string]string{
|
||||
env.Silent: "",
|
||||
env.Verbose: "",
|
||||
})
|
||||
os.Args = []string{"some", "random", "--silent", "args"}
|
||||
if !SilenceEnabled() {
|
||||
t.Fail()
|
||||
@ -125,20 +263,27 @@ func TestSilent(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("nothing = nothing", func(t *testing.T) {
|
||||
t.Setenv(env.Silent, "")
|
||||
t.Setenv(env.Verbose, "")
|
||||
withEnv(t, map[string]string{
|
||||
env.Silent: "",
|
||||
env.Verbose: "",
|
||||
})
|
||||
os.Args = []string{"some", "random", "args"}
|
||||
if SilenceEnabled() {
|
||||
t.Fail()
|
||||
}
|
||||
if VerboseEnabled() {
|
||||
t.Fail()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnvironmentMapEnabled(t *testing.T) {
|
||||
trueString := strconv.FormatBool(true)
|
||||
os.Setenv(env.ForceColor, trueString)
|
||||
os.Setenv(env.Debug, trueString)
|
||||
os.Setenv(env.Verbose, trueString)
|
||||
withEnv(t, map[string]string{
|
||||
env.ForceColor: trueString,
|
||||
env.Debug: trueString,
|
||||
env.Verbose: trueString,
|
||||
})
|
||||
|
||||
res := EnvironmentMap()
|
||||
if res == nil {
|
||||
|
Loading…
Reference in New Issue
Block a user