package namedconf import ( "fmt" "strings" ) // Value is a single word or quoted string. type Value struct { Text string // unescaped text Quoted bool Pos Pos } // Block is a brace-enclosed list of statements. type Block struct { Stmts []*Stmt Pos Pos } // Item is one element of a statement: either a value, a "!" negation // marker, or a block. type Item struct { Value *Value Block *Block Bang bool } // Stmt is a statement: a sequence of items terminated by a semicolon. type Stmt struct { Items []Item Pos Pos // Comment is written before the statement (only by Jarl: comments in // parsed text are dropped). ExpandRanges notes the range there. Comment string } // Keyword returns the statement's first word, lowercased, or "" if the // statement doesn't start with a value. func (s *Stmt) Keyword() string { if len(s.Items) == 0 || s.Items[0].Value == nil { return "" } return strings.ToLower(s.Items[0].Value.Text) } // Arg returns the text of the n-th value after the keyword (0-based), or "". func (s *Stmt) Arg(n int) string { idx := 0 for _, it := range s.Items[1:] { if it.Value == nil { break } if idx == n { return it.Value.Text } idx++ } return "" } // Values returns the texts of the leading values after the keyword, up to // the first block. func (s *Stmt) Values() []string { var out []string for _, it := range s.Items[1:] { if it.Value == nil { break } out = append(out, it.Value.Text) } return out } // FirstBlock returns the statement's first block, or nil. func (s *Stmt) FirstBlock() *Block { for _, it := range s.Items { if it.Block != nil { return it.Block } } return nil } // Rest returns a copy of the statement without its keyword. func (s *Stmt) Rest() *Stmt { if len(s.Items) == 0 { return &Stmt{Pos: s.Pos} } return &Stmt{Items: append([]Item(nil), s.Items[1:]...), Pos: s.Pos} } // Find returns the statements in stmts with the given keyword. func Find(stmts []*Stmt, keyword string) []*Stmt { var out []*Stmt for _, s := range stmts { if s.Keyword() == keyword { out = append(out, s) } } return out } // FindOne returns the first statement with the given keyword, or nil. func FindOne(stmts []*Stmt, keyword string) *Stmt { for _, s := range stmts { if s.Keyword() == keyword { return s } } return nil } // NewStmt builds a statement from values: strings become unquoted words and // *Block become blocks. Use Q to make a quoted string. func NewStmt(items ...any) *Stmt { st := &Stmt{} for _, it := range items { switch v := it.(type) { case string: st.Items = append(st.Items, Item{Value: &Value{Text: v}}) case Quoted: st.Items = append(st.Items, Item{Value: &Value{Text: string(v), Quoted: true}}) case *Block: st.Items = append(st.Items, Item{Block: v}) case Item: st.Items = append(st.Items, v) default: panic(fmt.Sprintf("namedconf.NewStmt: unsupported item %T", it)) } } return st } // Quoted marks a string for NewStmt as a quoted string. type Quoted string // NewBlock builds a block from statements. func NewBlock(stmts ...*Stmt) *Block { return &Block{Stmts: stmts} } // Clone returns a deep copy of the statement. func (s *Stmt) Clone() *Stmt { out := &Stmt{Pos: s.Pos, Items: make([]Item, len(s.Items))} for i, it := range s.Items { switch { case it.Value != nil: v := *it.Value out.Items[i] = Item{Value: &v} case it.Block != nil: b := &Block{Pos: it.Block.Pos} for _, st := range it.Block.Stmts { b.Stmts = append(b.Stmts, st.Clone()) } out.Items[i] = Item{Block: b} default: out.Items[i] = it } } return out }