paragraph
`, 18 | want: ` 19 | 20 | 21 |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, "\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 |
7 | cat alternative written in Go. 8 |
9 | 10 | 17 | 18 |
19 |
20 |
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 | 
78 |
79 | ## `abap`
80 |
81 | 
82 |
83 | ## `algol`
84 |
85 | 
86 |
87 | ## `algol_nu`
88 |
89 | 
90 |
91 | ## `arduino`
92 |
93 | 
94 |
95 | ## `ashen`
96 |
97 | 
98 |
99 | ## `aura-theme-dark`
100 |
101 | 
102 |
103 | ## `aura-theme-dark-soft`
104 |
105 | 
106 |
107 | ## `autumn`
108 |
109 | 
110 |
111 | ## `average`
112 |
113 | 
114 |
115 | ## `base16-snazzy`
116 |
117 | 
118 |
119 | ## `borland`
120 |
121 | 
122 |
123 | ## `bw`
124 |
125 | 
126 |
127 | ## `catppuccin-frappe`
128 |
129 | 
130 |
131 | ## `catppuccin-latte`
132 |
133 | 
134 |
135 | ## `catppuccin-macchiato`
136 |
137 | 
138 |
139 | ## `catppuccin-mocha`
140 |
141 | 
142 |
143 | ## `colorful`
144 |
145 | 
146 |
147 | ## `doom-one`
148 |
149 | 
150 |
151 | ## `doom-one2`
152 |
153 | 
154 |
155 | ## `dracula`
156 |
157 | 
158 |
159 | ## `emacs`
160 |
161 | 
162 |
163 | ## `evergarden`
164 |
165 | 
166 |
167 | ## `friendly`
168 |
169 | 
170 |
171 | ## `fruity`
172 |
173 | 
174 |
175 | ## `github`
176 |
177 | 
178 |
179 | ## `github-dark`
180 |
181 | 
182 |
183 | ## `gruvbox`
184 |
185 | 
186 |
187 | ## `gruvbox-light`
188 |
189 | 
190 |
191 | ## `hr_high_contrast`
192 |
193 | 
194 |
195 | ## `hrdark`
196 |
197 | 
198 |
199 | ## `igor`
200 |
201 | 
202 |
203 | ## `lovelace`
204 |
205 | 
206 |
207 | ## `manni`
208 |
209 | 
210 |
211 | ## `modus-operandi`
212 |
213 | 
214 |
215 | ## `modus-vivendi`
216 |
217 | 
218 |
219 | ## `monokai`
220 |
221 | 
222 |
223 | ## `monokailight`
224 |
225 | 
226 |
227 | ## `murphy`
228 |
229 | 
230 |
231 | ## `native`
232 |
233 | 
234 |
235 | ## `noop`
236 |
237 | 
238 |
239 | ## `nord`
240 |
241 | 
242 |
243 | ## `nordic`
244 |
245 | 
246 |
247 | ## `onedark`
248 |
249 | 
250 |
251 | ## `onesenterprise`
252 |
253 | 
254 |
255 | ## `paraiso-dark`
256 |
257 | 
258 |
259 | ## `paraiso-light`
260 |
261 | 
262 |
263 | ## `pastie`
264 |
265 | 
266 |
267 | ## `perldoc`
268 |
269 | 
270 |
271 | ## `pygments`
272 |
273 | 
274 |
275 | ## `rainbow_dash`
276 |
277 | 
278 |
279 | ## `rose-pine`
280 |
281 | 
282 |
283 | ## `rose-pine-dawn`
284 |
285 | 
286 |
287 | ## `rose-pine-moon`
288 |
289 | 
290 |
291 | ## `rrt`
292 |
293 | 
294 |
295 | ## `solarized-dark`
296 |
297 | 
298 |
299 | ## `solarized-dark256`
300 |
301 | 
302 |
303 | ## `solarized-light`
304 |
305 | 
306 |
307 | ## `swapoff`
308 |
309 | 
310 |
311 | ## `tango`
312 |
313 | 
314 |
315 | ## `tokyonight-day`
316 |
317 | 
318 |
319 | ## `tokyonight-moon`
320 |
321 | 
322 |
323 | ## `tokyonight-night`
324 |
325 | 
326 |
327 | ## `tokyonight-storm`
328 |
329 | 
330 |
331 | ## `trac`
332 |
333 | 
334 |
335 | ## `vim`
336 |
337 | 
338 |
339 | ## `vs`
340 |
341 | 
342 |
343 | ## `vulcan`
344 |
345 | 
346 |
347 | ## `witchhazel`
348 |
349 | 
350 |
351 | ## `xcode`
352 |
353 | 
354 |
355 | ## `xcode-dark`
356 |
357 | 
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 |
58 |
--------------------------------------------------------------------------------
/assets/logo_light.svg:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------