diff --git a/attribute/generated.go b/attribute/generated.go index 887785b..02d05d1 100644 --- a/attribute/generated.go +++ b/attribute/generated.go @@ -4,6 +4,7 @@ package attribute import "gitlab.codemonkeysoftware.net/b/hatmill" +import "strconv" // Accept creates a "accept" attribute func Accept(value string) hatmill.Attrib { @@ -146,18 +147,18 @@ func Class(value string) hatmill.Attrib { } // Cols creates a "cols" attribute -func Cols(value string) hatmill.Attrib { +func Cols(value int) hatmill.Attrib { return hatmill.Attrib{ Key: "cols", - Value: value, + Value: strconv.FormatInt(int64(value), 10), } } // Colspan creates a "colspan" attribute -func Colspan(value string) hatmill.Attrib { +func Colspan(value int) hatmill.Attrib { return hatmill.Attrib{ Key: "colspan", - Value: value, + Value: strconv.FormatInt(int64(value), 10), } } @@ -326,10 +327,10 @@ func Headers(value string) hatmill.Attrib { } // Height creates a "height" attribute -func Height(value string) hatmill.Attrib { +func Height(value int) hatmill.Attrib { return hatmill.Attrib{ Key: "height", - Value: value, + Value: strconv.FormatInt(int64(value), 10), } } @@ -490,11 +491,11 @@ func Max(value string) hatmill.Attrib { } } -// Maxlengh creates a "maxlengh" attribute -func Maxlengh(value string) hatmill.Attrib { +// Maxlength creates a "maxlength" attribute +func Maxlength(value int) hatmill.Attrib { return hatmill.Attrib{ - Key: "maxlengh", - Value: value, + Key: "maxlength", + Value: strconv.FormatInt(int64(value), 10), } } @@ -523,10 +524,10 @@ func Min(value string) hatmill.Attrib { } // Minlength creates a "minlength" attribute -func Minlength(value string) hatmill.Attrib { +func Minlength(value int) hatmill.Attrib { return hatmill.Attrib{ Key: "minlength", - Value: value, + Value: strconv.FormatInt(int64(value), 10), } } @@ -652,18 +653,18 @@ func Reversed() hatmill.Attrib { } // Rows creates a "rows" attribute -func Rows(value string) hatmill.Attrib { +func Rows(value int) hatmill.Attrib { return hatmill.Attrib{ Key: "rows", - Value: value, + Value: strconv.FormatInt(int64(value), 10), } } // Rowspan creates a "rowspan" attribute -func Rowspan(value string) hatmill.Attrib { +func Rowspan(value int) hatmill.Attrib { return hatmill.Attrib{ Key: "rowspan", - Value: value, + Value: strconv.FormatInt(int64(value), 10), } } @@ -699,10 +700,10 @@ func Shape(value string) hatmill.Attrib { } // Size creates a "size" attribute -func Size(value string) hatmill.Attrib { +func Size(value int) hatmill.Attrib { return hatmill.Attrib{ Key: "size", - Value: value, + Value: strconv.FormatInt(int64(value), 10), } } @@ -803,10 +804,10 @@ func Summary(value string) hatmill.Attrib { } // Tabindex creates a "tabindex" attribute -func Tabindex(value string) hatmill.Attrib { +func Tabindex(value int) hatmill.Attrib { return hatmill.Attrib{ Key: "tabindex", - Value: value, + Value: strconv.FormatInt(int64(value), 10), } } @@ -859,10 +860,10 @@ func Value(value string) hatmill.Attrib { } // Width creates a "width" attribute -func Width(value string) hatmill.Attrib { +func Width(value int) hatmill.Attrib { return hatmill.Attrib{ Key: "width", - Value: value, + Value: strconv.FormatInt(int64(value), 10), } } diff --git a/defs.json b/defs.json index 6697efc..72ea458 100644 --- a/defs.json +++ b/defs.json @@ -18,8 +18,8 @@ {"name": "checked", "type": "bool"}, {"name": "cite", "type": "string"}, {"name": "class", "type": "string"}, - {"name": "cols", "type": "string"}, - {"name": "colspan", "type": "string"}, + {"name": "cols", "type": "int"}, + {"name": "colspan", "type": "int"}, {"name": "content", "type": "string"}, {"name": "contenteditable", "type": "string"}, {"name": "contextmenu", "type": "string"}, @@ -41,7 +41,7 @@ {"name": "form", "type": "string"}, {"name": "formaction", "type": "string"}, {"name": "headers", "type": "string"}, - {"name": "height", "type": "string"}, + {"name": "height", "type": "int"}, {"name": "hidden", "type": "bool"}, {"name": "high", "type": "string"}, {"name": "href", "type": "string"}, @@ -62,11 +62,11 @@ {"name": "low", "type": "string"}, {"name": "manifest", "type": "string"}, {"name": "max", "type": "string"}, - {"name": "maxlengh", "type": "string"}, + {"name": "maxlength", "type": "int"}, {"name": "media", "type": "string"}, {"name": "method", "type": "string"}, {"name": "min", "type": "string"}, - {"name": "minlength", "type": "string"}, + {"name": "minlength", "type": "int"}, {"name": "multiple", "type": "bool"}, {"name": "muted", "type": "bool"}, {"name": "name", "type": "string"}, @@ -83,13 +83,13 @@ {"name": "rel", "type": "string"}, {"name": "required", "type": "bool"}, {"name": "reversed", "type": "bool"}, - {"name": "rows", "type": "string"}, - {"name": "rowspan", "type": "string"}, + {"name": "rows", "type": "int"}, + {"name": "rowspan", "type": "int"}, {"name": "sandbox", "type": "string"}, {"name": "scope", "type": "string"}, {"name": "selected", "type": "bool"}, {"name": "shape", "type": "string"}, - {"name": "size", "type": "string"}, + {"name": "size", "type": "int"}, {"name": "sizes", "type": "string"}, {"name": "slot", "type": "string"}, {"name": "span", "type": "string"}, @@ -102,14 +102,14 @@ {"name": "step", "type": "string"}, {"name": "style", "type": "string"}, {"name": "summary", "type": "string"}, - {"name": "tabindex", "type": "string"}, + {"name": "tabindex", "type": "int"}, {"name": "target", "type": "string"}, {"name": "title", "type": "string"}, {"name": "translate", "type": "string"}, {"name": "type", "type": "string"}, {"name": "usemap", "type": "string"}, {"name": "value", "type": "string"}, - {"name": "width", "type": "string"}, + {"name": "width", "type": "int"}, {"name": "wrap", "type": "string"} ], "elements": [ diff --git a/hatmill_test.go b/hatmill_test.go index f18e605..a9bd1df 100644 --- a/hatmill_test.go +++ b/hatmill_test.go @@ -190,9 +190,10 @@ func Example() { he.Img(ha.Src("./me.jpg"), ha.Id("profile-photo")), hatmill.Text(html.EscapeString(userInput)), he.Div(ha.Disabled(), ha.CustomData("coolness", "awesome"))(), + he.Textarea(ha.Height(25))(), ), ), ) hatmill.WriteDocument(os.Stdout, document) - // Output:
<script>launchMissiles();</script>
+ // Output:
<script>launchMissiles();</script>
} diff --git a/internal/codegen/codegen.go b/internal/codegen/codegen.go index 2c9aecc..f437b4f 100644 --- a/internal/codegen/codegen.go +++ b/internal/codegen/codegen.go @@ -19,59 +19,67 @@ func identifier(s string) string { return strings.Join(words, "") } -var attribTypes = map[string]string { - "string": `// %[1]s creates a "%[2]s" attribute - func %[1]s(value string) hatmill.Attrib { - return hatmill.Attrib{ - Key: "%[2]s", - Value: value, - } - } - `, +type AttribTypeInfo struct { + Template string + Imports []string +} - "bool": `// %[1]s creates a "%[2]s" attribute - func %[1]s() hatmill.Attrib { - return hatmill.Attrib{ - Key: "%[2]s", +var attribTypes = map[string]AttribTypeInfo{ + "string": { + Template: `// %[1]s creates a "%[2]s" attribute + func %[1]s(value string) hatmill.Attrib { + return hatmill.Attrib{ + Key: "%[2]s", + Value: value, + } } - } - `, + `, + }, + + "bool": { + Template: `// %[1]s creates a "%[2]s" attribute + func %[1]s() hatmill.Attrib { + return hatmill.Attrib{ + Key: "%[2]s", + } + } + `, + }, + + "int": { + Template: `// %[1]s creates a "%[2]s" attribute + func %[1]s(value int) hatmill.Attrib { + return hatmill.Attrib{ + Key: "%[2]s", + Value: strconv.FormatInt(int64(value), 10), + } + } + `, + Imports: []string{"strconv"}, + }, +} + +// Def represents a top-level definition and its required imports. +type Def struct { + Source string + Imports []string +} + +type Spec interface { + Generate() Def } type AttribSpec struct { - Name string `json:"name"` + Name string `json:"name"` Type string `json:"type"` } -type AttribSpecs []AttribSpec - -func (a AttribSpecs) Code() Code { - var c Code - const hatmillImport = "gitlab.codemonkeysoftware.net/b/hatmill" - - c.Imports = append(c.Imports, hatmillImport) - for _, spec := range a { - c.Defs = append(c.Defs, spec.Generate()) +func (spec AttribSpec) Generate() Def { + template := attribTypes[spec.Type].Template + return Def{ + Source: fmt.Sprintf(template, identifier(spec.Name), spec.Name), + Imports: attribTypes[spec.Type].Imports, } - return c -} - -type ElemSpecs []ElemSpec - -func (e ElemSpecs) Code() Code { - var c Code - const hatmillImport = "gitlab.codemonkeysoftware.net/b/hatmill" - - c.Imports = append(c.Imports, hatmillImport) - for _, spec := range e { - c.Defs = append(c.Defs, spec.Generate()) - } - return c -} - -func (def AttribSpec) Generate() string { - template := attribTypes[def.Type] - return fmt.Sprintf(template, identifier(def.Name), def.Name) } type ElemSpec struct { @@ -79,7 +87,7 @@ type ElemSpec struct { Void bool `json:"void"` } -func (def ElemSpec) Generate() string { +func (spec ElemSpec) Generate() Def { const ( parentTemplate = `// %[1]s creates a <%[2]s> element. func %[1]s(attribs ...hatmill.Attrib) func(children ...hatmill.Term) hatmill.ParentElement { @@ -105,48 +113,52 @@ func (def ElemSpec) Generate() string { ) template := parentTemplate - if def.Void { + if spec.Void { template = voidTemplate } - return fmt.Sprintf(template, identifier(def.Name), def.Name) + return Def{ + Source: fmt.Sprintf(template, identifier(spec.Name), spec.Name), + } } -type Specs struct { - Attributes AttribSpecs `json:"attributes"` - Elements ElemSpecs `json:"elements"` -} - -// Code is a very slight abstraction of a source code file. -type Code struct { - Package string - Imports []string - Defs []string -} - -func (c Code) Bytes() ([]byte, error) { +func Render(defs []Def, pkgName string) ([]byte, error) { buf := new(bytes.Buffer) buf.WriteString(`// GENERATED BY gitlab.codemonkeysoftware.net/b/hatmill/internal/codegen // DO NOT EDIT! `) - fmt.Fprintln(buf, "package ", c.Package) - for _, imp := range c.Imports { + fmt.Fprintln(buf, "package ", pkgName) + buf.WriteString(`import "gitlab.codemonkeysoftware.net/b/hatmill" + `) + + // Print each import only once. + imports := make(map[string]struct{}) + for _, def := range defs { + for _, imp := range def.Imports { + imports[imp] = struct{}{} + } + } + for imp := range imports { fmt.Fprintf(buf, "import \"%s\"\n", imp) } - for _, def := range c.Defs { - buf.WriteString(def) + + for _, def := range defs { + buf.WriteString(def.Source) } - return format.Source(buf.Bytes()) + + formatted, err := format.Source(buf.Bytes()) + if err != nil { + err = fmt.Errorf("generated invalid code: %s\nSource code:\n%s", err, buf) + } + return formatted, err } -type Coder interface { - Code() Code -} - -func WriteCodeFile(c Coder, path, pkg string) error { - code := c.Code() - code.Package = pkg - b, err := code.Bytes() +func WriteCodeFile(specs []Spec, path, pkgName string) error { + var defs []Def + for _, spec := range specs { + defs = append(defs, spec.Generate()) + } + b, err := Render(defs, pkgName) if err != nil { return err } @@ -166,19 +178,32 @@ func main() { log.Fatal(err) } - var specs Specs + var specs struct { + AttribSpecs []AttribSpec `json:"attributes"` + ElemSpecs []ElemSpec `json:"elements"` + } + err = json.Unmarshal(input, &specs) if err != nil { log.Fatal(err) } - err = WriteCodeFile(specs.Attributes, *attribPath, *attribPkg) + var attribSpecs []Spec + for _, spec := range specs.AttribSpecs { + attribSpecs = append(attribSpecs, spec) + } + err = WriteCodeFile(attribSpecs, *attribPath, *attribPkg) if err != nil { log.Fatal(err) } - err = WriteCodeFile(specs.Elements, *elemPath, *elemPkg) + var elemSpecs []Spec + for _, spec := range specs.ElemSpecs { + elemSpecs = append(elemSpecs, spec) + } + err = WriteCodeFile(elemSpecs, *elemPath, *elemPkg) if err != nil { log.Fatal(err) } + }