60 Commits

Author SHA1 Message Date
github-actions[bot]
cfc5e93013 chore: auto-update Go modules 2026-03-23 00:46:30 +00:00
849eb77fde add pre-commit config 2026-03-21 14:00:25 +00:00
github-actions[bot]
c3c6f5b592 chore: auto-update Go modules 2026-03-16 00:48:33 +00:00
github-actions[bot]
0c7d5649ab chore: auto-update Go modules 2026-03-09 00:42:58 +00:00
github-actions[bot]
144d8c1ba5 chore: auto-update Go modules 2026-03-02 00:42:26 +00:00
github-actions[bot]
31cdb5faa4 chore: auto-update Go modules 2026-02-23 00:43:28 +00:00
fad7a42559 rewrite versionFromBuild() 2026-02-16 01:27:39 +00:00
github-actions[bot]
021c504891 chore: auto-update Go modules 2026-02-16 00:43:30 +00:00
9f22086c73 add macos target to Taskfile 2026-02-15 15:26:20 +00:00
f8f317f576 lint fixes 2026-02-15 15:22:44 +00:00
1da96e4f30 migrate golangci config to version 2
update golangci workflow
2026-02-15 15:19:26 +00:00
github-actions[bot]
7e33557c79 chore: auto-update Go modules 2026-02-09 00:44:11 +00:00
github-actions[bot]
1837f70125 chore: auto-update Go modules 2026-02-02 02:44:37 +00:00
01ae5ef46f add chore: to changelog filter 2026-01-28 21:40:39 +00:00
github-actions[bot]
ff6053977a chore: auto-update Go modules 2026-01-26 00:38:23 +00:00
github-actions[bot]
ac166b80ae chore: auto-update Go modules 2026-01-19 00:37:38 +00:00
github-actions[bot]
6cab2f0708 chore: auto-update Go modules 2026-01-12 00:37:17 +00:00
github-actions[bot]
13a3338dfa chore: auto-update Go modules 2026-01-05 00:38:22 +00:00
github-actions[bot]
a7bd8f505b chore: auto-update Go modules 2025-12-22 00:36:02 +00:00
github-actions[bot]
36083f3655 chore: auto-update Go modules 2025-12-15 00:36:29 +00:00
github-actions[bot]
3972891e07 chore: auto-update Go modules 2025-12-08 00:34:06 +00:00
github-actions[bot]
546785a087 chore: auto-update Go modules 2025-12-01 00:40:26 +00:00
github-actions[bot]
278bbe18ae chore: auto-update Go modules 2025-11-24 00:34:46 +00:00
github-actions[bot]
4b2d1a5589 chore: auto-update Go modules 2025-11-17 00:33:21 +00:00
github-actions[bot]
beb8cf4fa4 chore: auto-update Go modules 2025-11-10 00:33:57 +00:00
github-actions[bot]
b2e8532d22 chore: auto-update Go modules 2025-11-03 00:33:33 +00:00
github-actions[bot]
63d326e1e2 chore: auto-update Go modules 2025-10-27 00:34:09 +00:00
github-actions[bot]
4d470cf385 chore: auto-update Go modules 2025-10-20 00:33:26 +00:00
github-actions[bot]
f70c5ad21a chore: auto-update Go modules 2025-10-13 00:32:46 +00:00
github-actions[bot]
b4ef031fa1 chore: auto-update Go modules 2025-10-06 00:30:55 +00:00
github-actions[bot]
365edb494a chore: auto-update Go modules 2025-09-29 00:31:26 +00:00
github-actions[bot]
ea20c27c86 chore: auto-update Go modules 2025-09-22 00:32:53 +00:00
github-actions[bot]
fa211f8478 chore: auto-update Go modules 2025-09-15 00:32:21 +00:00
github-actions[bot]
ac8092f738 chore: auto-update Go modules 2025-09-08 00:31:51 +00:00
github-actions[bot]
f88b196065 chore: auto-update Go modules 2025-09-01 00:37:41 +00:00
github-actions[bot]
1c91c69998 chore: auto-update Go modules 2025-08-25 00:32:39 +00:00
github-actions[bot]
e7fcceaeb9 chore: auto-update Go modules 2025-08-18 00:35:32 +00:00
github-actions[bot]
89fe511130 chore: auto-update Go modules 2025-08-11 00:36:13 +00:00
github-actions[bot]
78e5689d38 chore: auto-update Go modules 2025-08-04 00:38:00 +00:00
github-actions[bot]
6d20176b14 chore: auto-update Go modules 2025-07-28 00:36:59 +00:00
github-actions[bot]
be38fee0ea chore: auto-update Go modules 2025-07-21 00:36:48 +00:00
github-actions[bot]
dd1099f3f2 chore: auto-update Go modules 2025-07-14 00:35:41 +00:00
github-actions[bot]
69d715e4d0 chore: auto-update Go modules 2025-07-07 00:35:16 +00:00
github-actions[bot]
67969d3550 chore: auto-update Go modules 2025-06-30 00:35:02 +00:00
github-actions[bot]
df12de837b chore: auto-update Go modules 2025-06-23 00:34:42 +00:00
bae71b983e bump to 1.0.0 2025-06-19 04:39:43 +01:00
f4ddb45fd2 don't log.Fatal if we can't read version... 2025-06-19 02:21:12 +01:00
cad1585975 add versionFromBuild 2025-06-19 02:12:56 +01:00
4139c4b291 fix regression reading build info 2025-06-19 02:07:25 +01:00
856da5cd04 wrap CLI with fang 2025-06-19 01:53:16 +01:00
a6fb3c69c7 reword 2025-06-18 09:11:15 +01:00
cf93198462 add --start-search/-s flag
upd README
2025-06-18 08:56:05 +01:00
e1875fb894 upd selection prompt image 2025-06-18 06:43:08 +01:00
e1b879f54c change default height to 10
- with startswith filtering its more reasonable
2025-06-18 06:37:06 +01:00
f51302a945 update usage, header + footer. 2025-06-18 06:26:34 +01:00
11fe797f3f upd go.mod to match repository 2025-06-18 06:13:01 +01:00
5b389aa9dc update install link to match new repo name
update taskfile for local builds
2025-06-18 06:08:05 +01:00
bb535f296d fix link 2025-06-18 04:03:13 +01:00
5eccfc939c upd special thanks 2025-06-18 04:02:39 +01:00
00638a6bf4 fix typo 2025-06-18 03:51:58 +01:00
14 changed files with 373 additions and 177 deletions

