-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathdscan.d
321 lines (250 loc) · 7.17 KB
/
dscan.d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
/++
This program builds `dscanner`, does a recursive style check with it of the
`source` directory (or whatever is specified at the command line), builds
documentation with `dub build -b docs`, and finally returns `0` or non-`0`,
depending on whether all commands executed without errors.
Each line of dscanner output is matched with the regular expressions in
`dscan.txt`, and are culled if there is a hit. As such, the remaining output
reflects the dscanner errors for which there are no regular expressions,
constituting a new set of errors that should be addressed.
If a `dscan.txt` expression did not apply to any lines in the output,
it is printed as dead.
This is to be run as part of a CI pipeline.
Copyright: [JR](https://github.com/zorael)
License: [Boost Software License 1.0](https://www.boost.org/users/license.html)
Authors:
[JR](https://github.com/zorael)
+/
module zorael.dscan;
private:
import std.regex : Regex;
import std.stdio;
// main
/++
Main.
Params:
args = Command-line arguments passed to the program.
Returns:
`0` on success; non-`0` on failure.
+/
public auto main(string[] args)
{
import std.file : FileException;
import std.regex : RegexException;
enum defaultExpressionFile = "dscan.txt";
enum defaultTarget = "source";
immutable expressionsFile = (args.length > 1) ?
args[1] :
defaultExpressionFile;
immutable target = (args.length > 2) ?
args[2] :
defaultTarget;
string[] expressions;
Regex!char[] engines;
try
{
import std.algorithm.iteration : map;
import std.array : array;
import std.regex : regex;
expressions = readExpressions(expressionsFile);
engines = expressions.map!(expr => expr.regex).array;
}
catch (FileException e)
{
writeln(i"[!] failed to read expressions file; $(e.msg)");
return 1;
}
catch (RegexException e)
{
writeln(i"[!] failed to create regex engine(s); $(e.msg)");
return 1;
}
writeln(i"[*] $(expressions.length) expression(s) loaded from $(expressionsFile)");
int retval = buildDscanner();
if (retval != 0)
{
// No point continuing if dscanner failed to build
writeln();
writeln(i"[+] abort, return $(retval)");
return retval;
}
retval |= runDscanner(engines, expressions, target);
retval |= buildDocs();
writeln();
writeln(i"[+] done, return $(retval)");
return retval;
}
// buildDscanner
/++
Simply invokes `dub build dscanner`.
Returns:
The shell return value of the command run.
+/
auto buildDscanner()
{
import std.datetime.stopwatch : StopWatch;
import std.process : execute;
StopWatch sw;
static immutable command =
[
"dub",
"build",
"dscanner",
];
writeln();
writeln("[+] building dscanner...");
sw.start();
const result = execute(command);
sw.stop();
immutable wording = (result.status == 0) ?
"finished building" :
"failed to build";
writeln(i"[!] $(wording) in $(sw.peek), retval $(result.status)");
if (result.status != 0)
{
import std.string : strip;
writeln();
writeln(result.output.strip());
}
return result.status;
}
// runDscanner
/++
Simply invokes `dub run dscanner --nodeps --vquiet -- --skipTests -S $(dir)`.
Params:
engines = Regular expression matching engines.
expressions = List of regular expression strings from which `engines`
were created.
target = Directory or file to scan, such as `source`.
Returns:
The shell return value of the command run.
+/
auto runDscanner(
const Regex!char[] engines,
const string[] expressions,
const string target)
{
import std.algorithm.iteration : splitter;
import std.array : Appender;
import std.datetime.stopwatch : StopWatch;
import std.process : execute;
import std.range : walkLength;
import std.string : strip;
Appender!(string[]) uncaughtLines;
StopWatch sw;
auto engineMatches = new bool[engines.length];
immutable command =
[
"dub",
"run",
"dscanner",
"--nodeps",
"--vquiet",
"--",
"--skipTests",
"-S",
target,
];
writeln();
writeln(i"[+] invoking: $(command)");
sw.start();
const result = execute(command);
sw.stop();
writeln(i"[!] finished scanning in $(sw.peek), retval irrelevant");
auto range = result.output
.strip()
.splitter("\n");
foreach (const line; range)
{
bool atLeastOneMatch;
foreach (immutable i, engine; engines)
{
import std.regex : matchFirst;
const regexMatches = line.matchFirst(engine);
if (!regexMatches.empty)
{
engineMatches[i] = true;
atLeastOneMatch = true;
}
}
if (!atLeastOneMatch) uncaughtLines.put(line);
}
immutable numLines = range.walkLength();
int retval;
if (uncaughtLines[].length)
{
import std.algorithm.iteration : each;
writeln();
uncaughtLines[].each!writeln();
writeln();
writeln(i"[!] $(numLines) line(s) caught by regex");
writeln(i"[!] $(uncaughtLines[].length) line(s) got past regex, retval becomes $(result.status)");
retval = result.status;
}
else
{
writeln(i"[!] all $(numLines) line(s) caught by regex");
}
foreach (immutable i, engineMatched; engineMatches)
{
if (!engineMatched)
{
writeln(i`[!] dead expression: "$(expressions[i])"`);
//retval |= 1;
}
}
return retval;
}
// buildDocs
/++
Simply invokes `dub build -b docs`.`
Returns:
The shell return value of the command run.
+/
auto buildDocs()
{
import std.datetime.stopwatch : StopWatch;
import std.process : execute;
import std.string : strip;
StopWatch sw;
static immutable command =
[
"dub",
"build",
"-b",
"docs",
"--nodeps",
//"--vquiet",
];
writeln();
writeln("[+] building docs...");
sw.start();
const result = execute(command);
sw.stop();
writeln(i"[!] docs built in $(sw.peek), retval $(result.status)");
writeln();
writeln(result.output.strip());
return result.status;
}
// readExpressions
/++
Reads regular expressions from a specified file.
Params:
expressionsFile = Filename of file to read regular expression lines from.
Returns:
A `string[]` array of regular expressions, read from file.
+/
auto readExpressions(const string expressionsFile)
{
import std.algorithm.iteration : filter, map, splitter;
import std.algorithm.searching : startsWith;
import std.array : array;
import std.file : readText;
import std.string : strip;
return expressionsFile
.readText()
.splitter("\n")
.map!(line => line.strip())
.filter!(line => line.length && !line.startsWith("#"))
.array;
}