github.com/beauknowssoftware/makehcl@v0.0.0-20200322000747-1b9bb1e1c008/internal/parse/parseDefinition.go (about) 1 package parse 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl/v2" 7 "github.com/hashicorp/hcl/v2/hclparse" 8 "github.com/pkg/errors" 9 "github.com/zclconf/go-cty/cty" 10 11 "github.com/beauknowssoftware/makehcl/internal/definition" 12 ) 13 14 const ( 15 commandBlockType = "command" 16 ruleBlockType = "rule" 17 dynamicBlockType = "dynamic" 18 ) 19 20 var ( 21 definitionSchema = hcl.BodySchema{ 22 Attributes: []hcl.AttributeSchema{ 23 { 24 Name: "default_goal", 25 Required: false, 26 }, 27 }, 28 Blocks: []hcl.BlockHeaderSchema{ 29 { 30 Type: "import", 31 }, 32 { 33 Type: "opts", 34 }, 35 { 36 Type: "env", 37 }, 38 { 39 Type: "var", 40 }, 41 { 42 Type: commandBlockType, 43 LabelNames: []string{"name"}, 44 }, 45 { 46 Type: ruleBlockType, 47 }, 48 { 49 Type: dynamicBlockType, 50 LabelNames: []string{"type"}, 51 }, 52 }, 53 } 54 ) 55 56 func getAllBlocks(blockType string, con *hcl.BodyContent) []*hcl.Block { 57 var blocks []*hcl.Block 58 59 for _, blk := range con.Blocks { 60 if blk.Type == blockType { 61 blocks = append(blocks, blk) 62 } 63 } 64 65 return blocks 66 } 67 68 func getAllAttributes(blockType string, con *hcl.BodyContent) (map[string]*hcl.Attribute, error) { 69 attrs := make(map[string]*hcl.Attribute) 70 71 for _, blk := range getAllBlocks(blockType, con) { 72 attr, diag := blk.Body.JustAttributes() 73 if diag.HasErrors() { 74 return nil, errors.Wrapf(diag, "failed to get %v attributes", blockType) 75 } 76 77 for k, v := range attr { 78 attrs[k] = v 79 } 80 } 81 82 return attrs, nil 83 } 84 85 func fillGlobals(con *hcl.BodyContent, d *definition.Definition, ctx *hcl.EvalContext) error { 86 varAttrs, err := getAllAttributes("var", con) 87 if err != nil { 88 return err 89 } 90 91 envAttrs, err := getAllAttributes("env", con) 92 if err != nil { 93 return err 94 } 95 96 envs, err := getGlobals(map[string]map[string]*hcl.Attribute{ 97 "var": varAttrs, 98 "env": envAttrs, 99 }, ctx) 100 if err != nil { 101 return err 102 } 103 104 d.GlobalEnvironment = envs 105 106 return nil 107 } 108 109 func fillOpts(con *hcl.BodyContent, d *definition.Definition, ctx *hcl.EvalContext) error { 110 optsAttrs, err := getAllAttributes("opts", con) 111 if err != nil { 112 return err 113 } 114 115 for name, attr := range optsAttrs { 116 switch name { 117 case "shell": 118 v, err := evaluateString(attr.Expr, ctx) 119 if err != nil { 120 err = errors.Wrap(err, "failed to evaluate shell opt") 121 return err 122 } 123 124 d.Shell = v 125 case "shell_flags": 126 v, err := evaluateString(attr.Expr, ctx) 127 if err != nil { 128 err = errors.Wrap(err, "failed to evaluate shell_flag opt") 129 return err 130 } 131 132 d.ShellFlags = &v 133 } 134 } 135 136 return nil 137 } 138 139 func fillDefaultGoal(con *hcl.BodyContent, d *definition.Definition, ctx *hcl.EvalContext) error { 140 for name, attr := range con.Attributes { 141 if name == "default_goal" { 142 defaultGoal, err := evaluateStringArray(attr.Expr, ctx) 143 if err != nil { 144 err = errors.Wrap(err, "failed to evaluate default_goal") 145 return err 146 } 147 148 d.SetDefaultGoal(defaultGoal) 149 } 150 } 151 152 return nil 153 } 154 155 func fillRuleFromRuleBlock(blk *hcl.Block, d *definition.Definition, ctx *hcl.EvalContext) error { 156 r, err := constructRule(blk, ctx) 157 if err != nil { 158 return err 159 } 160 161 d.AddRule(r) 162 163 return nil 164 } 165 166 type dynamicTarget struct { 167 targetType string 168 alias string 169 targets []cty.Value 170 targetStrings []string 171 } 172 173 func (dt *dynamicTarget) addTarget(t string) { 174 dt.targetStrings = append(dt.targetStrings, t) 175 dt.targets = append(dt.targets, cty.StringVal(t)) 176 } 177 178 func fillFromDynamicBlock(blk *hcl.Block, d *definition.Definition, ctx *hcl.EvalContext) (*dynamicTarget, error) { 179 switch blk.Labels[0] { 180 case ruleBlockType: 181 dy, err := constructDynamicRules(blk, ctx) 182 if err != nil { 183 return nil, err 184 } 185 186 var dt dynamicTarget 187 dt.alias = dy.alias 188 dt.targetType = ruleBlockType 189 dt.targets = make([]cty.Value, 0, len(dy.rules)) 190 191 for _, dr := range dy.rules { 192 d.AddRule(dr) 193 dt.addTarget(dr.Target) 194 } 195 196 return &dt, nil 197 case commandBlockType: 198 dy, err := constructDynamicCommands(blk, ctx) 199 if err != nil { 200 return nil, err 201 } 202 203 var dt dynamicTarget 204 dt.alias = dy.alias 205 dt.targetType = commandBlockType 206 dt.targets = make([]cty.Value, 0, len(dy.commands)) 207 208 for _, dc := range dy.commands { 209 d.AddCommand(dc) 210 dt.addTarget(dc.Name) 211 } 212 213 return &dt, nil 214 default: 215 return nil, fmt.Errorf("unknown dynamic type %v", blk.Labels[0]) 216 } 217 } 218 219 func fillRuleFromCommandBlock(blk *hcl.Block, d *definition.Definition, ctx *hcl.EvalContext) error { 220 c, err := constructCommand(blk, ctx) 221 if err != nil { 222 return err 223 } 224 225 d.AddCommand(c) 226 227 return nil 228 } 229 230 func fillRules(con *hcl.BodyContent, d *definition.Definition, ctx *hcl.EvalContext) error { 231 for _, blk := range con.Blocks { 232 switch blk.Type { 233 case ruleBlockType: 234 if err := fillRuleFromRuleBlock(blk, d, ctx); err != nil { 235 return err 236 } 237 case commandBlockType: 238 if err := fillRuleFromCommandBlock(blk, d, ctx); err != nil { 239 return err 240 } 241 } 242 } 243 244 return nil 245 } 246 247 func fillDynamicRules(con *hcl.BodyContent, d *definition.Definition, ctx *hcl.EvalContext) error { 248 rules := make(map[string]cty.Value) 249 commands := make(map[string]cty.Value) 250 251 for _, blk := range con.Blocks { 252 if blk.Type == dynamicBlockType { 253 dt, err := fillFromDynamicBlock(blk, d, ctx) 254 if err != nil { 255 return err 256 } 257 258 if dt.alias != "" && dt.targetType == commandBlockType { 259 commands[dt.alias] = cty.ListVal(dt.targets) 260 } 261 262 if dt.alias != "" && dt.targetType == ruleBlockType { 263 rules[dt.alias] = cty.ListVal(dt.targets) 264 } 265 266 if dt.alias != "" { 267 d.AddCommand(&definition.Command{ 268 Name: dt.alias, 269 Dependencies: dt.targetStrings, 270 }) 271 } 272 } 273 } 274 275 if len(rules) > 0 { 276 ctx.Variables["rule"] = cty.MapVal(rules) 277 } 278 279 if len(commands) > 0 { 280 ctx.Variables["command"] = cty.MapVal(commands) 281 } 282 283 return nil 284 } 285 286 var ( 287 importSchema = hcl.BodySchema{ 288 Attributes: []hcl.AttributeSchema{ 289 { 290 Name: "file", 291 Required: true, 292 }, 293 }, 294 } 295 ) 296 297 func executeImport(blk *hcl.Block, ctx *hcl.EvalContext) (hcl.Body, error) { 298 con, diag := blk.Body.Content(&importSchema) 299 if diag.HasErrors() { 300 return nil, diag 301 } 302 303 file, err := evaluateString(con.Attributes["file"].Expr, ctx) 304 if err != nil { 305 return nil, err 306 } 307 308 p := hclparse.NewParser() 309 310 f, diag := p.ParseHCLFile(file) 311 if diag.HasErrors() { 312 return nil, diag 313 } 314 315 return f.Body, nil 316 } 317 318 func executeImports(con *hcl.BodyContent, ctx *hcl.EvalContext) ([]hcl.Body, error) { 319 importBlocks := getAllBlocks("import", con) 320 321 bodies := make([]hcl.Body, 0, len(importBlocks)) 322 323 for _, b := range importBlocks { 324 body, err := executeImport(b, ctx) 325 if err != nil { 326 return nil, err 327 } 328 329 bodies = append(bodies, body) 330 } 331 332 return bodies, nil 333 } 334 335 func constructDefinition(f *hcl.File, ctx *hcl.EvalContext) (*definition.Definition, error) { 336 con, diag := f.Body.Content(&definitionSchema) 337 if diag.HasErrors() { 338 return nil, diag 339 } 340 341 bodies, err := executeImports(con, ctx) 342 if err != nil { 343 return nil, err 344 } 345 346 bodies = append(bodies, f.Body) 347 348 con, diag = hcl.MergeBodies(bodies).Content(&definitionSchema) 349 if diag.HasErrors() { 350 return nil, diag 351 } 352 353 var d definition.Definition 354 355 if err := fillGlobals(con, &d, ctx); err != nil { 356 return nil, err 357 } 358 359 if err := fillOpts(con, &d, ctx); err != nil { 360 return nil, err 361 } 362 363 if err := fillDynamicRules(con, &d, ctx); err != nil { 364 return nil, err 365 } 366 367 if err := fillRules(con, &d, ctx); err != nil { 368 return nil, err 369 } 370 371 if err := fillDefaultGoal(con, &d, ctx); err != nil { 372 return nil, err 373 } 374 375 return &d, nil 376 }