1/*
2 A partial and basic implementation of GVariant formatted strings.
3 See [GVariant Format Strings](https://docs.gtk.org/glib/gvariant-format-strings.html) for details.
4
5 :::{.warning}
6 This API is not considered fully stable and it might therefore
7 change in backwards incompatible ways without prior notice.
8 :::
9*/
10
11# This file is based on https://github.com/nix-community/home-manager
12# Copyright (c) 2017-2022 Home Manager contributors
13{ lib }:
14
15let
16 inherit (lib)
17 concatMapStringsSep concatStrings escape head replaceStrings;
18
19 mkPrimitive = t: v: {
20 _type = "gvariant";
21 type = t;
22 value = v;
23 __toString = self: "@${self.type} ${toString self.value}"; # https://docs.gtk.org/glib/gvariant-text.html
24 };
25
26 type = {
27 arrayOf = t: "a${t}";
28 maybeOf = t: "m${t}";
29 tupleOf = ts: "(${concatStrings ts})";
30 dictionaryEntryOf = nameType: valueType: "{${nameType}${valueType}}";
31 string = "s";
32 boolean = "b";
33 uchar = "y";
34 int16 = "n";
35 uint16 = "q";
36 int32 = "i";
37 uint32 = "u";
38 int64 = "x";
39 uint64 = "t";
40 double = "d";
41 variant = "v";
42 };
43
44 /* Check if a value is a GVariant value
45
46 Type:
47 isGVariant :: Any -> Bool
48 */
49 isGVariant = v: v._type or "" == "gvariant";
50
51in
52rec {
53
54 inherit type isGVariant;
55
56 /* Returns the GVariant value that most closely matches the given Nix value.
57 If no GVariant value can be found unambiguously then error is thrown.
58
59 Type:
60 mkValue :: Any -> gvariant
61 */
62 mkValue = v:
63 if builtins.isBool v then
64 mkBoolean v
65 else if builtins.isFloat v then
66 mkDouble v
67 else if builtins.isString v then
68 mkString v
69 else if builtins.isList v then
70 mkArray v
71 else if isGVariant v then
72 v
73 else
74 throw "The GVariant type of ${v} can't be inferred.";
75
76 /* Returns the GVariant array from the given type of the elements and a Nix list.
77
78 Type:
79 mkArray :: [Any] -> gvariant
80
81 Example:
82 # Creating a string array
83 lib.gvariant.mkArray [ "a" "b" "c" ]
84 */
85 mkArray = elems:
86 let
87 vs = map mkValue (lib.throwIf (elems == [ ]) "Please create empty array with mkEmptyArray." elems);
88 elemType = lib.throwIfNot (lib.all (t: (head vs).type == t) (map (v: v.type) vs))
89 "Elements in a list should have same type."
90 (head vs).type;
91 in
92 mkPrimitive (type.arrayOf elemType) vs // {
93 __toString = self:
94 "@${self.type} [${concatMapStringsSep "," toString self.value}]";
95 };
96
97 /* Returns the GVariant array from the given empty Nix list.
98
99 Type:
100 mkEmptyArray :: gvariant.type -> gvariant
101
102 Example:
103 # Creating an empty string array
104 lib.gvariant.mkEmptyArray (lib.gvariant.type.string)
105 */
106 mkEmptyArray = elemType: mkPrimitive (type.arrayOf elemType) [ ] // {
107 __toString = self: "@${self.type} []";
108 };
109
110
111 /* Returns the GVariant variant from the given Nix value. Variants are containers
112 of different GVariant type.
113
114 Type:
115 mkVariant :: Any -> gvariant
116
117 Example:
118 lib.gvariant.mkArray [
119 (lib.gvariant.mkVariant "a string")
120 (lib.gvariant.mkVariant (lib.gvariant.mkInt32 1))
121 ]
122 */
123 mkVariant = elem:
124 let gvarElem = mkValue elem;
125 in mkPrimitive type.variant gvarElem // {
126 __toString = self: "<${toString self.value}>";
127 };
128
129 /* Returns the GVariant dictionary entry from the given key and value.
130
131 Type:
132 mkDictionaryEntry :: String -> Any -> gvariant
133
134 Example:
135 # A dictionary describing an Epiphany’s search provider
136 [
137 (lib.gvariant.mkDictionaryEntry "url" (lib.gvariant.mkVariant "https://duckduckgo.com/?q=%s&t=epiphany"))
138 (lib.gvariant.mkDictionaryEntry "bang" (lib.gvariant.mkVariant "!d"))
139 (lib.gvariant.mkDictionaryEntry "name" (lib.gvariant.mkVariant "DuckDuckGo"))
140 ]
141 */
142 mkDictionaryEntry =
143 # The key of the entry
144 name:
145 # The value of the entry
146 value:
147 let
148 name' = mkValue name;
149 value' = mkValue value;
150 dictionaryType = type.dictionaryEntryOf name'.type value'.type;
151 in
152 mkPrimitive dictionaryType { inherit name value; } // {
153 __toString = self: "@${self.type} {${name'},${value'}}";
154 };
155
156 /* Returns the GVariant maybe from the given element type.
157
158 Type:
159 mkMaybe :: gvariant.type -> Any -> gvariant
160 */
161 mkMaybe = elemType: elem:
162 mkPrimitive (type.maybeOf elemType) elem // {
163 __toString = self:
164 if self.value == null then
165 "@${self.type} nothing"
166 else
167 "just ${toString self.value}";
168 };
169
170 /* Returns the GVariant nothing from the given element type.
171
172 Type:
173 mkNothing :: gvariant.type -> gvariant
174 */
175 mkNothing = elemType: mkMaybe elemType null;
176
177 /* Returns the GVariant just from the given Nix value.
178
179 Type:
180 mkJust :: Any -> gvariant
181 */
182 mkJust = elem: let gvarElem = mkValue elem; in mkMaybe gvarElem.type gvarElem;
183
184 /* Returns the GVariant tuple from the given Nix list.
185
186 Type:
187 mkTuple :: [Any] -> gvariant
188 */
189 mkTuple = elems:
190 let
191 gvarElems = map mkValue elems;
192 tupleType = type.tupleOf (map (e: e.type) gvarElems);
193 in
194 mkPrimitive tupleType gvarElems // {
195 __toString = self:
196 "@${self.type} (${concatMapStringsSep "," toString self.value})";
197 };
198
199 /* Returns the GVariant boolean from the given Nix bool value.
200
201 Type:
202 mkBoolean :: Bool -> gvariant
203 */
204 mkBoolean = v:
205 mkPrimitive type.boolean v // {
206 __toString = self: if self.value then "true" else "false";
207 };
208
209 /* Returns the GVariant string from the given Nix string value.
210
211 Type:
212 mkString :: String -> gvariant
213 */
214 mkString = v:
215 let sanitize = s: replaceStrings [ "\n" ] [ "\\n" ] (escape [ "'" "\\" ] s);
216 in mkPrimitive type.string v // {
217 __toString = self: "'${sanitize self.value}'";
218 };
219
220 /* Returns the GVariant object path from the given Nix string value.
221
222 Type:
223 mkObjectpath :: String -> gvariant
224 */
225 mkObjectpath = v:
226 mkPrimitive type.string v // {
227 __toString = self: "objectpath '${escape [ "'" ] self.value}'";
228 };
229
230 /* Returns the GVariant uchar from the given Nix int value.
231
232 Type:
233 mkUchar :: Int -> gvariant
234 */
235 mkUchar = mkPrimitive type.uchar;
236
237 /* Returns the GVariant int16 from the given Nix int value.
238
239 Type:
240 mkInt16 :: Int -> gvariant
241 */
242 mkInt16 = mkPrimitive type.int16;
243
244 /* Returns the GVariant uint16 from the given Nix int value.
245
246 Type:
247 mkUint16 :: Int -> gvariant
248 */
249 mkUint16 = mkPrimitive type.uint16;
250
251 /* Returns the GVariant int32 from the given Nix int value.
252
253 Type:
254 mkInt32 :: Int -> gvariant
255 */
256 mkInt32 = v:
257 mkPrimitive type.int32 v // {
258 __toString = self: toString self.value;
259 };
260
261 /* Returns the GVariant uint32 from the given Nix int value.
262
263 Type:
264 mkUint32 :: Int -> gvariant
265 */
266 mkUint32 = mkPrimitive type.uint32;
267
268 /* Returns the GVariant int64 from the given Nix int value.
269
270 Type:
271 mkInt64 :: Int -> gvariant
272 */
273 mkInt64 = mkPrimitive type.int64;
274
275 /* Returns the GVariant uint64 from the given Nix int value.
276
277 Type:
278 mkUint64 :: Int -> gvariant
279 */
280 mkUint64 = mkPrimitive type.uint64;
281
282 /* Returns the GVariant double from the given Nix float value.
283
284 Type:
285 mkDouble :: Float -> gvariant
286 */
287 mkDouble = v:
288 mkPrimitive type.double v // {
289 __toString = self: toString self.value;
290 };
291}