Use unified JSON file for codegen input

This commit is contained in:
Brandon Dyck 2019-03-24 15:42:48 -06:00
parent 6664713f32
commit e5b056c1c1
8 changed files with 236 additions and 235 deletions

View File

@ -1,22 +0,0 @@
// GENERATED BY gitlab.codemonkeysoftware.net/b/hatmill/internal/attribgen
// DO NOT EDIT!
package hatmill
func Id(value string) Attrib {
return Attrib{
Key: "id",
Value: value,
}
}
func Disabled() Attrib {
return Attrib{
Key: "disabled",
}
}
func Src(value string) Attrib {
return Attrib{
Key: "src",
Value: value,
}
}

View File

@ -1,3 +0,0 @@
id string
disabled bool
src string

View File

@ -1,9 +0,0 @@
html parent
head parent
body parent
div parent
img empty
span parent
ul parent
li parent
title parent

View File

@ -2,7 +2,7 @@ package hatmill
import "io" import "io"
//go:generate go run ./internal/codegen/codegen.go //go:generate go run ./internal/codegen/codegen.go -input htmldefs.json -output htmldefs.go -package hatmill
// Term represents a fragment of HTML markup, and is one of EmptyElement, ParentElement, or Text. // Term represents a fragment of HTML markup, and is one of EmptyElement, ParentElement, or Text.
type Term interface { type Term interface {

View File

@ -1,34 +1,8 @@
// GENERATED BY gitlab.codemonkeysoftware.net/b/hatmill/internal/elementgen // GENERATED BY gitlab.codemonkeysoftware.net/b/hatmill/internal/codegen
// DO NOT EDIT! // DO NOT EDIT!
package hatmill package hatmill
// Html creates a <html> element.
func Html(attribs ...Attrib) func(children ...Term) *ParentElement {
return func(children ...Term) *ParentElement {
return &ParentElement{
EmptyElement: EmptyElement{
TagName: "html",
Attribs: attribs,
},
Children: children,
}
}
}
// Head creates a <head> element.
func Head(attribs ...Attrib) func(children ...Term) *ParentElement {
return func(children ...Term) *ParentElement {
return &ParentElement{
EmptyElement: EmptyElement{
TagName: "head",
Attribs: attribs,
},
Children: children,
}
}
}
// Body creates a <body> element. // Body creates a <body> element.
func Body(attribs ...Attrib) func(children ...Term) *ParentElement { func Body(attribs ...Attrib) func(children ...Term) *ParentElement {
return func(children ...Term) *ParentElement { return func(children ...Term) *ParentElement {
@ -55,6 +29,32 @@ func Div(attribs ...Attrib) func(children ...Term) *ParentElement {
} }
} }
// Head creates a <head> element.
func Head(attribs ...Attrib) func(children ...Term) *ParentElement {
return func(children ...Term) *ParentElement {
return &ParentElement{
EmptyElement: EmptyElement{
TagName: "head",
Attribs: attribs,
},
Children: children,
}
}
}
// Html creates a <html> element.
func Html(attribs ...Attrib) func(children ...Term) *ParentElement {
return func(children ...Term) *ParentElement {
return &ParentElement{
EmptyElement: EmptyElement{
TagName: "html",
Attribs: attribs,
},
Children: children,
}
}
}
// Img creates a <img> element. // Img creates a <img> element.
func Img(attribs ...Attrib) EmptyElement { func Img(attribs ...Attrib) EmptyElement {
return EmptyElement{ return EmptyElement{
@ -63,32 +63,6 @@ func Img(attribs ...Attrib) EmptyElement {
} }
} }
// Span creates a <span> element.
func Span(attribs ...Attrib) func(children ...Term) *ParentElement {
return func(children ...Term) *ParentElement {
return &ParentElement{
EmptyElement: EmptyElement{
TagName: "span",
Attribs: attribs,
},
Children: children,
}
}
}
// Ul creates a <ul> element.
func Ul(attribs ...Attrib) func(children ...Term) *ParentElement {
return func(children ...Term) *ParentElement {
return &ParentElement{
EmptyElement: EmptyElement{
TagName: "ul",
Attribs: attribs,
},
Children: children,
}
}
}
// Li creates a <li> element. // Li creates a <li> element.
func Li(attribs ...Attrib) func(children ...Term) *ParentElement { func Li(attribs ...Attrib) func(children ...Term) *ParentElement {
return func(children ...Term) *ParentElement { return func(children ...Term) *ParentElement {
@ -102,6 +76,19 @@ func Li(attribs ...Attrib) func(children ...Term) *ParentElement {
} }
} }
// Span creates a <span> element.
func Span(attribs ...Attrib) func(children ...Term) *ParentElement {
return func(children ...Term) *ParentElement {
return &ParentElement{
EmptyElement: EmptyElement{
TagName: "span",
Attribs: attribs,
},
Children: children,
}
}
}
// Title creates a <title> element. // Title creates a <title> element.
func Title(attribs ...Attrib) func(children ...Term) *ParentElement { func Title(attribs ...Attrib) func(children ...Term) *ParentElement {
return func(children ...Term) *ParentElement { return func(children ...Term) *ParentElement {
@ -114,3 +101,33 @@ func Title(attribs ...Attrib) func(children ...Term) *ParentElement {
} }
} }
} }
// Ul creates a <ul> element.
func Ul(attribs ...Attrib) func(children ...Term) *ParentElement {
return func(children ...Term) *ParentElement {
return &ParentElement{
EmptyElement: EmptyElement{
TagName: "ul",
Attribs: attribs,
},
Children: children,
}
}
}
func Disabled() Attrib {
return Attrib{
Key: "disabled",
}
}
func Id(value string) Attrib {
return Attrib{
Key: "id",
Value: value,
}
}
func Src(value string) Attrib {
return Attrib{
Key: "src",
Value: value,
}
}

18
htmldefs.json Normal file
View File

@ -0,0 +1,18 @@
{
"attributes": [
{"name": "disabled", "type": "bool"},
{"name": "id", "type": "string"},
{"name": "src", "type": "string"}
],
"elements": [
{"name": "body"},
{"name": "div"},
{"name": "head"},
{"name": "html"},
{"name": "img", "empty": true},
{"name": "li"},
{"name": "span"},
{"name": "title"},
{"name": "ul"}
]
}

View File

@ -1,21 +1,23 @@
# codegen # codegen
codegen reads simple descriptions of HTML elements and attributes from codegen reads JSON descriptions of HTML elements and attributes, and generates
`elements.txt` and `attribs.txt`, respectively, and generates Go functions to Go functions to create them.
create them.
The input file consists of a JSON object like the following, which includes all
options for attribute types and element emptiness:
The files contains one description per line. Each attribute description is in
the form
``` ```
attribute-name attribute-type {
"elements": [
{"name": "div"},
{"name": "img", "empty": true},
],
"attributes": [
{"name": "src", "type": "string"},
{"name": "disabled", "type": "bool"}
]
}
``` ```
where `attribute-name` is the lowercase name of the attribute, and
`attribute-type` is either `string` for normal attributes (e.g. `id`, 'style') The `"empty"` key can be omitted from an element definition and defaults to
or `bool` for boolean attributes (e.g. `disabled`). Each element description is `false`.
in the form
```
tag-name element-type
```
where `tag-name` is the lowercase name of the element, and `element-type` is
either `empty` for empty elements (e.g. `img`, 'br') or `parent` for elements
that can have child nodes (e.g. 'div', 'body').

View File

@ -1,104 +1,65 @@
package main package main
import ( import (
"bufio"
"bytes" "bytes"
"encoding/json"
"flag"
"fmt" "fmt"
"go/format" "go/format"
"io/ioutil" "io/ioutil"
"log" "log"
"os"
"strings" "strings"
) )
const packageName = "hatmill" const headerFmt = `// GENERATED BY gitlab.codemonkeysoftware.net/b/hatmill/internal/codegen
// DO NOT EDIT!
func genElements() { package %s
const ( `
elementInputPath = "elements.txt"
elementOutputPath = "elements.go"
parentType = "parent" func fileHeader(packageName string, needImport bool) string {
emptyType = "empty" header := fmt.Sprintf(headerFmt, packageName)
if needImport {
parentTemplate = `// %[1]s creates a <%[2]s> element. header += `import . "gitlab.codemonkeysoftware.net/b/hatmill"` + "\n"
func %[1]s(attribs ...Attrib) func(children ...Term) *ParentElement { }
return func(children ...Term) *ParentElement { return header
return &ParentElement{
EmptyElement: EmptyElement{
TagName: "%[2]s",
Attribs: attribs,
},
Children: children,
}
}
}
`
emptyTemplate = `// %[1]s creates a <%[2]s> element.
func %[1]s(attribs ...Attrib) EmptyElement {
return EmptyElement{
TagName: "%[2]s",
Attribs: attribs,
}
}
`
)
inputFile, err := os.Open(elementInputPath)
if err != nil {
log.Fatal(err)
}
defer inputFile.Close()
var output bytes.Buffer
fmt.Fprintln(&output, "// GENERATED BY gitlab.codemonkeysoftware.net/b/hatmill/internal/elementgen")
fmt.Fprintln(&output, "// DO NOT EDIT!\n")
fmt.Fprintln(&output, "package ", packageName, "\n")
scanner := bufio.NewScanner(inputFile)
for scanner.Scan() {
line := scanner.Text()
if line == "" {
continue
}
var tagName, elemType string
_, err = fmt.Sscanf(line, "%s %s", &tagName, &elemType)
if err != nil {
log.Fatalf("error parsing input line: %s", err)
}
var template string
switch elemType {
case parentType:
template = parentTemplate
case emptyType:
template = emptyTemplate
default:
log.Fatal("unknown element type: ", elemType)
}
fmt.Fprintf(&output, template, strings.Title(tagName), tagName)
}
if err := scanner.Err(); err != nil {
log.Fatalf("error scanning input: %s", err)
}
formatted, err := format.Source(output.Bytes())
if err != nil {
log.Fatalf("error formatting output: %s", err)
}
err = ioutil.WriteFile(elementOutputPath, formatted, 0644)
if err != nil {
log.Fatalf("error writing output: %s", err)
}
} }
func genAttribs() { type AttribType int
const (
inputPath = "attribs.txt"
outputPath = "attribs.go"
const (
String AttribType = iota
Bool
)
func (t *AttribType) UnmarshalJSON(data []byte) error {
if string(data) == "null" {
return nil
}
var typeName string
err := json.Unmarshal(data, &typeName)
if err != nil {
return fmt.Errorf("type property must be a string")
}
switch typeName {
case "bool":
*t = Bool
case "string":
*t = String
default:
return fmt.Errorf("unrecognized attribute type %s", typeName)
}
return nil
}
type AttribDef struct {
Name string `json:"name"`
Type AttribType `json:"type"`
}
func (def AttribDef) String() string {
const (
boolType = "bool" boolType = "bool"
stringType = "string" stringType = "string"
@ -117,58 +78,95 @@ func genAttribs() {
` `
) )
inputFile, err := os.Open(inputPath) var template string
if err != nil { switch def.Type {
log.Fatal(err) case Bool:
} template = boolTemplate
defer inputFile.Close() case String:
template = stringTemplate
default:
panic(fmt.Errorf("unknown attribute type: %s", def.Type))
}
var output bytes.Buffer return fmt.Sprintf(template, strings.Title(def.Name), def.Name)
}
fmt.Fprintln(&output, "// GENERATED BY gitlab.codemonkeysoftware.net/b/hatmill/internal/attribgen") type ElemDef struct {
fmt.Fprintln(&output, "// DO NOT EDIT!\n") Name string `json:"name"`
fmt.Fprintln(&output, "package ", packageName, "\n") Empty bool `json:"empty"`
}
scanner := bufio.NewScanner(inputFile) func (def ElemDef) String() string {
for scanner.Scan() { const (
line := scanner.Text() parentTemplate = `// %[1]s creates a <%[2]s> element.
if line == "" { func %[1]s(attribs ...Attrib) func(children ...Term) *ParentElement {
continue return func(children ...Term) *ParentElement {
} return &ParentElement{
EmptyElement: EmptyElement{
TagName: "%[2]s",
Attribs: attribs,
},
Children: children,
}
}
}
`
emptyTemplate = `// %[1]s creates a <%[2]s> element.
func %[1]s(attribs ...Attrib) EmptyElement {
return EmptyElement{
TagName: "%[2]s",
Attribs: attribs,
}
}
`
)
var attribName, attribType string template := parentTemplate
_, err = fmt.Sscanf(line, "%s %s", &attribName, &attribType) if def.Empty {
if err != nil { template = emptyTemplate
log.Fatalf("error parsing input line: %s", err) }
} return fmt.Sprintf(template, strings.Title(def.Name), def.Name)
}
var template string type Defs struct {
switch attribType { Attributes []AttribDef `json:"attributes"`
case boolType: Elements []ElemDef `json:"elements"`
template = boolTemplate
case stringType:
template = stringTemplate
default:
log.Fatal("unknown attribute type: ", attribType)
}
fmt.Fprintf(&output, template, strings.Title(attribName), attribName)
}
if err := scanner.Err(); err != nil {
log.Fatalf("error scanning input: %s", err)
}
formatted, err := format.Source(output.Bytes())
if err != nil {
log.Fatalf("error formatting output: %s", err)
}
err = ioutil.WriteFile(outputPath, formatted, 0644)
if err != nil {
log.Fatalf("error writing output: %s", err)
}
} }
func main() { func main() {
genElements() inputPath := flag.String("input", "", "JSON input file")
genAttribs() outputPath := flag.String("output", "", ".go output file")
packageName := flag.String("package", "", "output package name")
addImport := flag.Bool("import", false, "import hatmill in output package")
flag.Parse()
input, err := ioutil.ReadFile(*inputPath)
if err != nil {
log.Fatal(err)
}
var defs Defs
err = json.Unmarshal(input, &defs)
if err != nil {
log.Fatal(err)
}
output := new(bytes.Buffer)
output.WriteString(fileHeader(*packageName, *addImport))
for _, elemDef := range defs.Elements {
output.WriteString(elemDef.String())
}
for _, attribDef := range defs.Attributes {
output.WriteString(attribDef.String())
}
formatted, err := format.Source(output.Bytes())
if err != nil {
log.Fatal(err)
}
err = ioutil.WriteFile(*outputPath, formatted, 0644)
if err != nil {
log.Fatal(err)
}
} }