import { program } from "commander"; import * as fs from 'fs'; import path from "path"; import markdownit from "markdown-it"; import * as d3 from 'd3'; import hljs from 'highlight.js'; // folx warned me about jsdom import slugify from "slugify"; import { DOMParser, parseHTML } from 'linkedom'; program.version("2024-11-30").arguments("").parse(process.argv) const print = console.log; const idrefs = ["id", "aria-labelledby", "aria-describedby", "aria-controls", "aria-owns", "form"]; const idrefsSelector = idrefs.map(x => `[${x}]`).join(","); const default_css = fs.readFileSync(path.resolve(process.cwd(), "./refnb/refnb.css")); const tpl_doc = fs.readFileSync(path.resolve(process.cwd(), "./refnb/refnb.html")); const tpl_dom = newDocument(); const tpl_cell_row = tpl_dom.select("#tpl\\:cell-row").node().content.querySelector("tr"); const tpl_output_table = tpl_dom.select("#tpl\\:output").node().content.querySelector("table"); const tpl_output_row = tpl_dom.select("#tpl\\:output-row").node().content.querySelector("tr"); const cell_types = ["code", "markdown", "raw"] const output_types = ["display_data", "execute_result"] const literate = /^\s*%{2}\s+/m; function newDocument() { const { // note, these are *not* globals window, document, customElements, HTMLElement, Event, CustomEvent // other exports .. } = parseHTML(String(tpl_doc)); return d3.select(document); } program.args.forEach( (file, i, args) => { var nb = JSON.parse(fs.readFileSync(file)); var document = newDocument(); let cells = document.select("section").select("table.cells > tbody").selectAll( "tr.cell" ).data(nb.cells).join( (enter) => { enter.append( (cell, i) => { let cell_row = tpl_cell_row.cloneNode(true); if (cell.cell_type == "markdown") { cell.outputs = [{ output_type: "display_data", data: { "text/markdown": cell.source } }] } if (cell.outputs?.length > 0) { let outputs = d3.select(cell_row).select("td.outputs").append( _ => tpl_output_table.cloneNode(true) ).selectAll("tbody").data( cell.outputs ).join("tbody").selectAll("tr").data(d => d.data ? Object.entries(d.data).map( x => x.concat([d.metadata]) ) : [d]).join( (enter) => { enter.append((output, i) => { let output_id = ""; let output_row = tpl_output_row.cloneNode(true); enterOutput(output_id, d3.select(output_row), output, cell); return output_row }) } ) } let id = `c${i}`; // enter building the cell AFTER we've built the output form. enterCell(id, d3.select(cell_row), cell, i, nb) return cell_row } ); } ) // inject css for this specific template document.select("style[src='refnb.css']").attr("src", null).text( default_css ) document.node().querySelectorAll("h1,h2,h3,h4,h5,h6,[role=heading]").forEach( node => headingToId(d3.select(node), document.node()) ) for (let line of document.select("html").node().outerHTML.split("\n")) { process.stdout.write(`${line}\n`) } } ) function enterCell(cell_id, cell_row, cell, i, nb) { cell_types.forEach((t) => { cell_row.classed(t, cell.cell_type == t) }); cell_row.select("td.cell_type>label").text(cell.cell_type) cell_row.select(`option[value="${cell.cell_type}"]`).attr("selected", "") enterCellCode(cell_id, cell_row, cell, i, nb) enterCellIds(cell_id, cell_row) } function enterCellIds(cell_id, cell_row) { cell_row.selectAll(idrefsSelector).each((data, i, nodes) => { let node = d3.select(nodes[i]); for (let ref of idrefs) { let refs = node.attr(ref); if (!refs) { continue } refs = refs.split(" "); let idref = "" for (let [j, label] of refs.entries()) { idref += idref ? " " : ""; if (label.startsWith(":")) { idref += cell_id } else if (label == "#") { label = cell_id; } idref += label; } node.attr(ref, idref) } }) } function enterCellMarkdown(cell_row, cell, i, nb) { } function enterCellCode(cell_id, cell_row, cell, i, nb) { cell_row.select("th.pos>a").text(i + 1).attr("href", `#${cell_id}`); cell_row.select("th.id>a").text(cell.id).attr("href", `#${cell.id}`); cell.execution_count && cell_row.select("td.execution_count>label>output").text(cell.execution_count); let source = cell.source.join("") cell_row.select("td.source>textarea").text(source) cell_row.classed("markdown", cell.cell_type == "markdown" || Boolean(source.match(literate))) let highlighted = hljs.highlight(source, { language: cell.cell_type == "code" ? "python" : "markdown" }); cell_row.select("td.source>section.highlight").html( `
${highlighted.value}
` ) } function enterOutput(output_id, output_row, output, cell) { if (output.output_type) { } else { let [type, bundle, metadata] = output; output_row.select("td.output_type>label").text(type); if (type == "text/html") { output_row.select("td.data").html(bundle.join("")) } else if (type == "text/markdown") { let output = output_row.select("td.data").html( markdownit({ html: true, linkify: true, highlight: function (str, lang) { if (lang && hljs.getLanguage(lang)) { try { return hljs.highlight(str, { language: lang }).value; } catch (__) { } } return ''; // use external default escaping } }).render(bundle.join("")) ) output.selectAll("img").each( (_, j, nodes) => { let img = d3.select(nodes[j]); if (img.attr("src").startsWith("attachment:")) { let [_, attachment] = img.attr("src").split(":", 1); let bundle = cell.metadata[attachment] if (bundle) { for ([type, bundle] of Object.entries(bundle)) { bundle = [bundle] break } } } } ) } else if (type == "text/plain") { output_row.select("td.data").append("samp").text(bundle.join("")) } else if (type.startsWith("image")) { // we need the fucking cell metadata to attachments in for markdown cells // svgs need different treatment let img = output_row.select("td.data").append("img"); // alt text? img.attr("src", `data:${type};base64,${bundle.join("")}`); } } } function headingToId(heading, document) { let id = heading.attr("id") || slugify(heading.text()); heading.attr("id", id) let a = document.createElement("a") d3.select(a).attr("href", `#${id}`).attr("aria-labelledby", id).text("🔗").classed("perma", true) let h = heading.node(); // it makes sense for the link to be before the header. // mvoing forward int eh document confirms the location. h.insertBefore(a, h) }