7 Commits

Author SHA1 Message Date
39f762c0e6 make height a persistent flag
make a viper binding for it

env var prefix now IGNR_
2025-06-17 20:53:49 +01:00
4b756ee2fd move file 2025-06-17 20:13:40 +01:00
04e6b0ca49 add update-go-modules workflow 2025-06-17 13:36:36 +01:00
efc8511f26 add default 2025-06-17 13:33:17 +01:00
fac0150fcd md fix 2025-06-17 13:32:27 +01:00
0c8092528a add --height flag to readme 2025-06-17 13:30:44 +01:00
d15402bef1 move api list/get calls into runPrompt
add --height/-H flag to new command

add template language to template. This may be useful if combining gitignores.
2025-06-17 13:30:34 +01:00
6 changed files with 82 additions and 33 deletions

30
.github/workflows/update-go-modules.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: Auto-Update Go Modules
on:
schedule:
- cron: '0 0 * * 1' # Runs every Monday at midnight
jobs:
update-go-modules:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: stable
- name: Update Dependencies
run: |
go get -u ./...
go mod tidy
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add go.mod go.sum
git commit -m "chore: auto-update Go modules"
git push

3
.gitignore vendored
View File

@@ -1,4 +1,6 @@
# Generated by ignr-cli: github.com/onyx-and-iris/ignr-cli # Generated by ignr-cli: github.com/onyx-and-iris/ignr-cli
## 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:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
# #
@@ -28,6 +30,7 @@ go.work.sum
# env file # env file
.env .env
.envrc
# Editor/IDE # Editor/IDE
# .idea/ # .idea/

View File

@@ -14,22 +14,21 @@ Simple no-frills .gitignore generator backed by the Github API.
go install github.com/onyx-and-iris/ignr-cli@latest go install github.com/onyx-and-iris/ignr-cli@latest
``` ```
## Authentication ## Configuration
You can run this tool without authenticating but requests will have a stricter rate limiting. *flags*
If you prefer to authenticate you can pass a token in the following ways: - --token/-t: GitHub authentication token
- note, this tool can be used **without** authenticating but rate limiting will be stricter.
- --height/-H: Height of the selection prompt (default 20)
*Flag* *environment variables*
- --token/-t: Github API Token
*Environment Variable*
```bash ```bash
#!/usr/bin/env bash #!/usr/bin/env bash
export GH_TOKEN=<API Token> export IGNR_TOKEN=<API Token>
export IGNR_HEIGHT=20
``` ```
## Commands ## Commands

View File

@@ -8,9 +8,15 @@ import (
type contextKey string type contextKey string
const clientKey contextKey = "client" var clientKey = contextKey("client")
func getClientFromContext(ctx context.Context) (*github.Client, bool) { // withClient returns a new context with the GitHub client set.
func withClient(ctx context.Context, client *github.Client) context.Context {
return context.WithValue(ctx, clientKey, client)
}
// clientFromContext retrieves the GitHub client from the context.
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

@@ -31,7 +31,7 @@ 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 := context.WithValue(context.Background(), clientKey, client) ctx := withClient(context.Background(), client)
cmd.SetContext(ctx) cmd.SetContext(ctx)
}, },
RunE: func(cmd *cobra.Command, _ []string) error { RunE: func(cmd *cobra.Command, _ []string) error {
@@ -54,11 +54,14 @@ You may also list available templates and generate .gitignore files based on tho
// 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.Flags().BoolP("version", "v", false, "Print the version of the CLI") rootCmd.Flags().BoolP("version", "v", false, "Print the version of the CLI")
viper.SetEnvPrefix("GH") viper.SetEnvPrefix("IGNR")
viper.AutomaticEnv() viper.AutomaticEnv()
viper.BindPFlag("token", rootCmd.PersistentFlags().Lookup("token")) viper.BindPFlag("token", rootCmd.PersistentFlags().Lookup("token"))
viper.BindPFlag("height", rootCmd.PersistentFlags().Lookup("height"))
} }
// main is the entry point of the application. // main is the entry point of the application.

48
new.go
View File

@@ -11,6 +11,7 @@ import (
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"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"
) )
const gitignoreFileName = ".gitignore" const gitignoreFileName = ".gitignore"
@@ -30,29 +31,23 @@ func init() {
} }
// runNewCommand is the handler for the 'new' command. // runNewCommand is the handler for the 'new' command.
// It retrieves available .gitignore templates from GitHub, prompts the user to select one, // It retrieves the selected .gitignore template from GitHub and writes it to the .gitignore file.
// and writes the selected template to a new .gitignore file.
func runNewCommand(cmd *cobra.Command, _ []string) error { func runNewCommand(cmd *cobra.Command, _ []string) error {
client, ok := getClientFromContext(cmd.Context()) height := viper.GetInt("height")
if height <= 0 {
return errors.New("height must be a positive integer")
}
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")
} }
templates, _, err := client.Gitignores.List(context.Background()) content, err := runPrompt(client, height)
if err != nil { if err != nil {
return fmt.Errorf("error listing gitignore templates: %w", err)
}
var selection string
if err := runPrompt(templates, &selection); err != nil {
return fmt.Errorf("error running selection prompt: %w", err) return fmt.Errorf("error running selection prompt: %w", err)
} }
content, _, err := client.Gitignores.Get(context.Background(), selection)
if err != nil {
return fmt.Errorf("error retrieving gitignore template '%s': %w", selection, err)
}
f, err := os.OpenFile(gitignoreFileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600) f, err := os.OpenFile(gitignoreFileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
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)
@@ -67,13 +62,19 @@ func runNewCommand(cmd *cobra.Command, _ []string) error {
Bold(true). Bold(true).
Foreground(lipgloss.Color("#7D56F4")) // nolint: misspell Foreground(lipgloss.Color("#7D56F4")) // nolint: misspell
fmt.Println(style.Render("Created"), selection, style.Render(".gitignore file ✓")) fmt.Println(style.Render("Created"), content.GetName(), style.Render(".gitignore file ✓"))
return nil return nil
} }
// 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(templates []string, selection *string) error { func runPrompt(client *github.Client, height int) (*github.Gitignore, error) {
var selection string
templates, _, err := client.Gitignores.List(context.Background())
if err != nil {
return nil, fmt.Errorf("error retrieving gitignore template list: %w", err)
}
var options []huh.Option[string] var options []huh.Option[string]
for _, template := range templates { for _, template := range templates {
options = append(options, huh.NewOption(template, template)) options = append(options, huh.NewOption(template, template))
@@ -82,17 +83,24 @@ func runPrompt(templates []string, selection *string) error {
selectionPrompt := huh.NewSelect[string](). selectionPrompt := huh.NewSelect[string]().
Title("Select a .gitignore template"). Title("Select a .gitignore template").
Options(options...). Options(options...).
Value(selection) Height(height).
Value(&selection)
if err := selectionPrompt.Run(); err != nil { if err := selectionPrompt.Run(); err != nil {
return fmt.Errorf("error running selection prompt: %w", err) return nil, fmt.Errorf("error running selection prompt: %w", err)
} }
return nil
content, _, err := client.Gitignores.Get(context.Background(), selection)
if err != nil {
return nil, fmt.Errorf("error retrieving gitignore template '%s': %w", selection, err)
}
return content, nil
} }
// 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"); err != nil { if _, err := fmt.Fprintf(w, "# Generated by ignr-cli: github.com/onyx-and-iris/ignr-cli\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)
} }