Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: multiple snippet directory support #190

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,37 @@ Run `pet configure`
id = "" # GitLab Snippets ID
visibility = "private" # public or internal or private
auto_sync = false # sync automatically when editing snippets
```

## Multi directory and multi file setup

Directories musst be specified as an array.
All `toml` files will be scraped and found snippets will be added.

Example1: single directory

```
$ pet configure
[General]
...
snippetdirs = ["/path/to/some/snippets/"]
...
```

Example2: multiple directories

```
$ pet configure
[General]
...
snippetdirs = ["/path/to/some/snippets/", "/more/snippets/"]
...
```
If `snippetfile` setting is omitted, new snippets will be added in a seperate file to the first directory. The generated filename is time based.

Snippet files in `snippetdirs` will not be added to Gist or GitLab. You've to do version control manually.


## Selector option
Example1: Change layout (bottom up)

Expand Down
24 changes: 24 additions & 0 deletions cmd/edit.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package cmd

import (
"fmt"
"io/ioutil"

"github.com/knqyf263/pet/config"
petSync "github.com/knqyf263/pet/sync"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"gopkg.in/alessio/shellescape.v1"
)

// editCmd represents the edit command
Expand All @@ -17,9 +20,26 @@ var editCmd = &cobra.Command{
}

func edit(cmd *cobra.Command, args []string) (err error) {
flag := config.Flag
editor := config.Conf.General.Editor
snippetFile := config.Conf.General.SnippetFile

var options []string
if flag.Query != "" {
options = append(options, fmt.Sprintf("--query %s", shellescape.Quote(flag.Query)))
}

if len(config.Conf.General.SnippetDirs) > 0 {
snippetFile, err = selectFile(options, flag.FilterTag)
if err != nil {
return err
}
}

if snippetFile == "" {
return errors.New("No sippet file seleted")
}

// file content before editing
before := fileContent(snippetFile)

Expand Down Expand Up @@ -50,4 +70,8 @@ func fileContent(fname string) string {

func init() {
RootCmd.AddCommand(editCmd)
editCmd.Flags().StringVarP(&config.Flag.Query, "query", "q", "",
`Initial value for query`)
editCmd.Flags().StringVarP(&config.Flag.FilterTag, "tag", "t", "",
`Filter tag`)
}
4 changes: 4 additions & 0 deletions cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ func list(cmd *cobra.Command, args []string) error {
fmt.Fprintf(color.Output, "%s : %s\n",
color.GreenString(description), color.YellowString(command))
} else {
if config.Flag.Debug {
fmt.Fprintf(color.Output, "%12s %s\n",
color.RedString(" Filename:"), snippet.Filename)
}
fmt.Fprintf(color.Output, "%12s %s\n",
color.GreenString("Description:"), snippet.Description)
if strings.Contains(snippet.Command, "\n") {
Expand Down
9 changes: 7 additions & 2 deletions cmd/new.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func scan(message string) (string, error) {
}

func new(cmd *cobra.Command, args []string) (err error) {
var filename string = ""
var command string
var description string
var tags []string
Expand Down Expand Up @@ -103,7 +104,12 @@ func new(cmd *cobra.Command, args []string) (err error) {
}
}

if config.Conf.General.SnippetFile != "" {
filename = config.Conf.General.SnippetFile
}

newSnippet := snippet.SnippetInfo{
Filename: filename,
Description: description,
Command: command,
Tag: tags,
Expand All @@ -113,9 +119,8 @@ func new(cmd *cobra.Command, args []string) (err error) {
return err
}

snippetFile := config.Conf.General.SnippetFile
if config.Conf.Gist.AutoSync {
return petSync.AutoSync(snippetFile)
return petSync.AutoSync(filename)
}

return nil
Expand Down
54 changes: 54 additions & 0 deletions cmd/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,57 @@ func filter(options []string, tag string) (commands []string, err error) {
}
return commands, nil
}

func selectFile(options []string, tag string) (snippetFile string, err error) {
var snippets snippet.Snippets
if err := snippets.Load(); err != nil {
return snippetFile, fmt.Errorf("Load snippet failed: %v", err)
}

if 0 < len(tag) {
var filteredSnippets snippet.Snippets
for _, snippet := range snippets.Snippets {
for _, t := range snippet.Tag {
if tag == t {
filteredSnippets.Snippets = append(filteredSnippets.Snippets, snippet)
}
}
}
snippets = filteredSnippets
}

snippetTexts := map[string]snippet.SnippetInfo{}
var text string
for _, s := range snippets.Snippets {
command := s.Command
if strings.ContainsAny(command, "\n") {
command = strings.Replace(command, "\n", "\\n", -1)
}
t := fmt.Sprintf("[%s]: %s", s.Description, command)

tags := ""
for _, tag := range s.Tag {
tags += fmt.Sprintf(" #%s", tag)
}
t += tags

snippetTexts[t] = s
text += t + "\n"
}

var buf bytes.Buffer
selectCmd := fmt.Sprintf("%s %s",
config.Conf.General.SelectCmd, strings.Join(options, " "))
err = run(selectCmd, strings.NewReader(text), &buf)
if err != nil {
return snippetFile, nil
}

lines := strings.Split(strings.TrimSuffix(buf.String(), "\n"), "\n")

for _, line := range lines {
snippetInfo := snippetTexts[line]
snippetFile = fmt.Sprint(snippetInfo.Filename)
}
return snippetFile, nil
}
25 changes: 16 additions & 9 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os/exec"
"path/filepath"
"runtime"

"github.com/BurntSushi/toml"
"github.com/pkg/errors"
)
Expand All @@ -15,19 +16,20 @@ var Conf Config

// Config is a struct of config
type Config struct {
General GeneralConfig `toml:"General"`
Gist GistConfig `toml:"Gist"`
GitLab GitLabConfig `toml:"GitLab"`
General GeneralConfig `toml:"General"`
Gist GistConfig `toml:"Gist"`
GitLab GitLabConfig `toml:"GitLab"`
}

// GeneralConfig is a struct of general config
type GeneralConfig struct {
SnippetFile string `toml:"snippetfile"`
Editor string `toml:"editor"`
Column int `toml:"column"`
SelectCmd string `toml:"selectcmd"`
Backend string `toml:"backend"`
SortBy string `toml:"sortby"`
SnippetFile string `toml:"snippetfile"`
SnippetDirs []string `toml:"snippetdirs"`
Editor string `toml:"editor"`
Column int `toml:"column"`
SelectCmd string `toml:"selectcmd"`
Backend string `toml:"backend"`
SortBy string `toml:"sortby"`
}

// GistConfig is a struct of config for Gist
Expand Down Expand Up @@ -73,7 +75,12 @@ func (cfg *Config) Load(file string) error {
if err != nil {
return err
}
var snippetdirs []string
cfg.General.SnippetFile = expandPath(cfg.General.SnippetFile)
for _, dir := range cfg.General.SnippetDirs {
snippetdirs = append(snippetdirs, expandPath(dir)) // note the = instead of :=
}
cfg.General.SnippetDirs = snippetdirs
return nil
}

Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ require (
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
)

require (
github.com/kennygrant/sanitize v1.2.4
gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jroimartin/gocui v0.4.0 h1:52jnalstgmc25FmtGcWqa0tcbMEWS6RpFLsOIO+I+E8=
github.com/jroimartin/gocui v0.4.0/go.mod h1:7i7bbj99OgFHzo7kB2zPb8pXLqMBSQegY7azfqXMkyY=
github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o=
github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
Expand Down
41 changes: 33 additions & 8 deletions snippet/snippet.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"fmt"
"os"
"sort"
"strings"

"github.com/BurntSushi/toml"
"github.com/kennygrant/sanitize"
"github.com/knqyf263/pet/config"
)

Expand All @@ -15,34 +17,57 @@ type Snippets struct {
}

type SnippetInfo struct {
Filename string `toml:"-"`
Description string `toml:"description"`
Command string `toml:"command"`
Tag []string `toml:"tag"`
Output string `toml:"output"`
Output string `toml:"output,omitempty"`
}

// Load reads toml file.
func (snippets *Snippets) Load() error {
snippetFile := config.Conf.General.SnippetFile
if _, err := os.Stat(snippetFile); os.IsNotExist(err) {
return nil
var files []string
if config.Conf.General.SnippetFile != "" {
files = append(files, config.Conf.General.SnippetFile)
}
if _, err := toml.DecodeFile(snippetFile, snippets); err != nil {
return fmt.Errorf("Failed to load snippet file. %v", err)
for _, dir := range config.Conf.General.SnippetDirs {
files = append(files, getFiles(dir)...)
}

for _, file := range files {
tmp := Snippets{}
if _, err := toml.DecodeFile(file, &tmp); err != nil {
return fmt.Errorf("Failed to load snippet file. %v", err)
}
for _, snippet := range tmp.Snippets {
snippet.Filename = file
snippets.Snippets = append(snippets.Snippets, snippet)
}
}

snippets.Order()
return nil
}

// Save saves the snippets to toml file.
func (snippets *Snippets) Save() error {
snippetFile := config.Conf.General.SnippetFile
var snippetFile string
var newSnippets Snippets
for _, snippet := range snippets.Snippets {
if snippet.Filename == "" {
snippetFile = config.Conf.General.SnippetDirs[0] + fmt.Sprintf("%s.toml", strings.ToLower(sanitize.BaseName(snippet.Description)))
newSnippets.Snippets = append(newSnippets.Snippets, snippet)
} else if snippet.Filename == config.Conf.General.SnippetFile {
snippetFile = config.Conf.General.SnippetFile
newSnippets.Snippets = append(newSnippets.Snippets, snippet)
}
}
f, err := os.Create(snippetFile)
defer f.Close()
if err != nil {
return fmt.Errorf("Failed to save snippet file. err: %s", err)
}
return toml.NewEncoder(f).Encode(snippets)
return toml.NewEncoder(f).Encode(newSnippets)
}

// ToString returns the contents of toml file.
Expand Down
28 changes: 28 additions & 0 deletions snippet/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package snippet

import (
"log"
"os"
"path/filepath"
"regexp"
)

func getFiles(path string) (fileList []string) {
tomlRegEx, err := regexp.Compile("^.+\\.(toml)$")
if err != nil {
log.Fatal(err)
}

err = filepath.Walk(path, func(path string, f os.FileInfo, err error) error {
if err == nil && tomlRegEx.MatchString(f.Name()) {
fileList = append(fileList, path)
}
return nil
})

if err != nil {
panic(err)
}

return fileList
}