summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorewy <ewy0@protonmail.com>2026-05-17 01:37:24 +0200
committerewy <ewy0@protonmail.com>2026-05-17 01:37:24 +0200
commitf5807d9f3a6c96e70912b61fac17120f412b5782 (patch)
treed6928795e06b1af000ffba2ae50bb6f8f7b72685
parent7984fd9beaa7c903288142818cb328c584a139a5 (diff)
* integration tests with a pik target to run them
* add abstraction for paths to facilitate unit tests * flesh out completion (--install-completion) * do sync init before stateless modes so list knows more
-rw-r--r--.pik/integrations.sh40
-rw-r--r--.pik/test.sh4
-rw-r--r--cache/cache.go43
-rw-r--r--cache/cache_test.go64
-rw-r--r--completion/completion.go50
-rw-r--r--completion/completion.sh3
-rw-r--r--flags/flags.go7
-rw-r--r--integration_tests/defaults/.pik/defaults.sh3
-rw-r--r--integration_tests/defaults/.pik/dir/dir.sh2
-rw-r--r--integration_tests/defaults/README.md2
-rw-r--r--integration_tests/defaults/full.test.sh6
-rw-r--r--integration_tests/defaults/source.test.sh6
-rw-r--r--integration_tests/defaults/source_full.test.sh6
-rw-r--r--integration_tests/defaults/subdir.test.sh6
-rw-r--r--integration_tests/defaults/subdir_with_source.test.sh6
-rw-r--r--integration_tests/simple/.pik/echo.sh2
-rw-r--r--integration_tests/simple/.pik/result.sh2
-rw-r--r--integration_tests/simple/README.md1
-rw-r--r--integration_tests/simple/echo.test.sh5
-rw-r--r--integration_tests/simple/echo_full.test.sh5
-rw-r--r--integration_tests/simple/nonexistent_target.test.sh5
-rw-r--r--integration_tests/simple/result.test.sh5
-rw-r--r--main.go22
-rw-r--r--model/new.go12
-rw-r--r--modes.go20
-rw-r--r--paths/path.go20
-rw-r--r--paths/paths.go53
-rw-r--r--paths/paths_test.go43
-rw-r--r--paths/paths_testutil.go26
29 files changed, 365 insertions, 104 deletions
diff --git a/.pik/integrations.sh b/.pik/integrations.sh
new file mode 100644
index 0000000..4c1af78
--- /dev/null
+++ b/.pik/integrations.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# build pik and set $PIK to the built version
+go build -o pik .
+PIK="$(realpath ./pik)"
+export PIK
+
+cd integration_tests
+
+FAILED=""
+
+for dir in *
+do
+ if [ -d "$dir" ] ; then
+ cd "$dir"
+ for file in *.test.sh
+ do
+ tmpdir=$(mktemp -d)
+ XDG_CACHE_HOME="$tmpdir"
+ XDG_CONFIG_HOME="$tmpdir"
+ export XDG_CACHE_HOME XDG_CONFIG_HOME
+
+ if ! bash "$file" 1>/dev/null 2>&1 ; then
+ echo "$dir/$file $(tput setaf 1)failed$(tput sgr0)" 2>&1
+ bash -x "$file" || true
+ FAILED=yes
+ else
+ echo "$dir/$file $(tput setaf 2)succeeded$(tput sgr0)" 2>&1
+ rm -rf "$tmpdir"
+ fi
+ done
+ cd - > /dev/null
+ fi
+done
+
+if [ -n "$FAILED" ] ; then
+ exit 1
+fi \ No newline at end of file
diff --git a/.pik/test.sh b/.pik/test.sh
index bb91492..ac4945f 100644
--- a/.pik/test.sh
+++ b/.pik/test.sh
@@ -1,2 +1,4 @@
#!/usr/bin/env bash
-go test -tags test -v ./... \ No newline at end of file
+set -euo pipefail
+go test -tags test -v ./...
+bash .pik/integrations.sh \ No newline at end of file
diff --git a/cache/cache.go b/cache/cache.go
index 9e6b1bc..2784e23 100644
--- a/cache/cache.go
+++ b/cache/cache.go
@@ -8,7 +8,6 @@ import (
"io"
"io/fs"
"os"
- "path"
"slices"
"strings"
)
@@ -19,30 +18,22 @@ type Cache struct {
type cacheInit struct{}
-// Path is the file path to the "contexts" cache file
-var Path string
-
-// FsPath is the Path with the leading slash removed, to be opened from fs.FS
-var FsPath string
-
var Init model.Initializer = &cacheInit{}
func (i *cacheInit) Init() error {
- Path = path.Join(paths.Cache, "contexts")
- FsPath = Path[1:]
return nil
}
// Merge combines two caches and filters duplicate keys
func (c *Cache) Merge(other *Cache) *Cache {
- if other == nil && c != nil {
+ switch {
+ case other == nil && c != nil:
return c
- }
- if c == nil && other != nil {
+ case c == nil && other != nil:
return other
- }
- if c == nil {
+ case c == nil:
return nil
+
}
mp := make(map[string]string)
for _, e := range append(c.Entries, other.Entries...) {
@@ -60,9 +51,9 @@ type Entry struct {
Label string
}
-var Empty = Cache{}
-
-var UnexpectedEntryError = errors.New("unexpected cache entry")
+func (e Entry) String() string {
+ return e.Path + " # " + e.Label
+}
// LoadFile creates a Cache from a file or an empty one if the file does not exist
// this handles opening a reader for Unmarshal
@@ -97,8 +88,6 @@ func Unmarshal(r io.Reader) (*Cache, error) {
fallthrough
case 1:
entry.Path = strings.TrimSpace(parts[0])
- default:
- return c, UnexpectedEntryError
}
c.Entries = append(c.Entries, *entry)
}
@@ -109,9 +98,7 @@ func Unmarshal(r io.Reader) (*Cache, error) {
func (c *Cache) Marshal() []byte {
b := strings.Builder{}
for _, e := range c.Entries {
- b.WriteString(e.Path)
- b.WriteString(" # ")
- b.WriteString(e.Label)
+ b.WriteString(e.String())
b.WriteString("\n")
}
return []byte(b.String())
@@ -131,21 +118,23 @@ func New(st *model.State) *Cache {
return c
}
-func Insert(in *model.State) error {
- f := os.DirFS("/")
- loaded, err := LoadFile(f, FsPath)
+func MergeAndSave(in *model.State) error {
+ root := "/"
+ f := os.DirFS(root)
+ // remove leading slash from the dirfs
+ loaded, err := LoadFile(f, strings.TrimPrefix(paths.ContextsFile.String(), "/"))
if err != nil {
return err
}
insert := New(in)
result := loaded.Merge(insert)
if loaded == nil {
- return SaveFile(Path, result)
+ return SaveFile(paths.ContextsFile.String(), result)
}
if slices.Equal(loaded.Entries, result.Entries) {
return nil
}
- return SaveFile(Path, result)
+ return SaveFile(paths.ContextsFile.String(), result)
}
// SaveFile helps you use Save with a file path instead of a reader
diff --git a/cache/cache_test.go b/cache/cache_test.go
index 8753ad6..d2792ca 100644
--- a/cache/cache_test.go
+++ b/cache/cache_test.go
@@ -3,6 +3,8 @@
package cache
import (
+ "github.com/ewy1/pik/paths"
+ "os"
"path/filepath"
"strings"
"testing"
@@ -204,3 +206,65 @@ func TestMergeNilNormal(t *testing.T) {
}
_ = e.Merge(c)
}
+
+func TestCacheInit_Init(t *testing.T) {
+ d := t.TempDir()
+ paths.SetAll(d)
+ c := &cacheInit{}
+ err := c.Init()
+ assert.NoError(t, err)
+ assert.Contains(t, paths.ContextsFile, d)
+}
+
+func TestInsert(t *testing.T) {
+ d := t.TempDir()
+ st := TState(TSource("source", "target"))
+ paths.SetAll(d)
+ defer paths.Reset()
+ err := MergeAndSave(st)
+ assert.NoError(t, err)
+}
+
+func TestInsertNonExistent(t *testing.T) {
+ st := TState(TSource("source", "target"))
+ paths.SetAll("/../")
+ err := MergeAndSave(st)
+ assert.Error(t, err)
+}
+
+func TestLoadState(t *testing.T) {
+ paths.SetAll("/pik")
+ defer paths.Reset()
+ c := &Cache{
+ Entries: []Entry{
+ {Path: "/asdf", Label: "hjkl"},
+ },
+ }
+ f := fstest.MapFS{
+ "/pik/contexts": &fstest.MapFile{
+ Data: []byte("asdf # hjkl"),
+ },
+ }
+ st, errs := LoadState(f, c, nil, nil)
+ for _, e := range errs {
+ assert.NoError(t, e)
+ }
+ assert.NotNil(t, st)
+}
+
+func TestSavesFile(t *testing.T) {
+ d := t.TempDir()
+ paths.Set(paths.ContextsFile, filepath.Join(d, "contexts"))
+ defer paths.Reset()
+ f, err := os.OpenRoot("/")
+ defer f.Close()
+ assert.NoError(t, err)
+ st := TState(TSource("source", "target1", "target2"))
+ c := New(st)
+ assert.NotNil(t, c)
+ err = MergeAndSave(st)
+ assert.NoError(t, err)
+ content, err := os.ReadFile(paths.ContextsFile.String())
+ assert.NoError(t, err)
+ assert.Contains(t, string(content), c.Entries[0].String())
+}
diff --git a/completion/completion.go b/completion/completion.go
index 6aad9d1..f3d76d7 100644
--- a/completion/completion.go
+++ b/completion/completion.go
@@ -2,15 +2,61 @@ package completion
import (
_ "embed"
+ "errors"
+ "fmt"
+ "github.com/ewy1/pik/paths"
"github.com/ewy1/pik/spool"
+ "os"
+ "path/filepath"
+ "strings"
)
//go:embed completion.sh
var completionCode string
+var completionFormat = `
+# %s
+%s
+`
+
+var completionComment = "\n# pik completion (installed by `pik --install-completion`)\n"
+
var completionCodeByShell = map[string]string{
- "bash": ". <(pik --completion)",
- "zsh": `autoload -Uz compinit && compinit && source <(pik --completion)`,
+ "bash": ". <(pik --completion)\n\n",
+ "zsh": `autoload -Uz compinit && compinit && source <(pik --completion)\n\n`,
+}
+
+var completionFileByShell = map[string]string{
+ "bash": ".bashrc",
+ "zsh": ".zshrc",
+}
+
+var AlreadyInstalledError = errors.New("completion already installed")
+
+func Add(shell string) error {
+ f := filepath.Join(paths.HomeDir.String(), completionFileByShell[shell])
+ content, err := os.ReadFile(f)
+ if err != nil {
+ return err
+ }
+ if strings.Contains(string(content), strings.TrimSpace(completionCodeByShell[shell])) {
+ return AlreadyInstalledError
+ }
+ fd, err := os.OpenFile(f, os.O_APPEND|os.O_WRONLY, 0600)
+ defer fd.Close()
+ if err != nil {
+ return err
+ }
+ _, err = fd.Write([]byte(fmt.Sprintf(completionFormat, completionComment, completionCodeByShell[shell])))
+ if err != nil {
+ return err
+ }
+ successMessage(shell, f)
+ return nil
+}
+
+func successMessage(shell string, file string) {
+ _, _ = spool.Print("Installed completion for %s in %s\n", shell, file)
}
func Echo() error {
diff --git a/completion/completion.sh b/completion/completion.sh
index 36876cf..a48733f 100644
--- a/completion/completion.sh
+++ b/completion/completion.sh
@@ -1,9 +1,8 @@
-#/usr/bin/env bash
_pik_completions()
{
QUERY=""
for word in COMP_WORDS ; do
- if [ ! query = "-"* ] ; then
+ if [[ ! query = "-"* ]] ; then
QUERY="$QUERY $WORD"
fi
done
diff --git a/flags/flags.go b/flags/flags.go
index f349ce7..d8ac537 100644
--- a/flags/flags.go
+++ b/flags/flags.go
@@ -26,7 +26,8 @@ var (
List = pflag.BoolP("list", "l", false, "list available targets and exit")
// Inline means pik does not go to the terminal alt screen
// unused because it is accessed by `flags.Get("inline")`
- Inline = pflag.BoolP("inline", "i", false, "if true, will force alt screen; if forced false, will disable alt screen")
- Edit = pflag.Bool("edit", false, "edit the target in $EDITOR")
- Completion = pflag.Bool("completion", false, "echo completion scrip and exit")
+ Inline = pflag.BoolP("inline", "i", false, "if true, will force alt screen; if forced false, will disable alt screen")
+ Edit = pflag.Bool("edit", false, "edit the target in $EDITOR")
+ Completion = pflag.Bool("completion", false, "echo completion scrip and exit")
+ InstallCompletion = pflag.Bool("install-completion", false, "install completion script to shell rc")
)
diff --git a/integration_tests/defaults/.pik/defaults.sh b/integration_tests/defaults/.pik/defaults.sh
new file mode 100644
index 0000000..6500c83
--- /dev/null
+++ b/integration_tests/defaults/.pik/defaults.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+# i have the same name as the source directory
+echo "banana" \ No newline at end of file
diff --git a/integration_tests/defaults/.pik/dir/dir.sh b/integration_tests/defaults/.pik/dir/dir.sh
new file mode 100644
index 0000000..8d6b69b
--- /dev/null
+++ b/integration_tests/defaults/.pik/dir/dir.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+echo "apple" \ No newline at end of file
diff --git a/integration_tests/defaults/README.md b/integration_tests/defaults/README.md
new file mode 100644
index 0000000..9aaf7eb
--- /dev/null
+++ b/integration_tests/defaults/README.md
@@ -0,0 +1,2 @@
+these tests check whether pik's default targets (sharing a name with either subdirectory or source folder) are selected
+by default. \ No newline at end of file
diff --git a/integration_tests/defaults/full.test.sh b/integration_tests/defaults/full.test.sh
new file mode 100644
index 0000000..05d3ee4
--- /dev/null
+++ b/integration_tests/defaults/full.test.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+result="$($PIK defaults dir dir)"
+if [[ ! "$result" == *"apple"* ]] ; then
+ echo "expected apple" >&2
+ exit 1
+fi
diff --git a/integration_tests/defaults/source.test.sh b/integration_tests/defaults/source.test.sh
new file mode 100644
index 0000000..ee966f4
--- /dev/null
+++ b/integration_tests/defaults/source.test.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+result="$($PIK defaults)"
+if [[ ! "$result" == *"banana"* ]] ; then
+ echo "expected banana" >&2
+ exit 1
+fi \ No newline at end of file
diff --git a/integration_tests/defaults/source_full.test.sh b/integration_tests/defaults/source_full.test.sh
new file mode 100644
index 0000000..91784c4
--- /dev/null
+++ b/integration_tests/defaults/source_full.test.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+result="$($PIK defaults defaults)"
+if [[ ! "$result" == *"banana"* ]] ; then
+ echo "expected banana" >&2
+ exit 1
+fi \ No newline at end of file
diff --git a/integration_tests/defaults/subdir.test.sh b/integration_tests/defaults/subdir.test.sh
new file mode 100644
index 0000000..6edd874
--- /dev/null
+++ b/integration_tests/defaults/subdir.test.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+result="$($PIK dir)"
+if [[ ! "$result" == *"apple"* ]] ; then
+ echo "expected apple" >&2
+ exit 1
+fi
diff --git a/integration_tests/defaults/subdir_with_source.test.sh b/integration_tests/defaults/subdir_with_source.test.sh
new file mode 100644
index 0000000..3f41277
--- /dev/null
+++ b/integration_tests/defaults/subdir_with_source.test.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+result="$($PIK defaults dir)"
+if [[ ! "$result" == *"apple"* ]] ; then
+ echo "expected apple" >&2
+ exit 1
+fi
diff --git a/integration_tests/simple/.pik/echo.sh b/integration_tests/simple/.pik/echo.sh
new file mode 100644
index 0000000..c8a6153
--- /dev/null
+++ b/integration_tests/simple/.pik/echo.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+echo "$@" \ No newline at end of file
diff --git a/integration_tests/simple/.pik/result.sh b/integration_tests/simple/.pik/result.sh
new file mode 100644
index 0000000..7a534a6
--- /dev/null
+++ b/integration_tests/simple/.pik/result.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+echo "banana" \ No newline at end of file
diff --git a/integration_tests/simple/README.md b/integration_tests/simple/README.md
new file mode 100644
index 0000000..6925e22
--- /dev/null
+++ b/integration_tests/simple/README.md
@@ -0,0 +1 @@
+these tests try some simple pik usage \ No newline at end of file
diff --git a/integration_tests/simple/echo.test.sh b/integration_tests/simple/echo.test.sh
new file mode 100644
index 0000000..917a76d
--- /dev/null
+++ b/integration_tests/simple/echo.test.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+if [[ "$($PIK result)" != *"banana"* ]] ; then
+ echo "expected banana" 1>&2
+ exit 1
+fi \ No newline at end of file
diff --git a/integration_tests/simple/echo_full.test.sh b/integration_tests/simple/echo_full.test.sh
new file mode 100644
index 0000000..7290cd2
--- /dev/null
+++ b/integration_tests/simple/echo_full.test.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+if [[ "$($PIK simple result)" != *"banana"* ]] ; then
+ echo "expected banana" 1>&2
+ exit 1
+fi \ No newline at end of file
diff --git a/integration_tests/simple/nonexistent_target.test.sh b/integration_tests/simple/nonexistent_target.test.sh
new file mode 100644
index 0000000..4915e77
--- /dev/null
+++ b/integration_tests/simple/nonexistent_target.test.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+if [[ "$($PIK 123)" != "" ]] ; then
+ echo "expected no output" 1>&2
+ exit 1
+fi \ No newline at end of file
diff --git a/integration_tests/simple/result.test.sh b/integration_tests/simple/result.test.sh
new file mode 100644
index 0000000..3d4a2bc
--- /dev/null
+++ b/integration_tests/simple/result.test.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+if [[ "$($PIK echo asdf)" != *"asdf"* ]] ; then
+ echo "expected banana" 1>&2
+ exit 1
+fi \ No newline at end of file
diff --git a/main.go b/main.go
index 036504c..8b905d8 100644
--- a/main.go
+++ b/main.go
@@ -29,7 +29,7 @@ import (
// syncInitializers are ran before the initializers.
// useful for initializing stuff like paths, preparing directories, and reading the environment
var syncInitializers = ComponentList[model.Initializer]{
- paths.Paths,
+ paths.Component,
cache.Init,
}
@@ -108,6 +108,10 @@ func mode[T any](list ModeMap[T], fire func(mode T) error) *int {
func pik() int {
pflag.Parse()
+ syncInitializers.RunSync(func(initializer model.Initializer) error {
+ return initializer.Init()
+ })
+
code := mode(statelessModes, func(mode func() error) error {
return mode()
})
@@ -115,10 +119,6 @@ func pik() int {
return *code
}
- syncInitializers.RunSync(func(initializer model.Initializer) error {
- return initializer.Init()
- })
-
initializers.RunAsync(func(initializer model.Initializer) error {
return initializer.Init()
})
@@ -128,9 +128,9 @@ func pik() int {
_, _ = spool.Warn("%v\n", err)
return 1
}
- locs := crawl.RichLocations(here)
- last := locs[len(locs)-1]
- root, err := os.OpenRoot(last)
+ locs := append(crawl.RichLocations(here), paths.System.String())
+ root, err := os.OpenRoot("/")
+ defer root.Close()
if root == nil {
_, _ = spool.Warn("%v\n", err)
return 1
@@ -150,14 +150,14 @@ func pik() int {
if len(stateErrors) > 0 {
return
}
- err := cache.Insert(st)
+ err := cache.MergeAndSave(st)
if err != nil {
- spool.Warn("%v\n", err)
+ _, _ = spool.Warn("%v\n", err)
}
}()
} else {
- c, err = cache.LoadFile(fs, cache.Path[1:])
+ c, err = cache.LoadFile(fs, paths.ContextsFile.String()[1:])
if err != nil {
_, _ = spool.Warn("%v\n", err)
return 1
diff --git a/model/new.go b/model/new.go
index 94b039e..e83c314 100644
--- a/model/new.go
+++ b/model/new.go
@@ -10,7 +10,7 @@ import (
"sync"
)
-func NewState(f fs.FS, locations []string, indexers []Indexer, runners []Runner) (*State, []error) {
+func NewState(rootFs fs.FS, locations []string, indexers []Indexer, runners []Runner) (*State, []error) {
var errs []error
st := &State{
All: *flags.All,
@@ -32,16 +32,16 @@ func NewState(f fs.FS, locations []string, indexers []Indexer, runners []Runner)
return
}
- myWg := sync.WaitGroup{}
+ locationWg := sync.WaitGroup{}
var targets = make([][]Target, len(indexers), len(indexers))
for ti, indexer := range indexers {
- myWg.Go(func() {
- s, err := fs.Sub(f, loc)
+ locationWg.Go(func() {
+ subFs, err := fs.Sub(rootFs, loc)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
errs = append(errs, err)
return
}
- result, err := indexer.Index("/"+loc, s, runners)
+ result, err := indexer.Index("/"+loc, subFs, runners)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
errs = append(errs, err)
return
@@ -49,7 +49,7 @@ func NewState(f fs.FS, locations []string, indexers []Indexer, runners []Runner)
targets[ti] = result
})
}
- myWg.Wait()
+ locationWg.Wait()
for _, t := range targets {
if t == nil {
diff --git a/modes.go b/modes.go
index 5650f5c..bc9e90f 100644
--- a/modes.go
+++ b/modes.go
@@ -9,6 +9,7 @@ import (
"github.com/ewy1/pik/run"
"github.com/ewy1/pik/spool"
"os"
+ "path/filepath"
"runtime"
"runtime/pprof"
)
@@ -43,6 +44,8 @@ func (m ModeMap[T]) Traverse(then func(in T) error) error {
var profileFd *os.File
+var UnknownShellError = errors.New("$SHELL not set or empty")
+
// statelessModes are program modes which do not require state to operate.
// like --version and --completion
var statelessModes = ModeMap[func() error]{
@@ -50,6 +53,14 @@ var statelessModes = ModeMap[func() error]{
_, err := spool.Print("%s\n", version)
return err
},
+ flags.InstallCompletion: func() error {
+ sh := os.Getenv("SHELL")
+ if sh == "" {
+ return UnknownShellError
+ }
+ _, sh = filepath.Split(sh)
+ return completion.Add(sh)
+ },
flags.Completion: func() error {
return completion.Echo()
},
@@ -68,15 +79,24 @@ var statelessModes = ModeMap[func() error]{
},
}
+var NoTargetsError = errors.New("no targets or sources to list")
+
// statefulModes are program modes which require a built state to be executed
var statefulModes = ModeMap[func(st *model.State) error]{
flags.List: func(st *model.State) error {
+ count := 0
for _, s := range st.Sources {
+ count++
_, _ = spool.Print("%v", s.Label()+paths.Ifs)
for _, t := range s.Targets {
_, _ = spool.Print("%v", t.ShortestId()+paths.Ifs)
+ count++
}
}
+
+ if count == 0 {
+ return NoTargetsError
+ }
return nil
},
}
diff --git a/paths/path.go b/paths/path.go
new file mode 100644
index 0000000..5706d29
--- /dev/null
+++ b/paths/path.go
@@ -0,0 +1,20 @@
+package paths
+
+type Path struct {
+ Val *string
+}
+
+func (p *Path) Set(val string) {
+ p.Val = &val
+}
+
+func (p *Path) String() string {
+ if p == nil || p.Val == nil {
+ return "<empty>"
+ }
+ return *p.Val
+}
+
+func Empty() *Path {
+ return &Path{}
+}
diff --git a/paths/paths.go b/paths/paths.go
index cc0c878..b714a23 100644
--- a/paths/paths.go
+++ b/paths/paths.go
@@ -3,23 +3,39 @@ package paths
import (
"github.com/adrg/xdg"
"os"
+ "path"
"path/filepath"
"strings"
)
var (
- Ifs string
- Config string
- Cache string
- This string
- Home string
+ Ifs string
+ ConfigDir = Empty()
+ CacheDir = Empty()
+ ContextsFile = Empty()
+ HomeDir = Empty()
+ System = Empty()
+ This = "pik"
)
+var Paths = []*Path{
+ ConfigDir,
+ CacheDir,
+ ContextsFile,
+ HomeDir,
+ System,
+}
+
+var DirectoriesToMake = []*Path{
+ ConfigDir,
+ CacheDir,
+}
+
type paths struct {
Initialized bool
}
-var Paths = &paths{
+var Component = &paths{
Initialized: false,
}
@@ -31,20 +47,21 @@ func (p *paths) Init() error {
xdg.Reload()
}
- Home = xdg.Home
- This = "pik"
- Cache = filepath.Join(xdg.CacheHome, This)
- Config = filepath.Join(xdg.ConfigHome, This)
+ HomeDir.Set(xdg.Home)
+ CacheDir.Set(filepath.Join(xdg.CacheHome, This))
+ ConfigDir.Set(filepath.Join(xdg.ConfigHome, This))
+ ContextsFile.Set(path.Join(CacheDir.String(), "contexts"))
+ System.Set(path.Join(ConfigDir.String()))
+
Ifs = os.Getenv("IFS")
- err := os.MkdirAll(Cache, 0700)
- if err != nil {
- return err
- }
- err = os.MkdirAll(Config, 0700)
- if err != nil {
- return err
+ for _, d := range DirectoriesToMake {
+ err := os.MkdirAll(d.String(), 0700)
+ if err != nil {
+ return err
+ }
}
+
if Ifs == "" {
Ifs = "\n"
}
@@ -53,5 +70,5 @@ func (p *paths) Init() error {
}
func ReplaceHome(input string) string {
- return strings.Replace(input, Home, "~", 1)
+ return strings.Replace(input, HomeDir.String(), "~", 1)
}
diff --git a/paths/paths_test.go b/paths/paths_test.go
index 8ab3461..1dd9034 100644
--- a/paths/paths_test.go
+++ b/paths/paths_test.go
@@ -4,42 +4,17 @@ package paths
import (
"github.com/stretchr/testify/assert"
+ "strconv"
"testing"
)
-var bHome, bThis, bCache, bConfig string
-
-// Set temporarily sets the paths for unit test purposes
-// remember to defer Reset
-func Set(home, this, cache, config string) {
- bHome = Home
- bThis = This
- bCache = Cache
- bConfig = Config
-
- Home = home
- This = this
- Cache = cache
- Config = config
-}
-
-// Reset sets the path variables back to before the unit test
-func Reset() {
- Home = bHome
- This = bThis
- Cache = bCache
- Config = bConfig
-}
-
func TestSetAndReset(t *testing.T) {
- Set("1", "2", "3", "4")
- assert.Equal(t, Home, "1")
- assert.Equal(t, This, "2")
- assert.Equal(t, Cache, "3")
- assert.Equal(t, Config, "4")
- Reset()
- assert.NotEqual(t, Home, "1")
- assert.NotEqual(t, This, "2")
- assert.NotEqual(t, Cache, "3")
- assert.NotEqual(t, Config, "4")
+ for i, pt := range Paths {
+ old := pt.String()
+ val := strconv.Itoa(i)
+ Set(pt, val)
+ assert.Equal(t, pt.String(), val)
+ Reset()
+ assert.Equal(t, pt.String(), old)
+ }
}
diff --git a/paths/paths_testutil.go b/paths/paths_testutil.go
new file mode 100644
index 0000000..5b8af48
--- /dev/null
+++ b/paths/paths_testutil.go
@@ -0,0 +1,26 @@
+//go:build test
+
+package paths
+
+var backups = make(map[*Path]string)
+
+func SetAll(folder string) {
+ for _, p := range Paths {
+ Set(p, folder)
+ }
+}
+
+// Set temporarily sets the paths for unit test purposes
+// remember to defer Reset
+func Set(target *Path, value string) {
+ backups[target] = target.String()
+ target.Set(value)
+}
+
+// Reset sets the path variables back to before the unit test
+func Reset() {
+ for path, oldValue := range backups {
+ path.Set(oldValue)
+ }
+ backups = make(map[*Path]string)
+}