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 }