1 /**
2  * Authors: Pedro Tacla Yamada
3  * Date: August 21, 2014
4  * License: Licensed under the GPLv3 license. See LICENSE for more information.
5  */
6 module pyjamas;
7 
8 import std.algorithm : canFind, isSorted;
9 import std.conv : to;
10 import std.range : isInputRange, isForwardRange, hasLength, ElementEncodingType,
11                    isAssociativeArray, empty;
12 import std.regex : Regex, StaticRegex;// & std.regex.match
13 import std..string : format;
14 import std.traits : hasMember, isSomeString, isCallable,
15                     isImplicitlyConvertible, Unqual, std;
16 
17 Assertion!T should(T)(T context)
18 {
19   return new Assertion!T(context);
20 }
21 
22 class Assertion(T)
23 {
24   static bool callable = isCallable!T;
25   bool negated = false;
26   string operator = "be";
27   T context;
28 
29   this() {};
30   this(T _context)
31   {
32     context = _context;
33   }
34 
35   alias id be;
36   alias id as;
37   alias id of;
38   alias id a;
39   alias id and;
40   alias id have;
41   alias id which;
42 
43   Assertion id()
44   {
45     return this;
46   }
47 
48   Assertion not()
49   {
50     negated = !negated;
51     return this;
52   }
53 
54   U ok(U)(U expr,
55           lazy string message,
56           string file = __FILE__,
57           size_t line = __LINE__)
58   {
59     if(negated ? !expr : expr) return expr;
60     throw new Exception(message, file, line);
61   }
62 
63   T equal(U)(U other,
64              string file = __FILE__,
65              size_t line = __LINE__)
66   {
67     auto t_other = other.to!T;
68     ok(context == other, message(other), file, line);
69     return context;
70   }
71 
72   T exist(string file = __FILE__, size_t line = __LINE__)
73   {
74     static if(isImplicitlyConvertible!(T, typeof(null)))
75     {
76       operator = "exist";
77       if(context == null)
78       {
79         ok(false, message, file, line);
80       }
81     }
82     return context;
83   }
84 
85   string message(U)(U other)
86   {
87     return format("expected %s to %s%s%s", context.to!string,
88                                            (negated ? "not " : ""),
89                                            operator,
90                                            (" " ~ other.to!string));
91   }
92 
93   string message()
94   {
95     return format("expected %s to %s%s", context.to!string,
96                                          (negated ? "not " : ""),
97                                          operator);
98   }
99 
100   bool biggerThan(U)(U other, string file = __FILE__, size_t line = __LINE__)
101   {
102     operator = "be bigger than";
103     return ok(context > other, message(other), file, line);
104   }
105 
106   bool smallerThan(U)(U other, string file = __FILE__, size_t line = __LINE__)
107   {
108     operator = "be smaller than";
109     return ok(context < other, message(other), file, line);
110   }
111 
112   static if(isForwardRange!T && __traits(compiles, context.isSorted))
113   {
114     bool sorted(string file = __FILE__, size_t line = __LINE__)
115     {
116       operator = "be sorted";
117       return ok(context.isSorted, message, file, line);
118     }
119   }
120 
121   static if(isAssociativeArray!T) {
122     void key(U)(U other, string file = __FILE__, size_t line = __LINE__)
123     {
124       operator = "have key";
125       ok(!(other !in context), message(other), file, line);
126     }
127   }
128 
129   static if(isInputRange!T || isAssociativeArray!T)
130   {
131     U value(U)(U other, string file = __FILE__, size_t line = __LINE__)
132     {
133       static if(isAssociativeArray!T) auto pool = context.values;
134       else auto pool = context;
135 
136       operator = "contain value";
137       ok(canFind(pool, other), message(other), file, line);
138       return other;
139     }
140 
141     alias value include;
142     alias value contain;
143   }
144 
145   static if(hasLength!T || hasMember!(T, "string") || isSomeString!T)
146   {
147     U length(U)(U len, string file = __FILE__, size_t line = __LINE__)
148     {
149       operator = "have length of";
150       ok(context.length == len, message(len), file, line);
151       return len;
152     }
153   }
154 
155   static if(isSomeString!T)
156   {
157     private alias BasicElementOfT = Unqual!(ElementEncodingType!T);
158     private alias RegexOfT = Regex!(BasicElementOfT);
159     private alias StaticRegexOfT = StaticRegex!(BasicElementOfT);
160 
161     auto match(RegEx)(RegEx re, string file = __FILE__, size_t line = __LINE__)
162       if(is(RegEx == RegexOfT) ||
163          is(RegEx == StaticRegexOfT) ||
164          isSomeString!RegEx)
165     {
166       auto m = std.regex.match(context, re);
167       operator = "match";
168       ok(!m.empty, message(re), file, line);
169       return m;
170     }
171   }
172 
173   static if(is(T == bool))
174   {
175     bool True(string file = __FILE__, size_t line = __LINE__)
176     {
177       return ok(context == true, message(true), file, line);
178     }
179 
180     bool False(string file = __FILE__, size_t line = __LINE__)
181     {
182       return !ok(context == false, message(false), file, line);
183     }
184   }
185 
186   static if(isCallable!T)
187  {
188     void Throw(T : Throwable = Exception)(string file = __FILE__,
189                                           size_t line = __LINE__)
190     {
191       operator = "throw";
192       bool thrown = false;
193       try context();
194       catch(T) thrown = true;
195       ok(thrown, message(), file, line);
196     }
197   }
198 
199 }