OILS / test / wild_report.py View on Github | oilshell.org

813 lines, 298 significant
1#!/usr/bin/env python2
2from __future__ import print_function
3"""
4wild_report.py
5"""
6
7import json
8import optparse
9import os
10import sys
11
12from vendor import jsontemplate
13
14# JSON Template Evaluation:
15#
16# - {.if}{.or} is confusing
17# I think there is even a bug with {.if}{.else}{.end} -- it accepts it but
18# doesn't do the right thing!
19# - {.if test} does work though, but it took me awhile to remember that or
20# - I forgot about {.link?} too
21# even find it in the source code. I don't like this separate predicate
22# language. Could just be PHP-ish I guess.
23# - Predicates are a little annoying.
24# - Lack of location information on undefined variables is annoying. It spews
25# a big stack trace.
26# - The styles thing seems awkward. Copied from srcbook.
27# - I don't have {total_secs|%.3f} , but the
28# LookupChain/DictRegistry/CallableRegistry thing is quite onerous.
29#
30# Good parts:
31# Just making one big dict is pretty nice.
32
33T = jsontemplate.Template
34
35def F(format_str):
36 # {x|commas}
37 if format_str == 'commas':
38 return lambda n: '{:,}'.format(n)
39
40 # {x|printf %.1f}
41 if format_str.startswith('printf '):
42 fmt = format_str[len('printf '):]
43 return lambda value: fmt % value
44
45 #'urlesc': urllib.quote_plus,
46 return None
47
48
49def MakeHtmlGroup(title_str, body_str):
50 """Make a group of templates that we can expand with a common style."""
51 return {
52 'TITLE': T(title_str, default_formatter='html', more_formatters=F),
53 'BODY': T(body_str, default_formatter='html', more_formatters=F),
54 'NAV': NAV_TEMPLATE,
55 }
56
57BODY_STYLE = jsontemplate.Template("""\
58<!DOCTYPE html>
59<html>
60 <head>
61 <meta name="viewport" content="width=device-width, initial-scale=1">
62 <title>{.template TITLE}</title>
63
64 <script type="text/javascript" src="{base_url}../../web/ajax.js"></script>
65 <script type="text/javascript" src="{base_url}../../web/table/table-sort.js"></script>
66 <link rel="stylesheet" type="text/css" href="{base_url}../../web/base.css" />
67 <link rel="stylesheet" type="text/css" href="{base_url}../../web/table/table-sort.css" />
68 <link rel="stylesheet" type="text/css" href="{base_url}../../web/wild.css" />
69 </head>
70
71 <body onload="initPage(gUrlHash, gTables, gTableStates, kStatusElem);"
72 onhashchange="onHashChange(gUrlHash, gTableStates, kStatusElem);"
73 class="width60">
74 <p id="status"></p>
75
76 <p style="text-align: right"><a href="/">oilshell.org</a></p>
77<p>
78{.template NAV}
79</p>
80
81{.template BODY}
82 </body>
83
84</html>
85""", default_formatter='html')
86
87# NOTE: {.link} {.or id?} {.or} {.end} doesn't work? That is annoying.
88NAV_TEMPLATE = jsontemplate.Template("""\
89{.section nav}
90<span id="nav">
91{.repeated section @}
92 {.link?}
93 <a href="{link|htmltag}">{anchor}</a>
94 {.or}
95 {anchor}
96 {.end}
97{.alternates with}
98 /
99{.end}
100</span>
101{.end}
102""", default_formatter='html')
103
104
105PAGE_TEMPLATES = {}
106
107# <a href="{base_url}osh-to-oil.html#{rel_path|htmltag}/{name|htmltag}">view</a>
108PAGE_TEMPLATES['FAILED'] = MakeHtmlGroup(
109 '{task}_failed',
110"""\
111<h1>{failures|size} {task} failures</h1>
112
113{.repeated section failures}
114 <a href="{base_url}osh-to-oil.html#{rel_path|htmltag}">{rel_path|html}</a>
115 <pre>
116 {stderr}
117 </pre>
118{.end}
119""")
120
121# One is used for sort order. One is used for alignment.
122# type="string"
123# should we use the column css class as the sort order? Why not?
124
125# NOTES on columns:
126# - The col is used to COLOR the column when it's being sorted by
127# - But it can't be use to align text right. See
128# https://stackoverflow.com/questions/1238115/using-text-align-center-in-colgroup
129# - type="number" is used in table-sort.js for the sort order.
130# - We use CSS classes on individual cells like <td class="name"> to align
131# columns. That seems to be the only way to do it?
132
133PAGE_TEMPLATES['LISTING'] = MakeHtmlGroup(
134 'WILD/{rel_path} - Parsing and Translating Shell Scripts with Oil',
135"""\
136
137{.section subtree_stats}
138<div id="summary">
139<ul>
140{.parse_failed?}
141 <li>
142 Attempted to parse <b>{num_files|commas}</b> shell scripts totalling
143 <b>{num_lines|commas}</b> lines.
144 </li>
145 {.not_shell?}
146 <li>
147 <b>{not_shell|commas}</b> files are known not to be shell.
148 {.if test top_level_links}
149 (<a href="not-shell.html">full list</a>)
150 {.end}
151 </li>
152 {.end}
153 {.not_osh?}
154 <li>
155 <b>{not_osh|commas}</b> files are known not to be OSH.
156 {.if test top_level_links}
157 (<a href="not-osh.html">full list</a>)
158 {.end}
159 </li>
160 {.end}
161 <li>
162 Failed to parse <b>{parse_failed|commas}</b> scripts, leaving
163 <b>{lines_parsed|commas}</b> lines parsed in <b>{parse_proc_secs|printf %.1f}</b>
164 seconds (<b>{lines_per_sec|printf %.1f}</b> lines/sec).
165 {.if test top_level_links}
166 (<a href="parse-failed.html">all failures</a>,
167 <a href="parse-failed.txt">text</a>)
168 {.end}
169 </li>
170{.or}
171 <li>
172 Successfully parsed <b>{num_files|commas}</b> shell scripts totalling
173 <b>{num_lines|commas}</b> lines
174 in <b>{parse_proc_secs|printf %.1f}</b> seconds
175 (<b>{lines_per_sec|printf %.1f}</b> lines/sec).
176 </li>
177{.end}
178
179<li>
180 <b>{osh2oil_failed|commas}</b> OSH-to-Oil translations failed.
181 {.if test top_level_links}
182 (<a href="osh2oil-failed.html">all failures</a>,
183 <a href="osh2oil-failed.txt">text</a>)
184 {.end}
185</li>
186</ul>
187</div>
188
189<p></p>
190{.end}
191
192
193{.section dirs}
194<table id="dirs">
195 <colgroup> <!-- for table-sort.js -->
196 <col type="number">
197 <col type="number">
198 <col type="number">
199 <col type="number">
200 <col type="number">
201 <col type="number">
202 <col type="number">
203 <col type="case-insensitive">
204 </colgroup>
205 <thead>
206 <tr>
207 <td>Files</td>
208 <td>Max Lines</td>
209 <td>Total Lines</td>
210 <!-- <td>Lines Parsed</td> -->
211 <td>Parse Failures</td>
212 <td>Max Parse Time (secs)</td>
213 <td>Total Parse Time (secs)</td>
214 <td>Translation Failures</td>
215 <td class="name">Directory</td>
216 </tr>
217 </thead>
218 <tbody>
219 {.repeated section @}
220 <tr>
221 <td>{num_files|commas}</td>
222 <td>{max_lines|commas}</td>
223 <td>{num_lines|commas}</td>
224 <!-- <td>{lines_parsed|commas}</td> -->
225 {.parse_failed?}
226 <td class="fail">{parse_failed|commas}</td>
227 {.or}
228 <td class="ok">{parse_failed|commas}</td>
229 {.end}
230 <td>{max_parse_secs|printf %.2f}</td>
231 <td>{parse_proc_secs|printf %.2f}</td>
232
233 {.osh2oil_failed?}
234 <!-- <td class="fail">{osh2oil_failed|commas}</td> -->
235 <td>{osh2oil_failed|commas}</td>
236 {.or}
237 <!-- <td class="ok">{osh2oil_failed|commas}</td> -->
238 <td>{osh2oil_failed|commas}</td>
239 {.end}
240
241 <td class="name">
242 <a href="{name|htmltag}/index.html">{name|html}/</a>
243 </td>
244 </tr>
245 {.end}
246 </tbody>
247</table>
248{.end}
249
250<p>
251</p>
252
253{.section files}
254<table id="files">
255 <colgroup> <!-- for table-sort.js -->
256 <col type="case-insensitive">
257 <col type="number">
258 <col type="case-insensitive">
259 <col type="number">
260 <col type="case-insensitive">
261 <col type="case-insensitive">
262 </colgroup>
263 <thead>
264 <tr>
265 <td>Side By Side</td>
266 <td>Lines</td>
267 <td>Parsed?</td>
268 <td>Parse Process Time (secs)</td>
269 <td>Translated?</td>
270 <td class="name">Filename</td>
271 </tr>
272 </thead>
273 <tbody>
274 {.repeated section @}
275 <tr>
276 <td>
277 <a href="{base_url}osh-to-oil.html#{rel_path|htmltag}/{name|htmltag}">view</a>
278 </td>
279 <td>{num_lines|commas}</td>
280 <td>
281 {.parse_failed?}
282 <a class="fail" href="#stderr_parse_{name}">FAIL</a>
283 <td>{parse_proc_secs}</td>
284 {.or}
285 <span class="ok">OK</a>
286 <td>{parse_proc_secs}</td>
287 {.end}
288 </td>
289
290 <td>
291 {.osh2oil_failed?}
292 <a class="fail" href="#stderr_osh2oil_{name}">FAIL</a>
293 {.or}
294 <a class="ok" href="{name}__ysh.txt">OK</a>
295 {.end}
296 </td>
297 <td class="name">
298 <a href="{name|htmltag}.txt">{name|html}</a>
299 </td>
300 </tr>
301 {.end}
302 </tbody>
303</table>
304{.end}
305
306{.if test empty}
307 <i>(empty dir)</i>
308{.end}
309
310{.section stderr}
311 <h2>stderr</h2>
312
313 <table id="stderr">
314
315 {.repeated section @}
316 <tr>
317 <td>
318 <a name="stderr_{action}_{name|htmltag}"></a>
319 {.if test parsing}
320 Parsing {name|html}
321 {.or}
322 Translating {name|html}
323 {.end}
324 </td>
325 <td>
326 <pre>
327 {contents|html}
328 </pre>
329 </td>
330 <tr/>
331 {.end}
332
333 </table>
334{.end}
335
336{.if test top_level_links}
337<a href="version-info.txt">Date and OSH version<a>
338{.end}
339
340<!-- page globals -->
341<script type="text/javascript">
342 var gUrlHash = new UrlHash(location.hash);
343 var gTableStates = {};
344 var kStatusElem = document.getElementById('status');
345
346 var gTables = [];
347 var e1 = document.getElementById('dirs');
348 var e2 = document.getElementById('files');
349
350 // If no hash, "redirect" to a state where we sort ascending by dir name and
351 // filename. TODO: These column numbers are a bit fragile.
352 var params = [];
353 if (e1) {
354 gTables.push(e1);
355 params.push('t:dirs=8a');
356 }
357 if (e2) {
358 gTables.push(e2);
359 params.push('t:files=7a');
360 }
361
362 function initPage(urlHash, gTables, tableStates, statusElem) {
363 makeTablesSortable(urlHash, gTables, tableStates);
364 /* Disable for now, this seems odd? Think about mutability of gUrlHash.
365 if (location.hash === '') {
366 document.location = '#' + params.join('&');
367 gUrlHash = new UrlHash(location.hash);
368 }
369 */
370 updateTables(urlHash, tableStates, statusElem);
371 }
372
373 function onHashChange(urlHash, tableStates, statusElem) {
374 updateTables(urlHash, tableStates, statusElem);
375 }
376</script>
377""")
378
379
380def log(msg, *args):
381 if msg:
382 msg = msg % args
383 print(msg, file=sys.stderr)
384
385
386class DirNode:
387 """Entry in the file system tree."""
388
389 def __init__(self):
390 self.files = {} # filename -> stats for success/failure, time, etc.
391 self.dirs = {} # subdir name -> DirNode object
392
393 self.subtree_stats = {} # name -> value
394
395 # show all the non-empty stderr here?
396 # __osh2oil.stderr.txt
397 # __parse.stderr.txt
398 self.stderr = []
399
400
401def UpdateNodes(node, path_parts, file_stats):
402 """
403 Create a file node and update the stats of all its descendants in the FS
404 tree.
405 """
406 first = path_parts[0]
407 rest = path_parts[1:]
408
409 for name, value in file_stats.iteritems():
410 # Sum numerical properties, but not strings
411 if isinstance(value, int) or isinstance(value, float):
412 if name in node.subtree_stats:
413 node.subtree_stats[name] += value
414 else:
415 # NOTE: Could be int or float!!!
416 node.subtree_stats[name] = value
417
418 # Calculate maximums
419 m = node.subtree_stats.get('max_parse_secs', 0.0)
420 node.subtree_stats['max_parse_secs'] = max(m, file_stats['parse_proc_secs'])
421
422 m = node.subtree_stats.get('max_lines', 0) # integer
423 node.subtree_stats['max_lines'] = max(m, file_stats['num_lines'])
424
425 if rest: # update an intermediate node
426 if first in node.dirs:
427 child = node.dirs[first]
428 else:
429 child = DirNode()
430 node.dirs[first] = child
431
432 UpdateNodes(child, rest, file_stats)
433 else:
434 # TODO: Put these in different sections? Or least one below the other?
435
436 # Include stderr if non-empty, or if FAILED
437 parse_stderr = file_stats.pop('parse_stderr')
438 if parse_stderr or file_stats['parse_failed']:
439 node.stderr.append({
440 'parsing': True,
441 'action': 'parse',
442 'name': first,
443 'contents': parse_stderr,
444 })
445 osh2oil_stderr = file_stats.pop('osh2oil_stderr')
446
447 # TODO: Could disable this with a flag to concentrate on parse errors.
448 # Or just show parse errors all in one file.
449 if 1:
450 if osh2oil_stderr or file_stats['osh2oil_failed']:
451 node.stderr.append({
452 'parsing': False,
453 'action': 'osh2oil',
454 'name': first,
455 'contents': osh2oil_stderr,
456 })
457
458 # Attach to this dir
459 node.files[first] = file_stats
460
461
462def DebugPrint(node, indent=0):
463 """Debug print."""
464 ind = indent * ' '
465 #print('FILES', node.files.keys())
466 for name in node.files:
467 print('%s%s - %s' % (ind, name, node.files[name]))
468 for name, child in node.dirs.iteritems():
469 print('%s%s/ - %s' % (ind, name, child.subtree_stats))
470 DebugPrint(child, indent=indent+1)
471
472
473def WriteJsonFiles(node, out_dir):
474 """Write a index.json file for every directory."""
475 path = os.path.join(out_dir, 'index.json')
476 with open(path, 'w') as f:
477 raise AssertionError # fix dir_totals
478 d = {'files': node.files, 'dirs': node.dir_totals}
479 json.dump(d, f)
480
481 log('Wrote %s', path)
482
483 for name, child in node.dirs.iteritems():
484 WriteJsonFiles(child, os.path.join(out_dir, name))
485
486
487def MakeNav(rel_path, root_name='WILD', offset=0):
488 """
489 Args:
490 offset: for doctools/src_tree.py to render files
491 """
492 assert not rel_path.startswith('/'), rel_path
493 assert not rel_path.endswith('/'), rel_path
494 # Get rid of ['']
495 parts = [root_name] + [p for p in rel_path.split('/') if p]
496 data = []
497 n = len(parts)
498 for i, p in enumerate(parts):
499 if i == n - 1:
500 link = None # Current page shouldn't have link
501 else:
502 # files need to link to .
503 link = '../' * (n - 1 - i + offset) + 'index.html'
504 data.append({'anchor': p, 'link': link})
505 return data
506
507
508def _Lower(s):
509 return s.lower()
510
511
512def WriteHtmlFiles(node, out_dir, rel_path='', base_url=''):
513 """Write a index.html file for every directory.
514
515 NOTE:
516 - osh-to-oil.html lives at $base_url
517 - table-sort.js lives at $base_url/../table-sort.js
518
519 wild/
520 table-sort.js
521 table-sort.css
522 www/
523 index.html
524 osh-to-oil.html
525
526 wild/
527 table-sort.js
528 table-sort.css
529 wild.wwz/ # Zip file
530 index.html
531 osh-to-oil.html
532
533 wwz latency is subject to caching headers.
534 """
535 files = []
536 for name in sorted(node.files, key=_Lower):
537 stats = node.files[name]
538 entry = dict(stats)
539 entry['name'] = name
540 # TODO: This should be internal time
541 entry['lines_per_sec'] = entry['lines_parsed'] / entry['parse_proc_secs']
542 files.append(entry)
543
544 dirs = []
545 for name in sorted(node.dirs, key=_Lower):
546 entry = dict(node.dirs[name].subtree_stats)
547 entry['name'] = name
548 # TODO: This should be internal time
549 entry['lines_per_sec'] = entry['lines_parsed'] / entry['parse_proc_secs']
550 dirs.append(entry)
551
552 # TODO: Is there a way to make this less redundant?
553 st = node.subtree_stats
554 try:
555 st['lines_per_sec'] = st['lines_parsed'] / st['parse_proc_secs']
556 except KeyError:
557 # This usually there were ZERO files.
558 print(node, st, repr(rel_path), file=sys.stderr)
559 raise
560
561 data = {
562 'rel_path': rel_path,
563 'subtree_stats': node.subtree_stats, # redundant totals
564 'files': files,
565 'dirs': dirs,
566 'base_url': base_url,
567 'stderr': node.stderr,
568 'nav': MakeNav(rel_path),
569 }
570 # Hack to add links for top level page:
571 if rel_path == '':
572 data['top_level_links'] = True
573
574 group = PAGE_TEMPLATES['LISTING']
575 body = BODY_STYLE.expand(data, group=group)
576
577 path = os.path.join(out_dir, 'index.html')
578 with open(path, 'w') as f:
579 f.write(body)
580
581 log('Wrote %s', path)
582
583 # Recursive
584 for name, child in node.dirs.iteritems():
585 child_out = os.path.join(out_dir, name)
586 child_rel = os.path.join(rel_path, name)
587 child_base = base_url + '../'
588 WriteHtmlFiles(child, child_out, rel_path=child_rel, base_url=child_base)
589
590
591def _ReadTaskFile(path):
592 """
593 Parses the a file that looks like '0 0.11', for the status code and timing.
594 This is output by test/common.sh run-task-with-status.
595 """
596 try:
597 with open(path) as f:
598 parts = f.read().split()
599 status, secs = parts
600 except ValueError as e:
601 log('ERROR reading %s: %s', path, e)
602 raise
603 # Turn it into pass/fail
604 num_failed = 1 if int(status) >= 1 else 0
605 return num_failed, float(secs)
606
607
608def _ReadLinesToSet(path):
609 """Read blacklist files like not-shell.txt and not-osh.txt.
610
611 TODO: Consider adding globs here? There are a lot of FreeBSD and illumos
612 files we want to get rid of.
613
614 Or we could probably do that in the original 'find' expression.
615 """
616 result = set()
617 if not path:
618 return result
619
620 with open(path) as f:
621 for line in f:
622 # Allow comments. We assume filenames don't have #
623 i = line.find('#')
624 if i != -1:
625 line = line[:i]
626
627 line = line.strip()
628 if not line: # Lines that are blank or only comments.
629 continue
630
631 result.add(line)
632
633 return result
634
635
636def SumStats(stdin, in_dir, not_shell, not_osh, root_node, failures):
637 """Reads pairs of paths from stdin, and updates root_node."""
638 # Collect work into dirs
639 for line in stdin:
640 rel_path, abs_path = line.split()
641 #print proj, '-', abs_path, '-', rel_path
642
643 raw_base = os.path.join(in_dir, rel_path)
644 st = {}
645
646 st['not_shell'] = 1 if rel_path in not_shell else 0
647 st['not_osh'] = 1 if rel_path in not_osh else 0
648 if st['not_shell'] and st['not_osh']:
649 raise RuntimeError(
650 "%r can't be in both not-shell.txt and not-osh.txt" % rel_path)
651
652 expected_failure = bool(st['not_shell'] or st['not_osh'])
653
654 parse_task_path = raw_base + '__parse.task.txt'
655 parse_failed, st['parse_proc_secs'] = _ReadTaskFile(
656 parse_task_path)
657 st['parse_failed'] = 0 if expected_failure else parse_failed
658
659 with open(raw_base + '__parse.stderr.txt') as f:
660 st['parse_stderr'] = f.read()
661
662 if st['not_shell']:
663 failures.not_shell.append(
664 {'rel_path': rel_path, 'stderr': st['parse_stderr']}
665 )
666 if st['not_osh']:
667 failures.not_osh.append(
668 {'rel_path': rel_path, 'stderr': st['parse_stderr']}
669 )
670 if st['parse_failed']:
671 failures.parse_failed.append(
672 {'rel_path': rel_path, 'stderr': st['parse_stderr']}
673 )
674
675 osh2oil_task_path = raw_base + '__ysh-ify.task.txt'
676 osh2oil_failed, st['osh2oil_proc_secs'] = _ReadTaskFile(
677 osh2oil_task_path)
678
679 # Only count translation failures if the parse succeeded!
680 st['osh2oil_failed'] = osh2oil_failed if not parse_failed else 0
681
682 with open(raw_base + '__ysh-ify.stderr.txt') as f:
683 st['osh2oil_stderr'] = f.read()
684
685 if st['osh2oil_failed']:
686 failures.osh2oil_failed.append(
687 {'rel_path': rel_path, 'stderr': st['osh2oil_stderr']}
688 )
689
690 wc_path = raw_base + '__wc.txt'
691 with open(wc_path) as f:
692 st['num_lines'] = int(f.read().split()[0])
693 # For lines per second calculation
694 st['lines_parsed'] = 0 if st['parse_failed'] else st['num_lines']
695
696 st['num_files'] = 1
697
698 path_parts = rel_path.split('/')
699 #print path_parts
700 UpdateNodes(root_node, path_parts, st)
701
702
703class Failures(object):
704 """Simple object that gets transformed to HTML and text."""
705 def __init__(self):
706 self.parse_failed = []
707 self.osh2oil_failed = []
708 self.not_shell = []
709 self.not_osh = []
710
711 def Write(self, out_dir):
712 with open(os.path.join(out_dir, 'parse-failed.txt'), 'w') as f:
713 for failure in self.parse_failed:
714 print(failure['rel_path'], file=f)
715
716 with open(os.path.join(out_dir, 'osh2oil-failed.txt'), 'w') as f:
717 for failure in self.osh2oil_failed:
718 print(failure['rel_path'], file=f)
719
720 base_url = ''
721
722 with open(os.path.join(out_dir, 'not-shell.html'), 'w') as f:
723 data = {
724 'task': 'not-shell', 'failures': self.not_shell, 'base_url': base_url
725 }
726 body = BODY_STYLE.expand(data, group=PAGE_TEMPLATES['FAILED'])
727 f.write(body)
728
729 with open(os.path.join(out_dir, 'not-osh.html'), 'w') as f:
730 data = {
731 'task': 'not-osh', 'failures': self.not_osh, 'base_url': base_url
732 }
733 body = BODY_STYLE.expand(data, group=PAGE_TEMPLATES['FAILED'])
734 f.write(body)
735
736 with open(os.path.join(out_dir, 'parse-failed.html'), 'w') as f:
737 data = {
738 'task': 'parse', 'failures': self.parse_failed, 'base_url': base_url
739 }
740 body = BODY_STYLE.expand(data, group=PAGE_TEMPLATES['FAILED'])
741 f.write(body)
742
743 with open(os.path.join(out_dir, 'osh2oil-failed.html'), 'w') as f:
744 data = {
745 'task': 'osh2oil', 'failures': self.osh2oil_failed,
746 'base_url': base_url
747 }
748 body = BODY_STYLE.expand(data, group=PAGE_TEMPLATES['FAILED'])
749 f.write(body)
750
751
752def Options():
753 """Returns an option parser instance."""
754 p = optparse.OptionParser('wild_report.py [options] ACTION...')
755 p.add_option(
756 '-v', '--verbose', dest='verbose', action='store_true', default=False,
757 help='Show details about test execution')
758 p.add_option(
759 '--not-shell', default=None,
760 help="A file that contains a list of files that are known to be invalid "
761 "shell")
762 p.add_option(
763 '--not-osh', default=None,
764 help="A file that contains a list of files that are known to be invalid "
765 "under the OSH language.")
766 return p
767
768
769def main(argv):
770 o = Options()
771 (opts, argv) = o.parse_args(argv)
772
773 action = argv[1]
774
775 if action == 'summarize-dirs':
776 in_dir = argv[2]
777 out_dir = argv[3]
778
779 not_shell = _ReadLinesToSet(opts.not_shell)
780 not_osh = _ReadLinesToSet(opts.not_osh)
781
782 # lines and size, oops
783
784 # TODO: Need read the manifest instead, and then go by dirname() I guess
785 # I guess it is a BFS so you can just assume?
786 # os.path.dirname() on the full path?
787 # Or maybe you need the output files?
788
789 root_node = DirNode()
790 failures = Failures()
791 SumStats(sys.stdin, in_dir, not_shell, not_osh, root_node, failures)
792
793 failures.Write(out_dir)
794
795 # Debug print
796 #DebugPrint(root_node)
797 #WriteJsonFiles(root_node, out_dir)
798
799 WriteHtmlFiles(root_node, out_dir)
800
801 else:
802 raise RuntimeError('Invalid action %r' % action)
803
804
805if __name__ == '__main__':
806 try:
807 main(sys.argv)
808 except RuntimeError as e:
809 print('FATAL: %s' % e, file=sys.stderr)
810 sys.exit(1)
811
812
813# vim: sw=2