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 }