Skip to content

Commit

Permalink
Merge pull request #190 from helmecke/feat/multidirs
Browse files Browse the repository at this point in the history
feat: multiple snippet directory support
  • Loading branch information
RamiAwar authored Aug 19, 2024
2 parents b6ecc50 + a441891 commit f88549c
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 12 deletions.
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,14 @@ 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

[GHEGist]
base_url = "" # GHE base URL
Expand All @@ -269,6 +277,26 @@ Run `pet configure`
auto_sync = false # sync automatically when editing snippets

```
$ 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"
"os"

"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.HiGreenString(description), color.HiYellowString(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.HiGreenString("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 @@ -180,6 +180,7 @@ func countSnippetLines() int {
}

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 @@ -240,7 +241,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 @@ -250,9 +256,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
55 changes: 54 additions & 1 deletion cmd/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ func filter(options []string, tag string) (commands []string, err error) {

snippetTexts[t] = s
if config.Flag.Color || config.Conf.General.Color {
t = config.Conf.General.Format
t = strings.Replace(format, "$command", command, 1)
t = strings.Replace(t, "$description", color.HiRedString(s.Description), 1)
t = strings.Replace(t, "$tags", color.HiCyanString(tags), 1)
Expand Down Expand Up @@ -104,6 +103,60 @@ 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
}

// CountLines returns the number of lines in a certain buffer
func CountLines(r io.Reader) (int, error) {
buf := make([]byte, 32*1024)
Expand Down
10 changes: 10 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type Config struct {
// GeneralConfig is a struct of general config
type GeneralConfig struct {
SnippetFile string
SnippetDirs []string
Editor string
Column int
SelectCmd string
Expand Down Expand Up @@ -88,11 +89,20 @@ func (cfg *Config) Load(file string) error {
_, err := os.Stat(file)
if err == nil {
f, err := os.ReadFile(file)
if err != nil {
return err
}

err = toml.Unmarshal(f, cfg)
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 @@ -22,6 +22,10 @@ require (
golang.org/x/crypto v0.17.0
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 @@ -37,6 +37,8 @@ github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT
github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
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/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
Expand Down
65 changes: 56 additions & 9 deletions snippet/snippet.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"fmt"
"os"
"sort"
"strings"

"github.com/kennygrant/sanitize"
"github.com/knqyf263/pet/config"
"github.com/pelletier/go-toml"
)
Expand All @@ -15,6 +17,7 @@ type Snippets struct {
}

type SnippetInfo struct {
Filename string
Description string
Command string `toml:"command,multiline"`
Tag []string
Expand All @@ -23,18 +26,51 @@ type SnippetInfo struct {

// Load reads toml file.
func (snippets *Snippets) Load() error {
var snippetFiles []string

snippetFile := config.Conf.General.SnippetFile
if _, err := os.Stat(snippetFile); os.IsNotExist(err) {
return nil
if snippetFile != "" {
if _, err := os.Stat(snippetFile); err == nil {
snippetFiles = append(snippetFiles, snippetFile)
} else if !os.IsNotExist(err) {
return fmt.Errorf("failed to load snippet file. %v", err)
} else {
return fmt.Errorf(
`snippet file not found. %s
Please run 'pet configure' and provide a correct file path, or remove this
if you only want to provide snippetdirs instead`,
snippetFile,
)
}
}
f, err := os.ReadFile(snippetFile)
if err != nil {
return fmt.Errorf("failed to load snippet file. %v", err)

for _, dir := range config.Conf.General.SnippetDirs {
if _, err := os.Stat(dir); err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("snippet directory not found. %s", dir)
}
return fmt.Errorf("failed to load snippet directory. %v", err)
}
snippetFiles = append(snippetFiles, getFiles(dir)...)
}

err = toml.Unmarshal(f, snippets)
if err != nil {
return fmt.Errorf("failed to parse snippet file. %v", err)
// Read files and load snippets
for _, file := range snippetFiles {
tmp := Snippets{}
f, err := os.ReadFile(file)
if err != nil {
return fmt.Errorf("failed to load snippet file. %v", err)
}

err = toml.Unmarshal(f, &tmp)
if err != nil {
return fmt.Errorf("failed to parse snippet file. %v", err)
}

for _, snippet := range tmp.Snippets {
snippet.Filename = file
snippets.Snippets = append(snippets.Snippets, snippet)
}
}

snippets.Order()
Expand All @@ -43,11 +79,22 @@ func (snippets *Snippets) Load() error {

// 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)
if err != nil {
return fmt.Errorf("failed to save snippet file. err: %s", err)
}

defer f.Close()
return toml.NewEncoder(f).Encode(snippets)
}
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
}

0 comments on commit f88549c

Please sign in to comment.