aboutsummaryrefslogtreecommitdiff
path: root/gui/internal/model/cond.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-17 10:56:19 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-17 10:56:19 +0200
commit48d1781fed3f9ea5cfd2b270bd92fd2826605636 (patch)
tree8abddf0ae7aaab97426cad1f65038560c8b85b9f /gui/internal/model/cond.go
parenta2cb20851499e9c00bd3bf642680a9ce3148cae8 (diff)
downloadkrino-48d1781fed3f9ea5cfd2b270bd92fd2826605636.tar.gz
krino-48d1781fed3f9ea5cfd2b270bd92fd2826605636.zip
gui: conditions as a nested tree, and a way to add a directory
Diffstat (limited to 'gui/internal/model/cond.go')
-rw-r--r--gui/internal/model/cond.go126
1 files changed, 126 insertions, 0 deletions
diff --git a/gui/internal/model/cond.go b/gui/internal/model/cond.go
new file mode 100644
index 0000000..04cc4d1
--- /dev/null
+++ b/gui/internal/model/cond.go
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package model
+
+import (
+ "fmt"
+ "strings"
+
+ "krino/internal/sexp"
+)
+
+// CondOps are the forms that hold other conditions.
+var CondOps = map[string]bool{"and": true, "or": true, "not": true}
+
+// CondTests are the tests a condition can make, in the order a picker
+// offers them.
+var CondTests = []string{"type", "name", "path", "content", "size", "age", "duplicate", "matched"}
+
+// Cond is one condition in the tree the forms editor shows: either a test,
+// with its arguments as the file writes them, or one of and / or / not
+// holding the conditions under it (GUI design ยง5.1).
+type Cond struct {
+ Kind string
+ Args string
+ Children []*Cond
+}
+
+// ParseCond reads a parsed form into the tree. Anything that is not a list
+// - which the config parser refuses anyway - comes back as a test with the
+// text as its arguments, so nothing is ever silently dropped.
+func ParseCond(n *sexp.Node) *Cond {
+ if n == nil {
+ return &Cond{Kind: "type"}
+ }
+ if n.Kind != sexp.List || len(n.Children) == 0 {
+ return &Cond{Kind: n.String()}
+ }
+ head := n.Head()
+ if CondOps[head] {
+ c := &Cond{Kind: head}
+ for _, child := range n.Args() {
+ c.Children = append(c.Children, ParseCond(child))
+ }
+ return c
+ }
+ var args []string
+ for _, a := range n.Args() {
+ args = append(args, a.String())
+ }
+ return &Cond{Kind: head, Args: strings.Join(args, " ")}
+}
+
+// Text writes the condition back as krino reads it.
+func (c *Cond) Text() string {
+ if c == nil {
+ return ""
+ }
+ if CondOps[c.Kind] {
+ parts := make([]string, 0, len(c.Children))
+ for _, child := range c.Children {
+ if t := child.Text(); t != "" {
+ parts = append(parts, t)
+ }
+ }
+ if len(parts) == 0 {
+ return ""
+ }
+ return "(" + c.Kind + " " + strings.Join(parts, " ") + ")"
+ }
+ args := strings.TrimSpace(c.Args)
+ if args == "" {
+ return "(" + c.Kind + ")"
+ }
+ return "(" + c.Kind + " " + args + ")"
+}
+
+// Parse checks that a condition is one krino can read, and says where it is
+// wrong if it is not.
+func (c *Cond) Parse() error {
+ text := c.Text()
+ if text == "" {
+ return fmt.Errorf("%s has nothing under it", c.Kind)
+ }
+ nodes, err := sexp.Parse("form", []byte(text))
+ if err != nil {
+ return err
+ }
+ if len(nodes) != 1 {
+ return fmt.Errorf("%s is not one condition", text)
+ }
+ return nil
+}
+
+// Add puts a new condition under an operator; a test gets none.
+func (c *Cond) Add(kind string) *Cond {
+ if !CondOps[c.Kind] {
+ return nil
+ }
+ child := &Cond{Kind: kind}
+ c.Children = append(c.Children, child)
+ return child
+}
+
+// Remove takes one condition out of the tree, wherever it is, and reports
+// whether it found it. The root itself is never removed by this.
+func (c *Cond) Remove(target *Cond) bool {
+ for i, child := range c.Children {
+ if child == target {
+ c.Children = append(c.Children[:i], c.Children[i+1:]...)
+ return true
+ }
+ if child.Remove(target) {
+ return true
+ }
+ }
+ return false
+}
+
+// Walk calls f for every condition in the tree, depth first, with how deep
+// it sits and the operator holding it (nil for the roots).
+func (c *Cond) Walk(depth int, parent *Cond, f func(node, parent *Cond, depth int)) {
+ f(c, parent, depth)
+ for _, child := range c.Children {
+ child.Walk(depth+1, c, f)
+ }
+}