main 98594d0

data.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package expr
4
5import "math"
6
7// Kind is the type of a [Value].
8type Kind int
9
10// The kinds of value an expression can produce.
11const (
12	KindNull Kind = iota
13	KindBoolean
14	KindNumber
15	KindString
16	KindArray
17	KindDictionary
18)
19
20// String returns the name GitHub uses for the kind when a non-primitive
21// value is converted to a string: "Array" and "Object" for the two compound
22// kinds.
23func (k Kind) String() string {
24	switch k {
25	case KindNull:
26		return "Null"
27	case KindBoolean:
28		return "Boolean"
29	case KindNumber:
30		return "Number"
31	case KindString:
32		return "String"
33	case KindArray:
34		return "Array"
35	case KindDictionary:
36		return "Object"
37	}
38	return "unknown"
39}
40
41// Value is the result of an expression or an element of a context: one of
42// [Null], [Boolean], [Number], [String], [*Array] and [*Dictionary]. The set
43// is closed. A nil Value, or a nil *Array or *Dictionary, behaves as [Null].
44type Value interface {
45	// Kind reports the type of the value.
46	Kind() Kind
47	isValue()
48}
49
50// Null is the null value.
51type Null struct{}
52
53// Boolean is a true or false value.
54type Boolean bool
55
56// Number is a double-precision floating-point number. NaN and the infinities
57// are legal values.
58type Number float64
59
60// String is a text value.
61type String string
62
63func (Null) Kind() Kind        { return KindNull }
64func (Boolean) Kind() Kind     { return KindBoolean }
65func (Number) Kind() Kind      { return KindNumber }
66func (String) Kind() Kind      { return KindString }
67func (*Array) Kind() Kind      { return KindArray }
68func (*Dictionary) Kind() Kind { return KindDictionary }
69
70func (Null) isValue()        {}
71func (Boolean) isValue()     {}
72func (Number) isValue()      {}
73func (String) isValue()      {}
74func (*Array) isValue()      {}
75func (*Dictionary) isValue() {}
76
77// Array is an ordered list of values. Two arrays are equal in an expression
78// only if they are the same *Array.
79type Array struct {
80	items []Value
81	// filtered is set on arrays produced by the * filter ("a.*", "a[*]"),
82	// which flatten one level when indexed again ("a.*.b").
83	filtered bool
84}
85
86// NewArray returns an array holding items.
87func NewArray(items ...Value) *Array {
88	return &Array{items: append([]Value(nil), items...)}
89}
90
91// Len returns the number of elements.
92func (a *Array) Len() int {
93	if a == nil {
94		return 0
95	}
96	return len(a.items)
97}
98
99// At returns the element at index i, or [Null] when i is out of range.
100func (a *Array) At(i int) Value {
101	if a == nil || i < 0 || i >= len(a.items) {
102		return Null{}
103	}
104	return normalize(a.items[i])
105}
106
107// Items returns a copy of the elements.
108func (a *Array) Items() []Value {
109	if a == nil {
110		return nil
111	}
112	out := make([]Value, len(a.items))
113	for i, v := range a.items {
114		out[i] = normalize(v)
115	}
116	return out
117}
118
119// Append adds v at the end of the array.
120func (a *Array) Append(v Value) { a.items = append(a.items, v) }
121
122// Filtered reports whether the array was produced by a * filter.
123func (a *Array) Filtered() bool { return a != nil && a.filtered }
124
125// Pair is a key and its value in a [Dictionary].
126type Pair struct {
127	Key   string
128	Value Value
129}
130
131// Dictionary is an ordered map from string keys to values. Keys are looked up
132// case-insensitively and keep the spelling and position of their first
133// insertion. Two dictionaries are equal in an expression only if they are
134// the same *Dictionary.
135type Dictionary struct {
136	keys  []string
137	vals  []Value
138	index map[string]int
139}
140
141// NewDictionary returns an empty dictionary.
142func NewDictionary() *Dictionary {
143	return &Dictionary{index: map[string]int{}}
144}
145
146// Add inserts key with value v and reports whether it did. When a key that
147// differs only by case is already present it does nothing and returns false:
148// the first insertion wins.
149func (d *Dictionary) Add(key string, v Value) bool {
150	if d.index == nil {
151		d.index = map[string]int{}
152	}
153	folded := foldKey(key)
154	if _, ok := d.index[folded]; ok {
155		return false
156	}
157	d.index[folded] = len(d.vals)
158	d.keys = append(d.keys, key)
159	d.vals = append(d.vals, v)
160	return true
161}
162
163// set stores v under key: a key that differs only by case from an existing one
164// replaces its value and keeps its spelling and position.
165func (d *Dictionary) set(key string, v Value) {
166	if d.index == nil {
167		d.index = map[string]int{}
168	}
169	if i, ok := d.index[foldKey(key)]; ok {
170		d.vals[i] = v
171		return
172	}
173	d.Add(key, v)
174}
175
176// Get returns the value stored under key, compared case-insensitively.
177func (d *Dictionary) Get(key string) (Value, bool) {
178	if d == nil {
179		return nil, false
180	}
181	i, ok := d.index[foldKey(key)]
182	if !ok {
183		return nil, false
184	}
185	return normalize(d.vals[i]), true
186}
187
188// Len returns the number of entries.
189func (d *Dictionary) Len() int {
190	if d == nil {
191		return 0
192	}
193	return len(d.vals)
194}
195
196// Keys returns the keys in insertion order.
197func (d *Dictionary) Keys() []string {
198	if d == nil {
199		return nil
200	}
201	return append([]string(nil), d.keys...)
202}
203
204// Values returns the values in insertion order.
205func (d *Dictionary) Values() []Value {
206	if d == nil {
207		return nil
208	}
209	out := make([]Value, len(d.vals))
210	for i, v := range d.vals {
211		out[i] = normalize(v)
212	}
213	return out
214}
215
216// Pairs returns the entries in insertion order.
217func (d *Dictionary) Pairs() []Pair {
218	if d == nil {
219		return nil
220	}
221	out := make([]Pair, len(d.keys))
222	for i, k := range d.keys {
223		out[i] = Pair{Key: k, Value: normalize(d.vals[i])}
224	}
225	return out
226}
227
228// normalize maps nil values to Null so that callers can switch on Kind.
229func normalize(v Value) Value {
230	switch x := v.(type) {
231	case nil:
232		return Null{}
233	case *Array:
234		if x == nil {
235			return Null{}
236		}
237	case *Dictionary:
238		if x == nil {
239			return Null{}
240		}
241	}
242	return v
243}
244
245// isPrimitive reports whether v is a Null, Boolean, Number or String.
246func isPrimitive(v Value) bool {
247	switch normalize(v).Kind() {
248	case KindNull, KindBoolean, KindNumber, KindString:
249		return true
250	}
251	return false
252}
253
254// toNumber converts v to a number the way GitHub does: null is 0, booleans
255// are 1 and 0, strings are parsed like JavaScript's Number(), and arrays and
256// dictionaries are NaN.
257func toNumber(v Value) float64 {
258	switch x := normalize(v).(type) {
259	case Null:
260		return 0
261	case Boolean:
262		if x {
263			return 1
264		}
265		return 0
266	case Number:
267		return float64(x)
268	case String:
269		return parseNumber(string(x))
270	}
271	return nan
272}
273
274// CoerceString converts v to the string GitHub shows for it: null is "", booleans
275// are "true" and "false", numbers are formatted with at most 15 decimals
276// ("0.1" for 0.1+0.2) and arrays and dictionaries are "Array" and "Object".
277func CoerceString(v Value) string {
278	switch x := normalize(v).(type) {
279	case Null:
280		return ""
281	case Boolean:
282		if x {
283			return "true"
284		}
285		return "false"
286	case Number:
287		return numberToCoercedString(float64(x))
288	case String:
289		return string(x)
290	default:
291		return x.Kind().String()
292	}
293}
294
295// Truthy reports whether v is true when used as a condition. Null, false, 0,
296// NaN and the empty string are falsy; everything else, including every array
297// and dictionary, is truthy.
298func Truthy(v Value) bool {
299	switch x := normalize(v).(type) {
300	case Null:
301		return false
302	case Boolean:
303		return bool(x)
304	case Number:
305		return x != 0 && !math.IsNaN(float64(x))
306	case String:
307		return len(x) != 0
308	}
309	return true
310}

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