diff --git a/penify_hook/api_client.py b/penify_hook/api_client.py index ae5e130..903e529 100644 --- a/penify_hook/api_client.py +++ b/penify_hook/api_client.py @@ -10,29 +10,7 @@ def __init__(self, api_url, api_token: str = None, bearer_token: str = None): self.BEARER_TOKEN = bearer_token def send_file_for_docstring_generation(self, file_name, content, line_numbers, repo_details = None): - """Send file content and modified lines to the API and return modified - content. - - This function constructs a payload containing the file path, content, - and modified line numbers, and sends it to a specified API endpoint for - processing. It handles the response from the API, returning the modified - content if the request is successful. If the request fails, it logs the - error details and returns the original content. - - Args: - file_name (str): The path to the file being sent. - content (str): The content of the file to be processed. - line_numbers (list): A list of line numbers that have been modified. - repo_details (str?): Additional repository details if applicable. Defaults to None. - - Returns: - str: The modified content returned by the API, or the original content if the - request fails. - - Raises: - Exception: If there is an error in processing the file and no specific error - message is provided. - """ + """Send file content and modified lines to the API and return modified content.""" payload = { 'file_path': file_name, 'content': content, @@ -53,25 +31,22 @@ def send_file_for_docstring_generation(self, file_name, content, line_numbers, r raise Exception(f"API Error: {error_message}") def generate_commit_summary(self, git_diff, instruction: str = "", repo_details = None, jira_context: dict = None): - """Generate a commit summary by sending a POST request to the API endpoint. - - This function constructs a payload containing the git diff and any - additional instructions provided. It then sends this payload to a - specified API endpoint to generate a summary of the commit. If the - request is successful, it returns the response from the API; otherwise, - it returns None. - + """Generates a commit summary by sending a POST request to the API endpoint. + + This function constructs a payload containing the git diff and any additional + instructions provided. It then sends this payload to a specified API endpoint + to generate a summary of the commit. If the request is successful, it returns + the response from the API; otherwise, it returns None. The function also + handles optional repository details and JIRA context if they are provided. + Args: git_diff (str): The git diff of the commit. - instruction (str??): Additional instruction for the commit. Defaults to "". - repo_details (dict??): Details of the git repository. Defaults to None. - jira_context (dict??): JIRA issue details to enhance the commit summary. Defaults to None. - + instruction (str): Additional instruction for the commit. Defaults to "". + repo_details (dict): Details of the git repository. Defaults to None. + jira_context (dict): JIRA issue details to enhance the commit summary. Defaults to None. + Returns: dict: The response from the API if the request is successful, None otherwise. - - Raises: - Exception: If there is an error during the API request. """ payload = { 'git_diff': git_diff, @@ -100,18 +75,8 @@ def generate_commit_summary(self, git_diff, instruction: str = "", repo_details return None def get_supported_file_types(self) -> list[str]: - """Retrieve the supported file types from the API. - - This function sends a request to the API endpoint - `/v1/file/supported_languages` to obtain a list of supported file types. - If the API call is successful (status code 200), it parses the JSON - response and returns the list of supported file types. If the API call - fails, it returns a default list of common file types. - - Returns: - list[str]: A list of supported file types, either from the API or a default set. - """ + """Retrieve supported file types from the API or return a default list.""" url = self.api_url+"/v1/cli/supported_languages" response = requests.get(url) if response.status_code == 200: @@ -121,21 +86,8 @@ def get_supported_file_types(self) -> list[str]: return ["py", "js", "ts", "java", "kt", "cs", "c"] def generate_commit_summary_with_llm(self, diff, message, generate_description: bool, repo_details, llm_client : LLMClient, jira_context=None): - """Generates a commit summary using a local LLM client. If an error occurs - during the generation process, - it falls back to using the API. - - Args: - diff (str): The Git diff of changes. - message (str): User-provided commit message or instructions. - generate_description (bool): Flag indicating whether to generate a description for the commit. - repo_details (dict): Details about the repository. - llm_client (LLMClient): An instance of LLMClient used to generate the summary. - jira_context (JIRAContext?): Optional JIRA issue context to enhance the summary. - - Returns: - dict: A dictionary containing the title and description for the commit. - """ + """Generates a commit summary using a local LLM client; falls back to API on + error.""" try: return llm_client.generate_commit_summary(diff, message, generate_description, repo_details, jira_context) except Exception as e: @@ -144,17 +96,9 @@ def generate_commit_summary_with_llm(self, diff, message, generate_description: return self.generate_commit_summary(diff, message, repo_details, jira_context) def get_api_key(self): - """Fetch an API key from a specified URL. - - This function sends a GET request to retrieve an API token using a - Bearer token in the headers. It handles the response and returns the API - key if the request is successful, or `None` otherwise. - - Returns: - str: The API key if the request is successful, `None` otherwise. - """ + """Fetch an API key from a specified URL using a Bearer token.""" url = self.api_url+"/v1/apiToken/get" response = requests.get(url, headers={"Authorization": f"Bearer {self.BEARER_TOKEN}"}, timeout=60*10) if response.status_code == 200: diff --git a/penify_hook/commands/auth_commands.py b/penify_hook/commands/auth_commands.py index 4107525..5352d63 100644 --- a/penify_hook/commands/auth_commands.py +++ b/penify_hook/commands/auth_commands.py @@ -9,18 +9,26 @@ from pathlib import Path def save_credentials(api_key): - """ - Save the token and API keys based on priority: - 1. .env file in Git repo root (if in a git repo) - 2. .penify file in home directory (global fallback) + # Try to save in .env file in git repo first + """Save the API key in a priority-based manner. + + This function attempts to save the API key in two locations, based on priority: + 1. In a `.env` file located in the root of the Git repository if one is found. + 2. In a global `.penify` file located in the user's home directory as a + fallback. The function first tries to locate the Git repository using + `recursive_search_git_folder`. If a Git repository is found, it reads the + existing `.env` file (if present), updates or adds the API key under the key + `PENIFY_API_TOKEN`, and writes the updated content back. If any error occurs + during this process, it falls back to saving the credentials in the global + `.penify` file. The function handles exceptions and prints appropriate error + messages. Args: - api_key: The API key to save - + api_key (str): The API key to save. + Returns: - bool: True if saved successfully, False otherwise + bool: True if the API key is saved successfully, False otherwise. """ - # Try to save in .env file in git repo first try: from ..utils import recursive_search_git_folder current_dir = os.getcwd() @@ -80,21 +88,7 @@ def save_credentials(api_key): return False def login(api_url, dashboard_url): - """Open the login page in a web browser and listen for the redirect URL to - capture the token. - - This function generates a random redirect port, constructs the full - login URL with the provided dashboard URL, opens the login page in the - default web browser, and sets up a simple HTTP server to listen for the - redirect. Upon receiving the redirect, it extracts the token from the - query parameters, fetches API keys using the token, saves them if - successful, and handles login failures by notifying the user. - - Args: - api_url (str): The URL of the API service to fetch API keys. - dashboard_url (str): The URL of the dashboard where the user will be redirected after logging - in. - """ + """Open the login page in a web browser and capture the token via redirect.""" redirect_port = random.randint(30000, 50000) redirect_url = f"http://localhost:{redirect_port}/callback" @@ -105,16 +99,9 @@ def login(api_url, dashboard_url): class TokenHandler(http.server.SimpleHTTPRequestHandler): def do_GET(self): - """Handle a GET request to process login token and redirect or display - error message. - - This method processes the incoming GET request, extracts the token from - the query string, and performs actions based on whether the token is - present. If the token is valid, it redirects the user to the Penify - dashboard and fetches API keys if successful. If the token is invalid, - it displays an error message. - """ + """Handle a GET request to process login token and redirect or display error + message.""" query = urllib.parse.urlparse(self.path).query query_components = urllib.parse.parse_qs(query) token = query_components.get("token", [None])[0] @@ -171,6 +158,7 @@ def do_GET(self): def log_message(self, format, *args): # Suppress log messages + """Suppress log messages.""" return with socketserver.TCPServer(("", redirect_port), TokenHandler) as httpd: diff --git a/penify_hook/commands/commit_commands.py b/penify_hook/commands/commit_commands.py index a9d93f8..fd04b69 100644 --- a/penify_hook/commands/commit_commands.py +++ b/penify_hook/commands/commit_commands.py @@ -8,29 +8,27 @@ def commit_code(api_url, token, message, open_terminal, generate_description, llm_model=None, llm_api_base=None, llm_api_key=None, jira_url=None, jira_user=None, jira_api_token=None): - """Enhance Git commits with AI-powered commit messages. - - This function allows for the generation of enhanced commit messages - using natural language processing models and optionally integrates with - JIRA for additional context. It processes the current Git folder to find - relevant files and generates a detailed commit message based on the - provided parameters. + """Enhance Git commits with AI-powered commit messages. + + This function allows for the generation of enhanced commit messages using + natural language processing models and optionally integrates with JIRA for + additional context. It processes the current Git folder to find relevant files + and generates a detailed commit message based on the provided parameters. + Args: api_url (str): URL of the API endpoint. token (str): Authentication token for the API. message (str): Initial commit message provided by the user. open_terminal (bool): Whether to open the terminal after committing. generate_description (bool): Whether to generate a detailed description in the commit message. - llm_model (str?): The language model to use for generating the commit message. Defaults to - None. + llm_model (str?): The language model to use for generating the commit message. Defaults to None. llm_api_base (str?): Base URL of the LLM API. Defaults to None. llm_api_key (str?): API key for accessing the LLM service. Defaults to None. jira_url (str?): URL of the JIRA instance. Defaults to None. jira_user (str?): Username for authenticating with JIRA. Defaults to None. jira_api_token (str?): API token for accessing JIRA. Defaults to None. """ - from penify_hook.ui_utils import print_error from penify_hook.utils import recursive_search_git_folder from ..commit_analyzer import CommitDocGenHook @@ -98,18 +96,8 @@ def commit_code(api_url, token, message, open_terminal, generate_description, def setup_commit_parser(parser): - """Generates a parser for setting up a command to generate smart commit - messages. - - This function sets up an argument parser that can be used to generate - commit messages with contextual information. It allows users to specify - options such as including a message, opening an edit terminal before - committing, and generating a detailed commit message. - - Args: - parser (argparse.ArgumentParser): The ArgumentParser object to be configured. - """ + """Sets up an argument parser for generating smart commit messages.""" commit_parser_description = """ It generates smart commit messages. By default, it will just generate just the Title of the commit message. 1. If you have not configured LLM, it will give an error. You either need to configure LLM or use the API key. @@ -126,19 +114,9 @@ def setup_commit_parser(parser): parser.add_argument("-d", "--description", action="store_false", help="It will generate commit message with title and description.", default=False) def handle_commit(args): - """Handle the commit functionality by processing arguments and invoking the - appropriate commands. - - This function processes the provided command-line arguments to configure - settings for commit operations, including LLM (Language Model) and Jira - configurations. It then calls the `commit_code` function with these - configurations to perform the actual commit operation. - - Args: - args (argparse.Namespace): The parsed command-line arguments containing options like terminal, - description, message, etc. - """ + """Handle commit functionality by processing arguments and invoking the + appropriate commands.""" from penify_hook.commands.commit_commands import commit_code from penify_hook.commands.config_commands import get_jira_config, get_llm_config, get_token from penify_hook.constants import API_URL diff --git a/penify_hook/commands/config_commands.py b/penify_hook/commands/config_commands.py index 540aac5..82bcddd 100644 --- a/penify_hook/commands/config_commands.py +++ b/penify_hook/commands/config_commands.py @@ -20,15 +20,13 @@ def load_env_files() -> None: - """ - Load environment variables from .env files in various locations, - with proper priority (later files override earlier ones): - 1. User home directory .env (lowest priority) - 2. Git repo root directory .env (if in a git repo) - 3. Current directory .env (highest priority) - - This function is called when the module is imported, ensuring env variables - are available throughout the application lifecycle. + """Load environment variables from .env files in various locations with proper + priority. + + This function loads environment variables from .env files located in different + directories, prioritizing the current directory over the Git repo root and the + user home directory. The loading process ensures that later files override + earlier ones. """ if not DOTENV_AVAILABLE: logging.warning("python-dotenv is not installed. .env file loading is disabled.") @@ -66,16 +64,8 @@ def load_env_files() -> None: def get_penify_config() -> Path: - """Get the home directory for the .penify configuration file. - - This function searches for the `.penify` file in the current directory - and its parent directories until it finds it or reaches the home - directory. If not found, it creates the `.penify` directory and an empty - `config.json` file. - - Returns: - Path: The path to the `config.json` file within the `.penify` directory. - """ + """Returns the path to the `config.json` file within the `.penify` directory, + creating it if necessary.""" current_dir = os.getcwd() from penify_hook.utils import recursive_search_git_folder home_dir = recursive_search_git_folder(current_dir) @@ -103,26 +93,26 @@ def get_penify_config() -> Path: def get_env_var_or_default(env_var: str, default: Any = None) -> Any: - """ - Get environment variable or return default value. - - Args: - env_var: The environment variable name - default: Default value if environment variable is not set - - Returns: - Value of the environment variable or default - """ + """Get environment variable or return default value.""" return os.environ.get(env_var, default) def save_llm_config(model, api_base, api_key): - """ - Save LLM configuration settings to .env file. + """Save LLM configuration settings to an .env file. + + This function saves the LLM configuration following a specific priority: 1. Git + repo root .env (if inside a git repo) 2. User home directory .env It handles + the detection of the Git repo root, reads the existing .env content, updates it + with the new LLM configuration, and writes it back to the file. It also reloads + the environment variables to make changes immediately available. - This function saves LLM configuration in the following priority: - 1. Git repo root .env (if inside a git repo) - 2. User home directory .env + Args: + model (str): The name of the language model. + api_base (str): The base URL for the API. + api_key (str): The API key for authentication. + + Returns: + bool: True if the configuration is saved successfully, False otherwise. """ from pathlib import Path import os @@ -175,12 +165,24 @@ def save_llm_config(model, api_base, api_key): def save_jira_config(url, username, api_token): - """ - Save JIRA configuration settings to .env file. + """Save JIRA configuration settings to a .env file. + + This function saves JIRA configuration following these steps: 1. Determine the + target .env file location based on whether the current directory is inside a + Git repository. 2. If inside a Git repo, use the Git repo root's .env file; + otherwise, use the user home directory's .env file. 3. Read the existing + content of the .env file (if it exists) to preserve other settings. 4. Update + the .env content with the new JIRA configuration. 5. Write the updated content + back to the .env file. 6. Optionally, reload environment variables to make + changes immediately available. - This function saves JIRA configuration in the following priority: - 1. Git repo root .env (if inside a git repo) - 2. User home directory .env + Args: + url (str): The JIRA URL to be saved in the .env file. + username (str): The JIRA username to be saved in the .env file. + api_token (str): The JIRA API token to be saved in the .env file. + + Returns: + bool: True if the configuration was successfully saved, False otherwise. """ from pathlib import Path import os @@ -233,18 +235,8 @@ def save_jira_config(url, username, api_token): def get_llm_config() -> Dict[str, str]: - """ - Get LLM configuration from environment variables. - - Environment variables: - - PENIFY_LLM_MODEL: Model name - - PENIFY_LLM_API_BASE: API base URL - - PENIFY_LLM_API_KEY: API key - - Returns: - dict: Configuration dictionary with model, api_base, and api_key - """ # Ensure environment variables are loaded + """Retrieve LLM configuration from environment variables.""" if DOTENV_AVAILABLE: load_env_files() @@ -262,18 +254,8 @@ def get_llm_config() -> Dict[str, str]: def get_jira_config() -> Dict[str, str]: - """ - Get JIRA configuration from environment variables. - - Environment variables: - - PENIFY_JIRA_URL: JIRA URL - - PENIFY_JIRA_USER: JIRA username - - PENIFY_JIRA_TOKEN: JIRA API token - - Returns: - dict: Configuration dictionary with url, username, and api_token - """ # Ensure environment variables are loaded + """Retrieve JIRA configuration from environment variables.""" if DOTENV_AVAILABLE: load_env_files() @@ -291,17 +273,7 @@ def get_jira_config() -> Dict[str, str]: def config_llm_web(): - """Open a web browser interface for configuring LLM settings. - - This function starts a temporary HTTP server that serves an HTML - template for configuring Large Language Model (LLM) settings. It handles - GET and POST requests to retrieve the current configuration, save new - configurations, and suppress log messages. The server runs on a random - port between 30000 and 50000, and it is accessible via a URL like - http://localhost:. The function opens this URL in the - default web browser for configuration. Once configured, the server shuts - down. - """ + """Starts an HTTP server for configuring LLM settings via a web interface.""" redirect_port = random.randint(30000, 50000) server_url = f"http://localhost:{redirect_port}" @@ -309,15 +281,8 @@ def config_llm_web(): class ConfigHandler(http.server.SimpleHTTPRequestHandler): def do_GET(self): - """Handle HTTP GET requests. - - This function processes incoming GET requests and sends appropriate - responses based on the requested path. It serves an HTML template for - the root path ("/") and returns a JSON response with the current LLM - configuration for the "/get_config" path. For any other paths, it - returns a "Not Found" error. - """ + """Handle HTTP GET requests and serve appropriate responses based on path.""" if self.path == "/": self.send_response(200) self.send_header("Content-type", "text/html") @@ -359,17 +324,8 @@ def do_GET(self): self.wfile.write(b"Not Found") def do_POST(self): - """Handle POST requests on the /save endpoint. - - This method processes incoming POST requests to save language model - configuration data. It extracts the necessary parameters from the - request body, saves the configuration using the provided details, and - then schedules the server to shut down after a successful save. - - Args: - self (HTTPRequestHandler): The instance of the HTTPRequestHandler class handling the request. - """ + """Handle POST requests to save language model configuration data.""" if self.path == "/save": content_length = int(self.headers['Content-Length']) post_data = self.rfile.read(content_length) @@ -410,6 +366,7 @@ def do_POST(self): def log_message(self, format, *args): # Suppress log messages + """Suppresses log messages.""" return with socketserver.TCPServer(("", redirect_port), ConfigHandler) as httpd: @@ -422,14 +379,7 @@ def log_message(self, format, *args): def config_jira_web(): - """Open a web browser interface for configuring JIRA settings. - - This function sets up a simple HTTP server using Python's built-in - `http.server` module to handle GET and POST requests. The server serves - an HTML page for configuration and handles saving the JIRA configuration - details through API tokens and URLs. Upon successful configuration, it - shuts down the server gracefully. - """ + """Starts a web server for configuring JIRA settings.""" redirect_port = random.randint(30000, 50000) server_url = f"http://localhost:{redirect_port}" @@ -437,14 +387,8 @@ def config_jira_web(): class ConfigHandler(http.server.SimpleHTTPRequestHandler): def do_GET(self): - """Handle GET requests for different paths. - - This function processes GET requests based on the path requested. It - serves an HTML template for the root path, returns a JSON configuration - for a specific endpoint, and handles any other paths by returning a 404 - error. - """ + """Handle GET requests by serving HTML, JSON, or 404 responses based on the path.""" if self.path == "/": self.send_response(200) self.send_header("Content-type", "text/html") @@ -486,16 +430,8 @@ def do_GET(self): self.wfile.write(b"Not Found") def do_POST(self): - """Handle HTTP POST requests to save JIRA configuration. - - This method processes incoming POST requests to save JIRA configuration - details. It reads JSON data from the request body, extracts necessary - parameters (URL, username, API token, and verify), saves the - configuration using the `save_jira_config` function, and responds with - success or error messages. If an exception occurs during the process, it - sends a 500 Internal Server Error response. - """ + """Handle HTTP POST requests to save JIRA configuration.""" if self.path == "/save": content_length = int(self.headers['Content-Length']) post_data = self.rfile.read(content_length) @@ -539,6 +475,7 @@ def do_POST(self): def log_message(self, format, *args): # Suppress log messages + """Suppresses log messages.""" return with socketserver.TCPServer(("", redirect_port), ConfigHandler) as httpd: @@ -551,15 +488,17 @@ def log_message(self, format, *args): def get_token() -> Optional[str]: - """ - Get the API token based on priority: - 1. Environment variable PENIFY_API_TOKEN from any .env file - 2. Config file 'api_keys' value + # Ensure environment variables are loaded from all .env files + """Retrieves an API token using a prioritized method. + + This function first attempts to load environment variables from all `.env` + files and checks if the `PENIFY_API_TOKEN` environment variable is set. If + found, it returns the token. If not, it looks for the API key in a + configuration file named 'api_keys'. If both methods fail, it returns None. Returns: - str or None: API token if found, None otherwise + str or None: The API token if found, otherwise None. """ - # Ensure environment variables are loaded from all .env files if DOTENV_AVAILABLE: load_env_files() diff --git a/penify_hook/commands/doc_commands.py b/penify_hook/commands/doc_commands.py index 61ba704..19661d0 100644 --- a/penify_hook/commands/doc_commands.py +++ b/penify_hook/commands/doc_commands.py @@ -7,19 +7,18 @@ def generate_doc(api_url, token, location=None): """Generates documentation based on the given parameters. - - This function initializes an API client using the provided API URL and - token. It then generates documentation by analyzing the specified - location, which can be a folder, a file, or the current working - directory if no location is provided. The function handles different - types of analysis based on the input location and reports any errors - encountered during the process. - + + This function initializes an API client using the provided API URL and token. + It then generates documentation by analyzing the specified location, which can + be a folder, a file, or the current working directory if no location is + provided. The function handles different types of analysis based on the input + location and reports any errors encountered during the process. + Args: api_url (str): The URL of the API to connect to for documentation generation. token (str): The authentication token for accessing the API. - location (str?): The path to a specific file or folder to analyze. If not provided, the - current working directory is used. + location (str?): The path to a specific file or folder to analyze. If not provided, + the current working directory is used. """ t1 = time.time() from ..api_client import APIClient @@ -81,19 +80,9 @@ def generate_doc(api_url, token, location=None): """ def setup_docgen_parser(parser): - """Set up and configure a parser for documentation generation using Git - commands. - - This function configures a parser with various subcommands and arguments - necessary for generating documentation for Git diffs, files, or folders. - It also installs and uninstalls commit hooks to automate documentation - generation on commits. - - Args: - parser (argparse.ArgumentParser): The parser to configure. - """ # We don't need to create a new docgen_parser since it's passed as a parameter + """Configure a parser for generating documentation using Git commands.""" docgen_parser_description = """ It generates Documentation for the Git diff, file or folder. 1. By default, it will git diff documentation - visit https://penify.wiki/dcdc for more details. @@ -123,19 +112,9 @@ def setup_docgen_parser(parser): default=os.getcwd()) def handle_docgen(args): - """Handle various subcommands related to document generation and hook - management. - - This function processes different subcommands such as installing or - uninstalling git hooks, and directly generating documentation based on - provided arguments. - - Args: - args (Namespace): Parsed command-line arguments containing the subcommand and location - details. - """ # Only import dependencies needed for docgen functionality here + """Handle document generation and hook management based on subcommands.""" from penify_hook.commands.config_commands import get_token import sys from penify_hook.commands.doc_commands import generate_doc diff --git a/penify_hook/commands/hook_commands.py b/penify_hook/commands/hook_commands.py index 8f8110a..8ce33bc 100644 --- a/penify_hook/commands/hook_commands.py +++ b/penify_hook/commands/hook_commands.py @@ -10,15 +10,7 @@ """ def install_git_hook(location, token): - """Install a post-commit hook in the specified location that generates - documentation - for changed files after each commit. - - Args: - location (str): The path to the Git repository where the hook should be installed. - token (str): The authentication token required to access the documentation generation - service. - """ + """Install a post-commit Git hook that generates documentation for changed files.""" hooks_dir = Path(location) / ".git/hooks" hook_path = hooks_dir / HOOK_FILENAME @@ -34,16 +26,7 @@ def install_git_hook(location, token): print(f"Documentation will now be automatically generated after each commit.") def uninstall_git_hook(location): - """Uninstalls the post-commit hook from the specified location. - - This function attempts to remove a post-commit git hook located at the - given path. It constructs the path to the hook and checks if it exists. - If the hook is found, it is deleted, and a confirmation message is - printed. If no hook is found, a message indicating this is also printed. - - Args: - location (Path): The base directory where the .git/hooks directory is located. - """ + """Uninstalls the post-commit hook from the specified location.""" hook_path = Path(location) / ".git/hooks" / HOOK_FILENAME if hook_path.exists(): diff --git a/penify_hook/commit_analyzer.py b/penify_hook/commit_analyzer.py index 31ab46b..4a5020c 100644 --- a/penify_hook/commit_analyzer.py +++ b/penify_hook/commit_analyzer.py @@ -20,25 +20,18 @@ def __init__(self, repo_path: str, api_client: APIClient, llm_client=None, jira_ def get_summary(self, instruction: str, generate_description: bool) -> dict: """Generate a summary for the commit based on the staged changes. - - This function retrieves the differences of the staged changes in the - repository and generates a commit summary using the provided - instruction. If there are no changes staged for commit, an exception is - raised. If a JIRA client is connected, it will attempt to extract issue - keys from the current branch and use them to fetch context. The summary - can be generated either with a Language Model (LLM) client or through - the API client. - + + This function retrieves the differences of the staged changes in the repository + and generates a commit summary using the provided instruction. If there are no + changes staged for commit, an exception is raised. If a JIRA client is + connected, it will attempt to extract issue keys from the current branch and + use them to fetch context. The summary can be generated either with a Language + Model (LLM) client or through the API client. + Args: instruction (str): A string containing instructions for generating the commit summary. generate_description (bool): Whether to include detailed descriptions in the summary. - - Returns: - dict: The generated commit summary based on the staged changes, provided - instruction, and any relevant JIRA context. The dictionary contains keys - such as 'summary', 'description', etc., depending on whether a - description was requested. - + Raises: ValueError: If there are no changes staged for commit. """ @@ -72,21 +65,18 @@ def get_summary(self, instruction: str, generate_description: bool) -> dict: def run(self, msg: Optional[str], edit_commit_message: bool, generate_description: bool): """Run the post-commit hook. - - This method processes the modified files from the last commit, stages - them, and creates an auto-commit with an optional message. It also - handles JIRA integration if available. If there is an error generating - the commit summary, an exception is raised. - + + This method processes the modified files from the last commit, stages them, and + creates an auto-commit with an optional message. It also handles JIRA + integration if available. If there is an error generating the commit summary, + an exception is raised. + Args: msg (Optional[str]): An optional message to include in the commit. - edit_commit_message (bool): A flag indicating whether to open the git commit edit terminal after - committing. - generate_description (bool): A flag indicating whether to include a description in the commit - message. - - Raises: - Exception: If there is an error generating the commit summary. + edit_commit_message (bool): A flag indicating whether to open the git commit + edit terminal after committing. + generate_description (bool): A flag indicating whether to include a description + in the commit message. """ summary: dict = self.get_summary(msg, True) if not summary: @@ -111,18 +101,24 @@ def run(self, msg: Optional[str], edit_commit_message: bool, generate_descriptio self._amend_commit() def process_jira_integration(self, title: str, description: str, msg: str) -> tuple: - """Process JIRA integration for the commit message. - + # Look for JIRA issue keys in commit message, title, description and user message + """Process JIRA integration by extracting issue keys from commit message + components and branch name. + + This function looks for JIRA issue keys in the provided commit title, + description, original user message, and the active branch name. It uses these + keys to update the commit message with JIRA information and adds comments to + the corresponding JIRA issues. If no keys are found, it logs a warning. + Args: - title (str): Generated commit title. - description (str): Generated commit description. - msg (str): Original user message that might contain JIRA references. - + title (str): The generated commit title. + description (str): The generated commit description. + msg (str): The original user message that might contain JIRA references. + Returns: - tuple: A tuple containing the updated commit title and description with - included JIRA information. + tuple: A tuple containing the updated commit title and description with included JIRA + information. """ - # Look for JIRA issue keys in commit message, title, description and user message issue_keys = [] if self.jira_client: # Extract from message content @@ -160,13 +156,7 @@ def process_jira_integration(self, title: str, description: str, msg: str) -> tu return title, description def _amend_commit(self): - """Open the default git editor for editing the commit message. - - This function changes the current working directory to the repository - path, runs the git command to amend the last commit, and opens the - default editor for the user to modify the commit message. After the - operation, it returns to the original directory. - """ + """Amends the last commit message in the repository.""" try: # Change to the repository directory os.chdir(self.repo_path) diff --git a/penify_hook/config_command.py b/penify_hook/config_command.py index 9f75331..281e698 100644 --- a/penify_hook/config_command.py +++ b/penify_hook/config_command.py @@ -2,19 +2,9 @@ def setup_config_parser(parent_parser): - """Set up a configuration parser with subparsers for different types of - configurations. - - This function configures and adds subcommands to the parent parser. Each - subcommand corresponds to a specific type of configuration, such as LLM - (Language Model) or JIRA. It allows users to configure settings for - these systems through command-line arguments. - - Args: - parent_parser (argparse.ArgumentParser): The parent parser to which the config subparsers will be added. - """ # Config subcommand: Create subparsers for config types + """Set up configuration parsers with subcommands for LLM and JIRA settings.""" parser = parent_parser.add_subparsers(title="config_type", dest="config_type") # Config subcommand: llm @@ -39,23 +29,22 @@ def setup_config_parser(parent_parser): # Add all other necessary arguments for config command def handle_config(args): - """Handle configuration settings based on the specified config type. - - This function processes different types of configurations such as LLM - (Language Model) and JIRA. It saves configurations, sets up web-based - configurations, and verifies JIRA connections. - - Args: - args (argparse.Namespace): Command-line arguments containing the type of configuration to handle. - - Returns: - int: Exit code indicating success or failure. - """ # Only import dependencies needed for config functionality here + """Handle configuration settings based on the specified config type. + + This function processes different types of configurations such as LLM (Language + Model) and JIRA. It saves configurations, sets up web-based configurations, and + verifies JIRA connections. Depending on the `args.config_type`, it imports + necessary modules, handles configuration saving or setup, and optionally + verifies JIRA connectivity. + + Args: + args (argparse.Namespace): Command-line arguments containing the type of configuration to handle. + """ if args.config_type == "llm-cmd": from penify_hook.commands.config_commands import save_llm_config save_llm_config(args.model, args.api_base, args.api_key) diff --git a/penify_hook/file_analyzer.py b/penify_hook/file_analyzer.py index 35d2b99..8ee49fa 100644 --- a/penify_hook/file_analyzer.py +++ b/penify_hook/file_analyzer.py @@ -25,12 +25,17 @@ def __init__(self, file_path: str, api_client: APIClient): def process_file(self, file_path, pbar, new_param: str = ""): - """Processes a file by validating its extension, reading content, generating documentation, - and writing changes back to the file. + """Processes a file by validating its extension, reading content, + generating documentation, and writing changes back to the file. The function + performs several stages of processing: 1. Validates the file's extension to + ensure it is supported. 2. Reads the content of the file. 3. Sends the file + content for documentation generation. 4. Writes the generated documentation + back to the file if there are changes. Args: file_path (str): The path of the file to be processed. - pbar (tqdm.tqdm): A progress bar object to update the status of processing stages.""" + pbar (tqdm.tqdm): A progress bar object to update the status of processing stages. + new_param (str?): An additional parameter for future use. Defaults to an empty string.""" file_abs_path = os.path.join(os.getcwd(), file_path) file_extension = os.path.splitext(file_path)[1].lower() @@ -94,14 +99,20 @@ def process_file(self, file_path, pbar, new_param: str = ""): return False def print_processing(self, file_path): - """Prints a formatted message indicating that a file is being processed.""" + """Prints a message indicating that a file is being processed.""" formatted_path = format_file_path(file_path) print(f"\n{format_highlight(f'Processing file: {formatted_path}')}") def run(self): # Create a progress bar with appropriate stages - """Runs the documentation process with a progress bar.""" + """Runs the documentation process with a progress bar. + + This method orchestrates the documentation process by creating a progress bar, + processing the file, and handling exceptions to ensure the progress bar + completes properly. It updates the progress bar through various stages and + provides feedback based on the result of the file processing. + """ stages = ["Validating", "Reading content", "Documenting", "Writing changes", "Completed"] pbar, _ = create_stage_progress_bar(stages, f"Starting documenting") diff --git a/penify_hook/folder_analyzer.py b/penify_hook/folder_analyzer.py index 15ec20f..b2da49b 100644 --- a/penify_hook/folder_analyzer.py +++ b/penify_hook/folder_analyzer.py @@ -12,22 +12,17 @@ def __init__(self, dir_path: str, api_client: APIClient): super().__init__(dir_path, api_client) def list_all_files_in_dir(self, dir_path: str): - """List all non-hidden files in a directory and its subdirectories. + """List all non-hidden files in a directory and its subdirectories. + This function recursively traverses the specified directory and its - subdirectories, collecting paths of all non-hidden files. It filters out - hidden directories and files (those starting with a dot) to ensure only - visible files are returned. - + subdirectories, collecting paths of all non-hidden files. It filters out hidden + directories and files (those starting with a dot) to ensure only visible files + are returned. + Args: - dir_path (str): The path to the directory whose files and subdirectory files need to be - listed. - - Returns: - list: A list containing the full paths of all non-hidden files within the - specified directory and its subdirectories. + dir_path (str): The path to the directory whose files and subdirectory files need to be listed. """ - files = [] for dirpath, dirnames, filenames in os.walk(dir_path): dirnames[:] = [d for d in dirnames if not d.startswith(".")] @@ -38,17 +33,7 @@ def list_all_files_in_dir(self, dir_path: str): return files def run(self): - """Run the post-commit hook. - - This function processes all files in a specified directory using a - progress bar. It lists all files, initializes a `FileAnalyzerGenHook` - for each file, and runs it. Errors during processing of individual files - are caught and logged, but do not stop the processing of other files. A - progress bar is displayed indicating the number of files processed. - - Args: - self (PostCommitHook): The instance of the post-commit hook class. - """ + """Run the post-commit hook and process files with a progress bar.""" try: file_list = self.list_all_files_in_dir(self.dir_path) total_files = len(file_list) diff --git a/penify_hook/git_analyzer.py b/penify_hook/git_analyzer.py index f9e7127..32d4f5b 100644 --- a/penify_hook/git_analyzer.py +++ b/penify_hook/git_analyzer.py @@ -21,17 +21,7 @@ def __init__(self, repo_path: str, api_client: APIClient): super().__init__(repo_path, api_client) def get_modified_files_in_last_commit(self): - """Get the list of files modified in the last commit. - - This function retrieves the files that were modified in the most recent - commit of the repository. It accesses the last commit and iterates - through the differences to compile a list of unique file paths that were - changed. The function returns this list for further processing or - analysis. - - Returns: - list: A list of file paths that were modified in the last commit. - """ + """Get the list of files modified in the last commit.""" last_commit = self.repo.head.commit modified_files = [] for diff in last_commit.diff('HEAD~1'): @@ -41,18 +31,15 @@ def get_modified_files_in_last_commit(self): def get_modified_lines(self, diff_text): """Extract modified line numbers from a diff text. - - This function processes a diff text to identify and extract the line - numbers that have been modified. It distinguishes between added and - deleted lines and keeps track of the current line number as it parses - through the diff. The function handles hunk headers and ensures that any - deletions at the end of the file are also captured. - + + This function processes a diff text to identify and extract the line numbers + that have been modified. It distinguishes between added and deleted lines and + keeps track of the current line number as it parses through the diff. The + function handles hunk headers and ensures that any deletions at the end of the + file are also captured. + Args: diff_text (str): A string containing the diff text to be processed. - - Returns: - list: A sorted list of unique line numbers that have been modified. """ modified_lines = [] current_line = 0 @@ -89,23 +76,18 @@ def get_modified_lines(self, diff_text): return sorted(set(modified_lines)) # Remove duplicates and sort def process_file(self, file_path): - """Process a file by checking its type, reading its content, and sending it - to an API. - - This method constructs the absolute path of the specified file and - verifies if the file has a valid extension. If the file type is - supported, it reads the content of the file and retrieves the - differences from the last commit in the repository. If changes are - detected, it sends the file content along with the modified lines to an - API for further processing. If the API response indicates no changes, - the original file will not be overwritten. - + """Processes a file by checking its type, reading its content, and sending it to + an API. + + This method constructs the absolute path of the specified file and verifies if + the file has a valid extension. If the file type is supported, it reads the + content of the file and retrieves the differences from the last commit in the + repository. If changes are detected, it sends the file content along with the + modified lines to an API for further processing. If the API response indicates + no changes, the original file will not be overwritten. + Args: file_path (str): The relative path to the file to be processed. - - Returns: - bool: True if the file was successfully processed and updated, False - otherwise. """ file_abs_path = os.path.join(self.repo_path, file_path) file_extension = os.path.splitext(file_path)[1].lower() @@ -151,15 +133,14 @@ def process_file(self, file_path): def run(self): """Run the post-commit hook. - - This method retrieves the list of modified files from the last commit - and processes each file. It stages any files that have been modified - during processing and creates an auto-commit if changes were made. A - progress bar is displayed to indicate the processing status of each - file. The method handles any exceptions that occur during file - processing, printing an error message for each file that fails to - process. If any modifications are made to the files, an auto-commit is - created to save those changes. + + This method retrieves the list of modified files from the last commit and + processes each file. It stages any files that have been modified during + processing and creates an auto-commit if changes were made. A progress bar is + displayed to indicate the processing status of each file. The method handles + any exceptions that occur during file processing, printing an error message for + each file that fails to process. If any modifications are made to the files, an + auto-commit is created to save those changes. """ logger.info("Starting doc_gen_hook processing") print_info("Starting doc_gen_hook processing") diff --git a/penify_hook/jira_client.py b/penify_hook/jira_client.py index 56d2e22..36e7ac2 100644 --- a/penify_hook/jira_client.py +++ b/penify_hook/jira_client.py @@ -44,37 +44,12 @@ def __init__(self, jira_url: str = None, jira_user: str = None, jira_api_token: self.jira_client = None def is_connected(self) -> bool: - """Check if the JIRA client is connected. - - This function verifies whether the JIRA client has successfully - established a connection. It returns `True` if the client is connected, - and `False` otherwise. - - Returns: - bool: True if the JIRA client is connected, False otherwise - """ + """Check if the JIRA client is connected.""" return self.jira_client is not None def extract_issue_keys_from_branch(self, branch_name: str) -> List[str]: - """Extracts JIRA issue keys from a branch name. - - This function searches through a given git branch name to find and - return any JIRA issue keys that match the pattern. Common conventions - for JIRA issue keys in branch names include: - - feature/PROJECT-123-description - bugfix/PROJECT-123-fix-something - - hotfix/PROJECT-123/short-desc - - Args: - branch_name (str): The name of the git branch to search for JIRA issue keys. - - Returns: - List[str]: A list of unique JIRA issue keys found in the branch name. - - Examples: - extract_issue_keys_from_branch("feature/PROJ-456-add-new-feature") - # Output: ['PROJ-456'] - """ # Common JIRA issue key pattern: PROJECT-123 + """Extracts unique JIRA issue keys from a branch name.""" pattern = r'[A-Z][A-Z0-9_]+-[0-9]+' matches = re.findall(pattern, branch_name) if matches: @@ -82,44 +57,25 @@ def extract_issue_keys_from_branch(self, branch_name: str) -> List[str]: return list(set(matches)) # Remove duplicates def extract_issue_keys(self, text: str) -> List[str]: - """Extract JIRA issue keys from a given text. - - This function searches through the provided text to find and return all - unique JIRA issue keys. A JIRA issue key typically follows the pattern - of PROJECT-123, where PROJECT is alphanumeric and consists of at least - one uppercase letter followed by one or more alphanumeric characters, - and 123 is a numeric sequence. - - Args: - text (str): The text in which to search for JIRA issue keys. - - Returns: - List[str]: A list of unique JIRA issue keys found in the text. - """ # Common JIRA issue key pattern: PROJECT-123 + """Extract unique JIRA issue keys from the given text.""" pattern = r'[A-Z][A-Z0-9_]+-[0-9]+' matches = re.findall(pattern, text) return list(set(matches)) # Remove duplicates def get_issue_details(self, issue_key: str) -> Optional[Dict[str, Any]]: """Retrieve details of a JIRA issue based on its key. - - This function fetches detailed information about a specified JIRA issue - using the provided issue key. It checks if the JIRA client is connected - before attempting to retrieve the issue. If the client is not connected, - it logs a warning and returns `None`. The function then attempts to - fetch the issue from the JIRA server and constructs a dictionary - containing various details about the issue such as its key, summary, - status, description, assignee, reporter, type, priority, and URL. If any - errors occur during this process, they are logged, and `None` is - returned. - + + This function fetches detailed information about a specified JIRA issue using + the provided issue key. It first checks if the JIRA client is connected; if + not, it logs a warning and returns `None`. If connected, it attempts to + retrieve the issue from the JIRA server. On success, it constructs and returns + a dictionary containing various details such as the issue's key, summary, + status, description, assignee, reporter, type, priority, and URL. Errors during + this process are logged, and `None` is returned. + Args: issue_key (str): The JIRA issue key (e.g., "PROJECT-123"). - - Returns: - Dict[str, Any] or None: A dictionary containing the details of the JIRA - issue if found, otherwise `None`. """ if not self.is_connected(): logging.warning("JIRA client not connected") @@ -143,15 +99,7 @@ def get_issue_details(self, issue_key: str) -> Optional[Dict[str, Any]]: return None def add_comment(self, issue_key: str, comment: str) -> bool: - """Add a comment to a JIRA issue. - - Args: - issue_key (str): JIRA issue key (e.g., "PROJECT-123") - comment (str): Comment text to add - - Returns: - bool: True if the comment was added successfully, False otherwise - """ + """Adds a comment to a JIRA issue.""" if not self.is_connected(): logging.warning("JIRA client not connected") return False @@ -166,11 +114,16 @@ def add_comment(self, issue_key: str, comment: str) -> bool: def update_issue_status(self, issue_key: str, transition_name: str) -> bool: """Update the status of a JIRA issue. - + + This method checks if the JIRA client is connected, retrieves available + transitions for the given issue, finds the transition ID by name, and updates + the issue's status accordingly. If any step fails or the specified transition + is not found, appropriate logs are generated, and False is returned. + Args: issue_key (str): The key of the JIRA issue to be updated. transition_name (str): The name of the desired transition. - + Returns: bool: True if the status was successfully updated, False otherwise. """ @@ -202,20 +155,25 @@ def update_issue_status(self, issue_key: str, transition_name: str) -> bool: return False def format_commit_message_with_jira_info(self, commit_title: str, commit_description: str, issue_keys: List[str] = None) -> tuple: + # If no issue keys provided, extract them from title and description """Format commit message with JIRA issue information. - + + This function updates the provided commit title and description by + incorporating JIRA issue keys. If no issue keys are supplied, it extracts them + from the commit title and description. It then formats the commit title to + include the first issue key if not already present and appends detailed + information about each issue to the commit description. + Args: commit_title (str): The original commit title. commit_description (str): The original commit description. issue_keys (List[str]?): A list of JIRA issue keys to include in the commit message. If not - provided, issue keys will be extracted from both the title and the - description. - + provided, issue keys will be extracted from both the title and the description. + Returns: tuple: A tuple containing the updated commit title and description with JIRA information included. """ - # If no issue keys provided, extract them from title and description if not issue_keys: title_keys = self.extract_issue_keys(commit_title) desc_keys = self.extract_issue_keys(commit_description) @@ -252,19 +210,18 @@ def format_commit_message_with_jira_info(self, commit_title: str, commit_descrip return updated_title, updated_description def get_detailed_issue_context(self, issue_key: str) -> Dict[str, Any]: - """Retrieve comprehensive details about a JIRA issue including context for - better commit messages. - - This function fetches detailed information from a specified JIRA issue - and constructs a dictionary containing various context fields such as - the issue summary, description, type, status, priority, comments, URL, - and additional custom fields like acceptance criteria and sprint - information. If any errors occur during the fetching process, - appropriate warnings or errors are logged. - + """Retrieve comprehensive details about a JIRA issue including context for better + commit messages. + + This function fetches detailed information from a specified JIRA issue and + constructs a dictionary containing various context fields such as the issue + summary, description, type, status, priority, comments, URL, and additional + custom fields like acceptance criteria and sprint information. It handles + errors by logging appropriate warnings or errors. + Args: issue_key (str): The JIRA issue key (e.g., "PROJECT-123"). - + Returns: Dict[str, Any]: A dictionary containing business and technical context from the issue. """ @@ -336,19 +293,18 @@ def get_detailed_issue_context(self, issue_key: str) -> Dict[str, Any]: return {} def get_commit_context_from_issues(self, issue_keys: List[str]) -> Dict[str, Any]: - """Gather contextual information from JIRA issues to improve commit - messages. - - This function processes a list of JIRA issue keys, retrieves detailed - context for each issue, and aggregates it into a dictionary that can be - used to enhance commit messages. It first retrieves the primary issue - (the first key in the list) and then gathers basic details for any - related issues. The resulting context includes information from both the - primary and related issues, along with all issue keys. - + """Gather contextual information from JIRA issues to improve commit messages. + + This function processes a list of JIRA issue keys, retrieves detailed context + for each issue, and aggregates it into a dictionary that can be used to enhance + commit messages. It first retrieves the primary issue (the first key in the + list) and then gathers basic details for any related issues. The resulting + context includes information from both the primary and related issues, along + with all issue keys. + Args: issue_keys: List of JIRA issue keys to gather information from - + Returns: Dict containing business and technical context from the issues """ @@ -375,14 +331,24 @@ def get_commit_context_from_issues(self, issue_keys: List[str]) -> Dict[str, Any return context def enhance_commit_message(self, title: str, description: str, issue_keys: List[str]) -> tuple: - """Enhance a commit message with business and technical context from JIRA - issues. - + """Enhance a commit message with business and technical context from JIRA issues. + + This function first checks if the list of issue keys is empty or if there is no + connection. If so, it returns the original title and description without + modification. It then retrieves context information from the specified JIRA + issues. If the primary issue is missing, it formats the commit message with + basic JIRA info. The function enhances the commit title by prefixing it with + the primary issue key if not already included. It appends a business context + section to the description, including details like issue type, status, + priority, sprint, acceptance criteria, and a condensed issue description. If + comments are available, they are added as technical notes. Finally, related + issues are listed. + Args: title (str): Original commit title. description (str): Original commit description. issue_keys (List[str]): List of JIRA issue keys to include in the enhanced commit message. - + Returns: tuple: A tuple containing the enhanced commit title and description with added context from JIRA issues. diff --git a/penify_hook/llm_client.py b/penify_hook/llm_client.py index 2066467..cf86e1b 100644 --- a/penify_hook/llm_client.py +++ b/penify_hook/llm_client.py @@ -31,35 +31,32 @@ def __init__(self, model: str = None, api_base: str = None, api_key: str = None) @property def litellm(self): - """Lazy load litellm only when needed.""" + """Returns the litellm module, loading it if necessary.""" if self._litellm is None: import litellm self._litellm = litellm return self._litellm def generate_commit_summary(self, diff: str, message: str, generate_description: bool, repo_details: Dict, jira_context: Dict = None) -> Dict: - """Generate a commit summary using the LLM. - - This function generates a concise and descriptive commit summary based - on the provided Git diff, user instructions, repository details, and - optional JIRA context. It constructs a prompt for the LLM to produce a - commit title and an optional detailed description, adhering to Semantic - Commit Messages guidelines. If the JIRA context is provided, it enriches - the prompt with relevant issue information. - + """Generate a concise and descriptive commit summary based on Git diff, user + instructions, repository details, and optional JIRA context. + + This function constructs a prompt for an LLM to produce a commit title and, if + requested, a detailed description. The summary adheres to Semantic Commit + Messages guidelines. If JIRA context is provided, it enriches the prompt with + relevant issue information. + Args: diff (str): Git diff of changes. message (str): User-provided commit message or instructions. - generate_description (bool): Flag indicating whether to include a detailed description in the - summary. + generate_description (bool): Flag indicating whether to include a detailed description in the summary. repo_details (Dict): Details about the repository. jira_context (Dict?): Optional JIRA issue context to enhance the summary. - + Returns: Dict: A dictionary containing the title and description for the commit. If - generate_description is False, - the 'description' key may be absent. - + `generate_description` is False, the 'description' key may be absent. + Raises: ValueError: If the LLM model is not configured. """ diff --git a/penify_hook/login_command.py b/penify_hook/login_command.py index 2d97cc3..3f1dc0c 100644 --- a/penify_hook/login_command.py +++ b/penify_hook/login_command.py @@ -1,22 +1,12 @@ def setup_login_parser(parser): + """Set up command-line arguments for login.""" parser.add_argument("--token", help="Specify API token directly") # Add all other necessary arguments for login command def handle_login(args): - """Handle the login command. - - Initiates a user login process by calling the `login` function from the - `penify_hook.commands.auth_commands` module using predefined constants - `API_URL` and `DASHBOARD_URL` from the `penify_hook.constants` module. - - Args: - args (argparse.Namespace): Parsed arguments containing necessary parameters for the login command. - - Returns: - None: This function does not return any value; it is expected to handle the - login process internally. - """ + """Initiates a user login process using predefined constants and the `login` + function.""" from penify_hook.constants import API_URL, DASHBOARD_URL from penify_hook.commands.auth_commands import login diff --git a/penify_hook/main.py b/penify_hook/main.py index 35c332b..a8e5e66 100644 --- a/penify_hook/main.py +++ b/penify_hook/main.py @@ -4,19 +4,16 @@ def main(): - """Main function to handle command-line interface (CLI) interactions with - Penify services. + """Main function to handle command-line interface (CLI) interactions with Penify + services. + This tool provides a command-line interface for generating smart commit - messages, configuring local-LLM and JIRA, and generating code - documentation. It supports basic commands that do not require login and - advanced commands that require user authentication. The `--version` flag - can be used to display the version information. - - Returns: - int: Exit status of the program (0 for success, 1 for error). + messages, configuring local-LLM and JIRA, and generating code documentation. It + supports basic commands that do not require login and advanced commands that + require user authentication. The `--version` flag can be used to display the + version information. """ - parser = argparse.ArgumentParser( description="""Penify CLI tool for: 1. AI commit message generation with JIRA integration to enhance commit messages. diff --git a/penify_hook/ui_utils.py b/penify_hook/ui_utils.py index 5fce269..aa828e6 100644 --- a/penify_hook/ui_utils.py +++ b/penify_hook/ui_utils.py @@ -26,158 +26,52 @@ PROCESSING_SYMBOL = "⟳" def format_info(message): - """Format an informational message with appropriate color. - - Args: - message (str): The text of the informational message to be formatted. - - Returns: - str: The formatted informational message with the specified color. - """ + """Format an informational message with appropriate color.""" return f"{INFO_COLOR}{message}{Style.RESET_ALL}" def format_success(message): - """Format a success message with appropriate color. - - This function takes a message as input and wraps it in ANSI escape codes - to display it in green, indicating a successful operation. The - Style.RESET_ALL is applied at the end to ensure that any subsequent text - is displayed in the default style. - - Args: - message (str): The message to be formatted as a success message. - - Returns: - str: The formatted success message with green color and reset style. - """ + """Formats a success message with green color and reset style.""" return f"{SUCCESS_COLOR}{message}{Style.RESET_ALL}" def format_warning(message): - """Format a warning message with appropriate color. - - Args: - message (str): The warning message to be formatted. - - Returns: - str: The formatted warning message with the specified color. - """ + """Format a warning message with appropriate color.""" return f"{WARNING_COLOR}{message}{Style.RESET_ALL}" def format_error(message): - """Format an error message with appropriate color. - - This function takes a plain error message and wraps it in ANSI escape - codes to apply the specified error color, ensuring that the error - message is visually distinct when output. The function supports various - error colors defined by constants like `ERROR_COLOR`. - - Args: - message (str): The plain text error message to be formatted. - - Returns: - str: The formatted error message with the error color applied. - """ + """Format an error message with the specified error color.""" return f"{ERROR_COLOR}{message}{Style.RESET_ALL}" def format_highlight(message): - """Format a highlighted message with appropriate color. - - Args: - message (str): The message to be formatted and highlighted. - - Returns: - str: The formatted message with applied highlight style. - """ + """Format a highlighted message with appropriate color.""" return f"{HIGHLIGHT_COLOR}{message}{Style.RESET_ALL}" def format_file_path(file_path): - """Format a file path with appropriate color. - - This function takes a file path as input and wraps it in ANSI escape - codes to apply a warning color. The original file path is then reset to - default style using Style.RESET_ALL. - - Args: - file_path (str): The file path to be formatted. - - Returns: - str: The formatted file path with the warning color applied. - """ + """Format a file path with a warning color.""" return f"{WARNING_COLOR}{file_path}{Style.RESET_ALL}" def print_info(message): - """Print an informational message with appropriate formatting. - - This function takes a string message as input and prints it in a - formatted manner. It utilizes the `format_info` function to apply any - necessary formatting before printing. - - Args: - message (str): The message to be printed. - """ + """Prints an informational message with formatting.""" print(format_info(message)) def print_success(message): - """Print a formatted success message. - - This function takes a string `message` and prints it as a formatted - success message. The formatting includes adding a prefix "Success: " to - the message and enclosing it within asterisks for emphasis. - - Args: - message (str): The message to be printed as a success message. - """ + """Prints a formatted success message.""" print(format_success(message)) def print_warning(message): - """Print a warning message with appropriate formatting. - - This function takes a warning message as input and prints it with - formatted output. The formatting may include color, timestamp, or other - styles to emphasize that it is a warning. - - Args: - message (str): The warning message to be printed. - """ + """Prints a warning message with formatted output.""" print(format_warning(message)) def print_error(message): - """Print an error message with appropriate formatting. - - This function takes a string message, formats it as an error message, - and then prints it. The formatting typically includes prefixing the - message with "Error: " to clearly indicate that it is an error. - - Args: - message (str): The error message to be printed. - """ + """Print an error message with appropriate formatting.""" print(format_error(message)) def print_processing(file_path): - """Print a processing message for a specified file. - - This function takes a file path, formats it using `format_file_path`, - and then prints a formatted message indicating that the file is being - processed. The formatted path is highlighted using `format_highlight`. - - Args: - file_path (str): The path of the file to be processed. - """ + """Print a processing message for a specified file.""" formatted_path = format_file_path(file_path) print(f"\n{format_highlight(f'Processing file: {formatted_path}')}") def print_status(status, message): - """Print a status message with an appropriate symbol. - - This function takes a status and a message, then prints them with a - colored symbol that corresponds to the given status. The available - statuses are 'success', 'warning', 'error', and any other value will - default to a processing indicator. - - Args: - status (str): The status type ('success', 'warning', 'error') or another string. - message (str): The message to be displayed along with the symbol. - """ + """Print a status message with an appropriate symbol.""" if status == 'success': print(f" {SUCCESS_COLOR}{SUCCESS_SYMBOL} {message}{Style.RESET_ALL}") elif status == 'warning': @@ -188,16 +82,7 @@ def print_status(status, message): print(f" {PROCESSING_SYMBOL} {message}") def create_progress_bar(total, desc="Processing", unit="item"): - """Create a tqdm progress bar with consistent styling. - - Args: - total (int): Total number of items to process. - desc (str): Description for the progress bar. Defaults to "Processing". - unit (str): Unit label for the progress items. Defaults to "item". - - Returns: - tqdm: A configured tqdm progress bar instance. - """ + """Create a tqdm progress bar with consistent styling.""" return tqdm( total=total, desc=format_info(desc), @@ -207,20 +92,7 @@ def create_progress_bar(total, desc="Processing", unit="item"): ) def create_stage_progress_bar(stages, desc="Processing"): - """Create a tqdm progress bar for processing stages with consistent - styling. - - This function initializes and returns a tqdm progress bar object for - tracking the progress through a series of stages. It also provides a - description for the progress bar to enhance its usability. - - Args: - stages (list): A list of strings representing individual stages in the process. - desc (str?): A description for the progress bar. Defaults to "Processing". - - Returns: - tuple: A tuple containing the tqdm progress bar object and the list of stages. - """ + """Create a tqdm progress bar for processing stages with consistent styling.""" pbar = tqdm( total=len(stages), desc=format_info(desc), @@ -231,18 +103,8 @@ def create_stage_progress_bar(stages, desc="Processing"): return pbar, stages def update_stage(pbar, stage_name): - """Update the progress bar with a new stage name. - - This function updates the provided tqdm progress bar to reflect the - current stage of a process. It clears any existing postfix and sets a - new description based on the provided stage name. The display is then - refreshed to ensure that the update is visible immediately. - - Args: - pbar (tqdm): The progress bar object to be updated. - stage_name (str): A string representing the current stage of the process. - """ # Force refresh with a custom description and ensure it's visible + """Update the progress bar with a new stage name.""" pbar.set_postfix_str("") # Clear any existing postfix pbar.set_description_str(f"{format_info(stage_name)}") pbar.refresh() # Force refresh the display diff --git a/penify_hook/utils.py b/penify_hook/utils.py index 0d1565a..22c93ea 100644 --- a/penify_hook/utils.py +++ b/penify_hook/utils.py @@ -11,7 +11,23 @@ class GitRepoNotFoundError(Exception): def get_repo_details(repo: Repo): - """Determine the details of a repository including its remote URL, hosting service, organization name, and repository name.""" + """Determine the details of a repository including its remote URL, hosting + service, organization name, and repository name. + + This function extracts the remote URL from the given Git repository object and + determines the hosting service (e.g., GitHub, Azure DevOps, Bitbucket, GitLab). + It then parses the URL to extract the organization name and repository name. If + the URL does not match any known hosting service pattern, it sets the hosting + service as "Unknown". The function handles exceptions that may occur during + this process and logs an error message if needed. + + Args: + repo (Repo): A GitPython Repo object representing the local git repository. + + Returns: + dict: A dictionary containing the organization name, repository name, and hosting + service. + """ remote_url = None hosting_service = "Unknown" org_name = None @@ -72,7 +88,8 @@ def recursive_search_git_folder(folder_path): def find_git_parent(path): - """Traverse up from the given path to find the nearest directory containing a .git subdirectory.""" + """Traverse up from the given path to find the nearest directory containing a .git + subdirectory.""" current_dir = os.path.abspath(path) while current_dir != os.path.dirname(current_dir): # Traverse up to the root directory