Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 19 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,29 +57,27 @@ Generate a top languages chart for your GitHub profile that you can embed in a R
### Customization Options
Append these query parameters to the URL to customize the look and data of your chart:

| Parameter | Type | Description | Default | Example |
| :--- | :--- | :--- | :--- | :--- |
| `theme` | String | Sets the colour scheme. Available options: `default`, `light`, `dark`. | `default` | `?theme=dark` |
| `title` | String | Sets a custom title for the chart. | `Top Languages` | `?title=My%20Code%20Stack` |
| `hide_title` | Boolean | Hides the chart title completely. | `false` | `?hide_title=true` |
| `count` | Number | Sets the maximum number of languages to display. Max is **16**. | `8` | `?count=10` |
| `width` | Number | Sets the width of the SVG in pixels. | `400` | `?width=500` |
| `height` | Number | Sets the height of the SVG in pixels. | `300` | `?height=350` |
| `bg` | String | Sets the chart background colour. Accepts hex (`ffffff`) or theme names (`dark`, `light`). | `default` | `?bg=dark` |
| `text` | String | Sets the chart text colour. Accepts hex (`ffffff`). | `#000000` | `?text=ffffff` |
| `c1`-`c16` | String | Sets individual colours for languages 1-16. Accepts hex codes. | Auto-assigned | `?c1=ff0000&c2=00ff00` |
| `test` | Boolean | Uses samples data instead of fetching from GitHub API. | `false` | `?test=true` |
| `stroke` | Boolean | Adds an outline stroke to chart segments. | `false` | `?stroke=true` |
| Parameter | Type | Description | Default | Example |
| :--- | :--- | :--- | :--- | :--- |
| `theme` | String | Sets the colour scheme. Available options: `default`, `light`, `dark`. | `default` | `?theme=dark` |
| `type` | String | Sets the chart type. Available options: `donut`, `pie`. | `donut` | `?type=pie` |
| `title` | String | Sets a custom title for the chart. | `Top Languages` | `?title=My%20Code%20Stack` |
| `hide_title` | Boolean | Hides the chart title completely. | `false` | `?hide_title=true` |
| `text` | String | Sets the chart text colour. Accepts hex (`ffffff`). | `#000000` | `?text=ffffff` |
| `bg` | String | Sets the chart background colour. Accepts hex (`ffffff`) or theme names (`dark`). | `default` | `?bg=dark` |
| `c1`-`c16` | String | Sets individual colours for languages 1-16. Accepts hex codes. | Auto-assigned | `?c1=ff0000&c2=00ff00` |
| `count` | Number | Sets the maximum number of languages to display. Max is **16**. | `8` | `?count=10` |
| `width` | Number | Sets the width of the SVG in pixels. | `400` | `?width=500` |
| `height` | Number | Sets the height of the SVG in pixels. | `300` | `?height=350` |
| `stroke` | Boolean | Adds an outline stroke to chart segments. | `false` | `?stroke=true` |
| `test` | Boolean | Uses samples data instead of fetching from GitHub API. | `false` | `?test=true` |

#### Example URL
To get 10 languages, a dark theme, and a custom title:
```markdown
![My Custom Chart](https://your-deployment-url.vercel.app/api/languages?count=10&theme=dark&title=My%20Top%2010%20Languages)
```

<br/>


## Deployment & Configuration

### Prerequisites
Expand All @@ -96,8 +94,9 @@ npm install

### Configuration
Copy `.env.example` to `.env`, and update the variables.
- `GITHUB_USERNAME`: User to fetch repositories and statistics.
- `IGNORED_REPOS`: Optional repos you don't want counted in your chart.
- `GITHUB_USERNAMES`: Comma-separated GitHub usernames to fetch repositories from.
- `GITHUB_ORGS`: Optional comma-separated GitHub organization names to include.
- `IGNORED_REPOS`: Optional comma-separated repo names to exclude from the chart.

### Running Locally
```bash
Expand All @@ -106,12 +105,10 @@ vercel dev
```

### Deployment
- **Vercel**: Simply import your repo and deploy. The default endpoint is /api/languages

<br/>


[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/masonlet/github-top-languages&env=GITHUB_USERNAMES,IGNORED_REPOS&envDescription[GITHUB_USERNAMES]=Comma-separated%20GitHub%20usernames&envDescription[IGNORED_REPOS]=Optional%20comma-separated%20repos%20to%20exclude)

> The default endpoint is /api/languages

