github.com/informationsea/shellflow@v0.1.3/shellflow_flowtask.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"path/filepath"
    10  	"regexp"
    11  	"strings"
    12  
    13  	"github.com/informationsea/shellflow/flowscript"
    14  )
    15  
    16  type FlowTask interface {
    17  	DependentVariables() flowscript.StringSet
    18  	CreatedVariables() flowscript.StringSet
    19  	Line() int
    20  	Subscribe(env flowscript.Environment, builder *ShellTaskBuilder) error
    21  }
    22  
    23  type FlowTaskBlock interface {
    24  	FlowTask
    25  	AddTask(FlowTask)
    26  }
    27  
    28  type SingleScriptFlowTask struct {
    29  	LineNum   int
    30  	Script    string
    31  	evaluable flowscript.Evaluable
    32  }
    33  
    34  func NewSingleFlowScriptTask(lineNum int, line string) (*SingleScriptFlowTask, error) {
    35  	if !strings.HasPrefix(line, "#%") {
    36  		return nil, errors.New("flowscript line should be started with \"#%\"")
    37  	}
    38  	parsed, err := flowscript.ParseScript(line[2:])
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	return &SingleScriptFlowTask{LineNum: lineNum, Script: line[2:], evaluable: parsed}, nil
    43  }
    44  
    45  func (t *SingleScriptFlowTask) Subscribe(env flowscript.Environment, builder *ShellTaskBuilder) error {
    46  	_, e := t.evaluable.Evaluate(env)
    47  	if e != nil {
    48  		return fmt.Errorf("Parse error at line %d: %s", t.LineNum, e.Error())
    49  	}
    50  	return e
    51  }
    52  
    53  func (t *SingleScriptFlowTask) Line() int {
    54  	return t.LineNum
    55  }
    56  
    57  func (t *SingleScriptFlowTask) DependentVariables() flowscript.StringSet {
    58  	return flowscript.SearchDependentVariables(t.evaluable)
    59  }
    60  
    61  func (t *SingleScriptFlowTask) CreatedVariables() flowscript.StringSet {
    62  	return flowscript.SearchCreatedVariables(t.evaluable)
    63  }
    64  
    65  type SimpleTaskBlock struct {
    66  	LineNum int
    67  	SubTask []FlowTask
    68  }
    69  
    70  func NewSimpleTaskBlock(lineNum int) *SimpleTaskBlock {
    71  	return &SimpleTaskBlock{
    72  		LineNum: lineNum,
    73  		SubTask: make([]FlowTask, 0),
    74  	}
    75  }
    76  
    77  func (v *SimpleTaskBlock) AddTask(t FlowTask) {
    78  	v.SubTask = append(v.SubTask, t)
    79  }
    80  
    81  func (v *SimpleTaskBlock) DependentVariables() flowscript.StringSet {
    82  	vals := flowscript.NewStringSet()
    83  	for _, x := range v.SubTask {
    84  		vals.AddAll(x.DependentVariables())
    85  	}
    86  	return vals
    87  }
    88  
    89  func (v *SimpleTaskBlock) CreatedVariables() flowscript.StringSet {
    90  	vals := flowscript.NewStringSet()
    91  	for _, x := range v.SubTask {
    92  		vals.AddAll(x.CreatedVariables())
    93  	}
    94  	return vals
    95  }
    96  
    97  func (v *SimpleTaskBlock) Line() int {
    98  	return v.LineNum
    99  }
   100  
   101  func (v *SimpleTaskBlock) Subscribe(env flowscript.Environment, builder *ShellTaskBuilder) error {
   102  	for _, x := range v.SubTask {
   103  		err := x.Subscribe(env, builder)
   104  		if err != nil {
   105  			return err
   106  		}
   107  	}
   108  	return nil
   109  }
   110  
   111  type ForItem interface {
   112  	Values(flowscript.Environment) ([]flowscript.Value, error)
   113  }
   114  
   115  type StringForItem string
   116  
   117  func (s StringForItem) Values(flowscript.Environment) ([]flowscript.Value, error) {
   118  	return []flowscript.Value{flowscript.NewStringValue(string(s))}, nil
   119  }
   120  
   121  type EvaluableForItem struct {
   122  	Evaluable flowscript.Evaluable
   123  }
   124  
   125  func (s EvaluableForItem) Values(env flowscript.Environment) ([]flowscript.Value, error) {
   126  	value, err := s.Evaluable.Evaluate(env)
   127  	if err != nil {
   128  		return []flowscript.Value{}, err
   129  	}
   130  	if array, ok := value.(flowscript.ArrayValue); ok {
   131  		return array.AsRawArray(), nil
   132  	}
   133  	return []flowscript.Value{value}, nil
   134  }
   135  
   136  type ForFlowTask struct {
   137  	VariableName string
   138  	Items        []ForItem
   139  	LineNum      int
   140  	SubTask      []FlowTask
   141  }
   142  
   143  var spaceRegexp = regexp.MustCompile(`\s+`)
   144  
   145  func forItemSplit(data string) []string {
   146  	result := make([]string, 0)
   147  	pos := 0
   148  	for {
   149  		spacePos := spaceRegexp.FindStringIndex(data[pos:])
   150  		scriptPos := strings.Index(data[pos:], "{{")
   151  		if spacePos == nil && scriptPos < 0 {
   152  			break
   153  		}
   154  		if spacePos != nil && spacePos[0] == 0 {
   155  			//fmt.Printf("skip head space %s\n", strconv.Quote(data[:spacePos[1]]))
   156  			pos += spacePos[1]
   157  		} else if spacePos != nil && (spacePos[0] < scriptPos || scriptPos < 0) {
   158  			//fmt.Printf("add %s  %d %d\n", strconv.Quote(data[pos:pos+spacePos[0]]), pos, spacePos)
   159  			result = append(result, data[pos:pos+spacePos[0]])
   160  			pos += spacePos[1]
   161  		} else if scriptPos >= 0 && (spacePos == nil || scriptPos < spacePos[0]) {
   162  			//fmt.Printf("add %s  %d %d\n", strconv.Quote(data[pos:pos+scriptPos]), pos, scriptPos)
   163  			if scriptPos > 0 {
   164  				result = append(result, data[pos:pos+scriptPos])
   165  				pos += scriptPos
   166  			}
   167  
   168  			scriptEndPos := strings.Index(data[pos:], "}}")
   169  			if scriptEndPos < 0 {
   170  				result = append(result, data[pos:])
   171  				pos = len(data)
   172  				break
   173  			} else {
   174  				result = append(result, data[pos:pos+scriptEndPos+2])
   175  				pos += scriptEndPos + 2
   176  			}
   177  		} else {
   178  			fmt.Printf("bad case %d %d\n", scriptPos, spacePos)
   179  			break
   180  		}
   181  	}
   182  	if pos != len(data) {
   183  		result = append(result, data[pos:])
   184  	}
   185  	return result
   186  }
   187  
   188  func NewForFlowTask(variableName string, items string, lineNum int) (*ForFlowTask, error) {
   189  
   190  	rawItems := forItemSplit(items)
   191  	processedItems := []ForItem{}
   192  
   193  	for _, x := range rawItems {
   194  		if strings.HasPrefix(x, "{{") && strings.HasSuffix(x, "}}") {
   195  			parsed, err := flowscript.ParseScript(x[2 : len(x)-2])
   196  			if err != nil {
   197  				return nil, err
   198  			}
   199  			processedItems = append(processedItems, EvaluableForItem{parsed})
   200  		} else if strings.IndexRune(x, '*') >= 0 || strings.IndexRune(x, '?') >= 0 {
   201  			files, err := filepath.Glob(x)
   202  			if err != nil {
   203  				return nil, err
   204  			}
   205  			for _, y := range files {
   206  				processedItems = append(processedItems, StringForItem(y))
   207  			}
   208  
   209  		} else {
   210  			processedItems = append(processedItems, StringForItem(x))
   211  		}
   212  	}
   213  
   214  	return &ForFlowTask{
   215  		VariableName: variableName,
   216  		Items:        processedItems,
   217  		LineNum:      lineNum,
   218  		SubTask:      make([]FlowTask, 0),
   219  	}, nil
   220  }
   221  
   222  func (v *ForFlowTask) AddTask(t FlowTask) {
   223  	v.SubTask = append(v.SubTask, t)
   224  }
   225  
   226  func (v *ForFlowTask) DependentVariables() flowscript.StringSet {
   227  	vals := flowscript.NewStringSet()
   228  	for _, x := range v.SubTask {
   229  		vals.AddAll(x.DependentVariables())
   230  	}
   231  	return vals
   232  }
   233  
   234  func (v *ForFlowTask) CreatedVariables() flowscript.StringSet {
   235  	vals := flowscript.NewStringSet()
   236  	for _, x := range v.SubTask {
   237  		vals.AddAll(x.CreatedVariables())
   238  	}
   239  	return vals
   240  }
   241  
   242  func (v *ForFlowTask) Line() int {
   243  	return v.LineNum
   244  }
   245  
   246  func (v *ForFlowTask) Subscribe(env flowscript.Environment, builder *ShellTaskBuilder) error {
   247  	for _, x := range v.Items {
   248  		values, err := x.Values(env)
   249  		if err != nil {
   250  			return err
   251  		}
   252  		for _, z := range values {
   253  			env.Assign(v.VariableName, z)
   254  			for _, y := range v.SubTask {
   255  				err = y.Subscribe(env, builder)
   256  				if err != nil {
   257  					return err
   258  				}
   259  			}
   260  		}
   261  	}
   262  	return nil
   263  }
   264  
   265  type SingleShellTask struct {
   266  	LineNum           int
   267  	Script            string
   268  	embeddedPositions [][]int
   269  	evaluables        []flowscript.Evaluable
   270  }
   271  
   272  var embeddedFlowScriptBrace = regexp.MustCompile("{{[^}]*}}")
   273  
   274  func NewSingleShellTask(lineNum int, line string) (*SingleShellTask, error) {
   275  	positions := embeddedFlowScriptBrace.FindAllStringIndex(line, 100)
   276  	var evaluables []flowscript.Evaluable
   277  	for _, v := range positions {
   278  		sub := line[v[0]+2 : v[1]-1]
   279  		//fmt.Printf("sub: %s\n", sub)
   280  		ev, err := flowscript.ParseScript(sub)
   281  		if err != nil {
   282  			return nil, err
   283  		}
   284  		evaluables = append(evaluables, ev)
   285  	}
   286  	return &SingleShellTask{
   287  		LineNum:           lineNum,
   288  		Script:            line,
   289  		embeddedPositions: positions,
   290  		evaluables:        evaluables,
   291  	}, nil
   292  }
   293  
   294  func (t *SingleShellTask) DependentVariables() flowscript.StringSet {
   295  	vars := flowscript.NewStringSet()
   296  	for _, v := range t.evaluables {
   297  		vars.AddAll(flowscript.SearchDependentVariables(v))
   298  	}
   299  	return vars
   300  }
   301  
   302  func (t *SingleShellTask) CreatedVariables() flowscript.StringSet {
   303  	vars := flowscript.NewStringSet()
   304  	for _, v := range t.evaluables {
   305  		vars.AddAll(flowscript.SearchCreatedVariables(v))
   306  	}
   307  	return vars
   308  }
   309  
   310  func (t *SingleShellTask) EvaluatedShell(env flowscript.Environment) (string, error) {
   311  	var results []flowscript.Value = make([]flowscript.Value, len(t.evaluables))
   312  	for i, v := range t.evaluables {
   313  		val, err := v.Evaluate(env)
   314  		if err != nil {
   315  			return "", err
   316  		}
   317  		results[i] = val
   318  	}
   319  
   320  	line := t.Script
   321  	for i := len(results) - 1; i >= 0; i-- {
   322  		s, e := results[i].AsString()
   323  		if e != nil {
   324  			return "", e
   325  		}
   326  		line = line[:t.embeddedPositions[i][0]] + (s) + line[t.embeddedPositions[i][1]:]
   327  	}
   328  	return line, nil
   329  }
   330  
   331  func (t *SingleShellTask) Subscribe(env flowscript.Environment, builder *ShellTaskBuilder) error {
   332  	line, e := t.EvaluatedShell(env)
   333  	if e != nil {
   334  		return fmt.Errorf("Parse error at line %d: %s", t.LineNum, e.Error())
   335  	}
   336  
   337  	_, e = builder.CreateShellTask(t.LineNum, line)
   338  	if e != nil {
   339  		return fmt.Errorf(" error at line %d: %s", t.LineNum, e.Error())
   340  	}
   341  
   342  	return nil
   343  }
   344  
   345  func (t *SingleShellTask) Line() int {
   346  	return t.LineNum
   347  }
   348  
   349  var forBlockRegexp = regexp.MustCompile(`^for\s+(\w+)\s+in\s+(\S.+?)\s*(;?\s*do\s*)?$`)
   350  
   351  func ParseShellflowBlock(reader io.Reader, env *Environment) (FlowTaskBlock, string, error) {
   352  	workflowContent := bytes.NewBuffer(nil)
   353  	newReader := io.TeeReader(reader, workflowContent)
   354  
   355  	blockStack := []FlowTaskBlock{NewSimpleTaskBlock(1)}
   356  
   357  	scanner := bufio.NewScanner(newReader)
   358  	scanner.Split(bufio.ScanLines)
   359  	lineNum := 0
   360  	for scanner.Scan() {
   361  		lineNum++
   362  		line := strings.TrimSpace(scanner.Text())
   363  		var task FlowTask
   364  		var err error
   365  
   366  		if strings.HasPrefix(line, "for") {
   367  			submatch := forBlockRegexp.FindStringSubmatch(line)
   368  			if submatch == nil {
   369  				return nil, "", fmt.Errorf("Invalid for statement: %s", line)
   370  			}
   371  			//fmt.Printf("for %s in %s\n", submatch[1], submatch[2])
   372  
   373  			forTask, err := NewForFlowTask(submatch[1], submatch[2], lineNum)
   374  			if err != nil {
   375  				return nil, "", err
   376  			}
   377  			blockStack[len(blockStack)-1].AddTask(forTask)
   378  			blockStack = append(blockStack, forTask)
   379  			continue
   380  		} else if strings.HasPrefix(line, "done") {
   381  			if line == "done" {
   382  				blockStack = blockStack[0 : len(blockStack)-1]
   383  			} else {
   384  				return nil, "", fmt.Errorf("Invalid done statment: %s", line)
   385  			}
   386  			continue
   387  		} else if strings.HasPrefix(line, "#%") {
   388  			task, err = NewSingleFlowScriptTask(lineNum, line)
   389  		} else if strings.HasPrefix(line, "#") || len(line) == 0 {
   390  			continue
   391  		} else {
   392  			task, err = NewSingleShellTask(lineNum, line)
   393  		}
   394  
   395  		if err != nil {
   396  			return nil, "", err
   397  		}
   398  
   399  		blockStack[len(blockStack)-1].AddTask(task)
   400  
   401  	}
   402  	if e := scanner.Err(); e != nil {
   403  		return nil, "", e
   404  	}
   405  
   406  	return blockStack[0], string(workflowContent.Bytes()), nil
   407  }
   408  
   409  func ParseShellflow(reader io.Reader, env *Environment, param map[string]interface{}) (*ShellTaskBuilder, error) {
   410  	env.parameters = param
   411  	for key, value := range param {
   412  		switch value.(type) {
   413  		case string:
   414  			//fmt.Printf("key = %s   string value = %s\n", key, value)
   415  			env.flowEnvironment.Assign(key, flowscript.NewStringValue(value.(string)))
   416  		case float64:
   417  			//fmt.Printf("key = %s   numeric value = %f\n", key, value)
   418  			floatValue := value.(float64)
   419  			env.flowEnvironment.Assign(key, flowscript.NewIntValue(int64(floatValue)))
   420  		default:
   421  			return nil, fmt.Errorf("Unknown parameter type %s = %s", key, value)
   422  		}
   423  	}
   424  
   425  	builder, err := NewShellTaskBuilder()
   426  	if err != nil {
   427  		return nil, err
   428  	}
   429  
   430  	block, content, err := ParseShellflowBlock(reader, env)
   431  	if err != nil {
   432  		return nil, err
   433  	}
   434  
   435  	err = block.Subscribe(env.flowEnvironment, builder)
   436  	if err != nil {
   437  		return nil, err
   438  	}
   439  
   440  	builder.WorkflowContent = content
   441  
   442  	return builder, nil
   443  }