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}