github.com/jgarto/itcv@v0.0.0-20180826224514-4eea09c1aa0d/cmd/coreGen/generate.go (about)

     1  // Copyright (c) 2016 Paul Jolly <paul@myitcv.org.uk>, all rights reserved.
     2  // Use of this document is governed by a license found in the LICENSE document.
     3  
     4  package main
     5  
     6  import (
     7  	"bytes"
     8  	"go/ast"
     9  	"go/format"
    10  	"go/printer"
    11  	"go/token"
    12  	"io/ioutil"
    13  	"path/filepath"
    14  	"sort"
    15  	"strings"
    16  	"unicode/utf8"
    17  
    18  	"myitcv.io/gogenerate"
    19  )
    20  
    21  const (
    22  	reactPkg      = "myitcv.io/react"
    23  	compDefName   = "ComponentDef"
    24  	compDefSuffix = "Def"
    25  
    26  	stateTypeSuffix     = "State"
    27  	propsTypeSuffix     = "Props"
    28  	propsTypeTmplPrefix = "_"
    29  
    30  	getInitialState           = "GetInitialState"
    31  	componentWillReceiveProps = "ComponentWillReceiveProps"
    32  	equals                    = "Equals"
    33  )
    34  
    35  type typeFile struct {
    36  	ts   *ast.TypeSpec
    37  	file *ast.File
    38  }
    39  
    40  type funcFile struct {
    41  	fn   *ast.FuncDecl
    42  	file *ast.File
    43  }
    44  
    45  var fset = token.NewFileSet()
    46  
    47  func astNodeString(i interface{}) string {
    48  	b := bytes.NewBuffer(nil)
    49  	err := printer.Fprint(b, fset, i)
    50  	if err != nil {
    51  		fatalf("failed to astNodeString %v: %v", i, err)
    52  	}
    53  
    54  	return b.String()
    55  }
    56  
    57  func lowerInitial(s string) string {
    58  	if s == "" {
    59  		return ""
    60  	}
    61  
    62  	r, w := utf8.DecodeRuneInString(s)
    63  	return strings.ToLower(string(r)) + s[w:]
    64  }
    65  
    66  func dogen(dir, pkgName, license string) {
    67  	cg := newCoreGen()
    68  
    69  	// fill out templates
    70  	for _, t := range templates {
    71  		for n, a := range t {
    72  			a.Name = n
    73  			if a.React == "" {
    74  				a.React = lowerInitial(n)
    75  			}
    76  			if a.HTML == "" {
    77  				a.HTML = strings.ToLower(a.Name)
    78  			}
    79  			if a.HTMLConvert == "" && a.Type != "string" {
    80  				switch a.Type {
    81  				case "bool":
    82  					a.HTMLConvert = "parseBool"
    83  				}
    84  			}
    85  			if a.Type == "" {
    86  				a.Type = "string"
    87  			}
    88  		}
    89  	}
    90  
    91  	// fill out elements
    92  	for n, e := range elements {
    93  		e.Name = n
    94  		if e.React == "" {
    95  			e.React = strings.ToLower(n)
    96  		}
    97  		if !e.NonHTML {
    98  			e.Templates = append(e.Templates, "html")
    99  		}
   100  		if e.EmptyElement && (e.Child != "" || e.Children != "") {
   101  			fatalf("element %v specified as EmptyElement but also child or children properties", e.Name)
   102  		}
   103  		if e.Child != "" && e.Children != "" {
   104  			fatalf("element %v supplied both child and children properties", e.Name)
   105  		}
   106  		if !e.EmptyElement && e.Children == "" {
   107  			e.Children = "Element"
   108  		}
   109  		if e.Dom == "" {
   110  			e.Dom = "HTML" + e.Name + "Element"
   111  		}
   112  		if e.HTML == "" {
   113  			e.HTML = strings.ToLower(e.Name)
   114  		}
   115  		for n, a := range e.Attributes {
   116  			a.Name = n
   117  			if a.React == "" {
   118  				a.React = lowerInitial(n)
   119  			}
   120  			if a.HTML == "" {
   121  				a.HTML = strings.ToLower(a.Name)
   122  			}
   123  			if a.HTMLConvert == "" && a.Type != "string" {
   124  				switch a.Type {
   125  				case "bool":
   126  					a.HTMLConvert = "parseBool"
   127  				}
   128  			}
   129  			if a.Type == "" {
   130  				a.Type = "string"
   131  			}
   132  		}
   133  	}
   134  
   135  	var ens []string
   136  
   137  	// generate elements
   138  	for _, e := range elements {
   139  		ens = append(ens, e.Name)
   140  		attrs := make(map[string]*Attr)
   141  		addAttr := func(n string, a *Attr) {
   142  			if _, ok := attrs[n]; ok {
   143  				fatalf("element %v had a clash for attribute %v", e.Name, n)
   144  			}
   145  			attrs[n] = a
   146  		}
   147  
   148  		for _, tn := range e.Templates {
   149  			t, ok := templates[tn]
   150  			if !ok {
   151  				fatalf("element %v referenced non-existent template %q", e.Name, tn)
   152  			}
   153  			for _, a := range t {
   154  				addAttr(a.Name, a)
   155  			}
   156  		}
   157  
   158  		for _, a := range e.Attributes {
   159  			addAttr(a.Name, a)
   160  		}
   161  
   162  		e.Attributes = attrs
   163  	}
   164  
   165  	sort.Strings(ens)
   166  
   167  	// regular header
   168  	cg.pf("// Code generated by %v. DO NOT EDIT.\n", coreGenCmd)
   169  	cg.pln()
   170  	cg.pf("package %v\n", pkgName)
   171  	cg.pln()
   172  	cg.pln(`import "github.com/gopherjs/gopherjs/js"`)
   173  
   174  	// test header
   175  	cg.tpln("// +build js")
   176  	cg.tpln()
   177  	cg.tpf("// Code generated by %v. DO NOT EDIT.\n", coreGenCmd)
   178  	cg.tpln()
   179  	cg.tpf("package %v_test\n", pkgName)
   180  	cg.tpln(`
   181  import (
   182  	"testing"
   183  
   184  	"honnef.co/go/js/dom"
   185  
   186  	"myitcv.io/react"
   187  	"myitcv.io/react/testutils"
   188  )
   189  	`)
   190  
   191  	// jsx header
   192  	cg.jpf("// Code generated by %v. DO NOT EDIT.\n", coreGenCmd)
   193  	cg.jpln()
   194  	cg.jpln("package jsx")
   195  	cg.jpt(`
   196  import (
   197  	"fmt"
   198  	"strings"
   199  
   200  	"myitcv.io/react"
   201  
   202  	"golang.org/x/net/html"
   203  )
   204  
   205  func parse(n *html.Node) react.Element {
   206  	switch n.Type {
   207  	case html.TextNode:
   208  		return react.S(n.Data)
   209  	case html.ElementNode:
   210  		// we will fall out from here...
   211  	default:
   212  		panic(fmt.Errorf("cannot handle NodeType %v", n.Type))
   213  	}
   214  
   215  	switch n.Data {
   216  	{{range .}}
   217  	case "{{.React}}":
   218  		return parse{{.Name}}(n)
   219  	{{end}}
   220  	default:
   221  		panic(fmt.Errorf("cannot handle Element %v", n.Data))
   222  	}
   223  }
   224  	`, elements)
   225  
   226  	for _, en := range ens {
   227  		e := elements[en]
   228  		cg.pt(`
   229  {{$elem := .}}
   230  
   231  // {{.Name}}Elem is the React element definition corresponding to the HTML <{{.React}}> element
   232  type {{.Name}}Elem struct {
   233  	Element
   234  }
   235  
   236  func (a *{{.Name}}Elem) coreReactElement() {}
   237  
   238  {{range .Implements}}
   239  func (l *{{$elem.Name}}Elem) {{.}} {}
   240  {{end}}
   241  
   242  // {{.Name}}Props defines the properties for the <{{.React}}> element
   243  type {{.Name}}Props struct {
   244  	{{- range .Attributes}}
   245  	{{.Name}} {{.Type -}}
   246  	{{end}}
   247  }
   248  
   249  // {{.Name}} creates a new instance of a <{{.React}}> element with the provided props and
   250  // children
   251  func {{.Name}}(props *{{.Name}}Props, {{.ChildParam}}) *{{.Name}}Elem {
   252  	type _{{.Name}}Props struct {
   253  		o *js.Object
   254  		{{- range .Attributes}}
   255  		{{- if not .NoReact}}
   256  		{{.Name}} {{.Type}} {{.Tag -}}
   257  		{{end -}}
   258  		{{end}}
   259  	}
   260  
   261  	{{.ChildConvert}}
   262  
   263  	rprops := &_{{.Name}}Props{
   264  		o: object.New(),
   265  	}
   266  
   267  	if props != nil {
   268  		{{- range .Attributes}}
   269  			{{- if eq .Name "Ref" }}
   270  			if props.Ref != nil {
   271  				rprops.o.Set("ref", props.Ref.Ref)
   272  			}
   273  			{{- else if eq .Name "DataSet" }}
   274  			if props.DataSet != nil {
   275  				for dk, dv := range props.DataSet {
   276  					rprops.o.Set("data-"+dk, dv)
   277  				}
   278  			}
   279  			{{- else if eq .Name "AriaSet" }}
   280  			if props.AriaSet != nil {
   281  				for dk, dv := range props.AriaSet {
   282  					rprops.o.Set("aria-"+dk, dv)
   283  				}
   284  			}
   285  			{{- else}}
   286  			{{- if .OmitEmpty }}
   287  				if props.{{.Name}} != "" {
   288  					rprops.{{.Name}} = props.{{.Name}}
   289  				}
   290  			{{- else}}
   291  			{{- if .IsEvent}}
   292  				if props.{{.Name}} != nil {
   293  					rprops.o.Set("{{.React}}", props.{{.Name}}.{{.Name}})
   294  				}
   295  			{{- else if eq .Name "Style"}}
   296  				// TODO: until we have a resolution on
   297  				// https://github.com/gopherjs/gopherjs/issues/236
   298  				rprops.{{.Name}} = props.{{.Name}}.hack()
   299  			{{- else}}
   300  				rprops.{{.Name}} = props.{{.Name}}
   301  			{{end -}}
   302  			{{end -}}
   303  			{{end -}}
   304  		{{end -}}
   305  	}
   306  
   307  	return &{{.Name}}Elem{
   308  		Element: createElement("{{.React}}", rprops, {{.ChildArg}}),
   309  	}
   310  }
   311  		`, e)
   312  
   313  		if !e.SkipTests {
   314  			cg.tpt(`
   315  func Test{{.Name}}Elem(t *testing.T) {
   316  	class := "test"
   317  
   318  	x := testutils.Wrapper(react.{{.Name}}(&react.{{.Name}}Props{ClassName: class}))
   319  	cont := testutils.RenderIntoDocument(x)
   320  
   321  	el := testutils.FindRenderedDOMComponentWithClass(cont, class)
   322  
   323  	if _, ok := el.(*dom.{{.Dom}}); !ok {
   324  		t.Fatal("Failed to find <{{.React}}> element")
   325  	}
   326  }
   327  			`, e)
   328  		}
   329  
   330  		cg.jpt(`
   331  func parse{{.Name}}(n *html.Node) *react.{{.Name}}Elem {
   332  	{{if not .EmptyElement}}
   333  	var kids []{{.ChildrenReactType}}
   334  	{{end}}
   335  
   336  	var vp *react.{{.Name}}Props
   337  	var ds react.DataSet
   338  
   339  	if len(n.Attr) > 0 {
   340  		vp = new(react.{{.Name}}Props)
   341  
   342  		for _, a := range n.Attr {
   343  			switch v := a.Key; {
   344  			{{range .HTMLAttributes}}
   345  			case v == "{{.HTML}}":
   346  				vp.{{.Name}} = {{.HTMLConvertor "a.Val"}}
   347  			{{end}}
   348  			case strings.HasPrefix(v, "data-"):
   349  				if ds == nil {
   350  					ds = make(react.DataSet)
   351  				}
   352  				ds[strings.TrimPrefix(v, "data-")] = a.Val
   353  			default:
   354  				panic(fmt.Errorf("don't know how to handle <{{.React}}> attribute %q", a.Key))
   355  			}
   356  		}
   357  
   358  		vp.DataSet = ds
   359  	}
   360  
   361  	{{if not .EmptyElement}}
   362  	for c := n.FirstChild; c != nil; c = c.NextSibling {
   363  		kids = append(kids, parse(c).({{.ChildrenReactType}}))
   364  	}
   365  
   366  	return react.{{.Name}}(vp, kids...)
   367  	{{else}}
   368  	return react.{{.Name}}(vp)
   369  	{{end}}
   370  }
   371  		`, e)
   372  	}
   373  
   374  	write := func(w *bytes.Buffer, fn string) {
   375  		toWrite := w.Bytes()
   376  
   377  		out, err := format.Source(toWrite)
   378  		if err == nil {
   379  			toWrite = out
   380  		}
   381  
   382  		if err := ioutil.WriteFile(fn, toWrite, 0644); err != nil {
   383  			fatalf("failed to write to %v; %v", fn)
   384  		}
   385  	}
   386  
   387  	write(cg.buf, gogenerate.NameFile(pkgName, coreGenCmd))
   388  	write(cg.tbuf, gogenerate.NameTestFile(pkgName, coreGenCmd))
   389  	write(cg.jbuf, filepath.Join("jsx", gogenerate.NameFile("jsx", coreGenCmd)))
   390  }