Skip to content

Commit

Permalink
Merge pull request #50 from shihanng/issues-39__autogen
Browse files Browse the repository at this point in the history
Add autogen subcommand
  • Loading branch information
shihanng committed Dec 14, 2019
2 parents 6f0f241 + 22a4cb7 commit 2881945
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 11 deletions.
32 changes: 25 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# `gig` -- .*g*it*i*gnore *g*enerator
# `gig` -- .gitignore generator

[![](https://github.com/shihanng/gig/workflows/main/badge.svg?branch=develop)](https://github.com/shihanng/gig/actions?query=workflow%3Amain)
[![](https://github.com/shihanng/gig/workflows/release/badge.svg?branch=develop)](https://github.com/shihanng/gig/actions?query=workflow%3Arelease)
Expand Down Expand Up @@ -39,7 +39,9 @@ go get github.com/shihanng/gig

# Usage

1. Use the supported language as input arguments, e.g. `Go Elm`.
There are several ways you can generate the `.gitignore` file:

### Using the supported language as input arguments

```
$ gig gen Go Elm
Expand All @@ -55,7 +57,7 @@ repl-temp-*
...
```

2. Use the search functionality (depends on [fzf](https://github.com/junegunn/fzf))
### Using the search functionality (depends on [fzf](https://github.com/junegunn/fzf))

```
$ gig search
Expand All @@ -67,13 +69,19 @@ At the very first run the program will clone the templates repository <https://g
into `$XDG_CACHE_HOME/gig`.
This means that internet connection is not required after the first successful run.

For more information:
### Using the EXPERIMENTAL auto generate functionality

```
$ gig autogen
```

### For more information, see

```
gig --help
```

# Contribute
# Development

Found a bug or want to hack around? Clone the repository:

Expand All @@ -93,5 +101,15 @@ Where `test` run the tests that can be run locally.
`integ-test` run the tests that require internet access.
`lint` help you write better Go codes.

If you are adding new subcommand, we are using [cobra](https://github.com/spf13/cobra)
and managed the version with [tools.go](./tools.go) and [go.mod](./go.mod).
Add subcommand with [cobra](https://github.com/spf13/cobra)
(version managed in [tools.go](./tools.go) with [go.mod](./go.mod)):

```
$ cobra --config .cobra.yaml add <new subcommand>
```

Update golden file:

```
$ go test . -tags=integration -update
```
145 changes: 145 additions & 0 deletions cmd/autogen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
Copyright © 2019 Shi Han NG <shihanng@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package cmd

import (
"bytes"
"io"
"io/ioutil"
"os"
"path/filepath"

"github.com/cockroachdb/errors"
"github.com/shihanng/gig/internal/file"
"github.com/spf13/cobra"
"github.com/src-d/enry/v2"
)

func newAutogenCmd(c *command) *cobra.Command {
return &cobra.Command{
Use: "autogen",
Short: "(EXPERIMENTAL) I'm Feeling Lucky",
Long: `(EXPERIMENTAL) Automatically generate .gitignore based on the content of the current directory.
This feature uses github.com/src-d/enry
(which is a port of GitHub's linguist library) to detect
the programming languages in the current working directory
and pass that to the gen command. Known limitation of this
feature is that it could not detect a framework.`,
RunE: c.autogenRunE,
}
}

// Heavily borrowed from:
// https://github.com/src-d/enry/blob/697929e1498cbdb7726a4d3bf4c48e706ee8c967/cmd/enry/main.go#L27
func (c *command) autogenRunE(cmd *cobra.Command, args []string) error {
templates, err := file.List(c.templatePath())
if err != nil {
return err
}

supported := make(map[string]bool, len(templates))

for _, t := range templates {
supported[file.Canon(t)] = true
}

var found []string

errWalk := filepath.Walk(".", func(path string, f os.FileInfo, err error) error {
if err != nil {
return filepath.SkipDir
}

if !f.Mode().IsDir() && !f.Mode().IsRegular() {
return nil
}

if enry.IsVendor(path) ||
enry.IsDotFile(path) ||
enry.IsDocumentation(path) ||
enry.IsConfiguration(path) {
if f.IsDir() {
return filepath.SkipDir
}

return nil
}

if f.IsDir() {
return nil
}

content, err := readFile(path, 16*1024)
if err != nil {
return nil
}

language := enry.GetLanguage(path, content)
if language == enry.OtherLanguage {
return nil
}

if supported[file.Canon(language)] {
found = append(found, language)
}

return nil
})

if errWalk != nil {
return errors.Wrap(err, "cmd: walking file")
}

return c.generateIgnoreFile(found)
}

func readFile(path string, limit int64) ([]byte, error) {
if limit <= 0 {
b, err := ioutil.ReadFile(path)
return b, errors.Wrap(err, "cmd: read file")
}

f, err := os.Open(path)
if err != nil {
return nil, errors.Wrap(err, "cmd: open file")
}

defer f.Close()

st, err := f.Stat()
if err != nil {
return nil, errors.Wrap(err, "cmd: get file stat")
}

size := st.Size()
if limit > 0 && size > limit {
size = limit
}

buf := bytes.NewBuffer(nil)
buf.Grow(int(size))

_, err = io.Copy(buf, io.LimitReader(f, limit))

return buf.Bytes(), errors.Wrap(err, "cmd: copy to buffer")
}
12 changes: 10 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,12 @@ func Execute(w io.Writer, version string) {
rootCmd := newRootCmd(command)

rootCmd.PersistentFlags().StringVarP(&command.commitHash, "commit-hash", "c", "",
"use templates from a specific commit hash of github.com/toptal/gitignore")
`use templates from a specific commit hash of
github.com/toptal/gitignore`)

rootCmd.PersistentFlags().StringVarP(&command.cachePath, "cache-path", "", filepath.Join(xdg.CacheHome(), `gig`),
"location where the content of github.com/toptal/gitignore will be cached in")
`location where the content of github.com/toptal/gitignore
will be cached in`)

genCmd := newGenCmd(command)

Expand All @@ -61,11 +63,17 @@ func Execute(w io.Writer, version string) {
searchCmd.Flags().BoolVarP(&command.genIsFile, "file", "f", false,
"if specified will create .gitignore file in the current working directory")

autogenCmd := newAutogenCmd(command)

autogenCmd.Flags().BoolVarP(&command.genIsFile, "file", "f", false,
"if specified will create .gitignore file in the current working directory")

rootCmd.AddCommand(
newListCmd(command),
genCmd,
newVersionCmd(command),
searchCmd,
autogenCmd,
)

if err := rootCmd.Execute(); err != nil {
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.6.1 // indirect
github.com/src-d/enry/v2 v2.1.0
github.com/stretchr/testify v1.3.0
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 // indirect
gopkg.in/src-d/go-git.v4 v4.13.1
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,12 @@ github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.6.1 h1:VPZzIkznI1YhVMRi6vNFLHSwhnhReBfgTxIPccpfdZk=
github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
github.com/src-d/enry/v2 v2.1.0 h1:z1L8t+B8bh3mmjPkJrgOTnVRpFGmTPJsplHX9wAn6BI=
github.com/src-d/enry/v2 v2.1.0/go.mod h1:qQeCMRwzMF3ckeGr+h0tJLdxXnq+NVZsIDMELj0t028=
github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
github.com/src-d/go-oniguruma v1.1.0 h1:EG+Nm5n2JqWUaCjtM0NtutPxU7ZN5Tp50GWrrV8bTww=
github.com/src-d/go-oniguruma v1.1.0/go.mod h1:chVbff8kcVtmrhxtZ3yBVLLquXbzCS6DrxQaAK/CeqM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
Expand All @@ -172,6 +176,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/toqueteos/trie v1.0.0 h1:8i6pXxNUXNRAqP246iibb7w/pSFquNTQ+uNfriG7vlk=
github.com/toqueteos/trie v1.0.0/go.mod h1:Ywk48QhEqhU1+DwhMkJ2x7eeGxDHiGkAdc9+0DYcbsM=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
Expand Down Expand Up @@ -245,6 +251,8 @@ gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOA
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE=
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
gopkg.in/toqueteos/substring.v1 v1.0.2 h1:urLqCeMm6x/eTuQa1oZerNw8N1KNOIp5hD5kGL7lFsE=
gopkg.in/toqueteos/substring.v1 v1.0.2/go.mod h1:Eb2Z1UYehlVK8LYW2WBVR2rwbujsz3aX8XDrM1vbNew=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
Expand Down
22 changes: 20 additions & 2 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ func (s *MainTestSuite) TearDownSuite() {
s.Require().NoError(os.RemoveAll(s.tempDir))
}

func (s *MainTestSuite) TestCli() {
func (s *MainTestSuite) TestGen() {
os.Args = []string{"gig", "gen", "go", "--cache-path", s.tempDir}

actual := new(bytes.Buffer)

cmd.Execute(actual, "test")

goldenPath := `./testdata/cli.golden`
goldenPath := `./testdata/gen.golden`

if *update {
s.Require().NoError(ioutil.WriteFile(goldenPath, actual.Bytes(), 0644))
Expand Down Expand Up @@ -130,6 +130,24 @@ func (s *MainTestSuite) TestVersion() {
s.Assert().Equal(expected, actual.String())
}

func (s *MainTestSuite) TestAutogen() {
os.Args = []string{"gig", "autogen", "--cache-path", s.tempDir}

actual := new(bytes.Buffer)

cmd.Execute(actual, "test")

goldenPath := `./testdata/autogen.golden`

if *update {
s.Require().NoError(ioutil.WriteFile(goldenPath, actual.Bytes(), 0644))
}

expected, err := ioutil.ReadFile(goldenPath)
s.Require().NoError(err)
s.Assert().Equal(expected, actual.Bytes())
}

func TestMainTestSuite(t *testing.T) {
suite.Run(t, new(MainTestSuite))
}
32 changes: 32 additions & 0 deletions testdata/autogen.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

### Go ###
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

### Go Patch ###
/vendor/
/Godeps/

### Text ###
*.doc
*.docx
*.log
*.msg
*.pages
*.rtf
*.txt
*.wpd
*.wps
File renamed without changes.

0 comments on commit 2881945

Please sign in to comment.