v0.1.0 28f9d6e

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
109func hasStatusFunction(n expr.Node) bool {
110	switch n := n.(type) {
111	case *expr.FunctionCall:
112		if statusFunctions[strings.ToLower(n.Name.Lexeme)] {
113			return true
114		}
115		for _, a := range n.Args {
116			if hasStatusFunction(a) {
117				return true
118			}
119		}
120	case *expr.Binary:
121		return hasStatusFunction(n.Left) || hasStatusFunction(n.Right)
122	case *expr.Unary:
123		return hasStatusFunction(n.Expr)
124	case *expr.Logical:
125		for _, a := range n.Args {
126			if hasStatusFunction(a) {
127				return true
128			}
129		}
130	case *expr.Grouping:
131		return hasStatusFunction(n.Group)
132	case *expr.IndexAccess:
133		return hasStatusFunction(n.Expr) || hasStatusFunction(n.Index)
134	}
135	return false
136}
137
138// convertIf converts an if condition: the result always contains a status
139// function, wrapping the condition as "success() && (cond)" when it has none.
140// A condition that does not parse is recorded as an error and returns nil.
141func (c *converter) convertIf(t template.Token, defName string) *template.BasicExpression {
142	scalar := asScalar(t, "if condition")
143	def := t.Definition()
144	if def == nil {
145		def = c.schema.Info(defName)
146	}
147	var condition string
148	switch s := scalar.(type) {
149	case *template.BasicExpression:
150		condition = s.Expression
151	default:
152		condition = strings.TrimSpace(asString(scalar, "if condition").Value)
153	}
154	final := "success()"
155	if condition != "" {
156		final = condition
157		if node, err := expr.Parse(condition, def.NamedContexts(), def.Functions()); err == nil && !hasStatusFunction(node) {
158			final = "success() && (" + condition + ")"
159		}
160	}
161	if _, err := expr.Parse(final, def.NamedContexts(), def.Functions()); err != nil {
162		c.ctx.ErrorAt(t, err.Error())
163		return nil
164	}
165	return template.NewExpression(origin(t), final, def)
166}
167
168// defaultIf is the condition of a job or step that has none.
169func (c *converter) defaultIf(defName string) *template.BasicExpression {
170	return template.NewExpression(template.Origin{}, "success()", c.schema.Info(defName))
171}

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