# core/renderers/chapter.renderer.ts

> 97 lines of code and 24 definitions.

Tree: Build tree
Language: typescript
Layer: product
Canonical: https://banes-lab.com/anatomy/build#file-build-core-renderers-chapter-renderer-ts
Source text: https://banes-lab.com/assets/sources/source.9bf73960ef72abd2647bbc8d93407da6d572d105b29cfebcd867d67f42459d41.generated.txt

## Definitions

- `chapterPathOf` (lexical_declaration, line 26, exported)
- `repositoryResolver` (lexical_declaration, line 44, exported)
- `relativeTo` (lexical_declaration, line 36)
- `chapterList` (lexical_declaration, line 67)
- `folderOfFile` (lexical_declaration, line 89)
- `renderChapters` (lexical_declaration, line 94, exported)
- `chapterOf` (lexical_declaration, line 74)
- `folderOf` (lexical_declaration, line 22)
- `folder` (lexical_declaration, line 31, exported)
- `file` (lexical_declaration, line 32, exported)
- `prefix` (lexical_declaration, line 40)
- `found` (lexical_declaration, line 50, exported)
- `source` (lexical_declaration, line 51, exported)
- `footerOf` (lexical_declaration, line 58)
- `root` (lexical_declaration, line 59)
- `links` (lexical_declaration, line 60)
- `label` (lexical_declaration, line 61)
- `items` (lexical_declaration, line 68)
- `parts` (lexical_declaration, line 80)
- `leads` (lexical_declaration, line 81)
- `body` (lexical_declaration, line 85)
- `slash` (lexical_declaration, line 90)
- `composed` (lexical_declaration, line 95, exported)
- `chapters` (lexical_declaration, line 96, exported)

## Uses

- [core/normalizers/link.normalizer.ts](https://banes-lab.com/source/build/core/normalizers/link.normalizer.ts.md)
- [core/normalizers/readme.normalizer.ts](https://banes-lab.com/source/build/core/normalizers/readme.normalizer.ts.md)
- [core/renderers/readme.renderer.ts](https://banes-lab.com/source/build/core/renderers/readme.renderer.ts.md)

## Source

```typescript
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))),
    }));
};
```
