Skip to content

Commit f599a07

Browse files
authored
feat(FR-2597): add --chapters option to docs-toolkit pdf for selective chapter export (#6775)
1 parent 12c5c10 commit f599a07

4 files changed

Lines changed: 87 additions & 7 deletions

File tree

packages/backend.ai-docs-toolkit/src/cli.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ Options:
4141
pdf:
4242
--lang <all|en|ko|...> Language(s) to generate (default: all)
4343
--theme <name> Theme name (default: default)
44+
--chapters <list> Comma-separated chapter names to include (default: all)
45+
--note <text> Note displayed on the cover page (e.g. "Draft — internal review")
4446
4547
preview:
4648
--mode <sample|catalog|document> Preview mode (default: sample)
@@ -66,6 +68,7 @@ Options:
6668
Examples:
6769
docs-toolkit pdf --lang all
6870
docs-toolkit pdf --lang en
71+
docs-toolkit pdf --lang ko --chapters "quickstart,session_page,model_serving"
6972
docs-toolkit preview
7073
docs-toolkit preview --mode document --lang ko
7174
docs-toolkit preview:html --lang en
@@ -91,6 +94,17 @@ function hasFlag(argv: string[], flag: string): boolean {
9194
return argv.includes(flag);
9295
}
9396

97+
function getFlagValue(argv: string[], flag: string): string | undefined {
98+
const idx = argv.indexOf(flag);
99+
if (idx < 0) return undefined;
100+
const value = argv[idx + 1];
101+
if (!value || value.startsWith('--')) {
102+
console.error(`Error: ${flag} requires a value.`);
103+
process.exit(1);
104+
}
105+
return value;
106+
}
107+
94108
// ── Init Command ────────────────────────────────────────────────
95109

96110
async function runInit(): Promise<void> {
@@ -343,11 +357,18 @@ async function main(): Promise<void> {
343357
switch (command) {
344358
case 'pdf': {
345359
const { generatePdf } = await import('./generate-pdf.js');
346-
const langIdx = argv.indexOf('--lang');
347-
const themeIdx = argv.indexOf('--theme');
360+
const langArg = getFlagValue(argv, '--lang') ?? 'all';
361+
const themeArg = getFlagValue(argv, '--theme') ?? 'default';
362+
const chaptersRaw = getFlagValue(argv, '--chapters');
363+
const chaptersArg = chaptersRaw
364+
? chaptersRaw.split(',').map((c) => c.trim())
365+
: undefined;
366+
const noteArg = getFlagValue(argv, '--note');
348367
await generatePdf(config, {
349-
lang: langIdx >= 0 ? argv[langIdx + 1] : 'all',
350-
theme: themeIdx >= 0 ? argv[themeIdx + 1] : 'default',
368+
lang: langArg,
369+
theme: themeArg,
370+
chapters: chaptersArg,
371+
note: noteArg,
351372
});
352373
break;
353374
}

packages/backend.ai-docs-toolkit/src/generate-pdf.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ interface BookConfig {
1818
export interface GeneratePdfOptions {
1919
lang: string;
2020
theme: string;
21+
chapters?: string[];
22+
note?: string;
2123
}
2224

2325
function parseArgs(argv: string[]): GeneratePdfOptions {
@@ -95,12 +97,46 @@ export async function generatePdf(
9597
const startTime = Date.now();
9698
console.log(`[${lang}] Generating PDF...`);
9799

98-
const navigation = bookConfig.navigation[lang];
100+
let navigation = bookConfig.navigation[lang];
99101
if (!navigation) {
100102
console.warn(`[${lang}] No navigation found, skipping`);
101103
continue;
102104
}
103105

106+
// Filter chapters if --chapters option is specified
107+
const chapterFilter = args.chapters?.filter((c) => c.length > 0);
108+
if (chapterFilter && chapterFilter.length > 0) {
109+
const filterSet = new Set(chapterFilter);
110+
const filtered = navigation.filter((nav) => {
111+
const noExt = nav.path.replace(/\.md$/, '');
112+
const segments = noExt.split('/');
113+
const pathStem = segments[segments.length - 1];
114+
const dirName = segments.length > 1 ? segments[segments.length - 2] : '';
115+
return (
116+
filterSet.has(nav.path) ||
117+
filterSet.has(pathStem) ||
118+
(dirName !== '' && filterSet.has(dirName))
119+
);
120+
});
121+
if (filtered.length === 0) {
122+
const available = navigation
123+
.map((n) => n.path.replace(/\.md$/, '').split('/').pop())
124+
.join(', ');
125+
console.error(
126+
`[${lang}] No chapters matched filter: ${chapterFilter.join(', ')}`,
127+
);
128+
console.error(
129+
`[${lang}] Chapter identifiers can be a full path (e.g. overview/overview.md), directory name, or filename.`,
130+
);
131+
console.error(`[${lang}] Available chapters: ${available}`);
132+
process.exit(1);
133+
}
134+
navigation = filtered;
135+
console.log(
136+
`[${lang}] Filtered to ${navigation.length} chapter(s): ${navigation.map((n) => n.title).join(', ')}`,
137+
);
138+
}
139+
104140
// Process markdown files
105141
console.log(`[${lang}] Processing ${navigation.length} chapters...`);
106142
const chapters = await processMarkdownFiles(
@@ -115,7 +151,7 @@ export async function generatePdf(
115151
console.log(`[${lang}] Building HTML...`);
116152
const html = buildFullDocument(
117153
chapters,
118-
{ title, version, lang },
154+
{ title, version, lang, note: args.note },
119155
config,
120156
theme,
121157
);

packages/backend.ai-docs-toolkit/src/html-builder.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ export interface DocMetadata {
99
title: string;
1010
version: string;
1111
lang: string;
12+
note?: string;
13+
}
14+
15+
function escapeHtml(text: string): string {
16+
return text
17+
.replace(/&/g, '&amp;')
18+
.replace(/</g, '&lt;')
19+
.replace(/>/g, '&gt;')
20+
.replace(/"/g, '&quot;')
21+
.replace(/\n/g, '<br>');
1222
}
1323

1424
function getFormattedDate(lang: string): string {
@@ -52,7 +62,7 @@ function buildCoverHtml(
5262
<p class="company">${config.company}</p>
5363
<p class="date">${date}</p>
5464
<p class="lang">${langLabel}</p>
55-
</div>
65+
</div>${metadata.note ? `\n <div class="cover-note">${escapeHtml(metadata.note)}</div>` : ''}
5666
</section>
5767
`;
5868
}

packages/backend.ai-docs-toolkit/src/styles.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,19 @@ body {
105105
border: none;
106106
}
107107
108+
.cover-note {
109+
margin-top: 32px;
110+
padding: 12px 20px;
111+
border: 1.5px solid ${theme.brandColor};
112+
border-radius: 6px;
113+
background: #fafafa;
114+
color: #444;
115+
font-size: 11pt;
116+
line-height: 1.5;
117+
max-width: 420px;
118+
text-align: center;
119+
}
120+
108121
/* ==========================================================================
109122
Table of Contents
110123
========================================================================== */

0 commit comments

Comments
 (0)