From 6b24b4b8c11941920ab313ba35854f5c8aff4f1d Mon Sep 17 00:00:00 2001 From: Sergey Sorokin Date: Tue, 28 Mar 2023 01:23:05 +0300 Subject: [PATCH] 2.3.0 --- .github/workflows/go.yml | 4 +- .golangci.yml | 46 +------ bash/{decorator.sh => execute.sh} | 5 +- cmd/cli/main.go | 23 +++- go.mod | 2 +- internal/app/app.go | 57 ++++++++ internal/app/app_test.go | 58 +++++++++ internal/app/const.go | 6 + internal/app/usage.go | 19 +++ internal/cli/app.go | 31 ----- internal/cli/app_test.go | 61 --------- internal/cli/constants.go | 6 - internal/cli/shutdown.go | 21 --- internal/cmd/cmd.go | 99 -------------- internal/cmd/constants.go | 69 ---------- internal/cmd/contract.go | 11 ++ internal/cmd/docker_compose_ps.go | 119 +++++++++++++++++ internal/cmd/docker_images.go | 108 +++++++++++++++ internal/cmd/docker_ps.go | 123 ++++++++++++++++++ internal/cmd/parse.go | 34 +++++ internal/layout/column.go | 3 + internal/layout/header.go | 42 ++++++ internal/layout/parse.go | 55 ++++++++ internal/layout/row.go | 5 + internal/lines/contracts.go | 16 --- internal/lines/fmt/contracts.go | 5 - .../lines/fmt/docker_compose_ps_line_fmt.go | 75 ----------- internal/lines/fmt/docker_images_line_fmt.go | 89 ------------- internal/lines/fmt/docker_ps_line_fmt.go | 92 ------------- internal/lines/fmt/first_line_fmt.go | 13 -- internal/lines/line.go | 31 ----- internal/stdin/stdin.go | 14 +- internal/stdout/stdout.go | 6 +- internal/{utils/utils.go => util/util.go} | 6 +- .../utils_test.go => util/util_test.go} | 26 +++- internal/utils/pointer/pointer.go | 5 - internal/utils/pointer/pointer_test.go | 13 -- pkg/color/color_test.go | 55 +++++--- pkg/{utils => util}/assert/assertions.go | 2 + .../numbers.go => util/number/number.go} | 2 +- .../number/number_test.go} | 12 +- test/data/in/docker_compose_ps_1.out | 4 + test/data/in/invalid_cols.out | 2 +- test/data/out/docker_compose_ps_1.out | 4 + 44 files changed, 759 insertions(+), 720 deletions(-) rename bash/{decorator.sh => execute.sh} (58%) create mode 100644 internal/app/app.go create mode 100644 internal/app/app_test.go create mode 100644 internal/app/const.go create mode 100644 internal/app/usage.go delete mode 100644 internal/cli/app.go delete mode 100644 internal/cli/app_test.go delete mode 100644 internal/cli/constants.go delete mode 100644 internal/cli/shutdown.go delete mode 100644 internal/cmd/cmd.go delete mode 100644 internal/cmd/constants.go create mode 100644 internal/cmd/contract.go create mode 100644 internal/cmd/docker_compose_ps.go create mode 100644 internal/cmd/docker_images.go create mode 100644 internal/cmd/docker_ps.go create mode 100644 internal/cmd/parse.go create mode 100644 internal/layout/column.go create mode 100644 internal/layout/header.go create mode 100644 internal/layout/parse.go create mode 100644 internal/layout/row.go delete mode 100644 internal/lines/contracts.go delete mode 100644 internal/lines/fmt/contracts.go delete mode 100644 internal/lines/fmt/docker_compose_ps_line_fmt.go delete mode 100644 internal/lines/fmt/docker_images_line_fmt.go delete mode 100644 internal/lines/fmt/docker_ps_line_fmt.go delete mode 100644 internal/lines/fmt/first_line_fmt.go delete mode 100644 internal/lines/line.go rename internal/{utils/utils.go => util/util.go} (85%) rename internal/{utils/utils_test.go => util/util_test.go} (76%) delete mode 100644 internal/utils/pointer/pointer.go delete mode 100644 internal/utils/pointer/pointer_test.go rename pkg/{utils => util}/assert/assertions.go (94%) rename pkg/{utils/numbers/numbers.go => util/number/number.go} (93%) rename pkg/{utils/numbers/numbers_test.go => util/number/number_test.go} (69%) create mode 100644 test/data/in/docker_compose_ps_1.out create mode 100644 test/data/out/docker_compose_ps_1.out diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 7ef642a..2a45b3e 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.19 + go-version: 1.20 - name: Lint uses: golangci/golangci-lint-action@v2 @@ -28,4 +28,4 @@ jobs: run: go test -race -coverprofile=coverage.out -covermode=atomic -tags test ./... - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 \ No newline at end of file + uses: codecov/codecov-action@v2 diff --git a/.golangci.yml b/.golangci.yml index d4493ff..6aa9e23 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,44 +1,10 @@ linters: - enable: - - contextcheck - - depguard - - dogsled - - dupl - - errorlint - - exhaustive - - exportloopref - - forcetypeassert - - funlen - - gochecknoglobals - - gochecknoinits - - gocognit - - goconst - - gocritic - - gocyclo - - gofmt - - gofumpt - - goimports - - goprintffuncname - - gosec - - importas - - ireturn - - lll - - makezero - - misspell - - nakedret - - nestif - - nilerr - - nilnil - - nolintlint - - prealloc - - revive - - stylecheck - - tparallel - - unconvert - - unparam - - whitespace - - wsl + enable-all: true linters-settings: gofumpt: - module-path: docker-color-output \ No newline at end of file + module-path: docker-color-output + varnamelen: + ignore-names: [ i, in, j, m, sb, tt, v ] + wrapcheck: + ignorePackageGlobs: [ docker-color-output/internal/* ] diff --git a/bash/decorator.sh b/bash/execute.sh similarity index 58% rename from bash/decorator.sh rename to bash/execute.sh index 322b318..52c150a 100644 --- a/bash/decorator.sh +++ b/bash/execute.sh @@ -1,8 +1,7 @@ #!/usr/bin/env bash # Usage: -# alias docker='bash decorator.sh docker' -# alias docker-compose='bash decorator.sh docker-compose' +# alias docker='bash execute.sh docker' if [[ "$1" == "docker" ]]; then if [[ "$2" == "ps" || "$2" == "images" ]]; then "$@" | docker-color-output @@ -11,8 +10,6 @@ if [[ "$1" == "docker" ]]; then else "$@" fi -elif [[ "$1" == "docker-compose" && "$2" == "ps" ]]; then - "$@" | docker-color-output else "$@" fi diff --git a/cmd/cli/main.go b/cmd/cli/main.go index d6451a9..6154c55 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -3,23 +3,34 @@ package main import ( - "docker-color-output/internal/cli" + "os" + + "docker-color-output/internal/app" "docker-color-output/internal/stdin" "docker-color-output/internal/stdout" ) func main() { + if err := run(); err != nil { + app.Usage(err) + os.Exit(1) + } +} + +func run() error { in, err := stdin.Get() if err != nil { - cli.Exit(err) + return err } - out, err := cli.Execute(in) + rows, err := app.Run(in) if err != nil { - cli.Exit(err) + return err } - for _, v := range out { - stdout.Println(v) + for _, row := range rows { + stdout.Println(row) } + + return nil } diff --git a/go.mod b/go.mod index e7a594c..054bfc2 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module docker-color-output -go 1.19 +go 1.20 diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 0000000..f493396 --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,57 @@ +package app + +import ( + "errors" + "strings" + + "docker-color-output/internal/cmd" + "docker-color-output/internal/layout" + "docker-color-output/internal/util" + "docker-color-output/pkg/color" +) + +var ( + ErrNoFirstLine = errors.New("no first line") + ErrNullableColumns = errors.New("nullable columns more than one") +) + +func Run(in []string) ([]string, error) { + if len(in) == 0 { + return nil, ErrNoFirstLine + } + + header := layout.ParseHeader(in) + rows := layout.ParseRows(in, &header) + + if header.NullableCols() > 1 { + return nil, ErrNullableColumns + } + + command, err := cmd.Parse(header) + if err != nil { + return nil, err + } + + res := make([]string, len(in)) + + // First line + var sb strings.Builder + for _, col := range header { + sb.WriteString(util.Pad(color.LightBlue(string(col.Name)), col.MaxLength)) + } + + res[0] = sb.String() + + // Rows + for i, row := range rows { + sb.Reset() + + for _, col := range header { + sb.WriteString(util.Pad(command.Format(row, col.Name), col.MaxLength)) + } + + res[i+1] = sb.String() + } + + return res, nil +} diff --git a/internal/app/app_test.go b/internal/app/app_test.go new file mode 100644 index 0000000..d3a5a98 --- /dev/null +++ b/internal/app/app_test.go @@ -0,0 +1,58 @@ +package app_test + +import ( + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "docker-color-output/internal/app" + "docker-color-output/pkg/util/assert" +) + +func TestRun(t *testing.T) { + t.Parallel() + + read := func(filename string) []string { + _, b, _, _ := runtime.Caller(0) + path := filepath.Dir(b) + bytes, _ := os.ReadFile(path + "/../../test/data/" + filename) + res := strings.Split(string(bytes), "\n") + res = res[:len(res)-1] + + if len(res) == 0 { + return nil + } + + return res + } + + tests := map[string]struct { + in string + want string + wantErr bool + }{ + "no_stdin": {wantErr: true}, + "no_first_line": {in: "no_first_line.out", wantErr: true}, + "invalid_cols": {in: "invalid_cols.out", wantErr: true}, + "docker_ps": {in: "docker_ps.out", want: "docker_ps.out"}, + "docker_ps:custom_cols": {in: "docker_ps_custom_cols.out", want: "docker_ps_custom_cols.out"}, + "docker_ps:nullable_col": {in: "docker_ps_nullable_col.out", want: "docker_ps_nullable_col.out"}, + "docker_ps:nullable_cols": {in: "docker_ps_nullable_cols.out", wantErr: true}, + "docker_images": {in: "docker_images.out", want: "docker_images.out"}, + "docker_compose_ps": {in: "docker_compose_ps.out", want: "docker_compose_ps.out"}, + "docker_compose_ps_1": {in: "docker_compose_ps_1.out", want: "docker_compose_ps_1.out"}, + } + + for name, tt := range tests { + tt := tt + + t.Run(name, func(t *testing.T) { + t.Parallel() + rows, err := app.Run(read("in/" + tt.in)) + assert.Equal(t, tt.wantErr, err != nil) + assert.Equal(t, read("out/"+tt.want), rows) + }) + } +} diff --git a/internal/app/const.go b/internal/app/const.go new file mode 100644 index 0000000..aae996f --- /dev/null +++ b/internal/app/const.go @@ -0,0 +1,6 @@ +package app + +const ( + Ver = "2.3.0" + Name = "docker-color-output" +) diff --git a/internal/app/usage.go b/internal/app/usage.go new file mode 100644 index 0000000..be1861e --- /dev/null +++ b/internal/app/usage.go @@ -0,0 +1,19 @@ +//go:build !test + +package app + +import ( + "fmt" + + "docker-color-output/pkg/color" +) + +//nolint:forbidigo +func Usage(err error) { + fmt.Println(color.LightRed("šŸ’” Error: " + err.Error())) + fmt.Println("šŸ’„ Version: " + color.Green(Ver)) + fmt.Println("šŸ‘Œ Usage:") + fmt.Println(" " + color.Green("docker compose ps") + " [-a] | " + color.Brown(Name)) + fmt.Println(" " + color.Green("docker images") + " [--format] | " + color.Brown(Name)) + fmt.Println(" " + color.Green("docker ps") + " [-a] [--format] | " + color.Brown(Name)) +} diff --git a/internal/cli/app.go b/internal/cli/app.go deleted file mode 100644 index f309fd7..0000000 --- a/internal/cli/app.go +++ /dev/null @@ -1,31 +0,0 @@ -package cli - -import ( - "docker-color-output/internal/cmd" - "docker-color-output/internal/lines" - "docker-color-output/internal/lines/fmt" -) - -func Execute(in []string) ([]*lines.Line, error) { - c, err := cmd.ParseCmd(in) - if err != nil { - return nil, err - } - - res := make([]*lines.Line, len(in)) - - cols, vals := cmd.ParseFirstLine(in[0]) - - res[0] = lines.NewLine(vals, cols, fmt.NewFirstLineFmt()) - - pl, err := cmd.ParseLines(in[1:], cols) - if err != nil { - return nil, err - } - - for i, vals := range pl { - res[i+1] = lines.NewLine(vals, cols, c.GetFmt()) - } - - return res, nil -} diff --git a/internal/cli/app_test.go b/internal/cli/app_test.go deleted file mode 100644 index 7c264fc..0000000 --- a/internal/cli/app_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package cli - -import ( - "os" - "path/filepath" - "runtime" - "strings" - "testing" - - "docker-color-output/internal/lines" - "docker-color-output/pkg/utils/assert" -) - -func TestExecute(t *testing.T) { - read := func(filename string) []string { - _, b, _, _ := runtime.Caller(0) - path := filepath.Dir(b) - bytes, _ := os.ReadFile(path + "/../../test/data/" + filename) - vals := strings.Split(string(bytes), "\n") - vals = vals[:len(vals)-1] - - if len(vals) == 0 { - return nil - } - - return vals - } - - build := func(in []*lines.Line) []string { - var vals []string - for _, v := range in { - vals = append(vals, v.Build()) - } - - return vals - } - - tests := map[string]struct { - in string - want string - wantErr bool - }{ - "no_stdin": {wantErr: true}, - "no_first_line": {in: "in/no_first_line.out", wantErr: true}, - "invalid_cols": {in: "in/invalid_cols.out", wantErr: true}, - "docker_ps": {in: "in/docker_ps.out", want: "out/docker_ps.out"}, - "docker_ps:custom_cols": {in: "in/docker_ps_custom_cols.out", want: "out/docker_ps_custom_cols.out"}, - "docker_ps:nullable_col": {in: "in/docker_ps_nullable_col.out", want: "out/docker_ps_nullable_col.out"}, - "docker_ps:nullable_cols": {in: "in/docker_ps_nullable_cols.out", wantErr: true}, - "docker_images": {in: "in/docker_images.out", want: "out/docker_images.out"}, - "docker_compose_ps": {in: "in/docker_compose_ps.out", want: "out/docker_compose_ps.out"}, - } - - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - vals, err := Execute(read(tt.in)) - assert.Equal(t, tt.wantErr, err != nil) - assert.Equal(t, read(tt.want), build(vals)) - }) - } -} diff --git a/internal/cli/constants.go b/internal/cli/constants.go deleted file mode 100644 index bab79af..0000000 --- a/internal/cli/constants.go +++ /dev/null @@ -1,6 +0,0 @@ -package cli - -const ( - Ver = "2.2.0" - App = "docker-color-output" -) diff --git a/internal/cli/shutdown.go b/internal/cli/shutdown.go deleted file mode 100644 index d1d040a..0000000 --- a/internal/cli/shutdown.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build !test - -package cli - -import ( - "fmt" - "os" - - "docker-color-output/internal/cmd" - "docker-color-output/pkg/color" -) - -func Exit(err error) { - fmt.Println(color.LightRed("šŸ’” Error: " + err.Error())) - fmt.Println("šŸ’„ Docker color output " + color.Green(Ver)) - fmt.Println("āš”ļø Usage:") - fmt.Println(" " + color.Green(cmd.DockerComposePs.String()) + " [-a] | " + color.Brown(App)) - fmt.Println(" " + color.Green(cmd.DockerImages.String()) + " [--format] | " + color.Brown(App)) - fmt.Println(" " + color.Green(cmd.DockerPs.String()) + " [-a] [--format] | " + color.Brown(App)) - os.Exit(1) -} diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go deleted file mode 100644 index af06546..0000000 --- a/internal/cmd/cmd.go +++ /dev/null @@ -1,99 +0,0 @@ -package cmd - -import ( - "errors" - "strings" - - "docker-color-output/internal/lines" - "docker-color-output/internal/utils" - "docker-color-output/internal/utils/pointer" -) - -func ParseCmd(in []string) (Cmd, error) { - if len(in) == 0 || len(in[0]) == 0 { - return "", errors.New("no first line") - } - - parts := utils.Split(in[0]) - - if utils.Intersect(parts, DockerPs.Columns()) { - return DockerPs, nil - } - - if utils.Intersect(parts, DockerImages.Columns()) { - return DockerImages, nil - } - - if utils.Intersect(parts, DockerComposePs.Columns()) { - return DockerComposePs, nil - } - - return "", errors.New("invalid first line") -} - -func ParseFirstLine(in string) ([]lines.Column, lines.Values) { - parts := utils.Split(in) - - cols := make([]lines.Column, len(parts)) - vals := make(lines.Values, len(parts)) - - for i, part := range parts { - cols[i] = lines.Column{ - Name: part, - Len: pointer.ToInt(len(part)), - } - - vals[part] = part - } - - return cols, vals -} - -func ParseLines(ins []string, cols []lines.Column) ([]lines.Values, error) { - if calculateNullableCols(cols) > 1 { - return nil, errors.New("nullable columns more than one") - } - - rows := make([]lines.Values, len(ins)) - - for i, in := range ins { - rows[i] = make(lines.Values, len(cols)) - - parts := utils.Split(in) - - offset := 0 - mismatch := len(parts) < len(cols) - - for j, col := range cols { - if mismatch && col.IsNullable() { - offset++ - continue - } - - v := parts[j-offset] - - l := len(v) - if strings.Contains(v, "ā€¦") { - l -= 2 - } - - if l > *col.Len { - *col.Len = l - } - - rows[i][col.Name] = v - } - } - - return rows, nil -} - -func calculateNullableCols(cols []lines.Column) (total byte) { - for _, col := range cols { - if col.IsNullable() { - total++ - } - } - - return -} diff --git a/internal/cmd/constants.go b/internal/cmd/constants.go deleted file mode 100644 index 8b8868f..0000000 --- a/internal/cmd/constants.go +++ /dev/null @@ -1,69 +0,0 @@ -package cmd - -import "docker-color-output/internal/lines/fmt" - -const ( - DockerPs Cmd = "docker ps" - DockerImages Cmd = "docker images" - DockerComposePs Cmd = "docker compose ps" -) - -type Cmd string - -func (c Cmd) String() string { - return string(c) -} - -func (c Cmd) Columns() []string { - switch c { - case DockerPs: - return []string{ - "CONTAINER ID", // - "IMAGE", // - "COMMAND", // - "CREATED AT", // opt - "CREATED", // - "PORTS", // nullable - "STATE", // opt - "STATUS", // - "SIZE", // opt - "NAMES", // - "LABELS", // opt - "MOUNTS", // opt | nullable - "NETWORKS", // opt | nullable - } - case DockerImages: - return []string{ - "IMAGE ID", // - "REPOSITORY", // - "TAG", // - "DIGEST", // opt - "CREATED", // - "CREATED AT", // opt - "SIZE", // - } - case DockerComposePs: - return []string{ - "NAME", - "COMMAND", - "SERVICE", - "STATUS", - "PORTS", // nullable - } - default: - return nil - } -} - -func (c Cmd) GetFmt() fmt.Formatable { //nolint:ireturn - switch c { - case DockerPs: - return fmt.NewDockerPsLineFmt() - case DockerImages: - return fmt.NewDockerImagesLineFmt() - case DockerComposePs: - return fmt.NewDockerComposePsLineFmt() - default: - return nil - } -} diff --git a/internal/cmd/contract.go b/internal/cmd/contract.go new file mode 100644 index 0000000..74b1d6f --- /dev/null +++ b/internal/cmd/contract.go @@ -0,0 +1,11 @@ +package cmd + +import ( + "docker-color-output/internal/layout" +) + +const ValidTotalParts = 2 + +type Command interface { + Format(layout.Row, layout.Column) string +} diff --git a/internal/cmd/docker_compose_ps.go b/internal/cmd/docker_compose_ps.go new file mode 100644 index 0000000..2a9acfc --- /dev/null +++ b/internal/cmd/docker_compose_ps.go @@ -0,0 +1,119 @@ +package cmd + +import ( + "strings" + + "docker-color-output/internal/layout" + "docker-color-output/pkg/color" +) + +const ( + DockerComposePsName = "NAME" + DockerComposePsImage = "IMAGE" + DockerComposePsCommand = "COMMAND" + DockerComposePsService = "SERVICE" + DockerComposePsCreated = "CREATED" + DockerComposePsStatus = "STATUS" + DockerComposePsPorts = "PORTS" +) + +type DockerComposePs struct{} + +func (c *DockerComposePs) Columns() []string { + return []string{ + DockerComposePsName, // + DockerComposePsImage, // + DockerComposePsCommand, // + DockerComposePsService, // + DockerComposePsCreated, // + DockerComposePsStatus, // + DockerComposePsPorts, // nullable + } +} + +func (c *DockerComposePs) Format(row layout.Row, col layout.Column) string { + v := string(row[col]) + + switch col { + case DockerComposePsName: + return c.Name(v, row) + case DockerComposePsImage: + return c.Image(v) + case DockerComposePsCommand: + return c.Command(v) + case DockerComposePsService: + return c.Service(v, row) + case DockerComposePsCreated: + return c.Created(v) + case DockerComposePsStatus: + return c.Status(v) + case DockerComposePsPorts: + return c.Ports(v) + default: + return v + } +} + +func (*DockerComposePs) Name(v string, row layout.Row) string { + if strings.Contains(string(row[DockerComposePsStatus]), "exited") { + return color.DarkGray(v) + } + + return color.White(v) +} + +func (*DockerComposePs) Image(v string) string { + parts := strings.Split(v, ":") //nolint:ifshort + if len(parts) == ValidTotalParts { + return color.Yellow(parts[0]) + color.LightGreen(":"+parts[1]) + } + + return color.Yellow(v) +} + +func (*DockerComposePs) Command(v string) string { + return color.DarkGray(v) +} + +func (*DockerComposePs) Service(v string, row layout.Row) string { + if strings.Contains(string(row[DockerComposePsStatus]), "exited") { + return color.DarkGray(v) + } + + return color.Yellow(v) +} + +func (*DockerComposePs) Created(v string) string { + if strings.Contains(v, "months") { + return color.Brown(v) + } + + if strings.Contains(v, "years") { + return color.Red(v) + } + + return color.Green(v) +} + +func (*DockerComposePs) Status(v string) string { + if strings.Contains(v, "exited") { + return color.Red(v) + } + + return color.LightGreen(v) +} + +func (*DockerComposePs) Ports(v string) string { + ports := make([]string, 0) + + for _, port := range strings.Split(v, ", ") { + parts := strings.Split(port, "->") + if len(parts) == ValidTotalParts { + port = color.LightCyan(parts[0]) + "->" + parts[1] + } + + ports = append(ports, port) + } + + return strings.Join(ports, ", ") +} diff --git a/internal/cmd/docker_images.go b/internal/cmd/docker_images.go new file mode 100644 index 0000000..ad50e45 --- /dev/null +++ b/internal/cmd/docker_images.go @@ -0,0 +1,108 @@ +package cmd + +import ( + "strings" + + "docker-color-output/internal/layout" + "docker-color-output/pkg/color" + "docker-color-output/pkg/util/number" +) + +const ( + DockerImagesRepository = "REPOSITORY" + DockerImagesTag = "TAG" + DockerImagesDigest = "DIGEST" + DockerImagesImageID = "IMAGE ID" + DockerImagesCreated = "CREATED" + DockerImagesCreatedAt = "CREATED AT" + DockerImagesSize = "SIZE" +) + +type DockerImages struct{} + +func (c *DockerImages) Columns() []string { + return []string{ + DockerImagesImageID, // + DockerImagesRepository, // + DockerImagesTag, // + DockerImagesDigest, // opt + DockerImagesCreated, // + DockerImagesCreatedAt, // opt + DockerImagesSize, // + } +} + +func (c *DockerImages) Format(row layout.Row, col layout.Column) string { + v := string(row[col]) + + switch col { + case DockerImagesRepository: + return c.Repository(v) + case DockerImagesTag: + return c.Tag(v) + case DockerImagesImageID: + return c.ImageID(v) + case DockerImagesCreated: + return c.Created(v) + case DockerImagesSize: + return c.Size(v) + default: + return v + } +} + +func (*DockerImages) Repository(v string) string { + if strings.Contains(v, "/") { + return color.DarkGray(v) + } + + return color.White(v) +} + +func (*DockerImages) Tag(v string) string { + if v == "latest" { + return color.LightGreen(v) + } + + return v +} + +func (*DockerImages) ImageID(v string) string { + return color.DarkGray(v) +} + +func (*DockerImages) Created(v string) string { + if strings.Contains(v, "hour") { + return color.Green(v) + } + + if strings.Contains(v, "days") { + return color.Green(v) + } + + if strings.Contains(v, "weeks") { + return color.Green(v) + } + + if strings.Contains(v, "months") { + return color.Brown(v) + } + + if strings.Contains(v, "years") { + return color.Red(v) + } + + return v +} + +func (*DockerImages) Size(v string) string { + if strings.Contains(v, "GB") { + return color.Red(v) + } + + if strings.Contains(v, "MB") && number.ParseFloat(v) >= 500 { + return color.Brown(v) + } + + return v +} diff --git a/internal/cmd/docker_ps.go b/internal/cmd/docker_ps.go new file mode 100644 index 0000000..5b1b9bb --- /dev/null +++ b/internal/cmd/docker_ps.go @@ -0,0 +1,123 @@ +package cmd + +import ( + "strings" + + "docker-color-output/internal/layout" + "docker-color-output/pkg/color" +) + +const ( + DockerPsContainerID = "CONTAINER ID" + DockerPsImage = "IMAGE" + DockerPsCommand = "COMMAND" + DockerPsCreatedAt = "CREATED AT" + DockerPsCreated = "CREATED" + DockerPsPorts = "PORTS" + DockerPsState = "STATE" + DockerPsStatus = "STATUS" + DockerPsSize = "SIZE" + DockerPsNames = "NAMES" + DockerPsLabels = "LABELS" + DockerPsMounts = "MOUNTS" + DockerPsNetworks = "NETWORKS" +) + +type DockerPs struct{} + +func (c *DockerPs) Columns() []string { + return []string{ + DockerPsContainerID, // + DockerPsImage, // + DockerPsCommand, // + DockerPsCreatedAt, // opt + DockerPsCreated, // + DockerPsPorts, // nullable + DockerPsState, // opt + DockerPsStatus, // + DockerPsSize, // opt + DockerPsNames, // + DockerPsLabels, // opt + DockerPsMounts, // opt | nullable + DockerPsNetworks, // opt | nullable + } +} + +func (c *DockerPs) Format(rows layout.Row, col layout.Column) string { + v := string(rows[col]) + + switch col { + case DockerPsContainerID: + return c.ContainerID(v) + case DockerPsImage: + return c.Image(v) + case DockerPsCommand: + return c.Command(v) + case DockerPsCreated: + return c.Created(v) + case DockerPsStatus: + return c.Status(v) + case DockerPsPorts: + return c.Ports(v) + case DockerPsNames: + return c.Names(v) + default: + return v + } +} + +func (*DockerPs) ContainerID(v string) string { + return color.DarkGray(v) +} + +func (*DockerPs) Image(v string) string { + parts := strings.Split(v, ":") //nolint:ifshort + if len(parts) == ValidTotalParts { + return color.Yellow(parts[0]) + color.LightGreen(":"+parts[1]) + } + + return color.Yellow(v) +} + +func (*DockerPs) Command(v string) string { + return color.DarkGray(v) +} + +func (*DockerPs) Created(v string) string { + if strings.Contains(v, "months") { + return color.Brown(v) + } + + if strings.Contains(v, "years") { + return color.Red(v) + } + + return color.Green(v) +} + +func (*DockerPs) Status(v string) string { + if strings.Contains(v, "Exited") { + return color.Red(v) + } + + return color.LightGreen(v) +} + +func (*DockerPs) Ports(v string) string { + ports := make([]string, 0) + + for _, port := range strings.Split(v, ", ") { + parts := strings.Split(port, "->") + if len(parts) == ValidTotalParts { + port = color.LightCyan(parts[0]) + "->" + parts[1] + } + + ports = append(ports, port) + } + + return strings.Join(ports, ", ") +} + +func (*DockerPs) Names(v string) string { + return color.White(v) +} diff --git a/internal/cmd/parse.go b/internal/cmd/parse.go new file mode 100644 index 0000000..0e4e605 --- /dev/null +++ b/internal/cmd/parse.go @@ -0,0 +1,34 @@ +package cmd + +import ( + "errors" + + "docker-color-output/internal/layout" + "docker-color-output/internal/util" +) + +var ErrInvalidFirstLine = errors.New("invalid first line") + +func Parse(header layout.Header) (Command, error) { //nolint:ireturn + columns := make([]string, len(header)) + for i, col := range header { + columns[i] = string(col.Name) + } + + ps := &DockerPs{} + if util.Intersect(columns, ps.Columns()) { + return ps, nil + } + + images := &DockerImages{} + if util.Intersect(columns, images.Columns()) { + return images, nil + } + + composePs := &DockerComposePs{} + if util.Intersect(columns, composePs.Columns()) { + return composePs, nil + } + + return nil, ErrInvalidFirstLine +} diff --git a/internal/layout/column.go b/internal/layout/column.go new file mode 100644 index 0000000..0efc4fd --- /dev/null +++ b/internal/layout/column.go @@ -0,0 +1,3 @@ +package layout + +type Column string diff --git a/internal/layout/header.go b/internal/layout/header.go new file mode 100644 index 0000000..95158b2 --- /dev/null +++ b/internal/layout/header.go @@ -0,0 +1,42 @@ +package layout + +//nolint:gochecknoglobals +var nullableCols = map[Column]struct{}{ + "PORTS": {}, + "MOUNTS": {}, + "NETWORKS": {}, +} + +type Cell struct { + Name Column + MaxLength int +} + +func (c *Cell) IsNullable() bool { + _, ok := nullableCols[c.Name] + + return ok +} + +type Header []*Cell + +func (h Header) ToRow() Row { + res := make(Row, len(h)) + for _, col := range h { + res[col.Name] = Value(col.Name) + } + + return res +} + +func (h Header) NullableCols() byte { + var res byte + + for _, col := range h { + if col.IsNullable() { + res++ + } + } + + return res +} diff --git a/internal/layout/parse.go b/internal/layout/parse.go new file mode 100644 index 0000000..dd03b9b --- /dev/null +++ b/internal/layout/parse.go @@ -0,0 +1,55 @@ +package layout + +import ( + "strings" + + "docker-color-output/internal/util" +) + +func ParseHeader(rows []string) Header { + parts := util.Split(rows[0]) + + res := make(Header, len(parts)) + for i, part := range parts { + res[i] = &Cell{ + Name: Column(part), + MaxLength: len(part), + } + } + + return res +} + +func ParseRows(rows []string, header *Header) []Row { + res := make([]Row, len(rows)-1) + + for i, row := range rows[1:] { + offset := 0 + parts := util.Split(row) + res[i] = make(Row, len(*header)) + mismatch := len(parts) < len(*header) + + for j, col := range *header { + if mismatch && col.IsNullable() { + offset++ + + continue + } + + v := parts[j-offset] + + length := len(v) + if strings.Contains(v, "ā€¦") { + length -= 2 + } + + if length > col.MaxLength { + col.MaxLength = length + } + + res[i][col.Name] = Value(v) + } + } + + return res +} diff --git a/internal/layout/row.go b/internal/layout/row.go new file mode 100644 index 0000000..07c697a --- /dev/null +++ b/internal/layout/row.go @@ -0,0 +1,5 @@ +package layout + +type Value string + +type Row map[Column]Value diff --git a/internal/lines/contracts.go b/internal/lines/contracts.go deleted file mode 100644 index 218c99a..0000000 --- a/internal/lines/contracts.go +++ /dev/null @@ -1,16 +0,0 @@ -package lines - -type Buildable interface { - Build() string -} - -type Values map[string]string - -type Column struct { - Name string - Len *int -} - -func (c *Column) IsNullable() bool { - return c.Name == "PORTS" || c.Name == "MOUNTS" || c.Name == "NETWORKS" -} diff --git a/internal/lines/fmt/contracts.go b/internal/lines/fmt/contracts.go deleted file mode 100644 index 293691a..0000000 --- a/internal/lines/fmt/contracts.go +++ /dev/null @@ -1,5 +0,0 @@ -package fmt - -type Formatable interface { - Format(vals map[string]string, col string) string -} diff --git a/internal/lines/fmt/docker_compose_ps_line_fmt.go b/internal/lines/fmt/docker_compose_ps_line_fmt.go deleted file mode 100644 index c5d19ce..0000000 --- a/internal/lines/fmt/docker_compose_ps_line_fmt.go +++ /dev/null @@ -1,75 +0,0 @@ -package fmt - -import ( - "strings" - - "docker-color-output/pkg/color" -) - -type DockerComposePsLineFmt struct{} - -func NewDockerComposePsLineFmt() *DockerComposePsLineFmt { - return &DockerComposePsLineFmt{} -} - -func (*DockerComposePsLineFmt) Name(v, status string) string { - if strings.Contains(status, "exited") { - return color.DarkGray(v) - } - - return color.White(v) -} - -func (*DockerComposePsLineFmt) Command(v string) string { - return color.DarkGray(v) -} - -func (*DockerComposePsLineFmt) Service(v, status string) string { - if strings.Contains(status, "exited") { - return color.DarkGray(v) - } - - return color.Yellow(v) -} - -func (*DockerComposePsLineFmt) Status(v string) string { - if strings.Contains(v, "exited") { - return color.Red(v) - } - - return color.LightGreen(v) -} - -func (*DockerComposePsLineFmt) Ports(v string) string { - ports := make([]string, 0) - - for _, port := range strings.Split(v, ", ") { - parts := strings.Split(port, "->") - if len(parts) == 2 { - port = color.LightCyan(parts[0]) + "->" + parts[1] - } - - ports = append(ports, port) - } - - return strings.Join(ports, ", ") -} - -func (f *DockerComposePsLineFmt) Format(vals map[string]string, col string) string { - v := vals[col] - - switch col { - case "NAME": - return f.Name(v, vals["STATUS"]) - case "COMMAND": - return f.Command(v) - case "SERVICE": - return f.Service(v, vals["STATUS"]) - case "STATUS": - return f.Status(v) - case "PORTS": - return f.Ports(v) - default: - return v - } -} diff --git a/internal/lines/fmt/docker_images_line_fmt.go b/internal/lines/fmt/docker_images_line_fmt.go deleted file mode 100644 index 960bc1c..0000000 --- a/internal/lines/fmt/docker_images_line_fmt.go +++ /dev/null @@ -1,89 +0,0 @@ -package fmt - -import ( - "strings" - - "docker-color-output/pkg/color" - "docker-color-output/pkg/utils/numbers" -) - -type DockerImagesLineFmt struct{} - -func NewDockerImagesLineFmt() *DockerImagesLineFmt { - return &DockerImagesLineFmt{} -} - -func (*DockerImagesLineFmt) Repository(v string) string { - if strings.Contains(v, "/") { - return color.DarkGray(v) - } - - return color.White(v) -} - -func (*DockerImagesLineFmt) Tag(v string) string { - if v == "latest" { - return color.LightGreen(v) - } - - return v -} - -func (*DockerImagesLineFmt) ImageID(v string) string { - return color.DarkGray(v) -} - -func (*DockerImagesLineFmt) Created(v string) string { - if strings.Contains(v, "hour") { - return color.Green(v) - } - - if strings.Contains(v, "days") { - return color.Green(v) - } - - if strings.Contains(v, "weeks") { - return color.Green(v) - } - - if strings.Contains(v, "months") { - return color.Brown(v) - } - - if strings.Contains(v, "years") { - return color.Red(v) - } - - return v -} - -func (*DockerImagesLineFmt) Size(v string) string { - if strings.Contains(v, "GB") { - return color.Red(v) - } - - if strings.Contains(v, "MB") && numbers.ParseFloat(v) >= 500 { - return color.Brown(v) - } - - return v -} - -func (f *DockerImagesLineFmt) Format(vals map[string]string, col string) string { - v := vals[col] - - switch col { - case "REPOSITORY": - return f.Repository(v) - case "TAG": - return f.Tag(v) - case "IMAGE ID": - return f.ImageID(v) - case "CREATED": - return f.Created(v) - case "SIZE": - return f.Size(v) - default: - return v - } -} diff --git a/internal/lines/fmt/docker_ps_line_fmt.go b/internal/lines/fmt/docker_ps_line_fmt.go deleted file mode 100644 index 44ebd2f..0000000 --- a/internal/lines/fmt/docker_ps_line_fmt.go +++ /dev/null @@ -1,92 +0,0 @@ -package fmt - -import ( - "strings" - - "docker-color-output/pkg/color" -) - -type DockerPsLineFmt struct{} - -func NewDockerPsLineFmt() *DockerPsLineFmt { - return &DockerPsLineFmt{} -} - -func (*DockerPsLineFmt) ContainerID(v string) string { - return color.DarkGray(v) -} - -func (*DockerPsLineFmt) Image(v string) string { - parts := strings.Split(v, ":") - if len(parts) == 2 { - return color.Yellow(parts[0]) + color.LightGreen(":"+parts[1]) - } - - return color.Yellow(v) -} - -func (*DockerPsLineFmt) Command(v string) string { - return color.DarkGray(v) -} - -func (*DockerPsLineFmt) Created(v string) string { - if strings.Contains(v, "months") { - return color.Brown(v) - } - - if strings.Contains(v, "years") { - return color.Red(v) - } - - return color.Green(v) -} - -func (*DockerPsLineFmt) Status(v string) string { - if strings.Contains(v, "Exited") { - return color.Red(v) - } - - return color.LightGreen(v) -} - -func (*DockerPsLineFmt) Ports(v string) string { - ports := make([]string, 0) - - for _, port := range strings.Split(v, ", ") { - parts := strings.Split(port, "->") - if len(parts) == 2 { - port = color.LightCyan(parts[0]) + "->" + parts[1] - } - - ports = append(ports, port) - } - - return strings.Join(ports, ", ") -} - -func (*DockerPsLineFmt) Names(v string) string { - return color.White(v) -} - -func (f *DockerPsLineFmt) Format(vals map[string]string, col string) string { - v := vals[col] - - switch col { - case "CONTAINER ID": - return f.ContainerID(v) - case "IMAGE": - return f.Image(v) - case "COMMAND": - return f.Command(v) - case "CREATED": - return f.Created(v) - case "STATUS": - return f.Status(v) - case "PORTS": - return f.Ports(v) - case "NAMES": - return f.Names(v) - default: - return v - } -} diff --git a/internal/lines/fmt/first_line_fmt.go b/internal/lines/fmt/first_line_fmt.go deleted file mode 100644 index ebd451b..0000000 --- a/internal/lines/fmt/first_line_fmt.go +++ /dev/null @@ -1,13 +0,0 @@ -package fmt - -import "docker-color-output/pkg/color" - -type FirstLineFmt struct{} - -func NewFirstLineFmt() *FirstLineFmt { - return &FirstLineFmt{} -} - -func (f *FirstLineFmt) Format(_ map[string]string, col string) string { - return color.LightBlue(col) -} diff --git a/internal/lines/line.go b/internal/lines/line.go deleted file mode 100644 index 4b20f1e..0000000 --- a/internal/lines/line.go +++ /dev/null @@ -1,31 +0,0 @@ -package lines - -import ( - "strings" - - "docker-color-output/internal/lines/fmt" - "docker-color-output/internal/utils" -) - -type Line struct { - vals Values - cols []Column - fmt fmt.Formatable -} - -func NewLine(vals Values, cols []Column, fmt fmt.Formatable) *Line { - return &Line{ - vals: vals, - cols: cols, - fmt: fmt, - } -} - -func (l *Line) Build() string { - var sb strings.Builder - for _, col := range l.cols { - sb.WriteString(utils.Pad(l.fmt.Format(l.vals, col.Name), *col.Len)) - } - - return sb.String() -} diff --git a/internal/stdin/stdin.go b/internal/stdin/stdin.go index 67428cf..343944c 100644 --- a/internal/stdin/stdin.go +++ b/internal/stdin/stdin.go @@ -6,26 +6,28 @@ import ( "os" ) +var ErrNoStdin = errors.New("no stdin") + func Get() ([]string, error) { fi, err := os.Stdin.Stat() if err != nil { - return nil, err + return nil, err //nolint:wrapcheck } if fi.Mode()&os.ModeNamedPipe == 0 && fi.Size() <= 0 { - return nil, errors.New("no stdin") + return nil, ErrNoStdin } - var out []string + var res []string s := bufio.NewScanner(os.Stdin) for s.Scan() { - out = append(out, s.Text()) + res = append(res, s.Text()) } if err = s.Err(); err != nil { - return nil, err + return nil, err //nolint:wrapcheck } - return out, nil + return res, nil } diff --git a/internal/stdout/stdout.go b/internal/stdout/stdout.go index 2f9acf1..9fcadeb 100644 --- a/internal/stdout/stdout.go +++ b/internal/stdout/stdout.go @@ -2,10 +2,8 @@ package stdout import ( "fmt" - - "docker-color-output/internal/lines" ) -func Println(v lines.Buildable) { - fmt.Println(v.Build()) +func Println(in string) { + fmt.Println(in) //nolint:forbidigo } diff --git a/internal/utils/utils.go b/internal/util/util.go similarity index 85% rename from internal/utils/utils.go rename to internal/util/util.go index 47d756c..9ad36de 100644 --- a/internal/utils/utils.go +++ b/internal/util/util.go @@ -1,4 +1,4 @@ -package utils +package util import ( "fmt" @@ -12,8 +12,8 @@ const ( NonPrintableCharactersLength = 11 ) -func Split(v string) []string { - return regexp.MustCompile(`\s{2,}`).Split(v, -1) +func Split(in string) []string { + return regexp.MustCompile(`\s{2,}`).Split(in, -1) } func Pad(value string, length int) string { diff --git a/internal/utils/utils_test.go b/internal/util/util_test.go similarity index 76% rename from internal/utils/utils_test.go rename to internal/util/util_test.go index 14db0a1..78367cd 100644 --- a/internal/utils/utils_test.go +++ b/internal/util/util_test.go @@ -1,13 +1,16 @@ -package utils +package util_test import ( "testing" + "docker-color-output/internal/util" "docker-color-output/pkg/color" - "docker-color-output/pkg/utils/assert" + "docker-color-output/pkg/util/assert" ) func TestSplit(t *testing.T) { + t.Parallel() + tests := map[string]struct { in string want []string @@ -19,13 +22,18 @@ func TestSplit(t *testing.T) { } for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { - assert.Equal(t, tt.want, Split(tt.in)) + t.Parallel() + assert.Equal(t, tt.want, util.Split(tt.in)) }) } } func TestPad(t *testing.T) { + t.Parallel() + tests := map[string]struct { value string length int @@ -44,13 +52,18 @@ func TestPad(t *testing.T) { } for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { - assert.Equal(t, tt.want, Pad(tt.value, tt.length)) + t.Parallel() + assert.Equal(t, tt.want, util.Pad(tt.value, tt.length)) }) } } func TestIntersect(t *testing.T) { + t.Parallel() + tests := map[string]struct { needle []string haystack []string @@ -63,8 +76,11 @@ func TestIntersect(t *testing.T) { } for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { - assert.Equal(t, tt.want, Intersect(tt.needle, tt.haystack)) + t.Parallel() + assert.Equal(t, tt.want, util.Intersect(tt.needle, tt.haystack)) }) } } diff --git a/internal/utils/pointer/pointer.go b/internal/utils/pointer/pointer.go deleted file mode 100644 index 7806116..0000000 --- a/internal/utils/pointer/pointer.go +++ /dev/null @@ -1,5 +0,0 @@ -package pointer - -func ToInt(i int) *int { - return &i -} diff --git a/internal/utils/pointer/pointer_test.go b/internal/utils/pointer/pointer_test.go deleted file mode 100644 index 940b103..0000000 --- a/internal/utils/pointer/pointer_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package pointer - -import ( - "testing" - - "docker-color-output/pkg/utils/assert" -) - -func TestToInt(t *testing.T) { - v := 0 - - assert.Equal(t, &v, ToInt(v)) -} diff --git a/pkg/color/color_test.go b/pkg/color/color_test.go index 998f12c..626f9a5 100644 --- a/pkg/color/color_test.go +++ b/pkg/color/color_test.go @@ -1,76 +1,95 @@ -package color +package color_test import ( "testing" - "docker-color-output/pkg/utils/assert" + "docker-color-output/pkg/color" + "docker-color-output/pkg/util/assert" ) //nolint:funlen func TestColors(t *testing.T) { + t.Parallel() + msg := "Message" t.Run("Black", func(t *testing.T) { - assert.Equal(t, "\u001B[0;30m"+msg+"\u001B[0m", Black(msg)) + t.Parallel() + assert.Equal(t, "\u001B[0;30m"+msg+"\u001B[0m", color.Black(msg)) }) t.Run("DarkGray", func(t *testing.T) { - assert.Equal(t, "\u001B[1;30m"+msg+"\u001B[0m", DarkGray(msg)) + t.Parallel() + assert.Equal(t, "\u001B[1;30m"+msg+"\u001B[0m", color.DarkGray(msg)) }) t.Run("Red", func(t *testing.T) { - assert.Equal(t, "\u001B[0;31m"+msg+"\u001B[0m", Red(msg)) + t.Parallel() + assert.Equal(t, "\u001B[0;31m"+msg+"\u001B[0m", color.Red(msg)) }) t.Run("LightRed", func(t *testing.T) { - assert.Equal(t, "\u001B[1;31m"+msg+"\u001B[0m", LightRed(msg)) + t.Parallel() + assert.Equal(t, "\u001B[1;31m"+msg+"\u001B[0m", color.LightRed(msg)) }) t.Run("Green", func(t *testing.T) { - assert.Equal(t, "\u001B[0;32m"+msg+"\u001B[0m", Green(msg)) + t.Parallel() + assert.Equal(t, "\u001B[0;32m"+msg+"\u001B[0m", color.Green(msg)) }) t.Run("LightGreen", func(t *testing.T) { - assert.Equal(t, "\u001B[1;32m"+msg+"\u001B[0m", LightGreen(msg)) + t.Parallel() + assert.Equal(t, "\u001B[1;32m"+msg+"\u001B[0m", color.LightGreen(msg)) }) t.Run("Brown", func(t *testing.T) { - assert.Equal(t, "\u001B[0;33m"+msg+"\u001B[0m", Brown(msg)) + t.Parallel() + assert.Equal(t, "\u001B[0;33m"+msg+"\u001B[0m", color.Brown(msg)) }) t.Run("Yellow", func(t *testing.T) { - assert.Equal(t, "\u001B[1;33m"+msg+"\u001B[0m", Yellow(msg)) + t.Parallel() + assert.Equal(t, "\u001B[1;33m"+msg+"\u001B[0m", color.Yellow(msg)) }) t.Run("Blue", func(t *testing.T) { - assert.Equal(t, "\u001B[0;34m"+msg+"\u001B[0m", Blue(msg)) + t.Parallel() + assert.Equal(t, "\u001B[0;34m"+msg+"\u001B[0m", color.Blue(msg)) }) t.Run("LightBlue", func(t *testing.T) { - assert.Equal(t, "\u001B[1;34m"+msg+"\u001B[0m", LightBlue(msg)) + t.Parallel() + assert.Equal(t, "\u001B[1;34m"+msg+"\u001B[0m", color.LightBlue(msg)) }) t.Run("Purple", func(t *testing.T) { - assert.Equal(t, "\u001B[0;35m"+msg+"\u001B[0m", Purple(msg)) + t.Parallel() + assert.Equal(t, "\u001B[0;35m"+msg+"\u001B[0m", color.Purple(msg)) }) t.Run("LightPurple", func(t *testing.T) { - assert.Equal(t, "\u001B[1;35m"+msg+"\u001B[0m", LightPurple(msg)) + t.Parallel() + assert.Equal(t, "\u001B[1;35m"+msg+"\u001B[0m", color.LightPurple(msg)) }) t.Run("Cyan", func(t *testing.T) { - assert.Equal(t, "\u001B[0;36m"+msg+"\u001B[0m", Cyan(msg)) + t.Parallel() + assert.Equal(t, "\u001B[0;36m"+msg+"\u001B[0m", color.Cyan(msg)) }) t.Run("LightCyan", func(t *testing.T) { - assert.Equal(t, "\u001B[1;36m"+msg+"\u001B[0m", LightCyan(msg)) + t.Parallel() + assert.Equal(t, "\u001B[1;36m"+msg+"\u001B[0m", color.LightCyan(msg)) }) t.Run("LightGray", func(t *testing.T) { - assert.Equal(t, "\u001B[0;37m"+msg+"\u001B[0m", LightGray(msg)) + t.Parallel() + assert.Equal(t, "\u001B[0;37m"+msg+"\u001B[0m", color.LightGray(msg)) }) t.Run("White", func(t *testing.T) { - assert.Equal(t, "\u001B[1;37m"+msg+"\u001B[0m", White(msg)) + t.Parallel() + assert.Equal(t, "\u001B[1;37m"+msg+"\u001B[0m", color.White(msg)) }) } diff --git a/pkg/utils/assert/assertions.go b/pkg/util/assert/assertions.go similarity index 94% rename from pkg/utils/assert/assertions.go rename to pkg/util/assert/assertions.go index bde83a0..56294db 100644 --- a/pkg/utils/assert/assertions.go +++ b/pkg/util/assert/assertions.go @@ -6,6 +6,8 @@ import ( ) func Equal(t *testing.T, expected, actual interface{}) { + t.Helper() + if !reflect.DeepEqual(expected, actual) { t.Errorf("Not equal:\nexpected: %s\nactual : %s", expected, actual) } diff --git a/pkg/utils/numbers/numbers.go b/pkg/util/number/number.go similarity index 93% rename from pkg/utils/numbers/numbers.go rename to pkg/util/number/number.go index 6e784ba..0967f61 100644 --- a/pkg/utils/numbers/numbers.go +++ b/pkg/util/number/number.go @@ -1,4 +1,4 @@ -package numbers +package number import ( "strconv" diff --git a/pkg/utils/numbers/numbers_test.go b/pkg/util/number/number_test.go similarity index 69% rename from pkg/utils/numbers/numbers_test.go rename to pkg/util/number/number_test.go index 17ffc80..7982047 100644 --- a/pkg/utils/numbers/numbers_test.go +++ b/pkg/util/number/number_test.go @@ -1,12 +1,15 @@ -package numbers +package number_test import ( "testing" - "docker-color-output/pkg/utils/assert" + "docker-color-output/pkg/util/assert" + "docker-color-output/pkg/util/number" ) func TestParseFloat(t *testing.T) { + t.Parallel() + tests := map[string]struct { in string want float64 @@ -20,8 +23,11 @@ func TestParseFloat(t *testing.T) { } for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { - assert.Equal(t, tt.want, ParseFloat(tt.in)) + t.Parallel() + assert.Equal(t, tt.want, number.ParseFloat(tt.in)) }) } } diff --git a/test/data/in/docker_compose_ps_1.out b/test/data/in/docker_compose_ps_1.out new file mode 100644 index 0000000..e5ae266 --- /dev/null +++ b/test/data/in/docker_compose_ps_1.out @@ -0,0 +1,4 @@ +NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS +m-grafana grafana/grafana-enterprise:latest "/run.sh" grafana About a minute ago Up About a minute 127.0.0.1:3001->3000/tcp +m-node-exporter prom/node-exporter:latest "/bin/node_exporter ā€¦" node-exporter About a minute ago Up About a minute 9100/tcp +m-prometheus prom/prometheus:latest "/bin/prometheus --cā€¦" prometheus About a minute ago Up About a minute 9090/tcp diff --git a/test/data/in/invalid_cols.out b/test/data/in/invalid_cols.out index 8e17584..ee11e05 100644 --- a/test/data/in/invalid_cols.out +++ b/test/data/in/invalid_cols.out @@ -1,3 +1,3 @@ -NAME CREATED +NAME NULL dldash.home About an hour ago redis About an hour ago diff --git a/test/data/out/docker_compose_ps_1.out b/test/data/out/docker_compose_ps_1.out new file mode 100644 index 0000000..4bcfc5e --- /dev/null +++ b/test/data/out/docker_compose_ps_1.out @@ -0,0 +1,4 @@ +NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS +m-grafana grafana/grafana-enterprise:latest "/run.sh" grafana About a minute ago Up About a minute 127.0.0.1:3001->3000/tcp +m-node-exporter prom/node-exporter:latest "/bin/node_exporter ā€¦" node-exporter About a minute ago Up About a minute 9100/tcp +m-prometheus prom/prometheus:latest "/bin/prometheus --cā€¦" prometheus About a minute ago Up About a minute 9090/tcp