mirror of
https://github.com/prdlk/leetcode.git
synced 2026-09-16 23:16:26 +00:00
feat(sync): organize solution docs by category and add sidebar labels
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
description: Sync work/ leetcode solutions into docs/solutions/ pages (gold standard format)
|
description: Sync work/ leetcode solutions into docs/solutions/ pages (gold standard format)
|
||||||
---
|
---
|
||||||
|
|
||||||
Sync every leetcode solution under `work/` into a docs page under `docs/solutions/`, formatted like the gold standard `docs/solutions/344-reverse-string.mdx`. Extra focus (optional): $@
|
Sync every leetcode solution under `work/` into a docs page under `docs/solutions/`, formatted like the gold standard `docs/solutions/(two-pointers)/344-reverse-string.mdx`. Extra focus (optional): $@
|
||||||
|
|
||||||
## Run the script
|
## Run the script
|
||||||
|
|
||||||
@@ -16,12 +16,13 @@ bun run sync
|
|||||||
|
|
||||||
- Globs `work/**/*.{js,py}` (`<number>.<kebab-slug>.{js,py}` under `work/<Difficulty>/<Category>/`) and groups the two language variants of a problem by leetcode number.
|
- Globs `work/**/*.{js,py}` (`<number>.<kebab-slug>.{js,py}` under `work/<Difficulty>/<Category>/`) and groups the two language variants of a problem by leetcode number.
|
||||||
- Parses each file's header comment (js block comment / py docstring) into title, difficulty, statement, examples, constraints, and follow-up.
|
- Parses each file's header comment (js block comment / py docstring) into title, difficulty, statement, examples, constraints, and follow-up.
|
||||||
- New problems get a full page at `docs/solutions/<leetcode-number>-<kebab-slug>.mdx`: frontmatter, `<Badge>` topic (README curriculum table, falling back to the `work/` subdirectory), `::::warning` for follow-ups/special requirements, examples, constraints, and the solution code verbatim.
|
- New problems get a full page at `docs/solutions/(<category>)/<leetcode-number>-<kebab-slug>.mdx`: frontmatter, `<Badge>` topic (README curriculum table, falling back to the `work/` subdirectory), `::::warning` for follow-ups/special requirements, examples, constraints, and the solution code verbatim.
|
||||||
- The filename prefix is the leetcode problem number — it orders the sidebar numerically and is stripped from the URL (`344-reverse-string.mdx` → `/solutions/reverse-string`). The script renames any page whose filename drifts from `<leetcode-number>-<kebab-slug>.mdx`.
|
- `(<category>)` is the kebab-cased `work/` subdirectory (`(array)`, `(hash-table)`, `(two-pointers)`, …) and becomes the sidebar group; the leetcode-number filename prefix orders pages numerically. Both are stripped from the URL (`(two-pointers)/344-reverse-string.mdx` → `/solutions/reverse-string`). The script moves any page whose path drifts from this scheme.
|
||||||
|
- Frontmatter `title` keeps the number (`'344. Reverse String'`); `sidebar.label` is the number-free problem name. The script backfills a missing `sidebar.label` on existing pages but never overwrites one.
|
||||||
- Already-ported pages (frontmatter title starts with the leetcode number) only get their `## Solution` section regenerated — curated prose is never touched.
|
- Already-ported pages (frontmatter title starts with the leetcode number) only get their `## Solution` section regenerated — curated prose is never touched.
|
||||||
- Problems solved in both languages render as a `<CodeGroup>` (Python first, then JavaScript); single-language solutions render as a plain fence.
|
- Problems solved in both languages render as a `<CodeGroup>` (Python first, then JavaScript); single-language solutions render as a plain fence.
|
||||||
|
|
||||||
The script prints the report table: work file → docs page → created / renamed / updated / skipped (already in sync).
|
The script prints the report table: work file → docs page → created / moved / updated / skipped (already in sync).
|
||||||
|
|
||||||
## Review
|
## Review
|
||||||
|
|
||||||
|
|||||||
+21
-9
@@ -10,7 +10,7 @@
|
|||||||
* renders as a <CodeGroup> (Python first); single-language solutions
|
* renders as a <CodeGroup> (Python first); single-language solutions
|
||||||
* render as a plain fence.
|
* render as a plain fence.
|
||||||
*/
|
*/
|
||||||
import { readdir, rename } from "node:fs/promises";
|
import { mkdir, rename } from "node:fs/promises";
|
||||||
import { basename, join } from "node:path";
|
import { basename, join } from "node:path";
|
||||||
|
|
||||||
const ROOT = join(import.meta.dir, "..");
|
const ROOT = join(import.meta.dir, "..");
|
||||||
@@ -284,6 +284,7 @@ function renderPage(sol: Solution, topic: string): string {
|
|||||||
`title: '${h.num}. ${h.title.replaceAll("'", "''")}'`,
|
`title: '${h.num}. ${h.title.replaceAll("'", "''")}'`,
|
||||||
/[:#]/.test(description) ? `description: '${description.replaceAll("'", "''")}'` : `description: ${description}`,
|
/[:#]/.test(description) ? `description: '${description.replaceAll("'", "''")}'` : `description: ${description}`,
|
||||||
"sidebar:",
|
"sidebar:",
|
||||||
|
` label: '${h.title.replaceAll("'", "''")}'`,
|
||||||
` badge: '${h.difficulty}'`,
|
` badge: '${h.difficulty}'`,
|
||||||
"---",
|
"---",
|
||||||
"",
|
"",
|
||||||
@@ -346,12 +347,20 @@ for await (const rel of new Bun.Glob("**/*.{js,py}").scan(WORK)) {
|
|||||||
|
|
||||||
const topics = await readmeTopics();
|
const topics = await readmeTopics();
|
||||||
|
|
||||||
|
/** Insert a number-free `sidebar.label` into existing frontmatter when missing. */
|
||||||
|
function ensureLabel(page: string, h: Header): string {
|
||||||
|
const fm = page.match(/^---\n[\s\S]*?\n---/);
|
||||||
|
if (!fm || /^ {2}label: /m.test(fm[0])) return page;
|
||||||
|
const label = ` label: '${h.title.replaceAll("'", "''")}'`;
|
||||||
|
return page.replace(/^sidebar:$/m, `sidebar:\n${label}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Existing docs pages, keyed by leetcode number from the frontmatter title.
|
// Existing docs pages, keyed by leetcode number from the frontmatter title.
|
||||||
const pages = new Map<number, string>(); // num -> filename
|
const pages = new Map<number, string>(); // num -> path relative to DOCS
|
||||||
for (const f of (await readdir(DOCS)).filter((f) => f.endsWith(".mdx")).sort()) {
|
for (const rel of (await Array.fromAsync(new Bun.Glob("**/*.mdx").scan(DOCS))).sort()) {
|
||||||
const text = await Bun.file(join(DOCS, f)).text();
|
const text = await Bun.file(join(DOCS, rel)).text();
|
||||||
const t = text.match(/^title: '(\d+)\./m);
|
const t = text.match(/^title: '(\d+)\./m);
|
||||||
if (t) pages.set(Number(t[1]), f);
|
if (t) pages.set(Number(t[1]), rel);
|
||||||
}
|
}
|
||||||
|
|
||||||
const report: [string, string, string][] = [];
|
const report: [string, string, string][] = [];
|
||||||
@@ -360,8 +369,11 @@ const ordered = [...solutions.values()].sort((a, b) => a.num - b.num);
|
|||||||
for (const sol of ordered) {
|
for (const sol of ordered) {
|
||||||
const langs = Object.keys(sol.code).sort().join("+");
|
const langs = Object.keys(sol.code).sort().join("+");
|
||||||
const workRef = `${sol.num}.${sol.slug} (${langs})`;
|
const workRef = `${sol.num}.${sol.slug} (${langs})`;
|
||||||
const name = `${sol.num}-${sol.slug}.mdx`; // prefix = leetcode number (numeric sidebar order, stripped from the URL)
|
// (category) group folder + leetcode-number prefix: numeric sidebar order, both stripped from the URL.
|
||||||
|
const group = `(${sol.category.toLowerCase().replaceAll(" ", "-")})`;
|
||||||
|
const name = `${group}/${sol.num}-${sol.slug}.mdx`;
|
||||||
const existing = pages.get(sol.num);
|
const existing = pages.get(sol.num);
|
||||||
|
await mkdir(join(DOCS, group), { recursive: true });
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
const page = await Bun.file(join(DOCS, existing)).text();
|
const page = await Bun.file(join(DOCS, existing)).text();
|
||||||
@@ -370,15 +382,15 @@ for (const sol of ordered) {
|
|||||||
report.push([workRef, existing, "ERROR: no '## Solution' heading"]);
|
report.push([workRef, existing, "ERROR: no '## Solution' heading"]);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const next = page.slice(0, at) + solutionSection(sol);
|
const next = ensureLabel(page.slice(0, at) + solutionSection(sol), sol.header);
|
||||||
const actions: string[] = [];
|
const actions: string[] = [];
|
||||||
if (existing !== name) {
|
if (existing !== name) {
|
||||||
await rename(join(DOCS, existing), join(DOCS, name));
|
await rename(join(DOCS, existing), join(DOCS, name));
|
||||||
actions.push("renamed");
|
actions.push("moved");
|
||||||
}
|
}
|
||||||
if (next !== page) {
|
if (next !== page) {
|
||||||
await Bun.write(join(DOCS, name), next);
|
await Bun.write(join(DOCS, name), next);
|
||||||
actions.push("updated (solution)");
|
actions.push("updated");
|
||||||
}
|
}
|
||||||
report.push([workRef, name, actions.join(" + ") || "skipped (in sync)"]);
|
report.push([workRef, name, actions.join(" + ") || "skipped (in sync)"]);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user