Merge lp:~sinzui/ubuntu/oneiric/pocket-lint/0.5.21 into lp:ubuntu/oneiric/pocket-lint

Proposed by Curtis Hovey
Status: Superseded
Proposed branch: lp:~sinzui/ubuntu/oneiric/pocket-lint/0.5.21
Merge into: lp:ubuntu/oneiric/pocket-lint
Diff against target: 2449 lines (+1499/-241)
10 files modified
ChangeLog (+121/-0)
PKG-INFO (+1/-1)
debian/changelog (+6/-0)
debian/control (+1/-2)
debian/copyright (+6/-1)
pocketlint/contrib/cssccc.py (+495/-0)
pocketlint/contrib/pep8.py (+683/-188)
pocketlint/formatcheck.py (+94/-34)
pocketlint/jsreporter.js (+71/-14)
setup.py (+21/-1)
To merge this branch: bzr merge lp:~sinzui/ubuntu/oneiric/pocket-lint/0.5.21
Reviewer Review Type Date Requested Status
Scott Moser Needs Fixing
Ubuntu branches Pending
Review via email: mp+74924@code.launchpad.net

This proposal has been superseded by a proposal from 2011-10-18.

Description of the change

Merge upstream release that improves CSS and JS support
debian/control was updated to require gjs

To post a comment you must log in.
Revision history for this message
Scott Moser (smoser) wrote :

It looks like debian/copyright needs updating at least for './pocketlint/contrib/cssccc.py', which is listed as "public domain", unlike the other files that are MIT.

The rest of it looks good to me.

review: Needs Fixing
Revision history for this message
Scott Moser (smoser) wrote :

oh, and just to be clear, you're not expecting this for SRU to oneiric, right?

Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you for spotting the license change. I updated the license file. No I do not think this deserves an SRU given that the branch is more than a month old.

6. By Curtis Hovey

Updated license information.

Unmerged revisions

6. By Curtis Hovey

Updated license information.

5. By Curtis Hovey

