summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorewy <ewy0@protonmail.com>2026-06-02 15:07:59 +0200
committerewy <ewy0@protonmail.com>2026-06-02 15:07:59 +0200
commitf77fc4ef5f2df96c6bba3cd6c7d88a77d61a5a80 (patch)
treebdead2d2b245407aaa20e71c433d7c8f757d7049
parent6d607f114f63184a43237a3ff80aee622401ee23 (diff)
add .wants and .includesHEADmaster
.wants adds potential out-of-tree sources to your state (for monorepos, for example) .includes adds targets from another source to this one, adding inheritance
-rw-r--r--cache/cache.go27
-rw-r--r--cache/cache_test.go2
-rw-r--r--indexers/pikdex/hydrate.go2
-rw-r--r--indexers/pikdex/hydrate_test.go3
-rw-r--r--indexers/pikdex/index.go26
-rw-r--r--indexers/pikdex/meta.go21
-rw-r--r--main.go10
-rw-r--r--model/multi.go73
-rw-r--r--model/new.go98
-rw-r--r--model/source.go10
-rw-r--r--paths/paths.go17
11 files changed, 198 insertions, 91 deletions
diff --git a/cache/cache.go b/cache/cache.go
index 9f25af0..7121f65 100644
--- a/cache/cache.go
+++ b/cache/cache.go
@@ -54,12 +54,12 @@ func (e Entry) String() string {
// LoadFile creates a Cache from a file or an empty one if the file does not exist
// this handles opening a reader for Unmarshal
-func LoadFile(root fs.FS, path string) (*Cache, error) {
+func LoadFile(root fs.FS, path string) (Cache, error) {
fd, err := root.Open(path)
if errors.Is(err, fs.ErrNotExist) {
- return nil, nil
+ return Cache{}, nil
} else if err != nil {
- return nil, err
+ return Cache{}, err
}
if fd != nil {
defer fd.Close()
@@ -68,7 +68,7 @@ func LoadFile(root fs.FS, path string) (*Cache, error) {
}
// Unmarshal attempts to create a Cache from reader content
-func Unmarshal(r io.Reader) (*Cache, error) {
+func Unmarshal(r io.Reader) (Cache, error) {
c := &Cache{}
scanner := bufio.NewScanner(r)
for scanner.Scan() {
@@ -88,7 +88,7 @@ func Unmarshal(r io.Reader) (*Cache, error) {
}
c.Entries = append(c.Entries, *entry)
}
- return c, nil
+ return *c, nil
}
// Marshal returns the file representation of the Cache
@@ -119,16 +119,13 @@ func New(st *model.State) *Cache {
// MergeAndSave creates a cache from a state, combines it with
// a potential saved context file, and writes it to disk
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(), "/"))
+ loaded, err := Get()
if err != nil {
return err
}
insert := New(in)
result := loaded.Merge(insert)
- if loaded == nil {
+ if loaded.Entries == nil {
return SaveFile(paths.ContextsFile.String(), result)
}
if slices.Equal(loaded.Entries, result.Entries) {
@@ -157,12 +154,12 @@ func Save(w io.Writer, loaded *Cache) error {
}
// LoadState creates a state with model.NewState based on cache content
-func LoadState(f fs.FS, cache *Cache, indexers []model.Indexer, runners []model.Runner) (*model.State, []error) {
+func LoadState(f fs.FS, data map[string]*model.SourceData, cache Cache, indexers []model.Indexer, runners []model.Runner) (*model.State, []error) {
var locs []string
for _, e := range cache.Entries {
locs = append(locs, e.Path)
}
- return model.NewState(f, locs, indexers, runners)
+ return model.NewState(f, locs, data, indexers, runners)
}
// Strip removes the needle's entries from the receiver's entries when they have matching paths.
@@ -182,3 +179,9 @@ outer:
Entries: result,
}
}
+
+func Get() (Cache, error) {
+ f := os.DirFS(paths.CacheDir.String())
+ loaded, err := LoadFile(f, strings.TrimPrefix(paths.ContextsFileName, "/"))
+ return loaded, err
+}
diff --git a/cache/cache_test.go b/cache/cache_test.go
index a58bf95..59d4269 100644
--- a/cache/cache_test.go
+++ b/cache/cache_test.go
@@ -227,7 +227,7 @@ func TestInsertNonExistent(t *testing.T) {
func TestLoadState(t *testing.T) {
paths.SetAll("/pik")
defer paths.Reset()
- c := &Cache{
+ c := Cache{
Entries: []Entry{
{Path: "/asdf", Label: "hjkl"},
},
diff --git a/indexers/pikdex/hydrate.go b/indexers/pikdex/hydrate.go
index 1f71992..a0dfc2b 100644
--- a/indexers/pikdex/hydrate.go
+++ b/indexers/pikdex/hydrate.go
@@ -6,7 +6,7 @@ import (
)
func (u *pikdex) Mod(src *model.Source, result *model.HydratedSource) error {
- mod := u.mods[strings.TrimSuffix(src.Path, "/")]
+ mod := u.Data[strings.TrimSuffix(src.Path, "/")]
if mod.Path != "" {
if mod.Aliases != nil {
result.Aliases = append(result.Aliases, mod.Aliases...)
diff --git a/indexers/pikdex/hydrate_test.go b/indexers/pikdex/hydrate_test.go
index ba5e1bc..cd2433a 100644
--- a/indexers/pikdex/hydrate_test.go
+++ b/indexers/pikdex/hydrate_test.go
@@ -3,6 +3,7 @@
package pikdex
import (
+ "github.com/ewy1/pik/model"
"github.com/ewy1/pik/runner"
"github.com/stretchr/testify/assert"
"testing"
@@ -11,7 +12,7 @@ import (
func TestHydrate(t *testing.T) {
aliases := []string{"alias1", "alias2"}
p := pikdex{
- mods: map[string]*SourceData{
+ Data: map[string]*model.SourceData{
"asdf": {
Aliases: aliases,
Icon: "I",
diff --git a/indexers/pikdex/index.go b/indexers/pikdex/index.go
index 1a9b38a..15a74f4 100644
--- a/indexers/pikdex/index.go
+++ b/indexers/pikdex/index.go
@@ -35,19 +35,11 @@ func (u *pikdex) Init() error {
return nil
}
-var Indexer = &pikdex{mods: make(map[string]*SourceData)}
+var Indexer = &pikdex{Data: make(map[string]*model.SourceData)}
type pikdex struct {
sync.Mutex
- mods map[string]*SourceData
-}
-
-type SourceData struct {
- Aliases []string
- Icon string
- Path string
- Wants []string
- Includes []string
+ Data map[string]*model.SourceData
}
func (u *pikdex) Index(absPath string, f fs.FS, runners []model.Runner) ([]model.Target, error) {
@@ -57,14 +49,14 @@ func (u *pikdex) Index(absPath string, f fs.FS, runners []model.Runner) ([]model
}
var targets []model.Target
u.Lock()
- mod := u.mods[absPath]
+ mod := u.Data[absPath]
u.Unlock()
if mod == nil {
u.Lock()
- u.mods[absPath] = &SourceData{
+ u.Data[absPath] = &model.SourceData{
Path: absPath,
}
- mod = u.mods[absPath]
+ mod = u.Data[absPath]
u.Unlock()
}
err = fs.WalkDir(f, root, func(p string, d fs.DirEntry, err error) error {
@@ -99,12 +91,12 @@ func (u *pikdex) Index(absPath string, f fs.FS, runners []model.Runner) ([]model
for _, r := range runners {
wants, err := r.Wants(f, p, d)
if err != nil {
- spool.Warn("%v\n", err)
+ _, _ = spool.Warn("%v\n", err)
}
if wants {
t, err := r.CreateTarget(f, absPath, p, d)
if err != nil {
- spool.Warn("%v\n", err)
+ _, _ = spool.Warn("%v\n", err)
}
sub := t.Sub()
if strings.Join(sub, " ") == t.ShortestId() {
@@ -123,13 +115,13 @@ func (u *pikdex) Index(absPath string, f fs.FS, runners []model.Runner) ([]model
return nil
}
if err != nil {
- spool.Warn("%v\n", err)
+ _, _ = spool.Warn("%v\n", err)
}
}
return nil
})
u.Lock()
- u.mods[absPath] = mod
+ u.Data[absPath] = mod
u.Unlock()
return targets, err
diff --git a/indexers/pikdex/meta.go b/indexers/pikdex/meta.go
index ef2c029..69b3f92 100644
--- a/indexers/pikdex/meta.go
+++ b/indexers/pikdex/meta.go
@@ -3,22 +3,29 @@ package pikdex
import (
"github.com/charmbracelet/lipgloss"
"github.com/ewy1/pik/describe"
+ "github.com/ewy1/pik/model"
"strings"
)
-type MetaSetter func(s *SourceData, content string)
+type MetaSetter func(s *model.SourceData, content string)
var MetaFiles = map[string]MetaSetter{
- ".wants": func(s *SourceData, content string) {
- s.Wants = contentLines(content)
+ ".want": func(s *model.SourceData, content string) {
+ s.Wants = append(s.Wants, contentLines(content)...)
},
- ".includes": func(s *SourceData, content string) {
- s.Includes = contentLines(content)
+ ".wants": func(s *model.SourceData, content string) {
+ s.Wants = append(s.Wants, contentLines(content)...)
},
- ".alias": func(s *SourceData, content string) {
+ ".include": func(s *model.SourceData, content string) {
+ s.Includes = append(s.Includes, contentLines(content)...)
+ },
+ ".includes": func(s *model.SourceData, content string) {
+ s.Includes = append(s.Includes, contentLines(content)...)
+ },
+ ".alias": func(s *model.SourceData, content string) {
s.Aliases = contentLines(content)
},
- ".icon": func(s *SourceData, content string) {
+ ".icon": func(s *model.SourceData, content string) {
lines := contentLines(content)
if len(lines) == 0 {
return
diff --git a/main.go b/main.go
index 496d09b..89d90e6 100644
--- a/main.go
+++ b/main.go
@@ -148,9 +148,9 @@ func pik() spool.ExitCode {
var st *model.State
var stateErrors []error
- var c *cache.Cache
+ var c cache.Cache
if !*flags.All {
- st, stateErrors = model.NewState(fs, locs, indexers, runners)
+ st, stateErrors = model.NewState(fs, locs, pikdex.Indexer.Data, indexers, runners)
go func() {
if len(stateErrors) > 0 {
return
@@ -162,12 +162,12 @@ func pik() spool.ExitCode {
}()
} else {
- c, err = cache.LoadFile(fs, paths.ContextsFile.String()[1:])
+ c, err = cache.Get()
if err != nil {
_, _ = spool.Warn("%v\n", err)
return spool.CacheReadFailure
}
- st, stateErrors = cache.LoadState(fs, c, indexers, runners)
+ st, stateErrors = cache.LoadState(fs, pikdex.Indexer.Data, c, indexers, runners)
}
if stateErrors != nil {
_, _ = spool.Warn("%v\n", stateErrors)
@@ -212,7 +212,7 @@ func pik() spool.ExitCode {
// TODO: Move auto-all logic into Search?
if !*flags.All && result.Target == nil && len(result.Args) > 0 && SourcesWithoutResults == nil && !ForceConfirm {
ForceConfirm = true
- SourcesWithoutResults = c
+ SourcesWithoutResults = &c
return pik()
}
diff --git a/model/multi.go b/model/multi.go
new file mode 100644
index 0000000..35b792f
--- /dev/null
+++ b/model/multi.go
@@ -0,0 +1,73 @@
+package model
+
+import (
+ "errors"
+ "io/fs"
+ "path/filepath"
+ "slices"
+ "strings"
+)
+
+func Include(root fs.FS, st *State, data map[string]*SourceData, indexers []Indexer, runners []Runner) error {
+ for _, s := range st.Sources {
+ if data[s.Path] == nil || data[s.Path].Includes == nil {
+ continue
+ }
+ for _, w := range data[s.Path].Includes {
+ if IsRelative(w) {
+ p, err := filepath.Abs(filepath.Join(s.Path, w))
+ if err != nil {
+ return err
+ }
+
+ // check if source is already included
+ for _, maybeSource := range st.Sources {
+ if maybeSource.Path == p {
+ continue
+ }
+ }
+
+ src, errs := NewSource(root, p, indexers, runners)
+ if len(errs) > 0 {
+ return errors.Join(errs...)
+ }
+ s.Targets = append(s.Targets, src.Targets...)
+ }
+ }
+ }
+ return nil
+}
+
+func Wants(root fs.FS, st *State, data map[string]*SourceData, indexers []Indexer, runners []Runner) error {
+ for i, s := range st.Sources {
+ if data[s.Path] == nil || data[s.Path].Wants == nil {
+ continue
+ }
+ for _, w := range data[s.Path].Wants {
+ if IsRelative(w) {
+ p, err := filepath.Abs(filepath.Join(s.Path, w))
+ if err != nil {
+ return err
+ }
+
+ // check if source is already included
+ for _, maybeSource := range st.Sources {
+ if maybeSource.Path == p {
+ continue
+ }
+ }
+
+ src, errs := NewSource(root, p, indexers, runners)
+ if len(errs) > 0 {
+ return errors.Join(errs...)
+ }
+ st.Sources = slices.Insert(st.Sources, i+1, src)
+ }
+ }
+ }
+ return nil
+}
+
+func IsRelative(path string) bool {
+ return strings.HasPrefix(path, ".")
+}
diff --git a/model/new.go b/model/new.go
index 94e19a8..fc57ad0 100644
--- a/model/new.go
+++ b/model/new.go
@@ -4,13 +4,58 @@ import (
"errors"
"github.com/ewy1/pik/flags"
"github.com/ewy1/pik/identity"
+ "github.com/ewy1/pik/spool"
"io/fs"
"path/filepath"
"strings"
"sync"
)
-func NewState(rootFs fs.FS, locations []string, indexers []Indexer, runners []Runner) (*State, []error) {
+func NewSource(rootFs fs.FS, loc string, indexers []Indexer, runners []Runner) (*Source, []error) {
+ var errs []error
+ _, dirName := filepath.Split(strings.TrimSuffix(loc, "/"))
+ src := &Source{
+ Path: loc,
+ Identity: identity.New(dirName),
+ Whitelists: make(map[string][]string),
+ }
+ loc = strings.TrimSuffix(loc, "/")
+ loc = strings.TrimPrefix(loc, "/")
+
+ if loc == "" {
+ return nil, nil
+ }
+
+ locationWg := sync.WaitGroup{}
+ var targets = make([][]Target, len(indexers), len(indexers))
+ for ti, indexer := range indexers {
+ 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, subFs, runners)
+ if err != nil && !errors.Is(err, fs.ErrNotExist) {
+ errs = append(errs, err)
+ return
+ }
+ targets[ti] = result
+ })
+ }
+ locationWg.Wait()
+
+ for _, t := range targets {
+ if t == nil {
+ continue
+ }
+ src.Targets = append(src.Targets, t...)
+ }
+
+ return src, errs
+}
+
+func NewState(rootFs fs.FS, locations []string, data map[string]*SourceData, indexers []Indexer, runners []Runner) (*State, []error) {
var errs []error
st := &State{
All: *flags.All,
@@ -19,46 +64,12 @@ func NewState(rootFs fs.FS, locations []string, indexers []Indexer, runners []Ru
var sources = make([]*Source, len(locations), len(locations))
for i, loc := range locations {
wg.Go(func() {
- _, dirName := filepath.Split(strings.TrimSuffix(loc, "/"))
- src := &Source{
- Path: loc,
- Identity: identity.New(dirName),
- Whitelists: make(map[string][]string),
+ src, err := NewSource(rootFs, loc, indexers, runners)
+ errs = append(errs, err...)
+ for _, e := range err {
+ _, _ = spool.Warn("%v\n", e)
}
sources[i] = src
- loc = strings.TrimSuffix(loc, "/")
- loc = strings.TrimPrefix(loc, "/")
-
- if loc == "" {
- return
- }
-
- locationWg := sync.WaitGroup{}
- var targets = make([][]Target, len(indexers), len(indexers))
- for ti, indexer := range indexers {
- 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, subFs, runners)
- if err != nil && !errors.Is(err, fs.ErrNotExist) {
- errs = append(errs, err)
- return
- }
- targets[ti] = result
- })
- }
- locationWg.Wait()
-
- for _, t := range targets {
- if t == nil {
- continue
- }
- sources[i].Targets = append(sources[i].Targets, t...)
- }
-
})
}
@@ -71,5 +82,14 @@ func NewState(rootFs fs.FS, locations []string, indexers []Indexer, runners []Ru
st.Sources = append(st.Sources, s)
}
+ err := Wants(rootFs, st, data, indexers, runners)
+ if err != nil {
+ errs = append(errs, err)
+ }
+ err = Include(rootFs, st, data, indexers, runners)
+ if err != nil {
+ errs = append(errs, err)
+ }
+
return st, errs
}
diff --git a/model/source.go b/model/source.go
index 246929e..2844d23 100644
--- a/model/source.go
+++ b/model/source.go
@@ -6,10 +6,20 @@ import (
"github.com/ewy1/pik/spool"
)
+// SourceData is data we want to save from a Source before it has been properly instantiated
+type SourceData struct {
+ Aliases []string
+ Icon string
+ Path string
+ Wants []string
+ Includes []string
+}
+
// Source is a location containing stuff we can run
// these get created when we find a makefile, .pik folder, etc.
type Source struct {
identity.Identity
+ SourceData
Tags
Path string
Targets []Target
diff --git a/paths/paths.go b/paths/paths.go
index b714a23..1e6e2f9 100644
--- a/paths/paths.go
+++ b/paths/paths.go
@@ -9,13 +9,14 @@ import (
)
var (
- Ifs string
- ConfigDir = Empty()
- CacheDir = Empty()
- ContextsFile = Empty()
- HomeDir = Empty()
- System = Empty()
- This = "pik"
+ Ifs string
+ ConfigDir = Empty()
+ CacheDir = Empty()
+ ContextsFile = Empty()
+ HomeDir = Empty()
+ System = Empty()
+ This = "pik"
+ ContextsFileName = "contexts"
)
var Paths = []*Path{
@@ -50,7 +51,7 @@ func (p *paths) Init() error {
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"))
+ ContextsFile.Set(path.Join(CacheDir.String(), ContextsFileName))
System.Set(path.Join(ConfigDir.String()))
Ifs = os.Getenv("IFS")