139 lines
2.9 KiB
Go
139 lines
2.9 KiB
Go
package actions
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/hashicorp/hcl"
|
|
"github.com/hashicorp/hcl/hcl/ast"
|
|
"github.com/hashicorp/hcl/hcl/token"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
func parseWorkflowsFile(workflowReader io.Reader) (*workflowsFile, error) {
|
|
// TODO: add validation logic
|
|
// - check for circular dependencies
|
|
// - check for valid local path refs
|
|
// - check for valid dependencies
|
|
|
|
buf := new(bytes.Buffer)
|
|
_, err := buf.ReadFrom(workflowReader)
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
|
|
workflows := new(workflowsFile)
|
|
|
|
astFile, err := hcl.ParseBytes(buf.Bytes())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rootNode := ast.Walk(astFile.Node, cleanWorkflowsAST)
|
|
err = hcl.DecodeObject(workflows, rootNode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return workflows, nil
|
|
}
|
|
|
|
func cleanWorkflowsAST(node ast.Node) (ast.Node, bool) {
|
|
if objectItem, ok := node.(*ast.ObjectItem); ok {
|
|
key := objectItem.Keys[0].Token.Value()
|
|
|
|
// handle condition where value is a string but should be a list
|
|
switch key {
|
|
case "args", "runs":
|
|
if literalType, ok := objectItem.Val.(*ast.LiteralType); ok {
|
|
listType := new(ast.ListType)
|
|
parts, err := parseCommand(literalType.Token.Value().(string))
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
quote := literalType.Token.Text[0]
|
|
for _, part := range parts {
|
|
part = fmt.Sprintf("%c%s%c", quote, part, quote)
|
|
listType.Add(&ast.LiteralType{
|
|
Token: token.Token{
|
|
Type: token.STRING,
|
|
Text: part,
|
|
},
|
|
})
|
|
}
|
|
objectItem.Val = listType
|
|
|
|
}
|
|
case "resolves", "needs":
|
|
if literalType, ok := objectItem.Val.(*ast.LiteralType); ok {
|
|
listType := new(ast.ListType)
|
|
listType.Add(literalType)
|
|
objectItem.Val = listType
|
|
|
|
}
|
|
}
|
|
}
|
|
return node, true
|
|
}
|
|
|
|
// reused from: https://github.com/laurent22/massren/blob/ae4c57da1e09a95d9383f7eb645a9f69790dec6c/main.go#L172
|
|
// nolint: gocyclo
|
|
func parseCommand(cmd string) ([]string, error) {
|
|
var args []string
|
|
state := "start"
|
|
current := ""
|
|
quote := "\""
|
|
for i := 0; i < len(cmd); i++ {
|
|
c := cmd[i]
|
|
|
|
if state == "quotes" {
|
|
if string(c) != quote {
|
|
current += string(c)
|
|
} else {
|
|
args = append(args, current)
|
|
current = ""
|
|
state = "start"
|
|
}
|
|
continue
|
|
}
|
|
|
|
if c == '"' || c == '\'' {
|
|
state = "quotes"
|
|
quote = string(c)
|
|
continue
|
|
}
|
|
|
|
if state == "arg" {
|
|
if c == ' ' || c == '\t' {
|
|
args = append(args, current)
|
|
current = ""
|
|
state = "start"
|
|
} else {
|
|
current += string(c)
|
|
}
|
|
continue
|
|
}
|
|
|
|
if c != ' ' && c != '\t' {
|
|
state = "arg"
|
|
current += string(c)
|
|
}
|
|
}
|
|
|
|
if state == "quotes" {
|
|
return []string{}, fmt.Errorf("unclosed quote in command line: %s", cmd)
|
|
}
|
|
|
|
if current != "" {
|
|
args = append(args, current)
|
|
}
|
|
|
|
if len(args) == 0 {
|
|
return []string{}, errors.New("empty command line")
|
|
}
|
|
|
|
log.Debugf("Parsed literal %+q to list %+q", cmd, args)
|
|
|
|
return args, nil
|
|
}
|