diff --git a/attribs.go b/attribs.go
deleted file mode 100644
index 98952ac..0000000
--- a/attribs.go
+++ /dev/null
@@ -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,
- }
-}
diff --git a/attribs.txt b/attribs.txt
deleted file mode 100644
index 1185490..0000000
--- a/attribs.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-id string
-disabled bool
-src string
diff --git a/elements.txt b/elements.txt
deleted file mode 100644
index 4ae5e2c..0000000
--- a/elements.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-html parent
-head parent
-body parent
-div parent
-img empty
-span parent
-ul parent
-li parent
-title parent
diff --git a/hatmill.go b/hatmill.go
index baf2bc2..522f2b9 100644
--- a/hatmill.go
+++ b/hatmill.go
@@ -2,7 +2,7 @@ package hatmill
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.
type Term interface {
diff --git a/elements.go b/htmldefs.go
similarity index 91%
rename from elements.go
rename to htmldefs.go
index e89465e..02a5e41 100644
--- a/elements.go
+++ b/htmldefs.go
@@ -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!
package hatmill
-// Html creates a 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
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 element.
func Body(attribs ...Attrib) func(children ...Term) *ParentElement {
return func(children ...Term) *ParentElement {
@@ -55,6 +29,32 @@ func Div(attribs ...Attrib) func(children ...Term) *ParentElement {
}
}
+// Head creates a 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 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 element.
func Img(attribs ...Attrib) EmptyElement {
return EmptyElement{
@@ -63,32 +63,6 @@ func Img(attribs ...Attrib) EmptyElement {
}
}
-// Span creates a 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 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 - element.
func Li(attribs ...Attrib) func(children ...Term) *ParentElement {
return func(children ...Term) *ParentElement {
@@ -102,6 +76,19 @@ func Li(attribs ...Attrib) func(children ...Term) *ParentElement {
}
}
+// Span creates a 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 element.
func Title(attribs ...Attrib) func(children ...Term) *ParentElement {
return func(children ...Term) *ParentElement {
@@ -114,3 +101,33 @@ func Title(attribs ...Attrib) func(children ...Term) *ParentElement {
}
}
}
+
+// Ul creates a
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,
+ }
+}
diff --git a/htmldefs.json b/htmldefs.json
new file mode 100644
index 0000000..d858664
--- /dev/null
+++ b/htmldefs.json
@@ -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"}
+ ]
+}
diff --git a/internal/codegen/README.md b/internal/codegen/README.md
index 7832603..c681b2e 100644
--- a/internal/codegen/README.md
+++ b/internal/codegen/README.md
@@ -1,21 +1,23 @@
# codegen
-codegen reads simple descriptions of HTML elements and attributes from
-`elements.txt` and `attribs.txt`, respectively, and generates Go functions to
-create them.
+codegen reads JSON descriptions of HTML elements and attributes, and generates
+Go functions to 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')
-or `bool` for boolean attributes (e.g. `disabled`). Each element description is
-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').
+
+The `"empty"` key can be omitted from an element definition and defaults to
+`false`.
diff --git a/internal/codegen/codegen.go b/internal/codegen/codegen.go
index ee0a882..9f8e582 100644
--- a/internal/codegen/codegen.go
+++ b/internal/codegen/codegen.go
@@ -1,104 +1,65 @@
package main
import (
- "bufio"
"bytes"
+ "encoding/json"
+ "flag"
"fmt"
"go/format"
"io/ioutil"
"log"
- "os"
"strings"
)
-const packageName = "hatmill"
+const headerFmt = `// GENERATED BY gitlab.codemonkeysoftware.net/b/hatmill/internal/codegen
+// DO NOT EDIT!
-func genElements() {
- const (
- elementInputPath = "elements.txt"
- elementOutputPath = "elements.go"
+package %s
+`
- parentType = "parent"
- emptyType = "empty"
-
- parentTemplate = `// %[1]s creates a <%[2]s> element.
- func %[1]s(attribs ...Attrib) func(children ...Term) *ParentElement {
- 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,
- }
- }
- `
- )
- 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 fileHeader(packageName string, needImport bool) string {
+ header := fmt.Sprintf(headerFmt, packageName)
+ if needImport {
+ header += `import . "gitlab.codemonkeysoftware.net/b/hatmill"` + "\n"
+ }
+ return header
}
-func genAttribs() {
- const (
- inputPath = "attribs.txt"
- outputPath = "attribs.go"
+type AttribType int
+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"
stringType = "string"
@@ -117,58 +78,95 @@ func genAttribs() {
`
)
- inputFile, err := os.Open(inputPath)
- if err != nil {
- log.Fatal(err)
- }
- defer inputFile.Close()
+ var template string
+ switch def.Type {
+ case Bool:
+ template = boolTemplate
+ 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")
- fmt.Fprintln(&output, "// DO NOT EDIT!\n")
- fmt.Fprintln(&output, "package ", packageName, "\n")
+type ElemDef struct {
+ Name string `json:"name"`
+ Empty bool `json:"empty"`
+}
- scanner := bufio.NewScanner(inputFile)
- for scanner.Scan() {
- line := scanner.Text()
- if line == "" {
- continue
- }
+func (def ElemDef) String() string {
+ const (
+ parentTemplate = `// %[1]s creates a <%[2]s> element.
+ func %[1]s(attribs ...Attrib) func(children ...Term) *ParentElement {
+ 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
- _, err = fmt.Sscanf(line, "%s %s", &attribName, &attribType)
- if err != nil {
- log.Fatalf("error parsing input line: %s", err)
- }
+ template := parentTemplate
+ if def.Empty {
+ template = emptyTemplate
+ }
+ return fmt.Sprintf(template, strings.Title(def.Name), def.Name)
+}
- var template string
- switch attribType {
- case boolType:
- 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)
- }
+type Defs struct {
+ Attributes []AttribDef `json:"attributes"`
+ Elements []ElemDef `json:"elements"`
}
func main() {
- genElements()
- genAttribs()
+ inputPath := flag.String("input", "", "JSON input file")
+ 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)
+ }
}