docstring-format-checker 0.5.0__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {docstring_format_checker-0.5.0 → docstring_format_checker-0.7.0}/PKG-INFO +1 -1
- {docstring_format_checker-0.5.0 → docstring_format_checker-0.7.0}/pyproject.toml +1 -1
- {docstring_format_checker-0.5.0 → docstring_format_checker-0.7.0}/src/docstring_format_checker/__init__.py +1 -1
- {docstring_format_checker-0.5.0 → docstring_format_checker-0.7.0}/src/docstring_format_checker/cli.py +59 -16
- {docstring_format_checker-0.5.0 → docstring_format_checker-0.7.0}/src/docstring_format_checker/core.py +60 -10
- {docstring_format_checker-0.5.0 → docstring_format_checker-0.7.0}/README.md +0 -0
- {docstring_format_checker-0.5.0 → docstring_format_checker-0.7.0}/src/docstring_format_checker/config.py +0 -0
- {docstring_format_checker-0.5.0 → docstring_format_checker-0.7.0}/src/docstring_format_checker/utils/__init__.py +0 -0
- {docstring_format_checker-0.5.0 → docstring_format_checker-0.7.0}/src/docstring_format_checker/utils/exceptions.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: docstring-format-checker
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: A CLI tool to check and validate Python docstring formatting and completeness
|
|
5
5
|
Author: Chris Mahoney
|
|
6
6
|
Author-email: Chris Mahoney <docstring-format-checker@data-science-extensions.com>
|
|
@@ -4,7 +4,7 @@ Docstring Format Checker.
|
|
|
4
4
|
A CLI tool to check and validate Python docstring formatting and completeness.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
__version__ = "v0.
|
|
7
|
+
__version__ = "v0.7.0"
|
|
8
8
|
__author__ = "Chris Mahoney"
|
|
9
9
|
__email__ = "docstring-format-checker@data-science-extensions.com"
|
|
10
10
|
|
|
@@ -376,13 +376,36 @@ def _display_results(results: dict[str, list[DocstringError]], quiet: bool, outp
|
|
|
376
376
|
console.print(_green("✓ All docstrings are valid!"))
|
|
377
377
|
return 0
|
|
378
378
|
|
|
379
|
-
# Count total errors
|
|
380
|
-
|
|
379
|
+
# Count total errors (individual error messages, not error objects)
|
|
380
|
+
total_individual_errors: int = 0
|
|
381
|
+
total_functions: int = 0
|
|
382
|
+
|
|
383
|
+
for errors in results.values():
|
|
384
|
+
total_functions += len(errors) # Count functions/items with errors
|
|
385
|
+
for error in errors:
|
|
386
|
+
# Count individual error messages within each error object
|
|
387
|
+
if "; " in error.message:
|
|
388
|
+
individual_errors = [msg.strip() for msg in error.message.split("; ") if msg.strip()]
|
|
389
|
+
total_individual_errors += len(individual_errors)
|
|
390
|
+
else:
|
|
391
|
+
total_individual_errors += 1
|
|
392
|
+
|
|
393
|
+
total_errors: int = total_individual_errors
|
|
381
394
|
total_files: int = len(results)
|
|
382
395
|
|
|
383
396
|
if quiet:
|
|
384
|
-
# In quiet mode, only show summary
|
|
385
|
-
|
|
397
|
+
# In quiet mode, only show summary with improved format
|
|
398
|
+
if total_functions == 1:
|
|
399
|
+
functions_text = f"1 function"
|
|
400
|
+
else:
|
|
401
|
+
functions_text = f"{total_functions} functions"
|
|
402
|
+
|
|
403
|
+
if total_files == 1:
|
|
404
|
+
files_text = f"1 file"
|
|
405
|
+
else:
|
|
406
|
+
files_text = f"{total_files} files"
|
|
407
|
+
|
|
408
|
+
console.print(_red(f"{NEW_LINE}Found {total_errors} error(s) in {functions_text} over {files_text}"))
|
|
386
409
|
return 1
|
|
387
410
|
|
|
388
411
|
if output == "table":
|
|
@@ -411,22 +434,37 @@ def _display_results(results: dict[str, list[DocstringError]], quiet: bool, outp
|
|
|
411
434
|
console.print(table)
|
|
412
435
|
|
|
413
436
|
else:
|
|
414
|
-
# Show compact output
|
|
437
|
+
# Show compact output with grouped errors under function/class headers
|
|
415
438
|
for file_path, errors in results.items():
|
|
416
439
|
console.print(f"{NEW_LINE}{_cyan(file_path)}")
|
|
417
440
|
for error in errors:
|
|
418
|
-
#
|
|
419
|
-
formatted_error_message: str = _format_error_messages(error.message)
|
|
420
|
-
|
|
441
|
+
# Print the header line with line number, item type and name
|
|
421
442
|
if error.line_number > 0:
|
|
422
|
-
console.print(
|
|
423
|
-
|
|
424
|
-
)
|
|
443
|
+
console.print(f" [red]Line {error.line_number}[/red] - {error.item_type} '{error.item_name}':")
|
|
444
|
+
else:
|
|
445
|
+
console.print(f" {_red('Error')} - {error.item_type} '{error.item_name}':")
|
|
446
|
+
|
|
447
|
+
# Split error message into individual errors and indent them
|
|
448
|
+
if "; " in error.message:
|
|
449
|
+
individual_errors = [msg.strip() for msg in error.message.split("; ") if msg.strip()]
|
|
450
|
+
for individual_error in individual_errors:
|
|
451
|
+
console.print(f" - {individual_error}")
|
|
425
452
|
else:
|
|
426
|
-
|
|
453
|
+
# Single error message
|
|
454
|
+
console.print(f" - {error.message.strip()}")
|
|
455
|
+
|
|
456
|
+
# Summary - more descriptive message
|
|
457
|
+
if total_functions == 1:
|
|
458
|
+
functions_text = f"1 function"
|
|
459
|
+
else:
|
|
460
|
+
functions_text = f"{total_functions} functions"
|
|
461
|
+
|
|
462
|
+
if total_files == 1:
|
|
463
|
+
files_text = f"1 file"
|
|
464
|
+
else:
|
|
465
|
+
files_text = f"{total_files} files"
|
|
427
466
|
|
|
428
|
-
|
|
429
|
-
console.print(_red(f"{NEW_LINE}Found {total_errors} error(s) in {total_files} file(s)"))
|
|
467
|
+
console.print(_red(f"{NEW_LINE}Found {total_errors} error(s) in {functions_text} over {files_text}"))
|
|
430
468
|
|
|
431
469
|
return 1
|
|
432
470
|
|
|
@@ -439,7 +477,7 @@ def _display_results(results: dict[str, list[DocstringError]], quiet: bool, outp
|
|
|
439
477
|
|
|
440
478
|
|
|
441
479
|
# This will be the default behavior when no command is specified
|
|
442
|
-
def
|
|
480
|
+
def check_docstrings(
|
|
443
481
|
path: str,
|
|
444
482
|
config: Optional[str] = None,
|
|
445
483
|
exclude: Optional[list[str]] = None,
|
|
@@ -456,14 +494,19 @@ def _check_docstrings(
|
|
|
456
494
|
The path to the file or directory to check.
|
|
457
495
|
config (Optional[str]):
|
|
458
496
|
The path to the configuration file.
|
|
497
|
+
Default: `None`.
|
|
459
498
|
exclude (Optional[list[str]]):
|
|
460
499
|
List of glob patterns to exclude from checking.
|
|
500
|
+
Default: `None`.
|
|
461
501
|
quiet (bool):
|
|
462
502
|
Whether to suppress output.
|
|
503
|
+
Default: `False`.
|
|
463
504
|
output (str):
|
|
464
505
|
Output format: 'table' or 'list'.
|
|
506
|
+
Default: `'list'`.
|
|
465
507
|
check (bool):
|
|
466
508
|
Whether to throw error if issues are found.
|
|
509
|
+
Default: `False`.
|
|
467
510
|
|
|
468
511
|
Returns:
|
|
469
512
|
(None):
|
|
@@ -626,7 +669,7 @@ def main(
|
|
|
626
669
|
console.print(_red(f"Error: Invalid output format '{output}'. Use 'table' or 'list'."))
|
|
627
670
|
raise Exit(1)
|
|
628
671
|
|
|
629
|
-
|
|
672
|
+
check_docstrings(
|
|
630
673
|
path=path,
|
|
631
674
|
config=config,
|
|
632
675
|
exclude=exclude,
|
|
@@ -924,6 +924,7 @@ class DocstringChecker:
|
|
|
924
924
|
# Check each line in the docstring
|
|
925
925
|
lines: list[str] = docstring.split("\n")
|
|
926
926
|
current_section = None
|
|
927
|
+
type_line_indent = None # Track indentation of type definition lines
|
|
927
928
|
|
|
928
929
|
for i, line in enumerate(lines):
|
|
929
930
|
stripped_line: str = line.strip()
|
|
@@ -938,6 +939,7 @@ class DocstringChecker:
|
|
|
938
939
|
current_section: Optional[SectionConfig] = next(
|
|
939
940
|
(s for s in parentheses_sections if s.name.lower() == section_name), None
|
|
940
941
|
)
|
|
942
|
+
type_line_indent = None # Reset for new section
|
|
941
943
|
continue
|
|
942
944
|
|
|
943
945
|
# Non-admonition sections - only match actual section headers, not indented content
|
|
@@ -955,6 +957,7 @@ class DocstringChecker:
|
|
|
955
957
|
current_section = next(
|
|
956
958
|
(s for s in parentheses_sections if s.name.lower() == section_name), None
|
|
957
959
|
)
|
|
960
|
+
type_line_indent = None # Reset for new section
|
|
958
961
|
continue
|
|
959
962
|
# If it doesn't match a known section, fall through to content processing
|
|
960
963
|
|
|
@@ -962,19 +965,66 @@ class DocstringChecker:
|
|
|
962
965
|
if current_section and stripped_line and not stripped_line.startswith(("!", "?", "#")):
|
|
963
966
|
# Look for parameter/type definitions
|
|
964
967
|
if ":" in stripped_line:
|
|
968
|
+
# Calculate current line indentation
|
|
969
|
+
current_indent = len(line) - len(line.lstrip())
|
|
970
|
+
|
|
971
|
+
# Skip description lines that start with common description words
|
|
972
|
+
description_prefixes = [
|
|
973
|
+
"default:",
|
|
974
|
+
"note:",
|
|
975
|
+
"example:",
|
|
976
|
+
"see:",
|
|
977
|
+
"warning:",
|
|
978
|
+
"info:",
|
|
979
|
+
"tip:",
|
|
980
|
+
"returns:",
|
|
981
|
+
]
|
|
982
|
+
is_description_line = any(
|
|
983
|
+
stripped_line.lower().startswith(prefix) for prefix in description_prefixes
|
|
984
|
+
)
|
|
985
|
+
|
|
986
|
+
# Skip lines that are clearly descriptions (containing "Default:", etc.)
|
|
987
|
+
if (
|
|
988
|
+
is_description_line
|
|
989
|
+
or "Default:" in stripped_line
|
|
990
|
+
or "Output format:" in stripped_line
|
|
991
|
+
or "Show examples:" in stripped_line
|
|
992
|
+
):
|
|
993
|
+
continue
|
|
994
|
+
|
|
995
|
+
# For list_type sections, we need special handling
|
|
996
|
+
if current_section.type == "list_type":
|
|
997
|
+
# Check if this line has parentheses at the beginning
|
|
998
|
+
if re.search(r"^\s*\([^)]+\):", stripped_line):
|
|
999
|
+
# This is a valid type definition line, remember its indentation
|
|
1000
|
+
type_line_indent = current_indent
|
|
1001
|
+
continue
|
|
1002
|
+
else:
|
|
1003
|
+
# If no type definition has been found yet, allow lines with colons as possible descriptions
|
|
1004
|
+
if type_line_indent is None:
|
|
1005
|
+
continue
|
|
1006
|
+
# Check if this is a description line (more indented than type line)
|
|
1007
|
+
if current_indent > type_line_indent:
|
|
1008
|
+
# This is a description line, skip validation
|
|
1009
|
+
continue
|
|
1010
|
+
else:
|
|
1011
|
+
# This should be a type definition but doesn't have proper format
|
|
1012
|
+
errors.append(
|
|
1013
|
+
f"Section '{current_section.name}' (type: '{current_section.type}') requires "
|
|
1014
|
+
f"parenthesized types, see: '{stripped_line}'"
|
|
1015
|
+
)
|
|
965
1016
|
# For list_name_and_type sections, check format like "name (type):" or "(type):"
|
|
966
|
-
|
|
1017
|
+
elif current_section.type == "list_name_and_type":
|
|
967
1018
|
# Pattern: name (type): or (type):
|
|
968
|
-
if
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
)
|
|
1019
|
+
# But skip if it doesn't look like a parameter definition (e.g., has multiple words before the colon)
|
|
1020
|
+
colon_part = stripped_line.split(":")[0].strip()
|
|
1021
|
+
# Skip if it contains phrases that indicate it's a description, not a parameter
|
|
1022
|
+
if any(
|
|
1023
|
+
word in colon_part.lower() for word in ["default", "output", "format", "show", "example"]
|
|
1024
|
+
):
|
|
1025
|
+
continue
|
|
973
1026
|
|
|
974
|
-
|
|
975
|
-
elif current_section.type == "list_type":
|
|
976
|
-
# Pattern: (Type):
|
|
977
|
-
if not re.search(r"^\s*\([^)]+\):", stripped_line):
|
|
1027
|
+
if not re.search(r"\([^)]+\):", stripped_line):
|
|
978
1028
|
errors.append(
|
|
979
1029
|
f"Section '{current_section.name}' (type: '{current_section.type}') requires "
|
|
980
1030
|
f"parenthesized types, see: '{stripped_line}'"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|