import { ATTRIBUTION_SEPARATOR, CHAPTER_FOOTER_LEAD, CHAPTER_README_LEAD, README_LABEL, } from "#configuration/strings/chapter.strings"; import type { Attribution, Chapter, ChapterSource, LinkResolver, PageSources, ShapeInput } from "#types/chapter.types"; import { CHAPTER_SEPARATOR, FOLDER_SEPARATOR, LIST_ITEM, PARAGRAPH_BREAK, PARENT_FOLDER, README_FILE, } from "#configuration/constants/chapter.constants"; import { attributionLine, link, tabFileOf } from "#core/renderers/markdown.renderer"; import { LINE_END } from "#configuration/constants/inventory.constants"; import { pageFolderOf } from "#core/normalizers/readme.normalizer"; import { relinked } from "#core/normalizers/link.normalizer"; import { renderReadme } from "#core/renderers/readme.renderer"; const folderOf = function folderOf(page: PageSources): string { return pageFolderOf(page.page, page.label); }; export const chapterPathOf = function chapterPathOf( page: PageSources, source: ChapterSource, composed: boolean, ): string { const folder = folderOf(page); const file = source.first && !composed && folder.length === 0 ? README_FILE : tabFileOf(source.tab); return folder.length === 0 ? file : folder + FOLDER_SEPARATOR + file; }; const relativeTo = function relativeTo(from: string, target: string): string { if (from.length === 0) { return target; } const prefix = from + FOLDER_SEPARATOR; return target.startsWith(prefix) ? target.slice(prefix.length) : PARENT_FOLDER + target; }; export const repositoryResolver = function repositoryResolver( pages: readonly PageSources[], composed: boolean, from: string, ): LinkResolver { return (page, tab) => { const found = pages.find((candidate) => candidate.page === page); const source = found?.sources.find((candidate) => (tab === null ? candidate.first : candidate.tab === tab)); return found === undefined || source === undefined ? null : relativeTo(from, chapterPathOf(found, source, composed)); }; }; const footerOf = function footerOf(page: PageSources, composed: boolean): string { const root = folderOf(page).length === 0; const links = page.sources.map((source) => { const label = source.first && !composed && root ? README_LABEL : source.label; return link(label, relativeTo(folderOf(page), chapterPathOf(page, source, composed))); }); return CHAPTER_FOOTER_LEAD + links.join(ATTRIBUTION_SEPARATOR); }; const chapterList = function chapterList(page: PageSources): string { const items = page.sources .filter((source) => !source.first) .map((source) => LIST_ITEM + link(source.label, tabFileOf(source.tab))); return [CHAPTER_README_LEAD, "", ...items].join(LINE_END); }; const chapterOf = function chapterOf( page: PageSources, source: ChapterSource, attribution: Attribution, composed: boolean, ): Chapter { const parts = [attributionLine(attribution), source.markdown.trimEnd()]; const leads = source.first && !composed && folderOf(page).length === 0; if (leads) { parts.push(chapterList(page)); } const body = parts.join(PARAGRAPH_BREAK) + CHAPTER_SEPARATOR + footerOf(page, composed) + LINE_END; return { body, file: chapterPathOf(page, source, composed), label: source.label }; }; const folderOfFile = function folderOfFile(file: string): string { const slash = file.lastIndexOf(FOLDER_SEPARATOR); return slash === -1 ? "" : file.slice(0, slash); }; export const renderChapters = function renderChapters(input: ShapeInput, attribution: Attribution): Chapter[] { const composed = input.readme !== null; const chapters = input.pages.flatMap((page) => page.sources.map((source) => chapterOf(page, source, attribution, composed)), ); if (input.readme !== null) { chapters.unshift({ body: renderReadme(input.readme, attribution), file: README_FILE, label: README_LABEL }); } return chapters.map((chapter) => ({ ...chapter, body: relinked(chapter.body, repositoryResolver(input.pages, composed, folderOfFile(chapter.file))), })); };