package cms import ( "fmt" "strconv" "strings" ) type simpleTable struct { values map[string]string lists map[string][]string } func parseSimpleTable(data string) (simpleTable, error) { table := simpleTable{ values: map[string]string{}, lists: map[string][]string{}, } for lineNo, raw := range strings.Split(data, "\n") { line := strings.TrimSpace(raw) if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "[[") { continue } key, rawValue, ok := strings.Cut(line, "=") if !ok { return table, fmt.Errorf("line %d: expected key = value", lineNo+1) } key = strings.TrimSpace(key) rawValue = strings.TrimSpace(rawValue) if key == "" { return table, fmt.Errorf("line %d: empty key", lineNo+1) } if strings.HasPrefix(rawValue, "[") { list, err := parseStringList(rawValue) if err != nil { return table, fmt.Errorf("line %d: %w", lineNo+1, err) } table.lists[key] = list continue } value, err := parseScalar(rawValue) if err != nil { return table, fmt.Errorf("line %d: %w", lineNo+1, err) } table.values[key] = value } return table, nil } func parseMetadata(data string) (simpleTable, error) { table, err := parseSimpleTable(data) if err == nil { return table, nil } yamlTable, yamlErr := parseSimpleYAMLTable(data) if yamlErr == nil { return yamlTable, nil } return table, err } func parseSimpleYAMLTable(data string) (simpleTable, error) { table := simpleTable{ values: map[string]string{}, lists: map[string][]string{}, } for lineNo, raw := range strings.Split(data, "\n") { line := strings.TrimSpace(raw) if line == "" || strings.HasPrefix(line, "#") { continue } key, rawValue, ok := strings.Cut(line, ":") if !ok { return table, fmt.Errorf("line %d: expected key: value", lineNo+1) } key = strings.TrimSpace(key) rawValue = strings.TrimSpace(rawValue) if key == "" { return table, fmt.Errorf("line %d: empty key", lineNo+1) } if strings.HasPrefix(rawValue, "[") { list, err := parseStringList(rawValue) if err != nil { return table, fmt.Errorf("line %d: %w", lineNo+1, err) } table.lists[key] = list continue } if key == "tags" && strings.Contains(rawValue, ",") { table.lists[key] = splitCommaList(rawValue) continue } value, err := parseScalar(rawValue) if err != nil { return table, fmt.Errorf("line %d: %w", lineNo+1, err) } table.values[key] = value } return table, nil } func parseScalar(raw string) (string, error) { raw = strings.TrimSpace(raw) if raw == "" { return "", nil } if strings.HasPrefix(raw, "\"") { value, err := strconv.Unquote(raw) if err != nil { return "", err } return value, nil } return raw, nil } func splitCommaList(value string) []string { var out []string for _, part := range strings.Split(value, ",") { part = strings.TrimSpace(part) if part != "" { out = append(out, part) } } return out } func parseStringList(raw string) ([]string, error) { raw = strings.TrimSpace(raw) if !strings.HasPrefix(raw, "[") || !strings.HasSuffix(raw, "]") { return nil, fmt.Errorf("expected string list") } raw = strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(raw, "["), "]")) if raw == "" { return nil, nil } var values []string for _, part := range strings.Split(raw, ",") { value, err := parseScalar(strings.TrimSpace(part)) if err != nil { return nil, err } if value != "" { values = append(values, value) } } return values, nil } func formatString(value string) string { return strconv.Quote(value) } func formatStringList(values []string) string { quoted := make([]string, 0, len(values)) for _, value := range values { quoted = append(quoted, strconv.Quote(value)) } return "[" + strings.Join(quoted, ", ") + "]" } func boolString(value bool) string { if value { return "true" } return "false" }