v0.2.1 c0399be

util.go RawHistoryFolder

1// package util implements some utility functions for parsing/converting gir files
2// TODO: Maybe some of this can more easily be done with regexes?
3//
4//	But using regexes introduces 2 problems :^)
5package util
6
7import (
8	"path/filepath"
9	"strings"
10)
11
12var (
13	// Variable names that should not be dereferenced when using ConvertPtr() in handlePtr mode
14	// TODO: This was mostly discovered via trial and error, and might point towards issues in
15	// the GIR files
16	specialConvertPtrVars = []string{
17		"ModelVar",
18		"TreeModelVar",
19		"OutChildVar",
20		"ChildVar",
21	}
22)
23
24// delimToCamel to camel converts a string with parts separated by `delim` to CamelCase
25func delimToCamel(s string, delim string) string {
26	var sb strings.Builder
27	words := strings.Split(s, delim)
28	for _, w := range words {
29		sb.WriteString(strings.Title(w))
30	}
31	return sb.String()
32}
33
34// StarsInFront adds pointer characters (*, stars) in front of the type
35// if there is a slice in front
36// we need to add the slice and then afterwards the stars
37// e.g. [2]foo becomes [2]*foo with n=1
38func StarsInFront(str string, n int) string {
39	b := strings.Index(str, "[")
40	e := strings.Index(str, "]")
41	stars := strings.Repeat("*", n)
42	if b == 0 && e != -1 {
43		return str[b:e+1] + stars + str[e+1:]
44	}
45	return stars + str
46}
47
48// SnakeToCamel converts hello_world to HelloWorld
49func SnakeToCamel(s string) string {
50	return delimToCamel(s, "_")
51}
52
53// DashToCamel converts hello-world to HelloWorld
54func DashToCamel(s string) string {
55	return delimToCamel(s, "-")
56}
57
58// RemoveSnakePrefix removes `prefix` from string `s` if that prefix ise separated with a _
59// it removes lowercase or all u
60func RemoveSnakePrefix(s string, prefix string) string {
61	parts := strings.Split(s, "_")
62	if len(parts) <= 1 {
63		return s
64	}
65	if strings.EqualFold(parts[0], prefix) {
66		parts = parts[1:]
67	}
68	return strings.Join(parts, "_")
69}
70
71// ReplaceExtension replaces an extension from filename with ext
72// the extension is found by splitting on "." and taking the last part
73func ReplaceExtension(filename string, ext string) string {
74	splt := strings.Split(filename, ".")
75	if len(splt) == 1 {
76		return filename
77	}
78	splt[len(splt)-1] = ext
79	return strings.Join(splt, ".")
80}
81
82func PrefixValue(val, prefix string) string {
83	// if it's a slice, it has to come first
84	b := strings.Index(val, "[")
85	e := strings.Index(val, "]")
86	if b == 0 && e != -1 {
87		return val[b:e+1] + prefix + val[e+1:]
88	}
89	return prefix + val
90}
91
92func AddNamespace(val, ns string) string {
93	if ns == "" || strings.Count(val, ".") >= 1 {
94		return val
95	}
96	// Don't add namespace to Go builtin types
97	if isGoBuiltinType(val) {
98		return val
99	}
100	return PrefixValue(val, ns+".")
101}
102
103// goBuiltinTypes contains Go builtin types that should never be namespace-prefixed
104var goBuiltinTypes = map[string]bool{
105	"bool": true, "byte": true, "complex64": true, "complex128": true,
106	"error": true, "float32": true, "float64": true, "int": true,
107	"int8": true, "int16": true, "int32": true, "int64": true,
108	"rune": true, "string": true, "uint": true, "uint8": true,
109	"uint16": true, "uint32": true, "uint64": true, "uintptr": true,
110}
111
112// isGoBuiltinType checks if the given type (after stripping pointers and slices) is a Go builtin
113func isGoBuiltinType(gotype string) bool {
114	// Strip pointer prefixes
115	baseType := strings.TrimLeft(gotype, "*")
116	// Strip slice prefix
117	baseType = strings.TrimPrefix(baseType, "[]")
118	// Strip any remaining pointers (e.g., *[]byte -> []byte -> byte)
119	baseType = strings.TrimLeft(baseType, "*")
120	return goBuiltinTypes[baseType]
121}
122
123// NormalizeNamespace converts a type to one that always includes a lowercase namespace
124// if no namespace is found, it adds `ns`, unless if strip is True then namespaces always equaling `ns` will be removed
125func NormalizeNamespace(ns string, gotype string, strip bool) string {
126	if ns == "" {
127		return ""
128	}
129
130	// Check if this is a Go builtin type before any modifications
131	// Note: gotype here may still have * prefixes which are trimmed below
132	trimmedForCheck := strings.Trim(gotype, "*")
133	if isGoBuiltinType(trimmedForCheck) {
134		return trimmedForCheck
135	}
136
137	gotype = strings.Trim(gotype, "*")
138	splt := strings.Split(gotype, ".")
139	if len(splt) <= 1 {
140		splt = append([]string{ns}, splt...)
141	}
142	splt[0] = strings.ToLower(splt[0])
143	if strip && splt[0] == strings.ToLower(ns) {
144		splt = splt[1:]
145	}
146	return strings.Join(splt, ".")
147}
148
149// TranslateFilename translates a file path by renaming the file to a go suitable file
150func TranslateFilename(filename string) string {
151	if filename == "" {
152		return "main.go"
153	}
154
155	b := filepath.Base(filename)
156	return ReplaceExtension(b, "go")
157}
158
159func ConvertArgs(a []string) string {
160	return strings.Join(a, ", ")
161}
162
163func ConvertArgsComma(a []string) string {
164	if len(a) == 0 {
165		return ""
166	}
167	return ", " + strings.Join(a, ", ")
168}
169
170func convertCallbackArgs(a []string, prependComma, skipEmpty, skipErr, handlePtr bool) string {
171	var validArgs []string
172	for _, arg := range a {
173		if skipEmpty && arg == "" {
174			continue
175		}
176		if skipErr && arg == "&cerr" {
177			continue
178		}
179
180		if strings.Contains(arg, "{Ptr:") {
181			if !handlePtr {
182				// For ConvertCallbackArgs: remove * prefix and add &
183				arg = strings.TrimPrefix(arg, "*")
184			}
185			validArgs = append(validArgs, "&"+arg)
186		} else if strings.Contains(arg, "ConvertPtr(") && handlePtr {
187			isSpecialVar := false
188			for _, specialVar := range specialConvertPtrVars {
189				if strings.Contains(arg, specialVar) {
190					isSpecialVar = true
191
192					break
193				}
194			}
195
196			if isSpecialVar {
197				validArgs = append(validArgs, arg)
198			} else {
199				validArgs = append(validArgs, "*"+arg)
200			}
201		} else {
202			validArgs = append(validArgs, arg)
203		}
204	}
205
206	if len(validArgs) == 0 {
207		return ""
208	}
209
210	result := strings.Join(validArgs, ", ")
211	if prependComma {
212		return ", " + result
213	}
214
215	return result
216}
217
218func ConvertCallbackArgs(a []string) string {
219	return convertCallbackArgs(a, false, false, false, false)
220}
221
222func ConvertArgsCommaDeref(a []string) string {
223	return convertCallbackArgs(a, true, true, false, true)
224}
225
226func ConvertArgsDeref(a []string) string {
227	return convertCallbackArgs(a, false, true, false, true)
228}
229
230func ConvertCallbackArgsNoErr(a []string) string {
231	return convertCallbackArgs(a, false, true, true, true)
232}
233
234// ConstructorName returns a Go friendly constructor name given the raw constructor name `name` and the class/record name `outer`
235func ConstructorName(name string, outer string) string {
236	cname := SnakeToCamel(name)
237	// construct the final constructor name
238	// for example if we have gtk_builder
239	// gtk_builder_new_from_file
240	// cname will be NewFromFile
241	// we convert it to NewBuilderFromFile
242	if strings.HasPrefix(cname, "New") {
243		return "New" + outer + cname[3:]
244	}
245	// the default is just a concatenation if the constructor doesn't start with New
246	return outer + cname
247}
248
249// PropertyScalarSet returns the code for setting a scalar property value
250func PropertyScalarSet(notGObject bool, gvalueType, setMethod string) string {
251	prefix := ""
252	if notGObject {
253		prefix = "gobject."
254	}
255	if setMethod == "SetString" {
256		return prefix + gvalueType + ")\n\tv." + setMethod + "(&value"
257	}
258	return prefix + gvalueType + ")\n\tv." + setMethod + "(value"
259}
260
261// PropertyScalarGet returns the code for getting a scalar property value
262func PropertyScalarGet(getMethod string) string {
263	return "v." + getMethod + "()"
264}
265
266// PropertyVectorSet returns the code for setting a vector property value
267func PropertyVectorSet(notGLib bool, goType string) string {
268	prefix := ""
269	if notGLib {
270		prefix = "glib."
271	}
272
273	switch goType {
274	case "[]string":
275		return prefix + `StrvGetType())
276	v.SetBoxed(uintptr(unsafe.Pointer(core.ByteSlice(value))))`
277	case "[]byte":
278		return prefix + `ByteArrayGLibType())
279	v.SetBoxed(uintptr(unsafe.Pointer(&value[0])))`
280	case "[]uintptr":
281		return prefix + `PtrArrayGLibType())
282	v.SetBoxed(uintptr(unsafe.Pointer(&value[0])))`
283	default:
284		return prefix + `StrvGetType())
285	v.SetBoxed(uintptr(unsafe.Pointer(core.ByteSlice(value))))`
286	}
287}
288
289// PropertyVectorGet returns the code for getting a vector property value
290func PropertyVectorGet(goType string) string {
291	switch goType {
292	case "[]string":
293		return `return core.GoStringSlice(v.GetBoxed())`
294	case "[]byte":
295		return `ptr := v.GetBoxed()
296	if ptr == 0 {
297		return nil
298	}
299	// GByteArray layout: { guint8 *data; guint len; }
300	dataPtr := *(*uintptr)(unsafe.Pointer(ptr))
301	length := *(*uint32)(unsafe.Pointer(ptr + unsafe.Sizeof(uintptr(0))))
302	if length == 0 || dataPtr == 0 {
303		return nil
304	}
305	return unsafe.Slice((*byte)(unsafe.Pointer(dataPtr)), length)`
306	case "[]uintptr":
307		return `ptr := v.GetBoxed()
308	if ptr == 0 {
309		return nil
310	}
311	// GPtrArray layout: { gpointer *pdata; guint len; }
312	dataPtr := *(*uintptr)(unsafe.Pointer(ptr))
313	length := *(*uint32)(unsafe.Pointer(ptr + unsafe.Sizeof(uintptr(0))))
314	if length == 0 || dataPtr == 0 {
315		return nil
316	}
317	return unsafe.Slice((*uintptr)(unsafe.Pointer(dataPtr)), length)`
318	default:
319		return `return core.GoStringSlice(v.GetBoxed())`
320	}
321}

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 102

↑↓ moveenter openesc close