@@ -52,7 +52,15 @@ def next(obj, default=nothing):
52
52
return default
53
53
54
54
55
- __version__ = '0.5.0-alpha'
55
+ # If possible (python >= 3.2) use tokenize.open to open files, so PEP 263
56
+ # encoding markers are interpreted.
57
+ try :
58
+ tokenize_open = tk .open
59
+ except AttributeError :
60
+ tokenize_open = open
61
+
62
+
63
+ __version__ = '0.5.0'
56
64
__all__ = ('check' , 'collect' )
57
65
58
66
PROJECT_CONFIG = ('setup.cfg' , 'tox.ini' , '.pep257' )
@@ -100,7 +108,8 @@ def __repr__(self):
100
108
101
109
class Definition (Value ):
102
110
103
- _fields = 'name _source start end docstring children parent' .split ()
111
+ _fields = ('name' , '_source' , 'start' , 'end' , 'decorators' , 'docstring' ,
112
+ 'children' , 'parent' )
104
113
105
114
_human = property (lambda self : humanize (type (self ).__name__ ))
106
115
kind = property (lambda self : self ._human .split ()[- 1 ])
@@ -122,7 +131,8 @@ def __str__(self):
122
131
123
132
class Module (Definition ):
124
133
125
- _fields = 'name _source start end docstring children parent _all' .split ()
134
+ _fields = ('name' , '_source' , 'start' , 'end' , 'decorators' , 'docstring' ,
135
+ 'children' , 'parent' , '_all' )
126
136
is_public = True
127
137
_nest = staticmethod (lambda s : {'def' : Function , 'class' : Class }[s ])
128
138
module = property (lambda self : self )
@@ -154,6 +164,11 @@ class Method(Function):
154
164
155
165
@property
156
166
def is_public (self ):
167
+ # Check if we are a setter/deleter method, and mark as private if so.
168
+ for decorator in self .decorators :
169
+ # Given 'foo', match 'foo.bar' but not 'foobar' or 'sfoo'
170
+ if re (r"^{0}\." .format (self .name )).match (decorator .name ):
171
+ return False
157
172
name_is_public = not self .name .startswith ('_' ) or is_magic (self .name )
158
173
return self .parent .is_public and name_is_public
159
174
@@ -169,6 +184,13 @@ class NestedClass(Class):
169
184
is_public = False
170
185
171
186
187
+ class Decorator (Value ):
188
+
189
+ """A decorator for function, method or class."""
190
+
191
+ _fields = 'name arguments' .split ()
192
+
193
+
172
194
class TokenKind (int ):
173
195
def __repr__ (self ):
174
196
return "tk.{}" .format (tk .tok_name [self ])
@@ -225,6 +247,7 @@ def __call__(self, filelike, filename):
225
247
self .stream = TokenStream (StringIO (src ))
226
248
self .filename = filename
227
249
self .all = None
250
+ self ._accumulated_decorators = []
228
251
return self .parse_module ()
229
252
230
253
current = property (lambda self : self .stream .current )
@@ -260,13 +283,59 @@ def parse_docstring(self):
260
283
return docstring
261
284
return None
262
285
286
+ def parse_decorators (self ):
287
+ """Called after first @ is found.
288
+
289
+ Parse decorators into self._accumulated_decorators.
290
+ Continue to do so until encountering the 'def' or 'class' start token.
291
+ """
292
+ name = []
293
+ arguments = []
294
+ at_arguments = False
295
+
296
+ while self .current is not None :
297
+ if (self .current .kind == tk .NAME and
298
+ self .current .value in ['def' , 'class' ]):
299
+ # Done with decorators - found function or class proper
300
+ break
301
+ elif self .current .kind == tk .OP and self .current .value == '@' :
302
+ # New decorator found. Store the decorator accumulated so far:
303
+ self ._accumulated_decorators .append (
304
+ Decorator ('' .join (name ), '' .join (arguments )))
305
+ # Now reset to begin accumulating the new decorator:
306
+ name = []
307
+ arguments = []
308
+ at_arguments = False
309
+ elif self .current .kind == tk .OP and self .current .value == '(' :
310
+ at_arguments = True
311
+ elif self .current .kind == tk .OP and self .current .value == ')' :
312
+ # Ignore close parenthesis
313
+ pass
314
+ elif self .current .kind == tk .NEWLINE or self .current .kind == tk .NL :
315
+ # Ignore newlines
316
+ pass
317
+ else :
318
+ # Keep accumulating current decorator's name or argument.
319
+ if not at_arguments :
320
+ name .append (self .current .value )
321
+ else :
322
+ arguments .append (self .current .value )
323
+ self .stream .move ()
324
+
325
+ # Add decorator accumulated so far
326
+ self ._accumulated_decorators .append (
327
+ Decorator ('' .join (name ), '' .join (arguments )))
328
+
263
329
def parse_definitions (self , class_ , all = False ):
264
330
"""Parse multiple defintions and yield them."""
265
331
while self .current is not None :
266
332
log .debug ("parsing defintion list, current token is %r (%s)" ,
267
333
self .current .kind , self .current .value )
268
334
if all and self .current .value == '__all__' :
269
335
self .parse_all ()
336
+ elif self .current .kind == tk .OP and self .current .value == '@' :
337
+ self .consume (tk .OP )
338
+ self .parse_decorators ()
270
339
elif self .current .value in ['def' , 'class' ]:
271
340
yield self .parse_definition (class_ ._nest (self .current .value ))
272
341
elif self .current .kind == tk .INDENT :
@@ -330,7 +399,7 @@ def parse_module(self):
330
399
assert self .current is None , self .current
331
400
end = self .line
332
401
module = Module (self .filename , self .source , start , end ,
333
- docstring , children , None , self .all )
402
+ [], docstring , children , None , self .all )
334
403
for child in module .children :
335
404
child .parent = module
336
405
log .debug ("finished parsing module." )
@@ -362,17 +431,20 @@ def parse_definition(self, class_):
362
431
self .leapfrog (tk .INDENT )
363
432
assert self .current .kind != tk .INDENT
364
433
docstring = self .parse_docstring ()
434
+ decorators = self ._accumulated_decorators
435
+ self ._accumulated_decorators = []
365
436
log .debug ("parsing nested defintions." )
366
437
children = list (self .parse_definitions (class_ ))
367
438
log .debug ("finished parsing nested defintions for '%s'" , name )
368
439
end = self .line - 1
369
440
else : # one-liner definition
370
441
docstring = self .parse_docstring ()
442
+ decorators = [] # TODO
371
443
children = []
372
444
end = self .line
373
445
self .leapfrog (tk .NEWLINE )
374
446
definition = class_ (name , self .source , start , end ,
375
- docstring , children , None )
447
+ decorators , docstring , children , None )
376
448
for child in definition .children :
377
449
child .parent = definition
378
450
log .debug ("finished parsing %s '%s'. Next token is %r (%s)" ,
@@ -641,7 +713,7 @@ def check(filenames, select=None, ignore=None):
641
713
for filename in filenames :
642
714
log .info ('Checking file %s.' , filename )
643
715
try :
644
- with open (filename ) as file :
716
+ with tokenize_open (filename ) as file :
645
717
source = file .read ()
646
718
for error in PEP257Checker ().check_source (source , filename ):
647
719
code = getattr (error , 'code' , None )
@@ -999,11 +1071,12 @@ def check_triple_double_quotes(self, definition, docstring):
999
1071
1000
1072
'''
1001
1073
if docstring and '"""' in eval (docstring ) and docstring .startswith (
1002
- ("'''" , "r'''" , "u'''" )):
1074
+ ("'''" , "r'''" , "u'''" , "ur'''" )):
1003
1075
# Allow ''' quotes if docstring contains """, because otherwise """
1004
1076
# quotes could not be expressed inside docstring. Not in PEP 257.
1005
1077
return
1006
- if docstring and not docstring .startswith (('"""' , 'r"""' , 'u"""' )):
1078
+ if docstring and not docstring .startswith (
1079
+ ('"""' , 'r"""' , 'u"""' , 'ur"""' )):
1007
1080
quotes = "'''" if "'''" in docstring [:4 ] else "'"
1008
1081
return D300 (quotes )
1009
1082
@@ -1017,7 +1090,8 @@ def check_backslashes(self, definition, docstring):
1017
1090
'''
1018
1091
# Just check that docstring is raw, check_triple_double_quotes
1019
1092
# ensures the correct quotes.
1020
- if docstring and '\\ ' in docstring and not docstring .startswith ('r' ):
1093
+ if docstring and '\\ ' in docstring and not docstring .startswith (
1094
+ ('r' , 'ur' )):
1021
1095
return D301 ()
1022
1096
1023
1097
@check_for (Definition )
@@ -1030,7 +1104,8 @@ def check_unicode_docstring(self, definition, docstring):
1030
1104
# Just check that docstring is unicode, check_triple_double_quotes
1031
1105
# ensures the correct quotes.
1032
1106
if docstring and sys .version_info [0 ] <= 2 :
1033
- if not is_ascii (docstring ) and not docstring .startswith ('u' ):
1107
+ if not is_ascii (docstring ) and not docstring .startswith (
1108
+ ('u' , 'ur' )):
1034
1109
return D302 ()
1035
1110
1036
1111
@check_for (Definition )
0 commit comments