Coverage for src / docstring_format_checker / cli.py: 100%
201 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-25 08:09 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-25 08:09 +0000
1# ============================================================================ #
2# #
3# Title: Title #
4# Purpose: Purpose #
5# Notes: Notes #
6# Author: chrimaho #
7# Created: Created #
8# References: References #
9# Sources: Sources #
10# Edited: Edited #
11# #
12# ============================================================================ #
15# ---------------------------------------------------------------------------- #
16# #
17# Overview ####
18# #
19# ---------------------------------------------------------------------------- #
22# ---------------------------------------------------------------------------- #
23# Description ####
24# ---------------------------------------------------------------------------- #
27"""
28!!! note "Summary"
29 Command-line interface for the docstring format checker.
30"""
33# ---------------------------------------------------------------------------- #
34# #
35# Setup ####
36# #
37# ---------------------------------------------------------------------------- #
40## --------------------------------------------------------------------------- #
41## Imports ####
42## --------------------------------------------------------------------------- #
45# ## Python StdLib Imports ----
46import os
47from functools import partial
48from pathlib import Path
49from textwrap import dedent
50from typing import Optional
52# ## Python Third Party Imports ----
53import pyfiglet
54from rich.console import Console
55from rich.markup import escape
56from rich.panel import Panel
57from rich.table import Table
58from typer import Argument, CallbackParam, Context, Exit, Option, Typer, echo
60# ## Local First Party Imports ----
61from docstring_format_checker import __version__
62from docstring_format_checker.config import (
63 Config,
64 find_config_file,
65 load_config,
66)
67from docstring_format_checker.core import DocstringChecker, DocstringError
70## --------------------------------------------------------------------------- #
71## Exports ####
72## --------------------------------------------------------------------------- #
75__all__: list[str] = [
76 "main",
77 "entry_point",
78 "check_docstrings",
79]
82## --------------------------------------------------------------------------- #
83## Constants ####
84## --------------------------------------------------------------------------- #
87NEW_LINE = "\n"
90## --------------------------------------------------------------------------- #
91## Helpers ####
92## --------------------------------------------------------------------------- #
95### Colours ----
96def _colour(text: str, colour: str) -> str:
97 """
98 !!! note "Summary"
99 Apply Rich colour markup to text.
101 Params:
102 text (str):
103 The text to colour.
104 colour (str):
105 The colour to apply, e.g., 'red', 'green', 'blue'.
107 Returns:
108 (str):
109 The text wrapped in Rich colour markup.
110 """
111 return f"[{colour}]{text}[/{colour}]"
114_green = partial(_colour, colour="green")
115_red = partial(_colour, colour="red")
116_cyan = partial(_colour, colour="cyan")
117_blue = partial(_colour, colour="blue")
120# ---------------------------------------------------------------------------- #
121# #
122# Main Application ####
123# #
124# ---------------------------------------------------------------------------- #
127app = Typer(
128 name="docstring-format-checker",
129 help="A CLI tool to check and validate Python docstring formatting and completeness.",
130 add_completion=False,
131 rich_markup_mode="rich",
132 add_help_option=False, # Disable automatic help so we can add our own with -h
133)
134console = Console()
137# ---------------------------------------------------------------------------- #
138# #
139# Callbacks ####
140# #
141# ---------------------------------------------------------------------------- #
144def _version_callback(ctx: Context, param: CallbackParam, value: bool) -> None:
145 """
146 !!! note "Summary"
147 Print version and exit.
149 Params:
150 ctx (Context):
151 The context object.
152 param (CallbackParam):
153 The parameter object.
154 value (bool):
155 The boolean value indicating if the flag was set.
157 Returns:
158 (None):
159 Nothing is returned.
160 """
161 if value:
162 echo(f"docstring-format-checker version {__version__}")
163 raise Exit()
166def _example_callback(ctx: Context, param: CallbackParam, value: Optional[str]) -> None:
167 """
168 !!! note "Summary"
169 Handle example flag and show appropriate example content.
171 Params:
172 ctx (Context):
173 The context object.
174 param (CallbackParam):
175 The parameter object.
176 value (Optional[str]):
177 The example type to show: 'config' or 'usage'.
179 Returns:
180 (None):
181 Nothing is returned.
182 """
184 if not value or ctx.resilient_parsing:
185 return
187 if value == "config":
188 _show_config_example_callback()
189 elif value == "usage":
190 _show_usage_examples_callback()
191 else:
192 console.print(_red(f"Error: Invalid example type '{value}'. Use 'config' or 'usage'."))
193 raise Exit(1)
194 raise Exit()
197def _show_usage_examples_callback() -> None:
198 """
199 !!! note "Summary"
200 Show examples and exit.
202 Returns:
203 (None):
204 Nothing is returned.
205 """
207 examples_content: str = dedent(
208 f"""
209 Execute the below commands in any terminal after installing the package.
211 {_blue("dfc myfile.py")} {_green("# Check a single Python file (list output)")}
212 {_blue("dfc myfile.py other_file.py")} {_green("# Check multiple Python files")}
213 {_blue("dfc src/")} {_green("# Check all Python files in src/ directory")}
214 {_blue("dfc -x src/app/__init__.py src/")} {_green("# Check all Python files in src/ directory, excluding one init file")}
215 {_blue("dfc --output=table myfile.py")} {_green("# Check with table output format")}
216 {_blue("dfc -o list myfile.py")} {_green("# Check with list output format (default)")}
217 {_blue("dfc --check myfile.py")} {_green("# Check and exit with error if issues found")}
218 {_blue("dfc --quiet myfile.py")} {_green("# Check quietly, only show pass/fail")}
219 {_blue("dfc --quiet --check myfile.py")} {_green("# Check quietly and exit with error if issues found")}
220 {_blue("dfc . --exclude '*/tests/*'")} {_green("# Check current directory, excluding tests")}
221 {_blue("dfc . -c custom.toml")} {_green("# Use custom configuration file")}
222 {_blue("dfc --example=config")} {_green("# Show example configuration")}
223 {_blue("dfc -e usage")} {_green("# Show usage examples (this help)")}
224 """
225 ).strip()
227 panel = Panel(
228 examples_content,
229 title="Usage Examples",
230 title_align="left",
231 border_style="dim",
232 padding=(0, 1),
233 )
235 console.print(panel)
238def _show_config_example_callback() -> None:
239 """
240 !!! note "Summary"
241 Show configuration example and exit.
243 Returns:
244 (None):
245 Nothing is returned.
246 """
248 example_config: str = dedent(
249 r"""
250 Place the below config in your `pyproject.toml` file.
252 [blue]\[tool.dfc][/blue]
253 [green]# or \[tool.docstring-format-checker][/green]
254 [blue]allow_undefined_sections = false[/blue]
255 [blue]require_docstrings = true[/blue]
256 [blue]check_private = true[/blue]
257 [blue]validate_param_types = true[/blue]
258 [blue]optional_style = "validate"[/blue] [green]# "silent", "validate", or "strict"[/green]
259 [blue]sections = [[/blue]
260 [blue]{ order = 1, name = "summary", type = "free_text", required = true, admonition = "note", prefix = "!!!" },[/blue]
261 [blue]{ order = 2, name = "details", type = "free_text", required = false, admonition = "abstract", prefix = "???+" },[/blue]
262 [blue]{ order = 3, name = "params", type = "list_name_and_type", required = false },[/blue]
263 [blue]{ order = 4, name = "raises", type = "list_type", required = false },[/blue]
264 [blue]{ order = 5, name = "returns", type = "list_name_and_type", required = false },[/blue]
265 [blue]{ order = 6, name = "yields", type = "list_type", required = false },[/blue]
266 [blue]{ order = 7, name = "examples", type = "free_text", required = false, admonition = "example", prefix = "???+" },[/blue]
267 [blue]{ order = 8, name = "notes", type = "free_text", required = false, admonition = "note", prefix = "???" },[/blue]
268 [blue]][/blue]
269 """
270 ).strip()
272 panel = Panel(
273 example_config,
274 title="Configuration Example",
275 title_align="left",
276 border_style="dim",
277 padding=(0, 1),
278 )
280 # Print without Rich markup processing to avoid bracket interpretation
281 console.print(panel)
284def _help_callback_main(ctx: Context, param: CallbackParam, value: bool) -> None:
285 """
286 !!! note "Summary"
287 Show help and exit.
289 Params:
290 ctx (Context):
291 The context object.
292 param (CallbackParam):
293 The parameter object.
294 value (bool):
295 The boolean value indicating if the flag was set.
297 Returns:
298 (None):
299 Nothing is returned.
300 """
302 # Early exit if help flag is set
303 if not value or ctx.resilient_parsing:
304 return
306 # Determine terminal width for ASCII art
307 try:
308 terminal_width: int = os.get_terminal_size().columns
309 except OSError:
310 terminal_width = 80
312 # Determine title based on terminal width
313 title: str = "dfc" if terminal_width < 130 else "docstring-format-checker"
315 # Print ASCII art title
316 console.print(
317 pyfiglet.figlet_format(title, font="standard", justify="left", width=140),
318 style="magenta",
319 markup=False,
320 )
322 # Show help message
323 echo(ctx.get_help())
325 # Show usage and config examples
326 _show_usage_examples_callback()
327 _show_config_example_callback()
329 raise Exit()
332def _format_error_messages(error_message: str) -> str:
333 """
334 !!! note "Summary"
335 Format error messages for better readability in CLI output.
337 Params:
338 error_message (str):
339 The raw error message that may contain semicolon-separated errors
341 Returns:
342 (str):
343 Formatted error message with each error prefixed with "- " and separated by ";\n"
344 """
345 if "; " in error_message:
346 # Split by semicolon and rejoin with proper formatting
347 errors: list[str] = error_message.split("; ")
348 formatted_errors: list[str] = [f"- {error.strip()}" for error in errors if error.strip()]
349 return ";\n".join(formatted_errors) + "."
351 # Single error message
352 else:
353 return f"- {error_message.strip()}."
356def _display_results(
357 results: dict[str, list[DocstringError]],
358 quiet: bool,
359 output: str,
360 check: bool,
361) -> int:
362 """
363 !!! note "Summary"
364 Display the results of docstring checking.
366 Params:
367 results (dict[str, list[DocstringError]]):
368 Dictionary mapping file paths to lists of errors
369 quiet (bool):
370 Whether to suppress success messages and error details
371 output (str):
372 Output format: 'table' or 'list'
373 check (bool):
374 Whether this is a check run (affects quiet behavior)
376 Returns:
377 (int):
378 Exit code (`0` for success, `1` for errors found)
379 """
380 if not results:
381 if not quiet:
382 console.print(_green("✅ All docstrings are valid!"))
383 return 0
385 # Count errors and generate summary statistics
386 error_stats = _count_errors_and_files(results)
388 if quiet:
389 _display_quiet_summary(error_stats)
390 return 1
392 # Display detailed results based on output format
393 if output == "table":
394 _display_table_output(results)
395 else:
396 _display_list_output(results)
398 # Display final summary
399 _display_final_summary(error_stats)
400 return 1
403def _count_errors_and_files(results: dict[str, list[DocstringError]]) -> dict[str, int]:
404 """
405 !!! note "Summary"
406 Count total errors, functions, and files from results.
408 Params:
409 results (dict[str, list[DocstringError]]):
410 Dictionary mapping file paths to lists of errors.
412 Returns:
413 (dict[str, int]):
414 Dictionary containing total_errors, total_functions, and total_files.
415 """
416 total_individual_errors: int = 0
417 total_functions: int = 0
419 for errors in results.values():
420 total_functions += len(errors)
421 for error in errors:
422 if "; " in error.message:
423 individual_errors: list[str] = [msg.strip() for msg in error.message.split("; ") if msg.strip()]
424 total_individual_errors += len(individual_errors)
425 else:
426 total_individual_errors += 1
428 return {"total_errors": total_individual_errors, "total_functions": total_functions, "total_files": len(results)}
431def _display_quiet_summary(error_stats: dict[str, int]) -> None:
432 """
433 !!! note "Summary"
434 Display summary in quiet mode.
436 Params:
437 error_stats (dict[str, int]):
438 Dictionary containing total_errors, total_functions, and total_files.
439 """
440 functions_text = (
441 "1 function" if error_stats["total_functions"] == 1 else f"{error_stats['total_functions']} functions"
442 )
443 files_text: str = "1 file" if error_stats["total_files"] == 1 else f"{error_stats['total_files']} files"
445 console.print(_red(f"{NEW_LINE}Found {error_stats['total_errors']} error(s) in {functions_text} over {files_text}"))
448def _display_table_output(results: dict[str, list[DocstringError]]) -> None:
449 """
450 !!! note "Summary"
451 Display results in table format.
453 Params:
454 results (dict[str, list[DocstringError]]):
455 Dictionary mapping file paths to lists of errors.
456 """
457 table = Table(show_header=True, header_style="bold magenta")
458 table.add_column("File", style="cyan", no_wrap=False)
459 table.add_column("Line", justify="right", style="white")
460 table.add_column("Item", style="yellow")
461 table.add_column("Type", style="blue")
462 table.add_column("Error", style="red")
464 for file_path, errors in results.items():
465 for i, error in enumerate(errors):
466 file_display = file_path if i == 0 else ""
467 formatted_error_message = _format_error_messages(error.message)
469 table.add_row(
470 file_display,
471 str(error.line_number) if error.line_number > 0 else "",
472 error.item_name,
473 error.item_type,
474 f"[red]{formatted_error_message}[/red]",
475 )
476 console.print(table)
479def _create_error_header(error: DocstringError) -> str:
480 """
481 !!! note "Summary"
482 Create formatted header for a single error.
484 Params:
485 error (DocstringError):
486 The error to create a header for.
488 Returns:
489 (str):
490 Formatted header string with line number, item type, and name.
491 """
492 if error.line_number > 0:
493 return f" [red]Line {error.line_number}[/red] - {error.item_type} '{error.item_name}':"
494 else:
495 return f" {_red('Error')} - {error.item_type} '{error.item_name}':"
498def _split_error_messages(message: str) -> list[str]:
499 """
500 !!! note "Summary"
501 Split compound error message into individual messages.
503 Params:
504 message (str):
505 The error message to split.
507 Returns:
508 (list[str]):
509 List of individual error messages.
510 """
511 if "; " in message:
512 return [msg.strip() for msg in message.split("; ") if msg.strip()]
513 else:
514 return [message.strip()]
517def _format_error_output(error: DocstringError) -> list[str]:
518 """
519 !!! note "Summary"
520 Format single error for display output.
522 Params:
523 error (DocstringError):
524 The error to format.
526 Returns:
527 (list[str]):
528 List of formatted lines to print.
529 """
530 lines: list[str] = [_create_error_header(error)]
531 individual_errors: list[str] = _split_error_messages(error.message)
533 for individual_error in individual_errors:
534 # Escape square brackets for Rich markup using Rich's escape function
535 individual_error: str = escape(individual_error)
537 # Check if this error has multi-line content (e.g., parameter type mismatches)
538 if "\n" in individual_error:
539 # Split by newlines and add 4 spaces of extra indentation to each line
540 error_lines: list[str] = individual_error.split("\n")
541 lines.append(f" - {error_lines[0]}") # First line gets the bullet
542 for sub_line in error_lines[1:]:
543 if sub_line.strip(): # Only add non-empty lines
544 lines.append(f" {sub_line}") # Continuation lines get 4 spaces
545 else:
546 lines.append(f" - {individual_error}")
548 return lines
551def _display_list_output(results: dict[str, list[DocstringError]]) -> None:
552 """
553 !!! note "Summary"
554 Display results in list format.
556 Params:
557 results (dict[str, list[DocstringError]]):
558 Dictionary mapping file paths to lists of errors.
559 """
560 for file_path, errors in results.items():
561 console.print(f"{NEW_LINE}{_cyan(file_path)}")
562 for error in errors:
563 output_lines: list[str] = _format_error_output(error)
564 for line in output_lines:
565 console.print(line)
568def _display_final_summary(error_stats: dict[str, int]) -> None:
569 """
570 !!! note "Summary"
571 Display the final summary line.
573 Params:
574 error_stats (dict[str, int]):
575 Dictionary containing total_errors, total_functions, and total_files.
576 """
577 functions_text: str = (
578 "1 function" if error_stats["total_functions"] == 1 else f"{error_stats['total_functions']} functions"
579 )
580 files_text: str = "1 file" if error_stats["total_files"] == 1 else f"{error_stats['total_files']} files"
582 console.print(_red(f"{NEW_LINE}Found {error_stats['total_errors']} error(s) in {functions_text} over {files_text}"))
585# ---------------------------------------------------------------------------- #
586# #
587# Main Logic ####
588# #
589# ---------------------------------------------------------------------------- #
592# This will be the default behavior when no command is specified
593def check_docstrings(
594 paths: list[str],
595 config: Optional[str] = None,
596 exclude: Optional[list[str]] = None,
597 quiet: bool = False,
598 output: str = "list",
599 check: bool = False,
600) -> None:
601 """
602 !!! note "Summary"
603 Core logic for checking docstrings.
605 Params:
606 paths (list[str]):
607 The path(s) to the file(s) or directory(ies) to check.
608 config (Optional[str]):
609 The path to the configuration file.
610 Default: `None`.
611 exclude (Optional[list[str]]):
612 List of glob patterns to exclude from checking.
613 Default: `None`.
614 quiet (bool):
615 Whether to suppress output.
616 Default: `False`.
617 output (str):
618 Output format: 'table' or 'list'.
619 Default: `'list'`.
620 check (bool):
621 Whether to throw error if issues are found.
622 Default: `False`.
624 Returns:
625 (None):
626 Nothing is returned.
627 """
628 # Validate and process input paths
629 target_paths: list[Path] = _validate_and_process_paths(paths)
631 # Load and validate configuration
632 config_obj: Config = _load_and_validate_config(config, target_paths)
634 # Initialize checker and process all paths
635 checker = DocstringChecker(config_obj)
636 all_results: dict[str, list[DocstringError]] = _process_all_paths(checker, target_paths, exclude)
638 # Display results and handle exit
639 exit_code: int = _display_results(all_results, quiet, output, check)
640 if exit_code != 0:
641 raise Exit(exit_code)
644def _validate_and_process_paths(paths: list[str]) -> list[Path]:
645 """
646 !!! note "Summary"
647 Validate input paths and return valid paths.
649 Params:
650 paths (list[str]):
651 List of path strings to validate.
653 Raises:
654 (Exit):
655 If any paths do not exist.
657 Returns:
658 (list[Path]):
659 List of valid Path objects.
660 """
661 path_objs: list[Path] = [Path(path) for path in paths]
662 target_paths: list[Path] = [p for p in path_objs if p.exists()]
663 invalid_paths: list[Path] = [p for p in path_objs if not p.exists()]
665 if invalid_paths:
666 console.print(
667 _red("[bold]Error: Paths do not exist:[/bold]"),
668 NEW_LINE,
669 NEW_LINE.join([f"- '{invalid_path}'" for invalid_path in invalid_paths]),
670 )
671 raise Exit(1)
673 return target_paths
676def _load_and_validate_config(config: Optional[str], target_paths: list[Path]) -> Config:
677 """
678 !!! note "Summary"
679 Load and validate configuration from file or auto-discovery.
681 Params:
682 config (Optional[str]):
683 Optional path to configuration file.
684 target_paths (list[Path]):
685 List of target paths for auto-discovery.
687 Raises:
688 (Exit):
689 If configuration loading fails.
691 Returns:
692 (Config):
693 Loaded configuration object.
694 """
695 try:
696 if config:
697 return _load_explicit_config(config)
698 else:
699 return _load_auto_discovered_config(target_paths)
700 except Exception as e:
701 console.print(_red(f"Error loading configuration: {e}"))
702 raise Exit(1) from e
705def _load_explicit_config(config: str) -> Config:
706 """
707 !!! note "Summary"
708 Load configuration from explicitly specified path.
710 Params:
711 config (str):
712 Path to configuration file.
714 Raises:
715 (Exit):
716 If configuration file does not exist.
718 Returns:
719 (Config):
720 Loaded configuration object.
721 """
722 config_path = Path(config)
723 if not config_path.exists():
724 console.print(_red(f"Error: Configuration file does not exist: {config}"))
725 raise Exit(1)
726 return load_config(config_path)
729def _load_auto_discovered_config(target_paths: list[Path]) -> Config:
730 """
731 !!! note "Summary"
732 Load configuration from auto-discovery or defaults.
734 Params:
735 target_paths (list[Path]):
736 List of target paths to search for configuration.
738 Returns:
739 (Config):
740 Loaded configuration object from found config or defaults.
741 """
742 first_path: Path = target_paths[0]
743 search_path: Path = first_path if first_path.is_dir() else first_path.parent
744 found_config: Optional[Path] = find_config_file(search_path)
746 if found_config:
747 return load_config(found_config)
748 else:
749 return load_config()
752def _process_all_paths(
753 checker: DocstringChecker, target_paths: list[Path], exclude: Optional[list[str]]
754) -> dict[str, list[DocstringError]]:
755 """
756 !!! note "Summary"
757 Process all target paths and collect docstring errors.
759 Params:
760 checker (DocstringChecker):
761 The checker instance to use.
762 target_paths (list[Path]):
763 List of paths to check (files or directories).
764 exclude (Optional[list[str]]):
765 Optional list of exclusion patterns.
767 Raises:
768 (Exit):
769 If an error occurs during checking.
771 Returns:
772 (dict[str, list[DocstringError]]):
773 Dictionary mapping file paths to lists of errors.
774 """
775 all_results: dict[str, list[DocstringError]] = {}
777 try:
778 for target_path in target_paths:
779 if target_path.is_file():
780 errors: list[DocstringError] = checker.check_file(target_path)
781 if errors:
782 all_results[str(target_path)] = errors
783 else:
784 directory_results: dict[str, list[DocstringError]] = checker.check_directory(
785 target_path, exclude_patterns=exclude
786 )
787 all_results.update(directory_results)
788 except Exception as e:
789 console.print(_red(f"Error during checking: {e}"))
790 raise Exit(1) from e
792 return all_results
795# ---------------------------------------------------------------------------- #
796# #
797# App Operators ####
798# #
799# ---------------------------------------------------------------------------- #
802# Simple callback that only handles global options and delegates to subcommands
803@app.callback(invoke_without_command=True)
804def main(
805 ctx: Context,
806 paths: Optional[list[str]] = Argument(None, help="Path(s) to Python file(s) or directory(s) for DFC to check"),
807 config: Optional[str] = Option(None, "--config", "-f", help="Path to configuration file (TOML format)"),
808 exclude: Optional[list[str]] = Option(
809 None,
810 "--exclude",
811 "-x",
812 help="Glob patterns to exclude (can be used multiple times)",
813 ),
814 output: str = Option(
815 "list",
816 "--output",
817 "-o",
818 help="Output format: 'table' or 'list'",
819 show_default=True,
820 ),
821 check: bool = Option(
822 False,
823 "--check",
824 "-c",
825 help="Throw error (exit 1) if any issues are found",
826 ),
827 quiet: bool = Option(
828 False,
829 "--quiet",
830 "-q",
831 help="Only output pass/fail confirmation, suppress errors unless failing",
832 ),
833 example: Optional[str] = Option(
834 None,
835 "--example",
836 "-e",
837 callback=_example_callback,
838 is_eager=True,
839 help="Show examples: 'config' for configuration example, 'usage' for usage examples",
840 ),
841 version: Optional[bool] = Option(
842 None,
843 "--version",
844 "-v",
845 callback=_version_callback,
846 is_eager=True,
847 help="Show version and exit",
848 ),
849 help_flag: Optional[bool] = Option(
850 None,
851 "--help",
852 "-h",
853 callback=_help_callback_main,
854 is_eager=True,
855 help="Show this message and exit",
856 ),
857) -> None:
858 """
859 !!! note "Summary"
860 Check Python docstring formatting and completeness.
862 ???+ abstract "Details"
863 This tool analyzes Python files and validates that functions, methods, and classes have properly formatted docstrings according to the configured sections.
865 Params:
866 ctx (Context):
867 The context object for the command.
868 paths (Optional[list[str]]):
869 Path(s) to Python file(s) or directory(ies) to check.
870 config (Optional[str]):
871 Path to configuration file (TOML format).
872 exclude (Optional[list[str]]):
873 Glob patterns to exclude.
874 output (str):
875 Output format: 'table' or 'list'.
876 check (bool):
877 Throw error if any issues are found.
878 quiet (bool):
879 Only output pass/fail confirmation.
880 example (Optional[str]):
881 Show examples: 'config' or 'usage'.
882 version (Optional[bool]):
883 Show version and exit.
884 help_flag (Optional[bool]):
885 Show help message and exit.
887 Returns:
888 (None):
889 Nothing is returned.
890 """
892 # If no paths are provided, show help
893 if not paths:
894 echo(ctx.get_help())
895 raise Exit(0)
897 # Validate output format
898 if output not in ["table", "list"]:
899 console.print(_red(f"Error: Invalid output format '{output}'. Use 'table' or 'list'."))
900 raise Exit(1)
902 check_docstrings(
903 paths=paths,
904 config=config,
905 exclude=exclude,
906 quiet=quiet,
907 output=output,
908 check=check,
909 )
912def entry_point() -> None:
913 """
914 !!! note "Summary"
915 Entry point for the CLI scripts defined in pyproject.toml.
916 """
917 app()