package main import ( "fmt" "log" "strconv" "strings" "golang.org/x/exp/slices" ) // A interconnected structure which connects to multiple entities // // Handles depth (in tabs) for the Endless sky save format (which is ugly) type Node struct { Tags []string // A tag, useful for identifying this node from other nodes in Children Line string // The line of text for the node (the Parser removes the depth number of tabs) Parent *Node `json:"-"` // The parent of this node (used for traversing up the node tree), must be json ignored or recursive looping Children []*Node // Any other nodes that can be tied to this node } func (n *Node) HasTag(tag string) bool { return slices.Contains(n.Tags, tag) } func (n *Node) AddTag(tag string) { if !n.HasTag(tag) { n.Tags = append(n.Tags, strings.ToLower(tag)) } } func (n *Node) RmTag(tag string) { if n.HasTag(tag) { tags := []string{} for _, t := range n.Tags { if t != tag { tags = append(tags, t) } } n.Tags = tags } } // How deep in tabs we are func (n *Node) Depth() int { if n.Parent == nil { return 0 } depth := 0 at := n for at.Parent != nil { at = at.Parent depth += 1 } if at.HasTag("root") { depth -= 1 } return depth } // Returns the Endless sky representation (node depth is number of tabs) // // Useful for representing the node's line at the correct depth func (n *Node) String() string { return fmt.Sprintf("%s%s", strings.Repeat("\t", n.Depth()), n.Line) } // Strings everything (the current line plus any children) func (n *Node) StringAll() string { out := strings.Builder{} out.WriteString(n.String() + "\n") if n.Len() != 0 { // Recurse over children calling this function for the line and other children for _, kid := range n.Children { out.WriteString(kid.StringAll()) } } return out.String() } // Adds the given node as a child // // Used to build complex structures func (n *Node) AddChild(child *Node) { child.Parent = n n.Children = append(n.Children, child) } // Creates a new child node and returns it // // Used to build complex structures func (n *Node) NewChild() *Node { kid := Node{ Parent: n, } n.AddChild(&kid) return &kid } // Removes the child given index func (n *Node) RmChild(index int) error { if index > n.Len() || index < 0 { return fmt.Errorf("invalid index %d (min=0, max=%d)", index, n.Len()) } var kids []*Node for idx, k := range n.Children { if idx != index { kids = append(kids, k) } else { k.Parent = nil } } n.Children = kids return nil } // Removes all children // // Useful for when you will be reassigning a collection of node children underneath func (n *Node) RmAllChildren() { for _, k := range n.Children { k.Parent = nil } n.Children = []*Node{} } // Removes the current node from the parent // // Similar to RmChild or RmAllChildren except called from the child's perspective func (n *Node) DetachFromParent() (*Node, error) { if n.Parent != nil { p := n.Parent index := -1 for idx, k := range p.Children { if k.Line == n.Line && k.Depth() == n.Depth() && slices.Equal(k.Tags, n.Tags) && k.Len() == n.Len() { index = idx break } } if index != -1 { return p.SplitChild(index) } else { return nil, fmt.Errorf("failed detaching, didn't find ourselves in parent") } } return nil, fmt.Errorf("failed detaching, invalid parent") } // Obtain a pointer to the child func (n *Node) Child(index int) (*Node, error) { if index > n.Len() || index < 0 { return nil, fmt.Errorf("invalid index %d (min=0, max=%d)", index, n.Len()) } return n.Children[index], nil } // Replaces the child by index func (n *Node) ReplaceChild(index int, node *Node) error { if index > n.Len() || index < 0 { return fmt.Errorf("invalid index %d (min=0, max=%d)", index, n.Len()) } n.Children[index].Parent = nil node.Parent = n n.Children[index] = node return nil } // Removes the child but returns it (useful for if you plan on replacing it) func (n *Node) SplitChild(index int) (*Node, error) { if index > n.Len() || index < 0 { return nil, fmt.Errorf("invalid index %d (min=0, max=%d)", index, n.Len()) } k := n.Children[index] k.Parent = nil err := n.RmChild(index) if err != nil { k.Parent = n // Some error recovery return nil, err } return k, nil } // Returns the number of children underneath func (n *Node) Len() int { return len(n.Children) } // Endless sky stores most data as 'key value' // // With special cases: // // As String '"some key" value' (Or '"some key" "some value"') // // As Int: '"some key" 13' // // As Float: '"some key" 9.81' // // Return's key part func (n *Node) Key() string { parts := strings.Split(n.Line, " ") if strings.Contains(parts[0], "\"") { pref := "" for _, part := range parts { if strings.HasSuffix(part, "\"") { if pref != "" { pref += " " } pref += part break } if pref != "" { pref += " " } pref += part } pref = strings.ReplaceAll(pref, "\"", "") return pref } return parts[0] } // Endless sky stores mose data as 'key value' // // With special cases: // // As String '"some key" value' (Or '"some key" "some value"') // // As Int: '"some key" 13' // // As Float: '"some key" 9.81' // // Return's value part (as string) func (n *Node) Value() string { key := n.Key() val := strings.Replace(n.Line, n.Format(key)+" ", "", 1) val = strings.ReplaceAll(val, "\"", "") return val } // Returns the value as integer (errors return 0) func (n *Node) ValueInt() int { v, err := strconv.Atoi(n.Value()) if err != nil { log.Printf("ValueInt('%s') => %v", n.Value(), err) return 0 } return v } // Returns the value as float64 (errors return 0.0, but are logged) func (n *Node) ValueFloat64() float64 { v, err := strconv.ParseFloat(n.Value(), 64) if err != nil { log.Printf("ValueFloat64('%s') => %v", n.Value(), err) return 0.0 } return v } // Returns the value as float32 (errors return 0.0, but are logged) func (n *Node) ValueFloat32() float32 { v, err := strconv.ParseFloat(n.Value(), 32) if err != nil { log.Printf("ValueFloat32('%s') => %v", n.Value(), err) return 0.0 } return float32(v) } // Format function // // Endless sky demands any multi word key or value must be wrapped in "s // // Both Node.Key() and Node.Value() remove the "s so this must be called to form the new Node.Line (for setters) func (n *Node) Format(value string) string { if strings.Contains(value, " ") { return "\"" + value + "\"" } return value } // Assigns a new value for the Node // // This updates/changes the Node.Line (as that's the only way to do it) func (n *Node) SetValue(val interface{}) { if val == nil { // Make it a flag/boolean n.Line = n.Format(n.Key()) return } str_val := fmt.Sprintf("%v", val) n.Line = fmt.Sprintf("%s %s", n.Format(n.Key()), n.Format(str_val)) } // Assigns a new key for the Node // // This must be a string (key's can't be anything else) // // This updates/changes the Node.Line (as that's the only way to do it) func (n *Node) SetKey(key string) { val := n.Value() if val != "" { n.Line = fmt.Sprintf("%s %s", n.Format(key), n.Format(val)) } else { n.Line = n.Format(key) } } // Assigns the whole line from the key to the value func (n *Node) Set(key string, val interface{}) { if val == nil { n.Line = n.Format(key) return } str_val := fmt.Sprintf("%v", val) n.Line = fmt.Sprintf("%s %s", n.Format(key), n.Format(str_val)) }