@theihtisham/dev-pulse 1.0.0

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.
@@ -0,0 +1,175 @@
1
+ """Rich terminal rendering helpers for DevPulse."""
2
+
3
+ from datetime import datetime, timedelta
4
+ from typing import Any
5
+
6
+ from rich.console import Console
7
+ from rich.table import Table
8
+ from rich.panel import Panel
9
+ from rich.columns import Columns
10
+ from rich.text import Text
11
+
12
+
13
+ def render_heatmap(console: Console, data: list[dict[str, Any]], days: int = 365) -> None:
14
+ """Render a GitHub-style contribution heatmap in the terminal.
15
+
16
+ Each column represents a week, each row a day of the week (Mon-Sun).
17
+ Cell characters indicate intensity:
18
+ . = 0 commits
19
+ o = 1-2
20
+ + = 3-5
21
+ # = 6-9
22
+ @ = 10+
23
+ """
24
+ if not data:
25
+ console.print("[yellow]No activity data to display.[/yellow]")
26
+ return
27
+
28
+ # Build a lookup: date_str -> count
29
+ counts: dict[str, int] = {d["day"]: d["count"] for d in data}
30
+
31
+ # Determine date range
32
+ today = datetime.utcnow().date()
33
+ start = today - timedelta(days=days)
34
+
35
+ # Determine max for scaling
36
+ max_count = max(d["count"] for d in data) if data else 1
37
+ if max_count == 0:
38
+ max_count = 1
39
+
40
+ day_labels = ["Mon", " ", "Wed", " ", "Fri", " ", "Sun"]
41
+
42
+ # Build grid: 7 rows x N weeks
43
+ # Each week column
44
+ weeks: list[list[str]] = []
45
+ current = start
46
+ # Align to start of week (Monday)
47
+ while current.weekday() != 0:
48
+ current -= timedelta(days=1)
49
+
50
+ total_commits = 0
51
+ total_active_days = 0
52
+
53
+ while current <= today:
54
+ week: list[str] = []
55
+ for dow in range(7):
56
+ d = current
57
+ key = d.isoformat()
58
+ count = counts.get(key, 0)
59
+ total_commits += count
60
+ if count > 0:
61
+ total_active_days += 1
62
+
63
+ if count == 0:
64
+ cell = "[dim].[/dim]"
65
+ elif count <= max_count * 0.2:
66
+ cell = f"[color(28)]o[/color(28)]"
67
+ elif count <= max_count * 0.4:
68
+ cell = f"[color(34)]+[/color(34)]"
69
+ elif count <= max_count * 0.7:
70
+ cell = f"[color(40)]#[/color(40)]"
71
+ else:
72
+ cell = f"[color(46)]@[/color(46)]"
73
+
74
+ week.append(cell)
75
+ current += timedelta(days=1)
76
+ weeks.append(week)
77
+
78
+ console.print(Panel("[bold]Activity Heatmap[/bold]", style="blue"))
79
+
80
+ # Print month labels
81
+ month_line = " "
82
+ last_month = ""
83
+ for week in weeks:
84
+ # Check the Wednesday of this week for month
85
+ mid_date = start + timedelta(weeks=weeks.index(week), days=2)
86
+ month_name = mid_date.strftime("%b")
87
+ if month_name != last_month:
88
+ month_line += f"{month_name:<4}"
89
+ last_month = month_name
90
+ else:
91
+ month_line += " "
92
+ console.print(month_line)
93
+
94
+ # Print each day row
95
+ for dow in range(7):
96
+ line = f"{day_labels[dow]} "
97
+ for week in weeks:
98
+ if dow < len(week):
99
+ line += week[dow] + " "
100
+ else:
101
+ line += " "
102
+ console.print(line)
103
+
104
+ console.print(f"\n Total: [bold]{total_commits}[/bold] commits across [bold]{total_active_days}[/bold] active days")
105
+ console.print(" Legend: [dim].[/dim]=0 [color(28)]o[/color(28)]=low [color(34)]+[/color(34)]=med [color(40)]#[/color(40)]=high [color(46)]@[/color(46)]=very high")
106
+
107
+
108
+ def render_metrics_panel(console: Console, metrics: Any) -> None:
109
+ """Render a single developer's metrics as a rich panel."""
110
+ console.print(Panel(f"[bold]{metrics.author}[/bold]", style="cyan", expand=False))
111
+
112
+ table = Table.grid(padding=(0, 2))
113
+ table.add_column(justify="right", style="dim")
114
+ table.add_column()
115
+
116
+ table.add_row("Commits:", str(metrics.commits_count))
117
+ table.add_row("Active Days:", str(metrics.active_days))
118
+ table.add_row("Commits/Day:", str(metrics.commits_per_day))
119
+ table.add_row("PRs:", f"{metrics.prs_created} created, {metrics.prs_merged} merged")
120
+ table.add_row("Avg Merge Time:", f"{metrics.avg_pr_merge_time_hours}h")
121
+ table.add_row("Reviews:", str(metrics.reviews_given))
122
+ table.add_row("Lines:", f"+{metrics.lines_added} / -{metrics.lines_removed}")
123
+
124
+ console.print(table)
125
+ console.print()
126
+
127
+
128
+ def render_insight_card(console: Console, insight: dict[str, str], index: int) -> None:
129
+ """Render a single insight as a styled card."""
130
+ severity = insight.get("severity", "low")
131
+ color = {"high": "red", "medium": "yellow", "low": "green"}.get(severity, "white")
132
+ icon = {"high": "!!", "medium": "!", "low": "*"}.get(severity, "*")
133
+
134
+ console.print(f" [{color}][{icon}][/{color}] {insight['message']}")
135
+ console.print(f" [dim]-> {insight['recommendation']}[/dim]")
136
+
137
+
138
+ def render_burndown(console: Console, points: list[dict[str, Any]]) -> None:
139
+ """Render an ASCII burndown chart."""
140
+ if not points:
141
+ console.print("[yellow]No burndown data.[/yellow]")
142
+ return
143
+
144
+ max_pts = max(p.get("remaining", 0) for p in points)
145
+ if max_pts == 0:
146
+ max_pts = 1
147
+ width = 60
148
+
149
+ console.print(f"\n{'Day':>4} | {'Remaining':>10} | Chart")
150
+ console.print("-" * 80)
151
+
152
+ for p in points:
153
+ remaining = p.get("remaining", 0)
154
+ ideal = p.get("ideal", 0)
155
+ bar_len = int(remaining / max_pts * width)
156
+ ideal_len = int(ideal / max_pts * width)
157
+
158
+ bar = "[green]" + "#" * bar_len + "[/green]"
159
+ ideal_marker = "[dim]|[/dim]" if ideal_len < width else ""
160
+
161
+ line = f"{p['day']:>4} | {remaining:>10.1f} | {bar}"
162
+ console.print(line)
163
+
164
+
165
+ def render_header(console: Console) -> None:
166
+ """Render the DevPulse ASCII art header."""
167
+ header = Text()
168
+ header.append(" ____ _ ____ _ \n", style="bold cyan")
169
+ header.append(" | _ \\ _ _ ___| | __ | _ \\ __ _ _ __ ___| |__\n", style="bold cyan")
170
+ header.append(" | | | | | | |/ __| |/ / | |_) / _` | '_ \\ / __| '_ \\\n", style="bold cyan")
171
+ header.append(" | |_| | |_| | (__| < | __/ (_| | | | | (__| | | |\n", style="bold cyan")
172
+ header.append(" |____/ \\__,_|\\___|_|\\_\\ |_| \\__,_|_| |_|\\___|_| |_|\n", style="bold cyan")
173
+ header.append("\n AI-Powered Developer Productivity Dashboard", style="dim")
174
+ console.print(header)
175
+ console.print()
@@ -0,0 +1,34 @@
1
+ """Core configuration and settings for DevPulse."""
2
+
3
+ from devpulse.core.config import Settings, get_settings
4
+ from devpulse.core.database import Database
5
+ from devpulse.core.models import (
6
+ Commit,
7
+ PullRequest,
8
+ Issue,
9
+ Review,
10
+ DeveloperMetrics,
11
+ SprintData,
12
+ TeamHealth,
13
+ CodeQuality,
14
+ Goal,
15
+ DailyReport,
16
+ WeeklyReport,
17
+ )
18
+
19
+ __all__ = [
20
+ "Settings",
21
+ "get_settings",
22
+ "Database",
23
+ "Commit",
24
+ "PullRequest",
25
+ "Issue",
26
+ "Review",
27
+ "DeveloperMetrics",
28
+ "SprintData",
29
+ "TeamHealth",
30
+ "CodeQuality",
31
+ "Goal",
32
+ "DailyReport",
33
+ "WeeklyReport",
34
+ ]