diff --git a/.github/images/search.gif b/.github/images/search.gif new file mode 100644 index 0000000..ca2e5ad Binary files /dev/null and b/.github/images/search.gif differ diff --git a/.golangci.yml b/.golangci.yml index 776c6fb..28381ee 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -29,3 +29,8 @@ issues: linters: - lll text: "line is 146 characters" + + - path: cmd/search.go + linters: + - gosec + text: "G204: Subprocess launched with function call as argument or cmd arguments" diff --git a/README.md b/README.md index 4da8ca8..5a39df8 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,10 @@ go get github.com/shihanng/gig # Usage -Use the supported language as input arguments, e.g. `Go Elm`. +1. Use the supported language as input arguments, e.g. `Go Elm`. ``` -gig gen Go Elm +$ gig gen Go Elm ### Elm ### # elm-package generated files @@ -52,25 +52,16 @@ repl-temp-* ### 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/ +2. Use the search functionality (depends on [fzf](https://github.com/junegunn/fzf)) -### Go Patch ### -/vendor/ -/Godeps/ ``` +$ gig search +``` + +![gig search demo](./.github/images/search.gif) At the very first run the program will clone the templates repository into `$XDG_CACHE_HOME/gig`. @@ -82,7 +73,7 @@ For more information: gig --help ``` -# Contributing +# Contribute Found a bug or want to hack around? Clone the repository: diff --git a/cmd/gen.go b/cmd/gen.go index 7e6dff5..15d58cd 100644 --- a/cmd/gen.go +++ b/cmd/gen.go @@ -22,10 +22,6 @@ THE SOFTWARE. package cmd import ( - "path/filepath" - - "github.com/shihanng/gig/internal/file" - "github.com/shihanng/gig/internal/order" "github.com/spf13/cobra" ) @@ -44,21 +40,5 @@ https://github.com/toptal/gitignore.git into $XDG_CACHE_HOME/gig.`, } func (c *command) genRunE(cmd *cobra.Command, args []string) error { - items := args - - orders, err := order.ReadOrder(filepath.Join(c.templatePath(), `order`)) - if err != nil { - return err - } - - items = file.Sort(items, orders) - - wc, err := c.newWriteCloser() - if err != nil { - return err - } - - defer wc.Close() - - return file.Generate(wc, c.templatePath(), items...) + return c.generateIgnoreFile(args) } diff --git a/cmd/root.go b/cmd/root.go index e69ee91..3e55fcd 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -28,6 +28,8 @@ import ( "github.com/OpenPeeDeeP/xdg" "github.com/cockroachdb/errors" + "github.com/shihanng/gig/internal/file" + "github.com/shihanng/gig/internal/order" "github.com/shihanng/gig/internal/repo" "github.com/spf13/cobra" "gopkg.in/src-d/go-git.v4/utils/ioutil" @@ -35,9 +37,10 @@ import ( func Execute(w io.Writer, version string) { command := &command{ - output: w, - cachePath: filepath.Join(xdg.CacheHome(), `gig`), - version: version, + output: w, + cachePath: filepath.Join(xdg.CacheHome(), `gig`), + version: version, + searchTool: "fzf -m", } rootCmd := newRootCmd(command) @@ -53,10 +56,16 @@ func Execute(w io.Writer, version string) { genCmd.Flags().BoolVarP(&command.genIsFile, "file", "f", false, "if specified will create .gitignore file in the current working directory") + searchCmd := newSearchCmd(command) + + searchCmd.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, ) if err := rootCmd.Execute(); err != nil { @@ -80,6 +89,7 @@ type command struct { commitHash string cachePath string version string + searchTool string genIsFile bool } @@ -100,6 +110,24 @@ func (c *command) rootRunE(cmd *cobra.Command, args []string) error { return nil } +func (c *command) generateIgnoreFile(items []string) error { + orders, err := order.ReadOrder(filepath.Join(c.templatePath(), `order`)) + if err != nil { + return err + } + + items = file.Sort(items, orders) + + wc, err := c.newWriteCloser() + if err != nil { + return err + } + + defer wc.Close() + + return file.Generate(wc, c.templatePath(), items...) +} + func (c *command) newWriteCloser() (io.WriteCloser, error) { if c.genIsFile { f, err := os.Create(".gitignore") diff --git a/cmd/search.go b/cmd/search.go new file mode 100644 index 0000000..7e9ef06 --- /dev/null +++ b/cmd/search.go @@ -0,0 +1,88 @@ +/* +Copyright © 2019 Shi Han NG + +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 ( + "bufio" + "bytes" + "os" + "os/exec" + "runtime" + "strings" + + "github.com/cockroachdb/errors" + "github.com/shihanng/gig/internal/file" + "github.com/spf13/cobra" +) + +func newSearchCmd(c *command) *cobra.Command { + return &cobra.Command{ + Use: "search", + Short: "Search and select supported templates list", + Long: `Search and select supported templates list. + +This subcommand depends on fzf (https://github.com/junegunn/fzf) +for the search functionality.`, + RunE: func(cmd *cobra.Command, args []string) error { + templates, err := file.List(c.templatePath()) + if err != nil { + return err + } + templateStr := strings.Join(templates, "\n") + + var selected bytes.Buffer + + cmdName, cmdArgs := "sh", []string{"-c"} + if runtime.GOOS == "windows" { + cmdName = "cmd" + cmdArgs[0] = "/c" + } + + shCmd := exec.Command(cmdName, append(cmdArgs, c.searchTool)...) + + shCmd.Stdin = strings.NewReader(templateStr) + shCmd.Stdout = &selected + shCmd.Stderr = os.Stderr + + if err := shCmd.Run(); err != nil { + var ee *exec.ExitError + if errors.As(err, &ee) && ee.ExitCode() == 130 { + return nil + } + return errors.Wrap(err, "cmd: searching") + } + + scanner := bufio.NewScanner(&selected) + + var items []string + for scanner.Scan() { + items = append(items, strings.TrimSpace(scanner.Text())) + } + + if err := scanner.Err(); err != nil { + return errors.Wrap(err, "cmd: read search result") + } + + return c.generateIgnoreFile(items) + }, + } +}