View File

@@ -23,7 +23,8 @@ jobs:
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: '1.24' go-version: '1.24'
- name: Install golangci-lint - name: golangci-lint
run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest uses: golangci/golangci-lint-action@v9
- name: Run golangci-lint with:
run: golangci-lint run ./... version: v2.6.0
args: --config .golangci.yml

4
.gitignore vendored
View File

@@ -1,4 +1,4 @@
# Generated by ignr-cli: github.com/onyx-and-iris/ignr-cli # Generated by ignr: github.com/onyx-and-iris/ignr
## Go ## ## Go ##
# If you prefer the allow list template instead of the deny list, see community template: # If you prefer the allow list template instead of the deny list, see community template:
@@ -36,4 +36,4 @@ go.work.sum
# .idea/ # .idea/
# .vscode/ # .vscode/
# End of ignr-cli # End of ignr

View File

@@ -1,54 +1,142 @@
version: '2'
run: run:
# timeout for analysis, e.g. 30s, 3m, default is 1m
timeout: 3m timeout: 3m
# exclude test files
tests: true tests: true
go: '1.24'
linters: linters:
# Set to true runs only fast linters. disable: []
# Good option for 'lint on save', pre-commit hook or CI.
fast: true
disable-all: true
enable: enable:
- gosimple # Default enabled linters
- govet - errcheck # Check for unchecked errors
- ineffassign - govet # Go's built-in vetting tool
- staticcheck - ineffassign # Detect ineffectual assignments
- unused - staticcheck # Advanced static analysis
- gofmt - unused # Check for unused code
- gofumpt # Additional useful linters
- misspell - misspell # Detect common misspellings
- unparam - unparam # Check for unused function parameters
- gosec - gosec # Security checks
- asciicheck - asciicheck # Check for non-ASCII characters
- errname - errname # Check error variable names
- gci - godot # Check for missing periods in comments
- godot - revive # Highly configurable linter for style and correctness
- goimports - gocritic # Detect code issues and suggest improvements
- revive - gocyclo # Check for cyclomatic complexity
- dupl # Check for code duplication
- predeclared # Check for shadowing of predeclared identifiers
- copyloopvar # Check for loop variable capture in goroutines
- errorlint # Check for common mistakes in error handling
- goconst # Check for repeated strings that could be constants
- gosmopolitan # Check for non-portable code
linters-settings: settings:
gofmt: misspell:
rewrite-rules: locale: UK
- pattern: 'interface{}'
replacement: 'any'
- pattern: 'a[b:len(a)]'
replacement: 'a[b:]'
misspell: errcheck:
locale: UK check-type-assertions: true
check-blank: true
exclude-functions:
- fmt.Fprintf
- fmt.Fprintln
- fmt.Printf
- fmt.Println
- fmt.Errorf
errcheck: revive:
check-type-assertions: true severity: warning
rules:
# Code quality and style
- name: exported
arguments:
- 'checkPrivateReceivers'
- 'sayRepetitiveInsteadOfStutters'
- name: var-naming
- name: package-comments
- name: range-val-in-closure
- name: time-naming
- name: context-as-argument
- name: context-keys-type
- name: dot-imports
- name: empty-block
- name: error-return
- name: error-strings
- name: error-naming
- name: if-return
- name: increment-decrement
- name: indent-error-flow
- name: receiver-naming
- name: redefines-builtin-id
- name: superfluous-else
- name: unexported-return
- name: unreachable-code
- name: unused-parameter
- name: var-declaration
- name: blank-imports
- name: range
# Disabled rules (can be enabled if needed)
# - name: line-length-limit
# arguments: [120]
# - name: function-length
# arguments: [50, 0]
# - name: cyclomatic
# arguments: [10]
gosec:
excludes:
- G104 # Duplicated errcheck checks
- G115 # integer overflow conversion int -> uint32
exclusions:
warn-unused: false
rules:
# Exclude some linters from running on tests files.
- path: _test\.go
linters:
- gocyclo
- errcheck
- dupl
- gosec
paths:
- vendor
# Formatters configuration
formatters:
# Enable specific formatters
enable:
- gofumpt # Stricter gofmt alternative
- goimports # Organizes imports
- gci # Controls import order/grouping
- golines # Enforces line length
# Formatter-specific settings
settings:
goimports:
local-prefixes: [github.com/onyx-and-iris/ignr]
gci:
# Define import sections order
sections:
- standard # Standard library
- default # Everything else
- prefix(github.com/onyx-and-iris/ignr) # Current module
gofumpt:
extra-rules: true # Enable additional formatting rules
exclusions:
warn-unused: true
paths:
- vendor
issues: issues:
max-same-issues: 0 # Limit the number of same issues reported to avoid spam
max-issues-per-linter: 0 max-same-issues: 50
exclude-use-default: false
exclude: # Limit the number of issues per linter to keep output manageable
# gosec: Duplicated errcheck checks max-issues-per-linter: 100
- G104
# gosec: integer overflow conversion int -> uint32
- G115

