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 }