github.com/urso/go-structform@v0.0.2/internal/gen/gen_yaml.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"flag"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"strings"
    12  	"text/template"
    13  
    14  	"github.com/elastic/go-ucfg"
    15  	"github.com/elastic/go-ucfg/cfgutil"
    16  	"github.com/elastic/go-ucfg/yaml"
    17  
    18  	"golang.org/x/tools/imports"
    19  )
    20  
    21  var cfgOpts = []ucfg.Option{
    22  	ucfg.PathSep("."),
    23  	ucfg.ResolveEnv,
    24  }
    25  
    26  var datOpts = append([]ucfg.Option{ucfg.VarExp}, cfgOpts...)
    27  
    28  func main() {
    29  	if err := run(); err != nil {
    30  		fmt.Fprintln(os.Stderr, err)
    31  		os.Exit(1)
    32  	}
    33  }
    34  
    35  func run() error {
    36  	to := flag.String("o", "", "write to")
    37  	format := flag.Bool("f", false, "format output using goimports")
    38  	dataFile := flag.String("d", "", "input data file for use to fill out")
    39  
    40  	flag.Parse()
    41  	args := flag.Args()
    42  	if len(args) == 0 {
    43  		return errors.New("Missing input file")
    44  	}
    45  
    46  	userData, err := loadData(*dataFile)
    47  	if err != nil {
    48  		return fmt.Errorf("Failed to read data file: %v", err)
    49  	}
    50  
    51  	gen := struct {
    52  		Import    []string
    53  		Templates map[string]string
    54  		Main      string
    55  	}{}
    56  	if err = loadConfigInto(args[0], &gen); err != nil {
    57  		errPrint("Failed to load script template")
    58  		return err
    59  	}
    60  
    61  	dat := struct {
    62  		Data *ucfg.Config
    63  	}{}
    64  	if err = loadConfigInto(args[0], &dat, ucfg.VarExp); err != nil {
    65  		errPrint("Failed to load script data")
    66  		return err
    67  	}
    68  
    69  	var T *template.Template
    70  	D := cfgutil.NewCollector(nil, datOpts...)
    71  	var data map[string]interface{}
    72  
    73  	var defaultFuncs = template.FuncMap{
    74  		"data":       func() map[string]interface{} { return data },
    75  		"toLower":    strings.ToLower,
    76  		"toUpper":    strings.ToUpper,
    77  		"capitalize": strings.Title,
    78  		"isnil": func(v interface{}) bool {
    79  			return v == nil
    80  		},
    81  		"default": func(D, v interface{}) interface{} {
    82  			if v == nil {
    83  				return D
    84  			}
    85  			return v
    86  		},
    87  		"dict":   makeDict,
    88  		"invoke": makeInvokeCommand(&T), // invoke another template with named parameters
    89  	}
    90  
    91  	var td *ucfg.Config
    92  	T, td, err = loadTemplates(template.New("").Funcs(defaultFuncs), gen.Import)
    93  	if err := D.Add(td, err); err != nil {
    94  		errPrint("Failed to load imported template files")
    95  		return err
    96  	}
    97  
    98  	if err := D.Add(dat.Data, nil); err != nil {
    99  		errPrint("Failed to merge template data with top-level script")
   100  		return err
   101  	}
   102  
   103  	if err := D.Add(ucfg.NewFrom(userData, datOpts...)); err != nil {
   104  		errPrintf("Failed to merge user data")
   105  		return err
   106  	}
   107  
   108  	if err := D.Config().Unpack(&data, datOpts...); err != nil {
   109  		errPrint("Failed to unpack data")
   110  		return err
   111  	}
   112  
   113  	T, err = addTemplates(T, gen.Templates)
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	var buf bytes.Buffer
   119  	header := fmt.Sprintf("// This file has been generated from '%v', do not edit\n", args[0])
   120  	buf.WriteString(header)
   121  	T = T.New("master")
   122  	T, err = T.Parse(gen.Main)
   123  	if err != nil {
   124  		return fmt.Errorf("Parsing 'template' fields failed with %v", err)
   125  	}
   126  
   127  	if err := T.Execute(&buf, data); err != nil {
   128  		return fmt.Errorf("executing template failed with %v", err)
   129  	}
   130  
   131  	content := buf.Bytes()
   132  	if *format {
   133  		content, err = imports.Process(*to, content, nil)
   134  		if err != nil {
   135  			return fmt.Errorf("Applying goimports failed with: %v", err)
   136  		}
   137  	}
   138  
   139  	if *to != "" {
   140  		return ioutil.WriteFile(*to, content, 0644)
   141  	}
   142  
   143  	_, err = os.Stdout.Write(content)
   144  	return err
   145  }
   146  
   147  func loadTemplates(T *template.Template, files []string) (*template.Template, *ucfg.Config, error) {
   148  
   149  	/*
   150  		var childData []*ucfg.Config
   151  		var templatesData []*ucfg.Config
   152  	*/
   153  
   154  	childData := cfgutil.NewCollector(nil, datOpts...)
   155  	templateData := cfgutil.NewCollector(nil, datOpts...)
   156  
   157  	for _, file := range files {
   158  		gen := struct {
   159  			Import    []string
   160  			Templates map[string]string
   161  		}{}
   162  
   163  		dat := struct {
   164  			Data *ucfg.Config
   165  		}{}
   166  
   167  		err := loadConfigInto(file, &gen)
   168  		if err != nil {
   169  			return nil, nil, err
   170  		}
   171  
   172  		var D *ucfg.Config
   173  		T, D, err = loadTemplates(T, gen.Import)
   174  		if err != nil {
   175  			return nil, nil, err
   176  		}
   177  
   178  		T, err = addTemplates(T, gen.Templates)
   179  		if err != nil {
   180  			return nil, nil, err
   181  		}
   182  
   183  		err = loadConfigInto(file, &dat, ucfg.VarExp)
   184  		if err != nil {
   185  			errPrint("Failed to load data from: ", file)
   186  			return nil, nil, err
   187  		}
   188  
   189  		childData.Add(D, nil)
   190  		templateData.Add(dat.Data, nil)
   191  	}
   192  
   193  	if err := childData.Error(); err != nil {
   194  		errPrintf("Procesing file %v: failed to merge child data: %v", files, err)
   195  		return nil, nil, err
   196  	}
   197  
   198  	if err := templateData.Error(); err != nil {
   199  		errPrintf("Procesing file %v: failed to merge template data: %v", files, err)
   200  		return nil, nil, err
   201  	}
   202  
   203  	if err := childData.Add(templateData.Config(), templateData.Error()); err != nil {
   204  		errPrintf("Failed to combine template data: ", err)
   205  		return nil, nil, err
   206  	}
   207  
   208  	return T, childData.Config(), nil
   209  }
   210  
   211  func addTemplates(T *template.Template, templates map[string]string) (*template.Template, error) {
   212  	for name, content := range templates {
   213  		var err error
   214  
   215  		T = T.New(name)
   216  		T, err = T.Parse(content)
   217  		if err != nil {
   218  			return nil, fmt.Errorf("failed to parse template %v: %v", name, err)
   219  		}
   220  	}
   221  
   222  	return T, nil
   223  }
   224  
   225  func loadConfig(file string, extraOpts ...ucfg.Option) (cfg *ucfg.Config, err error) {
   226  	opts := append(append([]ucfg.Option{}, extraOpts...), cfgOpts...)
   227  	cfg, err = yaml.NewConfigWithFile(file, opts...)
   228  	if err != nil {
   229  		err = fmt.Errorf("Failed to read file %v with: %v", file, err)
   230  	}
   231  	return
   232  }
   233  
   234  func loadConfigInto(file string, to interface{}, extraOpts ...ucfg.Option) error {
   235  	cfg, err := loadConfig(file, extraOpts...)
   236  	if err == nil {
   237  		err = readConfig(cfg, to, extraOpts...)
   238  	}
   239  	return err
   240  }
   241  
   242  func readConfig(cfg *ucfg.Config, to interface{}, extraOpts ...ucfg.Option) error {
   243  	opts := append(append([]ucfg.Option{}, extraOpts...), cfgOpts...)
   244  	if err := cfg.Unpack(to, opts...); err != nil {
   245  		return fmt.Errorf("Parsing template file failed with %v", err)
   246  	}
   247  	return nil
   248  }
   249  
   250  func makeDict(values ...interface{}) (map[string]interface{}, error) {
   251  	if len(values)%2 != 0 {
   252  		return nil, errors.New("invalid dict call")
   253  	}
   254  
   255  	dict := make(map[string]interface{}, len(values)/2)
   256  	for i := 0; i < len(values); i += 2 {
   257  		key, ok := values[i].(string)
   258  		if !ok {
   259  			return nil, errors.New("dict keys must be strings")
   260  		}
   261  		dict[key] = values[i+1]
   262  	}
   263  	return dict, nil
   264  }
   265  
   266  func makeInvokeCommand(T **template.Template) func(string, ...interface{}) (string, error) {
   267  	return func(name string, values ...interface{}) (string, error) {
   268  		params, err := makeDict(values...)
   269  		if err != nil {
   270  			return "", err
   271  		}
   272  
   273  		var buf bytes.Buffer
   274  		err = (*T).ExecuteTemplate(&buf, name, params)
   275  		return buf.String(), err
   276  
   277  	}
   278  }
   279  
   280  func loadData(file string) (map[string]interface{}, error) {
   281  	if file == "" {
   282  		return nil, nil
   283  	}
   284  
   285  	meta := struct {
   286  		Entries map[string]struct {
   287  			Default     string
   288  			Description string
   289  		} `config:",inline"`
   290  	}{}
   291  
   292  	err := loadConfigInto(file, &meta, ucfg.VarExp)
   293  	if err != nil {
   294  		return nil, err
   295  	}
   296  
   297  	reader := bufio.NewReader(os.Stdin)
   298  
   299  	state := map[string]interface{}{}
   300  	for name, entry := range meta.Entries {
   301  		// parse default value
   302  		T, err := template.New("").Parse(entry.Default)
   303  		if err != nil {
   304  			return nil, fmt.Errorf("Failed to parse data entry %v: %v", name, err)
   305  		}
   306  
   307  		var buf bytes.Buffer
   308  		if err := T.Execute(&buf, state); err != nil {
   309  			return nil, fmt.Errorf("Failed to evaluate data entry %v: %v", name, err)
   310  		}
   311  
   312  		// ask user for input
   313  		defaultValue := buf.String()
   314  		fmt.Printf("%v\n%v [%v]: ", entry.Description, name, defaultValue)
   315  		value, err := reader.ReadString('\n')
   316  		if err != nil {
   317  			return nil, fmt.Errorf("Error waiting for user input: %v", err)
   318  		}
   319  
   320  		value = strings.TrimSpace(value)
   321  		if value == "" {
   322  			value = defaultValue
   323  		}
   324  
   325  		state[name] = value
   326  	}
   327  
   328  	return state, nil
   329  }
   330  
   331  func errPrint(msg ...interface{}) {
   332  	fmt.Fprintln(os.Stderr, msg...)
   333  }
   334  
   335  func errPrintf(format string, msg ...interface{}) {
   336  	fmt.Fprintf(os.Stderr, format+"\n", msg...)
   337  }