@@ -43,6 +43,20 @@ class PEP257Checker(object):
43
43
44
44
"""
45
45
46
+ ALL_NUMPY_SECTIONS = ['Short Summary' ,
47
+ 'Extended Summary' ,
48
+ 'Parameters' ,
49
+ 'Returns' ,
50
+ 'Yields' ,
51
+ 'Other Parameters' ,
52
+ 'Raises' ,
53
+ 'See Also' ,
54
+ 'Notes' ,
55
+ 'References' ,
56
+ 'Examples' ,
57
+ 'Attributes' ,
58
+ 'Methods' ]
59
+
46
60
def check_source (self , source , filename , ignore_decorators ):
47
61
module = parse (StringIO (source ), filename )
48
62
for definition in module :
@@ -54,7 +68,7 @@ def check_source(self, source, filename, ignore_decorators):
54
68
len (ignore_decorators .findall (dec .name )) > 0
55
69
for dec in definition .decorators )
56
70
if not skipping_all and not decorator_skip :
57
- error = this_check (None , definition ,
71
+ error = this_check (self , definition ,
58
72
definition .docstring )
59
73
else :
60
74
error = None
@@ -190,6 +204,13 @@ def check_blank_after_summary(self, definition, docstring):
190
204
if blanks_count != 1 :
191
205
return violations .D205 (blanks_count )
192
206
207
+ @staticmethod
208
+ def _get_docstring_indent (definition , docstring ):
209
+ """Return the indentation of the docstring's opening quotes."""
210
+ before_docstring , _ , _ = definition .source .partition (docstring )
211
+ _ , _ , indent = before_docstring .rpartition ('\n ' )
212
+ return indent
213
+
193
214
@check_for (Definition )
194
215
def check_indent (self , definition , docstring ):
195
216
"""D20{6,7,8}: The entire docstring should be indented same as code.
@@ -199,8 +220,7 @@ def check_indent(self, definition, docstring):
199
220
200
221
"""
201
222
if docstring :
202
- before_docstring , _ , _ = definition .source .partition (docstring )
203
- _ , _ , indent = before_docstring .rpartition ('\n ' )
223
+ indent = self ._get_docstring_indent (definition , docstring )
204
224
lines = docstring .split ('\n ' )
205
225
if len (lines ) > 1 :
206
226
lines = lines [1 :] # First line does not need indent.
@@ -390,6 +410,144 @@ def check_starts_with_this(self, function, docstring):
390
410
if first_word .lower () == 'this' :
391
411
return violations .D404 ()
392
412
413
+ @check_for (Definition )
414
+ def check_numpy_content (self , definition , docstring ):
415
+ """Check the content of the docstring for numpy conventions."""
416
+ pass
417
+
418
+ def check_numpy_parameters (self , section , content , definition , docstring ):
419
+ print "LALALAL"
420
+ yield
421
+
422
+ def _check_numpy_section (self , section , content , definition , docstring ):
423
+ """Check the content of the docstring for numpy conventions."""
424
+ method_name = "check_numpy_%s" % section
425
+ if hasattr (self , method_name ):
426
+ gen_func = getattr (self , method_name )
427
+
428
+ for err in gen_func (section , content , definition , docstring ):
429
+ yield err
430
+ else :
431
+ print "Now checking numpy section %s" % section
432
+ for l in content :
433
+ print "##" , l
434
+
435
+ @check_for (Definition )
436
+ def check_numpy (self , definition , docstring ):
437
+ """Parse the general structure of a numpy docstring and check it."""
438
+ if not docstring :
439
+ return
440
+
441
+ lines = docstring .split ("\n " )
442
+ if len (lines ) < 2 :
443
+ # It's not a multiple lined docstring
444
+ return
445
+
446
+ lines_generator = ScrollableGenerator (lines [1 :]) # Skipping first line
447
+ indent = self ._get_docstring_indent (definition , docstring )
448
+
449
+ current_section = None
450
+ curr_section_lines = []
451
+ start_collecting_lines = False
452
+
453
+ for line in lines_generator :
454
+ for section in self .ALL_NUMPY_SECTIONS :
455
+ with_colon = section .lower () + ':'
456
+ if line .strip ().lower () in [section .lower (), with_colon ]:
457
+ # There's a chance that this line is a numpy section
458
+ try :
459
+ next_line = lines_generator .next ()
460
+ except StopIteration :
461
+ # It probably isn't :)
462
+ return
463
+
464
+ if '' .join (set (next_line .strip ())) == '-' :
465
+ # The next line contains only dashes, there's a good
466
+ # chance that it's a numpy section
467
+
468
+ if (leading_space (line ) > indent or
469
+ leading_space (next_line ) > indent ):
470
+ yield violations .D214 (section )
471
+
472
+ if section not in line :
473
+ # The capitalized section string is not in the line,
474
+ # meaning that the word appears there but not
475
+ # properly capitalized.
476
+ yield violations .D405 (section , line .strip ())
477
+ elif line .strip ().lower () == with_colon :
478
+ # The section name should not end with a colon.
479
+ yield violations .D406 (section , line .strip ())
480
+
481
+ if next_line .strip () != "-" * len (section ):
482
+ # The length of the underlining dashes does not
483
+ # match the length of the section name.
484
+ yield violations .D407 (section , len (section ))
485
+
486
+ # At this point, we're done with the structured part of
487
+ # the section and its underline.
488
+ # We will not collect the content of each section and
489
+ # let section handlers deal with it.
490
+
491
+ if current_section is not None :
492
+ for err in self ._check_numpy_section (
493
+ current_section ,
494
+ curr_section_lines ,
495
+ definition ,
496
+ docstring ):
497
+ yield err
498
+
499
+ start_collecting_lines = True
500
+ current_section = section .lower ()
501
+ curr_section_lines = []
502
+ else :
503
+ # The next line does not contain only dashes, so it's
504
+ # not likely to be a section header.
505
+ lines_generator .scroll_back ()
506
+
507
+ if current_section is not None :
508
+ if start_collecting_lines :
509
+ start_collecting_lines = False
510
+ else :
511
+ curr_section_lines .append (line )
512
+
513
+ if current_section is not None :
514
+ for err in self ._check_numpy_section (current_section ,
515
+ curr_section_lines ,
516
+ definition ,
517
+ docstring ):
518
+ yield err
519
+
520
+
521
+ class ScrollableGenerator (object ):
522
+ """A generator over a list that can be moved back during iteration."""
523
+
524
+ def __init__ (self , list_like ):
525
+ self ._list_like = list_like
526
+ self ._index = 0
527
+
528
+ def __iter__ (self ):
529
+ return self
530
+
531
+ def next (self ):
532
+ """Generate the next item or raise StopIteration."""
533
+ try :
534
+ return self ._list_like [self ._index ]
535
+ except IndexError :
536
+ raise StopIteration ()
537
+ finally :
538
+ self ._index += 1
539
+
540
+ def scroll_back (self , num = 1 ):
541
+ """Move the generator `num` items backwards."""
542
+ if num < 0 :
543
+ raise ValueError ('num cannot be a negative number' )
544
+ self ._index = max (0 , self ._index - num )
545
+
546
+ def clone (self ):
547
+ """Return a copy of the generator set to the same item index."""
548
+ obj_copy = self .__class__ (self ._list_like )
549
+ obj_copy ._index = self ._index
550
+
393
551
394
552
parse = Parser ()
395
553
0 commit comments