SPB Git forge
15commits 1branches 0releases
29.7 MBsize
maindefault branch
10 days agolast push
TypeScript 36.3% Python 31.8% Go 18% JavaScript 9.8% Shell 1.9% SQL 1.4% CSS 0.5%
3.8 KB · 160 lines go
Raw Blame History
1package config23import (4	"bufio"5	"bytes"6	"fmt"7	"strings"8)910// parseYAML reads the flat YAML subset used by probe.yaml:11//12//	key: value            # scalar (quotes optional, comments allowed)13//	key: [a, "b", c]      # inline list14//	key:                  # block list15//	  - a16//	  - b17//	key: []               # empty list18//19// Values are returned as string or []string; "~" / "null" / empty become "". Nested mappings are rejected.20func parseYAML(data []byte) (map[string]any, error) {21	doc := map[string]any{}22	sc := bufio.NewScanner(bytes.NewReader(data))23	var (24		curKey  string   // key awaiting a block list25		curList []string // accumulated block list items26		inList  bool27		lineNo  int28	)29	flush := func() {30		if inList {31			doc[curKey] = curList32			inList = false33			curList = nil34		}35	}36	for sc.Scan() {37		lineNo++38		raw := sc.Text()39		if lineNo == 1 {40			raw = strings.TrimPrefix(raw, "\uFEFF") // UTF-8 BOM41		}42		line := stripComment(raw)43		if strings.TrimSpace(line) == "" || strings.TrimSpace(line) == "---" {44			continue45		}46		trimmed := strings.TrimSpace(line)47		indented := line[0] == ' ' || line[0] == '\t'48		if indented {49			if inList && strings.HasPrefix(trimmed, "- ") || inList && trimmed == "-" {50				curList = append(curList, unquote(strings.TrimSpace(strings.TrimPrefix(trimmed, "-"))))51				continue52			}53			return nil, fmt.Errorf("line %d: nested structures are not supported", lineNo)54		}55		flush()56		key, val, ok := strings.Cut(trimmed, ":")57		if !ok || strings.ContainsAny(strings.TrimSpace(key), " \t\"'") {58			return nil, fmt.Errorf("line %d: expected `key: value`", lineNo)59		}60		key = strings.TrimSpace(key)61		val = strings.TrimSpace(val)62		if _, dup := doc[key]; dup {63			return nil, fmt.Errorf("line %d: duplicate key %q", lineNo, key)64		}65		switch {66		case val == "":67			// Either a block list follows or the value is null; decide on the next lines.68			curKey, inList, curList = key, true, []string{}69		case strings.HasPrefix(val, "["):70			if !strings.HasSuffix(val, "]") {71				return nil, fmt.Errorf("line %d: unterminated inline list", lineNo)72			}73			doc[key] = splitInline(val[1 : len(val)-1])74		default:75			doc[key] = unquote(val)76		}77	}78	if err := sc.Err(); err != nil {79		return nil, err80	}81	flush()82	// Keys that had no value and no list items are null scalars → "".83	for k, v := range doc {84		if l, ok := v.([]string); ok && len(l) == 0 {85			// keep empty list for list-typed keys, but scalar keys get "" (set() handles both).86			if k != "resolvers_override" {87				doc[k] = ""88			}89		}90	}91	return doc, nil92}9394// stripComment removes a trailing "# …" comment that is not inside quotes.95func stripComment(line string) string {96	var quote byte97	for i := 0; i < len(line); i++ {98		c := line[i]99		switch {100		case quote != 0:101			if c == quote {102				quote = 0103			}104		case c == '"' || c == '\'':105			quote = c106		case c == '#' && (i == 0 || line[i-1] == ' ' || line[i-1] == '\t'):107			return line[:i]108		}109	}110	return line111}112113func unquote(s string) string {114	s = strings.TrimSpace(s)115	if len(s) >= 2 && ((s[0] == '"' && s[len(s)-1] == '"') || (s[0] == '\'' && s[len(s)-1] == '\'')) {116		inner := s[1 : len(s)-1]117		if s[0] == '"' {118			inner = strings.NewReplacer(`\"`, `"`, `\\`, `\`, `\n`, "\n", `\t`, "\t").Replace(inner)119		}120		return inner121	}122	if s == "~" || s == "null" || s == "Null" || s == "NULL" {123		return ""124	}125	return s126}127128func splitInline(s string) []string {129	var out []string130	var cur strings.Builder131	var quote byte132	for i := 0; i < len(s); i++ {133		c := s[i]134		switch {135		case quote != 0:136			cur.WriteByte(c)137			if c == quote {138				quote = 0139			}140		case c == '"' || c == '\'':141			quote = c142			cur.WriteByte(c)143		case c == ',':144			if v := unquote(cur.String()); v != "" {145				out = append(out, v)146			}147			cur.Reset()148		default:149			cur.WriteByte(c)150		}151	}152	if v := unquote(cur.String()); v != "" {153		out = append(out, v)154	}155	if out == nil {156		out = []string{}157	}158	return out159}160