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