package main import ( "bytes" "encoding/json" "flag" "fmt" "go/format" "io/ioutil" "log" "strings" ) func identifier(s string) string { words := strings.Split(s, "-") for i, word := range words { words[i] = strings.Title(word) } return strings.Join(words, "") } type AttribTypeInfo struct { Template string Imports []string } 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", } } `, }, "explicit bool": { Template: `// %[1]s creates a "%[2]s" attribute func %[1]s(value bool) hatmill.Attrib { return hatmill.Attrib{ Key: "%[2]s", Value: strconv.FormatBool(value), } } `, Imports: []string{"strconv"}, }, "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"}, }, "float": { Template: `// %[1]s creates a "%[2]s" attribute func %[1]s(value float32) hatmill.Attrib { return hatmill.Attrib{ Key: "%[2]s", Value: strconv.FormatFloat(float64(value), 'G', -1, 32), } } `, 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"` Type string `json:"type"` } 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, } } type ElemSpec struct { Name string `json:"name"` Void bool `json:"void"` } 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 { return func(children ...hatmill.Term) hatmill.ParentElement { return hatmill.ParentElement{ VoidElement: hatmill.VoidElement{ TagName: "%[2]s", Attribs: attribs, }, Children: children, } } } ` voidTemplate = `// %[1]s creates a <%[2]s> element. func %[1]s(attribs ...hatmill.Attrib) hatmill.VoidElement { return hatmill.VoidElement{ TagName: "%[2]s", Attribs: attribs, } } ` ) template := parentTemplate if spec.Void { template = voidTemplate } return Def{ Source: fmt.Sprintf(template, identifier(spec.Name), spec.Name), } } 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 ", 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 defs { buf.WriteString(def.Source) } 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 } 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 } return ioutil.WriteFile(path, b, 0644) } func main() { inputPath := flag.String("input", "", "JSON input file") elemPath := flag.String("elemfile", "", "generated element .go file") elemPkg := flag.String("elempkg", "", "generated element package name") attribPath := flag.String("attribfile", "", "generated attribute .go file") attribPkg := flag.String("attribpkg", "", "generated attribute package name") flag.Parse() input, err := ioutil.ReadFile(*inputPath) if err != nil { log.Fatal(err) } var specs struct { AttribSpecs []AttribSpec `json:"attributes"` ElemSpecs []ElemSpec `json:"elements"` } err = json.Unmarshal(input, &specs) if err != nil { log.Fatal(err) } var attribSpecs []Spec for _, spec := range specs.AttribSpecs { attribSpecs = append(attribSpecs, spec) } err = WriteCodeFile(attribSpecs, *attribPath, *attribPkg) if err != nil { log.Fatal(err) } var elemSpecs []Spec for _, spec := range specs.ElemSpecs { elemSpecs = append(elemSpecs, spec) } err = WriteCodeFile(elemSpecs, *elemPath, *elemPkg) if err != nil { log.Fatal(err) } }