Restructured the BurntSushi testing code, to not let it be a part of the TOML AST.
This commit is contained in:
parent
dea3eb987b
commit
44f022544f
|
@ -1,84 +0,0 @@
|
|||
package ast
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MakeSushi generates a JSON string for an ast Table, which is compatible
|
||||
// with BurntSushi's TOML testing tool (https://github.com/BurntSushi/toml-test)
|
||||
func (t Table) MakeSushi() string {
|
||||
return MakeSushi(NewValue(TypeTable, t))
|
||||
}
|
||||
|
||||
// MakeSushi generates a JSON string for an ast Value, which is compatible
|
||||
// with BurntSushi's TOML testing tool (https://github.com/BurntSushi/toml-test)
|
||||
func MakeSushi(value *Value) string {
|
||||
switch value.Type {
|
||||
case TypeString:
|
||||
return renderValue("string", value.Data[0].(string))
|
||||
case TypeInteger:
|
||||
return renderValue("integer", fmt.Sprintf("%d", value.Data[0].(int64)))
|
||||
case TypeFloat:
|
||||
return renderValue("float", fmt.Sprintf("%v", value.Data[0].(float64)))
|
||||
case TypeBool:
|
||||
return renderValue("bool", fmt.Sprintf("%t", value.Data[0].(bool)))
|
||||
case TypeOffsetDateTime:
|
||||
return renderValue("datetime", value.Data[0].(time.Time).Format(time.RFC3339Nano))
|
||||
case TypeLocalDateTime:
|
||||
return renderValue("local_datetime", value.Data[0].(time.Time).Format("2006-01-02 15:04:05.999999999"))
|
||||
case TypeLocalDate:
|
||||
return renderValue("local_date", value.Data[0].(time.Time).Format("2006-01-02"))
|
||||
case TypeLocalTime:
|
||||
return renderValue("local_time", value.Data[0].(time.Time).Format("15:04:05.999999999"))
|
||||
case TypeArrayOfTables:
|
||||
fallthrough
|
||||
case 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].(*Array)
|
||||
values := make([]string, 0, arr.Length)
|
||||
for i := arr.First; i != nil; i = i.Next {
|
||||
values = append(values, MakeSushi(i.Value))
|
||||
}
|
||||
if arr.ItemType == TypeTable {
|
||||
return fmt.Sprintf("[%s]", strings.Join(values, ", "))
|
||||
}
|
||||
return fmt.Sprintf(`{"type": "array", "value": [%s]}`, strings.Join(values, ", "))
|
||||
case TypeImplicitTable:
|
||||
fallthrough
|
||||
case TypeTable:
|
||||
pairs := value.Data[0].(Table)
|
||||
keys := make([]string, len(pairs))
|
||||
i := 0
|
||||
for k := range pairs {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
values := make([]string, len(pairs))
|
||||
for i, k := range keys {
|
||||
values[i] = fmt.Sprintf("%q: %s", k, MakeSushi(pairs[k]))
|
||||
}
|
||||
return fmt.Sprintf("{%s}", strings.Join(values, ", "))
|
||||
default:
|
||||
panic(fmt.Sprintf("Unhandled data type: %s", value.Type))
|
||||
}
|
||||
}
|
||||
|
||||
func renderValue(t string, v string) string {
|
||||
return fmt.Sprintf("{%q: %q, %q: %s}", "type", t, "value", toJSON(v))
|
||||
}
|
||||
|
||||
func toJSON(s string) string {
|
||||
j, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unable to JSON encode %q: %s", s, err))
|
||||
}
|
||||
return string(j)
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
// 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(`"}`)
|
||||
}
|
|
@ -12,6 +12,7 @@ import (
|
|||
"path"
|
||||
"time"
|
||||
|
||||
"git.makaay.nl/mauricem/go-toml/burntsushi"
|
||||
"git.makaay.nl/mauricem/go-toml/parse"
|
||||
"github.com/pkg/profile"
|
||||
)
|
||||
|
@ -53,6 +54,6 @@ func main() {
|
|||
if err != nil {
|
||||
log.Fatalf("Error decoding TOML: %s", err)
|
||||
} else {
|
||||
fmt.Println(toml.MakeSushi())
|
||||
burntsushi.PrintJSON(toml)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"git.makaay.nl/mauricem/go-toml/burntsushi"
|
||||
|
||||
"git.makaay.nl/mauricem/go-toml/parse"
|
||||
)
|
||||
|
||||
|
@ -64,8 +66,9 @@ func Test_Valid(t *testing.T) {
|
|||
continue
|
||||
}
|
||||
var actual interface{}
|
||||
if err := json.Unmarshal([]byte(tomlTable.MakeSushi()), &actual); err != nil {
|
||||
t.Errorf("[%s] Could not convert parser output to BurntSushi format: %s", name, err)
|
||||
sushi := burntsushi.ToJSON(tomlTable)
|
||||
if err := json.Unmarshal([]byte(sushi), &actual); err != nil {
|
||||
t.Errorf("[%s] Could not JSON decode parser output %q to BurntSushi format: %s", name, sushi, err)
|
||||
fail++
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@ func (t *parser) parseBasicString(name string, p *parse.API) (string, bool) {
|
|||
sb := &strings.Builder{}
|
||||
for {
|
||||
switch {
|
||||
case p.PeekWithResult(controlCharacter):
|
||||
case p.Peek(controlCharacter):
|
||||
p.SetError("invalid character in %s: %q (must be escaped)", name, p.Result.Bytes[0])
|
||||
return sb.String(), false
|
||||
case p.Accept(validEscape):
|
||||
|
@ -146,7 +146,7 @@ func (t *parser) parseLiteralString(name string, p *parse.API) (string, bool) {
|
|||
return sb.String(), true
|
||||
case p.Skip(a.Tab):
|
||||
sb.WriteString("\t")
|
||||
case p.PeekWithResult(controlCharacter):
|
||||
case p.Peek(controlCharacter):
|
||||
p.SetError("invalid character in %s: %q (no control chars allowed, except for tab)", name, p.Result.Bytes[0])
|
||||
return sb.String(), false
|
||||
case p.Peek(a.InvalidRune):
|
||||
|
@ -194,7 +194,7 @@ func (t *parser) parseMultiLineBasicString(p *parse.API) (string, bool) {
|
|||
switch {
|
||||
case p.Skip(newline):
|
||||
sb.WriteString("\n")
|
||||
case p.PeekWithResult(controlCharacter):
|
||||
case p.Peek(controlCharacter):
|
||||
p.SetError("invalid character in multi-line basic string: %q (must be escaped)", p.Result.Bytes[0])
|
||||
return sb.String(), false
|
||||
case p.Accept(validEscape):
|
||||
|
@ -278,7 +278,7 @@ func (t *parser) parseMultiLineLiteralString(p *parse.API) (string, bool) {
|
|||
sb.WriteString("\t")
|
||||
case p.Skip(newline):
|
||||
sb.WriteString("\n")
|
||||
case p.PeekWithResult(controlCharacter):
|
||||
case p.Peek(controlCharacter):
|
||||
p.SetError("invalid character in literal string: %q (no control chars allowed, except for tab and newline)", p.Result.Bytes[0])
|
||||
return sb.String(), false
|
||||
case p.Accept(a.ValidRune):
|
||||
|
|
Loading…
Reference in New Issue