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, byte, short, ushort, int, uint, long, ulong,
58     double, string, ObjectPath);
59 
60 template basicDBus(T) {
61   static if (staticIndexOf!(T, BasicTypes) >= 0) {
62     enum basicDBus = true;
63   } else static if (is(T B == enum)) {
64     enum basicDBus = basicDBus!B;
65   } else static if (isInstanceOf!(BitFlags, T)) {
66     alias TemplateArgsOf!T[0] E;
67     enum basicDBus = basicDBus!E;
68   } else {
69     enum basicDBus = false;
70   }
71 }
72 
73 template canDBus(T) {
74   static if (basicDBus!T || is(T == DBusAny)) {
75     enum canDBus = true;
76   } else static if (isInstanceOf!(Variant, T)) {
77     enum canDBus = canDBus!(VariantType!T);
78   } else static if (isInstanceOf!(VariantN, T)) {
79     // Phobos-style variants are supported if limited to DBus compatible types.
80     enum canDBus = (T.AllowedTypes.length > 0) && allCanDBus!(T.AllowedTypes);
81   } else static if (isTuple!T) {
82     enum canDBus = allCanDBus!(T.Types);
83   } else static if (isInputRange!T) {
84     static if (is(ElementType!T == DictionaryEntry!(K, V), K, V)) {
85       enum canDBus = basicDBus!K && canDBus!V;
86     } else {
87       enum canDBus = canDBus!(ElementType!T);
88     }
89   } else static if (isAssociativeArray!T) {
90     enum canDBus = basicDBus!(KeyType!T) && canDBus!(ValueType!T);
91   } else static if (is(T == struct) && !isInstanceOf!(DictionaryEntry, T)) {
92     enum canDBus = allCanDBus!(AllowedFieldTypes!T);
93   } else {
94     enum canDBus = false;
95   }
96 }
97 
98 unittest {
99   import dunit.toolkit;
100 
101   (canDBus!int).assertTrue();
102   (canDBus!(int[])).assertTrue();
103   (allCanDBus!(int, string, bool)).assertTrue();
104   (canDBus!(Tuple!(int[], bool, Variant!short))).assertTrue();
105   (canDBus!(Tuple!(int[], int[string]))).assertTrue();
106   (canDBus!(int[string])).assertTrue();
107 }
108 
109 string typeSig(T)()
110     if (canDBus!T) {
111   static if (is(T == byte)) {
112     return "y";
113   } else static if (is(T == bool)) {
114     return "b";
115   } else static if (is(T == short)) {
116     return "n";
117   } else static if (is(T == ushort)) {
118     return "q";
119   } else static if (is(T == int)) {
120     return "i";
121   } else static if (is(T == uint)) {
122     return "u";
123   } else static if (is(T == long)) {
124     return "x";
125   } else static if (is(T == ulong)) {
126     return "t";
127   } else static if (is(T == double)) {
128     return "d";
129   } else static if (is(T == string)) {
130     return "s";
131   } else static if (is(T == ObjectPath)) {
132     return "o";
133   } else static if (isInstanceOf!(Variant, T) || isInstanceOf!(VariantN, T)) {
134     return "v";
135   } else static if (is(T B == enum)) {
136     return typeSig!B;
137   } else static if (isInstanceOf!(BitFlags, T)) {
138     alias TemplateArgsOf!T[0] E;
139     return typeSig!E;
140   } else static if (is(T == DBusAny)) {
141     static assert(false,
142         "Cannot determine type signature of DBusAny. Change to Variant!DBusAny if a variant was desired.");
143   } else static if (isTuple!T) {
144     string sig = "(";
145     foreach (i, S; T.Types) {
146       sig ~= typeSig!S();
147     }
148     sig ~= ")";
149     return sig;
150   } else static if (isInputRange!T) {
151     return "a" ~ typeSig!(ElementType!T)();
152   } else static if (isAssociativeArray!T) {
153     return "a{" ~ typeSig!(KeyType!T) ~ typeSig!(ValueType!T) ~ "}";
154   } else static if (is(T == struct)) {
155     string sig = "(";
156     foreach (i, S; AllowedFieldTypes!T) {
157       sig ~= typeSig!S();
158     }
159     sig ~= ")";
160     return sig;
161   }
162 }
163 
164 string typeSig(T)()
165     if (isInstanceOf!(DictionaryEntry, T)) {
166   alias typeof(T.key) K;
167   alias typeof(T.value) V;
168   return "{" ~ typeSig!K ~ typeSig!V ~ '}';
169 }
170 
171 string[] typeSigReturn(T)()
172     if (canDBus!T) {
173   static if (is(T == Tuple!TS, TS...))
174     return typeSigArr!TS;
175   else
176     return [typeSig!T];
177 }
178 
179 string typeSigAll(TS...)()
180     if (allCanDBus!TS) {
181   string sig = "";
182   foreach (i, T; TS) {
183     sig ~= typeSig!T();
184   }
185   return sig;
186 }
187 
188 string[] typeSigArr(TS...)()
189     if (allCanDBus!TS) {
190   string[] sig = [];
191   foreach (i, T; TS) {
192     sig ~= typeSig!T();
193   }
194   return sig;
195 }
196 
197 int typeCode(T)()
198     if (canDBus!T) {
199   int code = typeSig!T()[0];
200   return (code != '(') ? code : 'r';
201 }
202 
203 int typeCode(T)()
204     if (isInstanceOf!(DictionaryEntry, T) && canDBus!(T[])) {
205   return 'e';
206 }
207 
208 unittest {
209   import dunit.toolkit;
210 
211   // basics
212   typeSig!int().assertEqual("i");
213   typeSig!bool().assertEqual("b");
214   typeSig!string().assertEqual("s");
215   typeSig!(Variant!int)().assertEqual("v");
216   // enums
217   enum E : byte {
218     a,
219     b,
220     c
221   }
222 
223   typeSig!E().assertEqual(typeSig!byte());
224   enum U : string {
225     One = "One",
226     Two = "Two"
227   }
228 
229   typeSig!U().assertEqual(typeSig!string());
230   // bit flags
231   enum F : uint {
232     a = 1,
233     b = 2,
234     c = 4
235   }
236 
237   typeSig!(BitFlags!F)().assertEqual(typeSig!uint());
238   // tuples (represented as structs in DBus)
239   typeSig!(Tuple!(int, string, string)).assertEqual("(iss)");
240   typeSig!(Tuple!(int, string, Variant!int, Tuple!(int, "k", double, "x"))).assertEqual(
241       "(isv(id))");
242   // structs
243   struct S1 {
244     int a;
245     double b;
246     string s;
247   }
248 
249   typeSig!S1.assertEqual("(ids)");
250   struct S2 {
251     Variant!int c;
252     string d;
253     S1 e;
254     uint f;
255   }
256 
257   typeSig!S2.assertEqual("(vs(ids)u)");
258   // arrays
259   typeSig!(int[]).assertEqual("ai");
260   typeSig!(Variant!int[]).assertEqual("av");
261   typeSig!(Tuple!(byte)[][]).assertEqual("aa(y)");
262   // dictionaries
263   typeSig!(int[string]).assertEqual("a{si}");
264   typeSig!(DictionaryEntry!(string, int)[]).assertEqual("a{si}");
265   // multiple arguments
266   typeSigAll!(int, bool).assertEqual("ib");
267   // Phobos-style variants
268   canDBus!(std.variant.Variant).assertFalse();
269   typeSig!(std.variant.Algebraic!(int, double, string)).assertEqual("v");
270   // type codes
271   typeCode!int().assertEqual(cast(int)('i'));
272   typeCode!bool().assertEqual(cast(int)('b'));
273   typeCode!(Tuple!(int, string))().assertEqual(cast(int)('r'));
274   // ctfe-capable
275   static string sig = typeSig!ulong();
276   sig.assertEqual("t");
277   static string sig2 = typeSig!(Tuple!(int, string, string));
278   sig2.assertEqual("(iss)");
279   static string sig3 = typeSigAll!(int, string, string);
280   sig3.assertEqual("iss");
281 }
282 
283 private template AllowedFieldTypes(S)
284     if (is(S == struct)) {
285   import ddbus.attributes : isAllowedField;
286   import std.meta : Filter, staticMap;
287 
288   static alias TypeOf(alias sym) = typeof(sym);
289 
290   alias AllowedFieldTypes = staticMap!(TypeOf, Filter!(isAllowedField, S.tupleof));
291 }