Files
jarl/internal/namedconf/printer.go
T
adamandClaude Opus 5.5 ad8eb3fc3d Show and edit option values laid out as in named.conf
Values with blocks (logging, controls, ...) are shown over several lines
with indentation, and edited that way: the editor grows with its content,
Tab indents, Enter keeps the indentation. With a draft open, option tables
show the draft's values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 11:55:41 +00:00

179 lines
4.0 KiB
Go

package namedconf
import (
"strings"
)
// maxInline is the widest a block may be to be printed on one line.
const maxInline = 72
// Format returns stmts in canonical form, one statement per line, with
// nested blocks indented by tabs.
func Format(stmts []*Stmt) string {
var sb strings.Builder
for _, s := range stmts {
writeStmt(&sb, s, 0)
}
return sb.String()
}
// FormatStmt returns a single statement in canonical form, indented by
// indent tabs, including the trailing newline.
func FormatStmt(s *Stmt, indent int) string {
var sb strings.Builder
writeStmt(&sb, s, indent)
return sb.String()
}
// Compact returns the statement's items on a single line, without the
// trailing semicolon: `allow-query { 10.0.0.0/8; localhost; }`.
func Compact(items []Item) string {
var sb strings.Builder
writeItemsInline(&sb, items)
return sb.String()
}
// CompactStmt returns the statement on a single line with its semicolon.
func CompactStmt(s *Stmt) string { return Compact(s.Items) + ";" }
// FormatValue returns an option's value laid out as named.conf writes it:
// blocks over several lines, indented with tabs. Values without a block,
// and values that don't parse, are returned as they are.
func FormatValue(name, value string) string {
if !strings.Contains(value, "{") {
return value
}
st, err := ParseOptionValue(name, value)
if err != nil {
return value
}
out := strings.TrimSuffix(strings.TrimRight(FormatStmt(st, 0), "\n"), ";")
if rest, ok := strings.CutPrefix(out, st.Keyword()); ok {
return strings.TrimLeft(rest, " ")
}
return value
}
// QuoteString returns s as a named.conf quoted string.
func QuoteString(s string) string {
var sb strings.Builder
sb.WriteByte('"')
for i := 0; i < len(s); i++ {
if s[i] == '"' || s[i] == '\\' {
sb.WriteByte('\\')
}
sb.WriteByte(s[i])
}
sb.WriteByte('"')
return sb.String()
}
func valueText(v *Value) string {
if v.Quoted {
return QuoteString(v.Text)
}
return v.Text
}
func writeStmt(sb *strings.Builder, s *Stmt, indent int) {
tabs := strings.Repeat("\t", indent)
sb.WriteString(tabs)
inline := Compact(s.Items)
if !hasNestedBlocks(s.Items) && len(inline)+indent*8 <= maxInline+8 {
sb.WriteString(inline)
sb.WriteString(";\n")
return
}
needSpace := false
for _, it := range s.Items {
switch {
case it.Bang:
if needSpace {
sb.WriteByte(' ')
}
sb.WriteByte('!')
needSpace = false
case it.Value != nil:
if needSpace {
sb.WriteByte(' ')
}
sb.WriteString(valueText(it.Value))
needSpace = true
case it.Block != nil:
if needSpace {
sb.WriteByte(' ')
}
blockInline := Compact([]Item{it})
if !blockHasBlocks(it.Block) && len(blockInline) <= 40 {
sb.WriteString(blockInline)
} else if len(it.Block.Stmts) == 0 {
sb.WriteString("{ }")
} else {
sb.WriteString("{\n")
for _, inner := range it.Block.Stmts {
writeStmt(sb, inner, indent+1)
}
sb.WriteString(tabs)
sb.WriteString("}")
}
needSpace = true
}
}
sb.WriteString(";\n")
}
func hasNestedBlocks(items []Item) bool {
for _, it := range items {
if it.Block != nil && blockHasBlocks(it.Block) {
return true
}
}
return false
}
func blockHasBlocks(b *Block) bool {
for _, s := range b.Stmts {
for _, it := range s.Items {
if it.Block != nil {
return true
}
}
}
return false
}
func writeItemsInline(sb *strings.Builder, items []Item) {
needSpace := false
for _, it := range items {
switch {
case it.Bang:
if needSpace {
sb.WriteByte(' ')
}
sb.WriteByte('!')
needSpace = false
case it.Value != nil:
if needSpace {
sb.WriteByte(' ')
}
sb.WriteString(valueText(it.Value))
needSpace = true
case it.Block != nil:
if needSpace {
sb.WriteByte(' ')
}
if len(it.Block.Stmts) == 0 {
sb.WriteString("{ }")
} else {
sb.WriteString("{ ")
for _, s := range it.Block.Stmts {
writeItemsInline(sb, s.Items)
sb.WriteString("; ")
}
sb.WriteString("}")
}
needSpace = true
}
}
}