Collapse nested kbsearch/chats go.mod into the root module. Ranking stays cgo-free under internal/brain/rank so CI does not need ladybug. bin/kb/search is a deprecation wrapper that still sets CGO and builds the binary.
107 lines
2.0 KiB
Go
107 lines
2.0 KiB
Go
// YAML emitter ported from bin/kb/yamlout.py — preserves insertion order.
|
|
package brain
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// KV is an ordered key-value pair for maps.
|
|
type KV struct {
|
|
K string
|
|
V any
|
|
}
|
|
|
|
// Dict is an ordered map (slice of KV).
|
|
type Dict []KV
|
|
|
|
func toYAML(node any, indent int) string {
|
|
pad := strings.Repeat(" ", indent)
|
|
switch n := node.(type) {
|
|
case Dict:
|
|
if len(n) == 0 {
|
|
return pad + "{}\n"
|
|
}
|
|
var b strings.Builder
|
|
for _, kv := range n {
|
|
switch nv := kv.V.(type) {
|
|
case Dict:
|
|
if len(nv) == 0 {
|
|
b.WriteString(pad + kv.K + ": {}\n")
|
|
} else {
|
|
b.WriteString(pad + kv.K + ":\n" + toYAML(nv, indent+1))
|
|
}
|
|
case []any:
|
|
if len(nv) == 0 {
|
|
b.WriteString(pad + kv.K + ": []\n")
|
|
} else {
|
|
b.WriteString(pad + kv.K + ":\n" + toYAML(nv, indent+1))
|
|
}
|
|
default:
|
|
b.WriteString(pad + kv.K + ": " + scalar(nv) + "\n")
|
|
}
|
|
}
|
|
return b.String()
|
|
|
|
case []any:
|
|
if len(n) == 0 {
|
|
return pad + "[]\n"
|
|
}
|
|
var b strings.Builder
|
|
for _, item := range n {
|
|
if d, ok := item.(Dict); ok {
|
|
b.WriteString(pad + "-\n" + toYAML(d, indent+1))
|
|
} else {
|
|
b.WriteString(pad + "- " + scalar(item) + "\n")
|
|
}
|
|
}
|
|
return b.String()
|
|
|
|
default:
|
|
return pad + scalar(node) + "\n"
|
|
}
|
|
}
|
|
|
|
func scalar(v any) string {
|
|
switch t := v.(type) {
|
|
case nil:
|
|
return "null"
|
|
case bool:
|
|
if t {
|
|
return "true"
|
|
}
|
|
return "false"
|
|
case int:
|
|
return strconv.Itoa(t)
|
|
case int64:
|
|
return strconv.FormatInt(t, 10)
|
|
case float64:
|
|
return fmtFloat(t)
|
|
case float32:
|
|
return fmtFloat(float64(t))
|
|
case string:
|
|
return quoteIfNeeded(t)
|
|
default:
|
|
// fallback
|
|
return fmt.Sprintf("%v", v)
|
|
}
|
|
}
|
|
|
|
func fmtFloat(f float64) string {
|
|
s := strconv.FormatFloat(f, 'g', -1, 64)
|
|
if !strings.ContainsAny(s, ".eE") {
|
|
s += ".0"
|
|
}
|
|
return s
|
|
}
|
|
|
|
func quoteIfNeeded(s string) string {
|
|
if strings.Contains(s, "\n") {
|
|
return strconv.Quote(s)
|
|
}
|
|
if s == "" || strings.ContainsAny(s, ":#'\"[]{}&*!|>%@`") || s != strings.TrimSpace(s) {
|
|
return strconv.Quote(s)
|
|
}
|
|
return s
|
|
} |