## License
MIT License - see [LICENSE](./LICENSE) for details.
2 changes: 1 addition & 1 deletion api/languages/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { VercelRequest, VercelResponse } from "@vercel/node";
import { parseQueryParams, type QueryParams } from "../../src/utils/params.js";
import { fetchLanguageData, processLanguageData } from "../../src/api/github.js";
import { fetchLanguageData, processLanguageData } from "../../src/services/github.js";
import { generateChartData } from "../../src/render/chart.js";
import { renderSvg } from "../../src/render/svg.js";
import { renderError } from "../../src/render/error.js";
Expand Down
7 changes: 4 additions & 3 deletions src/render/error.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import type { Theme } from "../types.js";
import { THEMES } from "../constants/themes.js";
import { ERROR_STYLES } from "../constants/styles.js"
import { sanitize } from "../utils/sanitize.js";

export function renderError(
message: string,
width: number,
height: number,
selectedTheme?: Theme
): string {
const background = selectedTheme?.bg || THEMES.default.bg;
const background = selectedTheme?.bg || THEMES.default.bg;
return `
<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
<rect width="${width}" height="${height}" fill="${background}" rx="10"/>
<text x="${width/2}" y="${ERROR_STYLES.TEXT_Y}" text-anchor="middle" fill="${ERROR_STYLES.COLOUR}" font-family="Arial" font-size="${ERROR_STYLES.FONT_SIZE}">
Error: ${message}
Error: ${sanitize(message)}
</text>
</svg>
`;
`.trim();
}
2 changes: 1 addition & 1 deletion src/render/svg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ export function renderSvg(
${segments}
${legend}
</svg>
`;
`.trim();
}
File renamed without changes.
10 changes: 6 additions & 4 deletions src/utils/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ const parseIntSafe = (
return Number.isNaN(parsed) ? fallback : parsed;
}

const normalizeHex = (val: string) => `#${val.replace(/^#/, '')}`;

export function parseQueryParams(query: QueryParams) {
const baseTheme = THEMES[query["theme"] as keyof typeof THEMES] ?? THEMES.default;
const count = parseIntSafe(query["count"], DEFAULT_CONFIG.COUNT);

const customColours = [...baseTheme.colours] as string[];
const customColours: string[] = [...baseTheme.colours];
for (let i = 1; i <= MAX_COUNT; i++) {
const colourVal = query[`c${i}`];
if(colourVal) customColours[i - 1] = `#${colourVal.replace(/^#/, '')}` as string;
if(colourVal) customColours[i - 1] = normalizeHex(colourVal);
}

const typeParam = query["type"] as ChartType | undefined;
Expand All @@ -34,8 +36,8 @@ export function parseQueryParams(query: QueryParams) {
height: parseIntSafe(query["height"], DEFAULT_CONFIG.HEIGHT),
count: Math.min(Math.max(count, 1), MAX_COUNT),
selectedTheme: {
bg: THEMES[query["bg"] as keyof typeof THEMES]?.bg ?? query["bg"] ?? baseTheme.bg,
text: query["text"] ?? baseTheme.text,
bg: THEMES[query["bg"] as keyof typeof THEMES]?.bg ?? (query["bg"] ? normalizeHex(query["bg"]) : baseTheme.bg),
text: query["text"] ? normalizeHex(query["text"]) : baseTheme.text,
colours: customColours
},
stroke: query["stroke"] === "true",
Expand Down
4 changes: 2 additions & 2 deletions tests/api/languages/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import type { VercelRequest, VercelResponse } from "@vercel/node";
import type { ChartResult } from "../../../src/types.js";
import handler from "../../../api/languages/index.js";
import { parseQueryParams } from "../../../src/utils/params.js";
import { fetchLanguageData, processLanguageData } from "../../../src/api/github.js";
import { fetchLanguageData, processLanguageData } from "../../../src/services/github.js";
import { generateChartData } from "../../../src/render/chart.js";
import { renderSvg } from "../../../src/render/svg.js";
import { renderError } from "../../../src/render/error.js";

vi.mock("../../../src/utils/params.js");
vi.mock("../../../src/api/github.js");
vi.mock("../../../src/services/github.js");
vi.mock("../../../src/render/chart.js");
vi.mock("../../../src/render/svg.js");
vi.mock("../../../src/render/error.js");
Expand Down
2 changes: 1 addition & 1 deletion tests/api/github.test.ts → tests/services/github.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { fetchLanguageData, processLanguageData, resetCache } from "../../src/api/github.js";
import { fetchLanguageData, processLanguageData, resetCache } from "../../src/services/github.js";

const repos = [
{ name: "repo1", fork: false, full_name: "user/repo1" },
Expand Down
Loading