core/formatters/readme.section.formatter.ts
core/formatters/readme.section.formatter.ts is a file in GovLab Docs. 134 lines of code and 33 definitions.
import type {
ApiNote,
ConceptRecord,
Domain,
FlatRecord,
PackageJson,
PrincipleRecord,
PublicMember,
RenderContext,
RepoMetrics,
} from "#types/readme.types";
import {
README_HEADINGS,
README_TEXT,
RELATION_LABELS,
REPO_LABELS,
metricsLine,
} from "#configuration/strings/readme.strings";
import { bullets, titleCase } from "#core/formatters/markdown.formatter";
const METRIC_SEPARATOR = " · ";
const LIST_SEPARATOR = ", ";
const NOTE_JOIN = " — ";
const RULE = "---";
const TICK = "`";
const byText = function byText(left: string, right: string): number {
return left.localeCompare(right);
};
const surfaceLine = function surfaceLine(member: PublicMember, notes: ReadonlyMap<string, string>): string {
const head =
member.signature && member.signature !== member.name
? `\`${member.signature}\``
: `\`${member.name}\` (${member.kind})`;
const note = notes.get(member.name) ?? "";
return `- ${head}${note.length > 0 ? NOTE_JOIN + note : ""}`;
};
const axisBlock = function axisBlock(
axis: string,
surface: readonly PublicMember[],
notes: ReadonlyMap<string, string>,
): string {
const lines = surface.filter((member) => member.axis === axis).map((member) => surfaceLine(member, notes));
return `### ${titleCase(axis)}\n\n${lines.join("\n")}`;
};
export const renderApi = function renderApi(surface: readonly PublicMember[], apiNotes?: readonly ApiNote[]): string {
const notes = new Map((apiNotes ?? []).map((note) => [note.name, note.note]));
const axes = [...new Set(surface.map((member) => member.axis))];
if (axes.length <= 1) {
return surface.map((member) => surfaceLine(member, notes)).join("\n");
}
return axes.map((axis) => axisBlock(axis, surface, notes)).join("\n\n");
};
export const renderDeps = function renderDeps(pkg: PackageJson): string {
const deps = Object.keys(pkg.dependencies ?? {}).toSorted(byText);
if (deps.length > 0) {
return bullets(deps.map((dep) => `\`${dep}\``));
}
return Object.keys(pkg).length === 0 ? README_TEXT.noLocalPackage : README_TEXT.leaf;
};
const relationClause = function relationClause(label: string, list: readonly string[] | undefined): string {
return list !== undefined && list.length > 0 ? ` ${label} ${list.join(LIST_SEPARATOR)}.` : "";
};
const principleLine = function principleLine(record: PrincipleRecord): string {
const relations = [
relationClause(RELATION_LABELS.reinforces, record.reinforces),
relationClause(RELATION_LABELS.enables, record.enables),
relationClause(RELATION_LABELS.tensions, record.tensions_with),
relationClause(RELATION_LABELS.conflicts, record.conflicts_with),
].join("");
return `- **${record.name}** — _${record.category}_ · ${record.severity}.${relations}`;
};
export const renderPrinciples = function renderPrinciples(records: readonly PrincipleRecord[]): string {
return `## ${README_HEADINGS.principles}\n\n${README_TEXT.principlesIntro}\n\n${records.map(principleLine).join("\n")}`;
};
export const renderConcepts = function renderConcepts(records: readonly ConceptRecord[]): string {
const lines = records
.toSorted((left, right) => byText(left.dimension, right.dimension) || byText(left.id, right.id))
.map((record) => `- **${record.id}** — _${record.dimension}_`);
return `## ${README_HEADINGS.concepts}\n\n${README_TEXT.conceptsIntro}\n\n${lines.join("\n")}`;
};
export const renderDomains = function renderDomains(records: readonly Domain[]): string {
const byMeta = new Map<string, string[]>();
for (const domain of records) {
byMeta.set(domain.meta, [...(byMeta.get(domain.meta) ?? []), domain.sub]);
}
const lines = [...byMeta]
.toSorted((left, right) => byText(left[0], right[0]))
.map(([meta, subs]) => `- **${meta}** — ${subs.toSorted(byText).join(LIST_SEPARATOR)}`);
return `## ${README_HEADINGS.domains}\n\n${README_TEXT.domainsIntro}\n\n${lines.join("\n")}`;
};
const textRow = function textRow(label: string, value: string | undefined, code: boolean): string[] {
if (value === undefined || value.length === 0) {
return [];
}
const shown = code ? TICK + value + TICK : value;
return [`- **${label}**: ${shown}`];
};
const numberRow = function numberRow(label: string, value: number | undefined): string[] {
return value === undefined ? [] : [`- **${label}**: ${value}`];
};
const languageRow = function languageRow(languages: FlatRecord | undefined): string[] {
const entries = Object.entries(languages ?? {});
if (entries.length === 0) {
return [];
}
const counts = entries.map(([extension, count]) => `${extension} ${count}`).join(LIST_SEPARATOR);
return [`- **${REPO_LABELS.languages}**: ${counts}`];
};
export const renderRepoMetrics = function renderRepoMetrics(repo: RepoMetrics | null): string {
if (!repo) {
return "";
}
const rows = [
...textRow(REPO_LABELS.branch, repo.branch, true),
...numberRow(REPO_LABELS.commits, repo.commits),
...numberRow(REPO_LABELS.contributors, repo.contributors),
...textRow(REPO_LABELS.created, repo.created, false),
...textRow(REPO_LABELS.lastCommit, repo.lastCommit, false),
...textRow(REPO_LABELS.latestTag, repo.latestTag, true),
...numberRow(REPO_LABELS.files, repo.files),
...languageRow(repo.languages),
];
if (rows.length === 0) {
return "";
}
return `## ${README_HEADINGS.repository}\n\n${README_TEXT.repositoryNote}\n\n${rows.join("\n")}`;
};
export const renderMetrics = function renderMetrics(context: RenderContext): string {
const deps = Object.keys(context.pkg.dependencies ?? {}).length;
const stats = [
...(context.maturity === "" ? [] : [context.maturity]),
...metricsLine(context.surface.length, deps, context.principles.length, context.concepts.length),
];
return `${RULE}\n\n${stats.join(METRIC_SEPARATOR)}`;
};