Skip to content
This repository was archived by the owner on Nov 3, 2023. It is now read-only.

Commit 953dc00

Browse files
committed
Working on problems with various colon and parenthesis combinations in defintions.
[1] Fixed a problem with lambdas as a definition parameter. [2] Fixed a problem with old-style classes. [3] Added a --debug flag that prints debug information. [4] Added some documentation. [5] Changed some functions in Parser that iterated over stream to use self.stream.move(), so they would update self.current.
1 parent 632a209 commit 953dc00

File tree

2 files changed

+94
-21
lines changed

2 files changed

+94
-21
lines changed

pep257.py

Lines changed: 82 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@
1515

1616
import os
1717
import sys
18+
import logging
1819
import tokenize as tk
1920
from itertools import takewhile, dropwhile, chain
2021
from optparse import OptionParser
2122
from re import compile as re
2223

24+
log = logging.getLogger()
25+
log.addHandler(logging.StreamHandler())
26+
2327

2428
try:
2529
from StringIO import StringIO
@@ -60,8 +64,9 @@ class Value(object):
6064
__eq__ = lambda self, other: other and vars(self) == vars(other)
6165

6266
def __repr__(self):
63-
args = [vars(self)[field] for field in self._fields]
64-
return '%s(%s)' % (self.__class__.__name__, ', '.join(map(repr, args)))
67+
format_arg = lambda arg: '{}={!r}'.format(arg, getattr(self, arg))
68+
kwargs = ', '.join(format_arg(arg) for arg in self._fields)
69+
return '{}({})'.format(self.__class__.__name__, kwargs)
6570

6671

6772
class Definition(Value):
@@ -131,10 +136,19 @@ class NestedClass(Class):
131136
is_public = False
132137

133138

139+
class TokenKind(int):
140+
def __repr__(self):
141+
return "tk.{}".format(tk.tok_name[self])
142+
143+
134144
class Token(Value):
135145

136146
_fields = 'kind value start end source'.split()
137147

148+
def __init__(self, *args):
149+
super(Token, self).__init__(*args)
150+
self.kind = TokenKind(self.kind)
151+
138152

139153
class TokenStream(object):
140154

@@ -187,34 +201,53 @@ def consume(self, kind):
187201
assert self.stream.move().kind == kind
188202

189203
def leapfrog(self, kind, value=None):
190-
for token in self.stream:
191-
if token.kind == kind and (value is None or token.value == value):
204+
"""Skip tokens in the stream until a certain token kind is reached.
205+
206+
If `value` is specified, tokens whose values are different will also
207+
be skipped.
208+
"""
209+
while self.current is not None:
210+
if (self.current.kind == kind and
211+
(value is None or self.current.value == value)):
192212
self.consume(kind)
193213
return
214+
self.stream.move()
194215

195216
def parse_docstring(self):
196-
for token in self.stream:
197-
if token.kind in [tk.COMMENT, tk.NEWLINE, tk.NL]:
198-
continue
199-
elif token.kind == tk.STRING:
200-
return token.value
201-
else:
202-
return None
217+
"""Parse a single docstring and return its value."""
218+
log.debug("parsing docstring, token is %r (%s)",
219+
self.current.kind, self.current.value)
220+
while self.current.kind in (tk.COMMENT, tk.NEWLINE, tk.NL):
221+
self.stream.move()
222+
log.debug("parsing docstring, token is %r (%s)",
223+
self.current.kind, self.current.value)
224+
if self.current.kind == tk.STRING:
225+
docstring = self.current.value
226+
self.stream.move()
227+
return docstring
228+
return None
203229

204230
def parse_definitions(self, class_, all=False):
205-
for token in self.stream:
206-
if all and token.value == '__all__':
231+
"""Parse multiple defintions and yield them."""
232+
while self.current is not None:
233+
log.debug("parsing defintion list, current token is %r (%s)",
234+
self.current.kind, self.current.value)
235+
if all and self.current.value == '__all__':
207236
self.parse_all()
208-
if token.value in ['def', 'class']:
209-
yield self.parse_definition(class_._nest(token.value))
210-
if token.kind == tk.INDENT:
237+
elif self.current.value in ['def', 'class']:
238+
yield self.parse_definition(class_._nest(self.current.value))
239+
elif self.current.kind == tk.INDENT:
211240
self.consume(tk.INDENT)
212241
for definition in self.parse_definitions(class_):
213242
yield definition
214-
if token.kind == tk.DEDENT:
243+
elif self.current.kind == tk.DEDENT:
244+
self.consume(tk.DEDENT)
215245
return
246+
else:
247+
self.stream.move()
216248

