diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-17 10:56:19 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-17 10:56:19 +0200 |
| commit | 48d1781fed3f9ea5cfd2b270bd92fd2826605636 (patch) | |
| tree | 8abddf0ae7aaab97426cad1f65038560c8b85b9f /gui/internal/model/cond.go | |
| parent | a2cb20851499e9c00bd3bf642680a9ce3148cae8 (diff) | |
| download | krino-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.go | 126 |
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) + } +} |
