hatmill/internal/codegen/codegen.go

222 lines
4.8 KiB
Go

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",
}
}
`,
},
"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)
}
}