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  )