Skip to content

Commit ede11a1

Browse files
committed
feat: add -g, --graph option for printing ASCII contribution graph
1 parent 66320c7 commit ede11a1

7 files changed

Lines changed: 130 additions & 21 deletions

File tree

.github/screenshot.png

141 KB
Loading

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
[![version](https://img.shields.io/npm/v/streaker.svg)](https://github.com/jamieweavis/streaker-cli/releases)
88
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/jamieweavis/streaker-cli/blob/main/LICENSE)
99

10-
<img width=335 alt="Screenshot" src="./.github/screenshot.png">
10+
<img width=593 alt="Screenshot" src="./.github/screenshot.png">
1111

1212
## Installation
1313

@@ -17,10 +17,16 @@ npm install -g streaker
1717

1818
## Usage
1919

20+
Display streak & contribution stats for a user:
2021
```sh
2122
streaker <username>
2223
```
2324

25+
Display ASCII GitHub contribution graph for a user:
26+
```sh
27+
streaker <username> --graph
28+
```
29+
2430
## Related
2531

2632
- [Streaker](https://github.com/jamieweavis/streaker) - 🔥 GitHub contribution streak & stat tracking menu bar app

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"dependencies": {
4040
"colorette": "^2.0.20",
4141
"commander": "^13.1.0",
42-
"contribution": "^7.0.0"
42+
"contribution": "^7.1.0"
4343
},
4444
"devDependencies": {
4545
"@biomejs/biome": "^1.9.4",
@@ -62,9 +62,7 @@
6262
"typescript": "5.7.3"
6363
},
6464
"release": {
65-
"branches": [
66-
"main"
67-
],
65+
"branches": ["main"],
6866
"plugins": [
6967
"@semantic-release/changelog",
7068
"@semantic-release/commit-analyzer",
@@ -75,14 +73,9 @@
7573
]
7674
},
7775
"lint-staged": {
78-
"*.ts": [
79-
"biome format --write",
80-
"biome lint --write"
81-
]
76+
"*.ts": ["biome format --write", "biome lint --write"]
8277
},
8378
"commitlint": {
84-
"extends": [
85-
"@commitlint/config-conventional"
86-
]
79+
"extends": ["@commitlint/config-conventional"]
8780
}
8881
}

src/app.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,16 @@ const init = () => {
1010
.description(description)
1111
.version(version)
1212
.arguments('<username>')
13+
.option(
14+
'-g, --graph',
15+
'output ASCII GitHub contribution graph instead of stats',
16+
)
1317
.parse();
1418

1519
const username = result.args[0]?.trim();
16-
return run(username);
20+
const options = result.opts();
21+
22+
return run(username, options.graph);
1723
};
1824

1925
init();

src/contribution-graph.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { green } from 'colorette';
2+
import type { Contributions } from 'contribution';
3+
4+
export function buildContributionGraph(contributions: Contributions): string {
5+
// Define ASCII characters for different contribution levels
6+
const levels = [' ', '░', '▒', '▓', '█'];
7+
8+
// Sort dates to ensure chronological order
9+
const sortedDates = Object.keys(contributions).sort();
10+
if (sortedDates.length === 0) return 'No data available';
11+
12+
// Group dates by week
13+
const weeks: string[][] = [];
14+
let currentWeek: string[] = [];
15+
16+
for (const dateStr of sortedDates) {
17+
const currentDate = new Date(dateStr);
18+
19+
// Start new week if it's a Sunday
20+
if (currentDate.getDay() === 0 && currentWeek.length > 0) {
21+
weeks.push(currentWeek);
22+
currentWeek = [];
23+
}
24+
25+
currentWeek.push(dateStr);
26+
}
27+
28+
// Add the last week if it's not empty
29+
if (currentWeek.length > 0) {
30+
weeks.push(currentWeek);
31+
}
32+
33+
// Initialize the graph with proper dimensions
34+
const dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
35+
let graph = '';
36+
37+
// Add month labels at top of graph
38+
const months: string[] = [];
39+
const monthPositions: number[] = [];
40+
41+
weeks.forEach((week, weekIndex) => {
42+
const firstDayOfWeek = new Date(week[0]);
43+
const month = firstDayOfWeek.toLocaleString('default', { month: 'short' });
44+
45+
if (months.length === 0 || months[months.length - 1] !== month) {
46+
months.push(month);
47+
monthPositions.push(weekIndex);
48+
}
49+
});
50+
51+
// Add month labels
52+
graph += ' ';
53+
monthPositions.forEach((pos, idx) => {
54+
const month = months[idx];
55+
const nextPos =
56+
idx < monthPositions.length - 1 ? monthPositions[idx + 1] : weeks.length;
57+
// Adjust spacing to account for no spaces between week columns
58+
const width = nextPos - pos;
59+
const spaces = Math.max(0, width - month.length);
60+
61+
graph += month + ' '.repeat(spaces);
62+
});
63+
graph += '\n';
64+
65+
// Add a separator line
66+
graph += ` ┌${'─'.repeat(weeks.length)}─\n`;
67+
68+
// Build the graph row by row (day by day)
69+
for (let day = 0; day < 7; day++) {
70+
graph += `${dayLabels[day]} │ `;
71+
72+
for (let weekIndex = 0; weekIndex < weeks.length; weekIndex++) {
73+
const week = weeks[weekIndex];
74+
let cell = ' ';
75+
76+
// Find the day in this week
77+
for (const dateStr of week) {
78+
const date = new Date(dateStr);
79+
if (date.getDay() === day) {
80+
const level = contributions[dateStr].gitHubLegendLevel;
81+
cell = green(levels[level]);
82+
break;
83+
}
84+
}
85+
86+
graph += cell; // No space added after each cell
87+
}
88+
89+
graph += '\n';
90+
}
91+
92+
// Add a bottom line
93+
graph += ` └${'─'.repeat(weeks.length)}─`;
94+
95+
return graph;
96+
}

src/run.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { blue, red, yellow } from 'colorette';
22
import { fetchGitHubStats } from 'contribution';
33

4-
export const run = async (username?: string) => {
4+
import { buildContributionGraph } from './contribution-graph';
5+
6+
export const run = async (username?: string, showGraph?: boolean) => {
57
try {
68
if (!username) {
79
console.error(
@@ -14,6 +16,12 @@ export const run = async (username?: string) => {
1416

1517
const gitHubStats = await fetchGitHubStats(username);
1618

19+
if (showGraph) {
20+
console.info(`
21+
${buildContributionGraph(gitHubStats.contributions)}`);
22+
return process.exit(0);
23+
}
24+
1725
console.info(
1826
`
1927
${blue('Streak')}

0 commit comments

Comments
 (0)