New upstream release improves CSS and JS support.
Fixes Gtk3 bugs.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ChangeLog'
2--- ChangeLog 2011-04-16 15:32:48 +0000
3+++ ChangeLog 2011-10-14 21:24:26 +0000
4@@ -1,5 +1,126 @@
5 <Generated by bzr log --log-format=gnu>
6
7+2011-06-06 Curtis Hovey <sinzui.is@verizon.net>
8+
9+ [396] Use seed as the js interpreter.
10+
11+2011-06-06 Curtis Hovey <sinzui.is@verizon.net>
12+
13+ [395] Clear refernece.
14+
15+2011-06-06 Curtis Hovey <sinzui.is@verizon.net>
16+
17+ [394] Documented how to use jsreporter.js
18+
19+2011-06-06 Curtis Hovey <sinzui.is@verizon.net>
20+
21+ [393] Removed unused code.
22+
23+2011-06-06 Curtis Hovey <sinzui.is@verizon.net>
24+
25+ [392] Use seed as the default js command.
26+
27+2011-06-06 Curtis Hovey <sinzui.is@verizon.net>
28+
29+ [391] Updated attr name.
30+
31+2011-06-06 Curtis Hovey <sinzui.is@verizon.net>
32+
33+ [390] Removed js subprocess, but HTML5Browser is slow.
34+
35+2011-06-05 Curtis Hovey <sinzui.is@verizon.net>
36+
37+ [389] Updated documentation.
38+
39+2011-06-05 Curtis Hovey <sinzui.is@verizon.net>
40+
41+ [388] Minor speed changes.
42+
43+2011-06-05 Curtis Hovey <sinzui.is@verizon.net>
44+
45+ [387] Added rudimentary JS checker based on Webkit.
46+
47+2011-05-10 Curtis Hovey <sinzui.is@verizon.net>
48+
49+ [386] Incremented version.
50+
51+2011-05-10 Curtis Hovey <sinzui.is@verizon.net>
52+
53+ [385] merged Adi's fix.
54+
55+2011-05-04 Curtis Hovey <sinzui.is@verizon.net>
56+
57+ [384] Inremented version.
58+
59+2011-05-04 Curtis Hovey <sinzui.is@verizon.net>
60+
61+ [383] Corrected the js line number.
62+
63+2011-05-04 Curtis Hovey <sinzui.is@verizon.net>
64+
65+ [382] Catch and report IndentationExceptions.
66+
67+2011-05-04 Curtis Hovey <sinzui.is@verizon.net>
68+
69+ [381] Removed unneeded checks.
70+
71+2011-05-04 Curtis Hovey <sinzui.is@verizon.net>
72+
73+ [380] Remove trailing whitespace.
74+
75+2011-05-04 Curtis Hovey <sinzui.is@verizon.net>
76+
77+ [379] Merged python encoding fix.
78+
79+2011-04-19 Curtis Hovey <sinzui.is@verizon.net>
80+
81+ [378] Increment version.
82+
83+2011-04-19 Curtis Hovey <sinzui.is@verizon.net>
84+
85+ [377] Hush WindowError.
86+
87+2011-04-19 Curtis Hovey <sinzui.is@verizon.net>
88+
89+ [376] Move signed archives rules into setup.py.
90+
91+2011-04-17 Curtis Hovey <sinzui.is@verizon.net>
92+
93+ [375] Incremented version for cssccc
94+
95+2011-04-17 Curtis Hovey <sinzui.is@verizon.net>
96+
97+ [374] Merged cssccc lib.
98+
99+2011-04-17 Curtis Hovey <sinzui.is@verizon.net>
100+
101+ [373] Added a rule to sign the release after it is made.
102+
103+2011-04-17 Curtis Hovey <sinzui.is@verizon.net>
104+
105+ [372] Incremenet version for release.
106+
107+2011-04-17 Curtis Hovey <sinzui.is@verizon.net>
108+
109+ [371] Merged Adi's feature that returns the count od reported issues.
110+
111+2011-04-17 Curtis Hovey <sinzui.is@verizon.net>
112+
113+ [370] Added simple SQL checker.
114+
115+2011-04-17 Curtis Hovey <sinzui.is@verizon.net>
116+
117+ [369] Elaborated comment test.
118+
119+2011-04-17 Curtis Hovey <sinzui.is@verizon.net>
120+
121+ [368] Added a basic test to verify comments do not mess up blank line
122+ counts.
123+
124+2011-04-17 Curtis Hovey <sinzui.is@verizon.net>
125+
126+ [367] Update pep8 to version 0.6.1
127+
128 2011-04-15 Curtis Hovey <sinzui.is@verizon.net>
129
130 [366] Increment version.
131
132=== modified file 'PKG-INFO'
133--- PKG-INFO 2011-04-16 15:32:48 +0000
134+++ PKG-INFO 2011-10-14 21:24:26 +0000
135@@ -1,6 +1,6 @@
136 Metadata-Version: 1.0
137 Name: pocketlint
138-Version: 0.5.13
139+Version: 0.5.21
140 Summary: Pocket-lint a composite linter and style checker.
141 Home-page: https://launchpad.net/pocket-lint
142 Author: Curtis C. Hovey
143
144=== modified file 'debian/changelog'
145--- debian/changelog 2011-04-16 15:32:48 +0000
146+++ debian/changelog 2011-10-14 21:24:26 +0000
147@@ -1,3 +1,9 @@
148+pocket-lint (0.5.21-0ubuntu1) UNRELEASED; urgency=low
149+
150+ * New upstream release.
151+
152+ -- Curtis C. Hovey <sinzui.is@verizon.net> Sun, 11 Sep 2011 17:22:02 -0400
153+
154 pocket-lint (0.5.13-0ubuntu1) natty; urgency=low
155
156 * New upstream bug fix release
157
158=== modified file 'debian/control'
159--- debian/control 2011-04-15 13:37:44 +0000
160+++ debian/control 2011-10-14 21:24:26 +0000
161@@ -11,8 +11,7 @@
162 Package: python-pocket-lint
163 Architecture: all
164 Depends: ${misc:Depends}, ${shlibs:Depends}, ${python:Depends}
165-Recommends: python-cssutils
166-Suggests: spidermonkey-bin
167+Recommends: python-cssutils, gjs
168 Description: a composite linter and style checker
169 Pocket-lint has several notable features:
170 .
171
172=== modified file 'debian/copyright'
173--- debian/copyright 2010-11-17 15:46:54 +0000
174+++ debian/copyright 2011-10-14 21:24:26 +0000
175@@ -4,7 +4,7 @@
176 Copyright:
177 Upstream Author: Curtis C. Hovey
178
179-License:
180+Primary license:
181 The MIT License:
182
183 Permission is hereby granted, free of charge, to any person obtaining a copy
184@@ -25,6 +25,11 @@
185 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
186 THE SOFTWARE.
187
188+
189+contrib/cssccc.py license:
190+ Public Domain
191+
192+
193 The Debian packaging is:
194 Copyright 2010 Curtis C. Hovey
195 and is under the MIT licence.
196
197=== added file 'pocketlint/contrib/cssccc.py'
198--- pocketlint/contrib/cssccc.py 1970-01-01 00:00:00 +0000
199+++ pocketlint/contrib/cssccc.py 2011-10-14 21:24:26 +0000
200@@ -0,0 +1,495 @@
201+'''
202+This code is in the public domain.
203+
204+Check CSS code for some common coding conventions.
205+The code must be in a valid CSS format.
206+It is recommend to first parse it using cssutils.
207+It is also recommend to check it with pocket-lint for things like trailing
208+spaces or tab characters.
209+
210+If a comment is on the whole line, it will consume the whole line like it
211+was not there.
212+If a comment is inside a line it will only consume its own content.
213+
214+Bases on Stoyan Stefanov's http://www.phpied.com/css-coding-conventions/
215+
216+'@media' rule is not supported.
217+ @media print {
218+ html {
219+ background: #fff;
220+ color: #000;
221+ }
222+ body {
223+ padding: 1in;
224+ border: 0.5pt solid #666;
225+ }
226+ }
227+
228+The following at-rules are supported:
229+ * keyword / text at-rules
230+ * @charset "ISO-8859-15";
231+ * @import url(/css/screen.css) screen, projection;
232+ * @namespace foo "http://example.com/ns/foo";
233+ * keybord / block rules
234+ * @page { block; }
235+ * @font-face { block; }
236+
237+
238+TODO:
239+ * add warning for using px for fonts.
240+ * add Unicode support.
241+ * add AtRule checks
242+ * add support for TAB as a separator / identation.
243+ * add support for @media
244+'''
245+from __future__ import with_statement
246+
247+__version__ = '0.1.1'
248+
249+import sys
250+
251+SELECTOR_SEPARATOR = ','
252+DECLARATION_SEPARATOR = ';'
253+PROPERTY_SEPARATOR = ':'
254+COMMENT_START = r'/*'
255+COMMENT_END = r'*/'
256+AT_TEXT_RULES = ['import', 'charset', 'namespace']
257+AT_BLOCK_RULES = ['page', 'font-face']
258+# If you want
259+# selector,
260+# selector2
261+# {
262+# property:
263+# }
264+#IGNORED_MESSAGES = ['I013', 'I014']
265+
266+# If you want
267+# selector,
268+# selector {
269+# property:
270+# }
271+#IGNORED_MESSAGES = ['I005', 'I014']
272+
273+# If you want
274+# selector,
275+# selector2 {
276+# property:
277+# }
278+IGNORED_MESSAGES = ['I005', 'I006']
279+
280+
281+class CSSRule(object):
282+ '''A CSS rule.'''
283+
284+ def check(self):
285+ '''Check the rule.'''
286+ raise AssertionError('Method not implemtned.')
287+
288+
289+class CSSAtRule(object):
290+ '''A CSS @rule.'''
291+
292+ type = object()
293+
294+ def __init__(self, identifier, keyword, log, text=None, block=None):
295+ self.identifier = identifier
296+ self.keyword = keyword
297+ self.text = text
298+ self.block = block
299+ self.log = log
300+
301+ def check(self):
302+ '''Check the rule.'''
303+
304+
305+class CSSRuleSet(object):
306+ '''A CSS rule_set.'''
307+
308+ type = object()
309+
310+ def __init__(self, selector, declarations, log):
311+ self.selector = selector
312+ self.declarations = declarations
313+ self.log = log
314+
315+ def __str__(self):
316+ return '%s{%s}' % (str(self.selector), str(self.declarations))
317+
318+ def __repr__(self):
319+ return '%d:%s{%s}' % (
320+ self.selector.start_line,
321+ str(self.selector),
322+ str(self.declarations),
323+ )
324+
325+ def check(self):
326+ '''Check the rule set.'''
327+ self.checkSelector()
328+ self.checkDeclarations()
329+
330+ def checkSelector(self):
331+ '''Check rule-set selector.'''
332+ start_line = self.selector.getStartLine()
333+ selectors = self.selector.text.split(SELECTOR_SEPARATOR)
334+ offset = 0
335+ last_selector = selectors[-1]
336+ first_selector = selectors[0]
337+ rest_selectors = selectors[1:]
338+
339+ if first_selector.startswith('\n\n\n'):
340+ self.log(start_line, 'I002', 'To many newlines before selectors.')
341+ elif first_selector.startswith('\n\n'):
342+ pass
343+ elif start_line > 2:
344+ self.log(start_line, 'I003', 'To few newlines before selectors.')
345+ else:
346+ pass
347+
348+ for selector in rest_selectors:
349+ if not selector.startswith('\n'):
350+ self.log(
351+ start_line + offset,
352+ 'I004',
353+ 'Selector must be on a new line.')
354+ offset += selector.count('\n')
355+
356+ if not last_selector.endswith('\n'):
357+ self.log(
358+ start_line + offset,
359+ 'I005',
360+ 'No newline after last selector.')
361+
362+ if not (last_selector[-2] != ' ' and last_selector[-1] == (' ')):
363+ self.log(
364+ start_line + offset,
365+ 'I013',
366+ 'Last selector must be followed by " {".')
367+
368+ def checkDeclarations(self):
369+ '''Check rule-set declarations.'''
370+ start_line = self.declarations.getStartLine()
371+ declarations = self.declarations.text.split(DECLARATION_SEPARATOR)
372+ offset = 0
373+
374+ # Check all declarations except last as this is the new line.
375+ first_declaration = True
376+ for declaration in declarations[:-1]:
377+ if not declaration.startswith('\n'):
378+ self.log(
379+ start_line + offset,
380+ 'I007',
381+ 'Each declarations should start on a new line.',
382+ )
383+ elif (not declaration.startswith('\n ') or
384+ declaration[5] == ' '):
385+ self.log(
386+ start_line + offset,
387+ 'I008',
388+ 'Each declaration must be indented with 4 spaces.',
389+ )
390+
391+ parts = declaration.split(PROPERTY_SEPARATOR)
392+ if len(parts) != 2:
393+ self.log(
394+ start_line + offset,
395+ 'I009',
396+ 'Wrong separator on property: value pair.',
397+ )
398+ else:
399+ prop, value = parts
400+ if prop.endswith(' '):
401+ self.log(
402+ start_line + offset,
403+ 'I010',
404+ 'Whitespace before ":".',
405+ )
406+ if not (value.startswith(' ') or value.startswith('\n')):
407+ self.log(
408+ start_line + offset,
409+ 'I011',
410+ 'Missing whitespace after ":".',
411+ )
412+ elif value.startswith(' '):
413+ self.log(
414+ start_line + offset,
415+ 'I012',
416+ 'Multiple whitespaces after ":".',
417+ )
418+ if first_declaration:
419+ first_declaration = False
420+ else:
421+ offset += declaration.count('\n')
422+
423+ last_declaration = declarations[-1]
424+ offset += last_declaration.count('\n')
425+ if last_declaration != '\n':
426+ self.log(
427+ start_line + offset,
428+ 'I006',
429+ 'Rule declarations should end with a single new line.',
430+ )
431+ if last_declaration != '\n ':
432+ self.log(
433+ start_line + offset,
434+ 'I014',
435+ 'Rule declarations should end indented on a single new line.',
436+ )
437+
438+
439+class CSSStatementMember(object):
440+ '''A member of CSS statement.'''
441+
442+ def __init__(self, start_line, start_character, text):
443+ self.start_line = start_line
444+ self.start_character = start_character
445+ self.text = text
446+
447+ def getStartLine(self):
448+ '''Return the line number for first character in the statement and
449+ the number of new lines untilg the first character.'''
450+ index = 0
451+ text = self.text
452+ character = text[index]
453+ while character == '\n':
454+ index += 1
455+ character = text[index]
456+
457+ return self.start_line + index + 1
458+
459+ def __str__(self):
460+ return self.text
461+
462+ def __repr__(self):
463+ return '%d:%d:{%s}' % (
464+ self.start_line, self.start_character, self.text)
465+
466+
467+class CSSCodingConventionChecker(object):
468+ '''CSS coding convention checker.'''
469+
470+ icons = {
471+ 'E': 'error',
472+ 'I': 'info',
473+ }
474+
475+ def __init__(self, text, logger=None):
476+ self._text = text.splitlines(True)
477+ self.line_number = 0
478+ self.character_number = 0
479+ if logger:
480+ self._logger = logger
481+ else:
482+ self._logger = self._defaultLog
483+
484+ def log(self, line_number, code, message):
485+ '''Log the message with `code`.'''
486+ if code in IGNORED_MESSAGES:
487+ return
488+ icon = self.icons[code[0]]
489+ self._logger(line_number, code + ': ' + message, icon=icon)
490+
491+ def check(self):
492+ '''Check all rules.'''
493+ for rule in self.getRules():
494+ rule.check()
495+
496+ def getRules(self):
497+ '''Generates the next CSS rule ignoring comments.'''
498+ while True:
499+ yield self.getNextRule()
500+
501+ def getNextRule(self):
502+ '''Return the next parsed rule.
503+
504+ Raise `StopIteration` if we are at the last rule.
505+ '''
506+ if self._nextStatementIsAtRule():
507+ text = None
508+ block = None
509+ keyword = self._parse('@')
510+ # TODO: user regex [ \t {]
511+ keyword_text = self._parse(' ')
512+ keyword_name = keyword_text.text
513+ keyword.text += '@' + keyword_name + ' '
514+
515+ if keyword_name.lower() in AT_TEXT_RULES:
516+ text = self._parse(';')
517+ elif keyword_name.lower() in AT_BLOCK_RULES:
518+ start = self._parse('{')
519+ keyword.text += start.text
520+ block = self._parse('}')
521+ else:
522+ self._parse(';')
523+ raise StopIteration
524+
525+ return CSSAtRule(
526+ identifier=keyword_name,
527+ keyword=keyword,
528+ text=text,
529+ block=block,
530+ log=self.log)
531+ else:
532+ selector = self._parse('{')
533+ declarations = self._parse('}')
534+ return CSSRuleSet(
535+ selector=selector,
536+ declarations=declarations,
537+ log=self.log)
538+
539+ def _defaultLog(self, line_number, message, icon='info'):
540+ '''Log the message to STDOUT.'''
541+ print ' %4s:%s' % (line_number, message)
542+
543+ def _nextStatementIsAtRule(self):
544+ '''Return True if next statement in the buffer is an at-rule.
545+
546+ Just look for open brackets and see if there is an @ before that
547+ braket.
548+ '''
549+ search_buffer = []
550+ line_counter = self.line_number
551+ current_line = self._text[line_counter][self.character_number:]
552+ while current_line.find('@') == -1:
553+ search_buffer.append(current_line)
554+ line_counter += 1
555+ try:
556+ current_line = self._text[line_counter]
557+ except IndexError:
558+ return False
559+
560+ text_buffer = ''.join(search_buffer)
561+ if text_buffer.find('{') == -1:
562+ return True
563+ else:
564+ return False
565+
566+ def _parse(self, stop_character):
567+ '''Return the parsed text until stop_character.'''
568+ try:
569+ self._text[self.line_number][self.character_number]
570+ except IndexError:
571+ raise StopIteration
572+ result = []
573+ start_line = self.line_number
574+ start_character = self.character_number
575+ comment_started = False
576+ while True:
577+ try:
578+ data = self._text[self.line_number][self.character_number:]
579+ except IndexError:
580+ break
581+
582+ # Look for comment start/end.
583+ (comment_update,
584+ before_comment,
585+ after_comment,
586+ newline_consumed) = _check_comment(data)
587+ if comment_update is not None:
588+ comment_started = comment_update
589+
590+ if comment_started:
591+ # We are inside a comment.
592+ # Add the data before the comment and go to next line.
593+ if before_comment is not None:
594+ result.append(before_comment)
595+ self.character_number = 0
596+ self.line_number += 1
597+ continue
598+
599+ # If we have a comment, strip it from the data.
600+ # Remember the initial cursor position to know where to
601+ # continue.
602+ initial_position = data.find(stop_character)
603+ if before_comment is not None or after_comment is not None:
604+ if before_comment is None:
605+ before_comment = ''
606+ if after_comment is None:
607+ after_comment = ''
608+ data = before_comment + after_comment
609+
610+ if initial_position == -1 or newline_consumed:
611+ # We are not at the end.
612+ # Go to next line and append the data.
613+ result.append(data)
614+ self.character_number = 0
615+ self.line_number += 1
616+ continue
617+ else:
618+ # Delimiter found.
619+ # Find it again in the text that now has no comments.
620+ # Append data until the delimiter.
621+ # Move cursor to next character and stop searching for it.
622+ new_position = data.find(stop_character)
623+ result.append(data[:new_position])
624+ self.character_number += initial_position + 1
625+ break
626+
627+ return CSSStatementMember(
628+ start_line=start_line,
629+ start_character=start_character,
630+ text=''.join(result))
631+
632+
633+def _check_comment(data):
634+ '''Check the data for comment markers.'''
635+
636+ comment_started = None
637+ before_comment = None
638+ after_comment = None
639+ newline_consumed = False
640+
641+ comment_start = data.find(COMMENT_START)
642+ if comment_start != -1:
643+ comment_started = True
644+ before_comment = data[:comment_start]
645+ # Only use `None` to signal that there is no text before the comment.
646+ if before_comment == '':
647+ before_comment = None
648+
649+ comment_end = data.find(COMMENT_END)
650+ if comment_end != -1:
651+ comment_started = False
652+ # Set comment end after the lenght of the actual comment end
653+ # marker.
654+ comment_end += len(COMMENT_END)
655+ if before_comment is None and data[comment_end] == '\n':
656+ # Consume the new line if it next to the comment end and
657+ # the comment in on the whole line.
658+ comment_end += 1
659+ newline_consumed = True
660+ after_comment = data[comment_end:]
661+ return (comment_started, before_comment, after_comment, newline_consumed)
662+
663+
664+def show_usage():
665+ '''Print the command usage.'''
666+ print 'Usage: cssccc OPTIONS'
667+ print ' -h, --help\t\tShow this help.'
668+ print ' -v, --version\t\tShow version.'
669+ print ' -f FILE, --file=FILE\tCheck FILE'
670+
671+
672+def read_file(filename):
673+ '''Return the content of filename.'''
674+ text = ''
675+ with open(filename, 'r') as f:
676+ text = f.read()
677+ return text
678+
679+
680+if __name__ == '__main__':
681+ if len(sys.argv) < 2:
682+ show_usage()
683+ elif sys.argv[1] in ['-v', '--version']:
684+ print 'CSS Code Convention Checker %s' % (__version__)
685+ sys.exit(0)
686+ elif sys.argv[1] == '-f':
687+ text = read_file(sys.argv[2])
688+ checker = CSSCodingConventionChecker(text)
689+ sys.exit(checker.check())
690+ elif sys.argv[1] == '--file=':
691+ text = read_file(sys.argv[1][len('--file='):])
692+ checker = CSSCodingConventionChecker(text)
693+ sys.exit(checker.check())
694+ else:
695+ show_usage()
696
697=== modified file 'pocketlint/contrib/pep8.py'
698--- pocketlint/contrib/pep8.py 2010-11-17 15:46:54 +0000
699+++ pocketlint/contrib/pep8.py 2011-10-14 21:24:26 +0000
700@@ -1,6 +1,6 @@
701 #!/usr/bin/python
702 # pep8.py - Check Python source code formatting, according to PEP 8
703-# Copyright (C) 2006 Johann C. Rocholl <johann@browsershots.org>
704+# Copyright (C) 2006 Johann C. Rocholl <johann@rocholl.net>
705 #
706 # Permission is hereby granted, free of charge, to any person
707 # obtaining a copy of this software and associated documentation files
708@@ -30,8 +30,7 @@
709 $ python pep8.py -h
710
711 This program and its regression test suite live here:
712-http://svn.browsershots.org/trunk/devtools/pep8/
713-http://trac.browsershots.org/browser/trunk/devtools/pep8/
714+http://github.com/jcrocholl/pep8
715
716 Groups of errors and warnings:
717 E errors
718@@ -79,33 +78,65 @@
719
720 The docstring of each check function shall be the relevant part of
721 text from PEP 8. It is printed if the user enables --show-pep8.
722+Several docstrings contain examples directly from the PEP 8 document.
723+
724+Okay: spam(ham[1], {eggs: 2})
725+E201: spam( ham[1], {eggs: 2})
726+
727+These examples are verified automatically when pep8.py is run with the
728+--doctest option. You can add examples for your own check functions.
729+The format is simple: "Okay" or error/warning code followed by colon
730+and space, the rest of the line is example source code. If you put 'r'
731+before the docstring, you can use \n for newline, \t for tab and \s
732+for space.
733
734 """
735
736+__version__ = '0.5.1dev'
737+
738 import os
739 import sys
740 import re
741 import time
742 import inspect
743+import keyword
744 import tokenize
745 from optparse import OptionParser
746-from keyword import iskeyword
747 from fnmatch import fnmatch
748-
749-__version__ = '0.2.0'
750-__revision__ = '$Rev: 2208 $'
751-
752-default_exclude = '.svn,CVS,*.pyc,*.pyo'
753-
754-indent_match = re.compile(r'([ \t]*)').match
755-raise_comma_match = re.compile(r'raise\s+\w+\s*(,)').match
756-
757-operators = """
758-+ - * / % ^ & | = < > >> <<
759-+= -= *= /= %= ^= &= |= == <= >= >>= <<=
760-!= <> :
761-in is or not and
762-""".split()
763+try:
764+ frozenset
765+except NameError:
766+ from sets import ImmutableSet as frozenset
767+
768+
769+DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git'
770+DEFAULT_IGNORE = 'E24'
771+MAX_LINE_LENGTH = 79
772+
773+INDENT_REGEX = re.compile(r'([ \t]*)')
774+RAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*(,)')
775+SELFTEST_REGEX = re.compile(r'(Okay|[EW]\d{3}):\s(.*)')
776+ERRORCODE_REGEX = re.compile(r'[EW]\d{3}')
777+DOCSTRING_REGEX = re.compile(r'u?r?["\']')
778+WHITESPACE_AROUND_OPERATOR_REGEX = \
779+ re.compile('([^\w\s]*)\s*(\t| )\s*([^\w\s]*)')
780+EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[[({] | []}),;:]')
781+WHITESPACE_AROUND_NAMED_PARAMETER_REGEX = \
782+ re.compile(r'[()]|\s=[^=]|[^=!<>]=\s')
783+
784+
785+WHITESPACE = ' \t'
786+
787+BINARY_OPERATORS = frozenset(['**=', '*=', '+=', '-=', '!=', '<>',
788+ '%=', '^=', '&=', '|=', '==', '/=', '//=', '<=', '>=', '<<=', '>>=',
789+ '%', '^', '&', '|', '=', '/', '//', '<', '>', '<<'])
790+UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-'])
791+OPERATORS = BINARY_OPERATORS | UNARY_OPERATORS
792+SKIP_TOKENS = frozenset([tokenize.COMMENT, tokenize.NL, tokenize.INDENT,
793+ tokenize.DEDENT, tokenize.NEWLINE])
794+E225NOT_KEYWORDS = (frozenset(keyword.kwlist + ['print']) -
795+ frozenset(['False', 'None', 'True']))
796+BENCHMARK_KEYS = ('directories', 'files', 'logical lines', 'physical lines')
797
798 options = None
799 args = None
800@@ -117,7 +148,7 @@
801
802
803 def tabs_or_spaces(physical_line, indent_char):
804- """
805+ r"""
806 Never mix tabs and spaces.
807
808 The most popular way of indenting Python is with spaces only. The
809@@ -126,38 +157,65 @@
810 invoking the Python command line interpreter with the -t option, it issues
811 warnings about code that illegally mixes tabs and spaces. When using -tt
812 these warnings become errors. These options are highly recommended!
813+
814+ Okay: if a == 0:\n a = 1\n b = 1
815+ E101: if a == 0:\n a = 1\n\tb = 1
816 """
817- indent = indent_match(physical_line).group(1)
818+ indent = INDENT_REGEX.match(physical_line).group(1)
819 for offset, char in enumerate(indent):
820 if char != indent_char:
821 return offset, "E101 indentation contains mixed spaces and tabs"
822
823
824 def tabs_obsolete(physical_line):
825- """
826+ r"""
827 For new projects, spaces-only are strongly recommended over tabs. Most
828 editors have features that make this easy to do.
829+
830+ Okay: if True:\n return
831+ W191: if True:\n\treturn
832 """
833- indent = indent_match(physical_line).group(1)
834+ indent = INDENT_REGEX.match(physical_line).group(1)
835 if indent.count('\t'):
836 return indent.index('\t'), "W191 indentation contains tabs"
837
838
839 def trailing_whitespace(physical_line):
840- """
841+ r"""
842 JCR: Trailing whitespace is superfluous.
843+ FBM: Except when it occurs as part of a blank line (i.e. the line is
844+ nothing but whitespace). According to Python docs[1] a line with only
845+ whitespace is considered a blank line, and is to be ignored. However,
846+ matching a blank line to its indentation level avoids mistakenly
847+ terminating a multi-line statement (e.g. class declaration) when
848+ pasting code into the standard Python interpreter.
849+
850+ [1] http://docs.python.org/reference/lexical_analysis.html#blank-lines
851+
852+ The warning returned varies on whether the line itself is blank, for easier
853+ filtering for those who want to indent their blank lines.
854+
855+ Okay: spam(1)
856+ W291: spam(1)\s
857+ W293: class Foo(object):\n \n bang = 12
858 """
859- physical_line = physical_line.rstrip('\n') # chr(10), newline
860- physical_line = physical_line.rstrip('\r') # chr(13), carriage return
861- physical_line = physical_line.rstrip('\x0c') # chr(12), form feed, ^L
862+ physical_line = physical_line.rstrip('\n') # chr(10), newline
863+ physical_line = physical_line.rstrip('\r') # chr(13), carriage return
864+ physical_line = physical_line.rstrip('\x0c') # chr(12), form feed, ^L
865 stripped = physical_line.rstrip()
866 if physical_line != stripped:
867- return len(stripped), "W291 trailing whitespace"
868+ if stripped:
869+ return len(stripped), "W291 trailing whitespace"
870+ else:
871+ return 0, "W293 blank line contains whitespace"
872
873
874 def trailing_blank_lines(physical_line, lines, line_number):
875- """
876+ r"""
877 JCR: Trailing blank lines are superfluous.
878+
879+ Okay: spam(1)
880+ W391: spam(1)\n
881 """
882 if physical_line.strip() == '' and line_number == len(lines):
883 return 0, "W391 blank line at end of file"
884@@ -182,9 +240,18 @@
885 For flowing long blocks of text (docstrings or comments), limiting the
886 length to 72 characters is recommended.
887 """
888- length = len(physical_line.rstrip())
889- if length > 79:
890- return 79, "E501 line too long (%d characters)" % length
891+ line = physical_line.rstrip()
892+ length = len(line)
893+ if length > MAX_LINE_LENGTH:
894+ try:
895+ # The line could contain multi-byte characters
896+ if not hasattr(line, 'decode'): # Python 3
897+ line = line.encode('latin-1')
898+ length = len(line.decode('utf-8'))
899+ except UnicodeDecodeError:
900+ pass
901+ if length > MAX_LINE_LENGTH:
902+ return MAX_LINE_LENGTH, "E501 line too long (%d characters)" % length
903
904
905 ##############################################################################
906@@ -193,8 +260,9 @@
907
908
909 def blank_lines(logical_line, blank_lines, indent_level, line_number,
910- previous_logical):
911- """
912+ previous_logical, previous_indent_level,
913+ blank_lines_before_comment):
914+ r"""
915 Separate top-level function and class definitions with two blank lines.
916
917 Method definitions inside a class are separated by a single blank line.
918@@ -204,20 +272,33 @@
919 one-liners (e.g. a set of dummy implementations).
920
921 Use blank lines in functions, sparingly, to indicate logical sections.
922+
923+ Okay: def a():\n pass\n\n\ndef b():\n pass
924+ Okay: def a():\n pass\n\n\n# Foo\n# Bar\n\ndef b():\n pass
925+
926+ E301: class Foo:\n b = 0\n def bar():\n pass
927+ E302: def a():\n pass\n\ndef b(n):\n pass
928+ E303: def a():\n pass\n\n\n\ndef b(n):\n pass
929+ E303: def a():\n\n\n\n pass
930+ E304: @decorator\n\ndef a():\n pass
931 """
932 if line_number == 1:
933- return # Don't expect blank lines before the first line
934+ return # Don't expect blank lines before the first line
935+ max_blank_lines = max(blank_lines, blank_lines_before_comment)
936 if previous_logical.startswith('@'):
937- return # Don't expect blank lines after function decorator
938- if (logical_line.startswith('def ') or
939- logical_line.startswith('class ') or
940- logical_line.startswith('@')):
941- if indent_level > 0 and blank_lines != 1:
942- return 0, "E301 expected 1 blank line, found %d" % blank_lines
943- if indent_level == 0 and blank_lines != 2:
944- return 0, "E302 expected 2 blank lines, found %d" % blank_lines
945- if blank_lines > 2:
946- return 0, "E303 too many blank lines (%d)" % blank_lines
947+ if max_blank_lines:
948+ return 0, "E304 blank lines found after function decorator"
949+ elif max_blank_lines > 2 or (indent_level and max_blank_lines == 2):
950+ return 0, "E303 too many blank lines (%d)" % max_blank_lines
951+ elif (logical_line.startswith('def ') or
952+ logical_line.startswith('class ') or
953+ logical_line.startswith('@')):
954+ if indent_level:
955+ if not (max_blank_lines or previous_indent_level < indent_level or
956+ DOCSTRING_REGEX.match(previous_logical)):
957+ return 0, "E301 expected 1 blank line, found 0"
958+ elif max_blank_lines != 2:
959+ return 0, "E302 expected 2 blank lines, found %d" % max_blank_lines
960
961
962 def extraneous_whitespace(logical_line):
963@@ -227,45 +308,75 @@
964 - Immediately inside parentheses, brackets or braces.
965
966 - Immediately before a comma, semicolon, or colon.
967+
968+ Okay: spam(ham[1], {eggs: 2})
969+ E201: spam( ham[1], {eggs: 2})
970+ E201: spam(ham[ 1], {eggs: 2})
971+ E201: spam(ham[1], { eggs: 2})
972+ E202: spam(ham[1], {eggs: 2} )
973+ E202: spam(ham[1 ], {eggs: 2})
974+ E202: spam(ham[1], {eggs: 2 })
975+
976+ E203: if x == 4: print x, y; x, y = y , x
977+ E203: if x == 4: print x, y ; x, y = y, x
978+ E203: if x == 4 : print x, y; x, y = y, x
979 """
980 line = logical_line
981- for char in '([{':
982- found = line.find(char + ' ')
983- if found > -1:
984+ for match in EXTRANEOUS_WHITESPACE_REGEX.finditer(line):
985+ text = match.group()
986+ char = text.strip()
987+ found = match.start()
988+ if text == char + ' ' and char in '([{':
989 return found + 1, "E201 whitespace after '%s'" % char
990- for char in '}])':
991- found = line.find(' ' + char)
992- if found > -1 and line[found - 1] != ',':
993- return found, "E202 whitespace before '%s'" % char
994- for char in ',;:':
995- found = line.find(' ' + char)
996- if found > -1:
997- return found, "E203 whitespace before '%s'" % char
998+ if text == ' ' + char and line[found - 1] != ',':
999+ if char in '}])':
1000+ return found, "E202 whitespace before '%s'" % char
1001+ if char in ',;:':
1002+ return found, "E203 whitespace before '%s'" % char
1003
1004
1005 def missing_whitespace(logical_line):
1006 """
1007 JCR: Each comma, semicolon or colon should be followed by whitespace.
1008+
1009+ Okay: [a, b]
1010+ Okay: (3,)
1011+ Okay: a[1:4]
1012+ Okay: a[:4]
1013+ Okay: a[1:]
1014+ Okay: a[1:4:2]
1015+ E231: ['a','b']
1016+ E231: foo(bar,baz)
1017 """
1018 line = logical_line
1019 for index in range(len(line) - 1):
1020 char = line[index]
1021- if char in ',;:' and line[index + 1] != ' ':
1022+ if char in ',;:' and line[index + 1] not in WHITESPACE:
1023 before = line[:index]
1024 if char == ':' and before.count('[') > before.count(']'):
1025- continue # Slice syntax, no space required
1026+ continue # Slice syntax, no space required
1027 if char == ',' and line[index + 1] == ')':
1028- continue # Singleton tuple syntax, no space required
1029+ continue # Allow tuple with only one element: (3,)
1030 return index, "E231 missing whitespace after '%s'" % char
1031
1032
1033 def indentation(logical_line, previous_logical, indent_char,
1034 indent_level, previous_indent_level):
1035- """
1036+ r"""
1037 Use 4 spaces per indentation level.
1038
1039 For really old code that you don't want to mess up, you can continue to
1040 use 8-space tabs.
1041+
1042+ Okay: a = 1
1043+ Okay: if a == 0:\n a = 1
1044+ E111: a = 1
1045+
1046+ Okay: for item in items:\n pass
1047+ E112: for item in items:\npass
1048+
1049+ Okay: a = 1\nb = 2
1050+ E113: a = 1\n b = 2
1051 """
1052 if indent_char == ' ' and indent_level % 4:
1053 return 0, "E111 indentation is not a multiple of four"
1054@@ -285,6 +396,13 @@
1055
1056 - Immediately before the open parenthesis that starts an indexing or
1057 slicing.
1058+
1059+ Okay: spam(1)
1060+ E211: spam (1)
1061+
1062+ Okay: dict['key'] = list[index]
1063+ E211: dict ['key'] = list[index]
1064+ E211: dict['key'] = list [index]
1065 """
1066 prev_type = tokens[0][0]
1067 prev_text = tokens[0][1]
1068@@ -294,9 +412,11 @@
1069 if (token_type == tokenize.OP and
1070 text in '([' and
1071 start != prev_end and
1072- prev_type == tokenize.NAME and
1073+ (prev_type == tokenize.NAME or prev_text in '}])') and
1074+ # Syntax "class A (B):" is allowed, but avoid it
1075 (index < 2 or tokens[index - 2][1] != 'class') and
1076- (not iskeyword(prev_text))):
1077+ # Allow "return (a.foo for a in range(5))"
1078+ (not keyword.iskeyword(prev_text))):
1079 return prev_end, "E211 whitespace before '%s'" % text
1080 prev_type = token_type
1081 prev_text = text
1082@@ -309,21 +429,97 @@
1083
1084 - More than one space around an assignment (or other) operator to
1085 align it with another.
1086- """
1087- line = logical_line
1088- for operator in operators:
1089- found = line.find(' ' + operator)
1090- if found > -1:
1091- return found, "E221 multiple spaces before operator"
1092- found = line.find(operator + ' ')
1093- if found > -1:
1094- return found, "E222 multiple spaces after operator"
1095- found = line.find('\t' + operator)
1096- if found > -1:
1097- return found, "E223 tab before operator"
1098- found = line.find(operator + '\t')
1099- if found > -1:
1100- return found, "E224 tab after operator"
1101+
1102+ Okay: a = 12 + 3
1103+ E221: a = 4 + 5
1104+ E222: a = 4 + 5
1105+ E223: a = 4\t+ 5
1106+ E224: a = 4 +\t5
1107+ """
1108+ for match in WHITESPACE_AROUND_OPERATOR_REGEX.finditer(logical_line):
1109+ before, whitespace, after = match.groups()
1110+ tab = whitespace == '\t'
1111+ offset = match.start(2)
1112+ if before in OPERATORS:
1113+ return offset, (tab and "E224 tab after operator" or
1114+ "E222 multiple spaces after operator")
1115+ elif after in OPERATORS:
1116+ return offset, (tab and "E223 tab before operator" or
1117+ "E221 multiple spaces before operator")
1118+
1119+
1120+def missing_whitespace_around_operator(logical_line, tokens):
1121+ r"""
1122+ - Always surround these binary operators with a single space on
1123+ either side: assignment (=), augmented assignment (+=, -= etc.),
1124+ comparisons (==, <, >, !=, <>, <=, >=, in, not in, is, is not),
1125+ Booleans (and, or, not).
1126+
1127+ - Use spaces around arithmetic operators.
1128+
1129+ Okay: i = i + 1
1130+ Okay: submitted += 1
1131+ Okay: x = x * 2 - 1
1132+ Okay: hypot2 = x * x + y * y
1133+ Okay: c = (a + b) * (a - b)
1134+ Okay: foo(bar, key='word', *args, **kwargs)
1135+ Okay: baz(**kwargs)
1136+ Okay: negative = -1
1137+ Okay: spam(-1)
1138+ Okay: alpha[:-i]
1139+ Okay: if not -5 < x < +5:\n pass
1140+ Okay: lambda *args, **kw: (args, kw)
1141+
1142+ E225: i=i+1
1143+ E225: submitted +=1
1144+ E225: x = x*2 - 1
1145+ E225: hypot2 = x*x + y*y
1146+ E225: c = (a+b) * (a-b)
1147+ E225: c = alpha -4
1148+ E225: z = x **y
1149+ """
1150+ parens = 0
1151+ need_space = False
1152+ prev_type = tokenize.OP
1153+ prev_text = prev_end = None
1154+ for token_type, text, start, end, line in tokens:
1155+ if token_type in (tokenize.NL, tokenize.NEWLINE, tokenize.ERRORTOKEN):
1156+ # ERRORTOKEN is triggered by backticks in Python 3000
1157+ continue
1158+ if text in ('(', 'lambda'):
1159+ parens += 1
1160+ elif text == ')':
1161+ parens -= 1
1162+ if need_space:
1163+ if start != prev_end:
1164+ need_space = False
1165+ elif text == '>' and prev_text == '<':
1166+ # Tolerate the "<>" operator, even if running Python 3
1167+ pass
1168+ else:
1169+ return prev_end, "E225 missing whitespace around operator"
1170+ elif token_type == tokenize.OP and prev_end is not None:
1171+ if text == '=' and parens:
1172+ # Allow keyword args or defaults: foo(bar=None).
1173+ pass
1174+ elif text in BINARY_OPERATORS:
1175+ need_space = True
1176+ elif text in UNARY_OPERATORS:
1177+ # Allow unary operators: -123, -x, +1.
1178+ # Allow argument unpacking: foo(*args, **kwargs).
1179+ if prev_type == tokenize.OP:
1180+ if prev_text in '}])':
1181+ need_space = True
1182+ elif prev_type == tokenize.NAME:
1183+ if prev_text not in E225NOT_KEYWORDS:
1184+ need_space = True
1185+ else:
1186+ need_space = True
1187+ if need_space and start == prev_end:
1188+ return prev_end, "E225 missing whitespace around operator"
1189+ prev_type = token_type
1190+ prev_text = text
1191+ prev_end = end
1192
1193
1194 def whitespace_around_comma(logical_line):
1195@@ -334,6 +530,11 @@
1196 align it with another.
1197
1198 JCR: This should also be applied around comma etc.
1199+ Note: these checks are disabled by default
1200+
1201+ Okay: a = (1, 2)
1202+ E241: a = (1, 2)
1203+ E242: a = (1,\t2)
1204 """
1205 line = logical_line
1206 for separator in ',;:':
1207@@ -345,9 +546,77 @@
1208 return found + 1, "E242 tab after '%s'" % separator
1209
1210
1211+def whitespace_around_named_parameter_equals(logical_line):
1212+ """
1213+ Don't use spaces around the '=' sign when used to indicate a
1214+ keyword argument or a default parameter value.
1215+
1216+ Okay: def complex(real, imag=0.0):
1217+ Okay: return magic(r=real, i=imag)
1218+ Okay: boolean(a == b)
1219+ Okay: boolean(a != b)
1220+ Okay: boolean(a <= b)
1221+ Okay: boolean(a >= b)
1222+
1223+ E251: def complex(real, imag = 0.0):
1224+ E251: return magic(r = real, i = imag)
1225+ """
1226+ parens = 0
1227+ for match in WHITESPACE_AROUND_NAMED_PARAMETER_REGEX.finditer(
1228+ logical_line):
1229+ text = match.group()
1230+ if parens and len(text) == 3:
1231+ issue = "E251 no spaces around keyword / parameter equals"
1232+ return match.start(), issue
1233+ if text == '(':
1234+ parens += 1
1235+ elif text == ')':
1236+ parens -= 1
1237+
1238+
1239+def whitespace_before_inline_comment(logical_line, tokens):
1240+ """
1241+ Separate inline comments by at least two spaces.
1242+
1243+ An inline comment is a comment on the same line as a statement. Inline
1244+ comments should be separated by at least two spaces from the statement.
1245+ They should start with a # and a single space.
1246+
1247+ Okay: x = x + 1 # Increment x
1248+ Okay: x = x + 1 # Increment x
1249+ E261: x = x + 1 # Increment x
1250+ E262: x = x + 1 #Increment x
1251+ E262: x = x + 1 # Increment x
1252+ """
1253+ prev_end = (0, 0)
1254+ for token_type, text, start, end, line in tokens:
1255+ if token_type == tokenize.NL:
1256+ continue
1257+ if token_type == tokenize.COMMENT:
1258+ if not line[:start[1]].strip():
1259+ continue
1260+ if prev_end[0] == start[0] and start[1] < prev_end[1] + 2:
1261+ return (prev_end,
1262+ "E261 at least two spaces before inline comment")
1263+ if (len(text) > 1 and text.startswith('# ')
1264+ or not text.startswith('# ')):
1265+ return start, "E262 inline comment should start with '# '"
1266+ else:
1267+ prev_end = end
1268+
1269+
1270 def imports_on_separate_lines(logical_line):
1271- """
1272+ r"""
1273 Imports should usually be on separate lines.
1274+
1275+ Okay: import os\nimport sys
1276+ E401: import sys, os
1277+
1278+ Okay: from subprocess import Popen, PIPE
1279+ Okay: from myclas import MyClass
1280+ Okay: from foo.bar.yourclass import YourClass
1281+ Okay: import myclass
1282+ Okay: import foo.bar.yourclass
1283 """
1284 line = logical_line
1285 if line.startswith('import '):
1286@@ -357,17 +626,37 @@
1287
1288
1289 def compound_statements(logical_line):
1290- """
1291+ r"""
1292 Compound statements (multiple statements on the same line) are
1293 generally discouraged.
1294+
1295+ While sometimes it's okay to put an if/for/while with a small body
1296+ on the same line, never do this for multi-clause statements. Also
1297+ avoid folding such long lines!
1298+
1299+ Okay: if foo == 'blah':\n do_blah_thing()
1300+ Okay: do_one()
1301+ Okay: do_two()
1302+ Okay: do_three()
1303+
1304+ E701: if foo == 'blah': do_blah_thing()
1305+ E701: for x in lst: total += x
1306+ E701: while t < 10: t = delay()
1307+ E701: if foo == 'blah': do_blah_thing()
1308+ E701: else: do_non_blah_thing()
1309+ E701: try: something()
1310+ E701: finally: cleanup()
1311+ E701: if foo == 'blah': one(); two(); three()
1312+
1313+ E702: do_one(); do_two(); do_three()
1314 """
1315 line = logical_line
1316 found = line.find(':')
1317 if -1 < found < len(line) - 1:
1318 before = line[:found]
1319- if (before.count('{') <= before.count('}') and # {'a': 1} (dict)
1320- before.count('[') <= before.count(']') and # [1:2] (slice)
1321- not re.search(r'\blambda\b', before)): # lambda x: x
1322+ if (before.count('{') <= before.count('}') and # {'a': 1} (dict)
1323+ before.count('[') <= before.count(']') and # [1:2] (slice)
1324+ not re.search(r'\blambda\b', before)): # lambda x: x
1325 return found, "E701 multiple statements on one line (colon)"
1326 found = line.find(';')
1327 if -1 < found:
1328@@ -397,16 +686,49 @@
1329 continuation characters thanks to the containing parentheses. The older
1330 form will be removed in Python 3000.
1331 """
1332- match = raise_comma_match(logical_line)
1333+ match = RAISE_COMMA_REGEX.match(logical_line)
1334 if match:
1335 return match.start(1), "W602 deprecated form of raising exception"
1336
1337
1338+def python_3000_not_equal(logical_line):
1339+ """
1340+ != can also be written <>, but this is an obsolete usage kept for
1341+ backwards compatibility only. New code should always use !=.
1342+ The older syntax is removed in Python 3000.
1343+ """
1344+ pos = logical_line.find('<>')
1345+ if pos > -1:
1346+ return pos, "W603 '<>' is deprecated, use '!='"
1347+
1348+
1349+def python_3000_backticks(logical_line):
1350+ """
1351+ Backticks are removed in Python 3000.
1352+ Use repr() instead.
1353+ """
1354+ pos = logical_line.find('`')
1355+ if pos > -1:
1356+ return pos, "W604 backticks are deprecated, use 'repr()'"
1357+
1358+
1359 ##############################################################################
1360 # Helper functions
1361 ##############################################################################
1362
1363
1364+if '' == ''.encode():
1365+ # Python 2: implicit encoding.
1366+ def readlines(filename):
1367+ return open(filename).readlines()
1368+else:
1369+ # Python 3: decode to latin-1.
1370+ # This function is lazy, it does not read the encoding declaration.
1371+ # XXX: use tokenize.detect_encoding()
1372+ def readlines(filename):
1373+ return open(filename, encoding='latin-1').readlines()
1374+
1375+
1376 def expand_indent(line):
1377 """
1378 Return the amount of indentation.
1379@@ -426,7 +748,7 @@
1380 result = 0
1381 for char in line:
1382 if char == '\t':
1383- result = result / 8 * 8 + 8
1384+ result = result // 8 * 8 + 8
1385 elif char == ' ':
1386 result += 1
1387 else:
1388@@ -434,34 +756,6 @@
1389 return result
1390
1391
1392-##############################################################################
1393-# Framework to run all checks
1394-##############################################################################
1395-
1396-
1397-def message(text):
1398- """Print a message."""
1399- # print >> sys.stderr, options.prog + ': ' + text
1400- # print >> sys.stderr, text
1401- print text
1402-
1403-
1404-def find_checks(argument_name):
1405- """
1406- Find all globally visible functions where the first argument name
1407- starts with argument_name.
1408- """
1409- checks = []
1410- function_type = type(find_checks)
1411- for name, function in globals().iteritems():
1412- if type(function) is function_type:
1413- args = inspect.getargspec(function)[0]
1414- if len(args) >= 1 and args[0].startswith(argument_name):
1415- checks.append((name, function, args))
1416- checks.sort()
1417- return checks
1418-
1419-
1420 def mute_string(text):
1421 """
1422 Replace contents with 'xxx' to prevent syntax matching.
1423@@ -487,18 +781,53 @@
1424 return text[:start] + 'x' * (end - start) + text[end:]
1425
1426
1427-class Checker:
1428+def message(text):
1429+ """Print a message."""
1430+ # print >> sys.stderr, options.prog + ': ' + text
1431+ # print >> sys.stderr, text
1432+ print(text)
1433+
1434+
1435+##############################################################################
1436+# Framework to run all checks
1437+##############################################################################
1438+
1439+
1440+def find_checks(argument_name):
1441+ """
1442+ Find all globally visible functions where the first argument name
1443+ starts with argument_name.
1444+ """
1445+ checks = []
1446+ for name, function in globals().items():
1447+ if not inspect.isfunction(function):
1448+ continue
1449+ args = inspect.getargspec(function)[0]
1450+ if args and args[0].startswith(argument_name):
1451+ codes = ERRORCODE_REGEX.findall(inspect.getdoc(function) or '')
1452+ for code in codes or ['']:
1453+ if not code or not ignore_code(code):
1454+ checks.append((name, function, args))
1455+ break
1456+ checks.sort()
1457+ return checks
1458+
1459+
1460+class Checker(object):
1461 """
1462 Load a Python source file, tokenize it, check coding style.
1463 """
1464
1465- def __init__(self, filename):
1466+ def __init__(self, filename, lines=None):
1467 self.filename = filename
1468- self.lines = file(filename).readlines()
1469- self.physical_checks = find_checks('physical_line')
1470- self.logical_checks = find_checks('logical_line')
1471- options.counters['physical lines'] = \
1472- options.counters.get('physical lines', 0) + len(self.lines)
1473+ if filename is None:
1474+ self.filename = 'stdin'
1475+ self.lines = lines or []
1476+ elif lines is None:
1477+ self.lines = readlines(filename)
1478+ else:
1479+ self.lines = lines
1480+ options.counters['physical lines'] += len(self.lines)
1481
1482 def readline(self):
1483 """
1484@@ -535,7 +864,7 @@
1485 self.physical_line = line
1486 if self.indent_char is None and len(line) and line[0] in ' \t':
1487 self.indent_char = line[0]
1488- for name, check, argument_names in self.physical_checks:
1489+ for name, check, argument_names in options.physical_checks:
1490 result = self.run_check(check, argument_names)
1491 if result is not None:
1492 offset, text = result
1493@@ -551,20 +880,20 @@
1494 previous = None
1495 for token in self.tokens:
1496 token_type, text = token[0:2]
1497- if token_type in (tokenize.COMMENT, tokenize.NL,
1498- tokenize.INDENT, tokenize.DEDENT,
1499- tokenize.NEWLINE):
1500+ if token_type in SKIP_TOKENS:
1501 continue
1502 if token_type == tokenize.STRING:
1503 text = mute_string(text)
1504 if previous:
1505 end_line, end = previous[3]
1506 start_line, start = token[2]
1507- if end_line != start_line: # different row
1508- if self.lines[end_line - 1][end - 1] not in '{[(':
1509+ if end_line != start_line: # different row
1510+ prev_text = self.lines[end_line - 1][end - 1]
1511+ if prev_text == ',' or (prev_text not in '{[('
1512+ and text not in '}])'):
1513 logical.append(' ')
1514 length += 1
1515- elif end != start: # different column
1516+ elif end != start: # different column
1517 fill = self.lines[end_line - 1][end:start]
1518 logical.append(fill)
1519 length += len(fill)
1520@@ -580,22 +909,21 @@
1521 """
1522 Build a line from tokens and run all logical checks on it.
1523 """
1524- options.counters['logical lines'] = \
1525- options.counters.get('logical lines', 0) + 1
1526+ options.counters['logical lines'] += 1
1527 self.build_tokens_line()
1528 first_line = self.lines[self.mapping[0][1][2][0] - 1]
1529 indent = first_line[:self.mapping[0][1][2][1]]
1530 self.previous_indent_level = self.indent_level
1531 self.indent_level = expand_indent(indent)
1532 if options.verbose >= 2:
1533- print self.logical_line[:80].rstrip()
1534- for name, check, argument_names in self.logical_checks:
1535- if options.verbose >= 3:
1536- print ' ', name
1537+ print(self.logical_line[:80].rstrip())
1538+ for name, check, argument_names in options.logical_checks:
1539+ if options.verbose >= 4:
1540+ print(' ' + name)
1541 result = self.run_check(check, argument_names)
1542 if result is not None:
1543 offset, text = result
1544- if type(offset) is tuple:
1545+ if isinstance(offset, tuple):
1546 original_number, original_offset = offset
1547 else:
1548 for token_offset, token in self.mapping:
1549@@ -607,20 +935,29 @@
1550 text, check)
1551 self.previous_logical = self.logical_line
1552
1553- def check_all(self):
1554+ def check_all(self, expected=None, line_offset=0):
1555 """
1556 Run all checks on the input file.
1557 """
1558+ self.expected = expected or ()
1559+ self.line_offset = line_offset
1560+ self.line_number = 0
1561 self.file_errors = 0
1562- self.line_number = 0
1563 self.indent_char = None
1564 self.indent_level = 0
1565 self.previous_logical = ''
1566 self.blank_lines = 0
1567+ self.blank_lines_before_comment = 0
1568 self.tokens = []
1569 parens = 0
1570 for token in tokenize.generate_tokens(self.readline_check_physical):
1571- # print tokenize.tok_name[token[0]], repr(token)
1572+ if options.verbose >= 3:
1573+ if token[2][0] == token[3][0]:
1574+ pos = '[%s:%s]' % (token[2][1] or '', token[3][1])
1575+ else:
1576+ pos = 'l.%s' % token[3][0]
1577+ print('l.%s\t%s\t%s\t%r' %
1578+ (token[2][0], pos, tokenize.tok_name[token[0]], token[1]))
1579 self.tokens.append(token)
1580 token_type, text = token[0:2]
1581 if token_type == tokenize.OP and text in '([{':
1582@@ -630,40 +967,49 @@
1583 if token_type == tokenize.NEWLINE and not parens:
1584 self.check_logical()
1585 self.blank_lines = 0
1586+ self.blank_lines_before_comment = 0
1587 self.tokens = []
1588 if token_type == tokenize.NL and not parens:
1589- self.blank_lines += 1
1590+ if len(self.tokens) <= 1:
1591+ # The physical line contains only this token.
1592+ self.blank_lines += 1
1593 self.tokens = []
1594 if token_type == tokenize.COMMENT:
1595 source_line = token[4]
1596 token_start = token[2][1]
1597 if source_line[:token_start].strip() == '':
1598+ self.blank_lines_before_comment = max(self.blank_lines,
1599+ self.blank_lines_before_comment)
1600 self.blank_lines = 0
1601+ if text.endswith('\n') and not parens:
1602+ # The comment also ends a physical line. This works around
1603+ # Python < 2.6 behaviour, which does not generate NL after
1604+ # a comment which is on a line by itself.
1605+ self.tokens = []
1606 return self.file_errors
1607
1608 def report_error(self, line_number, offset, text, check):
1609 """
1610 Report an error, according to options.
1611 """
1612+ code = text[:4]
1613+ if ignore_code(code):
1614+ return
1615 if options.quiet == 1 and not self.file_errors:
1616 message(self.filename)
1617+ if code in options.counters:
1618+ options.counters[code] += 1
1619+ else:
1620+ options.counters[code] = 1
1621+ options.messages[code] = text[5:]
1622+ if options.quiet or code in self.expected:
1623+ # Don't care about expected errors or warnings
1624+ return
1625 self.file_errors += 1
1626- code = text[:4]
1627- options.counters[code] = options.counters.get(code, 0) + 1
1628- options.messages[code] = text[5:]
1629- if options.quiet:
1630- return
1631- if options.testsuite:
1632- base = os.path.basename(self.filename)[:4]
1633- if base == code:
1634- return
1635- if base[0] == 'E' and code[0] == 'W':
1636- return
1637- if ignore_code(code):
1638- return
1639 if options.counters[code] == 1 or options.repeat:
1640 message("%s:%s:%d: %s" %
1641- (self.filename, line_number, offset + 1, text))
1642+ (self.filename, self.line_offset + line_number,
1643+ offset + 1, text))
1644 if options.show_source:
1645 line = self.lines[line_number - 1]
1646 message(line.rstrip())
1647@@ -676,35 +1022,33 @@
1648 """
1649 Run all checks on a Python source file.
1650 """
1651- if excluded(filename) or not filename_match(filename):
1652- return {}
1653 if options.verbose:
1654 message('checking ' + filename)
1655- options.counters['files'] = options.counters.get('files', 0) + 1
1656 errors = Checker(filename).check_all()
1657- if options.testsuite and not errors:
1658- message("%s: %s" % (filename, "no errors found"))
1659-
1660-
1661-def input_dir(dirname):
1662+
1663+
1664+def input_dir(dirname, runner=None):
1665 """
1666 Check all Python source files in this directory and all subdirectories.
1667 """
1668 dirname = dirname.rstrip('/')
1669 if excluded(dirname):
1670 return
1671+ if runner is None:
1672+ runner = input_file
1673 for root, dirs, files in os.walk(dirname):
1674 if options.verbose:
1675 message('directory ' + root)
1676- options.counters['directories'] = \
1677- options.counters.get('directories', 0) + 1
1678+ options.counters['directories'] += 1
1679 dirs.sort()
1680 for subdir in dirs:
1681 if excluded(subdir):
1682 dirs.remove(subdir)
1683 files.sort()
1684 for filename in files:
1685- input_file(os.path.join(root, filename))
1686+ if filename_match(filename) and not excluded(filename):
1687+ options.counters['files'] += 1
1688+ runner(os.path.join(root, filename))
1689
1690
1691 def excluded(filename):
1692@@ -733,12 +1077,23 @@
1693 def ignore_code(code):
1694 """
1695 Check if options.ignore contains a prefix of the error code.
1696+ If options.select contains a prefix of the error code, do not ignore it.
1697 """
1698+ for select in options.select:
1699+ if code.startswith(select):
1700+ return False
1701 for ignore in options.ignore:
1702 if code.startswith(ignore):
1703 return True
1704
1705
1706+def reset_counters():
1707+ for key in list(options.counters.keys()):
1708+ if key not in BENCHMARK_KEYS:
1709+ del options.counters[key]
1710+ options.messages = {}
1711+
1712+
1713 def get_error_statistics():
1714 """Get error statistics."""
1715 return get_statistics("E")
1716@@ -759,7 +1114,7 @@
1717 prefix='E4' matches all errors that have to do with imports
1718 """
1719 stats = []
1720- keys = options.messages.keys()
1721+ keys = list(options.messages.keys())
1722 keys.sort()
1723 for key in keys:
1724 if key.startswith(prefix):
1725@@ -768,24 +1123,131 @@
1726 return stats
1727
1728
1729+def get_count(prefix=''):
1730+ """Return the total count of errors and warnings."""
1731+ keys = list(options.messages.keys())
1732+ count = 0
1733+ for key in keys:
1734+ if key.startswith(prefix):
1735+ count += options.counters[key]
1736+ return count
1737+
1738+
1739 def print_statistics(prefix=''):
1740 """Print overall statistics (number of errors and warnings)."""
1741 for line in get_statistics(prefix):
1742- print line
1743+ print(line)
1744
1745
1746 def print_benchmark(elapsed):
1747 """
1748 Print benchmark numbers.
1749 """
1750- print '%-7.2f %s' % (elapsed, 'seconds elapsed')
1751- keys = ['directories', 'files',
1752- 'logical lines', 'physical lines']
1753- for key in keys:
1754- if key in options.counters:
1755- print '%-7d %s per second (%d total)' % (
1756- options.counters[key] / elapsed, key,
1757- options.counters[key])
1758+ print('%-7.2f %s' % (elapsed, 'seconds elapsed'))
1759+ for key in BENCHMARK_KEYS:
1760+ print('%-7d %s per second (%d total)' % (
1761+ options.counters[key] / elapsed, key,
1762+ options.counters[key]))
1763+
1764+
1765+def run_tests(filename):
1766+ """
1767+ Run all the tests from a file.
1768+
1769+ A test file can provide many tests. Each test starts with a declaration.
1770+ This declaration is a single line starting with '#:'.
1771+ It declares codes of expected failures, separated by spaces or 'Okay'
1772+ if no failure is expected.
1773+ If the file does not contain such declaration, it should pass all tests.
1774+ If the declaration is empty, following lines are not checked, until next
1775+ declaration.
1776+
1777+ Examples:
1778+
1779+ * Only E224 and W701 are expected: #: E224 W701
1780+ * Following example is conform: #: Okay
1781+ * Don't check these lines: #:
1782+ """
1783+ lines = readlines(filename) + ['#:\n']
1784+ line_offset = 0
1785+ codes = ['Okay']
1786+ testcase = []
1787+ for index, line in enumerate(lines):
1788+ if not line.startswith('#:'):
1789+ if codes:
1790+ # Collect the lines of the test case
1791+ testcase.append(line)
1792+ continue
1793+ if codes and index > 0:
1794+ label = '%s:%s:1' % (filename, line_offset + 1)
1795+ codes = [c for c in codes if c != 'Okay']
1796+ # Run the checker
1797+ errors = Checker(filename, testcase).check_all(codes, line_offset)
1798+ # Check if the expected errors were found
1799+ for code in codes:
1800+ if not options.counters.get(code):
1801+ errors += 1
1802+ message('%s: error %s not found' % (label, code))
1803+ if options.verbose and not errors:
1804+ message('%s: passed (%s)' % (label, ' '.join(codes)))
1805+ # Keep showing errors for multiple tests
1806+ reset_counters()
1807+ # output the real line numbers
1808+ line_offset = index
1809+ # configure the expected errors
1810+ codes = line.split()[1:]
1811+ # empty the test case buffer
1812+ del testcase[:]
1813+
1814+
1815+def selftest():
1816+ """
1817+ Test all check functions with test cases in docstrings.
1818+ """
1819+ count_passed = 0
1820+ count_failed = 0
1821+ checks = options.physical_checks + options.logical_checks
1822+ for name, check, argument_names in checks:
1823+ for line in check.__doc__.splitlines():
1824+ line = line.lstrip()
1825+ match = SELFTEST_REGEX.match(line)
1826+ if match is None:
1827+ continue
1828+ code, source = match.groups()
1829+ checker = Checker(None)
1830+ for part in source.split(r'\n'):
1831+ part = part.replace(r'\t', '\t')
1832+ part = part.replace(r'\s', ' ')
1833+ checker.lines.append(part + '\n')
1834+ options.quiet = 2
1835+ checker.check_all()
1836+ error = None
1837+ if code == 'Okay':
1838+ if len(options.counters) > len(BENCHMARK_KEYS):
1839+ codes = [key for key in options.counters.keys()
1840+ if key not in BENCHMARK_KEYS]
1841+ error = "incorrectly found %s" % ', '.join(codes)
1842+ elif not options.counters.get(code):
1843+ error = "failed to find %s" % code
1844+ # Reset the counters
1845+ reset_counters()
1846+ if not error:
1847+ count_passed += 1
1848+ else:
1849+ count_failed += 1
1850+ if len(checker.lines) == 1:
1851+ print("pep8.py: %s: %s" %
1852+ (error, checker.lines[0].rstrip()))
1853+ else:
1854+ print("pep8.py: %s:" % error)
1855+ for line in checker.lines:
1856+ print(line.rstrip())
1857+ if options.verbose:
1858+ print("%d passed and %d failed." % (count_passed, count_failed))
1859+ if count_failed:
1860+ print("Test failed.")
1861+ else:
1862+ print("Test passed.")
1863
1864
1865 def process_options(arglist=None):
1866@@ -793,26 +1255,36 @@
1867 Process options passed either via arglist or via command line args.
1868 """
1869 global options, args
1870- usage = "%prog [options] input ..."
1871- parser = OptionParser(usage)
1872+ parser = OptionParser(version=__version__,
1873+ usage="%prog [options] input ...")
1874 parser.add_option('-v', '--verbose', default=0, action='count',
1875 help="print status messages, or debug with -vv")
1876 parser.add_option('-q', '--quiet', default=0, action='count',
1877 help="report only file names, or nothing with -qq")
1878- parser.add_option('--exclude', metavar='patterns', default=default_exclude,
1879- help="skip matches (default %s)" % default_exclude)
1880- parser.add_option('--filename', metavar='patterns',
1881- help="only check matching files (e.g. *.py)")
1882+ parser.add_option('-r', '--repeat', action='store_true',
1883+ help="show all occurrences of the same error")
1884+ parser.add_option('--exclude', metavar='patterns', default=DEFAULT_EXCLUDE,
1885+ help="exclude files or directories which match these "
1886+ "comma separated patterns (default: %s)" %
1887+ DEFAULT_EXCLUDE)
1888+ parser.add_option('--filename', metavar='patterns', default='*.py',
1889+ help="when parsing directories, only check filenames "
1890+ "matching these comma separated patterns (default: "
1891+ "*.py)")
1892+ parser.add_option('--select', metavar='errors', default='',
1893+ help="select errors and warnings (e.g. E,W6)")
1894 parser.add_option('--ignore', metavar='errors', default='',
1895 help="skip errors and warnings (e.g. E4,W)")
1896- parser.add_option('--repeat', action='store_true',
1897- help="show all occurrences of the same error")
1898 parser.add_option('--show-source', action='store_true',
1899 help="show source code for each error")
1900 parser.add_option('--show-pep8', action='store_true',
1901 help="show text of PEP 8 for each error")
1902 parser.add_option('--statistics', action='store_true',
1903 help="count errors and warnings")
1904+ parser.add_option('--count', action='store_true',
1905+ help="print total number of errors and warnings "
1906+ "to standard error and set exit code to 1 if "
1907+ "total is not null")
1908 parser.add_option('--benchmark', action='store_true',
1909 help="measure processing speed")
1910 parser.add_option('--testsuite', metavar='dir',
1911@@ -822,7 +1294,7 @@
1912 options, args = parser.parse_args(arglist)
1913 if options.testsuite:
1914 args.append(options.testsuite)
1915- if len(args) == 0:
1916+ if not args and not options.doctest:
1917 parser.error('input not specified')
1918 options.prog = os.path.basename(sys.argv[0])
1919 options.exclude = options.exclude.split(',')
1920@@ -830,13 +1302,25 @@
1921 options.exclude[index] = options.exclude[index].rstrip('/')
1922 if options.filename:
1923 options.filename = options.filename.split(',')
1924+ if options.select:
1925+ options.select = options.select.split(',')
1926+ else:
1927+ options.select = []
1928 if options.ignore:
1929 options.ignore = options.ignore.split(',')
1930- else:
1931+ elif options.select:
1932+ # Ignore all checks which are not explicitly selected
1933+ options.ignore = ['']
1934+ elif options.testsuite or options.doctest:
1935+ # For doctest and testsuite, all checks are required
1936 options.ignore = []
1937- options.counters = {}
1938+ else:
1939+ # The default choice: ignore controversial checks
1940+ options.ignore = DEFAULT_IGNORE.split(',')
1941+ options.physical_checks = find_checks('physical_line')
1942+ options.logical_checks = find_checks('logical_line')
1943+ options.counters = dict.fromkeys(BENCHMARK_KEYS, 0)
1944 options.messages = {}
1945-
1946 return options, args
1947
1948
1949@@ -847,18 +1331,29 @@
1950 options, args = process_options()
1951 if options.doctest:
1952 import doctest
1953- return doctest.testmod()
1954+ doctest.testmod(verbose=options.verbose)
1955+ selftest()
1956+ if options.testsuite:
1957+ runner = run_tests
1958+ else:
1959+ runner = input_file
1960 start_time = time.time()
1961 for path in args:
1962 if os.path.isdir(path):
1963- input_dir(path)
1964- else:
1965- input_file(path)
1966+ input_dir(path, runner=runner)
1967+ elif not excluded(path):
1968+ options.counters['files'] += 1
1969+ runner(path)
1970 elapsed = time.time() - start_time
1971 if options.statistics:
1972 print_statistics()
1973 if options.benchmark:
1974 print_benchmark(elapsed)
1975+ count = get_count()
1976+ if count:
1977+ if options.count:
1978+ sys.stderr.write(str(count) + '\n')
1979+ sys.exit(1)
1980
1981
1982 if __name__ == '__main__':
1983
1984=== modified file 'pocketlint/formatcheck.py'
1985--- pocketlint/formatcheck.py 2011-04-15 13:37:44 +0000
1986+++ pocketlint/formatcheck.py 2011-10-14 21:24:26 +0000
1987@@ -3,10 +3,15 @@
1988 # This software is licensed under the MIT license (see the file COPYING).
1989 """Check for syntax and style problems."""
1990
1991-from __future__ import with_statement
1992-
1993 __metaclass__ = type
1994
1995+
1996+__all__ = [
1997+ 'Reporter',
1998+ 'UniversalChecker',
1999+ ]
2000+
2001+
2002 import compiler
2003 import htmlentitydefs
2004 import logging
2005@@ -31,26 +36,39 @@
2006 from formatdoctest import DoctestReviewer
2007
2008 import contrib.pep8 as pep8
2009-from contrib.pyflakes.checker import Checker
2010+from contrib.cssccc import CSSCodingConventionChecker
2011+from contrib.pyflakes.checker import Checker as PyFlakesChecker
2012 try:
2013 import cssutils
2014 HAS_CSSUTILS = True
2015 except ImportError:
2016 HAS_CSSUTILS = False
2017
2018-# Javascript checking is available if spider money's js is available.
2019-js = subprocess.Popen(
2020- ['which', 'js'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2021-js_exec, ignore = js.communicate()
2022-if js.returncode != 0:
2023- JS = None
2024-else:
2025- JS = js_exec.strip()
2026-
2027-__all__ = [
2028- 'Reporter',
2029- 'UniversalChecker',
2030- ]
2031+
2032+def find_exec(names):
2033+ """Return the name of a GI enabled JS interpreter."""
2034+ for name in names:
2035+ js = subprocess.Popen(
2036+ ['which', name], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2037+ js_exec, ignore = js.communicate()
2038+ if js.returncode == 0:
2039+ return js_exec.strip()
2040+
2041+
2042+JS = find_exec(['gjs', 'seed'])
2043+
2044+
2045+class PocketLintPyFlakesChecker(PyFlakesChecker):
2046+ '''PocketLint checker for pyflakes.
2047+
2048+ This is here to work around some of the pyflakes problems.
2049+ '''
2050+
2051+ def NAME(self, node):
2052+ '''Locate name. Ignore WindowsErrors.'''
2053+ if node.name == 'WindowsError':
2054+ return
2055+ return super(PocketLintPyFlakesChecker, self).NAME(node)
2056
2057
2058 class Reporter:
2059@@ -65,10 +83,12 @@
2060 self.treestore = self.file_lines_view.get_model()
2061 self.piter = None
2062 self._last_file_name = None
2063+ self.call_count = 0
2064
2065 def __call__(self, line_no, message, icon=None,
2066 base_dir=None, file_name=None):
2067 """Report a message."""
2068+ self.call_count += 1
2069 if self.report_type == self.FILE_LINES:
2070 self._message_file_lines(
2071 line_no, message, icon=icon,
2072@@ -117,6 +137,7 @@
2073 ZCML = object()
2074 DOCBOOK = object()
2075 LOG = object()
2076+ SQL = object()
2077
2078 XML_LIKE = (XML, XSLT, HTML, ZPT, ZCML, DOCBOOK)
2079
2080@@ -130,6 +151,7 @@
2081 'text/css': CSS,
2082 'text/html': HTML,
2083 'text/plain': TEXT,
2084+ 'text/x-sql': SQL,
2085 'text/x-log': LOG,
2086 'application/javascript': JAVASCRIPT,
2087 'application/xml': XML,
2088@@ -285,6 +307,20 @@
2089 self.check_conflicts(line_no, line)
2090
2091
2092+class SQLChecker(BaseChecker, AnyTextMixin):
2093+ """Verify SQL style."""
2094+
2095+ def check(self):
2096+ """Call each line_method for each line in text."""
2097+ # Consider http://code.google.com/p/python-sqlparse/ to verify
2098+ # keywords and reformatting.
2099+ for line_no, line in enumerate(self.text.splitlines()):
2100+ line_no += 1
2101+ self.check_trailing_whitespace(line_no, line)
2102+ self.check_tab(line_no, line)
2103+ self.check_conflicts(line_no, line)
2104+
2105+
2106 class XMLChecker(BaseChecker, AnyTextMixin):
2107 """Check XML documents."""
2108
2109@@ -312,7 +348,7 @@
2110 start, end = match.span(0)
2111 text = text[:start] + self.xhtml_doctype + text[end:]
2112 try:
2113- root = ElementTree.parse(StringIO(text), parser)
2114+ ElementTree.parse(StringIO(text), parser)
2115 except (ExpatError, ParseError), error:
2116 if hasattr(error, 'code'):
2117 error_message = ErrorString(error.code)
2118@@ -326,7 +362,7 @@
2119 error_lineno = 0
2120 else:
2121 error_message, location = str(error).rsplit(':')
2122- error_lineno = int(location.split(',')[0].split()[1])- offset
2123+ error_lineno = int(location.split(',')[0].split()[1]) - offset
2124 self.message(error_lineno, error_message, icon='error')
2125 self.check_text()
2126
2127@@ -378,7 +414,18 @@
2128
2129 def check(self):
2130 """Check the syntax of the CSS code."""
2131- if self.text == '' or not HAS_CSSUTILS:
2132+ if self.text == '':
2133+ return
2134+
2135+ self.check_cssutils()
2136+ self.check_text()
2137+ # CSS coding conventoins checks should go last since they rely
2138+ # on previous checks.
2139+ self.check_css_coding_conventions()
2140+
2141+ def check_cssutils(self):
2142+ """Check the CSS code by parsing it using CSSUtils module."""
2143+ if not HAS_CSSUTILS:
2144 return
2145 handler = CSSReporterHandler(self)
2146 log = logging.getLogger('pocket-lint')
2147@@ -387,7 +434,6 @@
2148 log=log, loglevel=logging.INFO, raiseExceptions=False)
2149 parser.parseString(self.text)
2150 log.removeHandler(handler)
2151- self.check_text()
2152
2153 def check_text(self):
2154 """Call each line_method for each line in text."""
2155@@ -398,22 +444,29 @@
2156 self.check_conflicts(line_no, line)
2157 self.check_tab(line_no, line)
2158
2159+ def check_css_coding_conventions(self):
2160+ """Check the input using CSS Coding Convention checker."""
2161+ CSSCodingConventionChecker(self.text, logger=self.message).check()
2162+
2163
2164 class PythonChecker(BaseChecker, AnyTextMixin):
2165 """Check python source code."""
2166
2167+ # This regex is taken from PEP 0263.
2168+ encoding_pattern = re.compile("coding[:=]\s*([-\w.]+)")
2169+
2170 def __init__(self, file_path, text, reporter=None):
2171 super(PythonChecker, self).__init__(
2172 file_path, text, reporter=reporter)
2173- self.is_utf8 = False
2174+ self.encoding = 'ascii'
2175
2176 def check(self):
2177 """Check the syntax of the python code."""
2178 if self.text == '':
2179 return
2180+ self.check_text()
2181 self.check_flakes()
2182 self.check_pep8()
2183- self.check_text()
2184
2185 def check_flakes(self):
2186 """Check compilation and syntax."""
2187@@ -421,13 +474,12 @@
2188 tree = compiler.parse(self.text)
2189 except (SyntaxError, IndentationError), exc:
2190 line_no = exc.lineno or 0
2191- offset = exc.offset or 0
2192 line = exc.text or ''
2193 explanation = 'Could not compile; %s' % exc.msg
2194 message = '%s: %s' % (explanation, line.strip())
2195 self.message(line_no, message, icon='error')
2196 else:
2197- warnings = Checker(tree)
2198+ warnings = PocketLintPyFlakesChecker(tree)
2199 for warning in warnings.messages:
2200 dummy, line_no, message = str(warning).split(':')
2201 self.message(int(line_no), message.strip(), icon='error')
2202@@ -448,20 +500,24 @@
2203 except TokenError, er:
2204 message, location = er.args
2205 self.message(location[0], message, icon='error')
2206+ except IndentationError, er:
2207+ message, location = er.args
2208+ message = "%s: %s" % (message, location[3].strip())
2209+ self.message(location[1], message, icon='error')
2210 finally:
2211- Checker.report_error = original_report_error
2212+ pep8.Checker.report_error = original_report_error
2213
2214 def check_text(self):
2215 """Call each line_method for each line in text."""
2216 for line_no, line in enumerate(self.text.splitlines()):
2217 line_no += 1
2218- if line_no in (1, 2) and '# -*- coding: utf-8 -*-' in line:
2219- self.is_utf8 = True
2220+ if line_no in (1, 2):
2221+ match = self.encoding_pattern.search(line)
2222+ if match:
2223+ self.encoding = match.group(1).lower()
2224 self.check_pdb(line_no, line)
2225 self.check_length(line_no, line)
2226- self.check_trailing_whitespace(line_no, line)
2227 self.check_conflicts(line_no, line)
2228- self.check_tab(line_no, line)
2229 self.check_ascii(line_no, line)
2230
2231 def check_pdb(self, line_no, line):
2232@@ -473,10 +529,10 @@
2233
2234 def check_ascii(self, line_no, line):
2235 """Check that the line is ascii."""
2236- if self.is_utf8:
2237+ if self.encoding != 'ascii':
2238 return
2239 try:
2240- ascii_line = line.encode('ascii')
2241+ line.encode('ascii')
2242 except UnicodeEncodeError, error:
2243 self.message(
2244 line_no, 'Non-ascii characer at position %s.' % error.end,
2245@@ -494,7 +550,7 @@
2246 """Check the syntax of the javascript code."""
2247 if JS is None or self.text == '':
2248 return
2249- args = [JS, '-f', self.FULLJSLINT, self.JSREPORTER, self.text]
2250+ args = [JS, self.JSREPORTER, self.FULLJSLINT, self.file_path]
2251 jslint = subprocess.Popen(
2252 args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2253 issues, errors = jslint.communicate()
2254@@ -502,7 +558,9 @@
2255 if issues:
2256 for issue in issues.splitlines():
2257 line_no, char_no_, message = issue.split('::')
2258- self.message(int(line_no), message, icon='error')
2259+ line_no = int(line_no)
2260+ line_no -= 1
2261+ self.message(line_no, message, icon='error')
2262 self.check_text()
2263
2264 def check_text(self):
2265@@ -530,6 +588,7 @@
2266 def check_sources(sources, reporter=None):
2267 if reporter is None:
2268 reporter = Reporter(Reporter.CONSOLE)
2269+ reporter.call_count = 0
2270 for source in sources:
2271 file_path = os.path.normpath(source)
2272 if os.path.isdir(source) or not Language.is_editable(source):
2273@@ -540,6 +599,7 @@
2274 checker = UniversalChecker(
2275 file_path, text=text, language=language, reporter=reporter)
2276 checker.check()
2277+ return reporter.call_count
2278
2279
2280 def main(argv=None):
2281@@ -554,7 +614,7 @@
2282 if options.verbose:
2283 pass
2284 reporter = Reporter(Reporter.CONSOLE)
2285- check_sources(sources, reporter)
2286+ return check_sources(sources, reporter)
2287
2288
2289 if __name__ == '__main__':
2290
2291=== modified file 'pocketlint/jsreporter.js'
2292--- pocketlint/jsreporter.js 2011-04-15 13:37:44 +0000
2293+++ pocketlint/jsreporter.js 2011-10-14 21:24:26 +0000
2294@@ -1,44 +1,101 @@
2295-// Copyright (C) 2009-2010 - Curtis Hovey <sinzui.is at verizon.net>
2296+// Copyright (C) 2009-2011 - Curtis Hovey <sinzui.is at verizon.net>
2297 // This software is licensed under the MIT license (see the file COPYING).
2298
2299+// Run like:
2300+// <seed|gjs> jsreporter.js <path/to/fulljslint.js> <path/file/to/lint.js>
2301+
2302+
2303+function get_seed() {
2304+ // Define a common global object like seed.
2305+ var argv = ['gjs', 'jsreporter.js'];
2306+ var i;
2307+ for (i = 0; i < ARGV.length; i++) {
2308+ argv.push(ARGV[i]);
2309+ }
2310+ return {
2311+ 'print': print,
2312+ 'argv': argv
2313+ };
2314+ }
2315+
2316+
2317+var Seed = Seed || get_seed();
2318+
2319+
2320+jslint_path = Seed.argv[2].substring(0, Seed.argv[2].lastIndexOf('/'));
2321+imports.searchPath.push(jslint_path);
2322+var JSLINT = imports.fulljslint.JSLINT;
2323+
2324+
2325+function get_file_content(file_path) {
2326+ // Return the content of the file.
2327+ var Gio = imports.gi.Gio;
2328+ var file = Gio.file_new_for_path(file_path);
2329+ var istream = file.read(null);
2330+ var dstream = new Gio.DataInputStream({base_stream: istream});
2331+ var content_and_count = dstream.read_upto("", -1, null);
2332+ istream.close(null);
2333+ dstream = null;
2334+ return content_and_count[0];
2335+ }
2336+
2337+
2338 function report_implied_names() {
2339 // Report about implied global names.
2340 var implied_names = [];
2341- for (var name in JSLINT.implied) {
2342- if (JSLINT.implied.hasOwnPropery(name)) {
2343- implied_names.push(name);
2344+ var prop;
2345+ for (prop in JSLINT.implied) {
2346+ if (JSLINT.implied.hasOwnProperty(prop)) {
2347+ implied_names.push(prop);
2348 }
2349 }
2350 if (implied_names.length > 0) {
2351 implied_names.sort();
2352- print('0::0::Implied globals:' + implied_names.join(', '));
2353+ return '0::0::Implied globals:' + implied_names.join(', ');
2354 }
2355+ return '';
2356 }
2357
2358
2359 function report_lint_errors() {
2360 // Report about lint errors.
2361- for (var i = 0; i < JSLINT.errors.length; i++) {
2362+ var errors = [];
2363+ var i;
2364+ for (i = 0; i < JSLINT.errors.length; i++) {
2365 var error = JSLINT.errors[i];
2366 if (error === null) {
2367- print('0::0::JSLINT had a fatal error.');
2368+ error = {
2369+ 'line': -1,
2370+ 'character': -1,
2371+ 'reason': 'JSLINT had a fatal error.'
2372+ };
2373 }
2374 // Fix the line and character offset for editors.
2375 error.line += 1;
2376 error.character += 1;
2377- print(error.line + '::' + error.character + '::' + error.reason);
2378+ errors.push(
2379+ [error.line, error.character, error.reason].join('::'));
2380 }
2381+ return errors.join('\n');
2382 }
2383
2384
2385-function main(source_script) {
2386+function lint_script() {
2387 // Lint the source and report errors.
2388- var result = JSLINT(source_script);
2389+ var script = get_file_content(Seed.argv[3]);
2390+ var result = JSLINT(script);
2391 if (! result) {
2392- report_lint_errors();
2393- report_implied_names();
2394+ var issues = [];
2395+ errors = report_lint_errors();
2396+ if (errors) {
2397+ issues.push(errors);
2398+ }
2399+ implied = report_implied_names();
2400+ if (implied) {
2401+ issues.push(implied);
2402+ }
2403+ Seed.print(issues.join('\n'));
2404 }
2405 }
2406
2407-
2408-main(arguments[0]);
2409+lint_script();
2410
2411=== modified file 'setup.py' (properties changed: -x to +x)
2412--- setup.py 2011-04-16 15:32:48 +0000
2413+++ setup.py 2011-10-14 21:24:26 +0000
2414@@ -1,9 +1,26 @@
2415+import subprocess
2416+
2417 from distutils.core import setup
2418+from distutils.command.sdist import sdist
2419+
2420+
2421+class SignedSDistCommand(sdist):
2422+ """Sign the source archive with a detached signature."""
2423+
2424+ description = "Sign the source archive after it is generated."
2425+
2426+ def run(self):
2427+ sdist.run(self)
2428+ gpg_args = [
2429+ 'gpg', '--armor', '--sign', '--detach-sig', self.archive_files[0]]
2430+ gpg = subprocess.Popen(
2431+ gpg_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2432+ gpg.communicate()
2433
2434 setup(
2435 name="pocketlint",
2436 description="Pocket-lint a composite linter and style checker.",
2437- version="0.5.13",
2438+ version="0.5.21",
2439 maintainer="Curtis C. Hovey",
2440 maintainer_email="sinzui.is@verizon.net",
2441 url="https://launchpad.net/pocket-lint",
2442@@ -17,4 +34,7 @@
2443 'pocketlint/contrib': ['fulljslint.js'],
2444 },
2445 scripts=['scripts/pocketlint'],
2446+ cmdclass={
2447+ 'signed_sdist': SignedSDistCommand,
2448+ },
2449 )

Subscribers

People subscribed via source and target branches

to all changes: