From 45a297a8e526094e8fce6e2c5c0fd89b381d1765 Mon Sep 17 00:00:00 2001 From: ewy Date: Tue, 14 Apr 2026 16:37:17 +0200 Subject: i have to commit at some point! --- model/indexer.go | 9 ++++ model/init.go | 5 +++ model/matches.go | 5 +++ model/mod.go | 5 +++ model/new.go | 45 +++++++++++++++++++ model/runner.go | 11 +++++ model/source.go | 61 +++++++++++++++++++++++++ model/state.go | 10 +++++ model/tags.go | 77 ++++++++++++++++++++++++++++++++ model/tags_test.go | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++++ model/target.go | 23 ++++++++++ 11 files changed, 379 insertions(+) create mode 100644 model/indexer.go create mode 100644 model/init.go create mode 100644 model/matches.go create mode 100644 model/mod.go create mode 100644 model/new.go create mode 100644 model/runner.go create mode 100644 model/source.go create mode 100644 model/state.go create mode 100644 model/tags.go create mode 100644 model/tags_test.go create mode 100644 model/target.go (limited to 'model') diff --git a/model/indexer.go b/model/indexer.go new file mode 100644 index 0000000..e82e3af --- /dev/null +++ b/model/indexer.go @@ -0,0 +1,9 @@ +package model + +import ( + "io/fs" +) + +type Indexer interface { + Index(path string, fs fs.FS, runners []Runner) ([]Target, error) +} diff --git a/model/init.go b/model/init.go new file mode 100644 index 0000000..503d2b8 --- /dev/null +++ b/model/init.go @@ -0,0 +1,5 @@ +package model + +type HasInit interface { + Init() error +} diff --git a/model/matches.go b/model/matches.go new file mode 100644 index 0000000..da16c4f --- /dev/null +++ b/model/matches.go @@ -0,0 +1,5 @@ +package model + +type Matches interface { + Matches(input string) bool +} diff --git a/model/mod.go b/model/mod.go new file mode 100644 index 0000000..f6a7a94 --- /dev/null +++ b/model/mod.go @@ -0,0 +1,5 @@ +package model + +type Hydrator interface { + Hydrate(source *Source, result *HydratedSource) error +} diff --git a/model/new.go b/model/new.go new file mode 100644 index 0000000..f26bc8f --- /dev/null +++ b/model/new.go @@ -0,0 +1,45 @@ +package model + +import ( + "io/fs" + "path/filepath" + "pik/identity" + "strings" +) + +func NewState(f fs.FS, locations []string, indexers []Indexer, runners []Runner) (*State, error) { + st := &State{} + for _, loc := range locations { + _, dirName := filepath.Split(loc) + src := &Source{ + Path: loc, + Identity: identity.New(dirName), + } + loc = strings.TrimSuffix(loc, "/") + loc = strings.TrimPrefix(loc, "/") + + if loc == "" { + continue + } + + for _, indexer := range indexers { + + s, err := fs.Sub(f, loc) + if err != nil { + return nil, err + } + targets, err := indexer.Index("/"+loc, s, runners) + if err != nil { + return nil, err + } + src.Targets = append(src.Targets, targets...) + } + + if src.Targets != nil { + st.Sources = append(st.Sources, src) + } + + } + + return st, nil +} diff --git a/model/runner.go b/model/runner.go new file mode 100644 index 0000000..b413d51 --- /dev/null +++ b/model/runner.go @@ -0,0 +1,11 @@ +package model + +import ( + "io/fs" +) + +type Runner interface { + Hydrate(target Target) (HydratedTarget, error) + Wants(fs fs.FS, file string, entry fs.DirEntry) (bool, error) + CreateTarget(fs fs.FS, source string, file string, entry fs.DirEntry) (Target, error) +} diff --git a/model/source.go b/model/source.go new file mode 100644 index 0000000..3a5fe10 --- /dev/null +++ b/model/source.go @@ -0,0 +1,61 @@ +package model + +import ( + "pik/identity" + "pik/paths" + "pik/spool" +) + +type Source struct { + identity.Identity + Tags + Path string + Targets []Target +} + +type HydratedSource struct { + *Source + HydratedTargets []HydratedTarget + Aliases []string + Icon string +} + +func (s *Source) Label() string { + return s.Identity.Full +} + +func (s *HydratedSource) Label() string { + if len(s.Aliases) > 0 { + return s.Aliases[0] + } + return s.Identity.Full +} + +func (s *Source) Hydrate(hydrators []Hydrator) *HydratedSource { + hs := &HydratedSource{ + Source: s, + HydratedTargets: make([]HydratedTarget, 0, len(s.Targets)), + } + for _, h := range hydrators { + err := h.Hydrate(s, hs) + if err != nil { + spool.Warn("%v", err) + } + } + for _, t := range s.Targets { + if !t.Visible() { + continue + } + ht, err := t.Hydrate(s) + if err != nil { + spool.Warn("%v", err) + continue + } + hs.HydratedTargets = append(hs.HydratedTargets, ht) + } + return hs +} + +func (s *Source) ShortPath() string { + return paths.ReplaceHome(s.Path) +} diff --git a/model/state.go b/model/state.go new file mode 100644 index 0000000..96da3eb --- /dev/null +++ b/model/state.go @@ -0,0 +1,10 @@ +package model + +type State struct { + Sources []*Source +} + +type HydratedState struct { + *State + HydratedSources []*HydratedSource +} diff --git a/model/tags.go b/model/tags.go new file mode 100644 index 0000000..e3f9f2e --- /dev/null +++ b/model/tags.go @@ -0,0 +1,77 @@ +package model + +import ( + "slices" + "strings" +) + +type Tag *string +type TagAction func(src *Source) + +func New(input string) Tag { + result := &input + TagMap[input] = result + TagList = append(TagList, result) + return result +} + +var ( + Here = New("here") + Pre = New("pre") + Post = New("post") + Final = New("final") + Hidden = New("hidden") + Single = New("single") +) + +var TagList []Tag + +var TagMap = map[string]Tag{} + +type Tags []Tag + +func (t Tags) AnyOf(expected ...Tag) bool { + if len(expected) > 1 && len(t) == 0 { + return false + } + if len(expected) == 0 { + return true + } + for _, e := range expected { + if slices.Contains(t, e) { + return true + } + } + return false +} + +func (t Tags) Has(expected Tag) bool { + return slices.Contains(t, expected) +} + +func TagsFromFilename(filename string) Tags { + var tags Tags + // if hidden + if strings.HasPrefix(filename, ".") { + filename = strings.TrimPrefix(filename, ".") + tags = append(tags, Hidden) + } + + parts := strings.Split(filename, ".") + if len(parts) == 1 { + return nil + } + + for _, p := range parts { + p = strings.ToLower(p) + if TagMap[p] != nil { + tags = append(tags, TagMap[p]) + } + } + + return tags +} + +func (t Tags) Visible() bool { + return !t.AnyOf(Hidden, Pre, Post, Final) +} diff --git a/model/tags_test.go b/model/tags_test.go new file mode 100644 index 0000000..0ca159b --- /dev/null +++ b/model/tags_test.go @@ -0,0 +1,128 @@ +//go:build test + +package model + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "os/exec" + "testing" +) + +type taggedTarget struct { + MyTags Tags +} + +func (t taggedTarget) Matches(input string) bool { + //TODO implement me + panic("implement me") +} + +func (t taggedTarget) Create(s *Source) *exec.Cmd { + //TODO implement me + panic("implement me") +} + +func (t taggedTarget) Sub() []string { + //TODO implement me + panic("implement me") +} + +func (t taggedTarget) Label() string { + //TODO implement me + panic("implement me") +} + +func (t taggedTarget) Hydrate(src *Source) (HydratedTarget, error) { + //TODO implement me + panic("implement me") +} + +func (t taggedTarget) Tags() Tags { + return t.MyTags +} + +func (t taggedTarget) ShortestId() string { + //TODO implement me + panic("implement me") +} + +func tagged(in ...Tag) taggedTarget { + return taggedTarget{MyTags: tags(in...)} +} + +func tags(in ...Tag) Tags { + return Tags(in) +} + +func TestTags_Count(t *testing.T) { + input := tags(Here, Hidden) + assert.Len(t, input, 2) +} + +func TestTags_Is(t *testing.T) { + input := tags(Final, Post) + assert.True(t, input.Has(Final)) + assert.True(t, input.Has(Post)) + assert.False(t, input.Has(Pre)) +} + +func TestTags_AnyOf(t *testing.T) { + input := tags(Final, Post) + assert.True(t, input.AnyOf(Final)) + assert.False(t, input.AnyOf(Pre, Hidden)) +} + +func TestTags_AnyOf_Mix(t *testing.T) { + input := tags(Final, Post) + assert.True(t, input.AnyOf(Post, Hidden)) +} + +func TestTags_AnyOf_EmptyInput(t *testing.T) { + input := tags() + assert.False(t, input.AnyOf(Final)) + assert.False(t, input.AnyOf(Pre, Hidden)) +} + +func TestTags_AnyOf_EmptySearch(t *testing.T) { + input := tags(Final, Post) + assert.True(t, input.AnyOf()) +} + +func TestTagsFromFilename(t *testing.T) { + inputs := []any{ + *Pre, + *Here, + *Final, + } + input := fmt.Sprintf("script.%v.%v.%v.ext", inputs...) + output := TagsFromFilename(input) + assert.Len(t, output, len(inputs)) + assert.Contains(t, output, Pre) + assert.Contains(t, output, Here) + assert.Contains(t, output, Final) +} + +func TestTagsHidden_VisibleByDefault(t *testing.T) { + input := tags(Here) + target := tagged(input...) + assert.True(t, target.Tags().Visible()) +} + +func TestTagsHidden_FromHidden(t *testing.T) { + input := tags(Hidden) + target := tagged(input...) + assert.False(t, target.Tags().Visible()) +} + +func TestTagsHidden_FromFilename(t *testing.T) { + input := TagsFromFilename(".asdf.sh") + target := tagged(input...) + assert.False(t, target.Tags().Visible()) +} + +func TestTagsHidden_FromTrigger(t *testing.T) { + input := tags(Pre, Here) + target := tagged(input...) + assert.False(t, target.Tags().Visible()) +} diff --git a/model/target.go b/model/target.go new file mode 100644 index 0000000..5be97ed --- /dev/null +++ b/model/target.go @@ -0,0 +1,23 @@ +package model + +import ( + "os/exec" +) + +type Target interface { + Matches + Create(s *Source) *exec.Cmd + Sub() []string + Label() string + Hydrate(src *Source) (HydratedTarget, error) + Tags() Tags + ShortestId() string + Visible() bool +} + +type HydratedTarget interface { + Target + Icon() string + Description() string + Target() Target +} -- cgit v1.3