// Package burntsushi translates a TOML AST into a JSON format, for BurntSushi's testing tool (https://github.com/BurntSushi/toml-test) package burntsushi import ( "bufio" "fmt" "os" "sort" "strings" "time" "git.makaay.nl/mauricem/go-toml/ast" ) // PrintJSON outputs the JSON string for an ast Table to STDOUT. func PrintJSON(t ast.Table) { w := bufio.NewWriter(os.Stdout) writeSushi(w, ast.NewValue(ast.TypeTable, t)) w.Flush() } // ToJSON generates the JSON string for an ast Table. func ToJSON(t ast.Table) string { sb := &strings.Builder{} w := bufio.NewWriter(sb) writeSushi(w, ast.NewValue(ast.TypeTable, t)) w.Flush() return sb.String() } func writeSushi(w *bufio.Writer, value *ast.Value) { switch value.Type { case ast.TypeString: w.WriteString(`{"type": "string", "value":"`) for _, c := range value.Data[0].(string) { switch c { default: w.WriteRune(c) case '"': w.WriteString(`\"`) case '\\': w.WriteString(`\\`) case '\b': w.WriteString(`\b`) case '\f': w.WriteString(`\f`) case '\n': w.WriteString(`\n`) case '\r': w.WriteString(`\r`) case '\t': w.WriteString(`\t`) case '\u0000': w.WriteString(`\u0000`) } } w.WriteString(`"}`) case ast.TypeInteger: renderValue(w, "integer", fmt.Sprintf("%d", value.Data[0].(int64))) case ast.TypeFloat: renderValue(w, "float", fmt.Sprintf("%v", value.Data[0].(float64))) case ast.TypeBool: if value.Data[0].(bool) { renderValue(w, "bool", "true") } else { renderValue(w, "bool", "false") } case ast.TypeOffsetDateTime: renderValue(w, "datetime", value.Data[0].(time.Time).Format(time.RFC3339Nano)) case ast.TypeLocalDateTime: renderValue(w, "local_datetime", value.Data[0].(time.Time).Format("2006-01-02 15:04:05.999999999")) case ast.TypeLocalDate: renderValue(w, "local_date", value.Data[0].(time.Time).Format("2006-01-02")) case ast.TypeLocalTime: renderValue(w, "local_time", value.Data[0].(time.Time).Format("15:04:05.999999999")) case ast.TypeArrayOfTables: fallthrough case ast.TypeArray: // BurntSushi's tests sees [ {inline: "table"}, {array: "definitions"} ] // as an array of tables, so here we accomodate for that situation // by checking for that case and render such inline array definition // as if it were an [[array.of.tables]]. arr := value.Data[0].(*ast.Array) if arr.ItemType == ast.TypeTable { w.WriteByte('[') } else { w.WriteString(`{"type": "array", "value": [`) } for i := arr.First; i != nil; i = i.Next { if i != arr.First { w.WriteString(", ") } writeSushi(w, i.Value) } if arr.ItemType == ast.TypeTable { w.WriteByte(']') } else { w.WriteString(`]}`) } case ast.TypeImplicitTable: fallthrough case ast.TypeTable: pairs := value.Data[0].(ast.Table) keys := make([]string, len(pairs)) i := 0 for k := range pairs { keys[i] = k i++ } sort.Strings(keys) w.WriteByte('{') for i, k := range keys { if i > 0 { w.WriteByte(',') } w.WriteString(fmt.Sprintf("%q: ", k)) writeSushi(w, pairs[k]) } w.WriteByte('}') default: panic(fmt.Sprintf("Unhandled data type: %s", value.Type)) } } func renderValue(w *bufio.Writer, t string, v string) { w.WriteString(`{"type":"`) w.WriteString(t) w.WriteString(`", "value":"`) w.WriteString(v) w.WriteString(`"}`) }