aboutsummaryrefslogtreecommitdiffstats
path: root/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/lore/lint.py
blob: d58d9ad30cc3149ae67c8f010d402bbbbb971f0f (plain)
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
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.

"""
Checker for common errors in Lore documents.
"""

from xml.dom import minidom as dom
import parser, urlparse, os.path

from twisted.lore import tree, process
from twisted.web import domhelpers
from twisted.python import reflect


# parser.suite in Python 2.3 raises SyntaxError, <2.3 raises parser.ParserError
parserErrors = (SyntaxError, parser.ParserError)

class TagChecker:

    def check(self, dom, filename):
        self.hadErrors = 0
        for method in reflect.prefixedMethods(self, 'check_'):
            method(dom, filename)
        if self.hadErrors:
            raise process.ProcessingFailure("invalid format")

    def _reportError(self, filename, element, error):
        hlint = element.hasAttribute('hlint') and element.getAttribute('hlint')
        if hlint != 'off':
            self.hadErrors = 1
            pos = getattr(element, '_markpos', None) or (0, 0)
            print "%s:%s:%s: %s" % ((filename,)+pos+(error,))


class DefaultTagChecker(TagChecker):

    def __init__(self, allowedTags, allowedClasses):
        self.allowedTags = allowedTags
        self.allowedClasses = allowedClasses

    def check_disallowedElements(self, dom, filename):
        def m(node, self=self):
            return not self.allowedTags(node.tagName)
        for element in domhelpers.findElements(dom, m):
            self._reportError(filename, element,
                               'unrecommended tag %s' % element.tagName)

    def check_disallowedClasses(self, dom, filename):
        def matcher(element, self=self):
            if not element.hasAttribute('class'):
                return 0
            checker = self.allowedClasses.get(element.tagName, lambda x:0)
            return not checker(element.getAttribute('class'))
        for element in domhelpers.findElements(dom, matcher):
            self._reportError(filename, element,
                              'unknown class %s' %element.getAttribute('class'))

    def check_quote(self, doc, filename):
        def matcher(node):
            return ('"' in getattr(node, 'data', '') and
                    not isinstance(node, dom.Comment) and
                    not  [1 for n in domhelpers.getParents(node)[1:-1]
                           if n.tagName in ('pre', 'code')])
        for node in domhelpers.findNodes(doc, matcher):
            self._reportError(filename, node.parentNode, 'contains quote')

    def check_styleattr(self, dom, filename):
        for node in domhelpers.findElementsWithAttribute(dom, 'style'):
            self._reportError(filename, node, 'explicit style')

    def check_align(self, dom, filename):
        for node in domhelpers.findElementsWithAttribute(dom, 'align'):
            self._reportError(filename, node, 'explicit alignment')

    def check_style(self, dom, filename):
        for node in domhelpers.findNodesNamed(dom, 'style'):
            if domhelpers.getNodeText(node) != '':
                self._reportError(filename, node, 'hand hacked style')

    def check_title(self, dom, filename):
        doc = dom.documentElement
        title = domhelpers.findNodesNamed(dom, 'title')
        if len(title)!=1:
            return self._reportError(filename, doc, 'not exactly one title')
        h1 = domhelpers.findNodesNamed(dom, 'h1')
        if len(h1)!=1:
            return self._reportError(filename, doc, 'not exactly one h1')
        if domhelpers.getNodeText(h1[0]) != domhelpers.getNodeText(title[0]):
            self._reportError(filename, h1[0], 'title and h1 text differ')

    def check_80_columns(self, dom, filename):
        for node in domhelpers.findNodesNamed(dom, 'pre'):
            # the ps/pdf output is in a font that cuts off at 80 characters,
            # so this is enforced to make sure the interesting parts (which
            # are likely to be on the right-hand edge) stay on the printed
            # page.
            for line in domhelpers.gatherTextNodes(node, 1).split('\n'):
                if len(line.rstrip()) > 80:
                    self._reportError(filename, node,
                                      'text wider than 80 columns in pre')
        for node in domhelpers.findNodesNamed(dom, 'a'):
            if node.getAttribute('class').endswith('listing'):
                try:
                    fn = os.path.dirname(filename)
                    fn = os.path.join(fn, node.getAttribute('href'))
                    lines = open(fn,'r').readlines()
                except:
                    self._reportError(filename, node,
                                      'bad listing href: %r' %
                                      node.getAttribute('href'))
                    continue

                for line in lines:
                    if len(line.rstrip()) > 80:
                        self._reportError(filename, node,
                                          'listing wider than 80 columns')

    def check_pre_py_listing(self, dom, filename):
        for node in domhelpers.findNodesNamed(dom, 'pre'):
            if node.getAttribute('class') == 'python':
                try:
                    text = domhelpers.getNodeText(node)
                    # Fix < and >
                    text = text.replace('&gt;', '>').replace('&lt;', '<')
                    # Strip blank lines
                    lines = filter(None,[l.rstrip() for l in text.split('\n')])
                    # Strip leading space
                    while not [1 for line in lines if line[:1] not in ('',' ')]:
                        lines = [line[1:] for line in lines]
                    text = '\n'.join(lines) + '\n'
                    try:
                        parser.suite(text)
                    except parserErrors, e:
                        # Pretend the "..." idiom is syntactically valid
                        text = text.replace("...","'...'")
                        parser.suite(text)
                except parserErrors, e:
                    self._reportError(filename, node,
                                      'invalid python code:' + str(e))

    def check_anchor_in_heading(self, dom, filename):
        headingNames = ['h%d' % n for n in range(1,7)]
        for hname in headingNames:
            for node in domhelpers.findNodesNamed(dom, hname):
                if domhelpers.findNodesNamed(node, 'a'):
                    self._reportError(filename, node, 'anchor in heading')

    def check_texturl_matches_href(self, dom, filename):
        for node in domhelpers.findNodesNamed(dom, 'a'):
            if not node.hasAttribute('href'):
                continue
            text = domhelpers.getNodeText(node)
            proto = urlparse.urlparse(text)[0]
            if proto and ' ' not in text:
                if text != node.getAttribute('href'):
                    self._reportError(filename, node,
                                      'link text does not match href')

    def check_lists(self, dom, filename):
        for node in (domhelpers.findNodesNamed(dom, 'ul')+
                     domhelpers.findNodesNamed(dom, 'ol')):
            if not node.childNodes:
                self._reportError(filename, node, 'empty list')
            for child in node.childNodes:
                if child.nodeName != 'li':
                    self._reportError(filename, node,
                                      'only list items allowed in lists')


