summaryrefslogtreecommitdiff
path: root/model
diff options
context:
space:
mode:
Diffstat (limited to 'model')
-rw-r--r--model/indexer.go9
-rw-r--r--model/init.go5
-rw-r--r--model/matches.go5
-rw-r--r--model/mod.go5
-rw-r--r--model/new.go45
-rw-r--r--model/runner.go11
-rw-r--r--model/source.go61
-rw-r--r--model/state.go10
-rw-r--r--model/tags.go77
-rw-r--r--model/tags_test.go128
-rw-r--r--model/target.go23
11 files changed, 379 insertions, 0 deletions
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
+}