7
7
import pyparsing as pr
8
8
from itertools import takewhile
9
9
from re import compile as re
10
+ from collections import namedtuple
10
11
11
12
from . import violations
12
13
from .config import IllegalConfiguration
@@ -411,44 +412,21 @@ def check_starts_with_this(self, function, docstring):
411
412
if first_word .lower () == 'this' :
412
413
return violations .D404 ()
413
414
414
- @check_for (Definition )
415
- def check_numpy_content (self , definition , docstring ):
416
- """Check the content of the docstring for numpy conventions."""
417
- pass
418
-
419
- def check_numpy_parameters (self , section , content , definition , docstring ):
420
- print "LALALAL"
421
- yield
422
-
423
- def _check_numpy_section (self , section , content , definition , docstring ):
424
- """Check the content of the docstring for numpy conventions."""
425
- method_name = "check_numpy_%s" % section
426
- if hasattr (self , method_name ):
427
- gen_func = getattr (self , method_name )
428
-
429
- for err in gen_func (section , content , definition , docstring ):
430
- yield err
431
- else :
432
- print "Now checking numpy section %s" % section
433
- for l in content :
434
- print "##" , l
435
-
436
415
@staticmethod
437
- def _find_line_indices (lines , predicate ):
438
- """Return a list of indices of lines that match `predicate`."""
439
- return [i for i , line in enumerate (lines ) if predicate (line )]
416
+ def _get_leading_words (line ):
417
+ """Return any leading set of words from `line`.
440
418
441
- @ staticmethod
442
- def _rstrip_non_alpha ( line ):
419
+ For example, if `line` is " Hello world!!!", returns "Hello world".
420
+ """
443
421
result = re ("[A-Za-z ]+" ).match (line .strip ())
444
422
if result is not None :
445
423
return result .group ()
446
424
447
425
@staticmethod
448
- def _is_real_section ( section , line , prev_line ):
449
- """Check if the suspected line is a real section name or not .
426
+ def _is_a_docstring_section ( context ):
427
+ """Check if the suspected line is really a section.
450
428
451
- This is done by checking the next conditions:
429
+ This is done by checking the following conditions:
452
430
* Does the current line has a suffix after the suspected section name?
453
431
* Is the previous line not empty?
454
432
* Does the previous line end with a punctuation mark?
@@ -460,85 +438,146 @@ def _is_real_section(section, line, prev_line):
460
438
returns. <----- Not a real section name.
461
439
'''
462
440
"""
441
+ section_name_suffix = context .line .lstrip (context .section_name ).strip ()
442
+
463
443
punctuation = [',' , ';' , '.' , '-' , '\\ ' , '/' , ']' , '}' , ')' ]
464
444
prev_line_ends_with_punctuation = \
465
- any (prev_line .endswith (x ) for x in punctuation )
466
- prev_line_is_empty = prev_line == ''
445
+ any (context .previous_line .strip ().endswith (x ) for x in punctuation )
467
446
468
- suffix = line .lstrip (section ).strip ()
469
-
470
- # If there's a suffix to our suspected section name, and the previous
471
- # line is not empty and ends with a punctuation mark, this is probably
472
- # a false-positive.
473
- return (suffix and not
474
- prev_line_ends_with_punctuation and not
475
- prev_line_is_empty )
447
+ return not (section_name_suffix != '' and not
448
+ prev_line_ends_with_punctuation and not
449
+ context .previous_line .strip () == '' )
476
450
477
451
@classmethod
478
- def _check_section (cls , line_index , dashes_indices , lines ):
479
- line = lines [line_index ].strip ()
480
- prev_line = lines [line_index - 1 ].strip ()
481
- section = cls ._rstrip_non_alpha (line )
482
- capitalized_section = section .title ()
452
+ def _check_section_underline (cls , section_name , context , indentation ):
453
+ """D4{07,08,09,10}, D215: Section underline checks.
454
+
455
+ Check for correct formatting for docstring sections. Checks that:
456
+ * The line that follows the section name contains dashes (D40{7,8}).
457
+ * The amount of dashes is equal to the length of the section
458
+ name (D409).
459
+ * The line that follows the section header (with or without dashes)
460
+ is empty (D410).
461
+ * The indentation of the dashed line is equal to the docstring's
462
+ indentation (D215).
463
+ """
464
+ dash_line_found = False
465
+ next_non_empty_line_offset = 0
466
+
467
+ for line in context .following_lines :
468
+ line_set = '' .join (set (line .strip ()))
469
+ if line_set != '' :
470
+ dash_line_found = line_set == '-'
471
+ break
472
+ next_non_empty_line_offset += 1
473
+
474
+ if not dash_line_found :
475
+ yield violations .D407 (section_name )
476
+ if next_non_empty_line_offset == 0 :
477
+ yield violations .D410 (section_name )
478
+ else :
479
+ if next_non_empty_line_offset > 0 :
480
+ yield violations .D408 (section_name )
483
481
484
- if cls ._is_real_section (section , line , prev_line ):
485
- return
482
+ dash_line = context .following_lines [next_non_empty_line_offset ]
483
+ if dash_line .strip () != "-" * len (section_name ):
484
+ yield violations .D409 (section_name ,
485
+ len (section_name ),
486
+ len (dash_line .strip ()))
486
487
487
- suffix = line .lstrip (section ).strip ()
488
- if suffix :
489
- yield violations .D406 (capitalized_section , suffix )
488
+ line_after_dashes = \
489
+ context .following_lines [next_non_empty_line_offset + 1 ]
490
+ if line_after_dashes .strip () != '' :
491
+ yield violations .D410 (section_name )
490
492
491
- if prev_line != '' :
492
- yield violations .D410 ( capitalized_section ) # Missing blank line
493
+ if leading_space ( dash_line ) > indentation :
494
+ yield violations .D215 ( section_name )
493
495
494
- if (section not in cls .SECTION_NAMES and
496
+ @classmethod
497
+ def _check_section (cls , docstring , definition , context ):
498
+ """D4{05,06,11}, D214: Section name checks.
499
+
500
+ Check for valid section names. Checks that:
501
+ * The section name is properly capitalized (D405).
502
+ * The section is not over-indented (D214).
503
+ * The section name has no superfluous suffix to it (D406).
504
+ * There's a blank line before the section (D411).
505
+
506
+ Also yields all the errors from `_check_section_underline`.
507
+ """
508
+ capitalized_section = context .section_name .title ()
509
+ indentation = cls ._get_docstring_indent (definition , docstring )
510
+
511
+ if (context .section_name not in cls .SECTION_NAMES and
495
512
capitalized_section in cls .SECTION_NAMES ):
496
- yield violations .D405 (capitalized_section , section )
513
+ yield violations .D405 (capitalized_section , context . section_name )
497
514
498
- next_line_index = line_index + 1
499
- if next_line_index not in dashes_indices :
500
- yield violations .D407 (capitalized_section )
501
- else :
502
- if lines [next_line_index ].strip () != "-" * len (section ):
503
- # The length of the underlining dashes does not
504
- # match the length of the section name.
505
- yield violations .D408 (section , len (section ))
515
+ if leading_space (context .line ) > indentation :
516
+ yield violations .D214 (capitalized_section )
517
+
518
+ suffix = context .line .strip ().lstrip (context .section_name )
519
+ if suffix != '' :
520
+ yield violations .D406 (capitalized_section , suffix )
506
521
507
- # If there are no dashes - the next line after the section name
508
- # should be empty. Otherwise, it's the next line after the dashes.
509
- # This is why we increment the line index by 1 here.
510
- next_line_index += 1
522
+ if context .previous_line .strip () != '' :
523
+ yield violations .D411 (capitalized_section )
511
524
512
- if lines [next_line_index ].strip ():
513
- yield violations .D409 (capitalized_section )
525
+ for err in cls ._check_section_underline (capitalized_section ,
526
+ context ,
527
+ indentation ):
528
+ yield err
514
529
515
530
@check_for (Definition )
516
- def check_docstring_internal_structure (self , definition , docstring ):
517
- """Parse the general structure of a numpy docstring and check it."""
531
+ def check_docstring_sections (self , definition , docstring ):
532
+ """D21{4,5}, D4{05,06,07,08,09,10}: Docstring sections checks.
533
+
534
+ Check the general format of a sectioned docstring:
535
+ '''This is my one-liner.
536
+
537
+ Short Summary
538
+ -------------
539
+
540
+ This is my summary.
541
+
542
+ Returns
543
+ -------
544
+
545
+ None.
546
+ '''
547
+
548
+ Section names appear in `SECTION_NAMES`.
549
+ """
518
550
if not docstring :
519
551
return
520
552
521
553
lines = docstring .split ("\n " )
522
554
if len (lines ) < 2 :
523
- # It's not a multiple lined docstring
524
555
return
525
556
526
557
lower_section_names = [s .lower () for s in self .SECTION_NAMES ]
527
558
528
- def _suspected_as_section (line ):
529
- result = self ._rstrip_non_alpha ( line .lower ())
559
+ def _suspected_as_section (_line ):
560
+ result = self ._get_leading_words ( _line .lower ())
530
561
return result in lower_section_names
531
562
532
- def _contains_only_dashes (line ):
533
- return '' .join (set (line .strip ())) == '-'
534
-
535
563
# Finding our suspects.
536
- section_indices = self ._find_line_indices (lines , _suspected_as_section )
537
- dashes_indices = self ._find_line_indices (lines , _contains_only_dashes )
538
-
539
- for i in section_indices :
540
- for err in self ._check_section (i , dashes_indices , lines ):
541
- yield err
564
+ suspected_section_indices = [i for i , line in enumerate (lines ) if
565
+ _suspected_as_section (line )]
566
+
567
+ context = namedtuple ('SectionContext' , ('section_name' ,
568
+ 'previous_line' ,
569
+ 'line' ,
570
+ 'following_lines' ))
571
+
572
+ contexts = (context (self ._get_leading_words (lines [i ].strip ()),
573
+ lines [i - 1 ],
574
+ lines [i ],
575
+ lines [i + 1 :]) for i in suspected_section_indices )
576
+
577
+ for ctx in contexts :
578
+ if self ._is_a_docstring_section (ctx ):
579
+ for err in self ._check_section (docstring , definition , ctx ):
580
+ yield err
542
581
543
582
544
583
parse = Parser ()
0 commit comments