import { ADAPT_TEXT, ADAPT_TITLE, BY_AUTHOR, CALLOUT_BODY, CALLOUT_GRAMMAR, CALLOUT_LEAD, CALLOUT_TAIL, CHAPTER_SUMMARIES, CONTEXT_NOTE, CONTEXT_TEXT, CONTEXT_TITLE, EXHIBIT_LEAD, EXHIBIT_TITLE, EXPECT_TEXT, EXPECT_TITLE, FORMAT_TEXT, FORMAT_TITLE, INTRO_TEXT, LICENSED_UNDER, LOOP_LEAD, LOOP_TITLE, MACHINE_BODY, MACHINE_INDEX, MACHINE_LEAD, MACHINE_TAIL, PROBLEM_ITEMS, PROBLEM_LEAD, PROBLEM_TITLE, QUESTIONS_LEAD, QUESTIONS_NOTE, QUESTIONS_TITLE, QUESTION_ITEMS, READING_LEAD, READING_TITLE, SETUP_STEPS, STANCE_ITEMS, STANCE_LEAD, STANCE_TAIL, STANCE_TITLE, START_LEAD, START_TITLE, WHY_ITEMS, WHY_LEAD, WHY_TITLE, WORKS_LEAD, WORKS_NOTE, WORKS_TITLE, } from "#configuration/strings/readme.strings"; import { ANATOMY_PAGE, GRAMMAR_PAGE } from "@banes-lab/web/core/ids/page.ids.ts"; import { ANCHOR_MARK, BOLD, CENTER_CLOSE, CENTER_OPEN, FENCE_LANGUAGE, HEADING_ONE, HEADING_THREE, ICON_SIZE, LLMS_ROUTE, LOGO_WIDTH, QUOTE_MARK, SENTENCE_END, } from "#configuration/constants/readme.constants"; import { AUTHOR_PROFILE, METHODOLOGY_REPOSITORY, SITE_URL, pagePath } from "@banes-lab/web/core/assets/link.assets.ts"; import { BADGE_LOGO, LICENSE_BY, LICENSE_CC, LICENSE_SA } from "@banes-lab/web/core/assets/image.assets.ts"; import { CHAPTER_SEPARATOR, FENCE_MARK, FOLDER_SEPARATOR, LIST_ITEM, NESTED_ITEM, PARAGRAPH_BREAK, } from "#configuration/constants/chapter.constants"; import { LOOP_SECTION_ID, STANCE_SECTION_ID } from "@banes-lab/web/core/ids/methodology.start.ids.ts"; import type { ReadmeInputs, ReadmePage, ReadmeSection, ReadmeStep, ReadmeTab } from "#types/readme.types"; import { attributionLine, bullets, image, link, note, numbered, section, tabFileOf, } from "#core/renderers/markdown.renderer"; import { isRepositoryPage, pageFolderOf } from "#core/normalizers/readme.normalizer"; import type { Attribution } from "#types/chapter.types"; import { LINE_END } from "#configuration/constants/inventory.constants"; import { START_TAB } from "@banes-lab/web/core/ids/methodology.ids.ts"; import { renderFaq } from "#core/renderers/faq.renderer"; const ATTRIBUTION_SPACE = " "; const ITEM_INDENT = " "; const header = function header(inputs: ReadmeInputs, attribution: Attribution): string { const owner = link(inputs.heading, METHODOLOGY_REPOSITORY) + ATTRIBUTION_SPACE + attribution.copyright + BY_AUTHOR + link(attribution.author, AUTHOR_PROFILE); const licence = LICENSED_UNDER + link(attribution.licenseName, attribution.licenseUrl); const icons = [LICENSE_CC, LICENSE_BY, LICENSE_SA].map((icon) => image(SITE_URL + icon, ICON_SIZE, "")).join(""); const logo = image(SITE_URL + BADGE_LOGO, LOGO_WIDTH, inputs.heading); return [CENTER_OPEN, "", logo, "", owner + licence + ATTRIBUTION_SPACE + icons, "", CENTER_CLOSE].join(LINE_END); }; const callout = function callout(inputs: ReadmeInputs): string { const grammar = inputs.exhibits.find((page) => page.page === GRAMMAR_PAGE); const lines = [ CALLOUT_LEAD, "", CALLOUT_BODY + link(CALLOUT_GRAMMAR, SITE_URL + pagePath(GRAMMAR_PAGE)) + CALLOUT_TAIL, "", grammar?.description ?? "", "", MACHINE_LEAD, "", MACHINE_BODY + link(MACHINE_INDEX, SITE_URL + LLMS_ROUTE) + MACHINE_TAIL, ]; return lines.map((line) => (line.length === 0 ? QUOTE_MARK.trim() : QUOTE_MARK + line)).join(LINE_END); }; const sectionsOf = function sectionsOf(page: ReadmePage): readonly ReadmeSection[] { return page.tabs.flatMap((tab) => tab.sections); }; const chapterEntry = function chapterEntry(tab: ReadmeTab): string { const summary = CHAPTER_SUMMARIES.find((entry) => entry.tab === tab.id)?.summary ?? ""; return [HEADING_THREE + link(tab.label, tabFileOf(tab.id)), summary].join(PARAGRAPH_BREAK); }; const works = function works(page: ReadmePage): string { return section(WORKS_TITLE, WORKS_LEAD, WORKS_NOTE, ...page.tabs.map(chapterEntry)); }; const stance = function stance(): string { const target = tabFileOf(START_TAB) + ANCHOR_MARK + STANCE_SECTION_ID; const tail = STANCE_TAIL + link(STANCE_TITLE, target) + SENTENCE_END; return section(STANCE_TITLE, STANCE_LEAD, numbered(STANCE_ITEMS), tail); }; const exhibitChapters = function exhibitChapters(page: ReadmePage): string[] { if (!isRepositoryPage(page.page)) { return []; } const folder = pageFolderOf(page.page, page.title); return page.tabs.map((tab) => NESTED_ITEM + link(tab.label, folder + FOLDER_SEPARATOR + tabFileOf(tab.id))); }; const exhibits = function exhibits(inputs: ReadmeInputs): string { const items = inputs.exhibits.flatMap((page) => [ LIST_ITEM + link(page.title, SITE_URL + pagePath(page.page)) + PARAGRAPH_BREAK + ITEM_INDENT + page.description, ...exhibitChapters(page), ]); return section(EXHIBIT_TITLE, EXHIBIT_LEAD, items.join(LINE_END)); }; const stepGroup = function stepGroup(group: ReadmeStep): string { return [HEADING_THREE + group.title, numbered(group.steps)].join(PARAGRAPH_BREAK); }; const reading = function reading(page: ReadmePage): string { return section(READING_TITLE, READING_LEAD, numbered(page.tabs.map((tab) => link(tab.label, tabFileOf(tab.id))))); }; const loop = function loop(page: ReadmePage): string { const diagram = sectionsOf(page).find((candidate) => candidate.id === LOOP_SECTION_ID)?.diagram; if (diagram === null || diagram === undefined) { return ""; } return section(LOOP_TITLE, LOOP_LEAD, [FENCE_MARK + FENCE_LANGUAGE, diagram, FENCE_MARK].join(LINE_END)); }; export const renderReadme = function renderReadme(inputs: ReadmeInputs, attribution: Attribution): string { const body = [ header(inputs, attribution), callout(inputs), [HEADING_ONE + inputs.heading, BOLD + inputs.subtitle + BOLD, INTRO_TEXT].join(PARAGRAPH_BREAK), section(PROBLEM_TITLE, PROBLEM_LEAD, bullets(PROBLEM_ITEMS)), works(inputs.methodology), section(FORMAT_TITLE, FORMAT_TEXT), stance(), section(WHY_TITLE, WHY_LEAD, bullets(WHY_ITEMS)), exhibits(inputs), section(START_TITLE, START_LEAD, ...SETUP_STEPS.map(stepGroup)), section(CONTEXT_TITLE, CONTEXT_TEXT, note(CONTEXT_NOTE, SITE_URL + pagePath(ANATOMY_PAGE))), section(QUESTIONS_TITLE, QUESTIONS_LEAD, bullets(QUESTION_ITEMS), note(QUESTIONS_NOTE, SITE_URL + LLMS_ROUTE)), section(ADAPT_TITLE, ADAPT_TEXT), section(EXPECT_TITLE, EXPECT_TEXT), reading(inputs.methodology), loop(inputs.methodology), renderFaq(inputs.faq), ].filter((part) => part.length > 0); return body.join(CHAPTER_SEPARATOR) + CHAPTER_SEPARATOR + attributionLine(attribution) + LINE_END; };