// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT // Command interpres-decode is the toml-test harness adapter and a TOML // validator. Without flags it reads a TOML document from standard input and // writes the toml-test "tagged JSON" representation to standard output. With // -validate it checks the named documents, or standard input when none are // named, and exits non-zero on the first invalid one: // // interpres-decode -validate config.toml // // Run the official suite against the adapter with: // // toml-test ./interpres-decode package main import ( "encoding/json" "errors" "flag" "fmt" "io" "math" "os" "strconv" "time" "sourcedock.dev/petrbalvin/interpres" ) func main() { os.Exit(Run(os.Args[1:], os.Stdin, os.Stdout, os.Stderr)) } // Run runs the command line and returns the process exit code: 0 success, // 1 an invalid document, 2 a usage, reading, encoding, or // unsupported-value error. func Run(args []string, stdin io.Reader, stdout, stderr io.Writer) int { fs := flag.NewFlagSet("interpres-decode", flag.ContinueOnError) fs.SetOutput(stderr) validate := fs.Bool("validate", false, "validate the documents instead of emitting tagged JSON") if err := fs.Parse(args); err != nil { if errors.Is(err, flag.ErrHelp) { return 0 } return 2 } if *validate { return validatePaths(fs.Args(), stdin, stderr) } if fs.NArg() > 0 { fmt.Fprintln(stderr, "interpres-decode: the adapter mode takes no arguments; name files with -validate") return 2 } data, err := io.ReadAll(stdin) if err != nil { fmt.Fprintln(stderr, "read stdin:", err) return 2 } tree, err := interpres.Parse(data) if err != nil { fmt.Fprintln(stderr, err) return 1 } tagged, err := tag(tree) if err != nil { fmt.Fprintln(stderr, err) return 2 } enc := json.NewEncoder(stdout) enc.SetEscapeHTML(false) if err := enc.Encode(tagged); err != nil { fmt.Fprintln(stderr, "encode:", err) return 2 } return 0 } // validatePaths parses every named file, or standard input when none are // named, and reports each invalid document on stderr. It returns 0 when all // documents parse, 1 when one does not, and 2 on a usage or read failure. func validatePaths(paths []string, stdin io.Reader, stderr io.Writer) int { if len(paths) == 0 { paths = []string{"-"} } valid := true for _, p := range paths { name := p var data []byte var err error if p == "-" { data, err = io.ReadAll(stdin) name = "" } else { data, err = os.ReadFile(p) } if err != nil { fmt.Fprintf(stderr, "interpres-decode: %s: %v\n", name, err) return 2 } if _, err := interpres.Parse(data); err != nil { fmt.Fprintf(stderr, "%s: %v\n", name, err) valid = false } } if !valid { return 1 } return 0 } // tag converts an interpres value into its toml-test tagged-JSON form. Tables // become JSON objects and arrays become JSON arrays; scalars are wrapped in a // {"type", "value"} object. An error is returned for value types the encoder // has no tagged representation for. func tag(v any) (any, error) { switch x := v.(type) { case map[string]any: out := make(map[string]any, len(x)) for k, val := range x { tagged, err := tag(val) if err != nil { return nil, err } out[k] = tagged } return out, nil case []any: out := make([]any, len(x)) for i, e := range x { tagged, err := tag(e) if err != nil { return nil, err } out[i] = tagged } return out, nil case []map[string]any: out := make([]any, len(x)) for i, e := range x { tagged, err := tag(e) if err != nil { return nil, err } out[i] = tagged } return out, nil case string: return tagged("string", x), nil case bool: return tagged("bool", strconv.FormatBool(x)), nil case int64: return tagged("integer", strconv.FormatInt(x, 10)), nil case float64: return tagged("float", formatFloat(x)), nil case time.Time: return tagged("datetime", x.Format(time.RFC3339Nano)), nil case interpres.LocalDateTime: return tagged("datetime-local", x.Format("2006-01-02T15:04:05.999999999")), nil case interpres.LocalDate: return tagged("date-local", x.Format("2006-01-02")), nil case interpres.LocalTime: return tagged("time-local", x.Format("15:04:05.999999999")), nil default: return nil, fmt.Errorf("unsupported value type %T", v) } } func tagged(typ, value string) map[string]string { return map[string]string{"type": typ, "value": value} } func formatFloat(f float64) string { switch { case math.IsInf(f, 1): return "inf" case math.IsInf(f, -1): return "-inf" case math.IsNaN(f): return "nan" default: return strconv.FormatFloat(f, 'g', -1, 64) } }