| //go:build go1.21 | |
| package indenthandler | |
| import ( | |
| "bufio" | |
| "bytes" | |
| "fmt" | |
| "reflect" | |
| "regexp" | |
| "strconv" | |
| "strings" | |
| "testing" | |
| "unicode" | |
| "log/slog" | |
| "testing/slogtest" | |
| ) | |
| func TestSlogtest(t *testing.T) { | |
| var buf bytes.Buffer | |
| err := slogtest.TestHandler(New(&buf, nil), func() []map[string]any { | |
| return parseLogEntries(buf.String()) | |
| }) | |
| if err != nil { | |
| t.Error(err) | |
| } | |
| } | |
| func Test(t *testing.T) { | |
| var buf bytes.Buffer | |
| l := slog.New(New(&buf, nil)) | |
| l.Info("hello", "a", 1, "b", true, "c", 3.14, slog.Group("g", "h", 1, "i", 2), "d", "NO") | |
| got := buf.String() | |
| wantre := `time: [-0-9T:.]+Z? | |
| level: INFO | |
| source: ".*/indent_handler_test.go:\d+" | |
| msg: "hello" | |
| a: 1 | |
| b: true | |
| c: 3.14 | |
| g: | |
| h: 1 | |
| i: 2 | |
| d: "NO" | |
| ` | |
| re := regexp.MustCompile(wantre) | |
| if !re.MatchString(got) { | |
| t.Errorf("\ngot:\n%q\nwant:\n%q", got, wantre) | |
| } | |
| buf.Reset() | |
| l.Debug("test") | |
| if got := buf.Len(); got != 0 { | |
| t.Errorf("got buf.Len() = %d, want 0", got) | |
| } | |
| } | |
| func parseLogEntries(s string) []map[string]any { | |
| var ms []map[string]any | |
| scan := bufio.NewScanner(strings.NewReader(s)) | |
| for scan.Scan() { | |
| m := parseGroup(scan) | |
| ms = append(ms, m) | |
| } | |
| if scan.Err() != nil { | |
| panic(scan.Err()) | |
| } | |
| return ms | |
| } | |
| func parseGroup(scan *bufio.Scanner) map[string]any { | |
| m := map[string]any{} | |
| groupIndent := -1 | |
| for { | |
| line := scan.Text() | |
| if line == "---" { // end of entry | |
| break | |
| } | |
| k, v, found := strings.Cut(line, ":") | |
| if !found { | |
| panic(fmt.Sprintf("no ':' in line %q", line)) | |
| } | |
| indent := strings.IndexFunc(k, func(r rune) bool { | |
| return !unicode.IsSpace(r) | |
| }) | |
| if indent < 0 { | |
| panic("blank line") | |
| } | |
| if groupIndent < 0 { | |
| // First line in group; remember the indent. | |
| groupIndent = indent | |
| } else if indent < groupIndent { | |
| // End of group | |
| break | |
| } else if indent > groupIndent { | |
| panic(fmt.Sprintf("indent increased on line %q", line)) | |
| } | |
| key := strings.TrimSpace(k) | |
| if v == "" { | |
| // Just a key: start of a group. | |
| if !scan.Scan() { | |
| panic("empty group") | |
| } | |
| m[key] = parseGroup(scan) | |
| } else { | |
| v = strings.TrimSpace(v) | |
| if len(v) > 0 && v[0] == '"' { | |
| var err error | |
| v, err = strconv.Unquote(v) | |
| if err != nil { | |
| panic(err) | |
| } | |
| } | |
| m[key] = v | |
| if !scan.Scan() { | |
| break | |
| } | |
| } | |
| } | |
| return m | |
| } | |
| func TestParseLogEntries(t *testing.T) { | |
| in := ` | |
| a: 1 | |
| b: 2 | |
| c: 3 | |
| g: | |
| h: 4 | |
| i: 5 | |
| d: 6 | |
| --- | |
| e: 7 | |
| --- | |
| ` | |
| want := []map[string]any{ | |
| { | |
| "a": "1", | |
| "b": "2", | |
| "c": "3", | |
| "g": map[string]any{ | |
| "h": "4", | |
| "i": "5", | |
| }, | |
| "d": "6", | |
| }, | |
| { | |
| "e": "7", | |
| }, | |
| } | |
| got := parseLogEntries(in[1:]) | |
| if !reflect.DeepEqual(got, want) { | |
| t.Errorf("\ngot:\n%v\nwant:\n%v", got, want) | |
| } | |
| } |