spb/internetpressure
Public
TypeScript 36.3%
Python 31.8%
Go 18%
JavaScript 9.8%
Shell 1.9%
SQL 1.4%
CSS 0.5%
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