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  }