UNPKG

entangle-doc

Version:
128 lines (114 loc) 3.72 kB
import yaml from "js-yaml"; import CoffeeScript from "coffeescript"; import { remark } from "remark"; import remarkRehype from "remark-rehype"; import rehypeStringify from 'rehype-stringify' import { VFile } from "vfile"; import { dirname } from 'path'; import { fileURLToPath } from 'url'; import Handlebars from "handlebars"; import fs from 'fs' import path from 'path' import { trimChars, merge, hash } from "./utils.js" const __dirname = dirname(fileURLToPath(import.meta.url)); export function split(entangleText) { const [a, b, c] = entangleText.split("\n---\n"); const content = a; const config = yaml.load(b); const code = CoffeeScript.compile(c, { bare: true }); return { content, config, code }; } function parseVariables(markdown) { const variables = {} for (var match of markdown.matchAll(/\`[^`]*\$\{([^\}]+)\}[^`]*\`/g)) { const literal = match[0]; const name = match[1]; variables[name] = { literal, location: [match.index, match.index + literal.length], }; } return variables } function parseConfig(config) { // append config data to the variables already found in the content section const data = {} for (const variable of Object.keys(config)) { data[variable] = { ...data[variable], ...config[variable], }; } return data; } const TO_PREPEND_WITH_DATA = ["min", "max", "step", "format"]; const TO_EXCLUDE = ["literal", "location","initial"]; function renderContent(markdown) { // create html from markdown return (variables, id) => { // create spans const spans = {}; Object.keys(variables).forEach((varName) => { const attrs = variables[varName]; const literal = trimChars("`")(attrs.literal); const innerHTML = literal.replace(/\$\{[^\}]+\}/, ""); const attributes = {}; Object.keys(attrs).forEach((attr) => { var key = attr; if (TO_EXCLUDE.includes(attr)) { return; } if (TO_PREPEND_WITH_DATA.includes(attr)) { key = "data-" + attr; } attributes[key] = attrs[attr]; }); const attributesString = Object.keys(attributes) .map((key) => `${key}="${attributes[key]}"`) .join(" "); spans[varName] = `<span data-var="${varName}" ${attributesString}>${innerHTML}</span>`; markdown = markdown.replace( new RegExp(`\`[^\`]*\\$\\{${varName}\\}[^\`]*\``), // spans[varName] `\{\{\{${varName}\}\}\}` ); }); var markup = String(remark().use(remarkRehype).use(rehypeStringify).processSync(markdown)); const template = Handlebars.compile(markup) var markup = template(spans) return `<div id="${id}" class="Tangle">`+markup+'</div>' }; } function renderCode(code) { // return the initial object (based on the data) and the updater function // returned as javascript code strings return (data, id) => { const initialObj = {}; Object.keys(data) .filter((k) => "initial" in data[k]) .forEach((k) => { const d = data[k]; initialObj[k] = d.initial; }); const initial = "const initial=" + JSON.stringify(initialObj); const context = { initial, updater: code.replace(/update/g,'updater'), id }; // render js const jsSource = fs.readFileSync( path.join(__dirname, "../templates/example.template.js"), "utf8" ); const jsTemplate = Handlebars.compile(jsSource); const script = jsTemplate(context); return script }; } export function parse(entangle) { const { content, config, code } = split(entangle); const variables = parseVariables(content); const variableConfig = parseConfig(config); const variableData = merge(variables, variableConfig); const id = hash(entangle,4); const html = renderContent(content)(variableData,id); const javascript = renderCode(code)(variableData,id); return { html, javascript, id }; }