1 module ddbus.util;
2 
3 import ddbus.thin;
4 import std.meta : AliasSeq, staticIndexOf;
5 import std.range;
6 import std.traits;
7 import std.typecons : BitFlags, isTuple, Tuple;
8 import std.variant : VariantN;
9 
10 struct DictionaryEntry(K, V) {
11   K key;
12   V value;
13 }
14 
15 auto byDictionaryEntries(K, V)(V[K] aa) {
16   import std.algorithm : map;
17 
18   return aa.byKeyValue.map!(pair => DictionaryEntry!(K, V)(pair.key, pair.value));
19 }
20 
21 /+
22   Predicate template indicating whether T is an instance of ddbus.thin.Variant.
23 
24   Deprecated:
25     This template used to be undocumented and user code should not depend on it.
26     Its meaning became unclear when support for Phobos-style variants was added.
27     It seemed best to remove it at that point.
28 +/
29 deprecated("Use std.traits.isInstanceOf instead.") template isVariant(T) {
30   static if (isBasicType!T || isInputRange!T) {
31     enum isVariant = false;
32   } else static if (__traits(compiles, TemplateOf!T) && __traits(isSame, TemplateOf!T, Variant)) {
33     enum isVariant = true;
34   } else {
35     enum isVariant = false;
36   }
37 }
38 
39 template VariantType(T) {
40   alias VariantType = TemplateArgsOf!(T)[0];
41 }
42 
43 template allCanDBus(TS...) {
44   static if (TS.length == 0) {
45     enum allCanDBus = true;
46   } else static if (!canDBus!(TS[0])) {
47     enum allCanDBus = false;
48   } else {
49     enum allCanDBus = allCanDBus!(TS[1 .. $]);
50   }
51 }
52 
53 /++
54   AliasSeq of all basic types in terms of the DBus typesystem
55  +/
56 package  // Don't add to the API yet, 'cause I intend to move it later
57 alias BasicTypes = AliasSeq!(bool, ubyte, short, ushort, int, uint, long, ulong,
58     double, string, ObjectPath, InterfaceName, BusName, FileDescriptor);
59 
60 template basicDBus(U) {
61   alias T = Unqual!U;
62   static if (staticIndexOf!(T, BasicTypes) >= 0) {
63     enum basicDBus = true;
64   } else static if (is(T B == enum)) {
65     enum basicDBus = basicDBus!B;
66   } else static if (isInstanceOf!(BitFlags, T)) {
67     alias TemplateArgsOf!T[0] E;
68     enum basicDBus = basicDBus!E;
69   } else {
70     enum basicDBus = false;
71   }
72 }
73 
74 template canDBus(U) {
75   alias T = Unqual!U;
76   static if (basicDBus!T || is(T == DBusAny)) {
77     enum canDBus = true;
78   } else static if (isInstanceOf!(Variant, T)) {
79     enum canDBus = canDBus!(VariantType!T);
80   } else static if (isInstanceOf!(VariantN, T)) {
81     // Phobos-style variants are supported if limited to DBus compatible types.
82     enum canDBus = (T.AllowedTypes.length > 0) && allCanDBus!(T.AllowedTypes);
83   } else static if (isTuple!T) {
84     enum canDBus = allCanDBus!(T.Types);
85   } else static if (isInputRange!T) {
86     static if (is(ElementType!T == DictionaryEntry!(K, V), K, V)) {
87       enum canDBus = basicDBus!K && canDBus!V;
88     } else {
89       enum canDBus = canDBus!(ElementType!T);
90     }
91   } else static if (isAssociativeArray!T) {
92     enum canDBus = basicDBus!(KeyType!T) && canDBus!(ValueType!T);
93   } else static if (is(T == struct) && !isInstanceOf!(DictionaryEntry, T)) {
94     enum canDBus = allCanDBus!(AllowedFieldTypes!T);
95   } else {
96     enum canDBus = false;
97   }
98 }
99 
100 unittest {
101   import dunit.toolkit;
102 
103   (canDBus!int).assertTrue();
104   (canDBus!(int[])).assertTrue();
105   (allCanDBus!(int, string, bool)).assertTrue();
106   (canDBus!(Tuple!(int[], bool, Variant!short))).assertTrue();
107   (canDBus!(Tuple!(int[], int[string]))).assertTrue();
108   (canDBus!(int[string])).assertTrue();
109   (canDBus!FileDescriptor).assertTrue();
110 }
111 
112 string typeSig(U)()
113     if (canDBus!U) {
114   alias T = Unqual!U;
115   static if (is(T == ubyte)) {
116     return "y";
117   } else static if (is(T == bool)) {
118     return "b";
119   } else static if (is(T == short)) {
120     return "n";
121   } else static if (is(T == ushort)) {
122     return "q";
123   } else static if (is(T == FileDescriptor)) {
124     return "h";
125   } else static if (is(T == int)) {
126     return "i";
127   } else static if (is(T == uint)) {
128     return "u";
129   } else static if (is(T == long)) {
130     return "x";
131   } else static if (is(T == ulong)) {
132     return "t";
133   } else static if (is(T == double)) {
134     return "d";
135   } else static if (is(T == string) || is(T == InterfaceName) || is(T == BusName)) {
136     return "s";
137   } else static if (is(T == ObjectPath)) {
138     return "o";
139   } else static if (isInstanceOf!(Variant, T) || isInstanceOf!(VariantN, T)) {
140     return "v";
141   } else static if (is(T B == enum)) {
142     return typeSig!B;
143   } else static if (isInstanceOf!(BitFlags, T)) {
144     alias TemplateArgsOf!T[0] E;
145     return typeSig!E;
146   } else static if (is(T == DBusAny)) {
147     static assert(false,
148         "Cannot determine type signature of DBusAny. Change to Variant!DBusAny if a variant was desired.");
149   } else static if (isTuple!T) {
150     string sig = "(";
151     foreach (i, S; T.Types) {
152       sig ~= typeSig!S();
153     }
154     sig ~= ")";
155     return sig;
156   } else static if (isInputRange!T) {
157     return "a" ~ typeSig!(ElementType!T)();
158   } else static if (isAssociativeArray!T) {
159     return "a{" ~ typeSig!(KeyType!T) ~ typeSig!(ValueType!T) ~ "}";
160   } else static if (is(T == struct)) {
161     string sig = "(";
162     foreach (i, S; AllowedFieldTypes!T) {
163       sig ~= typeSig!S();
164     }
165     sig ~= ")";
166     return sig;
167   }
168 }
169 
170 string typeSig(T)()
171     if (isInstanceOf!(DictionaryEntry, T)) {
172   alias typeof(T.key) K;
173   alias typeof(T.value) V;
174   return "{" ~ typeSig!K ~ typeSig!V ~ '}';
175 }
176 
177 string[] typeSigReturn(T)()
178     if (canDBus!T) {
179   static if (is(T == Tuple!TS, TS...))
180     return typeSigArr!TS;
181   else
182     return [typeSig!T];
183 }
184 
185 string typeSigAll(TS...)()
186     if (allCanDBus!TS) {
187   string sig = "";
188   foreach (i, T; TS) {
189     sig ~= typeSig!T();
190   }
191   return sig;
192 }
193 
194 string[] typeSigArr(TS...)()
195     if (allCanDBus!TS) {
196   string[] sig = [];
197   foreach (i, T; TS) {
198     sig ~= typeSig!T();
199   }
200   return sig;
201 }
202 
203 int typeCode(T)()
204     if (canDBus!T) {
205   int code = typeSig!T()[0];
206   return (code != '(') ? code : 'r';
207 }
208 
209 int typeCode(T)()
210     if (isInstanceOf!(DictionaryEntry, T) && canDBus!(T[])) {
211   return 'e';
212 }
213 
214 /**
215   Params:
216     type = the type code of a type (first character in a type string)
217   Returns: true if the given type is an integer.
218 */
219 bool dbusIsIntegral(int type) @property {
220   return type == 'y' || type == 'n' || type == 'q' || type == 'i' || type == 'u' || type == 'x' || type == 't';
221 }
222 
223 /**
224   Params:
225     type = the type code of a type (first character in a type string)
226   Returns: true if the given type is a floating point value.
227 */
228 bool dbusIsFloating(int type) @property {
229   return type == 'd';
230 }
231 
232 /**
233   Params:
234     type = the type code of a type (first character in a type string)
235   Returns: true if the given type is an integer or a floating point value.
236 */
237 bool dbusIsNumeric(int type) @property {
238   return dbusIsIntegral(type) || dbusIsFloating(type);
239 }
240 
241 unittest {
242   import dunit.toolkit;
243 
244   static assert(canDBus!ObjectPath);
245   static assert(canDBus!InterfaceName);
246   static assert(canDBus!BusName);
247 
248   // basics
249   typeSig!int().assertEqual("i");
250   typeSig!bool().assertEqual("b");
251   typeSig!string().assertEqual("s");
252   typeSig!InterfaceName().assertEqual("s");
253   typeSig!(immutable(InterfaceName))().assertEqual("s");
254   typeSig!ObjectPath().assertEqual("o");
255   typeSig!(immutable(ObjectPath))().assertEqual("o");
256   typeSig!(Variant!int)().assertEqual("v");
257   typeSig!FileDescriptor().assertEqual("h");
258   // enums
259   enum E : ubyte {
260     a,
261     b,
262     c
263   }
264 
265   typeSig!E().assertEqual(typeSig!ubyte());
266   enum U : string {
267     One = "One",
268     Two = "Two"
269   }
270 
271   typeSig!U().assertEqual(typeSig!string());
272   // bit flags
273   enum F : uint {
274     a = 1,
275     b = 2,
276     c = 4
277   }
278 
279   typeSig!(BitFlags!F)().assertEqual(typeSig!uint());
280   // tuples (represented as structs in DBus)
281   typeSig!(Tuple!(int, string, string)).assertEqual("(iss)");
282   typeSig!(Tuple!(int, string, Variant!int, Tuple!(int, "k", double, "x"))).assertEqual(
283       "(isv(id))");
284   // structs
285   struct S1 {
286     int a;
287     double b;
288     string s;
289   }
290 
291   typeSig!S1.assertEqual("(ids)");
292   struct S2 {
293     Variant!int c;
294     string d;
295     S1 e;
296     uint f;
297     FileDescriptor g;
298   }
299 
300   typeSig!S2.assertEqual("(vs(ids)uh)");
301   // arrays
302   typeSig!(int[]).assertEqual("ai");
303   typeSig!(Variant!int[]).assertEqual("av");
304   typeSig!(Tuple!(ubyte)[][]).assertEqual("aa(y)");
305   // dictionaries
306   typeSig!(int[string]).assertEqual("a{si}");
307   typeSig!(DictionaryEntry!(string, int)[]).assertEqual("a{si}");
308   // multiple arguments
309   typeSigAll!(int, bool).assertEqual("ib");
310   // Phobos-style variants
311   canDBus!(std.variant.Variant).assertFalse();
312   typeSig!(std.variant.Algebraic!(int, double, string)).assertEqual("v");
313   // type codes
314   typeCode!int().assertEqual(cast(int)('i'));
315   typeCode!bool().assertEqual(cast(int)('b'));
316   typeCode!(Tuple!(int, string))().assertEqual(cast(int)('r'));
317   // ctfe-capable
318   static string sig = typeSig!ulong();
319   sig.assertEqual("t");
320   static string sig2 = typeSig!(Tuple!(int, string, string));
321   sig2.assertEqual("(iss)");
322   static string sig3 = typeSigAll!(int, string, InterfaceName, BusName);
323   sig3.assertEqual("isss");
324 }
325 
326 private template AllowedFieldTypes(S)
327     if (is(S == struct)) {
328   import ddbus.attributes : isAllowedField;
329   import std.meta : Filter, staticMap;
330 
331   static alias TypeOf(alias sym) = typeof(sym);
332 
333   alias AllowedFieldTypes = staticMap!(TypeOf, Filter!(isAllowedField, S.tupleof));
334 }