main 98594d0

util.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package action
4
5import (
6	"fmt"
7	"strings"
8	"unicode"
9
10	"bnema.dev/ermite-actions/expr"
11	"bnema.dev/ermite-actions/template"
12)
13
14// asciiEqualFold is String.Equals(a, b, OrdinalIgnoreCase) when b is an ASCII
15// literal ("docker", "default"): .NET's simple case mapping folds no non-ASCII
16// character into an ASCII one, so only A-Z and a-z can match. It is wrong for
17// two arbitrary strings ("é" and "É" are equal under OrdinalIgnoreCase); use
18// ordinalEqual for those.
19func asciiEqualFold(a, b string) bool {
20	if len(a) != len(b) {
21		return false
22	}
23	for i := 0; i < len(a); i++ {
24		x, y := a[i], b[i]
25		if x == y {
26			continue
27		}
28		if x >= 'a' && x <= 'z' {
29			x -= 'a' - 'A'
30		}
31		if y >= 'a' && y <= 'z' {
32			y -= 'a' - 'A'
33		}
34		if x != y {
35			return false
36		}
37	}
38	return true
39}
40
41// utf16Len is String.Length.
42func utf16Len(s string) int {
43	n := 0
44	for _, r := range s {
45		if r >= 0x10000 {
46			n += 2
47		} else {
48			n++
49		}
50	}
51	return n
52}
53
54// isNullOrWhiteSpace is String.IsNullOrWhiteSpace.
55func isNullOrWhiteSpace(s string) bool { return strings.TrimFunc(s, unicode.IsSpace) == "" }
56
57// The .NET exception classes the runner's loader raises.
58const (
59	exArgument     = "System.ArgumentException"
60	exArgumentNull = "System.ArgumentNullException"
61	exArgumentRng  = "System.ArgumentOutOfRangeException"
62	exNotSupported = "System.NotSupportedException"
63	exNullRef      = "System.NullReferenceException"
64	exInvalidOp    = "System.InvalidOperationException"
65	exTemplate     = "GitHub.DistributedTask.ObjectTemplating.TemplateValidationException"
66)
67
68// thrown stands for a .NET exception: the loader panics with it where the
69// runner throws, and Parse records it where the runner catches it.
70type thrown struct{ class, msg string }
71
72func throwf(class, format string, a ...any) { panic(thrown{class, fmt.Sprintf(format, a...)}) }
73
74// typeName is the CLR type name of a token, "" for null, as in the messages
75// of the runner's Assert methods.
76func typeName(t template.Token) string {
77	if t == nil {
78		return ""
79	}
80	return t.Kind().String()
81}
82
83func assertFail(t template.Token, desc, want string) {
84	throwf(exArgument, "Unexpected type '%s' encountered while reading '%s'. The type '%s' was expected.", typeName(t), desc, want)
85}
86
87func assertString(t template.Token, desc string) *template.String {
88	if s, ok := t.(*template.String); ok {
89		return s
90	}
91	assertFail(t, desc, "StringToken")
92	return nil
93}
94
95func assertMapping(t template.Token, desc string) *template.Mapping {
96	if m, ok := t.(*template.Mapping); ok {
97		return m
98	}
99	assertFail(t, desc, "MappingToken")
100	return nil
101}
102
103func assertSequence(t template.Token, desc string) *template.Sequence {
104	if s, ok := t.(*template.Sequence); ok {
105		return s
106	}
107	assertFail(t, desc, "SequenceToken")
108	return nil
109}
110
111func assertBoolean(t template.Token, desc string) *template.Boolean {
112	if b, ok := t.(*template.Boolean); ok {
113		return b
114	}
115	assertFail(t, desc, "BooleanToken")
116	return nil
117}
118
119func assertScalar(t template.Token, desc string) template.Token {
120	if t != nil && template.IsScalar(t) {
121		return t
122	}
123	assertFail(t, desc, "ScalarToken")
124	return nil
125}
126
127// trimSpaceJS is String.prototype.trim.
128func trimSpaceJS(s string) string {
129	return strings.TrimFunc(s, func(r rune) bool { return unicode.IsSpace(r) || r == '\ufeff' })
130}
131
132// ordinalEqual is String.Equals(a, b, StringComparison.OrdinalIgnoreCase).
133func ordinalEqual(a, b string) bool { return expr.OrdinalFold(a) == expr.OrdinalFold(b) }

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