Skip to content

Commit adc2d99

Browse files
authored
Advaced cli (#35)
* feat: Consolidate configuration subcommands for LLM and JIRA settings * feat(hook_commands): enhance post-commit hook to automatically generate documentation for changed files * feat: Rename and restructure documentation generation commands for clarity and consistency * feat: Enhance CLI tool description and help messages for improved user guidance * feat: Remove obsolete snorkell-auto-documentation workflow * feat: Update README and enhance CLI command descriptions for clarity and usability * feat: Simplify argument names in documentation generation CLI for clarity * feat: Simplify argument names and improve documentation generation command for clarity * feat: Enhance 'docgen' command description and improve help messages for clarity * feat: Refactor documentation generation logic to support file and folder inputs, enhancing error handling and improving git folder detection * feat: Implement logging in GitDocGenHook for better error tracking and process visibility * feat: Improve documentation generation process with enhanced logging and error handling * feat: Enhance documentation with detailed function descriptions and argument explanations * feat: Add colored terminal output for improved visibility in doc_gen_hook processing * feat: Remove unnecessary print statements in documentation generation and add colorama as a dependency * feat: Enhance file processing with logging, progress bar, and colored output * feat: Refactor doc_gen_hook for improved UI output and progress tracking * feat: Integrate tqdm for enhanced progress tracking in file processing * feat: Update progress tracking in file analysis stages for improved clarity * feat: Improve error handling and progress tracking in file processing * feat: Enhance error reporting in API client and improve error handling in file analyzer * feat: Add repository details retrieval and refactor git folder search utility
1 parent 0a7084e commit adc2d99

12 files changed

Lines changed: 545 additions & 211 deletions

File tree

.github/workflows/snorkell-auto-documentation.yml

Lines changed: 0 additions & 19 deletions
This file was deleted.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Penify CLI
22

3-
Penify CLI is a command-line tool for managing Git hooks, generating documentation, and streamlining the development workflow. It provides functionality to install and uninstall Git post-commit hooks, generate documentation for files or folders, perform Git commits with automated message generation, and manage authentication.
3+
Penify CLI is a command-line tool for managing Git commits, generating documentation, and streamlining the development workflow. It provides AI-powered commit message generation, JIRA integration, and documentation tools.
44

55
## Installation
66

@@ -49,7 +49,7 @@ penifycli uninstall-hook -l /path/to/git/repo
4949
To generate documentation for files or folders:
5050

5151
```bash
52-
penifycli doc-gen [options]
52+
penifycli docgen [options]
5353
```
5454

5555
Options:

penify_hook/api_client.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@ def send_file_for_docstring_generation(self, file_name, content, line_numbers, r
4141
response = response.json()
4242
return response.get('modified_content')
4343
else:
44-
print(f"Response: {response.status_code}")
45-
print(f"Error: {response.text}")
46-
return content
44+
error_message = response.json().get('detail')
45+
if not error_message:
46+
error_message = response.text
47+
48+
raise Exception(f"API Error: {error_message}")
4749

4850
def generate_commit_summary(self, git_diff, instruction: str = "", repo_details = None, jira_context: dict = None):
4951
"""Generate a commit summary by sending a POST request to the API endpoint.
@@ -82,9 +84,9 @@ def generate_commit_summary(self, git_diff, instruction: str = "", repo_details
8284
response = response.json()
8385
return response
8486
else:
85-
print(f"Response: {response.status_code}")
86-
print(f"Error: {response.text}")
87-
return None
87+
# print(f"Response: {response.status_code}")
88+
# print(f"Error: {response.text}")
89+
raise Exception(f"API Error: {response.text}")
8890
except Exception as e:
8991
print(f"Error: {e}")
9092
return None

penify_hook/commands/config_commands.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import pkg_resources
77
from pathlib import Path
88
from threading import Thread
9+
import logging
910

1011
def save_llm_config(model, api_base, api_key):
1112
"""
@@ -267,13 +268,10 @@ def log_message(self, format, *args):
267268

268269
print("Configuration completed.")
269270

270-
def get_token(passed_token):
271+
def get_token():
271272
"""
272273
Get the token based on priority.
273274
"""
274-
if passed_token:
275-
return passed_token
276-
277275
import os
278276
env_token = os.getenv('PENIFY_API_TOKEN')
279277
if env_token:
Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,49 @@
1+
import os
12
import sys
23
from ..folder_analyzer import FolderAnalyzerGenHook
34
from ..file_analyzer import FileAnalyzerGenHook
45
from ..git_analyzer import GitDocGenHook
56
from ..api_client import APIClient
67

7-
def generate_doc(api_url, token, file_path=None, complete_folder_path=None, git_folder_path=None):
8-
"""
9-
Generates documentation based on the given parameters.
8+
def generate_doc(api_url, token, location=None):
9+
"""Generates documentation based on the given parameters.
10+
11+
This function initializes an API client using the provided API URL and
12+
token. It then generates documentation by analyzing the specified
13+
location, which can be a folder, a file, or the current working
14+
directory if no location is provided. The function handles different
15+
types of analysis based on the input location and reports any errors
16+
encountered during the process.
17+
18+
Args:
19+
api_url (str): The URL of the API to connect to for documentation generation.
20+
token (str): The authentication token for accessing the API.
21+
location (str?): The path to a specific file or folder to analyze.
22+
If not provided, the current working directory is used.
1023
"""
1124
api_client = APIClient(api_url, token)
12-
13-
if file_path:
25+
if location is None:
26+
current_folder_path = os.getcwd()
1427
try:
15-
analyzer = FileAnalyzerGenHook(file_path, api_client)
28+
analyzer = GitDocGenHook(current_folder_path, api_client)
1629
analyzer.run()
1730
except Exception as e:
1831
print(f"Error: {e}")
1932
sys.exit(1)
20-
elif complete_folder_path:
33+
34+
# if location is a file
35+
elif len(location.split('.')) > 1:
2136
try:
22-
analyzer = FolderAnalyzerGenHook(complete_folder_path, api_client)
37+
analyzer = FileAnalyzerGenHook(location, api_client)
2338
analyzer.run()
2439
except Exception as e:
2540
print(f"Error: {e}")
2641
sys.exit(1)
42+
2743
else:
2844
try:
29-
analyzer = GitDocGenHook(git_folder_path, api_client)
45+
analyzer = FolderAnalyzerGenHook(location, api_client)
3046
analyzer.run()
3147
except Exception as e:
3248
print(f"Error: {e}")
33-
sys.exit(1)
49+
sys.exit(1)

penify_hook/commands/hook_commands.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
HOOK_FILENAME = "post-commit"
55
HOOK_TEMPLATE = """#!/bin/sh
66
# This is a post-commit hook generated by penifycli.
7+
# Automatically generates documentation for changed files after each commit.
78
8-
penifycli -t {token} -gf {git_folder_path}
9+
penifycli docgen -gf {git_folder_path} -t {token}
910
"""
1011

1112
def install_git_hook(location, token):
1213
"""
13-
Install a post-commit hook in the specified location.
14+
Install a post-commit hook in the specified location that generates documentation
15+
for changed files after each commit.
1416
"""
1517
hooks_dir = Path(location) / ".git/hooks"
1618
hook_path = hooks_dir / HOOK_FILENAME
@@ -24,6 +26,7 @@ def install_git_hook(location, token):
2426
hook_path.chmod(0o755) # Make the hook script executable
2527

2628
print(f"Post-commit hook installed in {hook_path}")
29+
print(f"Documentation will now be automatically generated after each commit.")
2730

2831
def uninstall_git_hook(location):
2932
"""

penify_hook/file_analyzer.py

Lines changed: 114 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,32 @@
11
import os
2+
import sys
23
from git import Repo
4+
from tqdm import tqdm
5+
import time
6+
7+
from penify_hook.utils import get_repo_details, recursive_search_git_folder
38
from .api_client import APIClient
9+
import logging
10+
from .ui_utils import (
11+
format_highlight, print_info, print_success, print_warning, print_error,
12+
print_status, create_stage_progress_bar,
13+
update_stage, format_file_path
14+
)
15+
16+
# Set up logger
17+
logger = logging.getLogger(__name__)
418

519
class FileAnalyzerGenHook:
620
def __init__(self, file_path: str, api_client: APIClient):
721
self.file_path = file_path
22+
self.repo_path = recursive_search_git_folder(file_path)
23+
self.repo = Repo(self.repo_path)
24+
self.repo_details = get_repo_details(self.repo)
25+
self.relative_file_path = os.path.relpath(file_path)
826
self.api_client = api_client
927
self.supported_file_types = set(self.api_client.get_supported_file_types())
1028

11-
def process_file(self, file_path):
29+
def process_file(self, file_path, pbar):
1230
"""Process a file by reading its content and sending it to an API for
1331
processing.
1432
@@ -19,49 +37,126 @@ def process_file(self, file_path):
1937
2038
Args:
2139
file_path (str): The relative path to the file that needs to be processed.
40+
pbar (tqdm): Progress bar to update during processing.
2241
2342
Returns:
2443
bool: True if the file was processed successfully, False otherwise.
2544
"""
2645
file_abs_path = os.path.join(os.getcwd(), file_path)
2746
file_extension = os.path.splitext(file_path)[1].lower()
28-
47+
48+
# --- STAGE 1: Validating ---
49+
update_stage(pbar, "Validating")
2950
if not file_extension:
30-
print(f"File {file_path} has no extension. Skipping.")
51+
logger.info(f"File {file_path} has no extension. Skipping.")
3152
return False
3253

3354
file_extension = file_extension[1:] # Remove the leading dot
3455

3556
if file_extension not in self.supported_file_types:
36-
print(f"File type {file_extension} is not supported. Skipping {file_path}.")
57+
logger.info(f"File type {file_extension} is not supported. Skipping {file_path}.")
3758
return False
3859

39-
with open(file_abs_path, 'r') as file:
40-
content = file.read()
60+
# Update progress bar to indicate we're moving to next stage
61+
pbar.update(1)
62+
63+
# --- STAGE 2: Reading content ---
64+
update_stage(pbar, "Reading content")
65+
try:
66+
with open(file_abs_path, 'r') as file:
67+
content = file.read()
68+
except Exception as e:
69+
logger.error(f"Error reading file {file_path}: {str(e)}")
70+
return False
4171

42-
modified_lines = [i for i in range(len(content.splitlines()))]
72+
modified_lines = [i for i in range(len(content.splitlines()))]
4373

44-
# Send data to API
45-
response = self.api_client.send_file_for_docstring_generation(file_path, content, modified_lines)
74+
# Update progress bar to indicate we're moving to next stage
75+
pbar.update(1)
4676

47-
# If the response is successful, replace the file content
48-
with open(file_abs_path, 'w') as file:
49-
file.write(response)
50-
print(f"File [{self.file_path}] processed successfully.")
51-
77+
# --- STAGE 3: Documenting ---
78+
update_stage(pbar, "Documenting")
79+
80+
response = self.api_client.send_file_for_docstring_generation(file_path, content, modified_lines, self.repo_details)
81+
82+
if response is None:
83+
return False
84+
85+
if response == content:
86+
logger.info(f"No changes needed for {file_path}")
87+
return False
88+
89+
# Update progress bar to indicate we're moving to next stage
90+
pbar.update(1)
91+
92+
# --- STAGE 4: Writing changes ---
93+
update_stage(pbar, "Writing changes")
94+
95+
try:
96+
with open(file_abs_path, 'w') as file:
97+
file.write(response)
98+
logger.info(f"Updated file {file_path} with generated documentation")
99+
100+
# Mark final stage as complete
101+
pbar.update(1)
102+
return True
103+
except Exception as e:
104+
logger.error(f"Error writing file {file_path}: {str(e)}")
105+
return False
106+
107+
def print_processing(self, file_path):
108+
"""Print a processing message for a file."""
109+
formatted_path = format_file_path(file_path)
110+
print(f"\n{format_highlight(f'Processing file: {formatted_path}')}")
52111

53112
def run(self):
54113
"""Run the post-commit hook.
55114
56115
This method executes the post-commit hook by processing a specified
57116
file. It attempts to process the file located at `self.file_path`. If an
58117
error occurs during the processing, it catches the exception and prints
59-
an error message indicating that the file was not processed.
118+
an error message indicating that the file was not processed. The method
119+
displays a progress bar and colored output to provide visual feedback on
120+
the processing status.
60121
"""
122+
123+
# Create a progress bar with appropriate stages
124+
stages = ["Validating", "Reading content", "Documenting", "Writing changes", "Completed"]
125+
pbar, _ = create_stage_progress_bar(stages, f"Starting documenting")
126+
61127
try:
62-
self.process_file(self.file_path)
63-
# Stage the modified file
128+
# Print a clear indication of which file is being processed
129+
# self.print_processing(self.file_path)
64130

131+
# Process the file
132+
result = self.process_file(self.file_path, pbar)
133+
134+
# Ensure all stages are completed
135+
remaining_steps = len(stages) - pbar.n
136+
pbar.update(remaining_steps)
137+
138+
139+
# Display appropriate message based on result
140+
remaining = len(stages) - pbar.n
141+
if remaining > 0:
142+
pbar.update(remaining)
143+
update_stage(pbar, "Complete")
144+
pbar.clear()
145+
pbar.close()
146+
65147
except Exception as e:
66-
print(f"File [{self.file_path}] was not processed.")
67-
148+
remaining = len(stages) - pbar.n
149+
if remaining > 0:
150+
pbar.update(remaining)
151+
update_stage(pbar, "Complete")
152+
pbar.clear()
153+
pbar.close()
154+
print_status('error', e)
155+
sys.exit(1)
156+
157+
# Ensure progress bar completes even on error
158+
if result:
159+
print_success(f"\n✓ Documentation updated for {self.relative_file_path}")
160+
else:
161+
print_success(f"\n✓ No changes needed for {self.relative_file_path}")
162+

0 commit comments

Comments
 (0)