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 }