first commit

This commit is contained in:
2025-06-15 12:49:07 +01:00
commit 647bdddb55
16 changed files with 737 additions and 0 deletions

16
cmd/context.go Normal file
View File

@@ -0,0 +1,16 @@
package cmd
import (
"context"
"github.com/google/go-github/v72/github"
)
type contextKey string
const clientKey contextKey = "client"
func getClientFromContext(ctx context.Context) (*github.Client, bool) {
client, ok := ctx.Value(clientKey).(*github.Client)
return client, ok
}

108
cmd/new.go Normal file
View File

@@ -0,0 +1,108 @@
package cmd
import (
"context"
"errors"
"fmt"
"io"
"os"
"github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
"github.com/google/go-github/v72/github"
"github.com/spf13/cobra"
)
const gitignoreFileName = ".gitignore"
// newCmd represents the new command.
var newCmd = &cobra.Command{
Use: "new",
Short: "Create a new .gitignore file",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runNewCommand(cmd, args)
},
}
func init() {
rootCmd.AddCommand(newCmd)
}
// runNewCommand is the handler for the 'new' command.
// It retrieves available .gitignore templates from GitHub, prompts the user to select one,
// and writes the selected template to a new .gitignore file.
func runNewCommand(cmd *cobra.Command, _ []string) error {
client, ok := getClientFromContext(cmd.Context())
if !ok {
return errors.New("failed to get GitHub client from context")
}
templates, _, err := client.Gitignores.List(context.Background())
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)
}
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)
if err != nil {
return fmt.Errorf("error opening file '%s': %w", gitignoreFileName, err)
}
defer f.Close()
if err = commitGitignore(content, f); err != nil {
return fmt.Errorf("error committing gitignore file: %w", err)
}
style := lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("#7D56F4")) // nolint: misspell
fmt.Println(style.Render("Created"), selection, style.Render(".gitignore file ✓"))
return nil
}
// runPrompt is a helper function to run the selection prompt for .gitignore templates.
func runPrompt(templates []string, selection *string) error {
var options []huh.Option[string]
for _, template := range templates {
options = append(options, huh.NewOption(template, template))
}
selectionPrompt := huh.NewSelect[string]().
Title("Select a .gitignore template").
Options(options...).
Value(selection)
if err := selectionPrompt.Run(); err != nil {
return fmt.Errorf("error running selection prompt: %w", err)
}
return nil
}
// commitGitignore writes the content of the selected gitignore template to the .gitignore file.
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 {
return fmt.Errorf("error writing header to file '%s': %w", gitignoreFileName, err)
}
if _, err := fmt.Fprintf(w, "%s", *content.Source); err != nil {
return fmt.Errorf("error writing to file '%s': %w", gitignoreFileName, err)
}
if _, err := fmt.Fprintf(w, "\n# End of ignr-cli\n"); err != nil {
return fmt.Errorf("error writing footer to file '%s': %w", gitignoreFileName, err)
}
return nil
}

68
cmd/root.go Normal file
View File

@@ -0,0 +1,68 @@
// Package cmd provides a command-line interface for generating .gitignore files.
package cmd
import (
"context"
"errors"
"fmt"
"runtime/debug"
"strings"
"github.com/google/go-github/v72/github"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var version string // Version of the CLI, set during build time
// rootCmd represents the base command when called without any subcommands.
var rootCmd = &cobra.Command{
Use: "ignr-cli",
Short: "A command-line interface for generating .gitignore files",
Long: `ignr-cli 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.
You may also list available templates and generate .gitignore files based on those templates.`,
SilenceUsage: true,
PersistentPreRun: func(cmd *cobra.Command, _ []string) {
var client *github.Client
if !viper.IsSet("token") || viper.GetString("token") == "" {
client = github.NewClient(nil)
} else {
client = github.NewClient(nil).WithAuthToken(viper.GetString("token"))
}
ctx := context.WithValue(context.Background(), clientKey, 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()
},
}
func init() {
rootCmd.PersistentFlags().StringP("token", "t", "", "GitHub authentication token")
rootCmd.Flags().BoolP("version", "v", false, "Print the version of the CLI")
viper.SetEnvPrefix("GH")
viper.AutomaticEnv()
viper.BindPFlag("token", rootCmd.PersistentFlags().Lookup("token"))
}
// Execute adds all child commands to the root command and sets flags appropriately.
func Execute() error {
if err := rootCmd.Execute(); err != nil {
return err
}
return nil
}