1 /// \file BehaviourIssue.d
2 module tagion.behaviour.BehaviourIssue;
3 
4 import std.algorithm : each, map;
5 import std.array : array, join;
6 import std.format;
7 import std.range : chain, tee;
8 import std.traits;
9 import tagion.behaviour.BehaviourFeature;
10 import tagion.utils.Escaper : escaper;
11 
12 @safe
13 MarkdownT!(Stream) Markdown(Stream)(Stream bout) {
14     alias MasterT = MarkdownT!Stream;
15     MasterT.master = masterMarkdown;
16     return MasterT(bout);
17 }
18 
19 @safe
20 DlangT!(Stream) Dlang(Stream)(Stream bout) {
21     alias MasterT = DlangT!Stream;
22     auto result = MasterT(bout);
23     return result;
24 }
25 
26 /**
27  * \struct MarkdownFMT
28  * Formator of the issuer
29  */
30 
31 @safe
32 struct MarkdownFMT {
33     string name;
34     string scenario;
35     string feature;
36     string property;
37     string comments;
38 }
39 
40 /** 
41  * Default formater for Markdown 
42  */
43 @safe
44 static MarkdownFMT masterMarkdown = {
45     name: "`%1$s`",
46     scenario: "### %1$s: %2$s",
47     feature: "## %1$s: %2$s",
48     property: "*%s* %s",
49     comments: "%-(%s\n%)",
50 };
51 
52 @safe
53 struct MarkdownT(Stream) {
54     Stream bout;
55     static MarkdownFMT master;
56 
57     void issue(Descriptor)(const(Descriptor) descriptor, string fmt,
58             string comment_fmt = null) if (isDescriptor!Descriptor) {
59         bout.writefln(fmt, Descriptor.stringof, descriptor.description);
60         if (descriptor.comments) {
61             comment_fmt = (comment_fmt is null) ? master.comments : comment_fmt;
62             bout.writefln(comment_fmt, descriptor.comments);
63         }
64     }
65 
66     void issue(I)(const(I) info, string fmt) if (isInfo!I) {
67         issue(info.property, fmt);
68         bout.write("\n");
69         bout.writefln(master.name, info.name);
70         bout.write("\n");
71     }
72 
73     void issue(Group)(const(Group) group, string fmt) if (isActionGroup!Group) {
74         if (group !is group.init) {
75             group.infos.each!(info => issue(info, master.property));
76         }
77     }
78 
79     void issue(const(ScenarioGroup) scenario_group) {
80         issue(scenario_group.info, master.scenario);
81         issue(scenario_group.given, master.property);
82         issue(scenario_group.when, master.property);
83         issue(scenario_group.then, master.property);
84         issue(scenario_group.but, master.property);
85     }
86 
87     void issue(const(FeatureGroup) feature_group) {
88         issue(feature_group.info, master.feature);
89         feature_group.scenarios
90             .tee!(a => bout.write("\n"))
91             .each!(a => issue(a));
92     }
93 }
94 
95 /// Examples: Converting a Scenario to Markdown 
96 @safe
97 unittest { // Markdown scenario test
98     auto bout = new OutBuffer;
99     auto markdown = Markdown(bout);
100     alias unit_mangle = mangleFunc!(MarkdownU);
101     auto awesome = new Some_awesome_feature;
102     const scenario_result = run(awesome);
103     {
104         scope (exit) {
105             bout.clear;
106         }
107         enum filename = unit_mangle("descriptor")
108                 .setExtension(FileExtension.markdown);
109         enum expected = import(filename);
110         //io.writefln("scenario_result.given.infos %s", scenario_result.given.infos);
111         markdown.issue(scenario_result.given.infos[0], markdown.master.property);
112         version (behaviour_unitdata)
113             filename.unitfile.setExtension("mdtest").fwrite(bout.toString);
114         assert(bout.toString == expected);
115     }
116     {
117         scope (exit) {
118             bout.clear;
119         }
120         enum filename = unit_mangle("scenario")
121                 .setExtension(FileExtension.markdown);
122         markdown.issue(scenario_result);
123         version (behaviour_unitdata)
124             filename.unitfile.setExtension("mdtest").fwrite(bout.toString);
125 
126         enum expected = import(filename);
127         assert(bout.toString == expected);
128     }
129 }
130 
131 /// Examples: Convering a FeatureGroup to Markdown
132 @safe
133 unittest {
134     auto bout = new OutBuffer;
135     auto markdown = Markdown(bout);
136     alias unit_mangle = mangleFunc!(MarkdownU);
137     const feature_group = getFeature!(tagion.behaviour.BehaviourUnittest);
138     {
139         scope (exit) {
140             bout.clear;
141         }
142         enum filename = unit_mangle("feature")
143                 .setExtension(FileExtension.markdown);
144         markdown.issue(feature_group);
145         version (behaviour_unitdata)
146             filename.unitfile.setExtension("mdtest").fwrite(bout.toString);
147 
148         immutable expected = import(filename);
149         assert(bout.toString == expected);
150     }
151 
152 }
153 
154 /**
155  * \struct DlangT
156  * D-source generator
157  */
158 @safe
159 struct DlangT(Stream) {
160     Stream bout;
161     static string[] preparations;
162     static this() {
163         preparations ~=
164             q{
165             // Auto generated imports
166             import tagion.behaviour.BehaviourException;
167             import tagion.behaviour.BehaviourFeature;
168             import tagion.behaviour.BehaviourResult;
169         };
170     }
171 
172     string issue(I)(const(I) info) if (isInfo!I) {
173         alias Property = TemplateArgsOf!(I)[0];
174         return format(q{
175                 @%2$s("%3$s")
176                 Document %1$s() {
177                         return Document();
178                     }
179                 },
180                 info.name,
181                 Property.stringof,
182                 info.property.description.escaper
183         );
184     }
185 
186     string[] issue(Group)(const(Group) group) if (isActionGroup!Group) {
187         if (group !is group.init) {
188             return group.infos
189                 .map!(info => issue(info))
190                 .array;
191         }
192         return null;
193     }
194 
195     string issue(const(ScenarioGroup) scenario_group) {
196         immutable scenario_param = format(
197                 "\"%s\",\n[%(%3$s,\n%)]",
198                 scenario_group.info.property.description,
199                 scenario_group.info.property.comments
200                 .map!(comment => comment.escaper.array)
201         );
202         auto behaviour_groups = chain(
203                 issue(scenario_group.given),
204                 issue(scenario_group.when),
205                 issue(scenario_group.then),
206                 issue(scenario_group.but),
207         );
208         return format(q{
209                 @safe @Scenario(%1$s)
210                     class %2$s {
211                         %3$s
212                     }
213                 },
214                 scenario_param,
215                 scenario_group.info.name,
216                 behaviour_groups
217                 .join
218         );
219     }
220 
221     void issue(const(FeatureGroup) feature_group) {
222         immutable comments = format("[%(%s,\n%)]",
223                 feature_group.info.property.comments
224                 .map!(comment => comment.escaper.array)
225         );
226         auto feature_tuple = chain(
227                 feature_group.scenarios
228                 .map!(scenario => [scenario.info.name, scenario.info.name]),
229         [["FeatureGroup*", "result"]])
230             .map!(ctx_type => format(`%s, "%s"`, ctx_type[0], ctx_type[1]))
231             .join(",\n");
232 
233         bout.writefln(q{
234                 module %1$s;
235                 %5$s
236                 enum feature = Feature(
237                     "%2$s",
238                     %3$s);
239                 
240                 alias FeatureContext = Tuple!(
241                     %4$s
242                 );
243             },
244                 feature_group.info.name,
245                 feature_group.info.property.description,
246                 comments,
247                 feature_tuple,
248                 preparations.join("\n")
249         );
250         if (feature_group.scenarios.length) {
251             feature_group.scenarios
252                 .map!(s => issue(s))
253                 .each!(a => bout.write(a));
254         }
255     }
256 }
257 
258 /// Examples: Converting a FeatureGroup to a D-source skeleten
259 @safe
260 unittest {
261     auto bout = new OutBuffer;
262     auto dlang = Dlang(bout);
263     alias unit_mangle = mangleFunc!(DlangU);
264     const feature_group = getFeature!(tagion.behaviour.BehaviourUnittest);
265     {
266         scope (exit) {
267             bout.clear;
268         }
269         enum filename = unit_mangle("feature")
270                 .setExtension(FileExtension.dsrc);
271         dlang.issue(feature_group);
272         immutable result = bout.toString;
273         version (behaviour_unitdata)
274             filename.unitfile.setExtension("dtest").fwrite(result.trim_source.join("\n"));
275         enum expected = import(filename);
276         assert(equal(
277                 result
278                 .trim_source,
279                 expected
280                 .trim_source
281         ));
282     }
283 }
284 
285 version (unittest) {
286     import std.algorithm.comparison : equal;
287     import std.algorithm.iteration : filter;
288     import std.file : freadText = readText, fwrite = write;
289     import std.outbuffer;
290     import std.path;
291     import std.range : enumerate, zip;
292     import std.string : splitLines, strip;
293     import tagion.basic.Types : FileExtension;
294     import tagion.basic.basic : mangleFunc, unitfile;
295     import tagion.behaviour.Behaviour;
296     import tagion.behaviour.BehaviourUnittest;
297     import tagion.hibon.Document;
298 
299     alias MarkdownU = Markdown!OutBuffer;
300     alias DlangU = Dlang!OutBuffer;
301     ///Returns: a stripped version of a d-source text
302     auto trim_source(S)(S source) {
303         return source
304             .splitLines
305             .map!(a => a.strip)
306             .filter!(a => a.length !is 0);
307 
308     }
309 }