1// SPDX-License-Identifier: MIT
2
3package workflow
4
5import (
6 "strings"
7
8 "bnema.dev/ermite-actions/template"
9)
10
11// convertSteps is a port of convertSteps: steps that fail to convert are
12// dropped, ids written in the file are checked, and the others get a
13// generated id.
14func (c *converter) convertSteps(t template.Token) []*Step {
15 seq, ok := t.(*template.Sequence)
16 if !ok {
17 c.ctx.ErrorAt(t, "Invalid format for steps")
18 return nil
19 }
20 ids := newIDBuilder()
21 var result []*Step
22 for _, it := range seq.Items {
23 c.handle(seq, func() {
24 if s := c.convertStep(ids, it); s != nil {
25 result = append(result, s)
26 }
27 })
28 }
29 for _, s := range result {
30 if s.ID != "" {
31 continue
32 }
33 id := ""
34 if s.Uses != nil {
35 id = actionStepID(s.Uses.Value)
36 }
37 if id == "" {
38 id = "run"
39 }
40 ids.appendSegment("__" + id)
41 s.ID = ids.build()
42 }
43 return result
44}
45
46func (c *converter) convertStep(ids *idBuilder, t template.Token) *Step {
47 m := asMap(t, "steps item")
48 s := &Step{Pos: t.Pos()}
49 var uses *template.String
50 var run template.Token
51 for _, p := range m.Pairs {
52 key := asString(p.Key, "steps item key")
53 v := p.Value
54 switch key.Value {
55 case "id":
56 id := asString(v, "steps item id")
57 if msg := ids.tryAddKnownID(id.Value); msg != "" {
58 c.ctx.ErrorAt(id, msg)
59 }
60 s.ID = id.Value
61 s.Explicit = id.Value != ""
62 case "name":
63 s.Name = asScalar(v, "steps item name")
64 case "run":
65 run = asScalar(v, "steps item run")
66 case "uses":
67 uses = asString(v, "steps item uses")
68 case "env":
69 s.Env = asMap(v, "step env")
70 case "if":
71 s.If = c.convertIf(v, "step-if")
72 case "continue-on-error":
73 if !template.IsExpression(v) {
74 asBool(v, "steps item continue-on-error")
75 } else {
76 asScalar(v, "steps item continue-on-error")
77 }
78 s.ContinueOnError = v
79 case "shell":
80 s.Shell = v
81 case "working-directory":
82 s.WorkingDirectory = v
83 case "timeout-minutes":
84 s.TimeoutMinutes = v
85 case "with":
86 if wm, ok := v.(*template.Mapping); ok {
87 s.With = wm
88 }
89 }
90 }
91 switch {
92 case run != nil:
93 s.Run = run
94 case uses != nil:
95 s.Uses = uses
96 default:
97 c.ctx.ErrorAt(t, "Expected one of uses or run to be defined")
98 return nil
99 }
100 if s.If == nil {
101 s.If = c.defaultIf("step-if")
102 }
103 return s
104}
105
106// actionStepID is the id segment generated for a uses step without an id.
107func actionStepID(uses string) string {
108 switch {
109 case strings.HasPrefix(uses, "docker://"):
110 return uses[len("docker://"):]
111 case strings.HasPrefix(uses, "./"), strings.HasPrefix(uses, ".\\"), strings.HasPrefix(uses, "$/"):
112 return "self"
113 }
114 segments := strings.Split(uses, "@")
115 if len(segments) != 2 {
116 return ""
117 }
118 path := strings.FieldsFunc(segments[0], func(r rune) bool { return r == '/' || r == '\\' })
119 if len(path) >= 2 && path[0] != "" && path[1] != "" && segments[1] != "" {
120 return path[0] + "/" + path[1]
121 }
122 return ""
123}