aboutsummaryrefslogtreecommitdiff
path: root/internal/config/print.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/config/print.go')
-rw-r--r--internal/config/print.go115
1 files changed, 115 insertions, 0 deletions
diff --git a/internal/config/print.go b/internal/config/print.go
new file mode 100644
index 0000000..5c3d467
--- /dev/null
+++ b/internal/config/print.go
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package config
+
+import (
+ "strings"
+
+ "krino/internal/sexp"
+)
+
+// PrintRule renders a rule the way krino.conf(5) and the examples write
+// one: the name on the first line, then each item on its own line indented
+// two spaces, conditions inside (when ...) aligned under the first, and the
+// closing paren on the last line. A GUI that edits a rule through a form
+// writes the result back with this and Splice, leaving the rest of the file
+// untouched (GUI design ยง5.1).
+//
+// What it prints comes from the parsed rule, so anything the parser dropped
+// - comments inside the form, the writer's own line breaks - is not in the
+// output; Splice's caller warns about that before replacing a form that
+// holds comments.
+func PrintRule(r *Rule) string {
+ var b strings.Builder
+ b.WriteString("(rule " + sexp.Quote(r.Name) + "\n")
+ if r.HasWhen {
+ b.WriteString(" (when ")
+ for i, c := range r.When {
+ if i > 0 {
+ b.WriteString("\n ")
+ }
+ b.WriteString(printNode(c))
+ }
+ b.WriteString(")\n")
+ }
+ for _, line := range printSettings(r.Settings) {
+ b.WriteString(" " + line + "\n")
+ }
+ for _, a := range r.Actions {
+ b.WriteString(" " + printAction(a) + "\n")
+ }
+ if r.Stop {
+ b.WriteString(" (stop)\n")
+ }
+ // The closing paren belongs to the last line written, whichever it was.
+ out := strings.TrimRight(b.String(), "\n")
+ return out + ")\n"
+}
+
+// PrintExclude renders an (exclude ...) form on one line.
+func PrintExclude(x *Exclude) string {
+ var b strings.Builder
+ b.WriteString("(exclude")
+ for _, c := range x.When {
+ b.WriteString(" " + printNode(c))
+ }
+ b.WriteString(")\n")
+ return b.String()
+}
+
+// Splice replaces the bytes of the form between start and end - a form's
+// Pos and End - with text, and returns the new file contents. Every other
+// byte of src, comments and layout included, is kept exactly.
+func Splice(src []byte, start, end sexp.Pos, text string) []byte {
+ out := make([]byte, 0, len(src)-(end.Offset-start.Offset)+len(text))
+ out = append(out, src[:start.Offset]...)
+ out = append(out, text...)
+ return append(out, src[end.Offset:]...)
+}
+
+// printAction renders one action form.
+func printAction(a Action) string {
+ switch a.Kind {
+ case Delete:
+ return "(delete)"
+ case DeletePermanent:
+ return "(delete permanent)"
+ }
+ return "(" + a.Kind.String() + " " + sexp.Quote(a.Arg) + ")"
+}
+
+// printSettings renders the settings a rule may carry (case, fold,
+// on-conflict), in that order, leaving out those it does not set.
+func printSettings(s Settings) []string {
+ var out []string
+ if s.Case != nil {
+ out = append(out, "(case "+s.Case.String()+")")
+ }
+ if s.Fold != nil {
+ word := "no"
+ if *s.Fold {
+ word = "yes"
+ }
+ out = append(out, "(fold "+word+")")
+ }
+ if s.OnConflict != nil {
+ out = append(out, "(on-conflict "+s.OnConflict.String()+")")
+ }
+ return out
+}
+
+// printNode renders one condition node: a symbol as written, a string
+// quoted, a list as its head and arguments separated by single spaces.
+func printNode(n *sexp.Node) string {
+ switch n.Kind {
+ case sexp.String:
+ return sexp.Quote(n.Text)
+ case sexp.Symbol:
+ return n.Text
+ }
+ parts := make([]string, 0, len(n.Children))
+ for _, c := range n.Children {
+ parts = append(parts, printNode(c))
+ }
+ return "(" + strings.Join(parts, " ") + ")"
+}