Skip to content

Commit

Permalink
feat: scan snippets (maaslalani#33)
Browse files Browse the repository at this point in the history
Should resolve maaslalani#24 

Note that this isn't currently gated by a flag, it is simply on all the
time. I think that makes sense, but I'm open to changing it.
:slightly_smiling_face:

Other ideas would be:
- A config setting, as mentioned in maaslalani#24 
- A flag `--scan`
- A sub-command `nap scan`

Some minor other changes in this PR:
- moved some config funcs to `config.go` to help with organizing
- noticed `snippet.go` using a hardcoded value for the default filename
instead of the const

---------

Signed-off-by: jolheiser <[email protected]>
  • Loading branch information
jolheiser authored Mar 26, 2023
1 parent 95da92f commit 3059aa7
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 71 deletions.
62 changes: 62 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package main

import (
"errors"
"io/fs"
"os"
"path/filepath"
"strings"

"github.com/adrg/xdg"
"github.com/caarlos0/env/v6"
"gopkg.in/yaml.v3"
)

// Config holds the configuration options for the application.
Expand Down Expand Up @@ -54,3 +60,59 @@ func newConfig() Config {
// default helpers for the configuration.
// We use $XDG_DATA_HOME to avoid cluttering the user's home directory.
func defaultHome() string { return filepath.Join(xdg.DataHome, "nap") }

// defaultConfig returns the default config path
func defaultConfig() string {
if c := os.Getenv("NAP_CONFIG"); c != "" {
return c
}
cfgPath, err := xdg.ConfigFile("nap/config.yaml")
if err != nil {
return "config.yaml"
}
return cfgPath
}

// readConfig returns a configuration read from the environment.
func readConfig() Config {
config := newConfig()
fi, err := os.Open(defaultConfig())
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return newConfig()
}
if fi != nil {
defer fi.Close()
if err := yaml.NewDecoder(fi).Decode(&config); err != nil {
return newConfig()
}
}

if err := env.Parse(&config); err != nil {
return newConfig()
}

if strings.HasPrefix(config.Home, "~") {
home, err := os.UserHomeDir()
if err == nil {
config.Home = filepath.Join(home, config.Home[1:])
}
}

return config
}

// writeConfig returns a configuration read from the environment.
func (config Config) writeConfig() error {
fi, err := os.Create(defaultConfig())
if err != nil {
return err
}
if fi != nil {
defer fi.Close()
if err := yaml.NewEncoder(fi).Encode(&config); err != nil {
return err
}
}

return nil
}
129 changes: 69 additions & 60 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import (

"github.com/mattn/go-isatty"

"github.com/adrg/xdg"
"github.com/caarlos0/env/v6"
"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/list"
"github.com/charmbracelet/bubbles/textinput"
Expand All @@ -25,7 +23,6 @@ import (
"github.com/sahilm/fuzzy"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
"gopkg.in/yaml.v3"
)

var (
Expand All @@ -51,6 +48,7 @@ func runCLI(args []string) {
config := readConfig()
snippets := readSnippets(config)
snippets = migrateSnippets(config, snippets)
snippets = scanSnippets(config, snippets)

stdin := readStdin()
if stdin != "" {
Expand Down Expand Up @@ -140,62 +138,6 @@ func readStdin() string {
return b.String()
}

// defaultConfig returns the default config path
func defaultConfig() string {
if c := os.Getenv("NAP_CONFIG"); c != "" {
return c
}
cfgPath, err := xdg.ConfigFile("nap/config.yaml")
if err != nil {
return "config.yaml"
}
return cfgPath
}

// readConfig returns a configuration read from the environment.
func readConfig() Config {
config := newConfig()
fi, err := os.Open(defaultConfig())
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return newConfig()
}
if fi != nil {
defer fi.Close()
if err := yaml.NewDecoder(fi).Decode(&config); err != nil {
return newConfig()
}
}

if err := env.Parse(&config); err != nil {
return newConfig()
}

if strings.HasPrefix(config.Home, "~") {
home, err := os.UserHomeDir()
if err == nil {
config.Home = filepath.Join(home, config.Home[1:])
}
}

return config
}

// writeConfig returns a configuration read from the environment.
func (config Config) writeConfig() error {
fi, err := os.Create(defaultConfig())
if err != nil {
return err
}
if fi != nil {
defer fi.Close()
if err := yaml.NewEncoder(fi).Encode(&config); err != nil {
return err
}
}

return nil
}

// readSnippets returns all the snippets read from the snippets.json file.
func readSnippets(config Config) []Snippet {
var snippets []Snippet
Expand All @@ -211,7 +153,7 @@ func readSnippets(config Config) []Snippet {
if err != nil {
fmt.Printf("Unable to create file %s, %+v", file, err)
}
content := fmt.Sprintf(defaultSnippetFileFormat, defaultSnippetFolder, defaultSnippetName, config.DefaultLanguage)
content := fmt.Sprintf(defaultSnippetFileFormat, defaultSnippetFolder, defaultSnippetName, defaultSnippetFileName, config.DefaultLanguage)
_, _ = f.WriteString(content)
dir = []byte(content)
}
Expand Down Expand Up @@ -254,6 +196,73 @@ func migrateSnippets(config Config, snippets []Snippet) []Snippet {
return snippets
}

// scanSnippets scans for any new/removed snippets and adds them to snippets.json
func scanSnippets(config Config, snippets []Snippet) []Snippet {
var modified bool
snippetExists := func(path string) bool {
for _, snippet := range snippets {
if path == snippet.Path() {
return true
}
}
return false
}

homeEntries, err := os.ReadDir(config.Home)
if err != nil {
fmt.Printf("could not scan config home: %v\n", err)
return snippets
}

for _, homeEntry := range homeEntries {
if !homeEntry.IsDir() {
continue
}

folderPath := filepath.Join(config.Home, homeEntry.Name())
folderEntries, err := os.ReadDir(folderPath)
if err != nil {
fmt.Printf("could not scan %q: %v\n", folderPath, err)
continue
}

for _, folderEntry := range folderEntries {
if folderEntry.IsDir() {
continue
}

snippetPath := filepath.Join(homeEntry.Name(), folderEntry.Name())
if !snippetExists(snippetPath) {
snippets = append(snippets, Snippet{
Folder: homeEntry.Name(),
Date: time.Now(),
Name: folderEntry.Name(),
File: folderEntry.Name(),
Language: strings.TrimPrefix(filepath.Ext(folderEntry.Name()), "."),
})
modified = true
}
}
}

var idx int
for _, snippet := range snippets {
snippetPath := filepath.Join(config.Home, snippet.Path())
if _, err := os.Stat(snippetPath); !errors.Is(err, fs.ErrNotExist) {
snippets[idx] = snippet
idx++
modified = true
}
}
snippets = snippets[:idx]

if modified {
writeSnippets(config, snippets)
}

return snippets
}

func saveSnippet(content string, args []string, config Config, snippets []Snippet) {
// Save snippet to location
name := defaultSnippetName
Expand Down
62 changes: 53 additions & 9 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ import (
)

func TestCLI(t *testing.T) {
tmp := t.TempDir()
if err := os.Setenv("NAP_HOME", tmp); err != nil {
t.Log("could not set NAP_HOME")
t.FailNow()
}
tmp := tmpHome(t)

t.Run("stdin", func(t *testing.T) {
r, w, err := os.Pipe()
Expand All @@ -29,8 +25,8 @@ func TestCLI(t *testing.T) {
cfg := readConfig()
snippets := readSnippets(cfg)

if len(snippets) != 2 {
t.Logf("snippet count is incorrect: got %d but want 2", len(snippets))
if len(snippets) != 1 {
t.Logf("snippet count is incorrect: got %d but want 1", len(snippets))
t.FailNow()
}

Expand Down Expand Up @@ -90,9 +86,57 @@ func TestCLI(t *testing.T) {
t.FailNow()
}

if string(out) != "foo/bar.baz\nmisc/Untitled Snippet.go\n" {
t.Logf(`snippet is incorrect: got %q but want "foo/bar.baz\nmisc/Untitled Snippet.go\n"`, string(out))
if string(out) != "foo/bar.baz\n" {
t.Logf(`snippet is incorrect: got %q but want "foo/bar.baz\n"`, string(out))
t.FailNow()
}
})
}

func TestScan(t *testing.T) {
tmp := tmpHome(t)

cfg := readConfig()
snippets := readSnippets(cfg)
snippets = scanSnippets(cfg, snippets)
initNum := len(snippets)

tmpSnippetFolder := filepath.Join(tmp, "foo")
tmpSnippet := filepath.Join(tmpSnippetFolder, "bar.baz")
if err := os.MkdirAll(tmpSnippetFolder, os.ModePerm); err != nil {
t.Logf("could not create snippet folder: %v", err)
t.FailNow()
}
if err := os.WriteFile(tmpSnippet, []byte("foo bar baz"), os.ModePerm); err != nil {
t.Logf("could not create snippet: %v", err)
t.FailNow()
}

snippets = scanSnippets(cfg, snippets)
if len(snippets) != initNum+1 {
t.Logf("incorrect number of snippets after scanning: want %d but got %d", initNum+1, len(snippets))
t.FailNow()
}

if err := os.Remove(tmpSnippet); err != nil {
t.Logf("could not remove snippet: %v", err)
t.FailNow()
}

snippets = scanSnippets(cfg, snippets)
if len(snippets) != initNum {
t.Logf("incorrect number of snippets after scanning: want %d but got %d", initNum+1, len(snippets))
t.FailNow()
}
}

func tmpHome(t *testing.T) string {
t.Helper()

tmp := t.TempDir()
if err := os.Setenv("NAP_HOME", tmp); err != nil {
t.Log("could not set NAP_HOME")
t.FailNow()
}
return tmp
}
2 changes: 1 addition & 1 deletion snippet.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const (
)

// defaultSnippetFileFormat is the file to use for an empty snippets.json file.
var defaultSnippetFileFormat = `[ { "folder": "%s", "title": "%s", "tags": [], "date": "2022-11-12T15:04:05Z", "favorite": false, "file": "nap.txt", "language": "%s" } ]`
var defaultSnippetFileFormat = `[ { "folder": "%s", "title": "%s", "tags": [], "date": "2022-11-12T15:04:05Z", "favorite": false, "file": "%s", "language": "%s" } ]`

// defaultSnippet is a snippet with all of the default values, used for when
// there are no snippets available.
Expand Down
1 change: 0 additions & 1 deletion state.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ func (s State) Save() error {
}
defer fi.Close()
return json.NewEncoder(fi).Encode(s)

}

// defaultState returns the default state path
Expand Down

0 comments on commit 3059aa7

Please sign in to comment.