Compare commits

..

19 Commits
v0.2.1 ... main

Author SHA1 Message Date
f5f89d8515 upd docstring 2025-04-14 09:03:32 +01:00
7a964a031c return err from createTemplate and check it 2025-04-13 17:41:18 +01:00
00641cdc85 move getEnv into main.go 2025-04-13 17:03:57 +01:00
478a172588 upd usage in readme 2025-04-13 14:49:18 +01:00
6adea84322 no need to pass client around 2025-04-13 14:35:43 +01:00
2fa49d0dd0 upd CHANGELOG, README 2025-04-13 14:23:57 +01:00
23ec3b85c1 migrate to cobra 2025-04-13 14:23:48 +01:00
github-actions[bot]
2c1d2ed99c chore: auto-update Go modules 2025-04-07 00:05:05 +00:00
29270a2c14 fix loglevel example in readme 2025-04-05 22:24:49 +01:00
ee86db76a2 fix loglevel example in readme 2025-04-05 22:19:37 +01:00
6aac14b9ed -loglevel now string flag
upd README, CHANGELOG
2025-04-05 22:18:28 +01:00
38b0611e4e update error/logging messages 2025-03-31 22:05:22 +01:00
6b41418c00 split long desc across lines 2025-03-17 22:52:12 +00:00
github-actions[bot]
4800b29707 chore: auto-update Go modules 2025-03-17 00:05:32 +00:00
8a9539ea60 Taskfile skip the generate task if templates already exist
make note of --force flag.
2025-03-16 02:14:25 +00:00
626e40b653 add 0.2.0 and 0.3.0 to CHANGELOG 2025-03-14 21:58:57 +00:00
fd9c7194c1 CLI may now accept multiple template names 2025-03-14 21:53:06 +00:00
388a204299 open file in append mode 2025-03-14 21:49:55 +00:00
a3c2d2cfbf test against empty string 2025-03-13 18:22:52 +00:00
12 changed files with 256 additions and 113 deletions

@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
# [0.5.0] - 2025-04-13
### Changed
- CLI component migrated to Cobra. This introduces the following changes:
- `list` is now a subcommand.
- `create` has been added as a subcommand, use it to create a new .gitignore file.
- Env var `GIGNORE_TEMPLATE_DIR` changed to `GIGNORE_TEMPLATE_ROOT`
- Env var `GIGNORE_LOGLEVEL` may now be used to set the logging level.
# [0.4.0] - 2025-04-05
### Changed
- `-loglevel` flag is now of type string. It accepts any one of trace, debug, info, warn, error, fatal or panic.
- It defaults to warn.
# [0.3.0] - 2025-14-03
### Added
- CLI may now accept multiple template names, example `gignore go python`. One will be appended after the other.
### Changed
- Filewriter now opens file in append mode.
# [0.2.0] - 2025-10-03
### Fixed
- Template .gitignore are now written concurrently.
# [0.1.0] - 2025-09-03
### Added

