main 98594d0

convert_misc.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package workflow
4
5import (
6	"strings"
7
8	"bnema.dev/ermite-actions/expr"
9	"bnema.dev/ermite-actions/template"
10)
11
12// convertConcurrency validates a concurrency token. Expressions are skipped.
13func (c *converter) convertConcurrency(t template.Token) {
14	if template.IsExpression(t) {
15		return
16	}
17	if _, ok := t.(*template.String); ok {
18		return
19	}
20	m := asMap(t, "concurrency group")
21	for _, p := range m.Pairs {
22		name := asString(p.Key, "concurrency group key")
23		if template.IsExpression(p.Key) || template.IsExpression(p.Value) {
24			continue
25		}
26		switch name.Value {
27		case "group":
28			asString(p.Value, "concurrency group")
29		case "cancel-in-progress":
30			asBool(p.Value, "cancel-in-progress")
31		case "queue":
32			asString(p.Value, "queue")
33		default:
34			c.ctx.Errorf(name, "Invalid property name: %s", name.Value)
35		}
36	}
37}
38
39// convertEnvironment validates a job environment token.
40func (c *converter) convertEnvironment(t template.Token) {
41	if template.IsExpression(t) || template.IsScalar(t) {
42		return
43	}
44	m := asMap(t, "job environment")
45	for _, p := range m.Pairs {
46		name := asString(p.Key, "job environment key")
47		if template.IsExpression(p.Key) || template.IsExpression(p.Value) {
48			continue
49		}
50		switch name.Value {
51		case "name":
52			asScalar(p.Value, "job environment name key")
53		case "deployment":
54			asBool(p.Value, "job environment deployment")
55		}
56	}
57}
58
59// convertRunsOn validates a runs-on token.
60func (c *converter) convertRunsOn(t template.Token) {
61	labels := runsOnLabels(t)
62	_ = labels
63	m, ok := t.(*template.Mapping)
64	if !ok {
65		return
66	}
67	for _, p := range m.Pairs {
68		key := asString(p.Key, "job runs-on property name")
69		switch key.Value {
70		case "group":
71			if template.IsExpression(p.Value) {
72				continue
73			}
74			group := asString(p.Value, "job runs-on group name").Value
75			if msg := checkGroup(group); msg != "" {
76				c.ctx.ErrorAt(p.Value, msg)
77			}
78		case "labels":
79			runsOnLabels(p.Value)
80		}
81	}
82}
83
84// runsOnLabels collects the literal labels of a runs-on token, asserting that
85// the items are strings.
86func runsOnLabels(t template.Token) []string {
87	if template.IsExpression(t) {
88		return nil
89	}
90	switch t := t.(type) {
91	case *template.String:
92		return []string{t.Value}
93	case *template.Sequence:
94		var out []string
95		for _, it := range t.Items {
96			if template.IsExpression(it) {
97				continue
98			}
99			out = append(out, asString(it, "job runs-on label sequence item").Value)
100		}
101		return out
102	}
103	return nil
104}
105
106// statusFunctions are the functions that make a condition a status check.
107var statusFunctions = map[string]bool{"success": true, "failure": true, "cancelled": true, "always": true}
108
109// HasStatusFunction reports whether an expression calls success, failure,
110// cancelled or always anywhere in its syntax tree, which decides whether an
111// "if" condition is wrapped as "success() && (cond)".
112func HasStatusFunction(n expr.Node) bool { return hasStatusFunction(n) }
113
114func hasStatusFunction(n expr.Node) bool {
115	switch n := n.(type) {
116	case *expr.FunctionCall:
117		if statusFunctions[strings.ToLower(n.Name.Lexeme)] {
118			return true
119		}
120		for _, a := range n.Args {
121			if hasStatusFunction(a) {
122				return true
123			}
124		}
125	case *expr.Binary:
126		return hasStatusFunction(n.Left) || hasStatusFunction(n.Right)
127	case *expr.Unary:
128		return hasStatusFunction(n.Expr)
129	case *expr.Logical:
130		for _, a := range n.Args {
131			if hasStatusFunction(a) {
132				return true
133			}
134		}
135	case *expr.Grouping:
136		return hasStatusFunction(n.Group)
137	case *expr.IndexAccess:
138		return hasStatusFunction(n.Expr) || hasStatusFunction(n.Index)
139	}
140	return false
141}
142
143// convertIf converts an if condition: the result always contains a status
144// function, wrapping the condition as "success() && (cond)" when it has none.
145// A condition that does not parse is recorded as an error and returns nil.
146func (c *converter) convertIf(t template.Token, defName string) *template.BasicExpression {
147	scalar := asScalar(t, "if condition")
148	def := t.Definition()
149	if def == nil {
150		def = c.schema.Info(defName)
151	}
152	var condition string
153	switch s := scalar.(type) {
154	case *template.BasicExpression:
155		condition = s.Expression
156	default:
157		condition = strings.TrimSpace(asString(scalar, "if condition").Value)
158	}
159	final := "success()"
160	if condition != "" {
161		final = condition
162		if node, err := expr.Parse(condition, def.NamedContexts(), def.Functions()); err == nil && !hasStatusFunction(node) {
163			final = "success() && (" + condition + ")"
164		}
165	}
166	if _, err := expr.Parse(final, def.NamedContexts(), def.Functions()); err != nil {
167		c.ctx.ErrorAt(t, err.Error())
168		return nil
169	}
170	return template.NewExpression(origin(t), final, def)
171}
172
173// defaultIf is the condition of a job or step that has none.
174func (c *converter) defaultIf(defName string) *template.BasicExpression {
175	return template.NewExpression(template.Origin{}, "success()", c.schema.Info(defName))
176}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 3

↑↓ moveenter openesc close