-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.cpp
More file actions
201 lines (173 loc) · 6.88 KB
/
main.cpp
File metadata and controls
201 lines (173 loc) · 6.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 199309L // Ensure consistent time functions (for localtime_r, mktime, etc.)
#endif
#include <iostream>
#include <vector>
#include <string>
#include <ctime> // for time_t, tm, localtime_r, mktime, strftime
#include <map>
#include <cstdlib>
#include <sstream>
#include <array>
#include <cstdio> // for popen, pclose, FILE, fgets
// Terminal color codes for shades of green
// To change background color insead of foreground change 38 for 48
const std::vector<std::string> shades = {
" \033[38;5;232m□", // No activity
// " ", // No activity
" \033[38;5;22m■", // Light green
" \033[38;5;28m■",
" \033[38;5;34m■",
" \033[38;5;40m■", // Darker green
" \033[38;5;46m■" // Most active
};
const std::string RESET = "\033[0m";
// Run a command and capture the output (stdout + stderr)
std::string execCommand(const std::string& cmd) {
std::array<char, 128> buffer;
std::string result;
// Append "2>&1" so that stderr is merged into stdout, allowing us to detect Git errors
std::string fullCmd = cmd + " 2>&1";
FILE* pipe = popen(fullCmd.c_str(), "r");
if (!pipe) {
std::cerr << "Error: popen() failed\n";
return "";
}
while (fgets(buffer.data(), static_cast<int>(buffer.size()), pipe) != nullptr) {
result += buffer.data();
}
int returnCode = pclose(pipe);
if (returnCode != 0) {
// Git or some other command failed; return the combined output so the caller can inspect
return result;
}
return result;
}
// Parse git log dates and count contributions per day
std::map<std::string, int> getGitContributions(const std::string& repo_path = ".") {
// Escape single quotes in path so we can safely wrap it in '…'
std::string safe_path = "'";
for (char c : repo_path) {
if (c == '\'') safe_path += "'\\''";
else safe_path += c;
}
safe_path += "'";
// Use only "--date=short" (which prints "YYYY-MM-DD" in local time) and a stable "1 year ago" syntax
std::string cmd =
"git -C " + safe_path +
" log --since=\"1 year ago\" --date=short --pretty=format:%ad";
std::string output = execCommand(cmd);
if (output.rfind("fatal:", 0) == 0) {
// Git reported a fatal error (e.g. not a Git repository)
std::cerr << output;
return {};
}
std::istringstream stream(output);
std::string line;
std::map<std::string, int> contributions;
while (std::getline(stream, line)) {
if (!line.empty()) {
++contributions[line];
}
}
return contributions;
}
// Print a year’s worth of Git activity as a 7×N grid, colored by number of commits
void printGitActivity(const std::map<std::string, int>& activity) {
// Compute "today at local midnight"
time_t now = time(nullptr);
tm today_tm;
localtime_r(&now, &today_tm);
today_tm.tm_hour = 0;
today_tm.tm_min = 0;
today_tm.tm_sec = 0;
time_t today_midnight = mktime(&today_tm);
// Compute "one year ago at the same local midnight"
tm one_year_tm = today_tm;
one_year_tm.tm_year -= 1; // subtract one year
time_t one_year_midnight = mktime(&one_year_tm);
// Determine how many days are in that span (could be 365 or 366)
int total_days = static_cast<int>((today_midnight - one_year_midnight) / 86400) + 1;
// Compute number of weeks needed (ceiling of total_days/7)
int total_weeks = (total_days + 6) / 7;
// Initialize a 7×total_weeks grid with "no activity" shade (shades[0])
std::vector<std::vector<std::string>> grid(7, std::vector<std::string>(total_weeks, shades[0]));
// Track where each month label should start
std::vector<int> month_starts;
std::vector<std::string> month_labels;
int prev_month = -1;
// Walk day-by-day, from one_year_midnight up to today_midnight
tm cur = one_year_tm; // this is one_year_midnight in tm form (normalized by mktime)
for (int day_index = 0; day_index < total_days; ++day_index) {
// Format current date as YYYY-MM-DD
char buffer[11];
strftime(buffer, sizeof(buffer), "%Y-%m-%d", &cur);
std::string date_str(buffer);
// Lookup how many commits on that date
int level = 0;
auto it = activity.find(date_str);
if (it != activity.end()) {
int count = it->second;
if (count >= 15) level = 5;
else if (count >= 10) level = 4;
else if (count >= 5) level = 3;
else if (count >= 2) level = 2;
else /* count >= 1 */ level = 1;
}
// Determine weekday (0=Sunday..6=Saturday) and week index (0..total_weeks-1)
int weekday = cur.tm_wday;
int week_index = day_index / 7;
if (week_index < total_weeks) {
grid[weekday][week_index] = shades[level];
}
// If this is within the first 7 days of a month, and the month changed, record the label
if (cur.tm_mday <= 7 && (prev_month == -1 || cur.tm_mon != prev_month)) {
month_starts.push_back(week_index);
char month_name[4]; // "Jan", "Feb", etc.
strftime(month_name, sizeof(month_name), "%b", &cur);
month_labels.push_back(month_name);
prev_month = cur.tm_mon;
}
// Move cur forward by one calendar day (handles DST, month/year roll)
cur.tm_mday += 1;
mktime(&cur); // normalize fields (tm_wday, tm_yday, carry months/years, etc.)
}
// Print month labels across the top
std::cout << " "; // Left padding of 4 spaces
int current_col = 4; // Current column position after padding
for (size_t i = 0; i < month_starts.size(); ++i) {
int week_index = month_starts[i];
int target_col = 4 + week_index * 2; // Starting column for this week's label
// Print spaces from current_col to target_col
while (current_col < target_col) {
std::cout << " ";
++current_col;
}
// Print the month label
std::cout << month_labels[i];
current_col += static_cast<int>(month_labels[i].length());
}
std::cout << "\n";
// Print each weekday line, followed by the grid cells for all weeks
const char* day_labels[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
for (int r = 0; r < 7; ++r) {
std::cout << day_labels[r] << " ";
for (int c = 0; c < total_weeks; ++c) {
std::cout << grid[r][c] << RESET;
}
std::cout << "\n";
}
std::cout << RESET;
}
int main(int argc, char* argv[]) {
std::string path = ".";
if (argc > 1) {
path = argv[1];
}
auto contributions = getGitContributions(path);
if (contributions.empty()) {
std::cerr << "Warning: No commits found in the past year or Git command failed.\n";
}
printGitActivity(contributions);
return 0;
}