1 /**
2 Copyright: Copyright (c) 2018, Joakim Brännström. All rights reserved.
3 License: MPL-2
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This Source Code Form is subject to the terms of the Mozilla Public License,
7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain
8 one at http://mozilla.org/MPL/2.0/.
9 
10 #SPC-plugin_mutate_report_for_human
11 */
12 module dextool.plugin.mutate.backend.report.markdown;
13 
14 import std.exception : collectException;
15 import logger = std.experimental.logger;
16 
17 import dextool.type;
18 
19 import dextool.plugin.mutate.type : MutationKind, ReportKind, ReportLevel;
20 import dextool.plugin.mutate.backend.database : Database, IterateMutantRow;
21 import dextool.plugin.mutate.backend.interface_ : FilesysIO, SafeInput;
22 import dextool.plugin.mutate.backend.type : Mutation, Offset;
23 
24 import dextool.plugin.mutate.backend.report.utility : MakeMutationTextResult,
25     makeMutationText, window, windowSize, reportMutationSubtypeStats,
26     reportStatistics, Table;
27 import dextool.plugin.mutate.backend.report.type : SimpleWriter, ReportEvent;
28 
29 @safe:
30 
31 struct Markdown(Writer, TraceWriter) {
32     import std.ascii : newline;
33     import std.format : formattedWrite, format;
34     import std.range : put;
35 
36     private int curr_head;
37     private Writer w;
38     private TraceWriter w_trace;
39 
40     private this(int heading, Writer w, TraceWriter w_trace) {
41         this.curr_head = heading;
42         this.w = w;
43         this.w_trace = w_trace;
44     }
45 
46     this(Writer w, TraceWriter w_trace) {
47         this.w = w;
48         this.w_trace = w_trace;
49     }
50 
51     auto heading(ARGS...)(auto ref ARGS args) {
52         import std.algorithm : copy;
53         import std.range : repeat, take;
54 
55         repeat('#').take(curr_head + 1).copy(w);
56         put(w, " ");
57         formattedWrite(w, args);
58 
59         // two newlines because some markdown parsers do not correctly identify
60         // a heading if it isn't separated
61         put(w, newline);
62         put(w, newline);
63         return (typeof(this)(curr_head + 1, w, w_trace));
64     }
65 
66     auto popHeading() {
67         if (curr_head != 0)
68             put(w, newline);
69         return typeof(this)(curr_head - 1, w, w_trace);
70     }
71 
72     auto beginSyntaxBlock(ARGS...)(auto ref ARGS args) {
73         put(w, "```");
74         static if (ARGS.length != 0)
75             formattedWrite(w, args);
76         put(w, newline);
77         return this;
78     }
79 
80     auto endSyntaxBlock() {
81         put(w, "```");
82         put(w, newline);
83         return this;
84     }
85 
86     auto write(ARGS...)(auto ref ARGS args) {
87         formattedWrite(w, "%s", args);
88         return this;
89     }
90 
91     auto writef(ARGS...)(auto ref ARGS args) {
92         formattedWrite(w, args);
93         return this;
94     }
95 
96     auto writeln(ARGS...)(auto ref ARGS args) {
97         this.write(args);
98         put(w, newline);
99         return this;
100     }
101 
102     auto writefln(ARGS...)(auto ref ARGS args) {
103         this.writef(args);
104         put(w, newline);
105         return this;
106     }
107 
108     auto trace(ARGS...)(auto ref ARGS args) {
109         this.writeln(w_trace, args);
110         return this;
111     }
112 
113     auto tracef(ARGS...)(auto ref ARGS args) {
114         formattedWrite(w_trace, args);
115         put(w_trace, newline);
116         return this;
117     }
118 }
119 
120 /** Report mutations in a format easily readable by a human.
121  */
122 @safe final class ReportMarkdown : ReportEvent {
123     import std.conv : to;
124     import std.format : format, FormatSpec;
125     import dextool.plugin.mutate.backend.utility;
126 
127     static immutable col_w = 10;
128     static immutable mutation_w = 10 + 8 + 8;
129 
130     const Mutation.Kind[] kinds;
131     const ReportLevel report_level;
132     FilesysIO fio;
133 
134     Markdown!(SimpleWriter, SimpleWriter) markdown;
135     Markdown!(SimpleWriter, SimpleWriter) markdown_loc;
136     Markdown!(SimpleWriter, SimpleWriter) markdown_sum;
137 
138     Table!5 mut_tbl;
139     alias Row = Table!(5).Row;
140 
141     long[MakeMutationTextResult] mutationStat;
142 
143     alias Writer = function(const(char)[] s) { import std.stdio : write;
144 
145     write(s); };
146 
147     this(Mutation.Kind[] kinds, ReportLevel report_level, FilesysIO fio) {
148         this.kinds = kinds;
149         this.report_level = report_level;
150         this.fio = fio;
151     }
152 
153     override void mutationKindEvent(const MutationKind[] kind_) {
154         auto writer = delegate(const(char)[] s) {
155             import std.stdio : write;
156 
157             write(s);
158         };
159 
160         SimpleWriter tracer;
161         if (report_level == ReportLevel.all) {
162             tracer = writer;
163         } else {
164             tracer = delegate(const(char)[] s) {  };
165         }
166 
167         markdown = Markdown!(SimpleWriter, SimpleWriter)(writer, tracer);
168         markdown = markdown.heading("Mutation Type %(%s, %)", kind_);
169     }
170 
171     override void locationStartEvent() {
172         if (report_level == ReportLevel.summary)
173             return;
174         markdown_loc = markdown.heading("Mutants");
175         mut_tbl.heading = ["From", "To", "File Line:Column", "ID", "Status"];
176     }
177 
178     override void locationEvent(const ref IterateMutantRow r) @trusted {
179         void report() {
180             MakeMutationTextResult mut_txt;
181             try {
182                 auto abs_path = AbsolutePath(FileName(r.file), DirName(fio.getOutputDir));
183                 mut_txt = makeMutationText(fio.makeInput(abs_path),
184                         r.mutationPoint.offset, r.mutation.kind);
185 
186                 if (r.mutation.status == Mutation.Status.alive) {
187                     if (auto v = mut_txt in mutationStat)
188                         ++(*v);
189                     else
190                         mutationStat[mut_txt] = 1;
191                 }
192             }
193             catch (Exception e) {
194                 logger.warning(e.msg);
195             }
196 
197             // dfmt off
198             Row r_ = [
199                 format("`%s`", window(mut_txt.original, windowSize)),
200                 format("`%s`", window(mut_txt.mutation, windowSize)),
201                 format("%s %s:%s", r.file, r.sloc.line, r.sloc.column),
202                 r.id.to!string,
203                 r.mutation.status.to!string,
204             ];
205             mut_tbl.put(r_);
206             // dfmt on
207         }
208 
209         try {
210             final switch (report_level) {
211             case ReportLevel.summary:
212                 break;
213             case ReportLevel.alive:
214                 if (r.mutation.status == Mutation.Status.alive) {
215                     report();
216                 }
217                 break;
218             case ReportLevel.all:
219                 report();
220                 break;
221             }
222         }
223         catch (Exception e) {
224             logger.trace(e.msg).collectException;
225         }
226     }
227 
228     override void locationEndEvent() {
229         if (report_level == ReportLevel.summary)
230             return;
231 
232         auto writer = delegate(const(char)[] s) {
233             import std.stdio : write;
234 
235             write(s);
236         };
237 
238         import std.format : FormatSpec;
239 
240         auto fmt = FormatSpec!char("%s");
241         mut_tbl.toString(writer, fmt);
242 
243         markdown_loc.popHeading;
244     }
245 
246     override void locationStatEvent() {
247         if (mutationStat.length != 0 && report_level != ReportLevel.summary) {
248             auto item = markdown.heading("Alive Mutation Statistics");
249 
250             Table!4 substat_tbl;
251             Table!4.Row SRow;
252 
253             substat_tbl.heading = ["Percentage", "Count", "From", "To"];
254             reportMutationSubtypeStats(mutationStat, substat_tbl);
255 
256             auto fmt = FormatSpec!char("%s");
257             substat_tbl.toString(Writer, fmt);
258             item.popHeading;
259         }
260     }
261 
262     override void statEvent(ref Database db) {
263         markdown_sum = markdown.heading("Summary");
264 
265         markdown_sum.beginSyntaxBlock;
266         reportStatistics(db, kinds, markdown_sum);
267         markdown_sum.endSyntaxBlock;
268 
269         markdown_sum.popHeading;
270     }
271 }