def list2dict(l):
    d = {}
    for el in l:
        d[el] = None
    return d

classes = list2dict(['shell', 'API', 'python', 'py-prototype', 'py-filename',
                     'py-src-string', 'py-signature', 'py-src-parameter',
                     'py-src-identifier', 'py-src-keyword'])

tags = list2dict(["html", "title", "head", "body", "h1", "h2", "h3", "ol", "ul",
                  "dl", "li", "dt", "dd", "p", "code", "img", "blockquote", "a",
                  "cite", "div", "span", "strong", "em", "pre", "q", "table",
                  "tr", "td", "th", "style", "sub", "sup", "link"])

span = list2dict(['footnote', 'manhole-output', 'index'])

div = list2dict(['note', 'boxed', 'doit'])

a = list2dict(['listing', 'py-listing', 'html-listing', 'absolute'])

pre = list2dict(['python', 'shell', 'python-interpreter', 'elisp'])

allowed = {'code': classes.has_key, 'span': span.has_key, 'div': div.has_key,
           'a': a.has_key, 'pre': pre.has_key, 'ul': lambda x: x=='toc',
           'ol': lambda x: x=='toc', 'li': lambda x: x=='ignoretoc'}

def getDefaultChecker():
    return DefaultTagChecker(tags.__contains__, allowed)

def doFile(file, checker):
    doc = tree.parseFileAndReport(file)
    if doc:
        checker.check(doc, file)