├── .gitignore ├── internal ├── styles │ ├── noop.xml │ └── styles.go ├── prettier │ ├── prettier.go │ ├── fallback.go │ ├── registry.go │ ├── html.go │ ├── go.go │ ├── json.go │ ├── css.go │ ├── fallback_test.go │ ├── yaml.go │ ├── yaml_test.go │ ├── css_test.go │ ├── xml_test.go │ ├── html_test.go │ ├── go_test.go │ ├── json_test.go │ └── xml.go ├── formatters │ ├── mime.go │ ├── formatters.go │ ├── svg_minified.go │ ├── json_minified.go │ └── html_minified.go ├── gat │ ├── formats.go │ ├── languages_test.go │ ├── formats_test.go │ ├── languages.go │ └── gat.go ├── lexers │ └── lexers.go └── masker │ ├── masker.go │ └── masker_test.go ├── docs ├── demo.gif ├── gess.gif ├── image.png ├── markdown.png ├── update.go ├── themes.md └── languages.md ├── tapes ├── main.go ├── demo.tape └── gess.tape ├── main.go ├── mise.toml ├── cmd ├── version.go ├── root.go └── flags.go ├── scripts └── build-completions.sh ├── renovate.json ├── .github ├── actions │ └── setup │ │ └── action.yml └── workflows │ ├── update-demo.yml │ ├── claude-renovate-review.yml │ ├── update-docs.yml │ ├── ci.yml │ ├── github-actions-lint.yml │ └── release-please.yml ├── LICENSE ├── .goreleaser.yaml ├── go.mod ├── CLAUDE.md ├── README.md ├── assets ├── logo_dark.svg └── logo_light.svg ├── CHANGELOG.md └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | /gat 2 | /dist/ 3 | /completions/ 4 | -------------------------------------------------------------------------------- /internal/styles/noop.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /docs/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koki-develop/gat/HEAD/docs/demo.gif -------------------------------------------------------------------------------- /docs/gess.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koki-develop/gat/HEAD/docs/gess.gif -------------------------------------------------------------------------------- /docs/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koki-develop/gat/HEAD/docs/image.png -------------------------------------------------------------------------------- /docs/markdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koki-develop/gat/HEAD/docs/markdown.png -------------------------------------------------------------------------------- /tapes/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("hello world") 7 | } 8 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/koki-develop/gat/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /internal/prettier/prettier.go: -------------------------------------------------------------------------------- 1 | package prettier 2 | 3 | type Prettier interface { 4 | Pretty(content string) (string, error) 5 | } 6 | -------------------------------------------------------------------------------- /internal/formatters/mime.go: -------------------------------------------------------------------------------- 1 | package formatters 2 | 3 | var ( 4 | mimeTypeHTML = "text/html" 5 | mimeTypeCSS = "text/css" 6 | mimeTypeJSON = "application/json" 7 | mimeTypeSVG = "image/svg+xml" 8 | ) 9 | -------------------------------------------------------------------------------- /mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | go = "1.25.5" 3 | 4 | "go:github.com/golangci/golangci-lint/v2/cmd/golangci-lint" = "2.7.2" 5 | "go:github.com/charmbracelet/vhs" = "0.9.0" 6 | "go:github.com/goreleaser/goreleaser/v2" = "2.9.0" 7 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import "runtime/debug" 4 | 5 | var version string 6 | 7 | func init() { 8 | if version == "" { 9 | if info, ok := debug.ReadBuildInfo(); ok { 10 | version = info.Main.Version 11 | } 12 | } 13 | rootCmd.Version = version 14 | } 15 | -------------------------------------------------------------------------------- /internal/prettier/fallback.go: -------------------------------------------------------------------------------- 1 | package prettier 2 | 3 | type FallbackPrettier struct{} 4 | 5 | func NewFallbackPrettier() *FallbackPrettier { 6 | return &FallbackPrettier{} 7 | } 8 | 9 | func (*FallbackPrettier) Pretty(content string) (string, error) { 10 | return content, nil 11 | } 12 | -------------------------------------------------------------------------------- /scripts/build-completions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | rm -rf completions 6 | mkdir completions 7 | 8 | go build . 9 | 10 | ./gat completion bash > completions/gat.bash 11 | ./gat completion zsh > completions/gat.zsh 12 | ./gat completion fish > completions/gat.fish 13 | -------------------------------------------------------------------------------- /internal/prettier/registry.go: -------------------------------------------------------------------------------- 1 | package prettier 2 | 3 | var Registry = map[string]Prettier{} 4 | 5 | func Register(name string, p Prettier) Prettier { 6 | Registry[name] = p 7 | return p 8 | } 9 | 10 | func Get(name string) (Prettier, bool) { 11 | p, ok := Registry[name] 12 | return p, ok 13 | } 14 | -------------------------------------------------------------------------------- /internal/formatters/formatters.go: -------------------------------------------------------------------------------- 1 | package formatters 2 | 3 | import ( 4 | "github.com/alecthomas/chroma/v2" 5 | "github.com/alecthomas/chroma/v2/formatters" 6 | ) 7 | 8 | func Get(name string) (chroma.Formatter, bool) { 9 | f, ok := formatters.Registry[name] 10 | return f, ok 11 | } 12 | 13 | func List() []string { 14 | return formatters.Names() 15 | } 16 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["github>koki-develop/renovate-config"], 4 | "packageRules": [ 5 | { 6 | "matchPackageNames": ["github.com/alecthomas/chroma/v2"], 7 | "matchUpdateTypes": ["minor", "major"], 8 | "semanticCommitType": "feat" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tapes/demo.tape: -------------------------------------------------------------------------------- 1 | # configuration 2 | Output ./docs/demo.gif 3 | Set Shell "bash" 4 | Set FontSize 32 5 | Set Width 1200 6 | Set Height 600 7 | 8 | # setup 9 | Hide 10 | Type "go install" Enter 11 | Type "gat --help" Enter 12 | Type "cd ./tapes" Enter 13 | Sleep 2s 14 | Ctrl+l 15 | Show 16 | 17 | # demo 18 | Type "gat ./main.go" Sleep 500ms Enter 19 | Sleep 5s 20 | -------------------------------------------------------------------------------- /tapes/gess.tape: -------------------------------------------------------------------------------- 1 | # configuration 2 | Output ./docs/gess.gif 3 | Set Shell "bash" 4 | Set FontSize 32 5 | Set Width 1800 6 | Set Height 600 7 | 8 | # setup 9 | Hide 10 | Type "go install" Enter 11 | Type "gat --help" Enter 12 | Sleep 2s 13 | Ctrl+l 14 | Show 15 | 16 | # demo 17 | Type "gat --force-color ./README.md | less -R" Sleep 500ms Enter 18 | Sleep 2s 19 | Down@100ms 30 20 | -------------------------------------------------------------------------------- /internal/prettier/html.go: -------------------------------------------------------------------------------- 1 | package prettier 2 | 3 | import ( 4 | "github.com/yosssi/gohtml" 5 | ) 6 | 7 | var ( 8 | HTML = Register("HTML", NewHTMLPrettier()) 9 | ) 10 | 11 | type HTMLPrettier struct{} 12 | 13 | func NewHTMLPrettier() *HTMLPrettier { 14 | return &HTMLPrettier{} 15 | } 16 | 17 | func (p *HTMLPrettier) Pretty(h string) (string, error) { 18 | return gohtml.Format(h), nil 19 | } 20 | -------------------------------------------------------------------------------- /internal/prettier/go.go: -------------------------------------------------------------------------------- 1 | package prettier 2 | 3 | import ( 4 | "go/format" 5 | ) 6 | 7 | var ( 8 | Go = Register("Go", NewGoPrettier()) 9 | ) 10 | 11 | type GoPrettier struct{} 12 | 13 | func NewGoPrettier() *GoPrettier { 14 | return &GoPrettier{} 15 | } 16 | 17 | func (*GoPrettier) Pretty(input string) (string, error) { 18 | b, err := format.Source([]byte(input)) 19 | if err != nil { 20 | return "", err 21 | } 22 | return string(b), nil 23 | } 24 | -------------------------------------------------------------------------------- /internal/prettier/json.go: -------------------------------------------------------------------------------- 1 | package prettier 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | ) 7 | 8 | var ( 9 | JSON = Register("JSON", NewJSONPrettier()) 10 | ) 11 | 12 | type JSONPrettier struct{} 13 | 14 | func NewJSONPrettier() *JSONPrettier { 15 | return &JSONPrettier{} 16 | } 17 | 18 | func (*JSONPrettier) Pretty(j string) (string, error) { 19 | b := new(bytes.Buffer) 20 | if err := json.Indent(b, []byte(j), "", " "); err != nil { 21 | return "", err 22 | } 23 | return b.String(), nil 24 | } 25 | -------------------------------------------------------------------------------- /internal/gat/formats.go: -------------------------------------------------------------------------------- 1 | package gat 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/koki-develop/gat/internal/formatters" 8 | ) 9 | 10 | func PrintFormats(w io.Writer) error { 11 | return printFormats(w, listFormats()) 12 | } 13 | 14 | func printFormats(w io.Writer, formats []string) error { 15 | for _, f := range formats { 16 | if _, err := fmt.Fprintln(w, f); err != nil { 17 | return err 18 | } 19 | } 20 | return nil 21 | } 22 | 23 | func listFormats() []string { 24 | return formatters.List() 25 | } 26 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup 2 | description: Setup the environment for the runner 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3.5.1 8 | with: 9 | experimental: true 10 | - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 11 | with: 12 | path: | 13 | ~/.cache/go-build 14 | ~/go/pkg/mod 15 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 16 | restore-keys: | 17 | ${{ runner.os }}-go- 18 | -------------------------------------------------------------------------------- /internal/prettier/css.go: -------------------------------------------------------------------------------- 1 | package prettier 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | 7 | "github.com/client9/csstool" 8 | ) 9 | 10 | var ( 11 | CSS = Register("CSS", NewCSSPrettier()) 12 | ) 13 | 14 | type CSSPrettier struct{} 15 | 16 | func NewCSSPrettier() *CSSPrettier { 17 | return &CSSPrettier{} 18 | } 19 | 20 | func (p *CSSPrettier) Pretty(c string) (string, error) { 21 | f := csstool.NewCSSFormat(2, false, nil) 22 | f.AlwaysSemicolon = true 23 | 24 | b := new(bytes.Buffer) 25 | if err := f.Format(strings.NewReader(c), b); err != nil { 26 | return "", err 27 | } 28 | 29 | return b.String(), nil 30 | } 31 | -------------------------------------------------------------------------------- /internal/gat/languages_test.go: -------------------------------------------------------------------------------- 1 | package gat 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func Test_printLanguages(t *testing.T) { 11 | langs := []Language{ 12 | {Name: "AAA", Aliases: []string{"a", "aa"}}, 13 | {Name: "BB", Aliases: []string{"b"}}, 14 | {Name: "CC", Aliases: []string{}}, 15 | {Name: "DDD", Aliases: []string{"d", "ddd"}}, 16 | } 17 | want := `AAA a, aa 18 | BB b 19 | CC 20 | DDD d, ddd 21 | ` 22 | 23 | buf := new(bytes.Buffer) 24 | err := printLanguages(buf, langs) 25 | 26 | assert.NoError(t, err) 27 | assert.Equal(t, want, buf.String()) 28 | } 29 | -------------------------------------------------------------------------------- /internal/prettier/fallback_test.go: -------------------------------------------------------------------------------- 1 | package prettier 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestFallbackPrettier_Pretty(t *testing.T) { 11 | tests := []struct { 12 | src string 13 | want string 14 | wantErr bool 15 | }{ 16 | { 17 | src: "CONTENT", 18 | want: "CONTENT", 19 | wantErr: false, 20 | }, 21 | } 22 | for i, tt := range tests { 23 | t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { 24 | f := NewFallbackPrettier() 25 | got, err := f.Pretty(tt.src) 26 | 27 | assert.Equal(t, tt.want, got) 28 | if tt.wantErr { 29 | assert.Error(t, err) 30 | } else { 31 | assert.NoError(t, err) 32 | } 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /internal/prettier/yaml.go: -------------------------------------------------------------------------------- 1 | package prettier 2 | 3 | import ( 4 | "github.com/google/yamlfmt" 5 | "github.com/google/yamlfmt/formatters/basic" 6 | ) 7 | 8 | var ( 9 | YAML = Register("YAML", NewYAMLPrettier()) 10 | ) 11 | 12 | type YAMLPrettier struct { 13 | factory yamlfmt.Factory 14 | } 15 | 16 | func NewYAMLPrettier() *YAMLPrettier { 17 | return &YAMLPrettier{ 18 | factory: &basic.BasicFormatterFactory{}, 19 | } 20 | } 21 | 22 | func (p *YAMLPrettier) Pretty(input string) (string, error) { 23 | formatter, err := p.factory.NewFormatter(nil) 24 | if err != nil { 25 | return "", err 26 | } 27 | 28 | b, err := formatter.Format([]byte(input)) 29 | if err != nil { 30 | return "", err 31 | } 32 | 33 | return string(b), nil 34 | } 35 | -------------------------------------------------------------------------------- /internal/prettier/yaml_test.go: -------------------------------------------------------------------------------- 1 | package prettier 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestYAMLPrettier_Pretty(t *testing.T) { 11 | tests := []struct { 12 | src string 13 | want string 14 | wantErr bool 15 | }{ 16 | { 17 | src: `foo: bar 18 | baz: qux 19 | `, 20 | want: `foo: bar 21 | baz: qux 22 | `, 23 | wantErr: false, 24 | }, 25 | } 26 | for i, tt := range tests { 27 | t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { 28 | p := NewYAMLPrettier() 29 | got, err := p.Pretty(tt.src) 30 | 31 | assert.Equal(t, tt.want, got) 32 | if tt.wantErr { 33 | assert.Error(t, err) 34 | } else { 35 | assert.NoError(t, err) 36 | } 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /internal/gat/formats_test.go: -------------------------------------------------------------------------------- 1 | package gat 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func Test_printFormats(t *testing.T) { 11 | fmts := []string{ 12 | "html", 13 | "html-min", 14 | "json", 15 | "json-min", 16 | "noop", 17 | "svg", 18 | "svg-min", 19 | "terminal", 20 | "terminal16", 21 | "terminal16m", 22 | "terminal256", 23 | "terminal8", 24 | "tokens", 25 | } 26 | 27 | want := `html 28 | html-min 29 | json 30 | json-min 31 | noop 32 | svg 33 | svg-min 34 | terminal 35 | terminal16 36 | terminal16m 37 | terminal256 38 | terminal8 39 | tokens 40 | ` 41 | 42 | buf := new(bytes.Buffer) 43 | err := printFormats(buf, fmts) 44 | 45 | assert.NoError(t, err) 46 | assert.Equal(t, want, buf.String()) 47 | } 48 | -------------------------------------------------------------------------------- /internal/prettier/css_test.go: -------------------------------------------------------------------------------- 1 | package prettier 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestCSSPrettier_Pretty(t *testing.T) { 11 | tests := []struct { 12 | src string 13 | want string 14 | wantErr bool 15 | }{ 16 | { 17 | src: `body{background-color:#fff;color:#000;}`, 18 | want: `body { 19 | background-color: #fff; 20 | color: #000; 21 | } 22 | `, 23 | wantErr: false, 24 | }, 25 | } 26 | for i, tt := range tests { 27 | t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { 28 | p := NewCSSPrettier() 29 | got, err := p.Pretty(tt.src) 30 | 31 | assert.Equal(t, tt.want, got) 32 | if tt.wantErr { 33 | assert.Error(t, err) 34 | } else { 35 | assert.NoError(t, err) 36 | } 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /internal/styles/styles.go: -------------------------------------------------------------------------------- 1 | package styles 2 | 3 | import ( 4 | "embed" 5 | "io/fs" 6 | 7 | "github.com/alecthomas/chroma/v2" 8 | "github.com/alecthomas/chroma/v2/styles" 9 | ) 10 | 11 | //go:embed *.xml 12 | var embedded embed.FS 13 | 14 | func init() { 15 | files, err := fs.ReadDir(embedded, ".") 16 | if err != nil { 17 | panic(err) 18 | } 19 | for _, file := range files { 20 | if file.IsDir() { 21 | continue 22 | } 23 | r, err := embedded.Open(file.Name()) 24 | if err != nil { 25 | panic(err) 26 | } 27 | style, err := chroma.NewXMLStyle(r) 28 | if err != nil { 29 | panic(err) 30 | } 31 | styles.Register(style) 32 | _ = r.Close() 33 | } 34 | } 35 | 36 | func Get(name string) (*chroma.Style, bool) { 37 | s, ok := styles.Registry[name] 38 | return s, ok 39 | } 40 | 41 | func List() []string { 42 | return styles.Names() 43 | } 44 | -------------------------------------------------------------------------------- /internal/gat/languages.go: -------------------------------------------------------------------------------- 1 | package gat 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | "text/tabwriter" 8 | 9 | "github.com/koki-develop/gat/internal/lexers" 10 | ) 11 | 12 | type Language struct { 13 | Name string 14 | Aliases []string 15 | } 16 | 17 | func PrintLanguages(w io.Writer) error { 18 | return printLanguages(w, listLanguages()) 19 | } 20 | 21 | func printLanguages(w io.Writer, langs []Language) error { 22 | tw := tabwriter.NewWriter(w, 0, 0, 1, ' ', 0) 23 | 24 | for _, l := range langs { 25 | if _, err := fmt.Fprintf(tw, "%s\t%s\n", l.Name, strings.Join(l.Aliases, ", ")); err != nil { 26 | return err 27 | } 28 | } 29 | 30 | return tw.Flush() 31 | } 32 | 33 | func listLanguages() []Language { 34 | ls := lexers.List() 35 | 36 | rtn := make([]Language, len(ls)) 37 | for i, l := range ls { 38 | cfg := l.Config() 39 | rtn[i] = Language{Name: cfg.Name, Aliases: cfg.Aliases} 40 | } 41 | 42 | return rtn 43 | } 44 | -------------------------------------------------------------------------------- /internal/prettier/xml_test.go: -------------------------------------------------------------------------------- 1 | package prettier 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestXMLPrettier_Pretty(t *testing.T) { 11 | tests := []struct { 12 | src string 13 | want string 14 | wantErr bool 15 | }{ 16 | { 17 | src: `text`, 18 | want: ` 19 | 20 | text 21 | `, 22 | wantErr: false, 23 | }, 24 | { 25 | src: "", 26 | want: "", 27 | wantErr: true, 28 | }, 29 | } 30 | for i, tt := range tests { 31 | t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { 32 | x := NewXMLPrettier() 33 | got, err := x.Pretty(tt.src) 34 | 35 | assert.Equal(t, tt.want, got) 36 | if tt.wantErr { 37 | assert.Error(t, err) 38 | } else { 39 | assert.NoError(t, err) 40 | } 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /internal/prettier/html_test.go: -------------------------------------------------------------------------------- 1 | package prettier 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestHTMLPrettier_Pretty(t *testing.T) { 11 | tests := []struct { 12 | src string 13 | want string 14 | wantErr bool 15 | }{ 16 | { 17 | src: `title

paragraph

`, 18 | want: ` 19 | 20 | 21 | 22 | title 23 | 24 | 25 | 26 |

27 | paragraph 28 |

29 | 30 | `, 31 | wantErr: false, 32 | }, 33 | } 34 | for i, tt := range tests { 35 | t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { 36 | p := NewHTMLPrettier() 37 | got, err := p.Pretty(tt.src) 38 | 39 | assert.Equal(t, tt.want, got) 40 | if tt.wantErr { 41 | assert.Error(t, err) 42 | } else { 43 | assert.NoError(t, err) 44 | } 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /internal/prettier/go_test.go: -------------------------------------------------------------------------------- 1 | package prettier 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestGoPrettier_Pretty(t *testing.T) { 11 | tests := []struct { 12 | src string 13 | want string 14 | wantErr bool 15 | }{ 16 | { 17 | src: `package main 18 | import "fmt" 19 | func main() { 20 | fmt.Println("Hello, world!") 21 | }`, 22 | want: `package main 23 | 24 | import "fmt" 25 | 26 | func main() { 27 | fmt.Println("Hello, world!") 28 | } 29 | `, 30 | wantErr: false, 31 | }, 32 | { 33 | src: "package main\nfunc main() {", 34 | want: "", 35 | wantErr: true, 36 | }, 37 | } 38 | for i, tt := range tests { 39 | t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { 40 | g := NewGoPrettier() 41 | got, err := g.Pretty(tt.src) 42 | 43 | assert.Equal(t, tt.want, got) 44 | if tt.wantErr { 45 | assert.Error(t, err) 46 | } else { 47 | assert.NoError(t, err) 48 | } 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/update-demo.yml: -------------------------------------------------------------------------------- 1 | name: Update demo 2 | 3 | permissions: {} 4 | 5 | on: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | update-demo: 10 | name: Update Demo 11 | timeout-minutes: 10 12 | permissions: 13 | contents: write # needed to commit regenerated demo artifacts 14 | pull-requests: write # needed to open the update pull request 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 18 | with: 19 | persist-credentials: false 20 | - uses: ./.github/actions/setup 21 | 22 | - run: vhs < ./tapes/demo.tape 23 | - run: vhs < ./tapes/gess.tape 24 | 25 | - uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11 26 | with: 27 | token: ${{ secrets.GITHUB_TOKEN }} 28 | title: "chore: Update demo" 29 | body: ${{ github.sha }} 30 | branch: docs/update 31 | branch-suffix: short-commit-hash 32 | -------------------------------------------------------------------------------- /internal/prettier/json_test.go: -------------------------------------------------------------------------------- 1 | package prettier 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestJSONPrettier_Pretty(t *testing.T) { 11 | tests := []struct { 12 | src string 13 | want string 14 | wantErr bool 15 | }{ 16 | { 17 | src: `{"name":"John","age":30,"cars":{"car1":"Ford","car2":"BMW","car3":"Fiat"}}`, 18 | want: `{ 19 | "name": "John", 20 | "age": 30, 21 | "cars": { 22 | "car1": "Ford", 23 | "car2": "BMW", 24 | "car3": "Fiat" 25 | } 26 | }`, 27 | wantErr: false, 28 | }, 29 | { 30 | src: "INVALID_JSON", 31 | want: "", 32 | wantErr: true, 33 | }, 34 | } 35 | for i, tt := range tests { 36 | t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { 37 | j := &JSONPrettier{} 38 | got, err := j.Pretty(tt.src) 39 | 40 | assert.Equal(t, tt.want, got) 41 | if tt.wantErr { 42 | assert.Error(t, err) 43 | } else { 44 | assert.NoError(t, err) 45 | } 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /internal/formatters/svg_minified.go: -------------------------------------------------------------------------------- 1 | package formatters 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | 7 | "github.com/alecthomas/chroma/v2" 8 | "github.com/alecthomas/chroma/v2/formatters" 9 | "github.com/tdewolff/minify/v2" 10 | "github.com/tdewolff/minify/v2/svg" 11 | ) 12 | 13 | var ( 14 | SVGMinified = formatters.Register("svg-min", NewSVGMinifiedFormatter()) 15 | ) 16 | 17 | type SVGMinifiedFormatter struct { 18 | svgFormatter chroma.Formatter 19 | m *minify.M 20 | } 21 | 22 | func NewSVGMinifiedFormatter() *SVGMinifiedFormatter { 23 | m := minify.New() 24 | m.AddFunc(mimeTypeSVG, svg.Minify) 25 | 26 | return &SVGMinifiedFormatter{ 27 | svgFormatter: formatters.SVG, 28 | m: m, 29 | } 30 | } 31 | 32 | func (f *SVGMinifiedFormatter) Format(w io.Writer, style *chroma.Style, iterator chroma.Iterator) error { 33 | b := new(bytes.Buffer) 34 | if err := f.svgFormatter.Format(b, style, iterator); err != nil { 35 | return err 36 | } 37 | 38 | if err := f.m.Minify(mimeTypeSVG, w, b); err != nil { 39 | return err 40 | } 41 | 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /internal/formatters/json_minified.go: -------------------------------------------------------------------------------- 1 | package formatters 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | 7 | "github.com/alecthomas/chroma/v2" 8 | "github.com/alecthomas/chroma/v2/formatters" 9 | "github.com/tdewolff/minify/v2" 10 | "github.com/tdewolff/minify/v2/json" 11 | ) 12 | 13 | var ( 14 | JSONMinified = formatters.Register("json-min", NewJSONMinifiedFormatter()) 15 | ) 16 | 17 | type JSONMinifiedFormatter struct { 18 | jsonFormatter chroma.Formatter 19 | m *minify.M 20 | } 21 | 22 | func NewJSONMinifiedFormatter() *JSONMinifiedFormatter { 23 | m := minify.New() 24 | m.AddFunc(mimeTypeJSON, json.Minify) 25 | 26 | return &JSONMinifiedFormatter{ 27 | jsonFormatter: formatters.JSON, 28 | m: m, 29 | } 30 | } 31 | 32 | func (f *JSONMinifiedFormatter) Format(w io.Writer, style *chroma.Style, iterator chroma.Iterator) error { 33 | b := new(bytes.Buffer) 34 | if err := f.jsonFormatter.Format(b, style, iterator); err != nil { 35 | return err 36 | } 37 | 38 | if err := f.m.Minify(mimeTypeJSON, w, b); err != nil { 39 | return err 40 | } 41 | 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Koki Sato 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /internal/prettier/xml.go: -------------------------------------------------------------------------------- 1 | package prettier 2 | 3 | import ( 4 | "bytes" 5 | "encoding/xml" 6 | "io" 7 | "strings" 8 | ) 9 | 10 | var ( 11 | XML = Register("XML", NewXMLPrettier()) 12 | ) 13 | 14 | type XMLPrettier struct{} 15 | 16 | func NewXMLPrettier() *XMLPrettier { 17 | return &XMLPrettier{} 18 | } 19 | 20 | func (*XMLPrettier) Pretty(input string) (string, error) { 21 | var b bytes.Buffer 22 | dec := xml.NewDecoder(strings.NewReader(input)) 23 | enc := xml.NewEncoder(&b) 24 | enc.Indent("", " ") 25 | 26 | for { 27 | token, err := dec.Token() 28 | if err != nil { 29 | if err == io.EOF { 30 | break 31 | } 32 | return "", err 33 | } 34 | 35 | if err := enc.EncodeToken(token); err != nil { 36 | return "", err 37 | } 38 | 39 | if declaration, ok := token.(xml.ProcInst); ok && declaration.Target == "xml" { 40 | if err := enc.Flush(); err != nil { 41 | return "", err 42 | } 43 | if _, err := b.WriteString("\n"); err != nil { 44 | return "", err 45 | } 46 | } 47 | } 48 | 49 | if err := enc.Flush(); err != nil { 50 | return "", err 51 | } 52 | 53 | return b.String(), nil 54 | } 55 | -------------------------------------------------------------------------------- /.github/workflows/claude-renovate-review.yml: -------------------------------------------------------------------------------- 1 | name: Claude Renovate Review 2 | 3 | permissions: {} 4 | 5 | on: 6 | pull_request: 7 | types: 8 | - opened 9 | - edited 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | claude-renovate-review: 17 | name: Claude Renovate Review 18 | if: github.event.pull_request.user.login == 'renovate[bot]' 19 | timeout-minutes: 30 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: read 23 | pull-requests: write # required so Claude can comment on Renovate PRs 24 | steps: 25 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 26 | with: 27 | persist-credentials: false 28 | - uses: koki-develop/claude-renovate-review@e3b6fc8f6ce1460c17b70ad0e106ba774d4efc62 # v1.0.8 29 | with: 30 | claude-code-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} 31 | allowed-tools: | 32 | WebFetch(domain:github.com) 33 | WebFetch(domain:raw.githubusercontent.com) 34 | WebFetch(domain:pkg.go.dev) 35 | WebFetch(domain:golang.org) 36 | WebFetch(domain:go.dev) 37 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | before: 4 | hooks: 5 | - go mod tidy 6 | - ./scripts/build-completions.sh 7 | builds: 8 | - ldflags: 9 | - -s -w -X github.com/koki-develop/gat/cmd.version=v{{.Version}} 10 | env: 11 | - CGO_ENABLED=0 12 | goos: 13 | - linux 14 | - windows 15 | - darwin 16 | 17 | archives: 18 | - formats: [tar.gz] 19 | files: 20 | - completions/* 21 | name_template: >- 22 | {{ .ProjectName }}_ 23 | {{- title .Os }}_ 24 | {{- if eq .Arch "amd64" }}x86_64 25 | {{- else if eq .Arch "386" }}i386 26 | {{- else }}{{ .Arch }}{{ end }} 27 | {{- if .Arm }}v{{ .Arm }}{{ end }} 28 | format_overrides: 29 | - goos: windows 30 | formats: [zip] 31 | checksum: 32 | name_template: 'checksums.txt' 33 | changelog: 34 | sort: asc 35 | filters: 36 | exclude: 37 | - '^docs:' 38 | - '^test:' 39 | 40 | brews: 41 | - repository: 42 | owner: koki-develop 43 | name: homebrew-tap 44 | token: "{{ .Env.TAP_GITHUB_TOKEN }}" 45 | directory: Formula 46 | install: | 47 | bin.install "gat" 48 | bash_completion.install "completions/gat.bash" => "gat" 49 | zsh_completion.install "completions/gat.zsh" => "_gat" 50 | fish_completion.install "completions/gat.fish" 51 | -------------------------------------------------------------------------------- /internal/lexers/lexers.go: -------------------------------------------------------------------------------- 1 | package lexers 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/alecthomas/chroma/v2" 7 | "github.com/alecthomas/chroma/v2/lexers" 8 | ) 9 | 10 | type option struct { 11 | Language string 12 | Filename string 13 | Source string 14 | } 15 | 16 | type Option func(*option) 17 | 18 | func WithLanguage(lang string) Option { 19 | return func(o *option) { 20 | o.Language = lang 21 | } 22 | } 23 | 24 | func WithFilename(name string) Option { 25 | return func(o *option) { 26 | o.Filename = name 27 | } 28 | } 29 | 30 | func WithSource(src string) Option { 31 | return func(o *option) { 32 | o.Source = src 33 | } 34 | } 35 | 36 | func Get(opts ...Option) (chroma.Lexer, error) { 37 | opt := &option{} 38 | for _, o := range opts { 39 | o(opt) 40 | } 41 | 42 | var l chroma.Lexer 43 | if opt.Language != "" { 44 | l = lexers.Get(opt.Language) 45 | if l == nil { 46 | return nil, fmt.Errorf("unknown language: %s", opt.Language) 47 | } 48 | } 49 | 50 | if l == nil && opt.Filename != "" { 51 | l = lexers.Match(opt.Filename) 52 | } 53 | 54 | if l == nil && opt.Source != "" { 55 | l = lexers.Analyse(opt.Source) 56 | } 57 | 58 | if l == nil { 59 | l = lexers.Fallback 60 | } 61 | return chroma.Coalesce(l), nil 62 | } 63 | 64 | func List() []chroma.Lexer { 65 | return lexers.GlobalLexerRegistry.Lexers 66 | } 67 | -------------------------------------------------------------------------------- /internal/formatters/html_minified.go: -------------------------------------------------------------------------------- 1 | package formatters 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | 7 | "github.com/alecthomas/chroma/v2" 8 | "github.com/alecthomas/chroma/v2/formatters" 9 | htmlformatter "github.com/alecthomas/chroma/v2/formatters/html" 10 | "github.com/tdewolff/minify/v2" 11 | "github.com/tdewolff/minify/v2/css" 12 | "github.com/tdewolff/minify/v2/html" 13 | ) 14 | 15 | var ( 16 | HTMLMinified = formatters.Register("html-min", NewHTMLMinifiedFormatter()) 17 | ) 18 | 19 | type HTMLMinifiedFormatter struct { 20 | htmlFormatter chroma.Formatter 21 | m *minify.M 22 | } 23 | 24 | func NewHTMLMinifiedFormatter() *HTMLMinifiedFormatter { 25 | m := minify.New() 26 | m.Add(mimeTypeHTML, &html.Minifier{ 27 | KeepDocumentTags: true, 28 | KeepQuotes: true, 29 | }) 30 | m.AddFunc(mimeTypeCSS, css.Minify) 31 | 32 | return &HTMLMinifiedFormatter{ 33 | htmlFormatter: htmlformatter.New(htmlformatter.Standalone(true), htmlformatter.WithClasses(true)), 34 | m: m, 35 | } 36 | } 37 | 38 | func (f *HTMLMinifiedFormatter) Format(w io.Writer, style *chroma.Style, iterator chroma.Iterator) error { 39 | b := new(bytes.Buffer) 40 | if err := f.htmlFormatter.Format(b, style, iterator); err != nil { 41 | return err 42 | } 43 | 44 | if err := f.m.Minify(mimeTypeHTML, w, b); err != nil { 45 | return err 46 | } 47 | 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /.github/workflows/update-docs.yml: -------------------------------------------------------------------------------- 1 | name: Update docs 2 | 3 | permissions: {} 4 | 5 | on: 6 | push: 7 | branches: 8 | - main 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | update-docs: 16 | name: Update Docs 17 | timeout-minutes: 10 18 | runs-on: ubuntu-latest 19 | permissions: 20 | contents: write # needed to commit generated documentation updates 21 | pull-requests: write # needed to open a PR with the doc changes 22 | steps: 23 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 24 | with: 25 | persist-credentials: false 26 | - uses: ./.github/actions/setup 27 | 28 | - name: Update docs 29 | id: update-docs 30 | run: | 31 | go run ./docs/update.go 32 | git add . 33 | if git diff --staged --exit-code --quiet; then 34 | echo "No changes." 35 | else 36 | echo "Changes detected." 37 | echo "diff=true" >> "${GITHUB_OUTPUT}" 38 | fi 39 | - uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11 40 | if: ${{ steps.update-docs.outputs.diff == 'true' }} 41 | with: 42 | token: ${{ github.token }} 43 | sign-commits: true 44 | title: "chore: Update docs" 45 | body: ${{ github.sha }} 46 | branch: docs/update 47 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | permissions: {} 4 | 5 | on: 6 | pull_request: 7 | push: 8 | branches: 9 | - main 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | test: 17 | name: Test 18 | timeout-minutes: 10 19 | permissions: 20 | contents: read 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 24 | with: 25 | persist-credentials: false 26 | - uses: ./.github/actions/setup 27 | - run: go test ./... 28 | 29 | build: 30 | name: Build 31 | timeout-minutes: 10 32 | permissions: 33 | contents: read 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 37 | with: 38 | persist-credentials: false 39 | - uses: ./.github/actions/setup 40 | - run: goreleaser check 41 | - run: goreleaser release --snapshot --clean 42 | 43 | lint: 44 | name: Lint 45 | timeout-minutes: 10 46 | permissions: 47 | contents: read 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 51 | with: 52 | persist-credentials: false 53 | - uses: ./.github/actions/setup 54 | - name: lint 55 | run: golangci-lint run --verbose ./... 56 | -------------------------------------------------------------------------------- /internal/masker/masker.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | var patterns = []*regexp.Regexp{ 9 | // AWS Access Key ID (permanent) 10 | regexp.MustCompile(`AKIA[0-9A-Z]{16}`), 11 | // AWS Access Key ID (temporary, STS/SSO) 12 | regexp.MustCompile(`ASIA[0-9A-Z]{16}`), 13 | // GitHub Tokens (ghp_, gho_, ghs_, ghr_) 14 | regexp.MustCompile(`gh[pousr]_[a-zA-Z0-9]{36,}`), 15 | // GitLab Personal Access Token 16 | regexp.MustCompile(`glpat-[a-zA-Z0-9\-_]{20,}`), 17 | // Slack Tokens 18 | regexp.MustCompile(`xox[baprs]-[0-9a-zA-Z\-]+`), 19 | // Anthropic API Key (must be before OpenAI to avoid false matches) 20 | regexp.MustCompile(`sk-ant-[a-zA-Z0-9\-_]+`), 21 | // OpenAI API Key (both legacy sk- and new sk-proj- formats) 22 | regexp.MustCompile(`sk-(?:proj-)?[a-zA-Z0-9_\-]{20,}`), 23 | // Supabase Secret Key 24 | regexp.MustCompile(`sb_secret_[a-zA-Z0-9\-_]+`), 25 | // JWT Tokens 26 | regexp.MustCompile(`eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*`), 27 | // Private Key Headers 28 | regexp.MustCompile(`-----BEGIN\s+(RSA|DSA|EC|OPENSSH|PGP)\s+PRIVATE\s+KEY-----`), 29 | // AWS Secret Access Key (must be last due to generic pattern that could match other secrets) 30 | regexp.MustCompile(`[a-zA-Z0-9+/]{40}`), 31 | } 32 | 33 | // Mask replaces sensitive patterns in content with asterisks of the same length 34 | func Mask(content string) string { 35 | result := content 36 | for _, p := range patterns { 37 | result = p.ReplaceAllStringFunc(result, func(match string) string { 38 | return strings.Repeat("*", len(match)) 39 | }) 40 | } 41 | return result 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/github-actions-lint.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Actions Lint 2 | 3 | permissions: {} 4 | 5 | on: 6 | pull_request: 7 | paths: 8 | - ".github/**" 9 | push: 10 | branches: 11 | - main 12 | paths: 13 | - ".github/**" 14 | 15 | concurrency: 16 | group: ${{ github.workflow }}-${{ github.ref }} 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | actionlint: 21 | name: actionlint 22 | timeout-minutes: 5 23 | runs-on: ubuntu-latest 24 | permissions: 25 | contents: read 26 | steps: 27 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 28 | with: 29 | persist-credentials: false 30 | - uses: koki-develop/github-actions-lint/actionlint@5ecd42b59803f14bfaa7ed0bfb8de8c1dc1b4c6d # v1.6.0 31 | 32 | ghalint: 33 | name: ghalint 34 | timeout-minutes: 5 35 | runs-on: ubuntu-latest 36 | permissions: 37 | contents: read 38 | steps: 39 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 40 | with: 41 | persist-credentials: false 42 | - uses: koki-develop/github-actions-lint/ghalint@5ecd42b59803f14bfaa7ed0bfb8de8c1dc1b4c6d # v1.6.0 43 | with: 44 | action-path: ./.github/actions/**/action.yml 45 | 46 | zizmor: 47 | name: zizmor 48 | timeout-minutes: 5 49 | runs-on: ubuntu-latest 50 | permissions: 51 | contents: read 52 | steps: 53 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 54 | with: 55 | persist-credentials: false 56 | - uses: koki-develop/github-actions-lint/zizmor@5ecd42b59803f14bfaa7ed0bfb8de8c1dc1b4c6d # v1.6.0 57 | with: 58 | github-token: ${{ github.token }} 59 | persona: auditor 60 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | name: Release Please 2 | 3 | permissions: {} 4 | 5 | on: 6 | push: 7 | branches: 8 | - main 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: false 13 | 14 | jobs: 15 | release-please: 16 | name: Release Please 17 | timeout-minutes: 10 18 | runs-on: ubuntu-latest 19 | permissions: 20 | contents: write # for create a release 21 | pull-requests: write # for open a pull request 22 | issues: write # for create labels 23 | outputs: 24 | should-release: ${{ steps.release-please.outputs.release_created }} 25 | steps: 26 | - uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4.4.0 27 | id: release-please 28 | with: 29 | release-type: simple 30 | token: ${{ github.token }} 31 | 32 | release: 33 | name: Release 34 | if: ${{ needs.release-please.outputs.should-release == 'true' }} 35 | timeout-minutes: 10 36 | needs: release-please 37 | runs-on: ubuntu-latest 38 | permissions: 39 | contents: write # for upload release assets 40 | steps: 41 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 42 | with: 43 | fetch-depth: 0 44 | persist-credentials: false 45 | - uses: ./.github/actions/setup 46 | - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 47 | id: app-token 48 | with: 49 | app-id: ${{ secrets.HOMEBREW_TAP_APP_ID }} 50 | private-key: ${{ secrets.HOMEBREW_TAP_APP_PRIVATE_KEY }} 51 | owner: ${{ github.repository_owner }} 52 | repositories: homebrew-tap 53 | permission-contents: write 54 | 55 | - name: release 56 | run: goreleaser release --clean 57 | env: 58 | GITHUB_TOKEN: ${{ github.token }} 59 | TAP_GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} 60 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | "github.com/koki-develop/gat/internal/gat" 8 | "github.com/spf13/cobra" 9 | "golang.org/x/term" 10 | ) 11 | 12 | // processFile handles opening, processing, and closing a single file with proper defer scope 13 | func processFile(g *gat.Gat, filename string, opts ...gat.PrintOption) error { 14 | f, err := os.Open(filename) 15 | if err != nil { 16 | return err 17 | } 18 | defer func() { _ = f.Close() }() 19 | 20 | return g.Print(os.Stdout, f, opts...) 21 | } 22 | 23 | var rootCmd = &cobra.Command{ 24 | Use: "gat [file]...", 25 | Short: "cat alternative written in Go", 26 | Long: "cat alternative written in Go.", 27 | RunE: func(cmd *cobra.Command, args []string) error { 28 | ist := term.IsTerminal(int(os.Stdout.Fd())) 29 | 30 | switch { 31 | case flagListLangs: 32 | return gat.PrintLanguages(os.Stdout) 33 | case flagListFormats: 34 | return gat.PrintFormats(os.Stdout) 35 | case flagListThemes: 36 | return gat.PrintThemes(os.Stdout, ist) 37 | } 38 | 39 | if strings.HasPrefix(flagFormat, "terminal") { 40 | if !ist { 41 | if !flagForceColor { 42 | flagTheme = "noop" 43 | } 44 | flagForceBinary = true 45 | flagNoResize = true 46 | } 47 | } 48 | 49 | g, err := gat.New(&gat.Config{ 50 | Language: flagLang, 51 | Format: flagFormat, 52 | Theme: flagTheme, 53 | RenderMarkdown: flagRenderMarkdown, 54 | ForceBinary: flagForceBinary, 55 | NoResize: flagNoResize, 56 | }) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | if len(args) == 0 { 62 | return g.Print(os.Stdout, os.Stdin, gat.WithPretty(flagPretty), gat.WithMask(flagMaskSecrets)) 63 | } 64 | 65 | for _, filename := range args { 66 | if err := processFile(g, filename, gat.WithPretty(flagPretty), gat.WithMask(flagMaskSecrets), gat.WithFilename(filename)); err != nil { 67 | return err 68 | } 69 | } 70 | 71 | return nil 72 | }, 73 | } 74 | 75 | func Execute() { 76 | err := rootCmd.Execute() 77 | if err != nil { 78 | os.Exit(1) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /cmd/flags.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import "os" 4 | 5 | func envOrDefault(key, def string) string { 6 | if v := os.Getenv(key); v != "" { 7 | return v 8 | } 9 | return def 10 | } 11 | 12 | var ( 13 | // --lang 14 | flagLang string 15 | 16 | // --format 17 | flagFormat string 18 | flagFormatDefault = envOrDefault("GAT_FORMAT", "terminal256") 19 | 20 | // --theme 21 | flagTheme string 22 | flagThemeDefault = envOrDefault("GAT_THEME", "monokai") 23 | 24 | // -M, --render-markdown 25 | flagRenderMarkdown bool 26 | 27 | // --force-color 28 | flagForceColor bool 29 | 30 | // --force-binary 31 | flagForceBinary bool 32 | 33 | // --no-resize 34 | flagNoResize bool 35 | 36 | // --pretty 37 | flagPretty bool 38 | 39 | // --mask-secrets 40 | flagMaskSecrets bool 41 | 42 | // --list-langs 43 | flagListLangs bool 44 | 45 | // --list-formats 46 | flagListFormats bool 47 | 48 | // --list-themes 49 | flagListThemes bool 50 | ) 51 | 52 | func init() { 53 | rootCmd.Flags().StringVarP(&flagLang, "lang", "l", "", "language for syntax highlighting") 54 | rootCmd.Flags().StringVarP(&flagFormat, "format", "f", flagFormatDefault, "output format") 55 | rootCmd.Flags().StringVarP(&flagTheme, "theme", "t", flagThemeDefault, "highlight theme") 56 | rootCmd.Flags().BoolVarP(&flagRenderMarkdown, "render-markdown", "M", false, "render markdown") 57 | rootCmd.Flags().BoolVarP(&flagForceColor, "force-color", "c", false, "force colored output") 58 | rootCmd.Flags().BoolVarP(&flagForceBinary, "force-binary", "b", false, "force binary output") 59 | rootCmd.Flags().BoolVar(&flagNoResize, "no-resize", false, "do not resize images") 60 | 61 | rootCmd.Flags().BoolVarP(&flagPretty, "pretty", "p", false, "whether to format a content pretty") 62 | rootCmd.Flags().BoolVar(&flagMaskSecrets, "mask-secrets", false, "mask sensitive information (API keys, tokens)") 63 | 64 | rootCmd.Flags().BoolVar(&flagListLangs, "list-langs", false, "print a list of supported languages for syntax highlighting") 65 | rootCmd.Flags().BoolVar(&flagListFormats, "list-formats", false, "print a list of supported output formats") 66 | rootCmd.Flags().BoolVar(&flagListThemes, "list-themes", false, "print a list of supported themes with preview") 67 | rootCmd.MarkFlagsMutuallyExclusive("list-langs", "list-formats", "list-themes") 68 | } 69 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/koki-develop/gat 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.25.5 6 | 7 | require ( 8 | github.com/alecthomas/chroma/v2 v2.21.1 9 | github.com/charmbracelet/glamour v0.10.0 10 | github.com/client9/csstool v0.2.2 11 | github.com/google/yamlfmt v0.20.0 12 | github.com/mattn/go-sixel v0.0.5 13 | github.com/spf13/cobra v1.10.2 14 | github.com/stretchr/testify v1.11.1 15 | github.com/tdewolff/minify/v2 v2.24.8 16 | github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 17 | golang.org/x/image v0.33.0 18 | golang.org/x/term v0.37.0 19 | ) 20 | 21 | require ( 22 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 23 | github.com/aymerick/douceur v0.2.0 // indirect 24 | github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect 25 | github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect 26 | github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect 27 | github.com/charmbracelet/x/ansi v0.8.0 // indirect 28 | github.com/charmbracelet/x/cellbuf v0.0.13 // indirect 29 | github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect 30 | github.com/charmbracelet/x/term v0.2.1 // indirect 31 | github.com/davecgh/go-spew v1.1.1 // indirect 32 | github.com/dlclark/regexp2 v1.11.5 // indirect 33 | github.com/google/go-cmp v0.6.0 // indirect 34 | github.com/gorilla/css v1.0.1 // indirect 35 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 36 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 37 | github.com/mattn/go-isatty v0.0.20 // indirect 38 | github.com/mattn/go-runewidth v0.0.16 // indirect 39 | github.com/microcosm-cc/bluemonday v1.0.27 // indirect 40 | github.com/mitchellh/mapstructure v1.5.0 // indirect 41 | github.com/muesli/reflow v0.3.0 // indirect 42 | github.com/muesli/termenv v0.16.0 // indirect 43 | github.com/pmezard/go-difflib v1.0.0 // indirect 44 | github.com/rivo/uniseg v0.4.7 // indirect 45 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect 46 | github.com/soniakeys/quant v1.0.0 // indirect 47 | github.com/spf13/pflag v1.0.9 // indirect 48 | github.com/tdewolff/parse v2.3.3+incompatible // indirect 49 | github.com/tdewolff/parse/v2 v2.8.5 // indirect 50 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 51 | github.com/yuin/goldmark v1.7.8 // indirect 52 | github.com/yuin/goldmark-emoji v1.0.5 // indirect 53 | golang.org/x/net v0.38.0 // indirect 54 | golang.org/x/sys v0.38.0 // indirect 55 | golang.org/x/text v0.31.0 // indirect 56 | gopkg.in/yaml.v3 v3.0.1 // indirect 57 | ) 58 | -------------------------------------------------------------------------------- /internal/masker/masker_test.go: -------------------------------------------------------------------------------- 1 | package masker 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestMask(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | input string 14 | want string 15 | }{ 16 | { 17 | name: "AWS Access Key", 18 | input: "aws_access_key_id = AKIAIOSFODNN7EXAMPLE", 19 | want: "aws_access_key_id = " + strings.Repeat("*", 20), 20 | }, 21 | { 22 | name: "AWS Access Key (temporary/SSO)", 23 | input: "aws_access_key_id = ASIAISEXAMPLEKEY1234", 24 | want: "aws_access_key_id = " + strings.Repeat("*", 20), 25 | }, 26 | { 27 | name: "AWS Secret Access Key", 28 | input: "aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", 29 | want: "aws_secret_access_key = " + strings.Repeat("*", 40), 30 | }, 31 | { 32 | name: "GitHub Personal Access Token", 33 | input: "token: ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 34 | want: "token: " + strings.Repeat("*", 40), 35 | }, 36 | { 37 | name: "GitHub OAuth Token", 38 | input: "GITHUB_TOKEN=gho_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 39 | want: "GITHUB_TOKEN=" + strings.Repeat("*", 40), 40 | }, 41 | { 42 | name: "GitLab Personal Access Token", 43 | input: "GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx", 44 | want: "GITLAB_TOKEN=" + strings.Repeat("*", 26), 45 | }, 46 | { 47 | name: "Slack Bot Token", 48 | input: "SLACK_TOKEN=xoxb-123456789-abcdefgh", 49 | want: "SLACK_TOKEN=" + strings.Repeat("*", 23), 50 | }, 51 | { 52 | name: "Anthropic API Key", 53 | input: "ANTHROPIC_API_KEY=sk-ant-api03-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 54 | want: "ANTHROPIC_API_KEY=" + strings.Repeat("*", 57), 55 | }, 56 | { 57 | name: "OpenAI API Key (legacy)", 58 | input: "OPENAI_API_KEY=sk-1234567890_abcdef-1234567890_abcdef-1234567890", 59 | want: "OPENAI_API_KEY=" + strings.Repeat("*", 49), 60 | }, 61 | { 62 | name: "OpenAI API Key (project)", 63 | input: "OPENAI_API_KEY=sk-proj-abcd_1234-efgh_5678-ijkl_9012-mnop", 64 | want: "OPENAI_API_KEY=" + strings.Repeat("*", 42), 65 | }, 66 | { 67 | name: "Supabase Secret Key", 68 | input: "SUPABASE_KEY=sb_secret_1234567890abcdef1234567890abcdef", 69 | want: "SUPABASE_KEY=" + strings.Repeat("*", 42), 70 | }, 71 | { 72 | name: "JWT Token", 73 | input: "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U", 74 | want: "Authorization: Bearer " + strings.Repeat("*", 108), 75 | }, 76 | { 77 | name: "RSA Private Key Header", 78 | input: "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA...", 79 | want: strings.Repeat("*", 31) + "\nMIIEpAIBAAKCAQEA...", 80 | }, 81 | { 82 | name: "No sensitive data", 83 | input: "const message = 'Hello World'", 84 | want: "const message = 'Hello World'", 85 | }, 86 | { 87 | name: "Multiple secrets", 88 | input: "GITHUB=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nAWS=AKIAIOSFODNN7EXAMPLE", 89 | want: "GITHUB=" + strings.Repeat("*", 40) + "\nAWS=" + strings.Repeat("*", 20), 90 | }, 91 | { 92 | name: "Empty string", 93 | input: "", 94 | want: "", 95 | }, 96 | { 97 | name: "Preserves length", 98 | input: "KEY=AKIAIOSFODNN7EXAMPLE", 99 | want: "KEY=" + strings.Repeat("*", 20), 100 | }, 101 | } 102 | 103 | for _, tt := range tests { 104 | t.Run(tt.name, func(t *testing.T) { 105 | got := Mask(tt.input) 106 | assert.Equal(t, tt.want, got) 107 | }) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /docs/update.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/koki-develop/gat/internal/formatters" 10 | "github.com/koki-develop/gat/internal/gat" 11 | "github.com/koki-develop/gat/internal/lexers" 12 | "github.com/koki-develop/gat/internal/styles" 13 | ) 14 | 15 | var ( 16 | src = `package main 17 | 18 | import "fmt" 19 | 20 | func main() { 21 | fmt.Println("hello world") 22 | }` 23 | ) 24 | 25 | func String(s string) *string { 26 | return &s 27 | } 28 | 29 | func main() { 30 | updateLanguages() 31 | updateThemes() 32 | updateFormats() 33 | } 34 | 35 | func Must[T any](t T, err error) T { 36 | if err != nil { 37 | panic(err) 38 | } 39 | return t 40 | } 41 | 42 | func OrPanic(err error) { 43 | if err != nil { 44 | panic(err) 45 | } 46 | } 47 | 48 | func Map[T, I any](ts []T, f func(t T) I) []I { 49 | is := []I{} 50 | for _, t := range ts { 51 | is = append(is, f(t)) 52 | } 53 | return is 54 | } 55 | 56 | func updateLanguages() { 57 | f := Must(os.Create("docs/languages.md")) 58 | defer func() { _ = f.Close() }() 59 | 60 | Must(f.WriteString("# Languages\n\n")) 61 | 62 | Must(f.WriteString("| Language | Aliases |\n")) 63 | Must(f.WriteString("| --- | --- |\n")) 64 | 65 | for _, l := range lexers.List() { 66 | cfg := l.Config() 67 | Must(fmt.Fprintf(f, "| `%s` ", cfg.Name)) 68 | 69 | if len(cfg.Aliases) > 0 { 70 | Must(fmt.Fprintf( 71 | f, 72 | "| %s |", 73 | strings.Join( 74 | Map( 75 | cfg.Aliases, 76 | func(a string) string { return fmt.Sprintf("`%s`", a) }, 77 | ), 78 | ", ", 79 | ), 80 | )) 81 | } else { 82 | Must(f.WriteString("| |")) 83 | } 84 | Must(f.WriteString("\n")) 85 | } 86 | } 87 | 88 | func updateFormats() { 89 | f := Must(os.Create("docs/formats.md")) 90 | defer func() { _ = f.Close() }() 91 | 92 | Must(f.WriteString("# Output Formats\n\n")) 93 | 94 | for _, format := range formatters.List() { 95 | Must(fmt.Fprintf(f, "- [`%s`](#%s)\n", format, format)) 96 | } 97 | Must(f.WriteString("\n")) 98 | 99 | for _, format := range formatters.List() { 100 | Must(fmt.Fprintf(f, "## `%s`\n\n", format)) 101 | 102 | g := Must(gat.New(&gat.Config{ 103 | Format: format, 104 | Theme: "monokai", 105 | Language: "go", 106 | })) 107 | 108 | b := new(bytes.Buffer) 109 | OrPanic(g.Print(b, strings.NewReader(src))) 110 | 111 | Must(fmt.Fprintf(f, "```%s\n", strings.TrimSuffix(format, "-min"))) 112 | if strings.HasPrefix(format, "terminal") { 113 | Must(f.WriteString(strings.Trim(strings.ReplaceAll(fmt.Sprintf("%#v", strings.TrimSpace(b.String())), "\\n", "\n"), "\""))) 114 | } else { 115 | Must(f.WriteString(strings.TrimSpace(b.String()))) 116 | } 117 | Must(f.WriteString("\n")) 118 | Must(f.WriteString("```\n")) 119 | 120 | Must(f.WriteString("\n")) 121 | } 122 | } 123 | 124 | func updateThemes() { 125 | f := Must(os.Create("docs/themes.md")) 126 | defer func() { _ = f.Close() }() 127 | 128 | Must(f.WriteString("# Highlight Themes\n\n")) 129 | 130 | for _, s := range styles.List() { 131 | Must(fmt.Fprintf(f, "- [`%s`](#%s)\n", s, s)) 132 | } 133 | Must(f.WriteString("\n")) 134 | 135 | for _, s := range styles.List() { 136 | Must(fmt.Fprintf(f, "## `%s`\n\n", s)) 137 | 138 | g := Must(gat.New(&gat.Config{ 139 | Format: "svg", 140 | Theme: s, 141 | Language: "go", 142 | })) 143 | 144 | b := new(bytes.Buffer) 145 | OrPanic(g.Print(b, strings.NewReader(src))) 146 | 147 | img := Must(os.Create(fmt.Sprintf("./docs/themes/%s.svg", s))) 148 | defer func() { _ = img.Close() }() 149 | Must(img.Write(b.Bytes())) 150 | 151 | Must(fmt.Fprintf(f, "![%s](./themes/%s.svg)\n\n", s, s)) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Project Overview 6 | 7 | gat is a cat command alternative written in Go that provides syntax highlighting, code formatting, and enhanced display capabilities for terminal output. 8 | 9 | ## Development Commands 10 | 11 | ### Testing 12 | ```bash 13 | # Run all tests 14 | go test ./... 15 | 16 | # Run tests with coverage 17 | go test -coverprofile=coverage.out ./... 18 | ``` 19 | 20 | ### Building 21 | ```bash 22 | # Build the project 23 | go build -o gat 24 | 25 | # Build with version information 26 | go build -ldflags "-X main.version=X.Y.Z" -o gat 27 | ``` 28 | 29 | ### Linting 30 | ```bash 31 | # Run linter (golangci-lint must be installed) 32 | golangci-lint run --verbose ./... 33 | ``` 34 | 35 | ### Release Management 36 | ```bash 37 | # Check GoReleaser configuration 38 | goreleaser check 39 | 40 | # Build release snapshot (for testing) 41 | goreleaser release --snapshot --clean 42 | ``` 43 | 44 | ## Architecture 45 | 46 | ### Core Components 47 | 48 | - **cmd/**: CLI command definitions using Cobra framework 49 | - `root.go`: Main command logic and flag handling 50 | - `flags.go`: Command-line flag definitions 51 | - `version.go`: Version command implementation 52 | 53 | - **internal/gat/**: Core gat functionality 54 | - Main logic for file processing, syntax highlighting, and output formatting 55 | 56 | - **internal/formatters/**: Output format processors 57 | - HTML minification, JSON formatting, SVG optimization 58 | 59 | - **internal/lexers/**: Custom syntax highlighters 60 | - Terraform lexer implementation 61 | 62 | - **internal/prettier/**: Code prettifiers 63 | - Language-specific formatting (CSS, Go, HTML, JSON, XML, YAML) 64 | 65 | - **internal/masker/**: Sensitive information masking 66 | - Masks API keys, tokens, and other secrets in output 67 | 68 | - **internal/styles/**: Theme definitions 69 | - Custom syntax highlighting themes 70 | 71 | - **scripts/**: Build scripts 72 | - Shell completion generation 73 | 74 | - **docs/**: Documentation assets 75 | - Demo GIFs, images, theme previews 76 | 77 | - **tapes/**: VHS tape files for generating demo GIFs 78 | 79 | - **assets/**: Logo files 80 | 81 | ### Key Dependencies 82 | 83 | - **Chroma**: Syntax highlighting engine (200+ language support) 84 | - **Cobra**: CLI framework for command parsing 85 | - **Glamour**: Markdown rendering with terminal styling 86 | - **go-sixel**: Image display in terminal via Sixel protocol 87 | 88 | ### Design Principles 89 | 90 | 1. **Modular Architecture**: Each formatter, lexer, and prettifier is isolated in its own package 91 | 2. **Internal Packages**: Core functionality is kept in `internal/` to prevent external imports 92 | 3. **Resource Management**: Proper cleanup of file handles and resources 93 | 4. **Smart Output Detection**: Automatic color handling based on terminal/pipe detection 94 | 95 | ## Release Process 96 | 97 | The project uses Release Please for automated releases: 98 | 1. PRs are automatically created with changelog updates 99 | 2. Merging a release PR triggers GoReleaser 100 | 3. Binaries are built for multiple platforms and published to GitHub Releases 101 | 4. Homebrew formula is automatically updated 102 | 103 | ## Testing Approach 104 | 105 | - Unit tests focus on formatters, prettifiers, and core functionality 106 | - Test files follow Go convention: `*_test.go` alongside implementation 107 | - Use table-driven tests where appropriate 108 | - Mock external dependencies when needed 109 | 110 | ## Masker Package Patterns 111 | 112 | When adding new API key patterns to `internal/masker/`: 113 | 114 | ### Pattern Ordering 115 | - Place more specific patterns before general ones to avoid false matches 116 | - Example: `sk-ant-` must be before `sk-` to prevent Anthropic keys from matching OpenAI pattern 117 | - Example: AWS Secret Access Key (`[a-zA-Z0-9+/]{40}`) must be last due to its generic pattern 118 | 119 | ### Supported Patterns (in order of application) 120 | - AWS Access Key ID (permanent): `AKIA[0-9A-Z]{16}` 121 | - AWS Access Key ID (temporary/SSO): `ASIA[0-9A-Z]{16}` 122 | - GitHub Tokens: `gh[pousr]_[a-zA-Z0-9]{36,}` 123 | - GitLab PAT: `glpat-[a-zA-Z0-9\-_]{20,}` 124 | - Slack Tokens: `xox[baprs]-[0-9a-zA-Z\-]+` 125 | - Anthropic API Key: `sk-ant-[a-zA-Z0-9\-_]+` 126 | - OpenAI API Key: `sk-(?:proj-)?[a-zA-Z0-9_\-]{20,}` (supports both legacy and project formats) 127 | - Supabase Secret Key: `sb_secret_[a-zA-Z0-9\-_]+` 128 | - JWT Tokens: `eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*` 129 | - Private Key Headers: `-----BEGIN\s+(RSA|DSA|EC|OPENSSH|PGP)\s+PRIVATE\s+KEY-----` 130 | - AWS Secret Access Key: `[a-zA-Z0-9+/]{40}` (must be last due to generic pattern) 131 | 132 | ### Pattern Update Workflow 133 | 1. Add regex pattern to `internal/masker/masker.go` 134 | 2. Add test cases to `internal/masker/masker_test.go` with realistic examples 135 | 3. Run tests: `go test ./internal/masker/...` 136 | 4. Update README.md supported patterns list 137 | 5. Test cases should include special characters (`_`, `-`) where applicable -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 |

5 | 6 |

7 | cat alternative written in Go. 8 |

9 | 10 |

11 | GitHub release (latest by date) 12 | GitHub all releases 13 | GitHub Workflow Status 14 | Go Report Card 15 | LICENSE 16 |

17 | 18 |

19 | 20 |

21 | 22 | ## Contents 23 | 24 | - [Installation](#installation) 25 | - [Usage](#usage) 26 | - [LICENSE](#license) 27 | 28 | ## Installation 29 | 30 | ### Homebrew Tap (Recommended) 31 | 32 | ```console 33 | $ brew install koki-develop/tap/gat 34 | ``` 35 | 36 | ### Homebrew 37 | 38 | ```console 39 | $ brew install gat 40 | ``` 41 | 42 | ### `go install` 43 | 44 | ```console 45 | $ go install github.com/koki-develop/gat@latest 46 | ``` 47 | 48 | ### Releases 49 | 50 | Download the binary from the [releases page](https://github.com/koki-develop/gat/releases/latest). 51 | 52 | ## Usage 53 | 54 | ```console 55 | $ gat --help 56 | cat alternative written in Go. 57 | 58 | Usage: 59 | gat [file]... [flags] 60 | 61 | Flags: 62 | -b, --force-binary force binary output 63 | -c, --force-color force colored output 64 | -f, --format string output format (default "terminal256") 65 | -h, --help help for gat 66 | -l, --lang string language for syntax highlighting 67 | --list-formats print a list of supported output formats 68 | --list-langs print a list of supported languages for syntax highlighting 69 | --list-themes print a list of supported themes with preview 70 | --mask-secrets mask sensitive information (API keys, tokens) 71 | --no-resize do not resize images 72 | -p, --pretty whether to format a content pretty 73 | -M, --render-markdown render markdown 74 | -t, --theme string highlight theme (default "monokai") 75 | -v, --version version for gat 76 | ``` 77 | 78 | ### `-l`, `--lang` 79 | 80 | Explicitly set the language for syntax highlighting. 81 | See [languages.md](./docs/languages.md) for valid languages. 82 | 83 | ### `-f`, `--format` 84 | 85 | Set the output format ( default: `terminal256` ). 86 | Alternatively, it can be set using the `GAT_FORMAT` environment variable. 87 | See [formats.md](./docs/formats.md) for valid formats. 88 | 89 | ### `-t`, `--theme` 90 | 91 | Set the highlight theme ( default: `monokai` ). 92 | Alternatively, it can be set using the `GAT_THEME` environment variable. 93 | See [themes.md](./docs/themes.md) for valid themes. 94 | 95 | ### `-p`, `--pretty` 96 | 97 | Format a content pretty. 98 | For unsupported languages, this flag is ignored. 99 | 100 | ### `--mask-secrets` 101 | 102 | Mask sensitive information such as API keys and tokens. 103 | Matched patterns are replaced with `*` characters of the same length. 104 | 105 | ```console 106 | $ echo 'AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE' | gat --mask-secrets 107 | AWS_ACCESS_KEY_ID=******************** 108 | ``` 109 | 110 | Supported patterns: 111 | - AWS Access Key ID 112 | - AWS Secret Access Key 113 | - GitHub Tokens (`ghp_`, `gho_`, `ghs_`, `ghr_`) 114 | - GitLab Personal Access Tokens 115 | - Slack Tokens 116 | - Anthropic API Keys 117 | - OpenAI API Keys 118 | - Supabase Secret Keys 119 | - JWT Tokens 120 | - Private Key Headers 121 | 122 | ### `-M`, `--render-markdown` 123 | 124 | Render markdown documents. 125 | 126 | ![](./docs/markdown.png) 127 | 128 | ### `-c`, `--force-color` 129 | 130 | `gat` disables colored output when piped to another program. 131 | Settings the `--force-color` forces colored output to be enabled. 132 | This is useful, for example, when used in combination with the `less -R` command. 133 | 134 | ![](/docs/gess.gif) 135 | 136 | It is also useful to declare the following function to allow `gat` to be used with a pager. 137 | 138 | ```sh 139 | function gess() { 140 | gat --force-color "$@" | less -R 141 | } 142 | ``` 143 | 144 | ### Print Image 145 | 146 | If your terminal supports Sixel, you can print images. 147 | 148 | ![](./docs/image.png) 149 | 150 | Supported image formats include: 151 | 152 | - JPEG 153 | - PNG 154 | - GIF (animation not supported) 155 | 156 | ## :coffee: Buy me a coffee 157 | 158 | If you like this project, please consider buying me a coffee. 159 | 160 | Buy Me A Coffee 161 | 162 | ## LICENSE 163 | 164 | [MIT](./LICENSE) 165 | -------------------------------------------------------------------------------- /docs/themes.md: -------------------------------------------------------------------------------- 1 | # Highlight Themes 2 | 3 | - [`RPGLE`](#RPGLE) 4 | - [`abap`](#abap) 5 | - [`algol`](#algol) 6 | - [`algol_nu`](#algol_nu) 7 | - [`arduino`](#arduino) 8 | - [`ashen`](#ashen) 9 | - [`aura-theme-dark`](#aura-theme-dark) 10 | - [`aura-theme-dark-soft`](#aura-theme-dark-soft) 11 | - [`autumn`](#autumn) 12 | - [`average`](#average) 13 | - [`base16-snazzy`](#base16-snazzy) 14 | - [`borland`](#borland) 15 | - [`bw`](#bw) 16 | - [`catppuccin-frappe`](#catppuccin-frappe) 17 | - [`catppuccin-latte`](#catppuccin-latte) 18 | - [`catppuccin-macchiato`](#catppuccin-macchiato) 19 | - [`catppuccin-mocha`](#catppuccin-mocha) 20 | - [`colorful`](#colorful) 21 | - [`doom-one`](#doom-one) 22 | - [`doom-one2`](#doom-one2) 23 | - [`dracula`](#dracula) 24 | - [`emacs`](#emacs) 25 | - [`evergarden`](#evergarden) 26 | - [`friendly`](#friendly) 27 | - [`fruity`](#fruity) 28 | - [`github`](#github) 29 | - [`github-dark`](#github-dark) 30 | - [`gruvbox`](#gruvbox) 31 | - [`gruvbox-light`](#gruvbox-light) 32 | - [`hr_high_contrast`](#hr_high_contrast) 33 | - [`hrdark`](#hrdark) 34 | - [`igor`](#igor) 35 | - [`lovelace`](#lovelace) 36 | - [`manni`](#manni) 37 | - [`modus-operandi`](#modus-operandi) 38 | - [`modus-vivendi`](#modus-vivendi) 39 | - [`monokai`](#monokai) 40 | - [`monokailight`](#monokailight) 41 | - [`murphy`](#murphy) 42 | - [`native`](#native) 43 | - [`noop`](#noop) 44 | - [`nord`](#nord) 45 | - [`nordic`](#nordic) 46 | - [`onedark`](#onedark) 47 | - [`onesenterprise`](#onesenterprise) 48 | - [`paraiso-dark`](#paraiso-dark) 49 | - [`paraiso-light`](#paraiso-light) 50 | - [`pastie`](#pastie) 51 | - [`perldoc`](#perldoc) 52 | - [`pygments`](#pygments) 53 | - [`rainbow_dash`](#rainbow_dash) 54 | - [`rose-pine`](#rose-pine) 55 | - [`rose-pine-dawn`](#rose-pine-dawn) 56 | - [`rose-pine-moon`](#rose-pine-moon) 57 | - [`rrt`](#rrt) 58 | - [`solarized-dark`](#solarized-dark) 59 | - [`solarized-dark256`](#solarized-dark256) 60 | - [`solarized-light`](#solarized-light) 61 | - [`swapoff`](#swapoff) 62 | - [`tango`](#tango) 63 | - [`tokyonight-day`](#tokyonight-day) 64 | - [`tokyonight-moon`](#tokyonight-moon) 65 | - [`tokyonight-night`](#tokyonight-night) 66 | - [`tokyonight-storm`](#tokyonight-storm) 67 | - [`trac`](#trac) 68 | - [`vim`](#vim) 69 | - [`vs`](#vs) 70 | - [`vulcan`](#vulcan) 71 | - [`witchhazel`](#witchhazel) 72 | - [`xcode`](#xcode) 73 | - [`xcode-dark`](#xcode-dark) 74 | 75 | ## `RPGLE` 76 | 77 | ![RPGLE](./themes/RPGLE.svg) 78 | 79 | ## `abap` 80 | 81 | ![abap](./themes/abap.svg) 82 | 83 | ## `algol` 84 | 85 | ![algol](./themes/algol.svg) 86 | 87 | ## `algol_nu` 88 | 89 | ![algol_nu](./themes/algol_nu.svg) 90 | 91 | ## `arduino` 92 | 93 | ![arduino](./themes/arduino.svg) 94 | 95 | ## `ashen` 96 | 97 | ![ashen](./themes/ashen.svg) 98 | 99 | ## `aura-theme-dark` 100 | 101 | ![aura-theme-dark](./themes/aura-theme-dark.svg) 102 | 103 | ## `aura-theme-dark-soft` 104 | 105 | ![aura-theme-dark-soft](./themes/aura-theme-dark-soft.svg) 106 | 107 | ## `autumn` 108 | 109 | ![autumn](./themes/autumn.svg) 110 | 111 | ## `average` 112 | 113 | ![average](./themes/average.svg) 114 | 115 | ## `base16-snazzy` 116 | 117 | ![base16-snazzy](./themes/base16-snazzy.svg) 118 | 119 | ## `borland` 120 | 121 | ![borland](./themes/borland.svg) 122 | 123 | ## `bw` 124 | 125 | ![bw](./themes/bw.svg) 126 | 127 | ## `catppuccin-frappe` 128 | 129 | ![catppuccin-frappe](./themes/catppuccin-frappe.svg) 130 | 131 | ## `catppuccin-latte` 132 | 133 | ![catppuccin-latte](./themes/catppuccin-latte.svg) 134 | 135 | ## `catppuccin-macchiato` 136 | 137 | ![catppuccin-macchiato](./themes/catppuccin-macchiato.svg) 138 | 139 | ## `catppuccin-mocha` 140 | 141 | ![catppuccin-mocha](./themes/catppuccin-mocha.svg) 142 | 143 | ## `colorful` 144 | 145 | ![colorful](./themes/colorful.svg) 146 | 147 | ## `doom-one` 148 | 149 | ![doom-one](./themes/doom-one.svg) 150 | 151 | ## `doom-one2` 152 | 153 | ![doom-one2](./themes/doom-one2.svg) 154 | 155 | ## `dracula` 156 | 157 | ![dracula](./themes/dracula.svg) 158 | 159 | ## `emacs` 160 | 161 | ![emacs](./themes/emacs.svg) 162 | 163 | ## `evergarden` 164 | 165 | ![evergarden](./themes/evergarden.svg) 166 | 167 | ## `friendly` 168 | 169 | ![friendly](./themes/friendly.svg) 170 | 171 | ## `fruity` 172 | 173 | ![fruity](./themes/fruity.svg) 174 | 175 | ## `github` 176 | 177 | ![github](./themes/github.svg) 178 | 179 | ## `github-dark` 180 | 181 | ![github-dark](./themes/github-dark.svg) 182 | 183 | ## `gruvbox` 184 | 185 | ![gruvbox](./themes/gruvbox.svg) 186 | 187 | ## `gruvbox-light` 188 | 189 | ![gruvbox-light](./themes/gruvbox-light.svg) 190 | 191 | ## `hr_high_contrast` 192 | 193 | ![hr_high_contrast](./themes/hr_high_contrast.svg) 194 | 195 | ## `hrdark` 196 | 197 | ![hrdark](./themes/hrdark.svg) 198 | 199 | ## `igor` 200 | 201 | ![igor](./themes/igor.svg) 202 | 203 | ## `lovelace` 204 | 205 | ![lovelace](./themes/lovelace.svg) 206 | 207 | ## `manni` 208 | 209 | ![manni](./themes/manni.svg) 210 | 211 | ## `modus-operandi` 212 | 213 | ![modus-operandi](./themes/modus-operandi.svg) 214 | 215 | ## `modus-vivendi` 216 | 217 | ![modus-vivendi](./themes/modus-vivendi.svg) 218 | 219 | ## `monokai` 220 | 221 | ![monokai](./themes/monokai.svg) 222 | 223 | ## `monokailight` 224 | 225 | ![monokailight](./themes/monokailight.svg) 226 | 227 | ## `murphy` 228 | 229 | ![murphy](./themes/murphy.svg) 230 | 231 | ## `native` 232 | 233 | ![native](./themes/native.svg) 234 | 235 | ## `noop` 236 | 237 | ![noop](./themes/noop.svg) 238 | 239 | ## `nord` 240 | 241 | ![nord](./themes/nord.svg) 242 | 243 | ## `nordic` 244 | 245 | ![nordic](./themes/nordic.svg) 246 | 247 | ## `onedark` 248 | 249 | ![onedark](./themes/onedark.svg) 250 | 251 | ## `onesenterprise` 252 | 253 | ![onesenterprise](./themes/onesenterprise.svg) 254 | 255 | ## `paraiso-dark` 256 | 257 | ![paraiso-dark](./themes/paraiso-dark.svg) 258 | 259 | ## `paraiso-light` 260 | 261 | ![paraiso-light](./themes/paraiso-light.svg) 262 | 263 | ## `pastie` 264 | 265 | ![pastie](./themes/pastie.svg) 266 | 267 | ## `perldoc` 268 | 269 | ![perldoc](./themes/perldoc.svg) 270 | 271 | ## `pygments` 272 | 273 | ![pygments](./themes/pygments.svg) 274 | 275 | ## `rainbow_dash` 276 | 277 | ![rainbow_dash](./themes/rainbow_dash.svg) 278 | 279 | ## `rose-pine` 280 | 281 | ![rose-pine](./themes/rose-pine.svg) 282 | 283 | ## `rose-pine-dawn` 284 | 285 | ![rose-pine-dawn](./themes/rose-pine-dawn.svg) 286 | 287 | ## `rose-pine-moon` 288 | 289 | ![rose-pine-moon](./themes/rose-pine-moon.svg) 290 | 291 | ## `rrt` 292 | 293 | ![rrt](./themes/rrt.svg) 294 | 295 | ## `solarized-dark` 296 | 297 | ![solarized-dark](./themes/solarized-dark.svg) 298 | 299 | ## `solarized-dark256` 300 | 301 | ![solarized-dark256](./themes/solarized-dark256.svg) 302 | 303 | ## `solarized-light` 304 | 305 | ![solarized-light](./themes/solarized-light.svg) 306 | 307 | ## `swapoff` 308 | 309 | ![swapoff](./themes/swapoff.svg) 310 | 311 | ## `tango` 312 | 313 | ![tango](./themes/tango.svg) 314 | 315 | ## `tokyonight-day` 316 | 317 | ![tokyonight-day](./themes/tokyonight-day.svg) 318 | 319 | ## `tokyonight-moon` 320 | 321 | ![tokyonight-moon](./themes/tokyonight-moon.svg) 322 | 323 | ## `tokyonight-night` 324 | 325 | ![tokyonight-night](./themes/tokyonight-night.svg) 326 | 327 | ## `tokyonight-storm` 328 | 329 | ![tokyonight-storm](./themes/tokyonight-storm.svg) 330 | 331 | ## `trac` 332 | 333 | ![trac](./themes/trac.svg) 334 | 335 | ## `vim` 336 | 337 | ![vim](./themes/vim.svg) 338 | 339 | ## `vs` 340 | 341 | ![vs](./themes/vs.svg) 342 | 343 | ## `vulcan` 344 | 345 | ![vulcan](./themes/vulcan.svg) 346 | 347 | ## `witchhazel` 348 | 349 | ![witchhazel](./themes/witchhazel.svg) 350 | 351 | ## `xcode` 352 | 353 | ![xcode](./themes/xcode.svg) 354 | 355 | ## `xcode-dark` 356 | 357 | ![xcode-dark](./themes/xcode-dark.svg) 358 | 359 | -------------------------------------------------------------------------------- /internal/gat/gat.go: -------------------------------------------------------------------------------- 1 | package gat 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "compress/gzip" 7 | "fmt" 8 | "image" 9 | _ "image/gif" 10 | _ "image/jpeg" 11 | _ "image/png" 12 | "io" 13 | "net/http" 14 | "strings" 15 | 16 | "github.com/alecthomas/chroma/v2" 17 | "github.com/charmbracelet/glamour" 18 | "github.com/koki-develop/gat/internal/formatters" 19 | "github.com/koki-develop/gat/internal/lexers" 20 | "github.com/koki-develop/gat/internal/masker" 21 | "github.com/koki-develop/gat/internal/prettier" 22 | "github.com/koki-develop/gat/internal/styles" 23 | "github.com/mattn/go-sixel" 24 | "golang.org/x/image/draw" 25 | ) 26 | 27 | type Config struct { 28 | Language string 29 | Format string 30 | Theme string 31 | RenderMarkdown bool 32 | ForceBinary bool 33 | NoResize bool 34 | } 35 | 36 | type Gat struct { 37 | explicitLexer chroma.Lexer 38 | formatter chroma.Formatter 39 | style *chroma.Style 40 | renderMarkdown bool 41 | forceBinary bool 42 | noResize bool 43 | } 44 | 45 | func New(cfg *Config) (*Gat, error) { 46 | g := &Gat{ 47 | renderMarkdown: cfg.RenderMarkdown, 48 | forceBinary: cfg.ForceBinary, 49 | noResize: cfg.NoResize, 50 | } 51 | 52 | // lexer 53 | if cfg.Language != "" { 54 | l, err := lexers.Get(lexers.WithLanguage(cfg.Language)) 55 | if err != nil { 56 | return nil, err 57 | } 58 | g.explicitLexer = l 59 | } 60 | 61 | // formatter 62 | f, ok := formatters.Get(cfg.Format) 63 | if !ok { 64 | return nil, fmt.Errorf("unknown format: %s", cfg.Format) 65 | } 66 | g.formatter = f 67 | 68 | // style 69 | s, ok := styles.Get(cfg.Theme) 70 | if !ok { 71 | return nil, fmt.Errorf("unknown theme: %s", cfg.Theme) 72 | } 73 | g.style = s 74 | 75 | return g, nil 76 | } 77 | 78 | type printOption struct { 79 | Pretty bool 80 | Mask bool 81 | Filename string 82 | } 83 | 84 | type PrintOption func(*printOption) 85 | 86 | func WithPretty(p bool) PrintOption { 87 | return func(o *printOption) { 88 | o.Pretty = p 89 | } 90 | } 91 | 92 | func WithFilename(name string) PrintOption { 93 | return func(o *printOption) { 94 | o.Filename = name 95 | } 96 | } 97 | 98 | func WithMask(m bool) PrintOption { 99 | return func(o *printOption) { 100 | o.Mask = m 101 | } 102 | } 103 | 104 | func (g *Gat) Print(w io.Writer, r io.Reader, opts ...PrintOption) error { 105 | // parse options 106 | opt := &printOption{} 107 | for _, o := range opts { 108 | o(opt) 109 | } 110 | 111 | br := bufio.NewReader(r) 112 | head, err := br.Peek(1024) 113 | if err != nil && err != io.EOF { 114 | return err 115 | } 116 | 117 | // detect content type 118 | contentType := http.DetectContentType(head) 119 | 120 | // print image 121 | if strings.HasPrefix(contentType, "image/") && !g.forceBinary { 122 | if err := g.printImage(w, br); err == nil { 123 | return nil 124 | } 125 | } 126 | 127 | // read source 128 | var src string 129 | switch contentType { 130 | case "application/x-gzip": 131 | s, err := g.readGzip(br) 132 | if err != nil { 133 | return err 134 | } 135 | src = s 136 | default: 137 | if isBinary(head) { 138 | if g.forceBinary { 139 | if _, err := br.WriteTo(w); err != nil { 140 | return err 141 | } 142 | } else { 143 | if _, err := w.Write([]byte("+----------------------------------------------------------------------------+\n| NOTE: This is a binary file. To force output, use the --force-binary flag. |\n+----------------------------------------------------------------------------+\n")); err != nil { 144 | return err 145 | } 146 | } 147 | return nil 148 | } 149 | 150 | buf := new(bytes.Buffer) 151 | if _, err := io.Copy(buf, br); err != nil { 152 | return err 153 | } 154 | src = buf.String() 155 | } 156 | 157 | // analyse lexer 158 | lexer := g.explicitLexer 159 | if lexer == nil { 160 | l, err := lexers.Get(lexers.WithFilename(opt.Filename), lexers.WithSource(src)) 161 | if err != nil { 162 | return err 163 | } 164 | lexer = l 165 | } 166 | 167 | if g.renderMarkdown && lexer.Config().Name == "markdown" { 168 | r, err := glamour.NewTermRenderer( 169 | glamour.WithAutoStyle(), 170 | glamour.WithWordWrap(-1), 171 | ) 172 | if err != nil { 173 | return err 174 | } 175 | defer func() { _ = r.Close() }() 176 | 177 | s, err := r.Render(src) 178 | if err != nil { 179 | return err 180 | } 181 | if _, err := w.Write([]byte(s)); err != nil { 182 | return err 183 | } 184 | return nil 185 | } 186 | 187 | // pretty code 188 | if opt.Pretty { 189 | p, ok := prettier.Get(lexer.Config().Name) 190 | if ok { 191 | s, err := p.Pretty(src) 192 | if err == nil { 193 | src = s 194 | } 195 | } 196 | } 197 | 198 | // mask sensitive information 199 | if opt.Mask { 200 | src = masker.Mask(src) 201 | } 202 | 203 | // print 204 | it, err := lexer.Tokenise(nil, src) 205 | if err != nil { 206 | return err 207 | } 208 | if err := g.formatter.Format(w, g.style, it); err != nil { 209 | return err 210 | } 211 | 212 | return nil 213 | } 214 | 215 | func (g *Gat) printImage(w io.Writer, r io.Reader) error { 216 | maxEdge := 1800 217 | 218 | img, _, err := image.Decode(r) 219 | if err != nil { 220 | return err 221 | } 222 | imgWidth, imgHeight := img.Bounds().Dx(), img.Bounds().Dy() 223 | 224 | if g.noResize || (imgWidth <= maxEdge && imgHeight <= maxEdge) { 225 | if err := sixel.NewEncoder(w).Encode(img); err != nil { 226 | return err 227 | } 228 | } else { 229 | var dstWidth, dstHeight int 230 | aspectRatio := float64(imgHeight) / float64(imgWidth) 231 | if imgWidth > imgHeight { 232 | dstWidth, dstHeight = maxEdge, int(float64(maxEdge)*aspectRatio) 233 | } else { 234 | dstWidth, dstHeight = int(float64(maxEdge)/aspectRatio), maxEdge 235 | } 236 | 237 | dst := image.NewRGBA(image.Rect(0, 0, dstWidth, dstHeight)) 238 | draw.ApproxBiLinear.Scale(dst, dst.Bounds(), img, img.Bounds(), draw.Src, nil) 239 | if err := sixel.NewEncoder(w).Encode(dst); err != nil { 240 | return err 241 | } 242 | } 243 | 244 | if _, err := w.Write([]byte{'\n'}); err != nil { 245 | return err 246 | } 247 | 248 | return nil 249 | } 250 | 251 | func (*Gat) readGzip(r io.Reader) (string, error) { 252 | buf := new(bytes.Buffer) 253 | gz, err := gzip.NewReader(r) 254 | if err != nil { 255 | return "", err 256 | } 257 | defer func() { _ = gz.Close() }() 258 | 259 | if _, err := io.Copy(buf, gz); err != nil { 260 | return "", err 261 | } 262 | 263 | return buf.String(), nil 264 | } 265 | 266 | func isBinary(data []byte) bool { 267 | if len(data) < 1024 { 268 | return bytes.IndexByte(data, 0) != -1 269 | } 270 | return bytes.IndexByte(data[:1024], 0) != -1 271 | } 272 | 273 | func PrintThemes(w io.Writer, withColor bool) error { 274 | if withColor { 275 | src := `package main 276 | 277 | import "fmt" 278 | 279 | func main() { 280 | fmt.Println("hello world") 281 | }` 282 | 283 | for _, t := range styles.List() { 284 | if _, err := fmt.Fprintf(w, "\x1b[1m%s\x1b[0m\n\n", t); err != nil { 285 | return err 286 | } 287 | 288 | g, err := New(&Config{ 289 | Language: "go", 290 | Theme: t, 291 | Format: "terminal256", 292 | }) 293 | if err != nil { 294 | return err 295 | } 296 | 297 | buf := new(bytes.Buffer) 298 | if err := g.Print(buf, strings.NewReader(src)); err != nil { 299 | return err 300 | } 301 | 302 | // indent source 303 | sc := bufio.NewScanner(buf) 304 | for sc.Scan() { 305 | if _, err := fmt.Fprintf(w, "\t%s\n", sc.Text()); err != nil { 306 | return err 307 | } 308 | } 309 | 310 | if _, err := fmt.Fprintln(w); err != nil { 311 | return err 312 | } 313 | } 314 | } else { 315 | for _, t := range styles.List() { 316 | if _, err := fmt.Fprintln(w, t); err != nil { 317 | return err 318 | } 319 | } 320 | } 321 | 322 | return nil 323 | } 324 | -------------------------------------------------------------------------------- /assets/logo_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 11 | 14 | 17 | 20 | 23 | 26 | 27 | 28 | 31 | 32 | 33 | 36 | 39 | 42 | 43 | 44 | 45 | 46 | 48 | 51 | 54 | 57 | 58 | -------------------------------------------------------------------------------- /assets/logo_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 11 | 14 | 17 | 20 | 23 | 26 | 27 | 28 | 31 | 32 | 33 | 36 | 39 | 42 | 43 | 44 | 45 | 46 | 48 | 51 | 54 | 57 | 58 | -------------------------------------------------------------------------------- /docs/languages.md: -------------------------------------------------------------------------------- 1 | # Languages 2 | 3 | | Language | Aliases | 4 | | --- | --- | 5 | | `ABAP` | `abap` | 6 | | `ABNF` | `abnf` | 7 | | `ActionScript` | `as`, `actionscript` | 8 | | `ActionScript 3` | `as3`, `actionscript3` | 9 | | `Ada` | `ada`, `ada95`, `ada2005` | 10 | | `Agda` | `agda` | 11 | | `AL` | `al` | 12 | | `Alloy` | `alloy` | 13 | | `Angular2` | `ng2` | 14 | | `ANTLR` | `antlr` | 15 | | `ApacheConf` | `apacheconf`, `aconf`, `apache` | 16 | | `APL` | `apl` | 17 | | `AppleScript` | `applescript` | 18 | | `ArangoDB AQL` | `aql` | 19 | | `Arduino` | `arduino` | 20 | | `ArmAsm` | `armasm` | 21 | | `ATL` | `atl` | 22 | | `AutoHotkey` | `autohotkey`, `ahk` | 23 | | `AutoIt` | `autoit` | 24 | | `Awk` | `awk`, `gawk`, `mawk`, `nawk` | 25 | | `Ballerina` | `ballerina` | 26 | | `Bash` | `bash`, `sh`, `ksh`, `zsh`, `shell` | 27 | | `Bash Session` | `bash-session`, `console`, `shell-session` | 28 | | `Batchfile` | `bat`, `batch`, `dosbatch`, `winbatch` | 29 | | `Beef` | `beef` | 30 | | `BibTeX` | `bib`, `bibtex` | 31 | | `Bicep` | `bicep` | 32 | | `BlitzBasic` | `blitzbasic`, `b3d`, `bplus` | 33 | | `BNF` | `bnf` | 34 | | `BQN` | `bqn` | 35 | | `Brainfuck` | `brainfuck`, `bf` | 36 | | `C#` | `csharp`, `c#` | 37 | | `C++` | `cpp`, `c++` | 38 | | `C` | `c` | 39 | | `C3` | `c3` | 40 | | `Cap'n Proto` | `capnp` | 41 | | `Cassandra CQL` | `cassandra`, `cql` | 42 | | `Ceylon` | `ceylon` | 43 | | `CFEngine3` | `cfengine3`, `cf3` | 44 | | `cfstatement` | `cfs` | 45 | | `ChaiScript` | `chai`, `chaiscript` | 46 | | `Chapel` | `chapel`, `chpl` | 47 | | `Cheetah` | `cheetah`, `spitfire` | 48 | | `Clojure` | `clojure`, `clj`, `edn` | 49 | | `CMake` | `cmake` | 50 | | `COBOL` | `cobol` | 51 | | `CoffeeScript` | `coffee-script`, `coffeescript`, `coffee` | 52 | | `Common Lisp` | `common-lisp`, `cl`, `lisp` | 53 | | `Coq` | `coq` | 54 | | `Core` | `core` | 55 | | `Crystal` | `cr`, `crystal` | 56 | | `CSS` | `css` | 57 | | `CSV` | `csv` | 58 | | `CUE` | `cue` | 59 | | `Cython` | `cython`, `pyx`, `pyrex` | 60 | | `D` | `d` | 61 | | `Dart` | `dart` | 62 | | `Dax` | `dax` | 63 | | `Desktop file` | `desktop`, `desktop_entry` | 64 | | `Devicetree` | `devicetree`, `dts` | 65 | | `Diff` | `diff`, `udiff` | 66 | | `Django/Jinja` | `django`, `jinja` | 67 | | `dns` | `zone`, `bind` | 68 | | `Docker` | `docker`, `dockerfile`, `containerfile` | 69 | | `DTD` | `dtd` | 70 | | `Dylan` | `dylan` | 71 | | `EBNF` | `ebnf` | 72 | | `Elixir` | `elixir`, `ex`, `exs` | 73 | | `Elm` | `elm` | 74 | | `EmacsLisp` | `emacs`, `elisp`, `emacs-lisp` | 75 | | `Erlang` | `erlang` | 76 | | `Factor` | `factor` | 77 | | `Fennel` | `fennel`, `fnl` | 78 | | `Fish` | `fish`, `fishshell` | 79 | | `Forth` | `forth` | 80 | | `Fortran` | `fortran`, `f90` | 81 | | `FortranFixed` | `fortranfixed` | 82 | | `FSharp` | `fsharp` | 83 | | `GAS` | `gas`, `asm` | 84 | | `GDScript` | `gdscript`, `gd` | 85 | | `GDScript3` | `gdscript3`, `gd3` | 86 | | `Gherkin` | `cucumber`, `Cucumber`, `gherkin`, `Gherkin` | 87 | | `Gleam` | `gleam` | 88 | | `GLSL` | `glsl` | 89 | | `Gnuplot` | `gnuplot` | 90 | | `Go Template` | `go-template` | 91 | | `GraphQL` | `graphql`, `graphqls`, `gql` | 92 | | `Groff` | `groff`, `nroff`, `man` | 93 | | `Groovy` | `groovy` | 94 | | `Handlebars` | `handlebars`, `hbs` | 95 | | `Hare` | `hare` | 96 | | `Haskell` | `haskell`, `hs` | 97 | | `HCL` | `hcl` | 98 | | `Hexdump` | `hexdump` | 99 | | `HLB` | `hlb` | 100 | | `HLSL` | `hlsl` | 101 | | `HolyC` | `holyc` | 102 | | `HTML` | `html` | 103 | | `Hy` | `hylang` | 104 | | `Idris` | `idris`, `idr` | 105 | | `Igor` | `igor`, `igorpro` | 106 | | `INI` | `ini`, `cfg`, `dosini` | 107 | | `Io` | `io` | 108 | | `ISCdhcpd` | `iscdhcpd` | 109 | | `J` | `j` | 110 | | `Janet` | `janet` | 111 | | `Java` | `java` | 112 | | `JavaScript` | `js`, `javascript` | 113 | | `JSON` | `json` | 114 | | `JSONata` | `jsonata` | 115 | | `Jsonnet` | `jsonnet` | 116 | | `Julia` | `julia`, `jl` | 117 | | `Jungle` | `jungle` | 118 | | `Kakoune` | `kak`, `kakoune`, `kakrc`, `kakscript` | 119 | | `Kotlin` | `kotlin` | 120 | | `Lean4` | `lean4`, `lean` | 121 | | `Lighttpd configuration file` | `lighty`, `lighttpd` | 122 | | `LLVM` | `llvm` | 123 | | `lox` | | 124 | | `Lua` | `lua`, `luau` | 125 | | `Makefile` | `make`, `makefile`, `mf`, `bsdmake` | 126 | | `Mako` | `mako` | 127 | | `Mason` | `mason` | 128 | | `Materialize SQL dialect` | `materialize`, `mzsql` | 129 | | `Mathematica` | `mathematica`, `mma`, `nb` | 130 | | `Matlab` | `matlab` | 131 | | `MCFunction` | `mcfunction`, `mcf` | 132 | | `Meson` | `meson`, `meson.build` | 133 | | `Metal` | `metal` | 134 | | `MiniZinc` | `minizinc`, `MZN`, `mzn` | 135 | | `MLIR` | `mlir` | 136 | | `Modelica` | `modelica` | 137 | | `Modula-2` | `modula2`, `m2` | 138 | | `Mojo` | `mojo`, `🔥` | 139 | | `MonkeyC` | `monkeyc` | 140 | | `MoonScript` | `moonscript`, `moon` | 141 | | `MorrowindScript` | `morrowind`, `mwscript` | 142 | | `Myghty` | `myghty` | 143 | | `MySQL` | `mysql`, `mariadb` | 144 | | `NASM` | `nasm` | 145 | | `Natural` | `natural` | 146 | | `NDISASM` | `ndisasm` | 147 | | `Newspeak` | `newspeak` | 148 | | `Nginx configuration file` | `nginx` | 149 | | `Nim` | `nim`, `nimrod` | 150 | | `Nix` | `nixos`, `nix` | 151 | | `NSIS` | `nsis`, `nsi`, `nsh` | 152 | | `Nu` | `nu` | 153 | | `Objective-C` | `objective-c`, `objectivec`, `obj-c`, `objc` | 154 | | `ObjectPascal` | `objectpascal` | 155 | | `OCaml` | `ocaml` | 156 | | `Octave` | `octave` | 157 | | `Odin` | `odin` | 158 | | `OnesEnterprise` | `ones`, `onesenterprise`, `1S`, `1S:Enterprise` | 159 | | `OpenEdge ABL` | `openedge`, `abl`, `progress`, `openedgeabl` | 160 | | `OpenSCAD` | `openscad` | 161 | | `Org Mode` | `org`, `orgmode` | 162 | | `PacmanConf` | `pacmanconf` | 163 | | `Perl` | `perl`, `pl` | 164 | | `PHP` | `php`, `php3`, `php4`, `php5` | 165 | | `Pig` | `pig` | 166 | | `PkgConfig` | `pkgconfig` | 167 | | `PL/pgSQL` | `plpgsql` | 168 | | `plaintext` | `text`, `plain`, `no-highlight` | 169 | | `Plutus Core` | `plutus-core`, `plc` | 170 | | `Pony` | `pony` | 171 | | `PostgreSQL SQL dialect` | `postgresql`, `postgres` | 172 | | `PostScript` | `postscript`, `postscr` | 173 | | `POVRay` | `pov` | 174 | | `PowerQuery` | `powerquery`, `pq` | 175 | | `PowerShell` | `powershell`, `posh`, `ps1`, `psm1`, `psd1`, `pwsh` | 176 | | `Prolog` | `prolog` | 177 | | `Promela` | `promela` | 178 | | `PromQL` | `promql` | 179 | | `properties` | `java-properties` | 180 | | `Protocol Buffer` | `protobuf`, `proto` | 181 | | `PRQL` | `prql` | 182 | | `PSL` | `psl` | 183 | | `Puppet` | `puppet` | 184 | | `Python` | `python`, `py`, `sage`, `python3`, `py3`, `starlark` | 185 | | `Python 2` | `python2`, `py2` | 186 | | `QBasic` | `qbasic`, `basic` | 187 | | `QML` | `qml`, `qbs` | 188 | | `R` | `splus`, `s`, `r` | 189 | | `Racket` | `racket`, `rkt` | 190 | | `Ragel` | `ragel` | 191 | | `react` | `jsx`, `react` | 192 | | `ReasonML` | `reason`, `reasonml` | 193 | | `reg` | `registry` | 194 | | `Rego` | `rego` | 195 | | `Rexx` | `rexx`, `arexx` | 196 | | `RGBDS Assembly` | `rgbasm` | 197 | | `Ring` | `ring` | 198 | | `RPGLE` | `SQLRPGLE`, `RPG IV` | 199 | | `RPMSpec` | `spec` | 200 | | `Ruby` | `rb`, `ruby`, `duby` | 201 | | `Rust` | `rust`, `rs` | 202 | | `SAS` | `sas` | 203 | | `Sass` | `sass` | 204 | | `Scala` | `scala` | 205 | | `Scheme` | `scheme`, `scm` | 206 | | `Scilab` | `scilab` | 207 | | `SCSS` | `scss` | 208 | | `Sed` | `sed`, `gsed`, `ssed` | 209 | | `Sieve` | `sieve` | 210 | | `Smali` | `smali` | 211 | | `Smalltalk` | `smalltalk`, `squeak`, `st` | 212 | | `Smarty` | `smarty` | 213 | | `SNBT` | `snbt` | 214 | | `Snobol` | `snobol` | 215 | | `Solidity` | `sol`, `solidity` | 216 | | `SourcePawn` | `sp` | 217 | | `SPARQL` | `sparql` | 218 | | `SQL` | `sql` | 219 | | `SquidConf` | `squidconf`, `squid.conf`, `squid` | 220 | | `Standard ML` | `sml` | 221 | | `stas` | | 222 | | `Stylus` | `stylus` | 223 | | `Swift` | `swift` | 224 | | `SYSTEMD` | `systemd` | 225 | | `systemverilog` | `systemverilog`, `sv` | 226 | | `TableGen` | `tablegen` | 227 | | `Tal` | `tal`, `uxntal` | 228 | | `TASM` | `tasm` | 229 | | `Tcl` | `tcl` | 230 | | `Tcsh` | `tcsh`, `csh` | 231 | | `Termcap` | `termcap` | 232 | | `Terminfo` | `terminfo` | 233 | | `Terraform` | `terraform`, `tf`, `hcl` | 234 | | `TeX` | `tex`, `latex` | 235 | | `Thrift` | `thrift` | 236 | | `TOML` | `toml` | 237 | | `TradingView` | `tradingview`, `tv` | 238 | | `Transact-SQL` | `tsql`, `t-sql` | 239 | | `Turing` | `turing` | 240 | | `Turtle` | `turtle` | 241 | | `Twig` | `twig` | 242 | | `Protocol Buffer Text Format` | `txtpb` | 243 | | `TypeScript` | `ts`, `tsx`, `typescript` | 244 | | `TypoScript` | `typoscript` | 245 | | `TypoScriptCssData` | `typoscriptcssdata` | 246 | | `TypoScriptHtmlData` | `typoscripthtmldata` | 247 | | `Typst` | `typst` | 248 | | `ucode` | | 249 | | `V` | `v`, `vlang` | 250 | | `V shell` | `vsh`, `vshell` | 251 | | `Vala` | `vala`, `vapi` | 252 | | `VB.net` | `vb.net`, `vbnet` | 253 | | `verilog` | `verilog`, `v` | 254 | | `VHDL` | `vhdl` | 255 | | `VHS` | `vhs`, `tape`, `cassette` | 256 | | `VimL` | `vim` | 257 | | `vue` | `vue`, `vuejs` | 258 | | `WebAssembly Text Format` | `wast`, `wat` | 259 | | `WDTE` | | 260 | | `WebGPU Shading Language` | `wgsl` | 261 | | `WebVTT` | `vtt` | 262 | | `Whiley` | `whiley` | 263 | | `XML` | `xml` | 264 | | `Xorg` | `xorg.conf` | 265 | | `YAML` | `yaml` | 266 | | `YANG` | `yang` | 267 | | `Z80 Assembly` | `z80` | 268 | | `Zed` | `zed` | 269 | | `Zig` | `zig` | 270 | | `Caddyfile` | `caddyfile`, `caddy` | 271 | | `Caddyfile Directives` | `caddyfile-directives`, `caddyfile-d`, `caddy-d` | 272 | | `Gemtext` | `gemtext`, `gmi`, `gmni`, `gemini` | 273 | | `Genshi Text` | `genshitext` | 274 | | `Genshi HTML` | `html+genshi`, `html+kid` | 275 | | `Genshi` | `genshi`, `kid`, `xml+genshi`, `xml+kid` | 276 | | `Go HTML Template` | `go-html-template` | 277 | | `Go Text Template` | `go-text-template` | 278 | | `Go` | `go`, `golang` | 279 | | `Haxe` | `hx`, `haxe`, `hxsl` | 280 | | `HTTP` | `http` | 281 | | `markdown` | `md`, `mkd` | 282 | | `PHTML` | `phtml` | 283 | | `Raku` | `perl6`, `pl6`, `raku` | 284 | | `reStructuredText` | `rst`, `rest`, `restructuredtext` | 285 | | `Svelte` | `svelte` | 286 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.26.0](https://github.com/koki-develop/gat/compare/v0.25.8...v0.26.0) (2025-12-21) 4 | 5 | 6 | ### Features 7 | 8 | * add --mask-secrets flag to mask sensitive information ([8782afc](https://github.com/koki-develop/gat/commit/8782afcff0695ed6e40c7a74841129649bfd2fae)) 9 | * **deps:** update module github.com/alecthomas/chroma/v2 to v2.21.1 ([#244](https://github.com/koki-develop/gat/issues/244)) ([4435462](https://github.com/koki-develop/gat/commit/4435462e439218e50d6c07b76471c0c49bbb5a19)) 10 | * **masker:** add support for Anthropic, OpenAI, and Supabase API keys ([aaeb24d](https://github.com/koki-develop/gat/commit/aaeb24d7c63833569f594cfe5a1e88d29dbcba64)) 11 | * **masker:** add support for AWS temporary credentials and secret keys ([ae2a833](https://github.com/koki-develop/gat/commit/ae2a83304e104ec3b19a6fc845d655be53ce0094)) 12 | 13 | 14 | ### Bug Fixes 15 | 16 | * **deps:** update module github.com/tdewolff/minify/v2 to v2.24.8 ([#243](https://github.com/koki-develop/gat/issues/243)) ([727dd1d](https://github.com/koki-develop/gat/commit/727dd1da059e37fd763bf76472e3121198ca1965)) 17 | 18 | ## [0.25.8](https://github.com/koki-develop/gat/compare/v0.25.7...v0.25.8) (2025-12-06) 19 | 20 | 21 | ### Bug Fixes 22 | 23 | * **deps:** update module github.com/spf13/cobra to v1.10.2 ([#237](https://github.com/koki-develop/gat/issues/237)) ([ddf6a51](https://github.com/koki-develop/gat/commit/ddf6a511e7ce2a49236231a51caeb5c1702a08e0)) 24 | 25 | ## [0.25.7](https://github.com/koki-develop/gat/compare/v0.25.6...v0.25.7) (2025-11-24) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * **deps:** update module github.com/google/yamlfmt to v0.20.0 ([#226](https://github.com/koki-develop/gat/issues/226)) ([9c49b11](https://github.com/koki-develop/gat/commit/9c49b114ec22412a3d2e3f5f6bb349b6ab2582fc)) 31 | * **deps:** update module golang.org/x/image to v0.33.0 ([#227](https://github.com/koki-develop/gat/issues/227)) ([8a6f996](https://github.com/koki-develop/gat/commit/8a6f9961570a6f0982dfc097065b267947ab0555)) 32 | * **deps:** update module golang.org/x/term to v0.37.0 ([#228](https://github.com/koki-develop/gat/issues/228)) ([bc65e75](https://github.com/koki-develop/gat/commit/bc65e75242bd53c694d796e866661e34fb5bb9ba)) 33 | 34 | ## [0.25.6](https://github.com/koki-develop/gat/compare/v0.25.5...v0.25.6) (2025-11-23) 35 | 36 | 37 | ### Bug Fixes 38 | 39 | * **ci:** Fix release workflow ([ec08eb4](https://github.com/koki-develop/gat/commit/ec08eb439ff48b5b673585d90722994fad4ebdf3)) 40 | 41 | ## [0.25.5](https://github.com/koki-develop/gat/compare/v0.25.4...v0.25.5) (2025-11-23) 42 | 43 | 44 | ### Bug Fixes 45 | 46 | * **deps:** update module github.com/tdewolff/minify/v2 to v2.24.7 ([#211](https://github.com/koki-develop/gat/issues/211)) ([695f1fc](https://github.com/koki-develop/gat/commit/695f1fc8fd10a0d44710e2c44f149c667e163c4f)) 47 | * **deps:** update module golang.org/x/image to v0.32.0 ([#214](https://github.com/koki-develop/gat/issues/214)) ([18b9cee](https://github.com/koki-develop/gat/commit/18b9ceededb2dee59e728572874f9f914da57b54)) 48 | 49 | ## [0.25.4](https://github.com/koki-develop/gat/compare/v0.25.3...v0.25.4) (2025-10-04) 50 | 51 | 52 | ### Bug Fixes 53 | 54 | * Resolve lexer per file instead of caching ([#216](https://github.com/koki-develop/gat/issues/216)) ([496638c](https://github.com/koki-develop/gat/commit/496638cbe4641c4201cb3a3f832155b117fa8f40)) 55 | 56 | ## [0.25.3](https://github.com/koki-develop/gat/compare/v0.25.2...v0.25.3) (2025-10-04) 57 | 58 | 59 | ### Bug Fixes 60 | 61 | * **deps:** update module github.com/spf13/cobra to v1.10.1 ([#208](https://github.com/koki-develop/gat/issues/208)) ([003a1b3](https://github.com/koki-develop/gat/commit/003a1b33403544ff86c0026e555fd9c50459682d)) 62 | * **deps:** update module github.com/stretchr/testify to v1.11.1 ([#205](https://github.com/koki-develop/gat/issues/205)) ([50b2a27](https://github.com/koki-develop/gat/commit/50b2a2752b3dcc10b7c655ba35037c9fceecaf12)) 63 | * **deps:** update module github.com/tdewolff/minify/v2 to v2.24.2 ([#206](https://github.com/koki-develop/gat/issues/206)) ([7c3f438](https://github.com/koki-develop/gat/commit/7c3f43820eae256427d746d9664ca71dcc01c351)) 64 | * Gracefully handle prettifier errors ([#215](https://github.com/koki-develop/gat/issues/215)) ([ee26c5e](https://github.com/koki-develop/gat/commit/ee26c5ee88995b005153d2ff04d66ad642f3821d)) 65 | 66 | ## [0.25.2](https://github.com/koki-develop/gat/compare/v0.25.1...v0.25.2) (2025-08-26) 67 | 68 | 69 | ### Bug Fixes 70 | 71 | * Add shell completion installation to Homebrew formula ([#204](https://github.com/koki-develop/gat/issues/204)) ([ba4b67e](https://github.com/koki-develop/gat/commit/ba4b67eeea685bfa0d9b08160511d26b20136663)) 72 | * **deps:** update module github.com/stretchr/testify to v1.11.0 ([#200](https://github.com/koki-develop/gat/issues/200)) ([82f2d9c](https://github.com/koki-develop/gat/commit/82f2d9c3fc51fe6efb8901e01ff2cbefcde976ba)) 73 | 74 | ## [0.25.1](https://github.com/koki-develop/gat/compare/v0.25.0...v0.25.1) (2025-08-24) 75 | 76 | 77 | ### Bug Fixes 78 | 79 | * **deps:** update module github.com/tdewolff/minify/v2 to v2.24.0 ([#199](https://github.com/koki-develop/gat/issues/199)) ([d5e2172](https://github.com/koki-develop/gat/commit/d5e2172d7d115af595f9b54a9bb2ab4e6e45fbc6)) 80 | * **deps:** update module golang.org/x/image to v0.30.0 ([#193](https://github.com/koki-develop/gat/issues/193)) ([08dac39](https://github.com/koki-develop/gat/commit/08dac39ddd679f01cb6604e2f622b970b800598e)) 81 | * **deps:** update module golang.org/x/term to v0.34.0 ([#195](https://github.com/koki-develop/gat/issues/195)) ([4d74783](https://github.com/koki-develop/gat/commit/4d74783dec7a622d67b2534a8e1b77ffd6ea7853)) 82 | 83 | ## [0.25.0](https://github.com/koki-develop/gat/compare/v0.24.1...v0.25.0) (2025-08-16) 84 | 85 | 86 | ### Features 87 | 88 | * **deps:** update module github.com/alecthomas/chroma/v2 to v2.20.0 ([#185](https://github.com/koki-develop/gat/issues/185)) ([99be01a](https://github.com/koki-develop/gat/commit/99be01a23af638cbc38328ee29bba56c8431d888)) 89 | 90 | 91 | ### Bug Fixes 92 | 93 | * **deps:** update module github.com/google/yamlfmt to v0.17.2 ([#180](https://github.com/koki-develop/gat/issues/180)) ([f8ea163](https://github.com/koki-develop/gat/commit/f8ea163413ce4287edba3d5add8363095b59c4d0)) 94 | * **deps:** update module github.com/tdewolff/minify/v2 to v2.23.10 ([#182](https://github.com/koki-develop/gat/issues/182)) ([9c5ba93](https://github.com/koki-develop/gat/commit/9c5ba93a18da2edd980c9095cdd5f9ff646de0d1)) 95 | * **deps:** update module github.com/tdewolff/minify/v2 to v2.23.11 ([#188](https://github.com/koki-develop/gat/issues/188)) ([7311e8c](https://github.com/koki-develop/gat/commit/7311e8ca7f80c3444d7efd1398a087abbe2aef80)) 96 | * **deps:** update module golang.org/x/image to v0.29.0 ([#176](https://github.com/koki-develop/gat/issues/176)) ([daea246](https://github.com/koki-develop/gat/commit/daea24621f766157334304798e7f8528614b3ca5)) 97 | * **deps:** update module golang.org/x/term to v0.33.0 ([#186](https://github.com/koki-develop/gat/issues/186)) ([f24517e](https://github.com/koki-develop/gat/commit/f24517e340c072fdc0116c7f0424aa8b4492d64d)) 98 | 99 | ## [0.24.1](https://github.com/koki-develop/gat/compare/v0.24.0...v0.24.1) (2025-06-05) 100 | 101 | 102 | ### Bug Fixes 103 | 104 | * **deps:** update module github.com/google/yamlfmt to v0.17.0 ([#170](https://github.com/koki-develop/gat/issues/170)) ([082e8f4](https://github.com/koki-develop/gat/commit/082e8f484d8d87177b06f00ecaa0c44e93e5a328)) 105 | * **deps:** update module github.com/tdewolff/minify/v2 to v2.23.8 ([#168](https://github.com/koki-develop/gat/issues/168)) ([a2319a8](https://github.com/koki-develop/gat/commit/a2319a85d6757f3a02e8218d073bac0f48dcccd9)) 106 | * resolve file handle resource leak in CLI processing ([#174](https://github.com/koki-develop/gat/issues/174)) ([86ef368](https://github.com/koki-develop/gat/commit/86ef36806fa6c48b2e0629be9c9f5a38c122b67f)) 107 | 108 | ## [0.24.0](https://github.com/koki-develop/gat/compare/v0.23.2...v0.24.0) (2025-05-19) 109 | 110 | 111 | ### Features 112 | 113 | * **deps:** update module github.com/alecthomas/chroma/v2 to v2.18.0 ([#164](https://github.com/koki-develop/gat/issues/164)) ([6b6ad62](https://github.com/koki-develop/gat/commit/6b6ad627f980af9e74e8a3954a39b9043297fb1e)) 114 | 115 | ## [0.23.2](https://github.com/koki-develop/gat/compare/v0.23.1...v0.23.2) (2025-05-10) 116 | 117 | 118 | ### Bug Fixes 119 | 120 | * **deps:** update module github.com/tdewolff/minify/v2 to v2.23.5 ([#161](https://github.com/koki-develop/gat/issues/161)) ([3234023](https://github.com/koki-develop/gat/commit/3234023adb9a77ca734ff3196645a256a99310bb)) 121 | * **deps:** update module golang.org/x/image to v0.27.0 ([#159](https://github.com/koki-develop/gat/issues/159)) ([2a0b861](https://github.com/koki-develop/gat/commit/2a0b861cb9efcb28cbab78570429238da4c55cfa)) 122 | * **deps:** update module golang.org/x/term to v0.32.0 ([#157](https://github.com/koki-develop/gat/issues/157)) ([f41652f](https://github.com/koki-develop/gat/commit/f41652f7ae40d6e470d58a850fde7e4bd95dba8a)) 123 | 124 | ## [0.23.1](https://github.com/koki-develop/gat/compare/v0.23.0...v0.23.1) (2025-05-03) 125 | 126 | 127 | ### Bug Fixes 128 | 129 | * Bump golang.org/x/net from 0.33.0 to 0.38.0 ([#135](https://github.com/koki-develop/gat/issues/135)) ([10db835](https://github.com/koki-develop/gat/commit/10db8353fbc2a6f4d4881c2c9955b25a2f80e936)) 130 | * **deps:** update module github.com/alecthomas/chroma/v2 to v2.17.2 ([#150](https://github.com/koki-develop/gat/issues/150)) ([13981c5](https://github.com/koki-develop/gat/commit/13981c573995f35a03cae0acb5a2f1032f50c2ec)) 131 | * **deps:** update module github.com/tdewolff/minify/v2 to v2.23.3 ([#148](https://github.com/koki-develop/gat/issues/148)) ([f38420a](https://github.com/koki-develop/gat/commit/f38420a522e8870c43400003d56bd8a75ddd776f)) 132 | 133 | ## [0.23.0](https://github.com/koki-develop/gat/compare/v0.22.3...v0.23.0) (2025-04-25) 134 | 135 | 136 | ### Features 137 | 138 | * **deps:** update module github.com/alecthomas/chroma/v2 to v2.17.0 ([#144](https://github.com/koki-develop/gat/issues/144)) ([62de9d3](https://github.com/koki-develop/gat/commit/62de9d30f1491257e000821da57b11dda36847ac)) 139 | 140 | ## [0.22.3](https://github.com/koki-develop/gat/compare/v0.22.2...v0.22.3) (2025-04-20) 141 | 142 | 143 | ### Bug Fixes 144 | 145 | * **deps:** update module github.com/charmbracelet/glamour to v0.10.0 ([#127](https://github.com/koki-develop/gat/issues/127)) ([4bba5f5](https://github.com/koki-develop/gat/commit/4bba5f5da244aad66ed99dc8c18d4d73434ad631)) 146 | 147 | ## [0.22.2](https://github.com/koki-develop/gat/compare/v0.22.1...v0.22.2) (2025-04-19) 148 | 149 | 150 | ### Features 151 | 152 | * Release ([fddbf66](https://github.com/koki-develop/gat/commit/fddbf66e487b98020babf3f540003cdaf21c3519)) 153 | 154 | ## [0.22.1](https://github.com/koki-develop/gat/compare/v0.22.0...v0.22.1) (2025-04-19) 155 | 156 | 157 | ### Features 158 | 159 | * Release ([90ea8b5](https://github.com/koki-develop/gat/commit/90ea8b5759c9b15d07e0fc808ca8280e91a3313e)) 160 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= 2 | github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= 3 | github.com/alecthomas/chroma/v2 v2.21.1 h1:FaSDrp6N+3pphkNKU6HPCiYLgm8dbe5UXIXcoBhZSWA= 4 | github.com/alecthomas/chroma/v2 v2.21.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o= 5 | github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= 6 | github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= 7 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 8 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 9 | github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= 10 | github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= 11 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= 12 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 13 | github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q= 14 | github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= 15 | github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= 16 | github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= 17 | github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY= 18 | github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk= 19 | github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE= 20 | github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= 21 | github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= 22 | github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= 23 | github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= 24 | github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= 25 | github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30= 26 | github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= 27 | github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI= 28 | github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU= 29 | github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= 30 | github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= 31 | github.com/client9/csstool v0.2.2 h1:TBRqhAul1HXYwMXzQ4j86TSOr50D6JqKAwj9nbro+3Y= 32 | github.com/client9/csstool v0.2.2/go.mod h1:gOROmZEK98JLAUl/Ru4QxZaSKkr6kVH3J0xeDJrnPJY= 33 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 34 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 35 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 36 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 37 | github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= 38 | github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 39 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 40 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 41 | github.com/google/yamlfmt v0.20.0 h1:EfMuMFEZGnXPn2NY+KgJTH9sNs6P+am/Of6IwE2K6P8= 42 | github.com/google/yamlfmt v0.20.0/go.mod h1:gs0UEklJOYkUJ+OOCG0hg9n+DzucKDPlJElTUasVNK8= 43 | github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= 44 | github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= 45 | github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= 46 | github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= 47 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 48 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 49 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 50 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 51 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 52 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 53 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 54 | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 55 | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 56 | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 57 | github.com/mattn/go-sixel v0.0.5 h1:55w2FR5ncuhKhXrM5ly1eiqMQfZsnAHIpYNGZX03Cv8= 58 | github.com/mattn/go-sixel v0.0.5/go.mod h1:h2Sss+DiUEHy0pUqcIB6PFXo5Cy8sTQEFr3a9/5ZLNw= 59 | github.com/mattn/go-zglob v0.0.0-20180803001819-2ea3427bfa53/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= 60 | github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= 61 | github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= 62 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 63 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 64 | github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= 65 | github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= 66 | github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 67 | github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 68 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 69 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 70 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 71 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 72 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 73 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 74 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 75 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= 76 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= 77 | github.com/soniakeys/quant v1.0.0 h1:N1um9ktjbkZVcywBVAAYpZYSHxEfJGzshHCxx/DaI0Y= 78 | github.com/soniakeys/quant v1.0.0/go.mod h1:HI1k023QuVbD4H8i9YdfZP2munIHU4QpjsImz6Y6zds= 79 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 80 | github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= 81 | github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= 82 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 83 | github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= 84 | github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 85 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 86 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 87 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 88 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 89 | github.com/tdewolff/minify/v2 v2.24.8 h1:58/VjsbevI4d5FGV0ZSuBrHMSSkH4MCH0sIz/eKIauE= 90 | github.com/tdewolff/minify/v2 v2.24.8/go.mod h1:0Ukj0CRpo/sW/nd8uZ4ccXaV1rEVIWA3dj8U7+Shhfw= 91 | github.com/tdewolff/parse v2.3.3+incompatible h1:q6OSjvHtvBucLb34z24OH1xl5wGdw1mI9Vd38Qj9evs= 92 | github.com/tdewolff/parse v2.3.3+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ= 93 | github.com/tdewolff/parse/v2 v2.8.5 h1:ZmBiA/8Do5Rpk7bDye0jbbDUpXXbCdc3iah4VeUvwYU= 94 | github.com/tdewolff/parse/v2 v2.8.5/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo= 95 | github.com/tdewolff/test v1.0.0/go.mod h1:DiQUlutnqlEvdvhSn2LPGy4TFwRauAaYDsL+683RNX4= 96 | github.com/tdewolff/test v1.0.11 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE= 97 | github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= 98 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 99 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 100 | github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 h1:0sw0nJM544SpsihWx1bkXdYLQDlzRflMgFJQ4Yih9ts= 101 | github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4/go.mod h1:+ccdNT0xMY1dtc5XBxumbYfOUhmduiGudqaDgD2rVRE= 102 | github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= 103 | github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= 104 | github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= 105 | github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk= 106 | github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= 107 | go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 108 | golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= 109 | golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= 110 | golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ= 111 | golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc= 112 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 113 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 114 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 115 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 116 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 117 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 118 | golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= 119 | golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= 120 | golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= 121 | golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= 122 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 123 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 124 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 125 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 126 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 127 | --------------------------------------------------------------------------------