@abreen/tada 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -33
- package/bin/tada.ts +356 -0
- package/bin/validators.test.ts +204 -0
- package/bin/validators.ts +83 -0
- package/{webpack/apply-base-path-plugin.js → build/apply-base-path-plugin.ts} +16 -7
- package/build/bundle.ts +117 -0
- package/{webpack/code.test.js → build/code.test.ts} +6 -7
- package/build/colors.ts +25 -0
- package/build/content-watch.ts +107 -0
- package/build/copy.ts +118 -0
- package/{webpack/deflist-id-plugin.js → build/deflist-id-plugin.ts} +7 -6
- package/{webpack/external-links-plugin.js → build/external-links-plugin.ts} +14 -5
- package/build/features.ts +11 -0
- package/build/generate-content-assets.ts +315 -0
- package/build/generate-favicon.ts +165 -0
- package/build/generate-fonts.ts +31 -0
- package/{webpack/generate-manifest-plugin.js → build/generate-manifest.ts} +29 -36
- package/build/globals.test.ts +101 -0
- package/{webpack/globals.js → build/globals.ts} +28 -13
- package/{webpack/heading-subtitle-plugin.js → build/heading-subtitle-plugin.ts} +4 -2
- package/build/json-schema.test.ts +57 -0
- package/build/json-schema.ts +33 -0
- package/build/log.test.ts +111 -0
- package/build/log.ts +167 -0
- package/{webpack/markdown-plugins.test.js → build/markdown-plugins.test.ts} +94 -9
- package/{webpack/pagefind-plugin.test.js → build/pagefind.test.ts} +74 -13
- package/build/pagefind.ts +339 -0
- package/{webpack/pdf-text.js → build/pdf-text.ts} +47 -27
- package/build/pipeline.ts +93 -0
- package/{webpack/reachability.test.js → build/reachability.test.ts} +3 -3
- package/{webpack/reachability.js → build/reachability.ts} +77 -34
- package/build/serve.ts +112 -0
- package/{webpack/site-variables.js → build/site-variables.ts} +22 -15
- package/{webpack → build}/site.schema.json +3 -10
- package/{webpack/templates.js → build/templates.ts} +35 -33
- package/{webpack/text-to-id.js → build/text-to-id.ts} +2 -2
- package/build/toc-plugin.test.ts +105 -0
- package/{webpack/toc-plugin.js → build/toc-plugin.ts} +32 -13
- package/build/types.ts +172 -0
- package/build/util.ts +26 -0
- package/{webpack/utils/code.js → build/utils/code.ts} +119 -60
- package/{webpack/utils/content-files.js → build/utils/content-files.ts} +40 -35
- package/build/utils/derive-theme.test.ts +111 -0
- package/build/utils/derive-theme.ts +85 -0
- package/build/utils/file-types.test.ts +61 -0
- package/build/utils/file-types.ts +13 -0
- package/build/utils/front-matter.test.ts +80 -0
- package/{webpack/utils/front-matter.js → build/utils/front-matter.ts} +22 -9
- package/{webpack → build}/utils/jdi-runner/LiterateRunner.java +1 -1
- package/{webpack/utils/literate-java.js → build/utils/literate-java.ts} +63 -34
- package/{webpack/utils/markdown.js → build/utils/markdown.ts} +94 -49
- package/build/utils/paths.test.ts +91 -0
- package/{webpack/utils/paths.js → build/utils/paths.ts} +14 -22
- package/{webpack/utils/render.js → build/utils/render.ts} +188 -123
- package/build/utils/shiki-highlighter.ts +29 -0
- package/build/validate-internal-links-plugin.test.ts +106 -0
- package/{webpack/validate-internal-links-plugin.js → build/validate-internal-links-plugin.ts} +47 -20
- package/{webpack/watch-reachability-state.test.js → build/watch-reachability-state.test.ts} +8 -8
- package/{webpack/watch-reachability-state.js → build/watch-reachability-state.ts} +63 -24
- package/{webpack/watch-reload-client.js → build/watch-reload-client.ts} +3 -1
- package/build/watch.ts +573 -0
- package/content/index.md +9 -3
- package/content/markdown.md +2 -1
- package/content/problem_sets/index.html +14 -0
- package/fonts/google-sans-code/woff2/GoogleSansCodeVariable-Italic.woff2 +0 -0
- package/fonts/google-sans-code/woff2/GoogleSansCodeVariable.woff2 +0 -0
- package/fonts/inter/woff2/InterVariable-Italic.woff2 +0 -0
- package/fonts/inter/woff2/InterVariable.woff2 +0 -0
- package/package.json +28 -19
- package/src/_alerts.scss +92 -0
- package/src/_base.scss +106 -0
- package/src/{layout.scss → _layout.scss} +0 -2
- package/src/anchor/style.scss +1 -9
- package/src/code/index.ts +3 -3
- package/src/code.scss +1 -1
- package/src/critical.scss +5 -0
- package/src/header/_base.scss +129 -0
- package/src/header/style.scss +3 -131
- package/src/index.ts +1 -2
- package/src/question/style.scss +1 -1
- package/src/search/index.ts +36 -15
- package/src/search/style.scss +9 -15
- package/src/style.scss +6 -269
- package/src/toc/style.scss +5 -39
- package/src/util.ts +8 -5
- package/templates/_theme.scss +38 -14
- package/tsconfig.json +10 -6
- package/types/file-system-access.d.ts +5 -0
- package/types/markdown-it-plugins.d.ts +11 -0
- package/types/untyped-modules.d.ts +40 -0
- package/bin/tada.js +0 -361
- package/content/problem_sets/index.md +0 -6
- package/webpack/build-state.js +0 -97
- package/webpack/colors.js +0 -15
- package/webpack/config.base.js +0 -151
- package/webpack/config.dev.js +0 -23
- package/webpack/config.prod.js +0 -32
- package/webpack/content-watch-plugin.js +0 -153
- package/webpack/features.js +0 -5
- package/webpack/generate-content-assets-plugin.js +0 -308
- package/webpack/generate-favicon-plugin.js +0 -198
- package/webpack/generate-fonts-plugin.js +0 -69
- package/webpack/json-schema.js +0 -19
- package/webpack/log.js +0 -143
- package/webpack/pagefind-plugin.js +0 -379
- package/webpack/print-flair-plugin.js +0 -22
- package/webpack/serve.js +0 -104
- package/webpack/util.js +0 -49
- package/webpack/utils/define-plugin.js +0 -20
- package/webpack/utils/file-types.js +0 -26
- package/webpack/utils/parse-hsl.js +0 -8
- package/webpack/utils/shiki-highlighter.js +0 -26
- package/webpack/watch.js +0 -166
- /package/{webpack → build}/flair.json +0 -0
- /package/{webpack → build}/utils/jdi-runner/LiterateRunner.class +0 -0
- /package/fonts/google-sans-code/{GoogleSansCodeVariable-Italic.ttf → ttf/GoogleSansCodeVariable-Italic.ttf} +0 -0
- /package/fonts/google-sans-code/{GoogleSansCodeVariable.ttf → ttf/GoogleSansCodeVariable.ttf} +0 -0
- /package/fonts/inter/{InterVariable-Italic.ttf → ttf/InterVariable-Italic.ttf} +0 -0
- /package/fonts/inter/{InterVariable.ttf → ttf/InterVariable.ttf} +0 -0
- /package/types/{dev.ts → dev.d.ts} +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import {
|
|
3
|
+
getProcessedExtensions,
|
|
4
|
+
extensionIsMarkdown,
|
|
5
|
+
isLiterateJava,
|
|
6
|
+
} from './file-types.js';
|
|
7
|
+
|
|
8
|
+
describe('getProcessedExtensions', () => {
|
|
9
|
+
test('includes md, markdown, html, and custom extensions', () => {
|
|
10
|
+
expect(getProcessedExtensions(['java'])).toEqual([
|
|
11
|
+
'md',
|
|
12
|
+
'markdown',
|
|
13
|
+
'html',
|
|
14
|
+
'java',
|
|
15
|
+
]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('works with empty code extensions', () => {
|
|
19
|
+
expect(getProcessedExtensions([])).toEqual(['md', 'markdown', 'html']);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('extensionIsMarkdown', () => {
|
|
24
|
+
test('returns true for .md', () => {
|
|
25
|
+
expect(extensionIsMarkdown('.md')).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('returns true for .markdown', () => {
|
|
29
|
+
expect(extensionIsMarkdown('.markdown')).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('returns false for .html', () => {
|
|
33
|
+
expect(extensionIsMarkdown('.html')).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('returns false for .txt', () => {
|
|
37
|
+
expect(extensionIsMarkdown('.txt')).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('isLiterateJava', () => {
|
|
42
|
+
test('returns true for .java.md files', () => {
|
|
43
|
+
expect(isLiterateJava('Example.java.md')).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('case insensitive', () => {
|
|
47
|
+
expect(isLiterateJava('Example.JAVA.MD')).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('returns false for plain .md files', () => {
|
|
51
|
+
expect(isLiterateJava('readme.md')).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('returns false for .java files', () => {
|
|
55
|
+
expect(isLiterateJava('Example.java')).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('handles paths with directories', () => {
|
|
59
|
+
expect(isLiterateJava('/content/code/Example.java.md')).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
export function getProcessedExtensions(codeExtensions: string[]): string[] {
|
|
4
|
+
return ['md', 'markdown', 'html', ...codeExtensions];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function extensionIsMarkdown(ext: string): boolean {
|
|
8
|
+
return ['.md', '.markdown'].includes(ext);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isLiterateJava(filePath: string): boolean {
|
|
12
|
+
return path.basename(filePath).toLowerCase().endsWith('.java.md');
|
|
13
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import {
|
|
3
|
+
parseFrontMatterAndContent,
|
|
4
|
+
parseFrontMatter,
|
|
5
|
+
} from './front-matter.js';
|
|
6
|
+
|
|
7
|
+
describe('parseFrontMatter', () => {
|
|
8
|
+
test('parses markdown front matter separated by blank line', () => {
|
|
9
|
+
const raw = 'title: Hello\nauthor: Alice\n\nBody content here.';
|
|
10
|
+
const result = parseFrontMatter(raw, '.md');
|
|
11
|
+
expect(result.frontMatter).toBe('title: Hello\nauthor: Alice');
|
|
12
|
+
expect(result.content).toBe('\nBody content here.');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('handles .markdown extension', () => {
|
|
16
|
+
const raw = 'title: Test\n\nContent.';
|
|
17
|
+
const result = parseFrontMatter(raw, '.markdown');
|
|
18
|
+
expect(result.frontMatter).toBe('title: Test');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('handles .html extension', () => {
|
|
22
|
+
const raw = 'title: Page\nlayout: full\n\n<h1>Hi</h1>';
|
|
23
|
+
const result = parseFrontMatter(raw, '.html');
|
|
24
|
+
expect(result.frontMatter).toBe('title: Page\nlayout: full');
|
|
25
|
+
expect(result.content).toBe('\n<h1>Hi</h1>');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('returns null frontMatter for unknown extensions', () => {
|
|
29
|
+
const raw = 'title: Nope\n\nContent';
|
|
30
|
+
const result = parseFrontMatter(raw, '.txt');
|
|
31
|
+
expect(result.frontMatter).toBe(null);
|
|
32
|
+
expect(result.content).toBe(raw);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('returns null frontMatter when content starts with blank line', () => {
|
|
36
|
+
const raw = '\nNo front matter here.';
|
|
37
|
+
const result = parseFrontMatter(raw, '.md');
|
|
38
|
+
expect(result.frontMatter).toBe(null);
|
|
39
|
+
expect(result.content).toBe(raw);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('handles YAML multiline pipe syntax', () => {
|
|
43
|
+
const raw =
|
|
44
|
+
'title: Hello\ndescription: |\n This is a\n multiline value\n\nBody.';
|
|
45
|
+
const result = parseFrontMatter(raw, '.md');
|
|
46
|
+
expect(result.frontMatter).toContain('description: |');
|
|
47
|
+
expect(result.frontMatter).toContain(' This is a');
|
|
48
|
+
expect(result.frontMatter).toContain(' multiline value');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('parseFrontMatterAndContent', () => {
|
|
53
|
+
test('returns parsed pageVariables and content', () => {
|
|
54
|
+
const raw = 'title: Hello World\nauthor: Bob\n\n# Main Content';
|
|
55
|
+
const result = parseFrontMatterAndContent(raw, '.md');
|
|
56
|
+
expect(result.pageVariables.title).toBe('Hello World');
|
|
57
|
+
expect(result.pageVariables.author).toBe('Bob');
|
|
58
|
+
expect(result.content).toContain('# Main Content');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('handles content with no front matter fields', () => {
|
|
62
|
+
const raw = '\nJust content, no front matter.';
|
|
63
|
+
const result = parseFrontMatterAndContent(raw, '.md');
|
|
64
|
+
expect(result.pageVariables).toEqual({});
|
|
65
|
+
expect(result.content).toBe(raw);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('handles boolean and numeric front matter values', () => {
|
|
69
|
+
const raw = 'toc: true\norder: 5\n\nContent.';
|
|
70
|
+
const result = parseFrontMatterAndContent(raw, '.md');
|
|
71
|
+
expect(result.pageVariables.toc).toBe(true);
|
|
72
|
+
expect(result.pageVariables.order).toBe(5);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('handles Windows-style line endings', () => {
|
|
76
|
+
const raw = 'title: Hello\r\n\r\nBody content.';
|
|
77
|
+
const result = parseFrontMatterAndContent(raw, '.md');
|
|
78
|
+
expect(result.pageVariables.title).toBe('Hello');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -1,18 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import fm from 'front-matter';
|
|
2
|
+
import { extensionIsMarkdown } from './file-types.js';
|
|
3
|
+
import type { ParsedContent } from '../types.js';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
interface RawParsedFrontMatter {
|
|
6
|
+
frontMatter: string | null;
|
|
7
|
+
content: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function parseFrontMatterAndContent(
|
|
11
|
+
raw: string,
|
|
12
|
+
ext: string,
|
|
13
|
+
): ParsedContent {
|
|
5
14
|
const { frontMatter, content } = parseFrontMatter(raw, ext);
|
|
6
15
|
|
|
7
16
|
// Add delimiters to satisfy the front-matter library
|
|
8
17
|
const result = fm(`---\n${frontMatter}\n---\n`);
|
|
9
18
|
|
|
10
|
-
return {
|
|
19
|
+
return {
|
|
20
|
+
pageVariables: result.attributes as Record<string, unknown>,
|
|
21
|
+
content,
|
|
22
|
+
};
|
|
11
23
|
}
|
|
12
24
|
|
|
13
|
-
function parseFrontMatterPlainText(rawContent) {
|
|
25
|
+
function parseFrontMatterPlainText(rawContent: string): RawParsedFrontMatter {
|
|
14
26
|
const lines = rawContent.split(/\r?\n/);
|
|
15
|
-
const fmLines = [];
|
|
27
|
+
const fmLines: string[] = [];
|
|
16
28
|
let i = 0;
|
|
17
29
|
|
|
18
30
|
while (i < lines.length) {
|
|
@@ -45,7 +57,10 @@ function parseFrontMatterPlainText(rawContent) {
|
|
|
45
57
|
};
|
|
46
58
|
}
|
|
47
59
|
|
|
48
|
-
function parseFrontMatter(
|
|
60
|
+
export function parseFrontMatter(
|
|
61
|
+
rawContent: string,
|
|
62
|
+
ext: string,
|
|
63
|
+
): RawParsedFrontMatter {
|
|
49
64
|
if (extensionIsMarkdown(ext) || ext === '.html') {
|
|
50
65
|
return parseFrontMatterPlainText(rawContent);
|
|
51
66
|
} else {
|
|
@@ -53,5 +68,3 @@ function parseFrontMatter(rawContent, ext) {
|
|
|
53
68
|
return { frontMatter: null, content: rawContent };
|
|
54
69
|
}
|
|
55
70
|
}
|
|
56
|
-
|
|
57
|
-
module.exports = { parseFrontMatter, parseFrontMatterAndContent };
|
|
@@ -186,7 +186,7 @@ public class LiterateRunner {
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
} catch (VMDisconnectedException e) {
|
|
189
|
-
// VM died unexpectedly
|
|
189
|
+
// VM died unexpectedly, drain any remaining output
|
|
190
190
|
Thread.sleep(10);
|
|
191
191
|
String output = drainStream(targetStdout);
|
|
192
192
|
if (!output.isEmpty() && activeBlock >= 0) {
|
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import { createMarkdown } from './markdown.js';
|
|
6
|
+
import { makeLogger } from '../log.js';
|
|
7
|
+
import { parseFrontMatterAndContent } from './front-matter.js';
|
|
8
|
+
import type {
|
|
9
|
+
SiteVariables,
|
|
10
|
+
LiterateJavaParseResult,
|
|
11
|
+
LiterateCodeBlock,
|
|
12
|
+
LiterateRunnerEntry,
|
|
13
|
+
} from '../types.js';
|
|
8
14
|
|
|
9
15
|
const log = makeLogger(__filename);
|
|
10
16
|
|
|
11
17
|
const MAIN_PATTERN = /\bvoid\s+main\s*\(/m;
|
|
12
18
|
|
|
13
|
-
function parseLiterateJava(
|
|
19
|
+
export function parseLiterateJava(
|
|
20
|
+
rawContent: string,
|
|
21
|
+
siteVariables: SiteVariables,
|
|
22
|
+
): LiterateJavaParseResult {
|
|
14
23
|
const { pageVariables, content } = parseFrontMatterAndContent(
|
|
15
24
|
rawContent,
|
|
16
25
|
'.md',
|
|
@@ -21,7 +30,7 @@ function parseLiterateJava(rawContent, siteVariables) {
|
|
|
21
30
|
});
|
|
22
31
|
const tokens = md.parse(content, {});
|
|
23
32
|
|
|
24
|
-
const codeBlocks = [];
|
|
33
|
+
const codeBlocks: LiterateCodeBlock[] = [];
|
|
25
34
|
let javaLine = 1;
|
|
26
35
|
|
|
27
36
|
for (let i = 0; i < tokens.length; i++) {
|
|
@@ -45,7 +54,7 @@ function parseLiterateJava(rawContent, siteVariables) {
|
|
|
45
54
|
const javaSource = codeBlocks.map(b => b.content).join('');
|
|
46
55
|
const visibleBlockIndices = codeBlocks
|
|
47
56
|
.map((b, i) => (b.hidden ? null : i))
|
|
48
|
-
.filter(i => i !== null);
|
|
57
|
+
.filter((i): i is number => i !== null);
|
|
49
58
|
|
|
50
59
|
const hiddenCount = codeBlocks.length - visibleBlockIndices.length;
|
|
51
60
|
log.debug`Parsed ${codeBlocks.length} code block(s) (${hiddenCount} hidden), ${javaSource.split('\n').length} Java line(s)`;
|
|
@@ -59,16 +68,19 @@ function parseLiterateJava(rawContent, siteVariables) {
|
|
|
59
68
|
};
|
|
60
69
|
}
|
|
61
70
|
|
|
62
|
-
function hasMainMethod(javaSource) {
|
|
71
|
+
export function hasMainMethod(javaSource: string): boolean {
|
|
63
72
|
return MAIN_PATTERN.test(javaSource);
|
|
64
73
|
}
|
|
65
74
|
|
|
66
|
-
function deriveClassName(filePath) {
|
|
75
|
+
export function deriveClassName(filePath: string): string {
|
|
67
76
|
const name = path.parse(filePath).name;
|
|
68
77
|
return path.parse(name).name;
|
|
69
78
|
}
|
|
70
79
|
|
|
71
|
-
function compileJavaSource(
|
|
80
|
+
export function compileJavaSource(
|
|
81
|
+
javaSource: string,
|
|
82
|
+
className: string,
|
|
83
|
+
): string {
|
|
72
84
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tada-literate-'));
|
|
73
85
|
const javaFile = path.join(tempDir, `${className}.java`);
|
|
74
86
|
fs.writeFileSync(javaFile, javaSource);
|
|
@@ -81,16 +93,19 @@ function compileJavaSource(javaSource, className) {
|
|
|
81
93
|
timeout: 30000,
|
|
82
94
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
83
95
|
});
|
|
84
|
-
} catch (err) {
|
|
85
|
-
const
|
|
96
|
+
} catch (err: unknown) {
|
|
97
|
+
const execErr = err as { stderr?: Buffer; message: string };
|
|
98
|
+
const stderr = execErr.stderr ? execErr.stderr.toString() : execErr.message;
|
|
86
99
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
87
|
-
throw new Error(`Compilation failed for ${className}.java:\n${stderr}
|
|
100
|
+
throw new Error(`Compilation failed for ${className}.java:\n${stderr}`, {
|
|
101
|
+
cause: err,
|
|
102
|
+
});
|
|
88
103
|
}
|
|
89
104
|
|
|
90
105
|
return tempDir;
|
|
91
106
|
}
|
|
92
107
|
|
|
93
|
-
function ensureRunnerCompiled(runnerDir) {
|
|
108
|
+
function ensureRunnerCompiled(runnerDir: string): void {
|
|
94
109
|
const sourceFile = path.join(runnerDir, 'LiterateRunner.java');
|
|
95
110
|
const classFile = path.join(runnerDir, 'LiterateRunner.class');
|
|
96
111
|
|
|
@@ -110,20 +125,27 @@ function ensureRunnerCompiled(runnerDir) {
|
|
|
110
125
|
timeout: 30000,
|
|
111
126
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
112
127
|
});
|
|
113
|
-
} catch (err) {
|
|
114
|
-
const
|
|
115
|
-
|
|
128
|
+
} catch (err: unknown) {
|
|
129
|
+
const execErr = err as { stderr?: Buffer; message: string };
|
|
130
|
+
const stderr = execErr.stderr ? execErr.stderr.toString() : execErr.message;
|
|
131
|
+
throw new Error(`Failed to compile LiterateRunner.java:\n${stderr}`, {
|
|
132
|
+
cause: err,
|
|
133
|
+
});
|
|
116
134
|
}
|
|
117
135
|
}
|
|
118
136
|
|
|
119
|
-
function executeLiterateJava(
|
|
137
|
+
export function executeLiterateJava(
|
|
138
|
+
className: string,
|
|
139
|
+
classPath: string,
|
|
140
|
+
codeBlocks: LiterateCodeBlock[],
|
|
141
|
+
): LiterateRunnerEntry[] {
|
|
120
142
|
const runnerDir = path.join(__dirname, 'jdi-runner');
|
|
121
143
|
ensureRunnerCompiled(runnerDir);
|
|
122
144
|
|
|
123
145
|
const blockRanges = codeBlocks.map(b => [b.javaStartLine, b.javaEndLine]);
|
|
124
146
|
const rangesJson = JSON.stringify(blockRanges);
|
|
125
147
|
|
|
126
|
-
log.
|
|
148
|
+
log.debug`Executing literate Java: ${className}`;
|
|
127
149
|
|
|
128
150
|
log.debug`Running LiterateRunner with ${blockRanges.length} block range(s)`;
|
|
129
151
|
|
|
@@ -132,22 +154,29 @@ function executeLiterateJava(className, classPath, codeBlocks) {
|
|
|
132
154
|
`java -cp "${runnerDir}" LiterateRunner "${className}" "${classPath}" '${rangesJson}'`,
|
|
133
155
|
{ timeout: 30000, encoding: 'utf-8' },
|
|
134
156
|
);
|
|
135
|
-
const entries = JSON.parse(result);
|
|
157
|
+
const entries: LiterateRunnerEntry[] = JSON.parse(result);
|
|
136
158
|
log.debug`LiterateRunner returned ${entries.length} output entries`;
|
|
137
159
|
return entries;
|
|
138
|
-
} catch (err) {
|
|
139
|
-
const
|
|
140
|
-
|
|
160
|
+
} catch (err: unknown) {
|
|
161
|
+
const execErr = err as {
|
|
162
|
+
stderr?: Buffer | string;
|
|
163
|
+
stdout?: Buffer | string;
|
|
164
|
+
message: string;
|
|
165
|
+
};
|
|
166
|
+
const stderr = execErr.stderr ? execErr.stderr.toString() : '';
|
|
167
|
+
const stdout = execErr.stdout ? execErr.stdout.toString() : '';
|
|
141
168
|
throw new Error(
|
|
142
|
-
`Execution failed for ${className}:\n${stderr || stdout ||
|
|
169
|
+
`Execution failed for ${className}:\n${stderr || stdout || execErr.message}`,
|
|
170
|
+
{ cause: err },
|
|
143
171
|
);
|
|
144
172
|
}
|
|
145
173
|
}
|
|
146
174
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
175
|
+
export function checkJavac(): boolean {
|
|
176
|
+
try {
|
|
177
|
+
execSync('javac -version', { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
178
|
+
return true;
|
|
179
|
+
} catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -1,9 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import MarkdownIt from 'markdown-it';
|
|
2
|
+
import type { Options } from 'markdown-it/lib/index.mjs';
|
|
3
|
+
import type Token from 'markdown-it/lib/token.mjs';
|
|
4
|
+
import type Renderer from 'markdown-it/lib/renderer.mjs';
|
|
5
|
+
import { convertMarkdown as curlyQuote } from 'quote-quote';
|
|
6
|
+
import markdownItAnchor from 'markdown-it-anchor';
|
|
7
|
+
import markdownItFootnote from 'markdown-it-footnote';
|
|
8
|
+
import markdownItDeflist from 'markdown-it-deflist';
|
|
9
|
+
import markdownItContainer from 'markdown-it-container';
|
|
10
|
+
import textToId from '../text-to-id.js';
|
|
11
|
+
import { getHighlighter } from './shiki-highlighter.js';
|
|
12
|
+
import headingSubtitlePlugin from '../heading-subtitle-plugin.js';
|
|
13
|
+
import deflistIdPlugin from '../deflist-id-plugin.js';
|
|
14
|
+
import externalLinksPlugin from '../external-links-plugin.js';
|
|
15
|
+
import validateInternalLinksPlugin from '../validate-internal-links-plugin.js';
|
|
16
|
+
import applyBasePathPlugin from '../apply-base-path-plugin.js';
|
|
17
|
+
import { tocPlugin } from '../toc-plugin.js';
|
|
18
|
+
import type { SiteVariables } from '../types.js';
|
|
5
19
|
|
|
6
|
-
|
|
20
|
+
interface CreateMarkdownOptions {
|
|
21
|
+
validatorOptions?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function capitalize(str: string): string {
|
|
7
25
|
if (str.length < 2) {
|
|
8
26
|
return str;
|
|
9
27
|
}
|
|
@@ -11,31 +29,34 @@ function capitalize(str) {
|
|
|
11
29
|
return str[0].toUpperCase() + str.slice(1);
|
|
12
30
|
}
|
|
13
31
|
|
|
14
|
-
function createMarkdown(
|
|
32
|
+
export function createMarkdown(
|
|
33
|
+
siteVariables: SiteVariables,
|
|
34
|
+
options: CreateMarkdownOptions = {},
|
|
35
|
+
): MarkdownIt {
|
|
15
36
|
const { validatorOptions = {} } = options;
|
|
16
37
|
const markdown = new MarkdownIt({ html: true, typographer: true })
|
|
17
|
-
.use(
|
|
18
|
-
.use(
|
|
19
|
-
.use(
|
|
20
|
-
.use(
|
|
21
|
-
.use(
|
|
22
|
-
.use(
|
|
23
|
-
.use(
|
|
24
|
-
.use(
|
|
25
|
-
.use(
|
|
26
|
-
.use(
|
|
38
|
+
.use(headingSubtitlePlugin)
|
|
39
|
+
.use(markdownItAnchor, { tabIndex: false })
|
|
40
|
+
.use(markdownItFootnote)
|
|
41
|
+
.use(markdownItDeflist)
|
|
42
|
+
.use(deflistIdPlugin)
|
|
43
|
+
.use(externalLinksPlugin, siteVariables)
|
|
44
|
+
.use(validateInternalLinksPlugin, validatorOptions)
|
|
45
|
+
.use(applyBasePathPlugin, siteVariables)
|
|
46
|
+
.use(tocPlugin)
|
|
47
|
+
.use(markdownItContainer, 'details', {
|
|
27
48
|
marker: '<',
|
|
28
|
-
validate: function (params) {
|
|
29
|
-
return params.trim().match(/^details\s+(.*)$/);
|
|
49
|
+
validate: function (params: string) {
|
|
50
|
+
return !!params.trim().match(/^details\s+(.*)$/);
|
|
30
51
|
},
|
|
31
52
|
|
|
32
|
-
render: function (tokens, idx) {
|
|
33
|
-
|
|
53
|
+
render: function (tokens: Token[], idx: number) {
|
|
54
|
+
const m = tokens[idx].info.trim().match(/^details\s+(.*)$/);
|
|
34
55
|
|
|
35
56
|
if (tokens[idx].nesting === 1) {
|
|
36
57
|
return (
|
|
37
58
|
'<details><summary>' +
|
|
38
|
-
markdown.renderInline(m[1]) +
|
|
59
|
+
markdown.renderInline(m![1]) +
|
|
39
60
|
'</summary><div class="content">\n'
|
|
40
61
|
);
|
|
41
62
|
} else {
|
|
@@ -43,12 +64,12 @@ function createMarkdown(siteVariables, options = {}) {
|
|
|
43
64
|
}
|
|
44
65
|
},
|
|
45
66
|
})
|
|
46
|
-
.use(
|
|
67
|
+
.use(markdownItContainer, 'section', {
|
|
47
68
|
marker: ':',
|
|
48
|
-
validate: function (params) {
|
|
49
|
-
return params.trim().match(/^section$/);
|
|
69
|
+
validate: function (params: string) {
|
|
70
|
+
return !!params.trim().match(/^section$/);
|
|
50
71
|
},
|
|
51
|
-
render: function (tokens, idx) {
|
|
72
|
+
render: function (tokens: Token[], idx: number) {
|
|
52
73
|
if (tokens[idx].nesting === 1) {
|
|
53
74
|
return '<section>\n';
|
|
54
75
|
} else {
|
|
@@ -57,13 +78,13 @@ function createMarkdown(siteVariables, options = {}) {
|
|
|
57
78
|
},
|
|
58
79
|
});
|
|
59
80
|
|
|
60
|
-
const usedIds = new Map();
|
|
61
|
-
markdown.use(
|
|
81
|
+
const usedIds = new Map<string, number>();
|
|
82
|
+
markdown.use(markdownItContainer, 'alert', {
|
|
62
83
|
marker: '!',
|
|
63
|
-
validate: function (params) {
|
|
64
|
-
return params.trim().match(/^(note|warning)\s*"?(.+)?"?$/);
|
|
84
|
+
validate: function (params: string) {
|
|
85
|
+
return !!params.trim().match(/^(note|warning)\s*"?(.+)?"?$/);
|
|
65
86
|
},
|
|
66
|
-
render: function (tokens, idx) {
|
|
87
|
+
render: function (tokens: Token[], idx: number) {
|
|
67
88
|
const matches = tokens[idx].info
|
|
68
89
|
.trim()
|
|
69
90
|
.match(/^(note|warning)\s*"?(.+)?"?$/);
|
|
@@ -86,7 +107,7 @@ function createMarkdown(siteVariables, options = {}) {
|
|
|
86
107
|
const renderedTitle = markdown.utils.escapeHtml(curlyQuote(title));
|
|
87
108
|
html += `<p class="title" id="${titleId}">${renderedTitle}</p>\n`;
|
|
88
109
|
} else {
|
|
89
|
-
const defaultTitle = capitalize(type);
|
|
110
|
+
const defaultTitle = capitalize(type || '');
|
|
90
111
|
html += `<p class="title">${defaultTitle}</p>\n`;
|
|
91
112
|
}
|
|
92
113
|
html += '<div class="content">\n';
|
|
@@ -97,15 +118,15 @@ function createMarkdown(siteVariables, options = {}) {
|
|
|
97
118
|
},
|
|
98
119
|
});
|
|
99
120
|
|
|
100
|
-
markdown.use(
|
|
121
|
+
markdown.use(markdownItContainer, 'question', {
|
|
101
122
|
marker: '?',
|
|
102
|
-
validate: function (params) {
|
|
103
|
-
return params.trim().match(/^question\s+(.+)$/);
|
|
123
|
+
validate: function (params: string) {
|
|
124
|
+
return !!params.trim().match(/^question\s+(.+)$/);
|
|
104
125
|
},
|
|
105
|
-
render: function (tokens, idx) {
|
|
126
|
+
render: function (tokens: Token[], idx: number) {
|
|
106
127
|
const m = tokens[idx].info.trim().match(/^question\s+(.+)$/);
|
|
107
128
|
if (tokens[idx].nesting === 1) {
|
|
108
|
-
const question = markdown.renderInline(m[1]);
|
|
129
|
+
const question = markdown.renderInline(m![1]);
|
|
109
130
|
return (
|
|
110
131
|
'<div class="question">' +
|
|
111
132
|
'<p class="question-q"><span class="question-label">Q.</span><span>' +
|
|
@@ -130,28 +151,54 @@ function createMarkdown(siteVariables, options = {}) {
|
|
|
130
151
|
markdown.renderer.rules.footnote_block_close = () => '</ol></div>';
|
|
131
152
|
|
|
132
153
|
// Change appearance of reference
|
|
133
|
-
const caption = markdown.renderer.rules.footnote_caption
|
|
134
|
-
markdown.renderer.rules.footnote_caption = (
|
|
135
|
-
|
|
154
|
+
const caption = markdown.renderer.rules.footnote_caption!;
|
|
155
|
+
markdown.renderer.rules.footnote_caption = (
|
|
156
|
+
tokens: Token[],
|
|
157
|
+
idx: number,
|
|
158
|
+
options: Options,
|
|
159
|
+
env: unknown,
|
|
160
|
+
self: Renderer,
|
|
161
|
+
) => {
|
|
162
|
+
const str = caption(tokens, idx, options, env, self);
|
|
136
163
|
return str.slice(1, str.length - 1);
|
|
137
164
|
};
|
|
138
165
|
|
|
139
|
-
const footnoteRef = markdown.renderer.rules.footnote_ref
|
|
140
|
-
markdown.renderer.rules.footnote_ref = (
|
|
141
|
-
|
|
166
|
+
const footnoteRef = markdown.renderer.rules.footnote_ref!;
|
|
167
|
+
markdown.renderer.rules.footnote_ref = (
|
|
168
|
+
tokens: Token[],
|
|
169
|
+
idx: number,
|
|
170
|
+
options: Options,
|
|
171
|
+
env: unknown,
|
|
172
|
+
self: Renderer,
|
|
173
|
+
) =>
|
|
174
|
+
footnoteRef(tokens, idx, options, env, self)
|
|
142
175
|
.replace('<sup class="footnote-ref">', '')
|
|
143
176
|
.replace('</sup>', '')
|
|
144
177
|
.replace('<a href="', '<a class="footnote-ref" href="');
|
|
145
178
|
|
|
146
|
-
const footnoteAnchor = markdown.renderer.rules.footnote_anchor
|
|
147
|
-
markdown.renderer.rules.footnote_anchor = (
|
|
148
|
-
|
|
179
|
+
const footnoteAnchor = markdown.renderer.rules.footnote_anchor!;
|
|
180
|
+
markdown.renderer.rules.footnote_anchor = (
|
|
181
|
+
tokens: Token[],
|
|
182
|
+
idx: number,
|
|
183
|
+
options: Options,
|
|
184
|
+
env: unknown,
|
|
185
|
+
self: Renderer,
|
|
186
|
+
) =>
|
|
187
|
+
footnoteAnchor(tokens, idx, options, env, self).replace(
|
|
188
|
+
'\u21a9\uFE0E',
|
|
189
|
+
'\u2191',
|
|
190
|
+
);
|
|
149
191
|
|
|
150
192
|
/*
|
|
151
193
|
* Customize lists (add wrapper element)
|
|
152
194
|
*/
|
|
153
|
-
const proxy = (
|
|
154
|
-
|
|
195
|
+
const proxy = (
|
|
196
|
+
tokens: Token[],
|
|
197
|
+
idx: number,
|
|
198
|
+
options: Options,
|
|
199
|
+
_env: unknown,
|
|
200
|
+
self: Renderer,
|
|
201
|
+
) => self.renderToken(tokens, idx, options);
|
|
155
202
|
|
|
156
203
|
const itemOpen = markdown.renderer.rules.list_item_open || proxy;
|
|
157
204
|
markdown.renderer.rules.list_item_open = (
|
|
@@ -240,5 +287,3 @@ function createMarkdown(siteVariables, options = {}) {
|
|
|
240
287
|
|
|
241
288
|
return markdown;
|
|
242
289
|
}
|
|
243
|
-
|
|
244
|
-
module.exports = { createMarkdown };
|