217249
def parse_all(self):
250+
"""Parse the __all__ definition in a module."""
218251
assert self.current.value == '__all__'
219252
self.consume(tk.NAME)
220253
if self.current.value != '=':
@@ -249,28 +282,49 @@ def parse_all(self):
249282
raise AllError('Could not evaluate contents of __all__: %s. ' % s)
250283

251284
def parse_module(self):
285+
"""Parse a module (and its children) and return a Module object."""
286+
log.debug("parsing module.")
252287
start = self.line
253288
docstring = self.parse_docstring()
254289
children = list(self.parse_definitions(Module, all=True))
255-
assert self.current is None
290+
assert self.current is None, self.current
256291
end = self.line
257292
module = Module(self.filename, self.source, start, end,
258293
docstring, children, None, self.all)
259294
for child in module.children:
260295
child.parent = module
296+
log.debug("finished parsing module.")
261297
return module
262298

263299
def parse_definition(self, class_):
300+
"""Parse a defintion and return its value in a `class_` object."""
264301
start = self.line
265302
self.consume(tk.NAME)
266303
name = self.current.value
267-
self.leapfrog(tk.OP, value=":")
304+
log.debug("parsing %s '%s'", class_.__name__, name)
305+
self.stream.move()
306+
if self.current.kind == tk.OP and self.current.value == '(':
307+
parenthesis_level = 0
308+
while True:
309+
if self.current.kind == tk.OP:
310+
if self.current.value == '(':
311+
parenthesis_level += 1
312+
elif self.current.value == ')':
313+
parenthesis_level -= 1
314+
if parenthesis_level == 0:
315+
break
316+
self.stream.move()
317+
if self.current.kind != tk.OP or self.current.value != ':':
318+
self.leapfrog(tk.OP, value=":")
319+
else:
320+
self.consume(tk.OP)
268321
if self.current.kind in (tk.NEWLINE, tk.COMMENT):
269322
self.leapfrog(tk.INDENT)
270323
assert self.current.kind != tk.INDENT
271324
docstring = self.parse_docstring()
325+
log.debug("parsing nested defintions.")
272326
children = list(self.parse_definitions(class_))
273-
assert self.current.kind == tk.DEDENT
327+
log.debug("finished parsing nested defintions for '%s'", name)
274328
end = self.line - 1
275329
else: # one-liner definition
276330
docstring = self.parse_docstring()
@@ -281,6 +335,9 @@ def parse_definition(self, class_):
281335
docstring, children, None)
282336
for child in definition.children:
283337
child.parent = definition
338+
log.debug("finished parsing %s '%s'. Next token is %r (%s)",
339+
class_.__name__, name, self.current.kind,
340+
self.current.value)
284341
return definition
285342

286343

@@ -359,6 +416,8 @@ def parse_options():
359416
help="search only dirs that exactly match <pattern> regular "
360417
"expression; default is --match-dir='[^\.].*', which matches "
361418
"all dirs that don't start with a dot")
419+
option('-d', '--debug', action='store_true',
420+
help='print debug information')
362421
return parser.parse_args()
363422

364423

@@ -411,6 +470,9 @@ def check(filenames, ignore=()):
411470

412471

413472
def main(options, arguments):
473+
if options.debug:
474+
log.setLevel(logging.DEBUG)
475+
log.debug("starting pep257 in debug mode.")
414476
Error.explain = options.explain
415477
Error.source = options.source
416478
collected = collect(arguments or ['.'],

test.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,11 +225,22 @@ def oneliner_d102(): return
225225
def oneliner_withdoc(): """One liner"""
226226

227227

228-
@expect("D208: Docstring is over-indented")
228+
@expect("D207: Docstring is under-indented")
229229
def docstring_start_in_same_line(): """First Line.
230230
231231
Second Line
232232
"""
233233

234234

235+
def function_with_lambda_arg(x=lambda y: y):
236+
"""A valid docstring."""
237+
238+
239+
def a_following_valid_function(x):
240+
"""Check for a bug where the previous function caused an assertion.
241+
242+
The assertion was caused in the next function, so this one is necessary.
243+
244+
"""
245+
235246
expect('test.py', 'D100: Docstring missing')

0 commit comments

Comments
 (0)