@@ -94,7 +94,8 @@ def __repr__(self):
94
94
95
95
class Definition (Value ):
96
96
97
- _fields = 'name _source start end docstring children parent' .split ()
97
+ _fields = ['name' , '_source' , 'start' , 'end' , 'decorators' , 'docstring' ,
98
+ 'children' , 'parent' ]
98
99
99
100
_human = property (lambda self : humanize (type (self ).__name__ ))
100
101
kind = property (lambda self : self ._human .split ()[- 1 ])
@@ -116,7 +117,8 @@ def __str__(self):
116
117
117
118
class Module (Definition ):
118
119
119
- _fields = 'name _source start end docstring children parent _all' .split ()
120
+ _fields = ['name' , '_source' , 'start' , 'end' , 'decorators' , 'docstring' ,
121
+ 'children' , 'parent' , '_all' ]
120
122
is_public = True
121
123
_nest = staticmethod (lambda s : {'def' : Function , 'class' : Class }[s ])
122
124
module = property (lambda self : self )
@@ -148,6 +150,10 @@ class Method(Function):
148
150
149
151
@property
150
152
def is_public (self ):
153
+ # Check if we are a setter method, and mark as private if so.
154
+ for decorator in self .decorators :
155
+ if decorator .name .startswith (self .name ):
156
+ return False
151
157
name_is_public = not self .name .startswith ('_' ) or is_magic (self .name )
152
158
return self .parent .is_public and name_is_public
153
159
@@ -163,6 +169,13 @@ class NestedClass(Class):
163
169
is_public = False
164
170
165
171
172
+ class Decorator (Value ):
173
+
174
+ """A decorator for function, method or class."""
175
+
176
+ _fields = 'name arguments' .split ()
177
+
178
+
166
179
class TokenKind (int ):
167
180
def __repr__ (self ):
168
181
return "tk.{}" .format (tk .tok_name [self ])
@@ -219,6 +232,7 @@ def __call__(self, filelike, filename):
219
232
self .stream = TokenStream (StringIO (src ))
220
233
self .filename = filename
221
234
self .all = None
235
+ self ._decorators = []
222
236
return self .parse_module ()
223
237
224
238
current = property (lambda self : self .stream .current )
@@ -254,13 +268,57 @@ def parse_docstring(self):
254
268
return docstring
255
269
return None
256
270
271
+ def parse_decorators (self ):
272
+ """Called after first @ is found.
273
+
274
+ Build list of current decorators and return last parsed token,
275
+ which is def or class start token.
276
+ """
277
+ name = []
278
+ arguments = []
279
+ at_arguments = False
280
+
281
+ while self .current is not None :
282
+ if (self .current .kind == tk .NAME and
283
+ self .current .value in ['def' , 'class' ]):
284
+ break
285
+ elif self .current .kind == tk .OP and self .current .value == '@' :
286
+ # New decorator found.
287
+ self ._decorators .append (
288
+ Decorator ('' .join (name ), '' .join (arguments )))
289
+ name = []
290
+ arguments = []
291
+ at_arguments = False
292
+ elif self .current .kind == tk .OP and self .current .value == '(' :
293
+ at_arguments = True
294
+ elif self .current .kind == tk .OP and self .current .value == ')' :
295
+ # Ignore close parenthesis
296
+ pass
297
+ elif self .current .kind == tk .NEWLINE or self .current .kind == tk .NL :
298
+ # Ignoe newlines
299
+ pass
300
+ else :
301
+ # Keep accumulating decorator's name or argument.
302
+ if not at_arguments :
303
+ name .append (self .current .value )
304
+ else :
305
+ arguments .append (self .current .value )
306
+ self .stream .move ()
307
+
308
+ # Add decorator accumulated so far
309
+ self ._decorators .append (
310
+ Decorator ('' .join (name ), '' .join (arguments )))
311
+
257
312
def parse_definitions (self , class_ , all = False ):
258
313
"""Parse multiple defintions and yield them."""
259
314
while self .current is not None :
260
315
log .debug ("parsing defintion list, current token is %r (%s)" ,
261
316
self .current .kind , self .current .value )
262
317
if all and self .current .value == '__all__' :
263
318
self .parse_all ()
319
+ elif self .current .kind == tk .OP and self .current .value == '@' :
320
+ self .consume (tk .OP )
321
+ self .parse_decorators ()
264
322
elif self .current .value in ['def' , 'class' ]:
265
323
yield self .parse_definition (class_ ._nest (self .current .value ))
266
324
elif self .current .kind == tk .INDENT :
@@ -324,7 +382,7 @@ def parse_module(self):
324
382
assert self .current is None , self .current
325
383
end = self .line
326
384
module = Module (self .filename , self .source , start , end ,
327
- docstring , children , None , self .all )
385
+ [], docstring , children , None , self .all )
328
386
for child in module .children :
329
387
child .parent = module
330
388
log .debug ("finished parsing module." )
@@ -356,17 +414,20 @@ def parse_definition(self, class_):
356
414
self .leapfrog (tk .INDENT )
357
415
assert self .current .kind != tk .INDENT
358
416
docstring = self .parse_docstring ()
417
+ decorators = self ._decorators
418
+ self ._decorators = []
359
419
log .debug ("parsing nested defintions." )
360
420
children = list (self .parse_definitions (class_ ))
361
421
log .debug ("finished parsing nested defintions for '%s'" , name )
362
422
end = self .line - 1
363
423
else : # one-liner definition
364
424
docstring = self .parse_docstring ()
425
+ decorators = [] # TODO
365
426
children = []
366
427
end = self .line
367
428
self .leapfrog (tk .NEWLINE )
368
429
definition = class_ (name , self .source , start , end ,
369
- docstring , children , None )
430
+ decorators , docstring , children , None )
370
431
for child in definition .children :
371
432
child .parent = definition
372
433
log .debug ("finished parsing %s '%s'. Next token is %r (%s)" ,
@@ -784,8 +845,8 @@ def check_indent(self, definition, docstring):
784
845
if set (' \t ' ) == set ('' .join (indents ) + indent ):
785
846
return Error ('D206: Docstring indented with both tabs and '
786
847
'spaces' )
787
- if (len (indents ) > 1 and min (indents [:- 1 ]) > indent
788
- or indents [- 1 ] > indent ):
848
+ if (len (indents ) > 1 and min (indents [:- 1 ]) > indent or
849
+ indents [- 1 ] > indent ):
789
850
return Error ('D208: Docstring is over-indented' )
790
851
if min (indents ) < indent :
791
852
return Error ('D207: Docstring is under-indented' )
0 commit comments