OILS / doctools / fmt_check.py View on Github | oilshell.org

111 lines, 70 significant
1#!/usr/bin/env python3
2"""fmt_check.py
3
4Check that the output HTML obeys the following rules:
5
6 - No orphaned backticks '`' should be part of a `inline code block`
7 (ie. any backticks not in a <code> block is treated as an error)
8 - Lines in a <code> should be shorter than 70 chars (else they overflow)
9"""
10
11import html.parser
12import sys
13
14from doctools.util import log
15
16
17class TagAwareHTMLParser(html.parser.HTMLParser):
18 def __init__(self, file):
19 super().__init__()
20 self.tag_stack = []
21 self.file = file
22
23 def location_str(self):
24 line, col = self.getpos()
25 return '%s:%d:%d' % (self.file, line, col)
26
27 def handle_starttag(self, tag, _attrs):
28 # Skip self-closing elements
29 if tag in ('meta', 'img'):
30 return
31
32 self.tag_stack.append(tag)
33
34 def handle_endtag(self, tag):
35 popped = self.tag_stack.pop()
36 if tag != popped:
37 print('%s [WARN] Mismatched tag!' % self.location_str(),
38 'Expected </%s> but got </%s>' % (popped, tag))
39
40class CheckBackticks(TagAwareHTMLParser):
41 def __init__(self, file):
42 super().__init__(file)
43 self.has_error = False
44
45 def handle_data(self, text):
46 # Ignore eg, <code> tags
47 if len(self.tag_stack) and (
48 self.tag_stack[-1] not in ("p", "h1", "h2", "h3", "a")):
49 return
50
51 idx = text.find('`')
52 if idx == -1:
53 return
54
55 print('%s [ERROR] Found stray backtick %r' % (self.location_str(), text))
56
57 self.has_error = True
58
59
60class CheckCodeLines(TagAwareHTMLParser):
61 # Found when the display is 801px in width
62 MAX_LINE_LENGTH = 70
63
64 def __init__(self, file):
65 super().__init__(file)
66 self.has_error = False
67
68 def handle_data(self, text):
69 # Ignore eg, <code> tags
70 if len(self.tag_stack) and self.tag_stack[-1] != 'code':
71 return
72
73 for i, line in enumerate(text.splitlines()):
74 if len(line) > self.MAX_LINE_LENGTH:
75 print('%s [ERROR] Line %d of <code> is too long: %r' % (self.location_str(), i + 1, line))
76 self.has_error = True
77
78
79def FormatCheck(filename):
80 backticks = CheckBackticks(filename)
81 with open(filename, "r") as f:
82 backticks.feed(f.read())
83
84 lines = CheckCodeLines(filename)
85 with open(filename, "r") as f:
86 lines.feed(f.read())
87
88 return backticks.has_error or lines.has_error
89
90def main(argv):
91 action = argv[1]
92
93 any_error = False
94 for path in argv[1:]:
95 if not path.endswith('.html'):
96 raise RuntimeError('Expected %r to be a .html file' % filename)
97
98 this_error = FormatCheck(path)
99 any_error = any_error or this_error
100 log("%s %s" % ("ER" if this_error else "OK", path))
101
102 if any_error:
103 raise RuntimeError("Formatting errors found")
104
105
106if __name__ == '__main__':
107 try:
108 main(sys.argv)
109 except RuntimeError as e:
110 print('FATAL: %s' % e, file=sys.stderr)
111 sys.exit(1)