Skip to content

Commit

Permalink
validate front matter
Browse files Browse the repository at this point in the history
  • Loading branch information
Fil committed Mar 19, 2024
1 parent 2a886d5 commit abbe0a4
Showing 1 changed file with 60 additions and 1 deletion.
61 changes: 60 additions & 1 deletion src/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ export interface ParseContext {
path: string;
}

interface FrontMatter {
title?: string;
toc?: boolean | {show?: boolean; label?: string};
index?: boolean;
keywords?: string[];
draft?: boolean;
sql?: {[key: string]: string};
}

function uniqueCodeId(context: ParseContext, content: string): string {
const hash = createHash("sha256").update(content).digest("hex").slice(0, 8);
let id = hash;
Expand Down Expand Up @@ -338,7 +347,7 @@ export function createMarkdownIt({

export function parseMarkdown(input: string, options: ParseOptions): MarkdownPage {
const {md, path} = options;
const {content, data} = matter(input, {});
const {content, data} = getContents(input);
const code: MarkdownCode[] = [];
const context: ParseContext = {code, startLine: 0, currentLine: 0, path};
const tokens = md.parse(content, context);
Expand All @@ -355,6 +364,56 @@ export function parseMarkdown(input: string, options: ParseOptions): MarkdownPag
};
}

function getContents(input: string): {content: string; data: FrontMatter & {[key: string]: any}} {
try {
const {data, content} = matter(input, {});
if ("title" in data) data.title = normalizeString(data.title);
if ("toc" in data) data.toc = normalizeToc(data.toc);
if ("index" in data) data.index = normalizeBoolean(data.index, "index");
if ("keywords" in data) data.keywords = normalizeStringArray(data.keywords);
if ("draft" in data) data.draft = normalizeBoolean(data.draft, "draft");
if ("sql" in data) data.sql = normalizeSqlData(data.sql);
return {data, content};
} catch (error) {
return {
data: {},
content: '<div class="observablehq--inspect observablehq--error">Invalid front matter</div>'
};
}
}

function normalizeString(value: any): string {
return value == null ? "" : String(value);
}

function normalizeToc(toc: any): FrontMatter["toc"] {
if (toc == null || typeof toc === "boolean") return toc;
if (typeof toc !== "object" || Array.isArray(toc)) console.warn(`Invalid toc format: ${toc}`);
if ("show" in toc) toc.show = normalizeBoolean(toc.show, "toc.show");
if ("label" in toc) toc.label = normalizeString(toc.label);
return toc;
}

function normalizeBoolean(value: any, name: string): boolean {
if (typeof value !== "boolean")
console.warn(`the ${name} option should be boolean, ${value} (${typeof value} found instead)`);
return Boolean(value);
}

function normalizeStringArray(value: any): string[] {
return value == null ? [] : typeof value === "string" ? [value] : Array.from(value, String);
}

function normalizeSqlData(sql: any): {[key: string]: string} {
if (!sql || typeof sql !== "object" || Array.isArray(sql)) return console.warn("Unsupported sql definition", sql), {};
const tables = new Map<string, string>();
for (const [name, source] of Object.entries(sql)) {
if (typeof source !== "string") return console.warn("Unsupported sql table source definition", source), {};
tables.set(name, source);
}
return Object.fromEntries(tables);
}

function getHtml(
key: "head" | "header" | "footer",
data: Record<string, any>,
Expand Down

0 comments on commit abbe0a4

Please sign in to comment.