import fs from "fs" import path from "path" import type { Part, Chapter, Section } from "./book-data" const BOOK_DIR = path.join(process.cwd(), "book") const CHINESE_NUM_MAP: Record = { 一: "01", 二: "02", 三: "03", 四: "04", 五: "05", 六: "06", 七: "07", 八: "08", 九: "09", 十: "10", } const SUBTITLES: Record = { 真实的人: "人性观察与社交逻辑", 真实的行业: "社会运作的底层规则", 真实的错误: "错过机会比失败更贵", 真实的赚钱: "所有行业的杠杆结构", 真实的社会: "人与系统的关系", } function parsePartFolderName(folderName: string): { number: string; title: string } | null { const match = folderName.match(/_第([一二三四五六七八九十]+)篇|(.+)/) if (match) { return { number: CHINESE_NUM_MAP[match[1]] || match[1], title: match[2], } } return null } function parseChapterFolderName(folderName: string): { id: string; title: string } | null { const match = folderName.match(/第(\d+)章|(.+)/) if (match) { return { id: match[1], title: match[2], } } return null } function parseSectionFileName(fileName: string): { id: string; title: string } | null { if (!fileName.endsWith(".md")) return null const name = fileName.replace(".md", "") const match = name.match(/^([\d.]+)\s+(.+)/) if (match) { return { id: match[1], title: match[2], } } return { id: fileName, title: name, } } export function getBookStructure(): Part[] { if (!fs.existsSync(BOOK_DIR)) { return [] } const partFolders = fs.readdirSync(BOOK_DIR).filter((f) => { const fullPath = path.join(BOOK_DIR, f) return fs.statSync(fullPath).isDirectory() && f.startsWith("_") }) const parts: Part[] = partFolders .map((folderName) => { const parsed = parsePartFolderName(folderName) if (!parsed) return null const partPath = path.join(BOOK_DIR, folderName) const chapterFolders = fs.readdirSync(partPath).filter((f) => { const fullPath = path.join(partPath, f) return fs.statSync(fullPath).isDirectory() && f.startsWith("第") }) const chapters: Chapter[] = chapterFolders .map((chapterFolderName) => { const parsedChapter = parseChapterFolderName(chapterFolderName) if (!parsedChapter) return null const chapterPath = path.join(partPath, chapterFolderName) const sectionFiles = fs.readdirSync(chapterPath).filter((f) => f.endsWith(".md")) const sections: Section[] = sectionFiles .map((fileName) => { const parsedSection = parseSectionFileName(fileName) if (!parsedSection) return null return { id: parsedSection.id, title: parsedSection.title, price: 1, isFree: parsedSection.id.endsWith(".1"), filePath: path.relative(process.cwd(), path.join(chapterPath, fileName)), } }) .filter((s): s is Section => s !== null) .sort((a, b) => { const partsA = a.id.split(".").map(Number) const partsB = b.id.split(".").map(Number) for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) { const valA = partsA[i] || 0 const valB = partsB[i] || 0 if (valA !== valB) return valA - valB } return 0 }) return { id: parsedChapter.id, title: parsedChapter.title, sections, } }) .filter((c): c is Chapter => c !== null) .sort((a, b) => Number(a.id) - Number(b.id)) return { id: parsed.number, number: parsed.number, title: parsed.title, subtitle: SUBTITLES[parsed.title] || "", chapters, } }) .filter((p): p is Part => p !== null) .sort((a, b) => Number(a.number) - Number(b.number)) return parts } export function getSectionBySlug(slug: string): Section | null { const parts = getBookStructure() for (const part of parts) { for (const chapter of part.chapters) { for (const section of chapter.sections) { if (section.id === slug) { return section } } } } return null } export function getChapterBySectionSlug(slug: string): { part: Part; chapter: Chapter } | null { const parts = getBookStructure() for (const part of parts) { for (const chapter of part.chapters) { for (const section of chapter.sections) { if (section.id === slug) { return { part, chapter } } } } } return null }