@ -3,7 +3,7 @@
![macOS](https://img.shields.io/badge/mac%20os-000000?style=for-the-badge&logo=macos&logoColor=F0F0F0)
# Gignore - Generate .gitinore files
# Gignore - Generate .gitignore files
## Install
@ -23,23 +23,28 @@ task install
## Usage
```bash
Usage of gignore:
gignore [flags] <template>
Usage:
gignore [flags]
gignore [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
create Create a new .gitignore file
help Help about any command
list List all .gitignore files in the root template repository
Flags:
-dir string
directory containing .gitignore templates (default "gitignoreio")
-l int
log level (shorthand) (default 3)
-list
list available templates
-loglevel int
log level (default 3)
-ls
list available templates (shorthand)
-h, --help help for gignore
-l, --loglevel string Log level (trace, debug, info, warn, error, fatal, panic) (default "warn")
-r, --root string Root directory to search for .gitignore files (default "gitignoreio")
Example:
gignore go
Use "gignore [command] --help" for more information about a command.
```
For example:
```bash
gignore create go
```
## Custom Templates
@ -49,13 +54,35 @@ It's possible to add your own custom templates, simply create a directory in `in
Then pass the dir name as a flag, for example:
```bash
gignore -dir=custom go
gignore -root=custom create go
```
You may set an environment variable `GIGNORE_TEMPLATE_DIR` to avoid passing the `-dir` flag each time.
You may set an environment variable `GIGNORE_TEMPLATE_ROOT` to avoid passing the `-root` flag each time.
If a template is requested but not found in the custom directory then the gitignoreio registry will act as a fallback.
## Logging
The `-loglevel` flag allows you to control the verbosity of the application's logging output.
Acceptable values for this flag are:
- `trace`
- `debug`
- `info`
- `warn`
- `error`
- `fatal`
- `panic`
For example, to set the log level to `debug`, you can use:
```bash
gignore -loglevel=debug create python
```
The default log level is `warn` if the flag is not specified.
## Special Thanks
[gitignore.io][gitignoreio] For providing such a useful .gitignore service

@ -35,19 +35,26 @@ tasks:
- go fmt ./...
generate:
desc: Generate the gitignore.io templates
desc: |
Generate the gitignore.io templates.
This task will be skipped if the templates already exist.
You may use the `--force` flag to regenerate the templates.
cmds:
- go generate ./...
status:
- ls internal/registry/templates/gitignoreio/*.gitignore >/dev/null
build-windows:
desc: Build the gignore project for Windows
cmds:
- GOOS=windows GOARCH=amd64 go build -o {{.WINDOWS}} -ldflags="-X main.Version={{.GIT_COMMIT}}" ./cmd/{{.PROGRAM}}
internal: true
build-linux:
desc: Build the gignore project for Linux
cmds:
- GOOS=linux GOARCH=amd64 go build -o {{.LINUX}} -ldflags="-X main.Version={{.GIT_COMMIT}}" ./cmd/{{.PROGRAM}}
internal: true
test:
desc: Run tests

47
cmd/gignore/create.go Normal file

@ -0,0 +1,47 @@
// Package main provides the entry point for the gignore CLI tool,
// including commands like listing available .gitignore templates.
package main
import (
"fmt"
"github.com/spf13/cobra"
)
var createCmd = &cobra.Command{
Use: "create",
Short: "Create a new .gitignore file",
Long: `Create a new .gitignore file in the current directory.
At least one template must be specified.
Multiple templates can be specified, and they will be combined into a single .gitignore file.
Example:
gignore create python
gignore create python go`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
cmd.Help()
return
}
for _, arg := range args {
err := createTemplate(arg)
cobra.CheckErr(err)
}
},
}
// init initialises the create command and adds it to the root command.
func init() {
rootCmd.AddCommand(createCmd)
}
// createTemplate creates a new .gitignore file using the specified template.
func createTemplate(template string) error {
err := client.Create(template)
if err != nil {
return err
}
fmt.Printf("√ created %s .gitignore file\n", template)
return nil
}

46
cmd/gignore/list.go Normal file

@ -0,0 +1,46 @@
// Package main provides the entry point for the gignore CLI tool,
// including commands like listing available .gitignore templates.
package main
import (
"fmt"
"strings"
"github.com/spf13/cobra"
)
var listCmd = &cobra.Command{
Use: "list",
Short: "List all .gitignore files in the root template repository",
Long: `List all .gitignore files in the root template repository.
This command will search the root template repository for .gitignore files and print their paths to the console.
The root template repository can be specified using the --root flag.
You can use this command to quickly find all available .gitignore templates.
Example:
gignore --root=<path> list`,
Run: func(_ *cobra.Command, _ []string) {
err := listTemplates()
cobra.CheckErr(err)
},
}
// init initialises the list command and adds it to the root command.
func init() {
rootCmd.AddCommand(listCmd)
}
// listTemplates retrieves and prints all .gitignore templates available from the gignore client.
func listTemplates() error {
templates, err := client.List()
if err != nil {
return err
}
var output strings.Builder
for _, template := range templates {
output.WriteString(template + "\n")
}
fmt.Print(output.String())
return nil
}

@ -1,84 +1,60 @@
// Package main provides the entry point for the gignore command-line tool,
// which generates .gitignore files based on specified templates.
// Package main provides the entry point for the gignore CLI tool,
// including commands like listing available .gitignore templates.
package main
import (
"flag"
"fmt"
"slices"
"os"
"github.com/onyx-and-iris/gignore"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func main() {
flag.Usage = func() {
w := flag.CommandLine.Output()
var client *gignore.Client
fmt.Fprint(w, "Usage of gignore:\n")
fmt.Fprintf(w, " gignore [flags] <template>\n")
fmt.Fprint(w, "\n")
var rootCmd = &cobra.Command{
Use: "gignore",
Short: "A command line tool to manage .gitignore files",
Long: `gignore is a command line tool that helps you manage your .gitignore files.
You can use it to list available templates and create new .gitignore files.
It supports various programming languages.
Example:
gignore list
gignore create python`,
PersistentPreRun: func(cmd *cobra.Command, _ []string) {
// Initialise the logger
loglevel, err := log.ParseLevel(cmd.Flag("loglevel").Value.String())
cobra.CheckErr(err)
log.SetLevel(loglevel)
fmt.Fprint(w, "Flags:\n")
flag.PrintDefaults()
// Initialise the gignore client
client = gignore.New(
gignore.WithTemplateDirectory(cmd.Flag("root").Value.String()),
)
},
Run: func(cmd *cobra.Command, _ []string) {
cmd.Help()
},
}
fmt.Fprint(w, "\n")
fmt.Fprintf(w, "Example:\n")
fmt.Fprint(w, " gignore go\n")
}
var (
list bool
templateDir string
loglevel int
)
flag.BoolVar(&list, "list", false, "list available templates")
flag.BoolVar(&list, "ls", false, "list available templates (shorthand)")
flag.StringVar(
&templateDir,
"dir",
getEnv("GIGNORE_TEMPLATE_DIR", "gitignoreio"),
"directory containing .gitignore templates",
)
flag.IntVar(&loglevel, "loglevel", int(log.WarnLevel), "log level")
flag.IntVar(&loglevel, "l", int(log.WarnLevel), "log level (shorthand)")
flag.Parse()
if slices.Contains(log.AllLevels, log.Level(loglevel)) {
log.SetLevel(log.Level(loglevel))
}
client := gignore.New(gignore.WithTemplateDirectory(templateDir))
if list {
if err := listTemplates(client); err != nil {
log.Fatalf("failed to list templates: %v", err)
// init initialises the root command and adds global flags.
func init() {
getEnv := func(key, defaultValue string) string {
value := os.Getenv(key)
if value == "" {
return defaultValue
}
return
return value
}
args := flag.Args()
if len(args) != 1 {
flag.Usage()
return
}
err := client.Create(args[0])
if err != nil {
log.Fatalf("failed to create .gitignore file: %v", err)
}
fmt.Printf("√ created %s .gitignore file\n", args[0])
rootCmd.PersistentFlags().
StringP("root", "r", getEnv("GIGNORE_TEMPLATE_ROOT", gignore.DefaultTemplateDirectory), "Root directory to search for .gitignore files")
rootCmd.PersistentFlags().
StringP("loglevel", "l", getEnv("GIGNORE_LOGLEVEL", "warn"), "Log level (trace, debug, info, warn, error, fatal, panic)")
}
func listTemplates(client *gignore.Client) error {
templates, err := client.List()
if err != nil {
return err
func main() {
if err := rootCmd.Execute(); err != nil {
log.Fatal(err)
}
for _, template := range templates {
fmt.Println(template)
}
return nil
}

@ -1,11 +0,0 @@
package main
import "os"
func getEnv(key, defaultValue string) string {
value := os.Getenv(key)
if len(value) == 0 {
return defaultValue
}
return value
}

@ -1,17 +1,16 @@
// Package gignore provides functionality for handling template errors and registry operations.
// Package gignore provides a way to manage .gitignore files and templates.
package gignore
import (
"fmt"
"github.com/onyx-and-iris/gignore/internal/registry"
"strings"
)
type templateNotFoundError struct {
template string
registry *registry.TemplateRegistry
template string
templatesSearched []string
}
func (e *templateNotFoundError) Error() string {
return fmt.Sprintf("template '%s' not found in %s registry", e.template, e.registry.Directory)
return fmt.Sprintf("template '%s' not found in %s registry", e.template, strings.Join(e.templatesSearched, ", "))
}

@ -10,6 +10,9 @@ import (
//go:generate go run cmd/gen/main.go
// DefaultTemplateDirectory is the default directory for .gitignore templates.
const DefaultTemplateDirectory = "gitignoreio"
// Client is a client for managing .gitignore templates.
type Client struct {
registry *registry.TemplateRegistry
@ -42,22 +45,23 @@ func (c *Client) Create(template string) error {
return err
}
if !ok {
templateNotFoundErr := &templateNotFoundError{template, c.registry}
if c.registry.Directory == "gitignoreio" {
templateNotFoundErr := &templateNotFoundError{template, []string{c.registry.Directory}}
if c.registry.Directory == DefaultTemplateDirectory {
return templateNotFoundErr
}
log.Errorf("%s. Checking default registry...", templateNotFoundErr)
c.registry.Directory = "gitignoreio"
c.registry.Directory = DefaultTemplateDirectory
ok, err = c.registry.Contains(template)
if err != nil {
return err
}
if !ok {
templateNotFoundErr.templatesSearched = append(templateNotFoundErr.templatesSearched, c.registry.Directory)
return templateNotFoundErr
}
log.Infof("template '%s' found in default gitignoreio registry", template)
log.Debugf("template '%s' found in gitignoreio registry", template)
} else {
log.Debugf("template '%s' found in %s registry", template, c.registry.Directory)
}
content, err := c.registry.Get(template)

7
go.mod

@ -5,6 +5,11 @@ go 1.24.0
require (
github.com/cuonglm/gogi v1.0.1
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.9.1
)
require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect
golang.org/x/sys v0.32.0 // indirect
)

14
go.sum

@ -1,17 +1,27 @@
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cuonglm/gogi v1.0.1 h1:Jotx6uAfFK6YHFOOek37R9y3Ae9qp/nUt/3mYGCl+44=
github.com/cuonglm/gogi v1.0.1/go.mod h1:ZLU5wl3d+FSSkiYYDpmPJI2dWdAGj8q28rFjpeWv1g4=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

@ -29,14 +29,14 @@ func (fw *FileWriter) writeContent(content []byte, dst io.Writer) (int64, error)
}
func (fw *FileWriter) Write(content []byte) (int, error) {
f, err := os.Create(fw.targetFileName)
f, err := os.OpenFile(fw.targetFileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
if err != nil {
return 0, err
}
defer f.Close()
const header = `# Auto-generated .gitignore by gignore: github.com/onyx-and-iris/gignore`
const footer = `# End of gignore: github.com/onyx-and-iris/gignore`
const header = "# Auto-generated .gitignore by gignore: github.com/onyx-and-iris/gignore\n"
const footer = "\n# End of gignore: github.com/onyx-and-iris/gignore\n"
var sz int64
n, err := fw.writeContent([]byte(header), f)