roastbuddy 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- roastbuddy/__init__.py +5 -0
- roastbuddy/cli.py +113 -0
- roastbuddy/core/__init__.py +1 -0
- roastbuddy/core/config.py +96 -0
- roastbuddy/core/database.py +198 -0
- roastbuddy/core/history.py +40 -0
- roastbuddy/core/initializer.py +61 -0
- roastbuddy/core/roaster.py +130 -0
- roastbuddy/core/share.py +18 -0
- roastbuddy/core/status.py +64 -0
- roastbuddy/core/streaks.py +22 -0
- roastbuddy/templates/__init__.py +1 -0
- roastbuddy/templates/praise_templates.py +113 -0
- roastbuddy/templates/roast_templates.py +87 -0
- roastbuddy/utils/__init__.py +1 -0
- roastbuddy/utils/code_analysis.py +164 -0
- roastbuddy/utils/copilot_utils.py +106 -0
- roastbuddy/utils/git_utils.py +201 -0
- roastbuddy-0.1.0.dist-info/METADATA +303 -0
- roastbuddy-0.1.0.dist-info/RECORD +24 -0
- roastbuddy-0.1.0.dist-info/WHEEL +5 -0
- roastbuddy-0.1.0.dist-info/entry_points.txt +2 -0
- roastbuddy-0.1.0.dist-info/licenses/LICENSE +21 -0
- roastbuddy-0.1.0.dist-info/top_level.txt +1 -0
roastbuddy/__init__.py
ADDED
roastbuddy/cli.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Main CLI entry point for RoastBuddy."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
console = Console()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
@click.version_option(version="0.1.0", prog_name="roastbuddy")
|
|
11
|
+
def main():
|
|
12
|
+
"""š„ RoastBuddy - Your witty coding companion!
|
|
13
|
+
|
|
14
|
+
Roasts, praises, and tracks your development journey.
|
|
15
|
+
"""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@main.command()
|
|
20
|
+
@click.option("--path", default=".", help="Path to git repository")
|
|
21
|
+
def init(path):
|
|
22
|
+
"""Initialize RoastBuddy in a git repository."""
|
|
23
|
+
from roastbuddy.core.initializer import initialize_roastbuddy
|
|
24
|
+
|
|
25
|
+
console.print("[bold cyan]š„ Initializing RoastBuddy...[/bold cyan]")
|
|
26
|
+
success = initialize_roastbuddy(path)
|
|
27
|
+
|
|
28
|
+
if success:
|
|
29
|
+
console.print("[bold green]ā
RoastBuddy is ready to roast![/bold green]")
|
|
30
|
+
console.print("\nš” Try: [bold]roastbuddy status[/bold]")
|
|
31
|
+
else:
|
|
32
|
+
console.print("[bold red]ā Failed to initialize RoastBuddy[/bold red]")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@main.command()
|
|
36
|
+
def status():
|
|
37
|
+
"""Show current streaks and recent activity."""
|
|
38
|
+
from roastbuddy.core.status import show_status
|
|
39
|
+
|
|
40
|
+
console.print("[bold cyan]š RoastBuddy Status[/bold cyan]\n")
|
|
41
|
+
show_status()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@main.command()
|
|
45
|
+
@click.option("--last", "-l", is_flag=True, help="Roast the last commit")
|
|
46
|
+
def roast(last):
|
|
47
|
+
"""Roast your code with humor."""
|
|
48
|
+
from roastbuddy.core.roaster import roast_code
|
|
49
|
+
|
|
50
|
+
console.print("[bold red]š„ Preparing your roast...[/bold red]\n")
|
|
51
|
+
roast_code(last_commit=last)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@main.command()
|
|
55
|
+
@click.option("--last", "-l", is_flag=True, help="Praise the last commit")
|
|
56
|
+
def praise(last):
|
|
57
|
+
"""Get some well-deserved praise!"""
|
|
58
|
+
from roastbuddy.core.roaster import praise_code
|
|
59
|
+
|
|
60
|
+
console.print("[bold green]⨠Preparing your praise...[/bold green]\n")
|
|
61
|
+
praise_code(last_commit=last)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@main.command()
|
|
65
|
+
@click.option("--type", "-t", type=click.Choice(["all", "clean", "daily", "test"]),
|
|
66
|
+
default="all", help="Streak type to display")
|
|
67
|
+
def streak(type):
|
|
68
|
+
"""View your coding streaks."""
|
|
69
|
+
from roastbuddy.core.streaks import show_streaks
|
|
70
|
+
|
|
71
|
+
console.print("[bold magenta]š„ Your Coding Streaks[/bold magenta]\n")
|
|
72
|
+
show_streaks(streak_type=type)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@main.command()
|
|
76
|
+
@click.option("--limit", "-n", default=10, help="Number of entries to show")
|
|
77
|
+
def history(limit):
|
|
78
|
+
"""View roast and praise history."""
|
|
79
|
+
from roastbuddy.core.history import show_history
|
|
80
|
+
|
|
81
|
+
console.print("[bold cyan]š RoastBuddy History[/bold cyan]\n")
|
|
82
|
+
show_history(limit=limit)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@main.command()
|
|
86
|
+
@click.option("--format", "-f", type=click.Choice(["linkedin", "twitter", "github"]),
|
|
87
|
+
default="linkedin", help="Output format")
|
|
88
|
+
def share(format):
|
|
89
|
+
"""Generate shareable social media content."""
|
|
90
|
+
from roastbuddy.core.share import generate_shareable
|
|
91
|
+
|
|
92
|
+
console.print(f"[bold cyan]š¤ Generating {format} content...[/bold cyan]\n")
|
|
93
|
+
generate_shareable(format_type=format)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@main.command()
|
|
97
|
+
@click.option("--show", is_flag=True, help="Show current configuration")
|
|
98
|
+
@click.option("--set", "set_value", nargs=2, help="Set config value (key value)")
|
|
99
|
+
def config(show, set_value):
|
|
100
|
+
"""Manage RoastBuddy configuration."""
|
|
101
|
+
from roastbuddy.core.config import show_config, set_config
|
|
102
|
+
|
|
103
|
+
if show:
|
|
104
|
+
show_config()
|
|
105
|
+
elif set_value:
|
|
106
|
+
key, value = set_value
|
|
107
|
+
set_config(key, value)
|
|
108
|
+
else:
|
|
109
|
+
console.print("[yellow]Use --show or --set to manage configuration[/yellow]")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if __name__ == "__main__":
|
|
113
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core __init__ file for roastbuddy.core package."""
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Configuration management for RoastBuddy."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import yaml
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict, Any
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
DEFAULT_CONFIG = {
|
|
13
|
+
"roast_intensity": "medium", # light, medium, spicy
|
|
14
|
+
"auto_roast_on_commit": False,
|
|
15
|
+
"use_copilot": "auto", # auto, always, never
|
|
16
|
+
"enable_streaks": True,
|
|
17
|
+
"show_emojis": True,
|
|
18
|
+
"color_output": True,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
CONFIG_DIR = Path.home() / ".roastbuddy"
|
|
22
|
+
CONFIG_FILE = CONFIG_DIR / "config.yaml"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_config_dir() -> Path:
|
|
26
|
+
"""Get or create the RoastBuddy config directory."""
|
|
27
|
+
CONFIG_DIR.mkdir(exist_ok=True)
|
|
28
|
+
return CONFIG_DIR
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_config_file() -> Path:
|
|
32
|
+
"""Get the config file path."""
|
|
33
|
+
return CONFIG_FILE
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def load_config() -> Dict[str, Any]:
|
|
37
|
+
"""Load configuration from file or return defaults."""
|
|
38
|
+
if not CONFIG_FILE.exists():
|
|
39
|
+
return DEFAULT_CONFIG.copy()
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
with open(CONFIG_FILE, "r") as f:
|
|
43
|
+
config = yaml.safe_load(f) or {}
|
|
44
|
+
return {**DEFAULT_CONFIG, **config}
|
|
45
|
+
except Exception as e:
|
|
46
|
+
console.print(f"[yellow]Warning: Failed to load config: {e}[/yellow]")
|
|
47
|
+
return DEFAULT_CONFIG.copy()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def save_config(config: Dict[str, Any]) -> bool:
|
|
51
|
+
"""Save configuration to file."""
|
|
52
|
+
try:
|
|
53
|
+
get_config_dir()
|
|
54
|
+
with open(CONFIG_FILE, "w") as f:
|
|
55
|
+
yaml.dump(config, f, default_flow_style=False)
|
|
56
|
+
return True
|
|
57
|
+
except Exception as e:
|
|
58
|
+
console.print(f"[red]Error saving config: {e}[/red]")
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def show_config():
|
|
63
|
+
"""Display current configuration."""
|
|
64
|
+
config = load_config()
|
|
65
|
+
|
|
66
|
+
table = Table(title="RoastBuddy Configuration")
|
|
67
|
+
table.add_column("Setting", style="cyan")
|
|
68
|
+
table.add_column("Value", style="green")
|
|
69
|
+
|
|
70
|
+
for key, value in config.items():
|
|
71
|
+
table.add_row(key, str(value))
|
|
72
|
+
|
|
73
|
+
console.print(table)
|
|
74
|
+
console.print(f"\nš Config file: [cyan]{CONFIG_FILE}[/cyan]")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def set_config(key: str, value: str):
|
|
78
|
+
"""Set a configuration value."""
|
|
79
|
+
config = load_config()
|
|
80
|
+
|
|
81
|
+
# Type conversion
|
|
82
|
+
if value.lower() in ["true", "false"]:
|
|
83
|
+
value = value.lower() == "true"
|
|
84
|
+
|
|
85
|
+
config[key] = value
|
|
86
|
+
|
|
87
|
+
if save_config(config):
|
|
88
|
+
console.print(f"[green]ā
Set {key} = {value}[/green]")
|
|
89
|
+
else:
|
|
90
|
+
console.print(f"[red]ā Failed to set configuration[/red]")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def get_config_value(key: str, default: Any = None) -> Any:
|
|
94
|
+
"""Get a single configuration value."""
|
|
95
|
+
config = load_config()
|
|
96
|
+
return config.get(key, default)
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Database management for RoastBuddy."""
|
|
2
|
+
|
|
3
|
+
import sqlite3
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional, Dict, Any, List
|
|
7
|
+
from roastbuddy.core.config import get_config_dir
|
|
8
|
+
|
|
9
|
+
DB_FILE = get_config_dir() / "roastbuddy.db"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_db_connection() -> sqlite3.Connection:
|
|
13
|
+
"""Get a database connection."""
|
|
14
|
+
conn = sqlite3.connect(DB_FILE)
|
|
15
|
+
conn.row_factory = sqlite3.Row
|
|
16
|
+
return conn
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def initialize_database():
|
|
20
|
+
"""Initialize the database schema."""
|
|
21
|
+
conn = get_db_connection()
|
|
22
|
+
cursor = conn.cursor()
|
|
23
|
+
|
|
24
|
+
# Commits table
|
|
25
|
+
cursor.execute("""
|
|
26
|
+
CREATE TABLE IF NOT EXISTS commits (
|
|
27
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
28
|
+
commit_hash TEXT UNIQUE NOT NULL,
|
|
29
|
+
message TEXT NOT NULL,
|
|
30
|
+
author TEXT NOT NULL,
|
|
31
|
+
timestamp DATETIME NOT NULL,
|
|
32
|
+
files_changed INTEGER DEFAULT 0,
|
|
33
|
+
lines_added INTEGER DEFAULT 0,
|
|
34
|
+
lines_deleted INTEGER DEFAULT 0,
|
|
35
|
+
is_clean BOOLEAN DEFAULT 0,
|
|
36
|
+
repo_path TEXT NOT NULL
|
|
37
|
+
)
|
|
38
|
+
""")
|
|
39
|
+
|
|
40
|
+
# Streaks table
|
|
41
|
+
cursor.execute("""
|
|
42
|
+
CREATE TABLE IF NOT EXISTS streaks (
|
|
43
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
44
|
+
streak_type TEXT NOT NULL,
|
|
45
|
+
current_count INTEGER DEFAULT 0,
|
|
46
|
+
best_count INTEGER DEFAULT 0,
|
|
47
|
+
last_updated DATETIME NOT NULL,
|
|
48
|
+
repo_path TEXT NOT NULL
|
|
49
|
+
)
|
|
50
|
+
""")
|
|
51
|
+
|
|
52
|
+
# Roasts and praise history
|
|
53
|
+
cursor.execute("""
|
|
54
|
+
CREATE TABLE IF NOT EXISTS interactions (
|
|
55
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
56
|
+
interaction_type TEXT NOT NULL,
|
|
57
|
+
content TEXT NOT NULL,
|
|
58
|
+
commit_hash TEXT,
|
|
59
|
+
timestamp DATETIME NOT NULL,
|
|
60
|
+
source TEXT NOT NULL,
|
|
61
|
+
repo_path TEXT NOT NULL
|
|
62
|
+
)
|
|
63
|
+
""")
|
|
64
|
+
|
|
65
|
+
# Achievements/milestones
|
|
66
|
+
cursor.execute("""
|
|
67
|
+
CREATE TABLE IF NOT EXISTS achievements (
|
|
68
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
69
|
+
achievement_type TEXT NOT NULL,
|
|
70
|
+
description TEXT NOT NULL,
|
|
71
|
+
earned_at DATETIME NOT NULL,
|
|
72
|
+
repo_path TEXT NOT NULL
|
|
73
|
+
)
|
|
74
|
+
""")
|
|
75
|
+
|
|
76
|
+
conn.commit()
|
|
77
|
+
conn.close()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def add_commit_record(commit_data: Dict[str, Any]) -> bool:
|
|
81
|
+
"""Add a commit record to the database."""
|
|
82
|
+
try:
|
|
83
|
+
conn = get_db_connection()
|
|
84
|
+
cursor = conn.cursor()
|
|
85
|
+
|
|
86
|
+
cursor.execute("""
|
|
87
|
+
INSERT OR REPLACE INTO commits
|
|
88
|
+
(commit_hash, message, author, timestamp, files_changed,
|
|
89
|
+
lines_added, lines_deleted, is_clean, repo_path)
|
|
90
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
91
|
+
""", (
|
|
92
|
+
commit_data.get("hash"),
|
|
93
|
+
commit_data.get("message"),
|
|
94
|
+
commit_data.get("author"),
|
|
95
|
+
commit_data.get("timestamp"),
|
|
96
|
+
commit_data.get("files_changed", 0),
|
|
97
|
+
commit_data.get("lines_added", 0),
|
|
98
|
+
commit_data.get("lines_deleted", 0),
|
|
99
|
+
commit_data.get("is_clean", False),
|
|
100
|
+
commit_data.get("repo_path"),
|
|
101
|
+
))
|
|
102
|
+
|
|
103
|
+
conn.commit()
|
|
104
|
+
conn.close()
|
|
105
|
+
return True
|
|
106
|
+
except Exception as e:
|
|
107
|
+
print(f"Error adding commit: {e}")
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def add_interaction(interaction_type: str, content: str, commit_hash: Optional[str],
|
|
112
|
+
source: str, repo_path: str) -> bool:
|
|
113
|
+
"""Add a roast/praise interaction to the database."""
|
|
114
|
+
try:
|
|
115
|
+
conn = get_db_connection()
|
|
116
|
+
cursor = conn.cursor()
|
|
117
|
+
|
|
118
|
+
cursor.execute("""
|
|
119
|
+
INSERT INTO interactions
|
|
120
|
+
(interaction_type, content, commit_hash, timestamp, source, repo_path)
|
|
121
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
122
|
+
""", (
|
|
123
|
+
interaction_type,
|
|
124
|
+
content,
|
|
125
|
+
commit_hash,
|
|
126
|
+
datetime.now().isoformat(),
|
|
127
|
+
source,
|
|
128
|
+
repo_path
|
|
129
|
+
))
|
|
130
|
+
|
|
131
|
+
conn.commit()
|
|
132
|
+
conn.close()
|
|
133
|
+
return True
|
|
134
|
+
except Exception as e:
|
|
135
|
+
print(f"Error adding interaction: {e}")
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_streak_data(streak_type: str, repo_path: str) -> Optional[Dict[str, Any]]:
|
|
140
|
+
"""Get streak data for a specific type."""
|
|
141
|
+
conn = get_db_connection()
|
|
142
|
+
cursor = conn.cursor()
|
|
143
|
+
|
|
144
|
+
cursor.execute("""
|
|
145
|
+
SELECT * FROM streaks
|
|
146
|
+
WHERE streak_type = ? AND repo_path = ?
|
|
147
|
+
""", (streak_type, repo_path))
|
|
148
|
+
|
|
149
|
+
row = cursor.fetchone()
|
|
150
|
+
conn.close()
|
|
151
|
+
|
|
152
|
+
if row:
|
|
153
|
+
return dict(row)
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def update_streak(streak_type: str, current_count: int, best_count: int, repo_path: str) -> bool:
|
|
158
|
+
"""Update or create a streak record."""
|
|
159
|
+
try:
|
|
160
|
+
conn = get_db_connection()
|
|
161
|
+
cursor = conn.cursor()
|
|
162
|
+
|
|
163
|
+
cursor.execute("""
|
|
164
|
+
INSERT OR REPLACE INTO streaks
|
|
165
|
+
(streak_type, current_count, best_count, last_updated, repo_path)
|
|
166
|
+
VALUES (?, ?, ?, ?, ?)
|
|
167
|
+
""", (
|
|
168
|
+
streak_type,
|
|
169
|
+
current_count,
|
|
170
|
+
best_count,
|
|
171
|
+
datetime.now().isoformat(),
|
|
172
|
+
repo_path
|
|
173
|
+
))
|
|
174
|
+
|
|
175
|
+
conn.commit()
|
|
176
|
+
conn.close()
|
|
177
|
+
return True
|
|
178
|
+
except Exception as e:
|
|
179
|
+
print(f"Error updating streak: {e}")
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def get_recent_interactions(limit: int = 10, repo_path: str = ".") -> List[Dict[str, Any]]:
|
|
184
|
+
"""Get recent roast/praise interactions."""
|
|
185
|
+
conn = get_db_connection()
|
|
186
|
+
cursor = conn.cursor()
|
|
187
|
+
|
|
188
|
+
cursor.execute("""
|
|
189
|
+
SELECT * FROM interactions
|
|
190
|
+
WHERE repo_path = ?
|
|
191
|
+
ORDER BY timestamp DESC
|
|
192
|
+
LIMIT ?
|
|
193
|
+
""", (repo_path, limit))
|
|
194
|
+
|
|
195
|
+
rows = cursor.fetchall()
|
|
196
|
+
conn.close()
|
|
197
|
+
|
|
198
|
+
return [dict(row) for row in rows]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""History display functionality."""
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.panel import Panel
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
from roastbuddy.core.database import get_recent_interactions
|
|
7
|
+
from roastbuddy.utils.git_utils import get_repo_path
|
|
8
|
+
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def show_history(limit: int = 10):
|
|
13
|
+
"""Show roast and praise history."""
|
|
14
|
+
repo_path = get_repo_path()
|
|
15
|
+
interactions = get_recent_interactions(limit=limit, repo_path=repo_path)
|
|
16
|
+
|
|
17
|
+
if not interactions:
|
|
18
|
+
console.print("[yellow]š No history yet![/yellow]")
|
|
19
|
+
console.print("[dim]Use [bold]roastbuddy roast[/bold] or [bold]roastbuddy praise[/bold] first![/dim]")
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
table = Table(title=f"š RoastBuddy History (last {len(interactions)})")
|
|
23
|
+
table.add_column("Type", style="cyan", width=8)
|
|
24
|
+
table.add_column("Content", style="white", width=60)
|
|
25
|
+
table.add_column("Source", style="dim", width=10)
|
|
26
|
+
table.add_column("Time", style="dim", width=16)
|
|
27
|
+
|
|
28
|
+
for interaction in interactions:
|
|
29
|
+
interaction_type = interaction["interaction_type"]
|
|
30
|
+
emoji = "š„" if interaction_type == "roast" else "āØ"
|
|
31
|
+
type_str = f"{emoji} {interaction_type.title()}"
|
|
32
|
+
|
|
33
|
+
content = interaction["content"][:60] + "..." if len(interaction["content"]) > 60 else interaction["content"]
|
|
34
|
+
|
|
35
|
+
source = interaction["source"]
|
|
36
|
+
timestamp = interaction["timestamp"][:16] # Just date and time
|
|
37
|
+
|
|
38
|
+
table.add_row(type_str, content, source, timestamp)
|
|
39
|
+
|
|
40
|
+
console.print(table)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Initializer for RoastBuddy."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from git import Repo, InvalidGitRepositoryError
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from roastbuddy.core.config import get_config_dir, save_config, DEFAULT_CONFIG
|
|
8
|
+
from roastbuddy.core.database import initialize_database
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def is_git_repository(path: str) -> bool:
|
|
14
|
+
"""Check if the path is a git repository."""
|
|
15
|
+
try:
|
|
16
|
+
Repo(path, search_parent_directories=True)
|
|
17
|
+
return True
|
|
18
|
+
except InvalidGitRepositoryError:
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def initialize_roastbuddy(path: str = ".") -> bool:
|
|
23
|
+
"""Initialize RoastBuddy in a git repository."""
|
|
24
|
+
path = Path(path).resolve()
|
|
25
|
+
|
|
26
|
+
# Check if it's a git repository
|
|
27
|
+
if not is_git_repository(str(path)):
|
|
28
|
+
console.print(f"[red]ā {path} is not a git repository![/red]")
|
|
29
|
+
console.print("[yellow]š” Initialize git first: [bold]git init[/bold][/yellow]")
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
# Create config directory
|
|
34
|
+
config_dir = get_config_dir()
|
|
35
|
+
console.print(f"[dim]Creating config directory: {config_dir}[/dim]")
|
|
36
|
+
|
|
37
|
+
# Initialize database
|
|
38
|
+
console.print("[dim]Initializing database...[/dim]")
|
|
39
|
+
initialize_database()
|
|
40
|
+
|
|
41
|
+
# Save default configuration
|
|
42
|
+
console.print("[dim]Creating default configuration...[/dim]")
|
|
43
|
+
save_config(DEFAULT_CONFIG)
|
|
44
|
+
|
|
45
|
+
# Create .roastbuddy marker in repo
|
|
46
|
+
marker_file = path / ".roastbuddy"
|
|
47
|
+
if not marker_file.exists():
|
|
48
|
+
marker_file.touch()
|
|
49
|
+
console.print(f"[dim]Created repository marker: {marker_file}[/dim]")
|
|
50
|
+
|
|
51
|
+
console.print("\n[bold green]š RoastBuddy initialized successfully![/bold green]")
|
|
52
|
+
console.print("\n[cyan]Next steps:[/cyan]")
|
|
53
|
+
console.print(" ⢠[bold]roastbuddy roast[/bold] - Get roasted!")
|
|
54
|
+
console.print(" ⢠[bold]roastbuddy praise[/bold] - Get some praise")
|
|
55
|
+
console.print(" ⢠[bold]roastbuddy status[/bold] - Check your stats")
|
|
56
|
+
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
except Exception as e:
|
|
60
|
+
console.print(f"[red]ā Error during initialization: {e}[/red]")
|
|
61
|
+
return False
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Roasting and praising functionality."""
|
|
2
|
+
|
|
3
|
+
import random
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.panel import Panel
|
|
6
|
+
from roastbuddy.utils.git_utils import find_git_repo, get_last_commit, get_repo_path
|
|
7
|
+
from roastbuddy.utils.copilot_utils import (
|
|
8
|
+
is_copilot_available,
|
|
9
|
+
generate_roast_with_copilot,
|
|
10
|
+
generate_praise_with_copilot,
|
|
11
|
+
suggest_copilot_install
|
|
12
|
+
)
|
|
13
|
+
from roastbuddy.templates.roast_templates import ROAST_TEMPLATES, get_roast_category
|
|
14
|
+
from roastbuddy.templates.praise_templates import PRAISE_TEMPLATES, get_praise_category
|
|
15
|
+
from roastbuddy.core.config import get_config_value
|
|
16
|
+
from roastbuddy.core.database import add_interaction
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def roast_code(last_commit: bool = True):
|
|
22
|
+
"""Generate a roast for the code."""
|
|
23
|
+
repo = find_git_repo()
|
|
24
|
+
|
|
25
|
+
if not repo:
|
|
26
|
+
console.print("[red]ā Not in a git repository![/red]")
|
|
27
|
+
console.print("[yellow]Initialize git first: [bold]git init[/bold][/yellow]")
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
commit_data = get_last_commit(repo)
|
|
31
|
+
|
|
32
|
+
if not commit_data:
|
|
33
|
+
console.print("[yellow]ā ļø No commits found yet![/yellow]")
|
|
34
|
+
console.print("[dim]Make your first commit and come back for a roast! š„[/dim]")
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
# Check if Copilot should be used
|
|
38
|
+
use_copilot = get_config_value("use_copilot", "auto")
|
|
39
|
+
copilot_available = is_copilot_available()
|
|
40
|
+
|
|
41
|
+
roast_text = None
|
|
42
|
+
source = "template"
|
|
43
|
+
|
|
44
|
+
# Try Copilot if configured
|
|
45
|
+
if use_copilot in ["auto", "always"] and copilot_available:
|
|
46
|
+
roast_text = generate_roast_with_copilot(commit_data)
|
|
47
|
+
source = "copilot"
|
|
48
|
+
if roast_text:
|
|
49
|
+
console.print("[dim]š¤ Roast powered by GitHub Copilot[/dim]\n")
|
|
50
|
+
|
|
51
|
+
# Fallback to templates
|
|
52
|
+
if not roast_text:
|
|
53
|
+
if use_copilot == "always":
|
|
54
|
+
console.print("[yellow]ā ļø Copilot not available but set to 'always'[/yellow]")
|
|
55
|
+
suggest_copilot_install()
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
category = get_roast_category(commit_data)
|
|
59
|
+
roast_text = random.choice(ROAST_TEMPLATES.get(category, ROAST_TEMPLATES["default"]))
|
|
60
|
+
|
|
61
|
+
if not copilot_available and use_copilot == "auto":
|
|
62
|
+
console.print("[dim]š” Using built-in roasts (Copilot not available)[/dim]\n")
|
|
63
|
+
|
|
64
|
+
# Display the roast
|
|
65
|
+
console.print(Panel.fit(
|
|
66
|
+
f"[bold red]š„ Your Roast[/bold red]\n\n"
|
|
67
|
+
f"{roast_text}\n\n"
|
|
68
|
+
f"[dim]Commit: {commit_data['short_hash']} - {commit_data['message'][:50]}...[/dim]",
|
|
69
|
+
title="Roast",
|
|
70
|
+
border_style="red"
|
|
71
|
+
))
|
|
72
|
+
|
|
73
|
+
# Save to database
|
|
74
|
+
add_interaction("roast", roast_text, commit_data["hash"], source, commit_data["repo_path"])
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def praise_code(last_commit: bool = True):
|
|
78
|
+
"""Generate praise for the code."""
|
|
79
|
+
repo = find_git_repo()
|
|
80
|
+
|
|
81
|
+
if not repo:
|
|
82
|
+
console.print("[red]ā Not in a git repository![/red]")
|
|
83
|
+
console.print("[yellow]Initialize git first: [bold]git init[/bold][/yellow]")
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
commit_data = get_last_commit(repo)
|
|
87
|
+
|
|
88
|
+
if not commit_data:
|
|
89
|
+
console.print("[yellow]ā ļø No commits found yet![/yellow]")
|
|
90
|
+
console.print("[dim]Make your first commit and come back for praise! āØ[/dim]")
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
# Check if Copilot should be used
|
|
94
|
+
use_copilot = get_config_value("use_copilot", "auto")
|
|
95
|
+
copilot_available = is_copilot_available()
|
|
96
|
+
|
|
97
|
+
praise_text = None
|
|
98
|
+
source = "template"
|
|
99
|
+
|
|
100
|
+
# Try Copilot if configured
|
|
101
|
+
if use_copilot in ["auto", "always"] and copilot_available:
|
|
102
|
+
praise_text = generate_praise_with_copilot(commit_data)
|
|
103
|
+
source = "copilot"
|
|
104
|
+
if praise_text:
|
|
105
|
+
console.print("[dim]š¤ Praise powered by GitHub Copilot[/dim]\n")
|
|
106
|
+
|
|
107
|
+
# Fallback to templates
|
|
108
|
+
if not praise_text:
|
|
109
|
+
if use_copilot == "always":
|
|
110
|
+
console.print("[yellow]ā ļø Copilot not available but set to 'always'[/yellow]")
|
|
111
|
+
suggest_copilot_install()
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
category = get_praise_category(commit_data)
|
|
115
|
+
praise_text = random.choice(PRAISE_TEMPLATES.get(category, PRAISE_TEMPLATES["default"]))
|
|
116
|
+
|
|
117
|
+
if not copilot_available and use_copilot == "auto":
|
|
118
|
+
console.print("[dim]š” Using built-in praise (Copilot not available)[/dim]\n")
|
|
119
|
+
|
|
120
|
+
# Display the praise
|
|
121
|
+
console.print(Panel.fit(
|
|
122
|
+
f"[bold green]⨠Your Praise[/bold green]\n\n"
|
|
123
|
+
f"{praise_text}\n\n"
|
|
124
|
+
f"[dim]Commit: {commit_data['short_hash']} - {commit_data['message'][:50]}...[/dim]",
|
|
125
|
+
title="Praise",
|
|
126
|
+
border_style="green"
|
|
127
|
+
))
|
|
128
|
+
|
|
129
|
+
# Save to database
|
|
130
|
+
add_interaction("praise", praise_text, commit_data["hash"], source, commit_data["repo_path"])
|
roastbuddy/core/share.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Shareable content generation."""
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.panel import Panel
|
|
5
|
+
|
|
6
|
+
console = Console()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def generate_shareable(format_type: str = "linkedin"):
|
|
10
|
+
"""Generate shareable social media content."""
|
|
11
|
+
# TODO: Implement shareable content generation
|
|
12
|
+
console.print(Panel.fit(
|
|
13
|
+
f"[bold cyan]š¤ {format_type.title()} Content[/bold cyan]\n\n"
|
|
14
|
+
"[yellow]Shareable content generation coming soon![/yellow]\n"
|
|
15
|
+
"Will generate awesome posts for social media!",
|
|
16
|
+
title="Share",
|
|
17
|
+
border_style="cyan"
|
|
18
|
+
))
|