View File

@@ -44,6 +44,7 @@ changelog:
exclude: exclude:
- '^docs:' - '^docs:'
- '^test:' - '^test:'
- '^chore:'
release: release:
footer: >- footer: >-

5
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,5 @@
repos:
- repo: https://github.com/tekwizely/pre-commit-golang
rev: v1.0.0-rc.4
hooks:
- id: go-mod-tidy

View File

@@ -2,7 +2,7 @@
![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black) ![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black)
![macOS](https://img.shields.io/badge/mac%20os-000000?style=for-the-badge&logo=macos&logoColor=F0F0F0) ![macOS](https://img.shields.io/badge/mac%20os-000000?style=for-the-badge&logo=macos&logoColor=F0F0F0)
# ignr-cli # ignr
Simple no-frills .gitignore generator backed by the Github API. Simple no-frills .gitignore generator backed by the Github API.
@@ -11,7 +11,7 @@ Simple no-frills .gitignore generator backed by the Github API.
## Install ## Install
```console ```console
go install github.com/onyx-and-iris/ignr-cli@latest go install github.com/onyx-and-iris/ignr@latest
``` ```
## Configuration ## Configuration
@@ -20,9 +20,10 @@ go install github.com/onyx-and-iris/ignr-cli@latest
- --token/-t: GitHub authentication token - --token/-t: GitHub authentication token
- note, this tool can be used **without** authenticating but rate limiting will be stricter. - note, this tool can be used **without** authenticating but rate limiting will be stricter.
- --height/-H: Height of the selection prompt (default 20) - --height/-H: Height of the selection prompt (default 10)
- --filter/-f: Type of filter to apply to the list of templates (default startswith) - --filter/-f: Type of filter to apply to the list of templates (default startswith)
- may be one of (startswith, contains) - may be one of (startswith, contains)
- --start-search/-s: Start the prompt in search mode (default false)
*environment variables* *environment variables*
@@ -30,8 +31,9 @@ go install github.com/onyx-and-iris/ignr-cli@latest
#!/usr/bin/env bash #!/usr/bin/env bash
export IGNR_TOKEN=<API Token> export IGNR_TOKEN=<API Token>
export IGNR_HEIGHT=20 export IGNR_HEIGHT=10
export IGNR_FILTER=startswith export IGNR_FILTER=startswith
export IGNR_START_SEARCH=false
``` ```
## Commands ## Commands
@@ -41,13 +43,14 @@ export IGNR_FILTER=startswith
Trigger the selection prompt. Trigger the selection prompt.
```console ```console
ignr-cli new ignr new
``` ```
The prompt filter can be activated by pressing `/`: Search mode can be activated by pressing `/`:
![Prompt Filter](./img/promptfilter.png) ![Prompt Filter](./img/promptfilter.png)
## Special Thanks ## Special Thanks
- [Charm](https://github.com/charmbracelet) for their awesome CLI packages. - [spf13](https://github.com/spf13) for the [cobra](https://github.com/spf13/cobra) and [viper](https://github.com/spf13/viper) packages.
- [Manifold](https://github.com/manifoldco) for the [promptui](https://github.com/manifoldco/promptui) package.

View File

@@ -1,7 +1,7 @@
version: '3' version: '3'
vars: vars:
PROGRAM: ignr-cli PROGRAM: ignr
SHELL: '{{if eq .OS "Windows_NT"}}powershell{{end}}' SHELL: '{{if eq .OS "Windows_NT"}}powershell{{end}}'
BIN_DIR: bin BIN_DIR: bin
VERSION: VERSION:
@@ -9,19 +9,21 @@ vars:
WINDOWS: '{{.BIN_DIR}}/{{.PROGRAM}}_windows_amd64.exe' WINDOWS: '{{.BIN_DIR}}/{{.PROGRAM}}_windows_amd64.exe'
LINUX: '{{.BIN_DIR}}/{{.PROGRAM}}_linux_amd64' LINUX: '{{.BIN_DIR}}/{{.PROGRAM}}_linux_amd64'
MACOS: '{{.BIN_DIR}}/{{.PROGRAM}}_darwin_amd64'
tasks: tasks:
default: default:
desc: Build the ignr-cli project desc: Build the ignr project
cmds: cmds:
- task: build - task: build
build: build:
desc: Build the ignr-cli project desc: Build the ignr project
deps: [vet] deps: [vet]
cmds: cmds:
- task: build-windows - task: build-windows
- task: build-linux - task: build-linux
- task: build-macos
vet: vet:
desc: Vet the code desc: Vet the code
@@ -35,17 +37,23 @@ tasks:
- go fmt ./... - go fmt ./...
build-windows: build-windows:
desc: Build the ignr-cli project for Windows desc: Build the ignr project for Windows
cmds: cmds:
- GOOS=windows GOARCH=amd64 go build -o {{.WINDOWS}} -ldflags="-X main.version={{.VERSION}}" - GOOS=windows GOARCH=amd64 go build -o {{.WINDOWS}} -ldflags="-X main.version={{.VERSION}}"
internal: true internal: true
build-linux: build-linux:
desc: Build the ignr-cli project for Linux desc: Build the ignr project for Linux
cmds: cmds:
- GOOS=linux GOARCH=amd64 go build -o {{.LINUX}} -ldflags="-X main.version={{.VERSION}}" - GOOS=linux GOARCH=amd64 go build -o {{.LINUX}} -ldflags="-X main.version={{.VERSION}}"
internal: true internal: true
build-macos:
desc: Build the ignr project for macOS
cmds:
- GOOS=darwin GOARCH=amd64 go build -o {{.MACOS}} -ldflags="-X main.version={{.VERSION}}"
internal: true
test: test:
desc: Run tests desc: Run tests
cmds: cmds:

View File

@@ -10,13 +10,13 @@ type contextKey string
var clientKey = contextKey("client") var clientKey = contextKey("client")
// withClient returns a new context with the GitHub client set. // WithClient returns a new context with the GitHub client set.
func withClient(ctx context.Context, client *github.Client) context.Context { func WithClient(ctx context.Context, client *github.Client) context.Context {
return context.WithValue(ctx, clientKey, client) return context.WithValue(ctx, clientKey, client)
} }
// clientFromContext retrieves the GitHub client from the context. // ClientFromContext retrieves the GitHub client from the context.
func clientFromContext(ctx context.Context) (*github.Client, bool) { func ClientFromContext(ctx context.Context) (*github.Client, bool) {
client, ok := ctx.Value(clientKey).(*github.Client) client, ok := ctx.Value(clientKey).(*github.Client)
return client, ok return client, ok
} }

View File

@@ -2,13 +2,11 @@ package main
import ( import (
"strings" "strings"
"github.com/spf13/viper"
) )
// filterFunc returns a function that filters templates based on the specified filter type. // filterFunc returns a function that filters templates based on the specified filter type.
func filterFunc(templates []string) func(input string, index int) bool { func filterFunc(templates []string, filterType string) func(input string, index int) bool {
switch viper.GetString("filter") { switch filterType {
case "contains": case "contains":
return func(input string, index int) bool { return func(input string, index int) bool {
return strings.Contains(strings.ToLower(templates[index]), strings.ToLower(input)) return strings.Contains(strings.ToLower(templates[index]), strings.ToLower(input))

57
go.mod
View File

@@ -1,30 +1,49 @@
module github.com/onyx-and-iris/ignr-cli module github.com/onyx-and-iris/ignr
go 1.24.3 go 1.25.0
require ( require (
github.com/charmbracelet/fang v1.0.0
github.com/google/go-github/v72 v72.0.0 github.com/google/go-github/v72 v72.0.0
github.com/manifoldco/promptui v0.9.0 github.com/manifoldco/promptui v0.9.0
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.20.1 github.com/spf13/viper v1.21.0
) )
require ( require (
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect charm.land/lipgloss/v2 v2.0.2 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/charmbracelet/colorprofile v0.4.3 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/charmbracelet/ultraviolet v0.0.0-20260316091819-b93f6a3b8502 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/charmbracelet/x/ansi v0.11.6 // indirect
github.com/charmbracelet/x/exp/charmtone v0.0.0-20260322003602-9b007323c5cd // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.2 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/google/go-querystring v1.2.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/mattn/go-runewidth v0.0.21 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/spf13/afero v1.12.0 // indirect github.com/muesli/mango v0.2.0 // indirect
github.com/spf13/cast v1.7.1 // indirect github.com/muesli/mango-cobra v1.3.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect github.com/muesli/mango-pflag v0.2.0 // indirect
github.com/muesli/roff v0.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.12.0 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.uber.org/multierr v1.9.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sys v0.31.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/text v0.23.0 // indirect golang.org/x/sys v0.42.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect golang.org/x/text v0.35.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
) )

125
go.sum
View File

@@ -1,71 +1,116 @@
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= charm.land/lipgloss/v2 v2.0.2 h1:xFolbF8JdpNkM2cEPTfXEcW1p6NRzOWTSamRfYEw8cs=
charm.land/lipgloss/v2 v2.0.2/go.mod h1:KjPle2Qd3YmvP1KL5OMHiHysGcNwq6u83MUjYkFvEkM=
github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
github.com/charmbracelet/fang v1.0.0 h1:jESBY40agJOlLYnnv9jE0mLqDGTxEk0hkOnx7YGyRlQ=
github.com/charmbracelet/fang v1.0.0/go.mod h1:P5/DNb9DddQ0Z0dbc0P3ol4/ix5Po7Ofr2KMBfAqoCo=
github.com/charmbracelet/ultraviolet v0.0.0-20260316091819-b93f6a3b8502 h1:hzWNs3UQRSUTS6YCbLaQnwqKBFXT5Yh1OOw6+26apqg=
github.com/charmbracelet/ultraviolet v0.0.0-20260316091819-b93f6a3b8502/go.mod h1:mkUCcxn9w9j89JJp3pOza5tmDQZPgIB75UfmQlFYvas=
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
github.com/charmbracelet/x/exp/charmtone v0.0.0-20260322003602-9b007323c5cd h1:eStB6uX52pgrm6TxQcEKctPrEC+a/9ubJC+P671idOc=
github.com/charmbracelet/x/exp/charmtone v0.0.0-20260322003602-9b007323c5cd/go.mod h1:nsExn0DGyX0lh9LwLHTn2Gg+hafdzfSXnC+QmEJTZFY=
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github/v72 v72.0.0 h1:FcIO37BLoVPBO9igQQ6tStsv2asG4IPcYFi655PPvBM= github.com/google/go-github/v72 v72.0.0 h1:FcIO37BLoVPBO9igQQ6tStsv2asG4IPcYFi655PPvBM=
github.com/google/go-github/v72 v72.0.0/go.mod h1:WWtw8GMRiL62mvIquf1kO3onRHeWWKmK01qdCY8c5fg= github.com/google/go-github/v72 v72.0.0/go.mod h1:WWtw8GMRiL62mvIquf1kO3onRHeWWKmK01qdCY8c5fg=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/mango v0.2.0 h1:iNNc0c5VLQ6fsMgAqGQofByNUBH2Q2nEbD6TaI+5yyQ=
github.com/muesli/mango v0.2.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4=
github.com/muesli/mango-cobra v1.3.0 h1:vQy5GvPg3ndOSpduxutqFoINhWk3vD5K2dXo5E8pqec=
github.com/muesli/mango-cobra v1.3.0/go.mod h1:Cj1ZrBu3806Qw7UjxnAUgE+7tllUBj1NCLQDwwGx19E=
github.com/muesli/mango-pflag v0.2.0 h1:QViokgKDZQCzKhYe1zH8D+UlPJzBSGoP9yx0hBG0t5k=
github.com/muesli/mango-pflag v0.2.0/go.mod h1:X9LT1p/pbGA1wjvEbtwnixujKErkP0jVmrxwrw3fL0Y=
github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 10 KiB

71
main.go
View File

@@ -3,12 +3,12 @@ package main
import ( import (
"context" "context"
"errors"
"fmt"
"log" "log"
"os"
"runtime/debug" "runtime/debug"
"strings" "strings"
"github.com/charmbracelet/fang"
"github.com/google/go-github/v72/github" "github.com/google/go-github/v72/github"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@@ -18,9 +18,9 @@ var version string // Version of the CLI, set during build time
// rootCmd represents the base command when called without any subcommands. // rootCmd represents the base command when called without any subcommands.
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "ignr-cli", Use: "ignr",
Short: "A command-line interface for generating .gitignore files", Short: "A command-line interface for generating .gitignore files",
Long: `ignr-cli is a command-line interface for generating .gitignore files. Long: `ignr is a command-line interface for generating .gitignore files.
It allows users to easily create and manage .gitignore files for various programming languages and frameworks. It allows users to easily create and manage .gitignore files for various programming languages and frameworks.
You may also list available templates and generate .gitignore files based on those templates.`, You may also list available templates and generate .gitignore files based on those templates.`,
SilenceUsage: true, SilenceUsage: true,
@@ -31,47 +31,58 @@ You may also list available templates and generate .gitignore files based on tho
} else { } else {
client = github.NewClient(nil).WithAuthToken(viper.GetString("token")) client = github.NewClient(nil).WithAuthToken(viper.GetString("token"))
} }
ctx := withClient(context.Background(), client) cmd.SetContext(WithClient(cmd.Context(), client))
cmd.SetContext(ctx)
},
RunE: func(cmd *cobra.Command, _ []string) error {
if cmd.Flags().Lookup("version").Changed {
if version == "" {
info, ok := debug.ReadBuildInfo()
if !ok {
return errors.New("unable to retrieve build information")
}
version = strings.Split(info.Main.Version, "-")[0]
}
fmt.Printf("ignr-cli version: %s\n", version)
return nil
}
return cmd.Help()
}, },
} }
// init initialises the root command and its flags. // init initialises the root command and its flags.
func init() { func init() {
rootCmd.PersistentFlags().StringP("token", "t", "", "GitHub authentication token") rootCmd.PersistentFlags().StringP("token", "t", "", "GitHub authentication token")
rootCmd.PersistentFlags().IntP("height", "H", 20, "Height of the selection prompt") rootCmd.PersistentFlags().IntP("height", "H", 10, "Height of the selection prompt")
rootCmd.PersistentFlags(). rootCmd.PersistentFlags().
StringP("filter", "f", "startswith", "Type of filter to apply to the list of templates (e.g., 'startswith', 'contains')") StringP("filter", "f", "startswith", "Type of filter to apply to the list of templates (e.g., 'startswith', 'contains')")
rootCmd.PersistentFlags().BoolP("start-search", "s", false, "Start the prompt in search mode")
rootCmd.Flags().BoolP("version", "v", false, "Print the version of the CLI") viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
viper.SetEnvPrefix("IGNR") viper.SetEnvPrefix("IGNR")
viper.AutomaticEnv() viper.AutomaticEnv()
viper.BindPFlag("token", rootCmd.PersistentFlags().Lookup("token")) if err := viper.BindPFlag("token", rootCmd.PersistentFlags().Lookup("token")); err != nil {
viper.BindPFlag("height", rootCmd.PersistentFlags().Lookup("height")) log.Fatal(err)
viper.BindPFlag("filter", rootCmd.PersistentFlags().Lookup("filter")) }
if err := viper.BindPFlag("height", rootCmd.PersistentFlags().Lookup("height")); err != nil {
log.Fatal(err)
}
if err := viper.BindPFlag("filter", rootCmd.PersistentFlags().Lookup("filter")); err != nil {
log.Fatal(err)
}
if err := viper.BindPFlag(
"start-search",
rootCmd.PersistentFlags().Lookup("start-search"),
); err != nil {
log.Fatal(err)
}
} }
// main is the entry point of the application. // main is the entry point of the application.
// It executes the root command and handles any errors. // It executes the root command and handles any errors.
func main() { func main() {
err := rootCmd.Execute() if err := fang.Execute(
if err != nil { context.Background(),
log.Fatal(err) rootCmd,
fang.WithVersion(versionFromBuild()),
); err != nil {
os.Exit(1)
} }
} }
func versionFromBuild() string {
if version != "" {
return version
}
info, ok := debug.ReadBuildInfo()
if !ok {
return "(unable to read version)"
}
return strings.Split(info.Main.Version, "-")[0]
}

55
new.go
View File

@@ -13,36 +13,44 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
) )
const gitignoreFileName = ".gitignore" const gitignoreFileName string = ".gitignore"
// newCmd represents the new command. // newCmd represents the new command.
var newCmd = &cobra.Command{ var newCmd = &cobra.Command{
Use: "new", Use: "new",
Short: "Create a new .gitignore file", Short: "Create a new .gitignore file",
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: runNewCommand,
return runNewCommand(cmd, args)
},
} }
func init() { func init() {
rootCmd.AddCommand(newCmd) rootCmd.AddCommand(newCmd)
} }
type promptConfig struct {
Height int
StartSearch bool
FilterType string
}
// runNewCommand is the handler for the 'new' command. // runNewCommand is the handler for the 'new' command.
// It retrieves the selected .gitignore template from GitHub and writes it to the .gitignore file. // It retrieves the selected .gitignore template from GitHub and writes it to the .gitignore file.
func runNewCommand(cmd *cobra.Command, _ []string) error { func runNewCommand(cmd *cobra.Command, _ []string) error {
height := viper.GetInt("height") pc := promptConfig{
if height <= 0 { Height: viper.GetInt("height"),
StartSearch: viper.GetBool("start-search"),
FilterType: viper.GetString("filter"),
}
if pc.Height <= 0 {
return errors.New("height must be a positive integer") return errors.New("height must be a positive integer")
} }
client, ok := clientFromContext(cmd.Context()) client, ok := ClientFromContext(cmd.Context())
if !ok { if !ok {
return errors.New("failed to get GitHub client from context") return errors.New("failed to get GitHub client from context")
} }
content, err := runPrompt(client, height) content, err := runPrompt(client, &pc)
if err != nil { if err != nil {
return fmt.Errorf("error running selection prompt: %w", err) return fmt.Errorf("error running selection prompt: %w", err)
} }
@@ -51,7 +59,11 @@ func runNewCommand(cmd *cobra.Command, _ []string) error {
if err != nil { if err != nil {
return fmt.Errorf("error opening file '%s': %w", gitignoreFileName, err) return fmt.Errorf("error opening file '%s': %w", gitignoreFileName, err)
} }
defer f.Close() defer func() {
if err := f.Close(); err != nil {
fmt.Fprintf(os.Stderr, "error closing file '%s': %v\n", gitignoreFileName, err)
}
}()
if err = commitGitignore(content, f); err != nil { if err = commitGitignore(content, f); err != nil {
return fmt.Errorf("error committing gitignore file: %w", err) return fmt.Errorf("error committing gitignore file: %w", err)
@@ -61,13 +73,13 @@ func runNewCommand(cmd *cobra.Command, _ []string) error {
} }
// runPrompt is a helper function to run the selection prompt for .gitignore templates. // runPrompt is a helper function to run the selection prompt for .gitignore templates.
func runPrompt(client *github.Client, height int) (*github.Gitignore, error) { func runPrompt(client *github.Client, pc *promptConfig) (*github.Gitignore, error) {
templates, _, err := client.Gitignores.List(context.Background()) templates, _, err := client.Gitignores.List(context.Background())
if err != nil { if err != nil {
return nil, fmt.Errorf("error retrieving gitignore template list: %w", err) return nil, fmt.Errorf("error retrieving gitignore template list: %w", err)
} }
selectTemmplates := &promptui.SelectTemplates{ selectTemplates := &promptui.SelectTemplates{
Label: ` {{ "\U0000007C" | faint }} {{ . | magenta | bold }}`, Label: ` {{ "\U0000007C" | faint }} {{ . | magenta | bold }}`,
Active: `{{ "\U0000007C" | faint }} {{ "🌶" | red }} {{ . | cyan | italic }}`, Active: `{{ "\U0000007C" | faint }} {{ "🌶" | red }} {{ . | cyan | italic }}`,
Inactive: `{{ "\U0000007C" | faint }} {{ . | faint }}`, Inactive: `{{ "\U0000007C" | faint }} {{ . | faint }}`,
@@ -75,16 +87,17 @@ func runPrompt(client *github.Client, height int) (*github.Gitignore, error) {
} }
prompt := promptui.Select{ prompt := promptui.Select{
Label: "Select a .gitignore template", Label: "Select a .gitignore template",
Items: templates, Items: templates,
Templates: selectTemmplates, Templates: selectTemplates,
Size: height, Size: pc.Height,
Searcher: filterFunc(templates), Searcher: filterFunc(templates, pc.FilterType),
StartInSearchMode: pc.StartSearch,
} }
i, _, err := prompt.Run() i, _, err := prompt.Run()
if err != nil { if err != nil {
return nil, fmt.Errorf("error running selection prompt: %w", err) return nil, err
} }
content, _, err := client.Gitignores.Get(context.Background(), templates[i]) content, _, err := client.Gitignores.Get(context.Background(), templates[i])
@@ -97,7 +110,11 @@ func runPrompt(client *github.Client, height int) (*github.Gitignore, error) {
// commitGitignore writes the content of the selected gitignore template to the .gitignore file. // commitGitignore writes the content of the selected gitignore template to the .gitignore file.
func commitGitignore(content *github.Gitignore, w io.Writer) error { func commitGitignore(content *github.Gitignore, w io.Writer) error {
if _, err := fmt.Fprintf(w, "# Generated by ignr-cli: github.com/onyx-and-iris/ignr-cli\n\n## %s ##\n", content.GetName()); err != nil { if _, err := fmt.Fprintf(
w,
"# Generated by ignr: github.com/onyx-and-iris/ignr\n\n## %s ##\n",
content.GetName(),
); err != nil {
return fmt.Errorf("error writing header to file '%s': %w", gitignoreFileName, err) return fmt.Errorf("error writing header to file '%s': %w", gitignoreFileName, err)
} }
@@ -105,7 +122,7 @@ func commitGitignore(content *github.Gitignore, w io.Writer) error {
return fmt.Errorf("error writing to file '%s': %w", gitignoreFileName, err) return fmt.Errorf("error writing to file '%s': %w", gitignoreFileName, err)
} }
if _, err := fmt.Fprintf(w, "\n# End of ignr-cli\n"); err != nil { if _, err := fmt.Fprintf(w, "\n# End of ignr\n"); err != nil {
return fmt.Errorf("error writing footer to file '%s': %w", gitignoreFileName, err) return fmt.Errorf("error writing footer to file '%s': %w", gitignoreFileName, err)
} }