github.com/shogo82148/goa-v1@v1.6.2/goagen/codegen/finalizer.go (about) 1 package codegen 2 3 import ( 4 "bytes" 5 "fmt" 6 "text/template" 7 8 "github.com/shogo82148/goa-v1/design" 9 ) 10 11 // Finalizer is the code generator for the 'Finalize' type methods. 12 type Finalizer struct { 13 assignmentT *template.Template 14 arrayAssignmentT *template.Template 15 seen map[*design.AttributeDefinition]map[*design.AttributeDefinition]*bytes.Buffer 16 } 17 18 // NewFinalizer instantiates a finalize code generator. 19 func NewFinalizer() *Finalizer { 20 var ( 21 f = &Finalizer{seen: make(map[*design.AttributeDefinition]map[*design.AttributeDefinition]*bytes.Buffer)} 22 err error 23 ) 24 fm := template.FuncMap{ 25 "tabs": Tabs, 26 "goify": Goify, 27 "gotyperef": GoTypeRef, 28 "gotypedef": GoTypeDef, 29 "add": Add, 30 "finalizeCode": f.Code, 31 } 32 f.assignmentT, err = template.New("assignment").Funcs(fm).Parse(assignmentTmpl) 33 if err != nil { 34 panic(err) 35 } 36 f.arrayAssignmentT, err = template.New("arrAssignment").Funcs(fm).Parse(arrayAssignmentTmpl) 37 if err != nil { 38 panic(err) 39 } 40 return f 41 } 42 43 // Code produces Go code that sets the default values for fields recursively for the given 44 // attribute. 45 func (f *Finalizer) Code(att *design.AttributeDefinition, target string, depth int) string { 46 buf := f.recurse(att, att, target, depth) 47 return buf.String() 48 } 49 50 func (f *Finalizer) recurse(root, att *design.AttributeDefinition, target string, depth int) *bytes.Buffer { 51 var ( 52 buf = new(bytes.Buffer) 53 first = true 54 ) 55 56 if s, ok := f.seen[root]; ok { 57 if buf, ok := s[att]; ok { 58 return buf 59 } 60 s[att] = buf 61 } else { 62 f.seen[root] = map[*design.AttributeDefinition]*bytes.Buffer{att: buf} 63 } 64 65 if o := att.Type.ToObject(); o != nil { 66 o.IterateAttributes(func(n string, catt *design.AttributeDefinition) error { 67 if att.HasDefaultValue(n) { 68 data := map[string]interface{}{ 69 "target": target, 70 "field": n, 71 "catt": catt, 72 "depth": depth, 73 "isDatetime": catt.Type == design.DateTime, 74 "defaultVal": PrintVal(catt.Type, catt.DefaultValue), 75 } 76 if !first { 77 buf.WriteByte('\n') 78 } else { 79 first = false 80 } 81 buf.WriteString(RunTemplate(f.assignmentT, data)) 82 } 83 a := f.recurse(root, catt, fmt.Sprintf("%s.%s", target, Goify(n, true)), depth+1).String() 84 if a != "" { 85 if catt.Type.IsObject() { 86 a = fmt.Sprintf("%sif %s.%s != nil {\n%s\n%s}", 87 Tabs(depth), target, Goify(n, true), a, Tabs(depth)) 88 } 89 if !first { 90 buf.WriteByte('\n') 91 } else { 92 first = false 93 } 94 buf.WriteString(a) 95 } 96 return nil 97 }) 98 } else if a := att.Type.ToArray(); a != nil { 99 data := map[string]interface{}{ 100 "elemType": a.ElemType, 101 "target": target, 102 "depth": 1, 103 } 104 if as := RunTemplate(f.arrayAssignmentT, data); as != "" { 105 buf.WriteString(as) 106 } 107 } 108 return buf 109 } 110 111 // PrintVal prints the given value corresponding to the given data type. 112 // The value is already checked for the compatibility with the data type. 113 func PrintVal(t design.DataType, val interface{}) string { 114 switch { 115 case t.IsPrimitive(): 116 // For primitive types, simply print the value 117 s := fmt.Sprintf("%#v", val) 118 switch t { 119 case design.Number: 120 v := val 121 if i, ok := val.(int); ok { 122 v = float64(i) 123 } 124 s = fmt.Sprintf("%f", v) 125 case design.DateTime: 126 s = fmt.Sprintf("time.Parse(time.RFC3339, %s)", s) 127 } 128 return s 129 case t.IsHash(): 130 // The input is a hash 131 h := t.ToHash() 132 hval := val.(map[interface{}]interface{}) 133 if len(hval) == 0 { 134 return fmt.Sprintf("%s{}", GoTypeName(t, nil, 0, false)) 135 } 136 var buffer bytes.Buffer 137 buffer.WriteString(fmt.Sprintf("%s{", GoTypeName(t, nil, 0, false))) 138 for k, v := range hval { 139 buffer.WriteString(fmt.Sprintf("%s: %s, ", PrintVal(h.KeyType.Type, k), PrintVal(h.ElemType.Type, v))) 140 } 141 buffer.Truncate(buffer.Len() - 2) // remove ", " 142 buffer.WriteString("}") 143 return buffer.String() 144 case t.IsArray(): 145 // Input is an array 146 a := t.ToArray() 147 aval := val.([]interface{}) 148 if len(aval) == 0 { 149 return fmt.Sprintf("%s{}", GoTypeName(t, nil, 0, false)) 150 } 151 var buffer bytes.Buffer 152 buffer.WriteString(fmt.Sprintf("%s{", GoTypeName(t, nil, 0, false))) 153 for _, e := range aval { 154 buffer.WriteString(fmt.Sprintf("%s, ", PrintVal(a.ElemType.Type, e))) 155 } 156 buffer.Truncate(buffer.Len() - 2) // remove ", " 157 buffer.WriteString("}") 158 return buffer.String() 159 default: 160 // shouldn't happen as the value's compatibility is already checked. 161 panic("unknown type") 162 } 163 } 164 165 const ( 166 assignmentTmpl = `{{ if .catt.Type.IsPrimitive }}{{ $defaultName := (print "default" (goify .field true)) }}{{/* 167 */}}{{ tabs .depth }}{{if .isDatetime}}var {{ $defaultName }}, _ = {{ .defaultVal }}{{ else }}var {{ $defaultName }} {{ gotypedef .catt 0 false false }} = {{ .defaultVal }}{{end}} 168 {{ tabs .depth }}if {{ .target }}.{{ goify .field true }} == nil { 169 {{ tabs .depth }} {{ .target }}.{{ goify .field true }} = &{{ $defaultName }} 170 }{{ else }}{{ tabs .depth }}if {{ .target }}.{{ goify .field true }} == nil { 171 {{ tabs .depth }} {{ .target }}.{{ goify .field true }} = {{ .defaultVal }} 172 }{{ end }}` 173 174 arrayAssignmentTmpl = `{{ $a := finalizeCode .elemType "e" (add .depth 1) }}{{/* 175 */}}{{ if $a }}{{ tabs .depth }}for _, e := range {{ .target }} { 176 {{ $a }} 177 {{ tabs .depth }}}{{ end }}` 178 )