1 /** 2 Copyright: Copyright (c) 2015-2017, 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 module cpptooling.testdouble.header_filter; 11 12 import std.regex : Regex; 13 import logger = std.experimental.logger; 14 15 enum LocationType { 16 Root, 17 Leaf 18 } 19 20 enum DummyPayload { 21 none 22 } 23 24 /** Includes intended for the test double. 25 * 26 * Filtered according to the user. 27 * 28 * State diagram in plantuml: 29 * @startuml 30 * [*] -> Waiting 31 * Waiting: Initialize pool 32 * 33 * Waiting -> SymbolInclude: Add 34 * Waiting --> RootInclude: Add root 35 * Waiting --> UserInclude: Add user 36 * Waiting --> Finalize: Done 37 * 38 * UserInclude: Static pool from CLI 39 * 40 * SymbolInclude: Temp pool of includes 41 * SymbolInclude --> SymbolInclude: Add 42 * SymbolInclude --> Process: Done 43 * SymbolInclude --> RootInclude: Clear pool, add root 44 * 45 * RootInclude: Temp pool of includes 46 * RootInclude --> RootInclude: Add 47 * RootInclude --> Process: Done 48 * 49 * Process: Temp pool to permanent pool 50 * Process --> Waiting 51 * 52 * Finalize: strip includes 53 * Finalize: Data ready to be used 54 * @enduml 55 * 56 * Params: 57 * PayloadT = extra data associated with a particulare file such as the language. 58 */ 59 struct GenericTestDoubleIncludes(PayloadT) { 60 import std.regex : Regex; 61 import std.container : RedBlackTree; 62 63 private enum State { 64 waiting, 65 symbolInclude, 66 rootInclude, 67 process, 68 finalize, 69 forceInclude 70 } 71 72 struct Include { 73 string filename; 74 PayloadT payload; 75 alias filename this; 76 } 77 78 private { 79 RedBlackTree!Include permanent_pool; 80 Include[] work_pool; 81 82 State st; 83 Regex!char strip_incl; 84 string[] unstripped_incls; 85 } 86 87 @disable this(); 88 89 this(Regex!char strip_incl) { 90 import std.container : make; 91 92 this.strip_incl = strip_incl; 93 this.permanent_pool = make!(typeof(this.permanent_pool)); 94 } 95 96 auto includes() @safe pure nothrow @nogc 97 in { 98 assert(st == State.finalize); 99 } 100 body { 101 import std.algorithm : map; 102 103 return permanent_pool[].map!(a => a.filename); 104 } 105 106 auto includesWithPayload() @safe pure nothrow @nogc 107 in { 108 assert(st == State.finalize); 109 } 110 body { 111 return permanent_pool[]; 112 } 113 114 /** Replace buffer of includes with argument. 115 * 116 * See description of states to understand what UserDefined entitles. 117 * 118 * TODO this contains a possible bug because the kind is not provided. 119 */ 120 void forceIncludes(string[] in_incls) { 121 import std.algorithm : each; 122 123 st = State.forceInclude; 124 125 /// Assuming user defined includes are good as they are so no stripping. 126 () @trusted{ in_incls.each!(a => permanent_pool.insert(Include(a))); }(); 127 } 128 129 void finalize() @safe pure nothrow @nogc 130 in { 131 import std.algorithm : among; 132 133 assert(st.among(State.waiting, State.forceInclude)); 134 } 135 body { 136 st = State.finalize; 137 } 138 139 void process() @safe 140 in { 141 import std.algorithm : among; 142 143 assert(st.among(State.waiting, State.rootInclude, State.symbolInclude, State.forceInclude)); 144 } 145 body { 146 import std.algorithm : each; 147 148 if (st == State.forceInclude) 149 return; 150 151 st = State.waiting; 152 153 // no paths added, nothing to do 154 if (work_pool.length == 0) 155 return; 156 157 () @trusted{ 158 stripIncl(work_pool, strip_incl).each!(a => permanent_pool.insert(a)); 159 }(); 160 work_pool.length = 0; 161 } 162 163 void put(string fname, LocationType type, PayloadT kind = PayloadT.init) @safe 164 in { 165 import std.utf : validate; 166 167 validate((cast(string) fname)); 168 } 169 body { 170 auto val = Include(fname, kind); 171 172 switch (st) with (State) { 173 case waiting: 174 work_pool ~= val; 175 if (type == LocationType.Root) { 176 st = rootInclude; 177 } else { 178 st = symbolInclude; 179 } 180 break; 181 182 case rootInclude: 183 st = rootInclude; 184 if (type == LocationType.Root) { 185 work_pool ~= val; 186 } 187 break; 188 189 case symbolInclude: 190 if (type == LocationType.Root) { 191 work_pool = [val]; // root override previous pool values 192 st = rootInclude; 193 } else { 194 work_pool ~= val; 195 } 196 break; 197 198 case forceInclude: 199 // ignore 200 break; 201 202 default: 203 assert(0); 204 } 205 } 206 207 string toString() @safe const { 208 import std.exception : assumeUnique; 209 210 char[] buf; 211 buf.reserve(100); 212 this.toString((const(char)[] s) { buf ~= s; }); 213 auto trustedUnique(T)(T t) @trusted { 214 return assumeUnique(t); 215 } 216 217 return trustedUnique(buf); 218 } 219 220 void toString(Writer)(scope Writer w) const { 221 import std.algorithm : copy, joiner, map; 222 import std.ascii : newline; 223 import std.conv : to; 224 import std.range : chain, only; 225 import std.range.primitives : put; 226 227 put(w, st.to!string()); 228 put(w, newline); 229 // dfmt off 230 chain(work_pool.map!(a => cast(string) a), 231 permanent_pool[].map!(a => cast(string) a)) 232 .joiner(newline) 233 .copy(w); 234 // dfmt on 235 } 236 } 237 238 /// Use when no extra payload is needed. 239 alias TestDoubleIncludes = GenericTestDoubleIncludes!(DummyPayload); 240 241 /** Strip the filename with the regexp or if that fails use the input filename as is. 242 */ 243 T stripFile(T)(T fname, Regex!char re) @trusted { 244 import std.array : appender; 245 import std.algorithm : copy; 246 import std.range : dropOne; 247 import std.regex : matchFirst; 248 249 if (re.empty) { 250 return fname; 251 } 252 253 auto c = matchFirst(cast(string) fname, re); 254 auto rval = fname; 255 256 debug logger.tracef("input is '%s'. After strip: %s", fname, c); 257 258 if (!c.empty) { 259 auto app = appender!string(); 260 c.dropOne.copy(app); 261 rval = app.data; 262 } 263 264 return rval; 265 } 266 267 /** Fixup the includes to be ready for usage as #include. 268 * 269 * Deduplicate. 270 * Strip the includes according to the user supplied configuration. 271 */ 272 private T[] stripIncl(T)(ref T[] incls, Regex!char re) { 273 import std.array : array; 274 import std.algorithm : cache, map, filter; 275 import cpptooling.utility : dedup; 276 277 // dfmt off 278 auto r = dedup(incls) 279 .map!(a => stripFile(a, re)) 280 .filter!(a => a.length > 0) 281 .array(); 282 // dfmt on 283 284 return r; 285 }