From 6d3043a320d4416c73c4b9d3ca23097965f2572a Mon Sep 17 00:00:00 2001 From: Gabe Cook Date: Thu, 1 Aug 2024 03:41:26 -0500 Subject: [PATCH] feat: Initial commit --- .github/changelog-generator.yaml | 14 +++++ .github/renovate.json | 7 +++ .github/workflows/build.yaml | 51 +++++++++++++++++ .gitignore | 2 + .golangci.yaml | 95 ++++++++++++++++++++++++++++++++ coloryaml.go | 53 ++++++++++++++++++ coloryaml_test.go | 32 +++++++++++ go.mod | 19 +++++++ go.sum | 25 +++++++++ printer.go | 68 +++++++++++++++++++++++ printer_test.go | 28 ++++++++++ 11 files changed, 394 insertions(+) create mode 100644 .github/changelog-generator.yaml create mode 100644 .github/renovate.json create mode 100644 .github/workflows/build.yaml create mode 100644 .gitignore create mode 100644 .golangci.yaml create mode 100644 coloryaml.go create mode 100644 coloryaml_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 printer.go create mode 100644 printer_test.go diff --git a/.github/changelog-generator.yaml b/.github/changelog-generator.yaml new file mode 100644 index 0000000..54674fc --- /dev/null +++ b/.github/changelog-generator.yaml @@ -0,0 +1,14 @@ +# yaml-language-server: $schema=https://gabe565.github.io/changelog-generator/config.schema.json +filters: + exclude: + - "^docs" + - "^test" +groups: + - title: Features + order: 0 + regexp: "^(feat)" + - title: Fixes + order: 1 + regexp: "^(fix|perf)" + - title: Others + order: 999 diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..ed0c43f --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "local>gabe565/renovate-config", + "local>gabe565/renovate-config:golang" + ] +} diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..c5e6c56 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,51 @@ +name: Build + +on: push + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: false + - name: Lint + uses: golangci/golangci-lint-action@v6 + + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - name: Test + run: go test ./... + + release: + name: Release + if: startsWith(github.ref, 'refs/tags/') + needs: [lint, test] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Generate Changelog + id: changelog + uses: gabe565/changelog-generator@v1 + - name: Release + uses: softprops/action-gh-release@v2 + with: + body: ${{ steps.changelog.outputs.changelog }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af56f61 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +.idea/ diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..2576f72 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,95 @@ +run: + timeout: 5m + +issues: + max-same-issues: 50 + exclude-dirs-use-default: false + exclude-rules: + - path: "examples" + linters: + - forbidigo + +linters-settings: + gosec: + excludes: + - G306 + - G404 + +linters: + enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - copyloopvar + - decorder + - dupl + - durationcheck + - errcheck + - errname + - errorlint + - exportloopref + - forbidigo + - gci + - ginkgolinter + - gocheckcompilerdirectives + - gochecknoglobals + - gochecknoinits + - gochecksumtype + - goconst + - gocritic + - gocyclo + - godox + - err113 + - gofumpt + - goheader + - goimports + - gomoddirectives + - gomodguard + - goprintffuncname + - gosec + - gosimple + - gosmopolitan + - govet + - importas + - inamedparam + - ineffassign + - interfacebloat + - intrange + - ireturn + - loggercheck + - makezero + - mirror + - musttag + - nakedret + - nilerr + - nilnil + - noctx + - nolintlint + - nonamedreturns + - nosprintfhostport + - paralleltest + - perfsprint + - prealloc + - predeclared + - promlinter + - protogetter + - reassign + - revive + - rowserrcheck + - sloglint + - spancheck + - sqlclosecheck + - staticcheck + - stylecheck + - tenv + - testableexamples + - testifylint + - typecheck + - unconvert + - unparam + - unused + - usestdlibvars + - wastedassign + - whitespace + - zerologlint diff --git a/coloryaml.go b/coloryaml.go new file mode 100644 index 0000000..3e65284 --- /dev/null +++ b/coloryaml.go @@ -0,0 +1,53 @@ +package coloryaml + +import ( + "bytes" + "io" + "os" + "strings" + + "github.com/goccy/go-yaml/lexer" + "gopkg.in/yaml.v3" +) + +func Colorize(s string) string { + // https://github.com/mikefarah/yq/blob/v4.43.1/pkg/yqlib/color_print.go + tokens := lexer.Tokenize(s) + return Printer().PrintTokens(tokens) +} + +func Sprintln(obj any) (string, error) { + b, err := yaml.Marshal(obj) + if err != nil { + return "", err + } + s := Colorize(string(b)) + if !strings.HasSuffix(s, "\n") { + s += "\n" + } + return s, nil +} + +func Fprintln(w io.Writer, obj any) error { + if !shouldColor(w) { + b, err := yaml.Marshal(obj) + if err != nil { + return err + } + if !bytes.HasSuffix(b, []byte{'\n'}) { + b = append(b, '\n') + } + _, err = w.Write(b) + return err + } + s, err := Sprintln(obj) + if err != nil { + return err + } + _, err = io.WriteString(w, s) + return err +} + +func Println(obj any) error { + return Fprintln(os.Stdout, obj) +} diff --git a/coloryaml_test.go b/coloryaml_test.go new file mode 100644 index 0000000..07ed669 --- /dev/null +++ b/coloryaml_test.go @@ -0,0 +1,32 @@ +package coloryaml + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestColorize(t *testing.T) { + t.Parallel() + type args struct { + s string + } + tests := []struct { + name string + args args + want string + }{ + {"map", args{"a: b"}, "\x1b[36ma\x1b[0m:\x1b[32m b\x1b[0m"}, + {"anchor", args{"&a"}, "\x1b[93m&\x1b[0m\x1b[93ma\x1b[0m"}, + {"alias", args{"*a"}, "\x1b[93m*\x1b[0m\x1b[93ma\x1b[0m"}, + {"bool", args{"true"}, "\x1b[95mtrue\x1b[0m"}, + {"string", args{"test"}, "\x1b[32mtest\x1b[0m"}, + {"number", args{"123"}, "\x1b[95m123\x1b[0m"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equalf(t, tt.want, Colorize(tt.args.s), "Colorize(%v)", tt.args.s) + }) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..817f762 --- /dev/null +++ b/go.mod @@ -0,0 +1,19 @@ +module github.com/gabe565/coloryaml + +go 1.22.5 + +require ( + github.com/fatih/color v1.17.0 + github.com/goccy/go-yaml v1.12.0 + github.com/mattn/go-isatty v0.0.20 + github.com/stretchr/testify v1.9.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..eb0d4a1 --- /dev/null +++ b/go.sum @@ -0,0 +1,25 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= +github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/printer.go b/printer.go new file mode 100644 index 0000000..812bdff --- /dev/null +++ b/printer.go @@ -0,0 +1,68 @@ +package coloryaml + +import ( + "io" + "os" + "strconv" + + "github.com/fatih/color" + "github.com/goccy/go-yaml/printer" + "github.com/mattn/go-isatty" +) + +func shouldColor(w io.Writer) bool { + if os.Getenv("NO_COLOR") != "" || os.Getenv("TERM") == "dumb" { + return false + } + if f, ok := w.(*os.File); ok { + return isatty.IsTerminal(f.Fd()) || isatty.IsCygwinTerminal(f.Fd()) + } + return false +} + +const escape = "\x1b" + +func format(attr color.Attribute) string { + return escape + "[" + strconv.Itoa(int(attr)) + "m" +} + +func Printer() *printer.Printer { + return &printer.Printer{ + MapKey: func() *printer.Property { + return &printer.Property{ + Prefix: format(color.FgCyan), + Suffix: format(color.Reset), + } + }, + Anchor: func() *printer.Property { + return &printer.Property{ + Prefix: format(color.FgHiYellow), + Suffix: format(color.Reset), + } + }, + Alias: func() *printer.Property { + return &printer.Property{ + Prefix: format(color.FgHiYellow), + Suffix: format(color.Reset), + } + }, + Bool: func() *printer.Property { + return &printer.Property{ + Prefix: format(color.FgHiMagenta), + Suffix: format(color.Reset), + } + }, + String: func() *printer.Property { + return &printer.Property{ + Prefix: format(color.FgGreen), + Suffix: format(color.Reset), + } + }, + Number: func() *printer.Property { + return &printer.Property{ + Prefix: format(color.FgHiMagenta), + Suffix: format(color.Reset), + } + }, + } +} diff --git a/printer_test.go b/printer_test.go new file mode 100644 index 0000000..87a2ae8 --- /dev/null +++ b/printer_test.go @@ -0,0 +1,28 @@ +package coloryaml + +import ( + "testing" + + "github.com/fatih/color" + "github.com/stretchr/testify/assert" +) + +func Test_format(t *testing.T) { + t.Parallel() + type args struct { + attr color.Attribute + } + tests := []struct { + name string + args args + want string + }{ + {"reset", args{color.Reset}, "\x1b[0m"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equalf(t, tt.want, format(tt.args.attr), "format(%v)", tt.args.attr) + }) + } +}