-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6 from tylerchr/generator
Implement a JSTN generator and related APIs
- Loading branch information
Showing
6 changed files
with
372 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package jstn | ||
|
||
import ( | ||
"bytes" | ||
"io" | ||
"sort" | ||
"strings" | ||
) | ||
|
||
// indentationString defines the whitespace string to use for indentation when | ||
// generating using the pretty format. | ||
const indentationString = " " | ||
|
||
// Generate formats t into a JSTN type declaration using the concise format | ||
// defined by the JSTN specification. | ||
func Generate(t Type) ([]byte, error) { | ||
return (generator{Pretty: false}).generate(t, 0), nil | ||
} | ||
|
||
// GeneratePretty formats t into a JSTN type declaration using the pretty | ||
// format defined by the JSTN specification. | ||
func GeneratePretty(t Type) ([]byte, error) { | ||
return (generator{Pretty: true, Indentation: indentationString}).generate(t, 0), nil | ||
} | ||
|
||
type generator struct { | ||
Pretty bool // Whether to render in pretty mode. | ||
Indentation string // When in pretty mode, the indentation character to use. | ||
} | ||
|
||
// generate formats t into a JSTN document. Because it is a recursive function, | ||
// depth tracks the object hierarchy depth for use when pretty-printing. | ||
func (g generator) generate(t Type, depth int) []byte { | ||
|
||
var buf bytes.Buffer | ||
|
||
switch t.Kind { | ||
case String: | ||
io.WriteString(&buf, "string") // token: string | ||
|
||
case Number: | ||
io.WriteString(&buf, "number") // token: number | ||
|
||
case Boolean: | ||
io.WriteString(&buf, "boolean") // token: boolean | ||
|
||
case Null: | ||
io.WriteString(&buf, "null") // token: null | ||
|
||
case Object: | ||
io.WriteString(&buf, "{") // token: begin-object | ||
|
||
// writePretty adds whitespace, but only if the generator is in pretty mode. | ||
writePretty := func(s string) { | ||
if g.Pretty && len(t.Properties) > 0 { | ||
io.WriteString(&buf, s) | ||
} | ||
} | ||
|
||
// Sort property names for determinism. | ||
var propertyNames []string | ||
for k := range t.Properties { | ||
propertyNames = append(propertyNames, k) | ||
} | ||
sort.Strings(propertyNames) | ||
|
||
writePretty("\n") | ||
for i, k := range propertyNames { | ||
|
||
// In pretty mode, indent the property declaration line. | ||
writePretty(strings.Repeat(g.Indentation, depth+1)) | ||
|
||
// token: name | ||
io.WriteString(&buf, k) | ||
|
||
// token: name-separator | ||
io.WriteString(&buf, ":") | ||
writePretty(" ") | ||
|
||
// token: member | ||
buf.Write(g.generate(*t.Properties[k], depth+1)) | ||
|
||
// token: delimiter | ||
writePretty("\n") | ||
if !g.Pretty && i < len(propertyNames)-1 { | ||
io.WriteString(&buf, ";") | ||
} | ||
} | ||
|
||
writePretty(strings.Repeat(g.Indentation, depth)) | ||
io.WriteString(&buf, "}") // token: end-object | ||
|
||
case Array: | ||
io.WriteString(&buf, "[") // token: begin-array | ||
|
||
// token: type-declaration | ||
if t.Items != nil { | ||
buf.Write(g.generate(*t.Items, depth)) | ||
} | ||
|
||
io.WriteString(&buf, "]") // token: end-array | ||
} | ||
|
||
if t.Optional { | ||
io.WriteString(&buf, "?") // token: value-optional | ||
} | ||
|
||
return buf.Bytes() | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
package jstn | ||
|
||
import "testing" | ||
|
||
func TestGenerator(t *testing.T) { | ||
|
||
cases := []struct { | ||
Type Type | ||
String string | ||
Pretty bool | ||
}{ | ||
{ | ||
Type: Type{Kind: String, Optional: false}, | ||
String: "string", | ||
}, | ||
{ | ||
Type: Type{Kind: String, Optional: true}, | ||
String: "string?", | ||
}, | ||
{ | ||
Type: Type{Kind: Number, Optional: false}, | ||
String: "number", | ||
}, | ||
{ | ||
Type: Type{Kind: Number, Optional: true}, | ||
String: "number?", | ||
}, | ||
{ | ||
Type: Type{Kind: Boolean, Optional: false}, | ||
String: "boolean", | ||
}, | ||
{ | ||
Type: Type{Kind: Boolean, Optional: true}, | ||
String: "boolean?", | ||
}, | ||
{ | ||
Type: Type{Kind: Null, Optional: false}, | ||
String: "null", | ||
}, | ||
{ | ||
Type: Type{Kind: Null, Optional: true}, | ||
String: "null?", | ||
}, | ||
|
||
// | ||
// ARRAYS | ||
// | ||
|
||
{ | ||
// This case is actually not permitted by the current spec, but is | ||
// proposed in https://github.com/tylerchr/jstn/issues/5. | ||
Type: Type{Kind: Array, Optional: false, Items: nil}, | ||
String: "[]", | ||
}, | ||
{ | ||
Type: Type{Kind: Array, Optional: false, Items: &Type{ | ||
Kind: String, | ||
}}, | ||
String: "[string]", | ||
}, | ||
{ | ||
// An array may be an optional type. | ||
Type: Type{Kind: Array, Optional: true, Items: &Type{ | ||
Kind: String, | ||
}}, | ||
String: "[string]?", | ||
}, | ||
{ | ||
// An array may contain an optional type. | ||
Type: Type{Kind: Array, Optional: false, Items: &Type{ | ||
Kind: String, Optional: true, | ||
}}, | ||
String: "[string?]", | ||
}, | ||
|
||
// | ||
// OBJECTS | ||
// | ||
|
||
{ | ||
Type: Type{Kind: Object, Optional: false, Properties: nil}, | ||
String: "{}", | ||
}, | ||
{ | ||
Type: Type{Kind: Object, Optional: false, Properties: nil}, | ||
String: "{}", | ||
Pretty: true, | ||
}, | ||
{ | ||
Type: Type{Kind: Object, Optional: false, Properties: map[string]*Type{ | ||
"firstName": &Type{Kind: String, Optional: false}, | ||
"age": &Type{Kind: Number, Optional: true}, | ||
}}, | ||
String: "{age:number?;firstName:string}", | ||
}, | ||
{ | ||
Type: Type{Kind: Object, Optional: false, Properties: map[string]*Type{ | ||
"firstName": &Type{Kind: String, Optional: false}, | ||
"age": &Type{Kind: Number, Optional: true}, | ||
}}, | ||
String: `{ | ||
age: number? | ||
firstName: string | ||
}`, | ||
Pretty: true, | ||
}, | ||
{ | ||
Type: Type{Kind: Object, Optional: true, Properties: map[string]*Type{ | ||
"firstName": &Type{Kind: String, Optional: false}, | ||
"age": &Type{Kind: Number, Optional: true}, | ||
"residences": &Type{Kind: Array, Optional: false, Items: &Type{ | ||
Kind: Object, Optional: false, Properties: map[string]*Type{ | ||
"city": &Type{Kind: String, Optional: false}, | ||
"country": &Type{Kind: String, Optional: true}, | ||
}, | ||
}}, | ||
}}, | ||
String: `{ | ||
age: number? | ||
firstName: string | ||
residences: [{ | ||
city: string | ||
country: string? | ||
}] | ||
}?`, | ||
Pretty: true, | ||
}, | ||
} | ||
|
||
for i, c := range cases { | ||
|
||
var out []byte | ||
var err error | ||
|
||
if c.Pretty { | ||
out, err = GeneratePretty(c.Type) | ||
} else { | ||
out, err = Generate(c.Type) | ||
} | ||
|
||
if err != nil { | ||
t.Errorf("[case %d] unexpected generator error: %s", i, err) | ||
continue | ||
} | ||
|
||
if string(out) != c.String { | ||
t.Errorf("[case %d] unexpected production:", i) | ||
t.Errorf(". expected: %q", c.String) | ||
t.Errorf(". got : %q", out) | ||
} | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.