diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..1699769 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,53 @@ + + +## Type of Change + + + +- [ ] 🐛 **Bug Fix**: A change that fixes an issue. +- [ ] ✨ **New Feature**: A change that adds new functionality. +- [ ] ♻️ **Refactor**: A code change that neither fixes a bug nor adds a feature. +- [ ] 📚 **Documentation**: Changes to the documentation only. +- [ ] ⚙️ **CI/CD**: Changes to our CI/CD configuration and scripts. +- [ ] 🔨 **Build**: Changes that affect the build system or external dependencies. +- [ ] 🎨 **Style**: Changes that do not affect the meaning of the code (white-space, formatting, etc.). +- [ ] ⏪ **Revert**: Reverts a previous commit. + +--- + +## Related Issue + + + +- *** + +## Description + + + +- + +## Checklist + + + +- [ ] My code follows the style guidelines of this project. +- [ ] I have performed a self-review of my own code. +- [ ] New unit tests have been added to cover the changes. +- [ ] Manual testing has been performed diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d95f3b6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,79 @@ +name: CI + +on: + push: + branches: [master] + pull_request_target: + branches: [master] + +jobs: + test: + name: Test on Python ${{ matrix.python-version }} (beta=${{ matrix.beta }}) + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13"] + beta: [false, true] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.22" + + - name: Build Go parsers + run: pwsh -File ./build_goparser.ps1 + shell: bash + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install .[dev] + + - name: CLI smoke test + run: | + agent-docstrings --version + if [ "${{ matrix.beta }}" = "true" ]; then + agent-docstrings --beta --version + fi + + - name: Run tests with coverage + run: | + pytest --cov=agent_docstrings --cov-report=xml --cov-report=term-missing + + - name: Upload coverage artifact + uses: actions/upload-artifact@v4 + with: + name: coverage-${{ matrix.python-version }}-${{ matrix.beta }} + path: coverage.xml + + report: + name: Report Coverage + runs-on: ubuntu-latest + needs: test + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download all coverage artifacts + uses: actions/download-artifact@v4 + with: + path: coverage-artifacts + pattern: coverage-* + merge-multiple: true + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + directory: ./coverage-artifacts/ + fail_ci_if_error: true diff --git a/.gitignore b/.gitignore index b0d05a0..4cae6eb 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST +/git_diff.txt # PyInstaller # Usually these files are written by a small stub bootstrap script and should be diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c47d82..3d48268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Additional language support: Swift, Perl, Curl, Fortran, Visual Basic, R, PHP, Lua, Bash, SQL - Configuration file format validation - Switching to the Abstract Syntax Tree (AST) +- [And more...](https://github.com/Artemonim/AgentDocstrings/issues) + +## [1.3.0] - 2025-06-30 + +### Added + +- **Process Individual Files**: The CLI now accepts both directory and individual file paths, allowing for more granular control over which files are processed. +- **Expanded Keywords**: Added a comprehensive list of keywords to `pyproject.toml` to significantly improve package discoverability on PyPI and search engines. +- **Beta Features Flag**: Introduced a `--beta` command-line flag to enable experimental features that are under development. + +### Changed + +- **Header Text Update**: Changed the auto-generated header to "Table of content is automatically generated by Agent Docstrings {version}". +- **Streamlined Header Format**: The format of the generated "Table of Contents" has been improved. Top-level functions and classes are now presented in a single, chronologically sorted list, removing the nested "Functions" section for a cleaner, more intuitive layout. +- **Deterministic Sorting**: All discovered items (classes, methods, functions) are now strictly sorted by their line number in the source file, ensuring a consistent and predictable output every time. +- **CLI Argument Renaming**: The `DIRECTORY` argument in the CLI has been renamed to `PATH` to accurately reflect its new capability to handle both files and directories. +- **Header Version Updates**: The tool will now update the header if the generator version has changed, even if the code structure remains the same, ensuring docstrings always reflect the version of the tool that generated them. +- **Repository URLs**: Updated project URLs in `pyproject.toml` to point to the correct `AgentDocstrings` repository name. + +### Fixed + +- **Error Handling for Inaccessible Directories**: Fixed a crash (`PermissionError`) that occurred when scanning directories with restricted read permissions. The application will now skip such directories and print a warning, preventing unintended modifications to files that might have been excluded by an unreadable `.gitignore` or other configuration files. +- **Deleting empty lines**: Detected and fixed the removal of empty lines at the end of processed files +- **Language-Specific Indentation**: Fixed the indentation in the generated 'Table of Contents' to respect common style conventions for each language (e.g., 4 spaces for Python, 2 for JavaScript). + +### Documentation + +- **Complete README Overhaul**: The `README.md` has been completely rewritten to be more comprehensive, professional, and user-friendly. It now includes a clear project mission, a detailed table of contents, expanded sections on features and usage, new examples, and platform compatibility information. ## [1.2.1] - 2025-06-30 diff --git a/Doc/AgentDocstringsExample130.mp4 b/Doc/AgentDocstringsExample130.mp4 new file mode 100644 index 0000000..cad3c04 Binary files /dev/null and b/Doc/AgentDocstringsExample130.mp4 differ diff --git a/README.md b/README.md index 70e17aa..c000980 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,49 @@ + [![PyPI version](https://badge.fury.io/py/agent-docstrings.svg)](https://badge.fury.io/py/agent-docstrings) [![Python versions](https://img.shields.io/pypi/pyversions/agent-docstrings.svg)](https://pypi.org/project/agent-docstrings/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + [![GitHub stars](https://img.shields.io/github/stars/Artemonim/AgentDocstrings.svg?style=social&label=Star)](https://github.com/Artemonim/AgentDocstrings) [![GitHub forks](https://img.shields.io/github/forks/Artemonim/AgentDocstrings.svg?style=social&label=Fork)](https://github.com/Artemonim/AgentDocstrings) [![Build Status](https://github.com/Artemonim/AgentDocstrings/workflows/Publish%20Python%20Package%20to%20PyPI/badge.svg)](https://github.com/Artemonim/AgentDocstrings/actions) + [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Typed with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/) [![codecov](https://codecov.io/gh/Artemonim/AgentDocstrings/branch/master/graph/badge.svg)](https://codecov.io/gh/Artemonim/AgentDocstrings) -# Agent Docstrings Generator +# Agent Docstrings: Automatic Code Summaries + +**Agent Docstrings** is a command-line tool that automatically generates and maintains a "Table of Contents" at the top of your source files. It scans for classes, functions, and methods, creating a summary that provides a high-level overview of the file's structure. + + + +This is especially useful for AI-Agents: quickly understanding large files, navigating unfamiliar codebases, etc. + +--- -A command-line tool to auto-generate and update file-level docstrings summarizing classes and functions. Useful for maintaining a high-level overview of your files, especially in projects with code generated or modified by AI assistants. +## Table of Contents + +- [Supported Languages](#supported-languages) +- [Why Use Agent Docstrings?](#why-use-agent-docstrings) +- [Features](#features) +- [Examples](#examples) +- [Platform Compatibility](#platform-compatibility) +- [Installation](#installation) +- [Usage](#usage) +- [Configuration](#configuration) +- [Limitations and Nuances](#limitations-and-nuances) +- [Integration with Development Workflow](#integration-with-development-workflow) +- [Development](#development) +- [Contributing](#contributing) +- [License](#license) +- [Changelog](#changelog) + +--- ## Supported Languages @@ -33,28 +61,76 @@ A command-line tool to auto-generate and update file-level docstrings summarizin | JavaScript | `.js`, `.jsx` | Functions, classes | | TypeScript | `.ts`, `.tsx` | Functions, classes | -## Why? +## Why Use Agent Docstrings? -When working in Cursor and similar IDEs, Agents often start reading files from the beginning. And regarding Cursor's behavior during the script's creation, in normal mode, the model reads 250 lines of code per call, and in MAX mode, 750 lines. However, I have projects with files over 1000 lines of code, which are not very appropriate to divide into smaller files. And anyway, Agent still have to call reading tools for each individual file. +In any large-scale project, quickly understanding the contents of a file is a major challenge. Scrolling through hundreds or thousands of lines just to find a specific function or get a sense of the file's architecture is inefficient and slows down development. -At the same time, the Agent can choose from which line to read the file. It can navigate and surf within your repository. The script literally provides the Agent with the table of contents of the current file, so that immediately after the first read, the Agent understands the entire structure and can read the file from a specific line, rather than trying to get to it (while also potentially making mistakes along the way). +**Agent Docstrings** solves this by providing an up-to-date, scannable "Table of Contents" at the beginning of each file. This offers several key advantages: -In addition to the advantage of quick navigation, the initial docstring also serves as a method to reduce context window usage. For example, if a required method in a 900-line file is on line 856, the Agent will only read lines 1-250 and 856-900, instead of sequentially going to the desired forty lines and filling its context with unnecessary code. +- **Improved Code Navigation**: Get an immediate high-level overview of any file's structure without reading its entire content. Jump directly to the code you need. +- **Faster Onboarding**: AI assistants (and New developers) can familiarize themselves with the codebase much faster. The generated docstring acts as a map to the file's contents. + +While the tool was initially inspired by the needs of AI-powered IDEs like Cursor, its utility extends to any developer or team looking to improve code maintainability and comprehension. ## Features -- **Multi-language support**: Python, Java, Kotlin, Go, PowerShell, Delphi, C, C++, C#, JavaScript, TypeScript -- **Automatic discovery**: Recursively scans directories for source files -- **Smart filtering**: Respects `.gitignore` files and custom blacklist/whitelist configurations -- **Incremental updates**: Only modifies files when changes are detected -- **Type annotations**: Full type hint support for Python 3.8+ -- **CLI interface**: Easy-to-use command-line tool +- **Multi-language support**: Works with a wide range of popular programming languages. +- **Automatic discovery**: Recursively scans directories for source files to process. +- **Smart filtering**: Automatically respects `.gitignore` files and allows for custom ignore (`.agent-docstrings-ignore`) and include (`.agent-docstrings-include`) files for fine-grained control. +- **Incremental updates**: Designed to be fast, it only modifies files when changes to the code structure are detected. +- **Robust Parsers**: Uses reliable AST (Abstract Syntax Tree) parsers for Python and Go, and intelligent regex-based parsing for other languages. +- **CLI interface**: A simple and easy-to-use command-line tool for manual runs or CI/CD integration. +- **Extensively Tested**: High reliability is ensured by a comprehensive suite of over 140 tests, covering everything from individual parsers (unit tests) to full command-line behavior (end-to-end tests). -## Python Version Compatibility +## Examples -This tool is compatible with **Python 3.8, 3.9, 3.10, 3.11, 3.12, and 3.13**. +### Python Example -- No dependency on external libraries +Before: + +```python +def calculate_fibonacci(n): + if n <= 1: + return n + return calculate_fibonacci(n-1) + calculate_fibonacci(n-2) + +class MathUtils: + def add(self, a, b): + return a + b +``` + +After: + +```python +""" + --- AUTO-GENERATED DOCSTRING --- + Table of content is automatically generated by Agent Docstrings v1.3.0 + + Classes/Functions: + - MathUtils (line 18): + - add(a, b) (line 19) + - Functions: + - calculate_fibonacci(n) (line 13) + --- END AUTO-GENERATED DOCSTRING --- +""" +def calculate_fibonacci(n): + if n <= 1: + return n + return calculate_fibonacci(n-1) + calculate_fibonacci(n-2) + +class MathUtils: + def add(self, a, b): + return a + b +``` + +## Platform Compatibility + +This tool is compatible with: + +- **Python**: 3.8, 3.9, 3.10, 3.11, 3.12, and 3.13 +- **Go**: >=1.22 (required only for building the Go parser during package development) + +- No dependency on external Python libraries at runtime ## Installation @@ -74,22 +150,32 @@ pip install -e . ## Usage -### Basic usage +### Processing paths + +You can process one or more directories, files, or a mix of both. + +Process a directory: ```bash agent-docstrings src/ ``` -### With verbose output +Process a single file: ```bash -agent-docstrings src/ --verbose +agent-docstrings src/main.py +``` + +Process multiple paths: + +```bash +agent-docstrings src/ tests/ lib/utils.py ``` -### Process multiple directories +### With verbose output ```bash -agent-docstrings src/ tests/ lib/ +agent-docstrings src/ --verbose ``` ### Using as a Python module @@ -97,40 +183,23 @@ agent-docstrings src/ tests/ lib/ ```python from agent_docstrings.core import discover_and_process_files -# Process directories -discover_and_process_files(["src/", "lib/"], verbose=True) +# Process a mix of files and directories +discover_and_process_files(["src/", "lib/utils.py"], verbose=True) ``` ## Configuration -### Blacklist (Ignore files) +### Gitignore Integration -Create a `.agent-docstrings-ignore` file in your project root to specify files and directories to ignore: +The tool automatically reads and respects `.gitignore` files in your project directory and its parents. Files and directories ignored by git will also be ignored by the docstring generator. -``` -# Test directories -tests/ -test_*.py - -# Build and cache directories -__pycache__/ -*.pyc -build/ -dist/ -*.egg-info/ - -# IDE files -.vscode/ -.idea/ - -# Documentation -docs/ -README.md -``` +### Blacklist (Ignore files) + +You can create a gitignore-like `.agent-docstrings-ignore` file in your project root to specify files and directories to ignore: ### Whitelist (Only process specific files) -Create a `.agent-docstrings-include` file to only process specific files: +You can create a gitignore-like `.agent-docstrings-include` file to only process specific files: ``` # Only process main source code @@ -141,10 +210,6 @@ agent_docstrings/*.py **Note**: If a whitelist file exists and is not empty, ONLY files matching the whitelist patterns will be processed. -### Gitignore Integration - -The tool automatically reads and respects `.gitignore` files in your project directory and its parents. Files and directories ignored by git will also be ignored by the docstring generator. - ## Limitations and Nuances It is important to understand the nuances of this tool to use it effectively. The quality and method of code parsing vary significantly by language. @@ -163,48 +228,6 @@ It is important to understand the nuances of this tool to use it effectively. Th - **In-Place File Modification**: The tool modifies files directly. It is designed to correctly remove its own previously generated headers, but it might struggle with files that have very complex, pre-existing header comments, potentially leading to incorrect placement of the new header. -## Examples - -### Python Example - -Before: - -```python -def calculate_fibonacci(n): - if n <= 1: - return n - return calculate_fibonacci(n-1) + calculate_fibonacci(n-2) - -class MathUtils: - def add(self, a, b): - return a + b -``` - -After: - -```python -""" - --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings. - Do not modify this block directly. - - Classes/Functions: - - MathUtils (line 8): - - add(a, b) (line 9) - - Functions: - - calculate_fibonacci(n) (line 1) - --- END AUTO-GENERATED DOCSTRING --- -""" -def calculate_fibonacci(n): - if n <= 1: - return n - return calculate_fibonacci(n-1) + calculate_fibonacci(n-2) - -class MathUtils: - def add(self, a, b): - return a + b -``` - ## Integration with Development Workflow ### Pre-commit Hook diff --git a/agent_docstrings/__init__.py b/agent_docstrings/__init__.py index 471edb9..5aea6e4 100644 --- a/agent_docstrings/__init__.py +++ b/agent_docstrings/__init__.py @@ -7,4 +7,4 @@ Attributes: __version__ (str): Current version of the *agent-docstrings* package. """ -__version__ = "1.2.1" \ No newline at end of file +__version__ = "1.3.0" \ No newline at end of file diff --git a/agent_docstrings/cli.py b/agent_docstrings/cli.py index a536e47..373733e 100644 --- a/agent_docstrings/cli.py +++ b/agent_docstrings/cli.py @@ -1,11 +1,9 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - Functions: - - main() (line 19) + - main() (line 17) --- END AUTO-GENERATED DOCSTRING --- """ import argparse @@ -29,10 +27,10 @@ def main(): epilog="Example:\n agent-docstrings ./src ./libs" ) parser.add_argument( - "directories", - metavar="DIRECTORY", + "paths", + metavar="PATH", nargs="+", - help="One or more directories to scan for source files.", + help="One or more files or directories to scan for source files.", ) parser.add_argument( "-v", @@ -45,16 +43,25 @@ def main(): action="version", version=f"%(prog)s {__version__}", ) + parser.add_argument( + "--beta", + action="store_true", + help="Enable experimental beta features that may have breaking changes." + ) args = parser.parse_args() - # Check if directories exist before starting - for d in args.directories: - if not Path(d).is_dir(): - print(f"Error: Directory not found at '{d}'", file=sys.stderr) + # Check if paths exist before starting + for p_str in args.paths: + p = Path(p_str) + if not p.exists(): + print(f"Error: Path not found at '{p_str}'", file=sys.stderr) + sys.exit(1) + if not p.is_dir() and not p.is_file(): + print(f"Error: Path is not a file or directory at '{p_str}'", file=sys.stderr) sys.exit(1) - core.discover_and_process_files(args.directories, args.verbose) + core.discover_and_process_files(args.paths, args.verbose, args.beta) print("Done.") if __name__ == "__main__": diff --git a/agent_docstrings/core.py b/agent_docstrings/core.py index 5186b2d..0f57c03 100644 --- a/agent_docstrings/core.py +++ b/agent_docstrings/core.py @@ -1,22 +1,20 @@ +from __future__ import annotations """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - Functions: - - parse_gitignore(gitignore_path: Path) -> Set[str] (line 54) - - is_path_ignored(path: Path, ignore_patterns: Set[str], root_dir: Path) -> bool (line 77) - - load_blacklist_whitelist(directory: Path) -> Tuple[Set[str], Set[str]] (line 101) - - should_process_file(file_path: Path, root_dir: Path, ignore_patterns: Set[str], blacklist: Set[str], whitelist: Set[str]) -> bool (line 136) - - _get_header_content_lines(classes: List[ClassInfo], functions: List[SignatureInfo], language: str, line_offset: int) -> List[str] (line 206) - - _format_header(classes: List[ClassInfo], functions: List[SignatureInfo], language: str, line_offset: int) -> str (line 242) - - get_preserved_header_end_line(lines: List[str], language: str) -> int (line 262) - - process_file(path: Path, verbose: bool = False) -> None (line 344) - - discover_and_process_files(directories: List[str], verbose: bool = False) -> None (line 468) + - parse_gitignore(gitignore_path: Path) -> Set[str] (line 53) + - is_path_ignored(path: Path, ignore_patterns: Set[str], root_dir: Path) -> bool (line 76) + - load_blacklist_whitelist(directory: Path) -> Tuple[Set[str], Set[str]] (line 100) + - should_process_file(file_path: Path, root_dir: Path, ignore_patterns: Set[str], blacklist: Set[str], whitelist: Set[str]) -> bool (line 135) + - _get_header_content_lines(classes: List[ClassInfo], functions: List[SignatureInfo], language: str, line_offset: int) -> List[str] (line 205) + - _format_header(classes: List[ClassInfo], functions: List[SignatureInfo], language: str, line_offset: int) -> str (line 246) + - get_preserved_header_end_line(lines: List[str], language: str) -> int (line 266) + - process_file(path: Path, verbose: bool = False, beta: bool = False) -> None (line 348) + - discover_and_process_files(paths: List[str], verbose: bool = False, beta: bool = False) -> None (line 474) --- END AUTO-GENERATED DOCSTRING --- """ -from __future__ import annotations import os import fnmatch from pathlib import Path @@ -24,6 +22,8 @@ import re from . import __version__ +# * Template for the auto-generated header line +DOCSTRING_HEADER_TEMPLATE = "Table of content is automatically generated by Agent Docstrings v{version}" from .languages.common import ( COMMENT_STYLES, ClassInfo, @@ -69,6 +69,8 @@ def parse_gitignore(gitignore_path: Path) -> Set[str]: line = line.strip() if line and not line.startswith("#"): patterns.add(line) + except PermissionError: + raise except Exception: pass return patterns @@ -118,6 +120,8 @@ def load_blacklist_whitelist(directory: Path) -> Tuple[Set[str], Set[str]]: for line in f if line.strip() and not line.startswith("#") ) + except PermissionError: + raise except Exception: pass if whitelist_file.exists(): @@ -128,6 +132,8 @@ def load_blacklist_whitelist(directory: Path) -> Tuple[Set[str], Set[str]]: for line in f if line.strip() and not line.startswith("#") ) + except PermissionError: + raise except Exception: pass return blacklist, whitelist @@ -213,28 +219,33 @@ def _get_header_content_lines( style = COMMENT_STYLES[language] lines = [ f"{style.prefix}{DOCSTRING_START_MARKER}", - f"{style.prefix}This docstring is automatically generated by Agent Docstrings v{__version__}", - f"{style.prefix}Do not modify this block directly.", + f"{style.prefix}{DOCSTRING_HEADER_TEMPLATE.format(version=__version__)}", f"{style.prefix}", f"{style.prefix}Classes/Functions:", ] def format_class(ci: ClassInfo, indent: str): lines.append(f"{indent}- {ci.name} (line {ci.line + line_offset}):") - for m in ci.methods: - lines.append(f"{indent} - {m.signature} (line {m.line + line_offset})") - for inner_ci in ci.inner_classes: - format_class(inner_ci, indent + " ") - - if classes: - for c in classes: - format_class(c, style.prefix) - if functions: - lines.append(f"{style.prefix} - Functions:") - for f in functions: + for m in sorted(ci.methods, key=lambda x: x.line): + lines.append(f"{indent}{style.indent}- {m.signature} (line {m.line + line_offset})") + for inner_ci in sorted(ci.inner_classes, key=lambda x: x.line): + format_class(inner_ci, indent + style.indent) + + # Combine classes and functions into a single list of top-level items + top_level_items = sorted( + classes + functions, key=lambda item: item.line + ) + + item_prefix = f"{style.prefix}{style.indent}" + for item in top_level_items: + if isinstance(item, ClassInfo): + # ! Pass the correct prefix for top-level classes + format_class(item, item_prefix) + elif isinstance(item, SignatureInfo): lines.append( - f"{style.prefix} - {f.signature} (line {f.line + line_offset})" + f"{item_prefix}- {item.signature} (line {item.line + line_offset})" ) + lines.append(f"{style.prefix}{DOCSTRING_END_MARKER}") return lines @@ -341,7 +352,7 @@ def get_preserved_header_end_line(lines: List[str], language: str) -> int: return len(lines) -def process_file(path: Path, verbose: bool = False) -> None: +def process_file(path: Path, verbose: bool = False, beta: bool = False) -> None: """Generate or refresh the header comment for *path*.""" ext = path.suffix.lower() if ext not in EXT_TO_LANG: @@ -354,7 +365,8 @@ def process_file(path: Path, verbose: bool = False) -> None: original_content = path.read_text(encoding="utf-8", errors="ignore") if not original_content.strip(): return - lines = original_content.splitlines() + # * Skip regeneration when only generator version changed in header + lines = original_content.split('\n') header_end_line = get_preserved_header_end_line(lines, language) file_prefix = "\n".join(lines[:header_end_line]) code_body = "\n".join(lines[header_end_line:]) @@ -363,11 +375,10 @@ def process_file(path: Path, verbose: bool = False) -> None: classes, functions = parser(cleaned_body.splitlines()) if not classes and not functions: # If all that was done was removing a docstring, write the cleaned content back - if cleaned_body.strip() != code_body.strip(): + if cleaned_body != code_body: path.write_text( (file_prefix + "\n" + cleaned_body).lstrip(), encoding="utf-8", - newline="\n", ) return @@ -450,12 +461,13 @@ def process_file(path: Path, verbose: bool = False) -> None: if file_prefix: new_content_parts.append(file_prefix) new_content_parts.append(final_header) - new_content_parts.append(cleaned_body.strip()) + new_content_parts.append(cleaned_body.lstrip()) # Use single newlines to test composition theory new_content = "\n".join(filter(None, new_content_parts)) - if new_content.strip() != original_content.strip(): - path.write_text(new_content, encoding="utf-8", newline="\n") + # Only write changes if content changed + if new_content != original_content: + path.write_text(new_content, encoding="utf-8") if verbose: print(f"Processed {language.capitalize()}: {path}") elif verbose: @@ -465,43 +477,57 @@ def process_file(path: Path, verbose: bool = False) -> None: print(f"Error processing {path}: {e}") -def discover_and_process_files(directories: List[str], verbose: bool = False) -> None: - """Recursively process all supported files inside *directories*. +def discover_and_process_files(paths: List[str], verbose: bool = False, beta: bool = False) -> None: + """Recursively process all supported files inside *paths*. Args: - directories (List[str]): White-list of root folders to scan. + paths (List[str]): White-list of root folders or files to scan. verbose (bool, optional): Enables per-file logging when *True*. + beta (bool, optional): Enables experimental beta features. """ - for dir_str in directories: - directory = Path(dir_str).resolve() - if not directory.is_dir(): - print(f"Warning: '{dir_str}' is not a valid directory. Skipping.") - continue - - # Collect all gitignore patterns from the directory tree - ignore_patterns = set() - current_dir = directory - while current_dir != current_dir.parent: - gitignore_path = current_dir / '.gitignore' - if gitignore_path.exists(): - ignore_patterns.update(parse_gitignore(gitignore_path)) - current_dir = current_dir.parent - - # Load blacklist and whitelist from the root directory - blacklist_patterns, whitelist_patterns = load_blacklist_whitelist(directory) - - for root, dirs, files in os.walk(directory): - root_path = Path(root) - - # Filter directories to avoid walking into ignored ones - dirs[:] = [d for d in dirs if d not in DEFAULT_IGNORE_DIRS and not is_path_ignored(root_path / d, ignore_patterns, directory)] - - for file in files: - file_path = root_path / file + files_to_process = [] + + for p_str in paths: + try: + path = Path(p_str).resolve() + if not path.exists(): + print(f"Warning: '{p_str}' is not a valid path. Skipping.") + continue - # Check if file should be processed - if not should_process_file(file_path, directory, ignore_patterns, - blacklist_patterns, whitelist_patterns): - continue + if path.is_dir(): + # Collect all gitignore patterns from the directory tree + ignore_patterns = set() + current_dir = path + while current_dir != current_dir.parent: + gitignore_path = current_dir / '.gitignore' + if gitignore_path.exists(): + ignore_patterns.update(parse_gitignore(gitignore_path)) + current_dir = current_dir.parent - process_file(file_path, verbose) \ No newline at end of file + # Load blacklist and whitelist from the root directory + blacklist_patterns, whitelist_patterns = load_blacklist_whitelist(path) + + for root, dirs, files in os.walk(path): + root_path = Path(root) + + # Filter directories to avoid walking into ignored ones + dirs[:] = [d for d in dirs if d not in DEFAULT_IGNORE_DIRS and not is_path_ignored(root_path / d, ignore_patterns, path)] + + for file in files: + file_path = root_path / file + + # Check if file should be processed + if not should_process_file(file_path, path, ignore_patterns, + blacklist_patterns, whitelist_patterns): + continue + + files_to_process.append(file_path) + elif path.is_file(): + files_to_process.append(path) + except PermissionError: + print(f"Warning: Could not read configuration (e.g., .gitignore) in '{p_str}' due to a permission error. Skipping path to ensure no unintended files are modified.") + continue + + # Process all collected files + for file_path in sorted(list(set(files_to_process))): + process_file(file_path, verbose, beta) \ No newline at end of file diff --git a/agent_docstrings/example/example.py b/agent_docstrings/example/example.py new file mode 100644 index 0000000..df296f3 --- /dev/null +++ b/agent_docstrings/example/example.py @@ -0,0 +1,18 @@ +""" + --- AUTO-GENERATED DOCSTRING --- + Table of content is automatically generated by Agent Docstrings v1.3.0 + + Classes/Functions: + - calculate_fibonacci(n) (line 11) + - MathUtils (line 16): + - add(a, b) (line 17) + --- END AUTO-GENERATED DOCSTRING --- +""" +def calculate_fibonacci(n): + if n <= 1: + return n + return calculate_fibonacci(n-1) + calculate_fibonacci(n-2) + +class MathUtils: + def add(self, a, b): + return a + b diff --git a/agent_docstrings/go_ast_parser.go b/agent_docstrings/go_ast_parser.go index ed38657..535dadb 100644 --- a/agent_docstrings/go_ast_parser.go +++ b/agent_docstrings/go_ast_parser.go @@ -1,309 +1,307 @@ -/* - * --- AUTO-GENERATED DOCSTRING --- - * This docstring is automatically generated by Agent Docstrings. - * Do not modify this block directly. - * - * Classes/Functions: - * - Functions: - * - func main() (line 50) - * - func parseGoCode(source string) (*ParseResult, error) (line 71) - * - func buildFunctionSignature(fn *ast.FuncDecl) string (line 141) - * - func buildMethodSignature(fn *ast.FuncDecl) string (line 152) - * - func buildInterfaceMethodSignature(method *ast.Field) string (line 170) - * - func buildParameterList(params *ast.FieldList) string (line 187) - * - func buildResultList(results *ast.FieldList) string (line 215) - * - func getTypeString(expr ast.Expr) string (line 241) - * - func getExprString(expr ast.Expr) string (line 282) - * - func joinStrings(strs []string, sep string) string (line 293) - * --- END AUTO-GENERATED DOCSTRING --- - */ - -package main - -import ( - "encoding/json" - "fmt" - "go/ast" - "go/parser" - "go/token" - "io" - "os" -) - -// SignatureInfo represents a function or method signature with line number -type SignatureInfo struct { - Signature string `json:"signature"` - Line int `json:"line"` -} - -// ClassInfo represents a struct or interface with its methods -type ClassInfo struct { - Name string `json:"name"` - Line int `json:"line"` - Methods []SignatureInfo `json:"methods"` - InnerClasses []ClassInfo `json:"inner_classes"` -} - -// ParseResult contains the parsing results -type ParseResult struct { - Classes []ClassInfo `json:"classes"` - Functions []SignatureInfo `json:"functions"` -} - -func main() { - // Read Go source code from stdin - input, err := io.ReadAll(os.Stdin) - if err != nil { - fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err) - os.Exit(1) - } - - result, err := parseGoCode(string(input)) - if err != nil { - fmt.Fprintf(os.Stderr, "Error parsing Go code: %v\n", err) - os.Exit(1) - } - - // Output JSON result - if err := json.NewEncoder(os.Stdout).Encode(result); err != nil { - fmt.Fprintf(os.Stderr, "Error encoding JSON: %v\n", err) - os.Exit(1) - } -} - -func parseGoCode(source string) (*ParseResult, error) { - fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "", source, parser.ParseComments) - if err != nil { - return nil, err - } - - result := &ParseResult{ - Classes: []ClassInfo{}, - Functions: []SignatureInfo{}, - } - - // Extract function declarations - ast.Inspect(file, func(n ast.Node) bool { - switch node := n.(type) { - case *ast.FuncDecl: - pos := fset.Position(node.Pos()) - - // Build function signature - var signature string - if node.Recv != nil { - // Method with receiver - signature = buildMethodSignature(node) - } else { - // Regular function - signature = buildFunctionSignature(node) - } - - result.Functions = append(result.Functions, SignatureInfo{ - Signature: signature, - Line: pos.Line, - }) - - case *ast.TypeSpec: - // Handle struct and interface types - pos := fset.Position(node.Pos()) - - switch typeNode := node.Type.(type) { - case *ast.StructType: - // For structs, we don't collect methods here since they're defined separately - // We could collect them if needed, but Go methods are typically defined outside the struct - _ = typeNode - case *ast.InterfaceType: - // For interfaces, collect method signatures - methods := []SignatureInfo{} - for _, method := range typeNode.Methods.List { - if len(method.Names) > 0 { - methodPos := fset.Position(method.Pos()) - methodSig := buildInterfaceMethodSignature(method) - methods = append(methods, SignatureInfo{ - Signature: methodSig, - Line: methodPos.Line, - }) - } - } - - result.Classes = append(result.Classes, ClassInfo{ - Name: node.Name.Name, - Line: pos.Line, - Methods: methods, - InnerClasses: []ClassInfo{}, // Go doesn't have nested types in the same way - }) - } - } - return true - }) - - return result, nil -} - -func buildFunctionSignature(fn *ast.FuncDecl) string { - signature := fmt.Sprintf("func %s", fn.Name.Name) - signature += buildParameterList(fn.Type.Params) - - if fn.Type.Results != nil { - signature += " " + buildResultList(fn.Type.Results) - } - - return signature -} - -func buildMethodSignature(fn *ast.FuncDecl) string { - signature := "func " - - // Add receiver - if fn.Recv != nil && len(fn.Recv.List) > 0 { - signature += buildParameterList(fn.Recv) - } - - signature += fmt.Sprintf(" %s", fn.Name.Name) - signature += buildParameterList(fn.Type.Params) - - if fn.Type.Results != nil { - signature += " " + buildResultList(fn.Type.Results) - } - - return signature -} - -func buildInterfaceMethodSignature(method *ast.Field) string { - if len(method.Names) == 0 { - return "" - } - - signature := method.Names[0].Name - - if funcType, ok := method.Type.(*ast.FuncType); ok { - signature += buildParameterList(funcType.Params) - if funcType.Results != nil { - signature += " " + buildResultList(funcType.Results) - } - } - - return signature -} - -func buildParameterList(params *ast.FieldList) string { - if params == nil || len(params.List) == 0 { - return "()" - } - - signature := "(" - paramStrs := []string{} - - for _, param := range params.List { - paramType := getTypeString(param.Type) - - if len(param.Names) == 0 { - // Unnamed parameter - paramStrs = append(paramStrs, paramType) - } else { - // Named parameters - for _, name := range param.Names { - paramStrs = append(paramStrs, fmt.Sprintf("%s %s", name.Name, paramType)) - } - } - } - - signature += joinStrings(paramStrs, ", ") - signature += ")" - - return signature -} - -func buildResultList(results *ast.FieldList) string { - if results == nil || len(results.List) == 0 { - return "" - } - - resultStrs := []string{} - - for _, result := range results.List { - resultType := getTypeString(result.Type) - - if len(result.Names) == 0 { - resultStrs = append(resultStrs, resultType) - } else { - for _, name := range result.Names { - resultStrs = append(resultStrs, fmt.Sprintf("%s %s", name.Name, resultType)) - } - } - } - - if len(resultStrs) == 1 { - return resultStrs[0] - } - - return "(" + joinStrings(resultStrs, ", ") + ")" -} - -func getTypeString(expr ast.Expr) string { - switch t := expr.(type) { - case *ast.Ident: - return t.Name - case *ast.StarExpr: - return "*" + getTypeString(t.X) - case *ast.ArrayType: - if t.Len == nil { - return "[]" + getTypeString(t.Elt) - } - return fmt.Sprintf("[%s]%s", getExprString(t.Len), getTypeString(t.Elt)) - case *ast.SelectorExpr: - return getTypeString(t.X) + "." + t.Sel.Name - case *ast.MapType: - return fmt.Sprintf("map[%s]%s", getTypeString(t.Key), getTypeString(t.Value)) - case *ast.ChanType: - direction := "" - switch t.Dir { - case ast.SEND: - direction = "chan<- " - case ast.RECV: - direction = "<-chan " - default: - direction = "chan " - } - return direction + getTypeString(t.Value) - case *ast.FuncType: - sig := "func" + buildParameterList(t.Params) - if t.Results != nil { - sig += " " + buildResultList(t.Results) - } - return sig - case *ast.InterfaceType: - return "interface{}" - case *ast.StructType: - return "struct{}" - default: - return "unknown" - } -} - -func getExprString(expr ast.Expr) string { - switch e := expr.(type) { - case *ast.BasicLit: - return e.Value - case *ast.Ident: - return e.Name - default: - return "..." - } -} - -func joinStrings(strs []string, sep string) string { - if len(strs) == 0 { - return "" - } - if len(strs) == 1 { - return strs[0] - } - - result := strs[0] - for i := 1; i < len(strs); i++ { - result += sep + strs[i] - } - return result -} \ No newline at end of file +/* + * --- AUTO-GENERATED DOCSTRING --- + * This docstring is automatically generated by Agent Docstrings v1.3.0 + * Do not modify this block directly. + * + * Classes/Functions: + * - func main() (line 50) + * - func parseGoCode(source string) (*ParseResult, error) (line 71) + * - func buildFunctionSignature(fn *ast.FuncDecl) string (line 141) + * - func buildMethodSignature(fn *ast.FuncDecl) string (line 152) + * - func buildInterfaceMethodSignature(method *ast.Field) string (line 170) + * - func buildParameterList(params *ast.FieldList) string (line 187) + * - func buildResultList(results *ast.FieldList) string (line 215) + * - func getTypeString(expr ast.Expr) string (line 241) + * - func getExprString(expr ast.Expr) string (line 282) + * - func joinStrings(strs []string, sep string) string (line 293) + * --- END AUTO-GENERATED DOCSTRING --- + */ +package main + +import ( + "encoding/json" + "fmt" + "go/ast" + "go/parser" + "go/token" + "io" + "os" +) + +// SignatureInfo represents a function or method signature with line number +type SignatureInfo struct { + Signature string `json:"signature"` + Line int `json:"line"` +} + +// ClassInfo represents a struct or interface with its methods +type ClassInfo struct { + Name string `json:"name"` + Line int `json:"line"` + Methods []SignatureInfo `json:"methods"` + InnerClasses []ClassInfo `json:"inner_classes"` +} + +// ParseResult contains the parsing results +type ParseResult struct { + Classes []ClassInfo `json:"classes"` + Functions []SignatureInfo `json:"functions"` +} + +func main() { + // Read Go source code from stdin + input, err := io.ReadAll(os.Stdin) + if err != nil { + fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err) + os.Exit(1) + } + + result, err := parseGoCode(string(input)) + if err != nil { + fmt.Fprintf(os.Stderr, "Error parsing Go code: %v\n", err) + os.Exit(1) + } + + // Output JSON result + if err := json.NewEncoder(os.Stdout).Encode(result); err != nil { + fmt.Fprintf(os.Stderr, "Error encoding JSON: %v\n", err) + os.Exit(1) + } +} + +func parseGoCode(source string) (*ParseResult, error) { + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, "", source, parser.ParseComments) + if err != nil { + return nil, err + } + + result := &ParseResult{ + Classes: []ClassInfo{}, + Functions: []SignatureInfo{}, + } + + // Extract function declarations + ast.Inspect(file, func(n ast.Node) bool { + switch node := n.(type) { + case *ast.FuncDecl: + pos := fset.Position(node.Pos()) + + // Build function signature + var signature string + if node.Recv != nil { + // Method with receiver + signature = buildMethodSignature(node) + } else { + // Regular function + signature = buildFunctionSignature(node) + } + + result.Functions = append(result.Functions, SignatureInfo{ + Signature: signature, + Line: pos.Line, + }) + + case *ast.TypeSpec: + // Handle struct and interface types + pos := fset.Position(node.Pos()) + + switch typeNode := node.Type.(type) { + case *ast.StructType: + // For structs, we don't collect methods here since they're defined separately + // We could collect them if needed, but Go methods are typically defined outside the struct + _ = typeNode + case *ast.InterfaceType: + // For interfaces, collect method signatures + methods := []SignatureInfo{} + for _, method := range typeNode.Methods.List { + if len(method.Names) > 0 { + methodPos := fset.Position(method.Pos()) + methodSig := buildInterfaceMethodSignature(method) + methods = append(methods, SignatureInfo{ + Signature: methodSig, + Line: methodPos.Line, + }) + } + } + + result.Classes = append(result.Classes, ClassInfo{ + Name: node.Name.Name, + Line: pos.Line, + Methods: methods, + InnerClasses: []ClassInfo{}, // Go doesn't have nested types in the same way + }) + } + } + return true + }) + + return result, nil +} + +func buildFunctionSignature(fn *ast.FuncDecl) string { + signature := fmt.Sprintf("func %s", fn.Name.Name) + signature += buildParameterList(fn.Type.Params) + + if fn.Type.Results != nil { + signature += " " + buildResultList(fn.Type.Results) + } + + return signature +} + +func buildMethodSignature(fn *ast.FuncDecl) string { + signature := "func " + + // Add receiver + if fn.Recv != nil && len(fn.Recv.List) > 0 { + signature += buildParameterList(fn.Recv) + } + + signature += fmt.Sprintf(" %s", fn.Name.Name) + signature += buildParameterList(fn.Type.Params) + + if fn.Type.Results != nil { + signature += " " + buildResultList(fn.Type.Results) + } + + return signature +} + +func buildInterfaceMethodSignature(method *ast.Field) string { + if len(method.Names) == 0 { + return "" + } + + signature := method.Names[0].Name + + if funcType, ok := method.Type.(*ast.FuncType); ok { + signature += buildParameterList(funcType.Params) + if funcType.Results != nil { + signature += " " + buildResultList(funcType.Results) + } + } + + return signature +} + +func buildParameterList(params *ast.FieldList) string { + if params == nil || len(params.List) == 0 { + return "()" + } + + signature := "(" + paramStrs := []string{} + + for _, param := range params.List { + paramType := getTypeString(param.Type) + + if len(param.Names) == 0 { + // Unnamed parameter + paramStrs = append(paramStrs, paramType) + } else { + // Named parameters + for _, name := range param.Names { + paramStrs = append(paramStrs, fmt.Sprintf("%s %s", name.Name, paramType)) + } + } + } + + signature += joinStrings(paramStrs, ", ") + signature += ")" + + return signature +} + +func buildResultList(results *ast.FieldList) string { + if results == nil || len(results.List) == 0 { + return "" + } + + resultStrs := []string{} + + for _, result := range results.List { + resultType := getTypeString(result.Type) + + if len(result.Names) == 0 { + resultStrs = append(resultStrs, resultType) + } else { + for _, name := range result.Names { + resultStrs = append(resultStrs, fmt.Sprintf("%s %s", name.Name, resultType)) + } + } + } + + if len(resultStrs) == 1 { + return resultStrs[0] + } + + return "(" + joinStrings(resultStrs, ", ") + ")" +} + +func getTypeString(expr ast.Expr) string { + switch t := expr.(type) { + case *ast.Ident: + return t.Name + case *ast.StarExpr: + return "*" + getTypeString(t.X) + case *ast.ArrayType: + if t.Len == nil { + return "[]" + getTypeString(t.Elt) + } + return fmt.Sprintf("[%s]%s", getExprString(t.Len), getTypeString(t.Elt)) + case *ast.SelectorExpr: + return getTypeString(t.X) + "." + t.Sel.Name + case *ast.MapType: + return fmt.Sprintf("map[%s]%s", getTypeString(t.Key), getTypeString(t.Value)) + case *ast.ChanType: + direction := "" + switch t.Dir { + case ast.SEND: + direction = "chan<- " + case ast.RECV: + direction = "<-chan " + default: + direction = "chan " + } + return direction + getTypeString(t.Value) + case *ast.FuncType: + sig := "func" + buildParameterList(t.Params) + if t.Results != nil { + sig += " " + buildResultList(t.Results) + } + return sig + case *ast.InterfaceType: + return "interface{}" + case *ast.StructType: + return "struct{}" + default: + return "unknown" + } +} + +func getExprString(expr ast.Expr) string { + switch e := expr.(type) { + case *ast.BasicLit: + return e.Value + case *ast.Ident: + return e.Name + default: + return "..." + } +} + +func joinStrings(strs []string, sep string) string { + if len(strs) == 0 { + return "" + } + if len(strs) == 1 { + return strs[0] + } + + result := strs[0] + for i := 1; i < len(strs); i++ { + result += sep + strs[i] + } + return result +} \ No newline at end of file diff --git a/agent_docstrings/languages/common.py b/agent_docstrings/languages/common.py index 3f43d06..ef987a2 100644 --- a/agent_docstrings/languages/common.py +++ b/agent_docstrings/languages/common.py @@ -1,14 +1,12 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - SignatureInfo (line 22): - - ClassInfo (line 28): - - CommentStyle (line 36): - - Functions: - - remove_agent_docstring(text: str, language: str) -> str (line 58) + - SignatureInfo (line 20): + - ClassInfo (line 26): + - CommentStyle (line 34): + - remove_agent_docstring(text: str, language: str) -> str (line 57) --- END AUTO-GENERATED DOCSTRING --- """ from __future__ import annotations @@ -34,24 +32,25 @@ class ClassInfo(NamedTuple): class CommentStyle(NamedTuple): - """Stores language-specific comment delimiters and formatting.""" + """Stores language-specific comment formatting information.""" start: str end: str - prefix: str # e.g., ' * ' or ' ' + prefix: str + indent: str COMMENT_STYLES: Dict[str, CommentStyle] = { - "python": CommentStyle('"""', '"""', " "), - "kotlin": CommentStyle('/**', ' */', ' * '), - "javascript": CommentStyle('/**', ' */', ' * '), - "typescript": CommentStyle('/**', ' */', ' * '), - "csharp": CommentStyle('/*', ' */', ' * '), - "cpp": CommentStyle('/*', ' */', ' * '), - "c": CommentStyle('/*', ' */', ' * '), - "java": CommentStyle('/**', ' */', ' * '), - "go": CommentStyle('/*', ' */', ' * '), - "powershell": CommentStyle('<#', '#>', ' # '), - "delphi": CommentStyle('(*', '*)', ' * '), + "python": CommentStyle('"""', '"""', " ", " "), + "kotlin": CommentStyle('/**', ' */', ' * ', " "), + "javascript": CommentStyle('/**', ' */', ' * ', " "), + "typescript": CommentStyle('/**', ' */', ' * ', " "), + "csharp": CommentStyle('/*', ' */', ' * ', " "), + "cpp": CommentStyle('/*', ' */', ' * ', " "), + "c": CommentStyle('/*', ' */', ' * ', " "), + "java": CommentStyle('/**', ' */', ' * ', " "), + "go": CommentStyle('/*', ' */', ' * ', "\t"), + "powershell": CommentStyle('<#', '#>', ' # ', " "), + "delphi": CommentStyle('(*', '*)', ' * ', " "), } diff --git a/agent_docstrings/languages/delphi.py b/agent_docstrings/languages/delphi.py index 230fd45..11c27b9 100644 --- a/agent_docstrings/languages/delphi.py +++ b/agent_docstrings/languages/delphi.py @@ -1,11 +1,9 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - Functions: - - parse_delphi_file(lines: List[str]) -> tuple[List[ClassInfo], List[SignatureInfo]] (line 30) + - parse_delphi_file(lines: List[str]) -> tuple[List[ClassInfo], List[SignatureInfo]] (line 28) --- END AUTO-GENERATED DOCSTRING --- """ from __future__ import annotations diff --git a/agent_docstrings/languages/generic.py b/agent_docstrings/languages/generic.py index 5de6f19..8e0ab92 100644 --- a/agent_docstrings/languages/generic.py +++ b/agent_docstrings/languages/generic.py @@ -1,11 +1,9 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - Functions: - - parse_generic_file(lines: List[str], lang: str) -> tuple[List[ClassInfo], List[SignatureInfo]] (line 38) + - parse_generic_file(lines: List[str], lang: str) -> tuple[List[ClassInfo], List[SignatureInfo]] (line 36) --- END AUTO-GENERATED DOCSTRING --- """ from __future__ import annotations diff --git a/agent_docstrings/languages/go.py b/agent_docstrings/languages/go.py index dcbe2a7..1202aa5 100644 --- a/agent_docstrings/languages/go.py +++ b/agent_docstrings/languages/go.py @@ -1,14 +1,12 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - Functions: - - _get_go_parser_path() -> Path (line 27) - - _parse_with_go_ast(source_code: str) -> Tuple[List[ClassInfo], List[SignatureInfo]] (line 62) - - parse_go_file(lines: List[str]) -> tuple[List[ClassInfo], List[SignatureInfo]] (line 120) - - _parse_with_regex(lines: List[str]) -> Tuple[List[ClassInfo], List[SignatureInfo]] (line 145) + - _get_go_parser_path() -> Path (line 25) + - _parse_with_go_ast(source_code: str) -> Tuple[List[ClassInfo], List[SignatureInfo]] (line 60) + - parse_go_file(lines: List[str]) -> tuple[List[ClassInfo], List[SignatureInfo]] (line 118) + - _parse_with_regex(lines: List[str]) -> Tuple[List[ClassInfo], List[SignatureInfo]] (line 143) --- END AUTO-GENERATED DOCSTRING --- """ from __future__ import annotations diff --git a/agent_docstrings/languages/java.py b/agent_docstrings/languages/java.py index 760d288..f76b6eb 100644 --- a/agent_docstrings/languages/java.py +++ b/agent_docstrings/languages/java.py @@ -1,11 +1,9 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - Functions: - - parse_java_file(lines: List[str]) -> tuple[List[ClassInfo], List[SignatureInfo]] (line 25) + - parse_java_file(lines: List[str]) -> tuple[List[ClassInfo], List[SignatureInfo]] (line 23) --- END AUTO-GENERATED DOCSTRING --- """ from __future__ import annotations diff --git a/agent_docstrings/languages/kotlin.py b/agent_docstrings/languages/kotlin.py index 7ac465c..71f857c 100644 --- a/agent_docstrings/languages/kotlin.py +++ b/agent_docstrings/languages/kotlin.py @@ -1,11 +1,9 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - Functions: - - parse_kotlin_file(lines: List[str]) -> tuple[List[ClassInfo], List[SignatureInfo]] (line 22) + - parse_kotlin_file(lines: List[str]) -> tuple[List[ClassInfo], List[SignatureInfo]] (line 20) --- END AUTO-GENERATED DOCSTRING --- """ from __future__ import annotations diff --git a/agent_docstrings/languages/powershell.py b/agent_docstrings/languages/powershell.py index d7206fb..fe3243d 100644 --- a/agent_docstrings/languages/powershell.py +++ b/agent_docstrings/languages/powershell.py @@ -1,11 +1,9 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - Functions: - - parse_powershell_file(lines: List[str]) -> tuple[List[ClassInfo], List[SignatureInfo]] (line 25) + - parse_powershell_file(lines: List[str]) -> tuple[List[ClassInfo], List[SignatureInfo]] (line 23) --- END AUTO-GENERATED DOCSTRING --- """ from __future__ import annotations diff --git a/agent_docstrings/languages/python.py b/agent_docstrings/languages/python.py index 0fa3d6c..2c446aa 100644 --- a/agent_docstrings/languages/python.py +++ b/agent_docstrings/languages/python.py @@ -1,14 +1,12 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - Functions: - - _format_arg(arg: ast.arg) -> str (line 22) - - _format_signature(node: Union[ast.FunctionDef, ast.AsyncFunctionDef]) -> str (line 30) - - parse_python_file(lines: List[str]) -> Tuple[List[ClassInfo], List[SignatureInfo]] (line 97) - - _parse_class_node(node: ast.ClassDef) -> ClassInfo (line 138) + - _format_arg(arg: ast.arg) -> str (line 20) + - _format_signature(node: Union[ast.FunctionDef, ast.AsyncFunctionDef]) -> str (line 28) + - parse_python_file(lines: List[str]) -> Tuple[List[ClassInfo], List[SignatureInfo]] (line 95) + - _parse_class_node(node: ast.ClassDef) -> ClassInfo (line 136) --- END AUTO-GENERATED DOCSTRING --- """ from __future__ import annotations diff --git a/pyproject.toml b/pyproject.toml index ba31b7a..4569ca3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "agent-docstrings" -version = "1.2.1" +version = "1.3.0" description = "A command-line tool to auto-generate and update file-level docstrings summarizing classes and functions. Useful for maintaining a high-level overview of your files, especially in projects with code generated or modified by AI assistants." readme = { file = "README.md", content-type = "text/markdown" } license = { file = "LICENSE" } @@ -14,7 +14,33 @@ authors = [ maintainers = [ { name = "Artemonim", email = "Artemonim@yandex.ru" } ] -keywords = ["docstrings", "documentation", "ai", "python", "java", "kotlin", "go", "powershell", "delphi", "code-analysis"] +keywords = [ + "docstrings", + "documentation", + "ai", + "developer-tools", + "automation", + "code-generator", + "maintainability", + "code-quality", + "static-analysis", + "pre-commit", + "linter", + "summarize-code", + "code-navigation", + "refactoring", + "python", + "java", + "kotlin", + "go", + "powershell", + "delphi", + "typescript", + "javascript", + "csharp", + "c++", + "c" +] classifiers = [ "Development Status :: 4 - Beta", "Environment :: Console", @@ -22,8 +48,6 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -33,7 +57,7 @@ classifiers = [ "Topic :: Text Processing :: General", "Typing :: Typed", ] -requires-python = ">=3.8" +requires-python = ">=3.10" dependencies = [] [project.optional-dependencies] @@ -47,11 +71,11 @@ dev = [ ] [project.urls] -"Homepage" = "https://github.com/Artemonim/agent-docstrings" -"Source" = "https://github.com/Artemonim/agent-docstrings" -"Tracker" = "https://github.com/Artemonim/agent-docstrings/issues" -"Documentation" = "https://github.com/Artemonim/agent-docstrings#readme" -"Changelog" = "https://github.com/Artemonim/agent-docstrings/blob/main/CHANGELOG.md" +"Homepage" = "https://github.com/Artemonim/AgentDocstrings" +"Source" = "https://github.com/Artemonim/AgentDocstrings" +"Tracker" = "https://github.com/Artemonim/AgentDocstrings/issues" +"Documentation" = "https://github.com/Artemonim/AgentDocstrings#readme" +"Changelog" = "https://github.com/Artemonim/AgentDocstrings/blob/master/CHANGELOG.md" [project.scripts] agent-docstrings = "agent_docstrings.cli:main" @@ -66,7 +90,7 @@ agent_docstrings = ["py.typed", "bin/*"] [tool.black] line-length = 88 -target-version = ['py38', 'py39', 'py310', 'py311', 'py312'] +target-version = ['py310', 'py311', 'py312', 'py313'] include = '\.pyi?$' extend-exclude = ''' /( @@ -83,7 +107,7 @@ extend-exclude = ''' ''' [tool.mypy] -python_version = "3.8" +python_version = "3.10" warn_return_any = true warn_unused_configs = true disallow_untyped_defs = true @@ -124,7 +148,7 @@ exclude_lines = [ ] [tool.bumpversion] -current_version = "1.2.1" +current_version = "1.3.0" commit = false tag = false diff --git a/tests/conftest.py b/tests/conftest.py index e93afa2..e8a3d11 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,24 +1,22 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - Functions: - - source_processor(tmp_path: Path) (line 36) - - fixtures_dir() -> Path (line 64) - - sample_python_file(tmp_path: Path) -> Iterator[Path] (line 70) - - sample_kotlin_file(tmp_path: Path) -> Iterator[Path] (line 92) - - sample_javascript_file(tmp_path: Path) -> Iterator[Path] (line 118) - - sample_typescript_file(tmp_path: Path) -> Iterator[Path] (line 151) - - sample_csharp_file(tmp_path: Path) -> Iterator[Path] (line 186) - - sample_cpp_file(tmp_path: Path) -> Iterator[Path] (line 229) - - complex_python_file(tmp_path: Path) -> Iterator[Path] (line 280) - - python_file_with_existing_header(tmp_path: Path) -> Iterator[Path] (line 388) - - multilanguage_project(tmp_path: Path) -> Iterator[Path] (line 421) - - empty_files_project(tmp_path: Path) -> Iterator[Path] (line 458) - - sample_files_by_language(tmp_path: Path) -> Iterator[Dict[str, Path]] (line 478) - - malformed_files_project(tmp_path: Path) -> Iterator[Path] (line 510) + - source_processor(tmp_path: Path) (line 34) + - fixtures_dir() -> Path (line 62) + - sample_python_file(tmp_path: Path) -> Iterator[Path] (line 68) + - sample_kotlin_file(tmp_path: Path) -> Iterator[Path] (line 90) + - sample_javascript_file(tmp_path: Path) -> Iterator[Path] (line 116) + - sample_typescript_file(tmp_path: Path) -> Iterator[Path] (line 149) + - sample_csharp_file(tmp_path: Path) -> Iterator[Path] (line 184) + - sample_cpp_file(tmp_path: Path) -> Iterator[Path] (line 227) + - complex_python_file(tmp_path: Path) -> Iterator[Path] (line 278) + - python_file_with_existing_header(tmp_path: Path) -> Iterator[Path] (line 386) + - multilanguage_project(tmp_path: Path) -> Iterator[Path] (line 419) + - empty_files_project(tmp_path: Path) -> Iterator[Path] (line 456) + - sample_files_by_language(tmp_path: Path) -> Iterator[Dict[str, Path]] (line 476) + - malformed_files_project(tmp_path: Path) -> Iterator[Path] (line 508) --- END AUTO-GENERATED DOCSTRING --- """ from __future__ import annotations diff --git a/tests/test_ast_integration.py b/tests/test_ast_integration.py index aeae975..cf3dcc0 100644 --- a/tests/test_ast_integration.py +++ b/tests/test_ast_integration.py @@ -1,17 +1,16 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - TestPythonASTIntegration (line 25): - - test_python_ast_integration_complex(source_processor) -> None (line 26) - - test_python_ast_integration_async_functions(source_processor) -> None (line 67) - - test_python_ast_integration_nested_classes(source_processor) -> None (line 90) - - TestGoASTIntegration (line 126): - - test_go_ast_integration_interfaces(source_processor) -> None (line 127) - - test_go_ast_integration_methods(source_processor) -> None (line 173) - - test_go_ast_fallback_integration(source_processor) -> None (line 218) + - TestPythonASTIntegration (line 24): + - test_python_ast_integration_complex(source_processor) -> None (line 25) + - test_python_ast_integration_async_functions(source_processor) -> None (line 66) + - test_python_ast_integration_nested_classes(source_processor) -> None (line 89) + - TestGoASTIntegration (line 125): + - test_go_ast_integration_interfaces(source_processor) -> None (line 126) + - test_go_ast_integration_methods(source_processor) -> None (line 172) + - test_go_ast_fallback_integration(source_processor) -> None (line 217) --- END AUTO-GENERATED DOCSTRING --- """ from __future__ import annotations diff --git a/tests/test_ast_parsers.py b/tests/test_ast_parsers.py index 1104000..5d164e1 100644 --- a/tests/test_ast_parsers.py +++ b/tests/test_ast_parsers.py @@ -2,23 +2,22 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - TestPythonASTParser (line 31): - - test_async_functions() -> None (line 32) - - test_class_with_nested_class() -> None (line 52) - - test_complex_type_annotations() -> None (line 82) - - test_decorators_and_properties() -> None (line 100) - - test_edge_cases() -> None (line 127) - - test_function_with_all_argument_types() -> None (line 159) - - test_malformed_syntax_fallback() -> None (line 179) - - TestGoASTParser (line 193): - - test_go_interface_parsing() -> None (line 194) - - test_go_methods_on_structs() -> None (line 220) - - test_go_complex_signatures() -> None (line 252) - - test_go_empty_functions() -> None (line 282) + - TestPythonASTParser (line 30): + - test_async_functions() -> None (line 31) + - test_class_with_nested_class() -> None (line 51) + - test_complex_type_annotations() -> None (line 81) + - test_decorators_and_properties() -> None (line 99) + - test_edge_cases() -> None (line 126) + - test_function_with_all_argument_types() -> None (line 158) + - test_malformed_syntax_fallback() -> None (line 178) + - TestGoASTParser (line 192): + - test_go_interface_parsing() -> None (line 193) + - test_go_methods_on_structs() -> None (line 219) + - test_go_complex_signatures() -> None (line 251) + - test_go_empty_functions() -> None (line 281) --- END AUTO-GENERATED DOCSTRING --- """ import pytest diff --git a/tests/test_cli.py b/tests/test_cli.py index ada5ce8..23c204d 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,36 +1,39 @@ from __future__ import annotations """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - TestCLIBasicFunctionality (line 44): - - test_cli_processes_directory(sample_python_file: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 47) - - test_cli_processes_multiple_directories(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 62) - - test_cli_verbose_mode(sample_python_file: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 84) - - test_cli_verbose_short_flag(sample_python_file: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 95) - - TestCLIArgumentParsing (line 105): - - test_help_message(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 108) - - test_no_arguments_provided(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 124) - - test_invalid_argument(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 137) - - TestCLIErrorHandling (line 151): - - test_nonexistent_directory(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 154) - - test_file_instead_of_directory(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 170) - - test_mixed_valid_invalid_directories(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 186) - - test_permission_denied_directory(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 205) - - test_empty_directory(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 226) - - TestCLIIntegration (line 239): - - test_cli_calls_core_function(mock_discover: MagicMock, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 243) - - test_cli_calls_core_function_verbose(mock_discover: MagicMock, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 256) - - test_cli_calls_core_with_multiple_dirs(mock_discover: MagicMock, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 269) - - test_full_integration_workflow(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 283) - - TestCLIEdgeCases (line 347): - - test_very_long_directory_paths(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 350) - - test_unicode_directory_names(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 365) - - test_special_characters_in_paths(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 381) - - test_relative_vs_absolute_paths(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 393) - - test_cli_with_current_directory(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 416) + - TestCLIBasicFunctionality (line 47): + - test_cli_processes_directory(sample_python_file: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 50) + - test_cli_processes_multiple_directories(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 65) + - test_cli_verbose_mode(sample_python_file: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 87) + - test_cli_verbose_short_flag(sample_python_file: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 98) + - TestCLIArgumentParsing (line 108): + - test_help_message(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 111) + - test_no_arguments_provided(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 127) + - test_invalid_argument(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 140) + - TestCLIErrorHandling (line 154): + - test_nonexistent_directory(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 157) + - test_process_single_file(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 173) + - test_process_mixed_file_and_directory(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 184) + - test_mixed_valid_invalid_paths(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 203) + - test_permission_denied_directory(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 224) + - test_empty_directory(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 245) + - TestCLIIntegration (line 258): + - test_cli_calls_core_function(mock_discover: MagicMock, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 262) + - test_cli_calls_core_function_verbose(mock_discover: MagicMock, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 275) + - test_cli_calls_core_with_multiple_dirs(mock_discover: MagicMock, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 288) + - test_full_integration_workflow(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None (line 302) + - test_ignore_file_option(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 365) + - test_include_file_option(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 379) + - test_cli_idempotent(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 393) + - TestCLIEdgeCases (line 408): + - test_very_long_directory_paths(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 411) + - test_unicode_directory_names(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 426) + - test_special_characters_in_paths(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 442) + - test_relative_vs_absolute_paths(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 454) + - test_cli_with_current_directory(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None (line 477) --- END AUTO-GENERATED DOCSTRING --- """ import sys @@ -118,7 +121,7 @@ def test_help_message(self, monkeypatch: pytest.MonkeyPatch, capsys: pytest.Capt captured = capsys.readouterr() assert "Generate file-level docstrings" in captured.out - assert "DIRECTORY" in captured.out + assert "PATH" in captured.out assert "--verbose" in captured.out @@ -165,43 +168,59 @@ def test_nonexistent_directory(self, monkeypatch: pytest.MonkeyPatch, capsys: py captured = capsys.readouterr() assert "Error:" in captured.err - assert "Directory not found" in captured.err + assert "Path not found at" in captured.err assert nonexistent_path in captured.err - def test_file_instead_of_directory(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: - """Test handling when a file is passed instead of directory.""" + def test_process_single_file(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Test that the CLI can process a single file argument.""" test_file = tmp_path / "test.py" test_file.write_text("def test(): pass") - + monkeypatch.setattr(sys, "argv", ["agent-docstrings", str(test_file)]) - - with pytest.raises(SystemExit) as exc_info: - cli.main() - - assert exc_info.value.code == 1 - - captured = capsys.readouterr() - assert "Error:" in captured.err - assert "Directory not found" in captured.err - def test_mixed_valid_invalid_directories(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: - """Test handling of mix of valid and invalid directories.""" + cli.main() + + assert "Classes/Functions:" in test_file.read_text() + + def test_process_mixed_file_and_directory(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Test that the CLI can process a mix of files and directories.""" + src_dir = tmp_path / "src" + src_dir.mkdir() + dir_file = src_dir / "dir_file.py" + dir_file.write_text("def dir_func(): pass") + + single_file = tmp_path / "single_file.py" + single_file.write_text("def single_func(): pass") + + monkeypatch.setattr( + sys, "argv", ["agent-docstrings", str(src_dir), str(single_file)] + ) + + cli.main() + + assert "Classes/Functions:" in dir_file.read_text() + assert "Classes/Functions:" in single_file.read_text() + + def test_mixed_valid_invalid_paths(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: + """Test handling of mix of valid and invalid paths.""" valid_dir = tmp_path / "valid" valid_dir.mkdir() (valid_dir / "test.py").write_text("def test(): pass") - invalid_dir = "/invalid/path" + invalid_path = "/invalid/path" - monkeypatch.setattr(sys, "argv", ["agent-docstrings", str(valid_dir), invalid_dir]) + monkeypatch.setattr(sys, "argv", ["agent-docstrings", str(valid_dir), invalid_path]) with pytest.raises(SystemExit) as exc_info: cli.main() - # * Should exit on first invalid directory + # * Should exit on first invalid path assert exc_info.value.code == 1 captured = capsys.readouterr() assert "Error:" in captured.err + assert "Path not found at" in captured.err + assert invalid_path in captured.err def test_permission_denied_directory(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: """Test handling of directories with permission issues.""" @@ -251,7 +270,7 @@ def test_cli_calls_core_function(self, mock_discover: MagicMock, tmp_path: Path, cli.main() # * Verify that the core function was called with correct arguments - mock_discover.assert_called_once_with([str(test_dir)], False) + mock_discover.assert_called_once_with([str(test_dir)], False, False) @patch('agent_docstrings.core.discover_and_process_files') def test_cli_calls_core_function_verbose(self, mock_discover: MagicMock, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: @@ -264,7 +283,7 @@ def test_cli_calls_core_function_verbose(self, mock_discover: MagicMock, tmp_pat cli.main() # * Verify that verbose=True was passed - mock_discover.assert_called_once_with([str(test_dir)], True) + mock_discover.assert_called_once_with([str(test_dir)], True, False) @patch('agent_docstrings.core.discover_and_process_files') def test_cli_calls_core_with_multiple_dirs(self, mock_discover: MagicMock, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: @@ -279,7 +298,7 @@ def test_cli_calls_core_with_multiple_dirs(self, mock_discover: MagicMock, tmp_p cli.main() # * Verify that both directories were passed - mock_discover.assert_called_once_with([str(dir1), str(dir2)], False) + mock_discover.assert_called_once_with([str(dir1), str(dir2)], False, False) def test_full_integration_workflow(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: """Test complete workflow from CLI to file processing.""" @@ -344,6 +363,48 @@ def test_run(self): assert "Classes/Functions:" in test_content assert "TestApplication" in test_content + def test_ignore_file_option(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Test that .agent-docstrings-ignore excludes specified files.""" + project = tmp_path / "project" + project.mkdir() + allowed = project / "allowed.py" + ignored = project / "ignored.py" + allowed.write_text("def foo(): pass") + ignored.write_text("def bar(): pass") + (project / ".agent-docstrings-ignore").write_text("ignored.py") + monkeypatch.setattr(sys, "argv", ["agent-docstrings", str(project)]) + cli.main() + assert "Classes/Functions:" in allowed.read_text() + assert ignored.read_text().strip() == "def bar(): pass" + + def test_include_file_option(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Test that .agent-docstrings-include includes only specified files.""" + project = tmp_path / "project2" + project.mkdir() + allowed = project / "allowed2.py" + ignored = project / "ignored2.py" + allowed.write_text("def foo2(): pass") + ignored.write_text("def bar2(): pass") + (project / ".agent-docstrings-include").write_text("allowed2.py") + monkeypatch.setattr(sys, "argv", ["agent-docstrings", str(project)]) + cli.main() + assert "Classes/Functions:" in allowed.read_text() + assert ignored.read_text().strip() == "def bar2(): pass" + + def test_cli_idempotent(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Test that running CLI twice does not change files after first run.""" + project = tmp_path / "proj" + project.mkdir() + file = project / "one.py" + file.write_text("def foo(): pass") + monkeypatch.setattr(sys, "argv", ["agent-docstrings", str(project)]) + cli.main() + first = file.read_text() + monkeypatch.setattr(sys, "argv", ["agent-docstrings", str(project)]) + cli.main() + second = file.read_text() + assert first == second + class TestCLIEdgeCases: """Tests for CLI edge cases and unusual scenarios.""" diff --git a/tests/test_common.py b/tests/test_common.py index 3e2b3d1..716a221 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -2,28 +2,27 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - TestDataClasses (line 44): - - test_signature_info_creation() -> None (line 47) - - test_class_info_creation() -> None (line 53) - - test_comment_style_creation() -> None (line 72) - - TestCommentStyles (line 80): - - test_all_supported_languages_have_styles() -> None (line 83) - - test_comment_style_values(language: str, expected_start: str, expected_end: str, expected_prefix: str) -> None (line 99) - - TestHeaderStripping (line 113): - - test_strip_python_header() -> None (line 116) - - test_strip_block_comment_header() -> None (line 141) - - test_strip_c_style_comment_header() -> None (line 159) - - test_no_header_to_strip() -> None (line 177) - - test_preserve_shebang_when_stripping() -> None (line 186) - - test_strip_header_with_various_whitespace() -> None (line 199) - - test_strip_only_first_matching_header() -> None (line 207) - - test_strip_header_edge_cases() -> None (line 223) - - test_header_not_at_start() -> None (line 235) - - test_invalid_language_patterns(language: str) -> None (line 249) + - TestDataClasses (line 43): + - test_signature_info_creation() -> None (line 46) + - test_class_info_creation() -> None (line 52) + - test_comment_style_creation() -> None (line 71) + - TestCommentStyles (line 80): + - test_all_supported_languages_have_styles() -> None (line 83) + - test_comment_style_values(language: str, expected_start: str, expected_end: str, expected_prefix: str, expected_indent: str) -> None (line 100) + - TestHeaderStripping (line 116): + - test_strip_python_header() -> None (line 119) + - test_strip_block_comment_header() -> None (line 144) + - test_strip_c_style_comment_header() -> None (line 162) + - test_no_header_to_strip() -> None (line 180) + - test_preserve_shebang_when_stripping() -> None (line 189) + - test_strip_header_with_various_whitespace() -> None (line 202) + - test_strip_only_first_matching_header() -> None (line 210) + - test_strip_header_edge_cases() -> None (line 226) + - test_header_not_at_start() -> None (line 238) + - test_invalid_language_patterns(language: str) -> None (line 252) --- END AUTO-GENERATED DOCSTRING --- """ """Tests for agent_docstrings.languages.common module.""" @@ -72,10 +71,11 @@ def test_class_info_creation(self) -> None: def test_comment_style_creation(self) -> None: """Test CommentStyle namedtuple creation.""" - style = CommentStyle(start="/*", end="*/", prefix=" * ") + style = CommentStyle(start="/*", end="*/", prefix=" * ", indent=" ") assert style.start == "/*" assert style.end == "*/" assert style.prefix == " * " + assert style.indent == " " class TestCommentStyles: @@ -89,26 +89,29 @@ def test_all_supported_languages_have_styles(self) -> None: } assert set(COMMENT_STYLES.keys()) == expected_languages - @pytest.mark.parametrize("language,expected_start,expected_end,expected_prefix", [ - ("python", '"""', '"""', " "), - ("kotlin", '/**', ' */', ' * '), - ("javascript", '/**', ' */', ' * '), - ("typescript", '/**', ' */', ' * '), - ("csharp", '/*', ' */', ' * '), - ("cpp", '/*', ' */', ' * '), + @pytest.mark.parametrize("language,expected_start,expected_end,expected_prefix,expected_indent", [ + ("python", '"""', '"""', " ", " "), + ("kotlin", '/**', ' */', ' * ', " "), + ("javascript", '/**', ' */', ' * ', " "), + ("typescript", '/**', ' */', ' * ', " "), + ("csharp", '/*', ' */', ' * ', " "), + ("cpp", '/*', ' */', ' * ', " "), + ("go", '/*', ' */', ' * ', "\t"), ]) def test_comment_style_values( self, language: str, expected_start: str, expected_end: str, - expected_prefix: str + expected_prefix: str, + expected_indent: str ) -> None: """Test specific comment style values for each language.""" style = COMMENT_STYLES[language] assert style.start == expected_start assert style.end == expected_end assert style.prefix == expected_prefix + assert style.indent == expected_indent class TestHeaderStripping: diff --git a/tests/test_core.py b/tests/test_core.py index cf470ae..7c9e96a 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -2,50 +2,51 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - TestConstants (line 76): - - test_ext_to_lang_mapping() -> None (line 79) - - test_lang_parsers_coverage() -> None (line 103) - - test_parser_functions_callable() -> None (line 108) - - TestHeaderFormatting (line 114): - - test_format_python_header_basic() -> None (line 117) - - test_format_header_with_line_offset() -> None (line 142) - - test_format_kotlin_header() -> None (line 156) - - test_format_csharp_header() -> None (line 170) - - test_format_header_nested_classes() -> None (line 184) - - test_format_header_empty_input() -> None (line 208) - - test_format_header_only_functions() -> None (line 219) - - test_format_header_only_classes() -> None (line 234) - - TestProcessFile (line 246): - - test_process_unsupported_extension(tmp_path: Path) -> None (line 249) - - test_process_python_file_creates_header(source_processor) -> None (line 258) - - test_process_file_with_existing_header(source_processor) -> None (line 276) - - test_process_file_merges_manual_docstring(source_processor) -> None (line 292) - - test_process_file_preserves_shebang(source_processor) -> None (line 312) - - test_process_empty_file(tmp_path: Path) -> None (line 326) - - test_process_file_no_classes_or_functions(tmp_path: Path) -> None (line 335) - - test_process_file_encoding_issues(source_processor) -> None (line 349) - - test_process_file_permission_error(tmp_path: Path) -> None (line 359) - - test_process_file_verbose_output(source_processor, capsys: pytest.CaptureFixture[str]) -> None (line 377) - - test_process_file_no_changes_needed(source_processor, capsys: pytest.CaptureFixture[str]) -> None (line 385) - - test_process_file_read_error(mock_read, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None (line 403) - - test_process_file_write_error(mock_write, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None (line 414) - - test_process_file_parser_error(source_processor) -> None (line 425) - - TestDiscoverAndProcessFiles (line 431): - - test_discover_single_directory(tmp_path: Path) -> None (line 434) - - test_discover_multiple_directories(tmp_path: Path) -> None (line 449) - - test_discover_recursive_subdirectories(tmp_path: Path) -> None (line 464) - - test_discover_skips_ignored_directories(tmp_path: Path) -> None (line 477) - - test_discover_invalid_directory(capsys: pytest.CaptureFixture[str]) -> None (line 501) - - test_discover_mixed_valid_invalid_directories(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None (line 511) - - test_discover_all_supported_extensions(tmp_path: Path) -> None (line 522) - - test_discover_verbose_mode(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None (line 554) - - test_discover_no_files_to_process(tmp_path: Path) -> None (line 563) - - TestErrorHandling (line 576): - - test_format_header_invalid_language() -> None (line 579) + - TestConstants (line 77): + - test_ext_to_lang_mapping() -> None (line 80) + - test_lang_parsers_coverage() -> None (line 104) + - test_parser_functions_callable() -> None (line 109) + - TestHeaderFormatting (line 115): + - test_format_python_header_basic() -> None (line 118) + - test_format_header_with_line_offset() -> None (line 143) + - test_format_kotlin_header() -> None (line 157) + - test_format_csharp_header() -> None (line 171) + - test_format_header_nested_classes() -> None (line 185) + - test_format_header_empty_input() -> None (line 209) + - test_format_header_only_functions() -> None (line 220) + - test_format_header_only_classes() -> None (line 234) + - TestProcessFile (line 246): + - test_process_unsupported_extension(tmp_path: Path) -> None (line 249) + - test_process_python_file_creates_header(source_processor) -> None (line 258) + - test_process_file_with_existing_header(source_processor) -> None (line 276) + - test_process_file_merges_manual_docstring(source_processor) -> None (line 292) + - test_process_file_preserves_shebang(source_processor) -> None (line 312) + - test_process_empty_file(tmp_path: Path) -> None (line 326) + - test_process_file_no_classes_or_functions(tmp_path: Path) -> None (line 335) + - test_process_file_encoding_issues(source_processor) -> None (line 349) + - test_process_file_permission_error(tmp_path: Path) -> None (line 359) + - test_process_file_verbose_output(source_processor, capsys: pytest.CaptureFixture[str]) -> None (line 377) + - test_process_file_no_changes_needed(source_processor, capsys: pytest.CaptureFixture[str]) -> None (line 385) + - test_process_file_read_error(mock_read, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None (line 403) + - test_process_file_write_error(mock_write, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None (line 414) + - test_process_file_parser_error(source_processor) -> None (line 425) + - test_process_file_version_bump_no_changes(source_processor) -> None (line 430) + - test_process_file_version_bump_no_changes_verbose(source_processor, capsys: pytest.CaptureFixture[str]) -> None (line 449) + - TestDiscoverAndProcessFiles (line 468): + - test_discover_single_directory(tmp_path: Path) -> None (line 471) + - test_discover_multiple_directories(tmp_path: Path) -> None (line 486) + - test_discover_recursive_subdirectories(tmp_path: Path) -> None (line 501) + - test_discover_skips_ignored_directories(tmp_path: Path) -> None (line 514) + - test_discover_invalid_directory(capsys: pytest.CaptureFixture[str]) -> None (line 538) + - test_discover_mixed_valid_invalid_directories(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None (line 548) + - test_discover_all_supported_extensions(tmp_path: Path) -> None (line 560) + - test_discover_verbose_mode(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None (line 592) + - test_discover_no_files_to_process(tmp_path: Path) -> None (line 601) + - TestErrorHandling (line 614): + - test_format_header_invalid_language() -> None (line 617) --- END AUTO-GENERATED DOCSTRING --- """ """Tests for agent_docstrings.core module.""" @@ -131,7 +132,7 @@ def test_format_python_header_basic(self) -> None: assert header.startswith('"""') assert header.endswith('"""') - assert "This docstring is automatically generated by Agent Docstrings" in header + assert "Table of content is automatically generated by Agent Docstrings" in header assert __version__ in header assert DOCSTRING_START_MARKER in header assert DOCSTRING_END_MARKER in header @@ -149,7 +150,7 @@ def test_format_header_with_line_offset(self) -> None: header = _format_header(classes, functions, "python", 5) # * Line numbers should be adjusted by the header length - assert "This docstring is automatically generated by Agent Docstrings" in header + assert "Table of content is automatically generated by Agent Docstrings" in header assert __version__ in header assert "- Test (line 6):" in header # * 1 + 5 assert "- func() (line 8)" in header # * 3 + 5 @@ -163,10 +164,10 @@ def test_format_kotlin_header(self) -> None: assert header.startswith('/**') assert header.endswith(' */') - assert "This docstring is automatically generated by Agent Docstrings" in header + assert "Table of content is automatically generated by Agent Docstrings" in header assert __version__ in header assert " * Classes/Functions:" in header - assert " * - MainActivity (line 1):" in header + assert " * - MainActivity (line 1):" in header def test_format_csharp_header(self) -> None: """Test C# style header formatting.""" @@ -177,10 +178,10 @@ def test_format_csharp_header(self) -> None: assert header.startswith('/*') assert header.endswith(' */') - assert "This docstring is automatically generated by Agent Docstrings" in header + assert "Table of content is automatically generated by Agent Docstrings" in header assert __version__ in header assert " * Classes/Functions:" in header - assert " * - Calculator (line 2):" in header + assert " * - Calculator (line 2):" in header def test_format_header_nested_classes(self) -> None: """Test header formatting with nested classes.""" @@ -199,7 +200,7 @@ def test_format_header_nested_classes(self) -> None: header = _format_header([outer_class], [], "python", 0) - assert "This docstring is automatically generated by Agent Docstrings" in header + assert "Table of content is automatically generated by Agent Docstrings" in header assert __version__ in header assert "- OuterClass (line 5):" in header assert "- outer_method() (line 6)" in header @@ -212,7 +213,7 @@ def test_format_header_empty_input(self) -> None: assert header.startswith('"""') assert header.endswith('"""') - assert "This docstring is automatically generated by Agent Docstrings" in header + assert "Table of content is automatically generated by Agent Docstrings" in header assert __version__ in header assert "Classes/Functions:" in header # * Should still create a valid header structure even if empty @@ -226,11 +227,10 @@ def test_format_header_only_functions(self) -> None: header = _format_header([], functions, "python", 0) - assert "This docstring is automatically generated by Agent Docstrings" in header + assert "Table of content is automatically generated by Agent Docstrings" in header assert __version__ in header - assert " - Functions:" in header - assert " - func1() (line 1)" in header - assert " - func2(param: str) (line 5)" in header + assert " - func1() (line 1)" in header + assert " - func2(param: str) (line 5)" in header def test_format_header_only_classes(self) -> None: """Test header formatting with only classes, no top-level functions.""" @@ -238,7 +238,7 @@ def test_format_header_only_classes(self) -> None: header = _format_header(classes, [], "python", 0) - assert "This docstring is automatically generated by Agent Docstrings" in header + assert "Table of content is automatically generated by Agent Docstrings" in header assert __version__ in header assert "- OnlyClass (line 1):" in header assert " - Functions:" not in header @@ -428,6 +428,43 @@ def test_process_file_parser_error(self, source_processor) -> None: # It should not crash source_processor("malformed.py", "def func(a,:", verbose=True) + def test_process_file_version_bump_no_changes(self, source_processor) -> None: + """Test that version bump in header without code changes updates the docstring to current version.""" + content = '''""" + --- AUTO-GENERATED DOCSTRING --- + This docstring is automatically generated by Agent Docstrings v1.2.0 + Do not modify this block directly. + + Classes/Functions: + - foo() (line 1) + --- END AUTO-GENERATED DOCSTRING --- +""" + def foo(): + pass''' + processed_content, _, _ = source_processor("test.py", content, verbose=False) + # The header version should be updated to the current version + assert "generated by Agent Docstrings v" in processed_content + assert "v1.3.0" in processed_content + assert "v1.2.0" not in processed_content + + def test_process_file_version_bump_no_changes_verbose(self, source_processor, capsys: pytest.CaptureFixture[str]) -> None: + """Test that version-only header changes produce a processed message when verbose.""" + content = '''""" + --- AUTO-GENERATED DOCSTRING --- + This docstring is automatically generated by Agent Docstrings v1.2.0 + Do not modify this block directly. + + Classes/Functions: + - foo() (line 1) + --- END AUTO-GENERATED DOCSTRING --- +""" + def foo(): + pass''' + source_processor("test.py", content, verbose=True) + captured = capsys.readouterr() + # Should indicate that the file was processed + assert "Processed Python:" in captured.out + class TestDiscoverAndProcessFiles: """Tests for discover_and_process_files function.""" @@ -507,7 +544,7 @@ def test_discover_invalid_directory(self, capsys: pytest.CaptureFixture[str]) -> captured = capsys.readouterr() assert "Warning:" in captured.out - assert "not a valid directory" in captured.out + assert "is not a valid path" in captured.out def test_discover_mixed_valid_invalid_directories(self, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: """Test handling mix of valid and invalid directories.""" @@ -517,6 +554,7 @@ def test_discover_mixed_valid_invalid_directories(self, tmp_path: Path, capsys: captured = capsys.readouterr() assert "Warning:" in captured.out + assert "is not a valid path" in captured.out # * Valid directory should still be processed assert "valid" in (tmp_path / "valid.py").read_text() diff --git a/tests/test_determinism.py b/tests/test_determinism.py index b23e4df..61b55fb 100644 --- a/tests/test_determinism.py +++ b/tests/test_determinism.py @@ -1,11 +1,9 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - Functions: - - test_process_file_determinism(sample_files_by_language) (line 16) + - test_process_file_determinism(sample_files_by_language) (line 14) --- END AUTO-GENERATED DOCSTRING --- """ import pytest diff --git a/tests/test_discovery.py b/tests/test_discovery.py index d95fa37..529e306 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -1,11 +1,9 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - Functions: - - test_discover_and_process_files_updates_header(tmp_path: Path) -> None (line 20) + - test_discover_and_process_files_updates_header(tmp_path: Path) -> None (line 18) --- END AUTO-GENERATED DOCSTRING --- """ from __future__ import annotations diff --git a/tests/test_header_preservation.py b/tests/test_header_preservation.py index 75d2bb1..7f6e72f 100644 --- a/tests/test_header_preservation.py +++ b/tests/test_header_preservation.py @@ -1,12 +1,10 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - Functions: - - test_header_preservation(source_processor, ext, header_lines, lang) (line 30) - - test_future_import_preservation(source_processor) -> None (line 64) + - test_header_preservation(source_processor, ext, header_lines, lang) (line 25) + - test_future_import_preservation(source_processor) -> None (line 59) --- END AUTO-GENERATED DOCSTRING --- """ import pytest diff --git a/tests/test_line_numbers.py b/tests/test_line_numbers.py index f774083..cf34880 100644 --- a/tests/test_line_numbers.py +++ b/tests/test_line_numbers.py @@ -1,15 +1,14 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - TestLineNumberCorrection (line 23): - - test_python_line_numbers_without_existing_docstring(source_processor) -> None (line 24) - - test_python_line_numbers_with_existing_docstring(source_processor) -> None (line 65) - - test_go_line_numbers_correction(source_processor) -> None (line 101) - - test_line_numbers_with_shebang(source_processor) -> None (line 149) - - test_line_numbers_after_multiple_updates(tmp_path: Path, source_processor) -> None (line 175) + - TestLineNumberCorrection (line 22): + - test_python_line_numbers_without_existing_docstring(source_processor) -> None (line 23) + - test_python_line_numbers_with_existing_docstring(source_processor) -> None (line 64) + - test_go_line_numbers_correction(source_processor) -> None (line 100) + - test_line_numbers_with_shebang(source_processor) -> None (line 148) + - test_line_numbers_after_multiple_updates(tmp_path: Path, source_processor) -> None (line 174) --- END AUTO-GENERATED DOCSTRING --- """ from __future__ import annotations diff --git a/tests/test_parser.py b/tests/test_parser.py index 94e5043..fad382e 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,30 +1,29 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - TestPythonParser (line 42): - - test_parse_python_file(source: str, expected_classes: list[ClassInfo], expected_funcs: list[SignatureInfo]) -> None (line 103) - - test_complex_python_structure() -> None (line 109) - - TestKotlinParser (line 141): - - test_basic_kotlin_parsing() -> None (line 144) - - test_kotlin_with_modifiers() -> None (line 166) - - TestGenericParser (line 185): - - test_empty_file(language: str) -> None (line 189) - - test_javascript_parsing() -> None (line 195) - - test_csharp_parsing() -> None (line 218) - - test_cpp_parsing() -> None (line 240) - - test_unsupported_language() -> None (line 259) - - TestErrorHandling (line 266): - - test_malformed_python_code() -> None (line 269) - - test_very_long_lines() -> None (line 282) - - TestMoreLanguages (line 292): - - test_c_parsing() (line 295) - - test_java_parsing() (line 316) - - test_go_parsing() (line 346) - - test_powershell_parsing() (line 369) - - test_delphi_parsing() (line 393) + - TestPythonParser (line 41): + - test_parse_python_file(source: str, expected_classes: list[ClassInfo], expected_funcs: list[SignatureInfo]) -> None (line 102) + - test_complex_python_structure() -> None (line 108) + - TestKotlinParser (line 140): + - test_basic_kotlin_parsing() -> None (line 143) + - test_kotlin_with_modifiers() -> None (line 165) + - TestGenericParser (line 184): + - test_empty_file(language: str) -> None (line 188) + - test_javascript_parsing() -> None (line 194) + - test_csharp_parsing() -> None (line 217) + - test_cpp_parsing() -> None (line 239) + - test_unsupported_language() -> None (line 258) + - TestErrorHandling (line 265): + - test_malformed_python_code() -> None (line 268) + - test_very_long_lines() -> None (line 281) + - TestMoreLanguages (line 291): + - test_c_parsing() (line 294) + - test_java_parsing() (line 315) + - test_go_parsing() (line 345) + - test_powershell_parsing() (line 368) + - test_delphi_parsing() (line 392) --- END AUTO-GENERATED DOCSTRING --- """ from __future__ import annotations diff --git a/tests/test_writer.py b/tests/test_writer.py index 14d1ea9..6060d98 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -1,11 +1,9 @@ """ --- AUTO-GENERATED DOCSTRING --- - This docstring is automatically generated by Agent Docstrings v1.2.0 - Do not modify this block directly. + Table of content is automatically generated by Agent Docstrings v1.3.0 Classes/Functions: - - Functions: - - test_process_file_inserts_header(source_processor) -> None (line 19) + - test_process_file_inserts_header(source_processor) -> None (line 17) --- END AUTO-GENERATED DOCSTRING --- """ from __future__ import annotations