works
This commit is contained in:
parent
7d14703242
commit
4d8e83a74a
7 changed files with 117 additions and 54 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
14
package.json
14
package.json
|
@ -1,22 +1,32 @@
|
||||||
{
|
{
|
||||||
"name": "remark-agda",
|
"name": "remark-agda",
|
||||||
|
"version": "0.0.1",
|
||||||
|
|
||||||
|
"main": "dist/index.js",
|
||||||
"module": "src/index.ts",
|
"module": "src/index.ts",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^1.9.0",
|
"@biomejs/biome": "^1.9.0",
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
|
"rehype-raw": "^7.0.0",
|
||||||
"rehype-stringify": "^10.0.0",
|
"rehype-stringify": "^10.0.0",
|
||||||
"remark-parse": "^11.0.0",
|
"remark-parse": "^11.0.0",
|
||||||
"remark-rehype": "^11.1.0",
|
"remark-rehype": "^11.1.0",
|
||||||
"vfile": "^6.0.3"
|
"to-vfile": "^8.0.0",
|
||||||
|
"vfile": "^6.0.3",
|
||||||
|
"vfile-reporter": "^8.1.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5.0.0"
|
"typescript": "^5.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"hast": "^1.0.0",
|
||||||
"hast-util-from-html": "^2.0.2",
|
"hast-util-from-html": "^2.0.2",
|
||||||
"unified": "^11.0.5",
|
"unified": "^11.0.5",
|
||||||
|
"unist": "^0.0.1",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit": "^5.0.0"
|
||||||
},
|
},
|
||||||
"trustedDependencies": ["@biomejs/biome"]
|
"trustedDependencies": ["@biomejs/biome"],
|
||||||
|
"files": ["dist/index.js", "dist/index.d.ts"]
|
||||||
}
|
}
|
||||||
|
|
5
scripts/prepare-release.sh
Executable file
5
scripts/prepare-release.sh
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
mkdir -p dist/
|
||||||
|
bun build --target node src/index.ts > dist/index.js
|
||||||
|
bunx tsc
|
69
src/index.ts
69
src/index.ts
|
@ -1,6 +1,6 @@
|
||||||
import { join, parse } from "node:path";
|
import { join, parse } from "node:path";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { spawnSync, spawn } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
import {
|
import {
|
||||||
readdir,
|
readdir,
|
||||||
mkdtemp,
|
mkdtemp,
|
||||||
|
@ -10,12 +10,13 @@ import {
|
||||||
writeFile,
|
writeFile,
|
||||||
} from "node:fs/promises";
|
} from "node:fs/promises";
|
||||||
import { mkdirSync } from "node:fs";
|
import { mkdirSync } from "node:fs";
|
||||||
|
|
||||||
import type { Plugin } from "unified";
|
import type { Plugin } from "unified";
|
||||||
import { visit } from "unist-util-visit";
|
import { visit } from "unist-util-visit";
|
||||||
import { fromMarkdown } from "mdast-util-from-markdown";
|
import { fromMarkdown } from "mdast-util-from-markdown";
|
||||||
import { fromHtml } from "hast-util-from-html";
|
import { fromHtml } from "hast-util-from-html";
|
||||||
import { toHtml } from "hast-util-to-html";
|
import { toHtml } from "hast-util-to-html";
|
||||||
|
import type { RootContent } from "hast";
|
||||||
|
import type { Node as UnistNode } from "unist";
|
||||||
|
|
||||||
export interface RemarkAgdaOptions {
|
export interface RemarkAgdaOptions {
|
||||||
/** Place to output the HTML files */
|
/** Place to output the HTML files */
|
||||||
|
@ -31,7 +32,7 @@ export interface RemarkAgdaOptions {
|
||||||
extraAgdaFlags?: string[];
|
extraAgdaFlags?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const remarkAgda: Plugin<[RemarkAgdaOptions]> = ({
|
export const remarkAgda: Plugin<[RemarkAgdaOptions]> = ({
|
||||||
agdaBin,
|
agdaBin,
|
||||||
extraAgdaFlags,
|
extraAgdaFlags,
|
||||||
destDir,
|
destDir,
|
||||||
|
@ -48,19 +49,14 @@ const remarkAgda: Plugin<[RemarkAgdaOptions]> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = history[history.length - 1];
|
const path = history[history.length - 1];
|
||||||
// console.log("path", path);
|
|
||||||
// if (!(path.endsWith(".lagda.md") || path.endsWith(".agda"))) return;
|
// if (!(path.endsWith(".lagda.md") || path.endsWith(".agda"))) return;
|
||||||
|
|
||||||
// console.log("AGDA:processing path", path);
|
// console.log("AGDA:processing path", path);
|
||||||
|
|
||||||
const agdaOutDir = await mkdtemp(join(tmpdir(), "agdaRender."));
|
const agdaOutDir = await mkdtemp(join(tmpdir(), "agdaRender."));
|
||||||
// const agdaOutDir = join(tempDir, "output");
|
// const agdaOutDir = join(tempDir, "output");
|
||||||
const agdaOutFilename = parse(path).base.replace(/\.lagda.md$/, ".md");
|
const agdaOutFilename = parse(path).base.replace(/\.lagda.md$/, ".md");
|
||||||
const agdaOutFile = join(agdaOutDir, agdaOutFilename);
|
const agdaOutFile = join(agdaOutDir, agdaOutFilename);
|
||||||
console.log("looking for file", agdaOutFile);
|
|
||||||
// mkdirSync(agdaOutDir, { recursive: true });
|
// mkdirSync(agdaOutDir, { recursive: true });
|
||||||
|
const childOutput = spawnSync(
|
||||||
const childOutput = await spawnSync(
|
|
||||||
agdaBin ?? "agda",
|
agdaBin ?? "agda",
|
||||||
[
|
[
|
||||||
"--html",
|
"--html",
|
||||||
|
@ -94,7 +90,6 @@ const remarkAgda: Plugin<[RemarkAgdaOptions]> = ({
|
||||||
// console.error(childOutput.stdout?.toString());
|
// console.error(childOutput.stdout?.toString());
|
||||||
// console.error(childOutput.stderr?.toString());
|
// console.error(childOutput.stderr?.toString());
|
||||||
// console.error("--AGDA OUTPUT--");
|
// console.error("--AGDA OUTPUT--");
|
||||||
|
|
||||||
const referencedFiles = new Set();
|
const referencedFiles = new Set();
|
||||||
|
|
||||||
const writtenFiles = await readdir(agdaOutDir);
|
const writtenFiles = await readdir(agdaOutDir);
|
||||||
|
@ -118,48 +113,70 @@ const remarkAgda: Plugin<[RemarkAgdaOptions]> = ({
|
||||||
|
|
||||||
const htmlname = parse(path).base.replace(/\.lagda.md/, ".html");
|
const htmlname = parse(path).base.replace(/\.lagda.md/, ".html");
|
||||||
|
|
||||||
const doc = await readFile(agdaOutFile);
|
const doc = await readFile(agdaOutFile, { encoding: "utf-8" });
|
||||||
|
|
||||||
// This is the post-processed markdown with HTML code blocks replacing the Agda code blocks
|
// This is the post-processed markdown with HTML code blocks replacing the Agda code blocks
|
||||||
const tree2 = fromMarkdown(doc);
|
const tree2 = fromMarkdown(doc);
|
||||||
|
|
||||||
const collectedCodeBlocks: RootContent[] = [];
|
const collectedCodeBlocks: string[] = [];
|
||||||
|
|
||||||
visit(tree2, "html", (node) => {
|
visit(tree2, "html", (node) => {
|
||||||
const html = fromHtml(node.value, { fragment: true });
|
const html = fromHtml(node.value, { fragment: true });
|
||||||
|
|
||||||
const firstChild: RootContent = html.children[0]!;
|
|
||||||
|
|
||||||
visit(html, "element", (node) => {
|
visit(html, "element", (node) => {
|
||||||
if (node.tagName !== "a") return;
|
if (node.tagName !== "a") return;
|
||||||
|
|
||||||
if (node.properties.href) {
|
if (typeof node.properties.href === "string") {
|
||||||
// Trim off end
|
// Trim off end
|
||||||
const [href, hash, ...rest] = node.properties.href.split("#");
|
const [href, hash, ...rest] = node.properties.href.split("#");
|
||||||
if (rest.length > 0) throw new Error("come look at this");
|
if (rest.length > 0) throw new Error("come look at this");
|
||||||
|
|
||||||
if (href === htmlname) node.properties.href = `#${hash}`;
|
if (href === htmlname) node.properties.href = `#${hash}`;
|
||||||
|
|
||||||
if (referencedFiles.has(href)) {
|
// TODO: Transform
|
||||||
node.properties.href = `${base}generated/agda/${href}${hash ? `#${hash}` : ""}`;
|
// if (referencedFiles.has(href)) {
|
||||||
node.properties.target = "_blank";
|
// node.properties.href = `${base}generated/agda/${href}${hash ? `#${hash}` : ""}`;
|
||||||
}
|
// node.properties.target = "_blank";
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!firstChild?.properties?.className?.includes("Agda")) return;
|
while (true) {
|
||||||
|
if (html.children.length > 0) {
|
||||||
|
const firstChild: RootContent = html.children[0];
|
||||||
|
|
||||||
const stringContents = toHtml(firstChild);
|
if (firstChild.type !== "element") break;
|
||||||
collectedCodeBlocks.push({
|
|
||||||
contents: stringContents,
|
const className = firstChild.properties.className;
|
||||||
});
|
|
||||||
|
// @ts-ignore TODO: Fix this
|
||||||
|
if (!className?.includes("Agda")) break;
|
||||||
|
|
||||||
|
const stringContents = toHtml(firstChild);
|
||||||
|
collectedCodeBlocks.push(stringContents);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(`Collected ${collectedCodeBlocks.length} blocks!`);
|
||||||
|
|
||||||
let idx = 0;
|
let idx = 0;
|
||||||
visit(tree, "code", (node) => {
|
|
||||||
|
visit(tree, "code", (node: UnistNode) => {
|
||||||
|
// Make sure it's either null (which gets interpreted as agda), or agda
|
||||||
|
// @ts-ignore
|
||||||
if (!(node.lang === null || node.lang === "agda")) return;
|
if (!(node.lang === null || node.lang === "agda")) return;
|
||||||
|
|
||||||
|
// node.type = "html";
|
||||||
|
|
||||||
node.type = "html";
|
node.type = "html";
|
||||||
node.value = collectedCodeBlocks[idx].contents;
|
|
||||||
|
// @ts-ignore
|
||||||
|
node.value = collectedCodeBlocks[idx];
|
||||||
|
|
||||||
|
console.log(node);
|
||||||
|
|
||||||
idx += 1;
|
idx += 1;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
# Commutativity of addition
|
||||||
|
|
||||||
|
This document shows how to prove commutativity of addition on natural numbers.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Imports</summary>
|
||||||
|
|
||||||
```
|
```
|
||||||
module Simple where
|
module Simple where
|
||||||
|
|
||||||
|
@ -5,23 +12,39 @@ open import Agda.Primitive
|
||||||
open import Relation.Binary.PropositionalEquality.Core
|
open import Relation.Binary.PropositionalEquality.Core
|
||||||
|
|
||||||
variable
|
variable
|
||||||
l : Level
|
l : Level
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
Declare our data structures:
|
||||||
|
|
||||||
|
```
|
||||||
data ℕ : Set where
|
data ℕ : Set where
|
||||||
zero : ℕ
|
zero : ℕ
|
||||||
suc : ℕ → ℕ
|
suc : ℕ → ℕ
|
||||||
|
```
|
||||||
|
|
||||||
|
Define addition:
|
||||||
|
|
||||||
|
```
|
||||||
_+_ : ℕ → ℕ → ℕ
|
_+_ : ℕ → ℕ → ℕ
|
||||||
zero + b = b
|
zero + b = b
|
||||||
suc a + b = suc (a + b)
|
suc a + b = suc (a + b)
|
||||||
|
```
|
||||||
|
|
||||||
|
Prove commutativity:
|
||||||
|
|
||||||
|
```
|
||||||
+-comm : (m n : ℕ) → m + n ≡ n + m
|
+-comm : (m n : ℕ) → m + n ≡ n + m
|
||||||
+-comm zero n = lemma n where
|
+-comm zero n = lemma n where
|
||||||
lemma : (n : ℕ) → n ≡ n + zero
|
lemma : (n : ℕ) → n ≡ n + zero
|
||||||
lemma zero = refl
|
lemma zero = refl
|
||||||
lemma (suc n) = cong suc (lemma n)
|
lemma (suc n) = cong suc (lemma n)
|
||||||
+-comm (suc m) n = trans (cong suc (+-comm m n)) (sym (lemma n m)) where
|
+-comm (suc m) n = trans (cong suc (+-comm m n)) (sym (lemma n m)) where
|
||||||
lemma : (m n : ℕ) → m + suc n ≡ suc (m + n)
|
lemma : (m n : ℕ) → m + suc n ≡ suc (m + n)
|
||||||
lemma zero n = refl
|
lemma zero n = refl
|
||||||
lemma (suc m) n = cong suc (lemma m n)
|
lemma (suc m) n = cong suc (lemma m n)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
That's it!
|
|
@ -1,22 +1,22 @@
|
||||||
import { test } from "bun:test";
|
import { test } from "bun:test";
|
||||||
import { resolve, dirname, join } from "node:path";
|
import { resolve, dirname, join } from "node:path";
|
||||||
import { unified } from "unified";
|
import { unified } from "unified";
|
||||||
import remarkAgda from "../src";
|
|
||||||
import remarkParse from "remark-parse";
|
import remarkParse from "remark-parse";
|
||||||
import remarkRehype from "remark-rehype";
|
import remarkRehype from "remark-rehype";
|
||||||
import rehypeStringify from "rehype-stringify";
|
import rehypeStringify from "rehype-stringify";
|
||||||
import { VFile } from "vfile";
|
import rehypeRaw from "rehype-raw";
|
||||||
|
import { read } from "to-vfile";
|
||||||
|
|
||||||
|
import remarkAgda, { type RemarkAgdaOptions } from "../src";
|
||||||
|
|
||||||
test("simple case", async () => {
|
test("simple case", async () => {
|
||||||
const file = join(dirname(import.meta.path), "Simple.lagda.md");
|
const file = join(dirname(import.meta.path), "Simple.lagda.md");
|
||||||
const vfile = new VFile({ path: file });
|
const vfile = await read(file);
|
||||||
|
|
||||||
const result = await unified()
|
const options: RemarkAgdaOptions = {
|
||||||
.use(remarkParse)
|
destDir: join(dirname(import.meta.path), "results"),
|
||||||
.use(remarkAgda, {
|
transformHtml: (src) => {
|
||||||
destDir: join(dirname(import.meta.path), "results"),
|
return `
|
||||||
transformHtml: (src) => {
|
|
||||||
return `
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
@ -29,10 +29,14 @@ test("simple case", async () => {
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
.use(remarkRehype)
|
|
||||||
|
await unified()
|
||||||
|
.use(remarkParse)
|
||||||
|
.use(remarkAgda, options)
|
||||||
|
.use(remarkRehype, { allowDangerousHtml: true })
|
||||||
|
.use(rehypeRaw)
|
||||||
.use(rehypeStringify)
|
.use(rehypeStringify)
|
||||||
.process(vfile);
|
.process(vfile);
|
||||||
console.log("result", result);
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,16 +12,20 @@
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"noEmit": true,
|
|
||||||
|
|
||||||
// Best practices
|
// Best practices
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
|
||||||
// Some stricter flags (disabled by default)
|
// Some stricter flags (disabled by default)
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"noPropertyAccessFromIndexSignature": false
|
"noPropertyAccessFromIndexSignature": false,
|
||||||
}
|
|
||||||
|
"outDir": "dist",
|
||||||
|
"declaration": true
|
||||||
|
},
|
||||||
|
"files": ["src/index.ts"]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue