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 do { 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 do { 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 do { 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 do { 146 import std.algorithm : each, map; 147 import std.array : array; 148 149 if (st == State.forceInclude) 150 return; 151 152 st = State.waiting; 153 154 // no paths added, nothing to do 155 if (work_pool.length == 0) 156 return; 157 158 () @trusted { 159 stripIncl(work_pool.map!(a => a.filename).array, strip_incl).each!( 160 a => permanent_pool.insert(Include(a))); 161 }(); 162 work_pool = null; 163 } 164 165 void put(string fname, LocationType type, PayloadT kind = PayloadT.init) @safe 166 in { 167 import std.utf : validate; 168 169 validate((cast(string) fname)); 170 } 171 do { 172 auto val = Include(fname, kind); 173 174 switch (st) with (State) { 175 case waiting: 176 work_pool ~= val; 177 if (type == LocationType.Root) { 178 st = rootInclude; 179 } else { 180 st = symbolInclude; 181 } 182 break; 183 184 case rootInclude: 185 st = rootInclude; 186 if (type == LocationType.Root) { 187 work_pool ~= val; 188 } 189 break; 190 191 case symbolInclude: 192 if (type == LocationType.Root) { 193 work_pool = [val]; // root override previous pool values 194 st = rootInclude; 195 } else { 196 work_pool ~= val; 197 } 198 break; 199 200 case forceInclude: 201 // ignore 202 break; 203 204 default: 205 assert(0); 206 } 207 } 208 209 string toString() @safe const { 210 import std.exception : assumeUnique; 211 212 char[] buf; 213 buf.reserve(100); 214 this.toString((const(char)[] s) { buf ~= s; }); 215 auto trustedUnique(T)(T t) @trusted { 216 return assumeUnique(t); 217 } 218 219 return trustedUnique(buf); 220 } 221 222 void toString(Writer)(scope Writer w) const { 223 import std.algorithm : copy, joiner, map; 224 import std.ascii : newline; 225 import std.conv : to; 226 import std.range : chain, only; 227 import std.range.primitives : put; 228 229 put(w, st.to!string()); 230 put(w, newline); 231 // dfmt off 232 chain(work_pool.map!(a => cast(string) a), 233 permanent_pool[].map!(a => cast(string) a)) 234 .joiner(newline) 235 .copy(w); 236 // dfmt on 237 } 238 } 239 240 /// Use when no extra payload is needed. 241 alias TestDoubleIncludes = GenericTestDoubleIncludes!(DummyPayload); 242 243 /** Strip the filename with the regexp or if that fails use the input filename as is. 244 */ 245 string stripFile(string fname, Regex!char re) @trusted { 246 import std.array : appender; 247 import std.algorithm : copy; 248 import std.range : dropOne; 249 import std.regex : matchFirst; 250 251 if (re.empty) { 252 return fname; 253 } 254 255 auto c = matchFirst(fname, re); 256 auto rval = fname; 257 258 debug logger.tracef("input is '%s'. After strip: %s", fname, c); 259 260 if (!c.empty) { 261 auto app = appender!string(); 262 c.dropOne.copy(app); 263 rval = app.data; 264 } 265 266 return rval; 267 } 268 269 /** Fixup the includes to be ready for usage as #include. 270 * 271 * Deduplicate. 272 * Strip the includes according to the user supplied configuration. 273 */ 274 private string[] stripIncl(string[] incls, Regex!char re) { 275 import std.array : array; 276 import std.algorithm : cache, map, filter; 277 import cpptooling.utility : dedup; 278 279 // dfmt off 280 auto r = dedup(incls) 281 .map!(a => stripFile(a, re)) 282 .filter!(a => a.length > 0) 283 .array(); 284 // dfmt on 285 286 return r; 287 }