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 }