markdown-maker 1.9.2 → 1.10.1
Sign up to get free protection for your applications and to get access to all the features.
- package/.github/workflows/node.js.yml +1 -1
- package/package.json +3 -2
- package/src/cltool.ts +11 -3
- package/src/commands.ts +98 -40
- package/src/parse.ts +45 -24
- package/src/templates/configTemplate.json +13 -0
- package/test/advanced.test.js +10 -8
- package/test/basic.test.js +10 -0
- package/test/errors.test.js +22 -1
- package/test/hooks.js +9 -0
- package/test/hooks.test.js +114 -0
- package/test/line.test.js +2 -4
- package/test/target.test.js +40 -32
- package/test/tester.test.js +15 -4
- package/test/vars.test.js +22 -30
- package/doc/.mdmconfig.json +0 -7
- package/doc/main.md +0 -2
- package/doc/other.md +0 -1
- package/re-test.js +0 -10
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "markdown-maker",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.10.1",
|
4
4
|
"description": "",
|
5
5
|
"main": "src/cltool.ts",
|
6
6
|
"bin": {
|
@@ -23,7 +23,7 @@
|
|
23
23
|
]
|
24
24
|
},
|
25
25
|
"scripts": {
|
26
|
-
"test": "mocha",
|
26
|
+
"test": "mocha --require test/hooks.js",
|
27
27
|
"test:yarn": "mocha",
|
28
28
|
"bundle": "tsc --project tsconfig.json",
|
29
29
|
"main": "node build/cltool.js document/main.md -o dist/bundle.md --debug",
|
@@ -52,6 +52,7 @@
|
|
52
52
|
"colors": "^1.4.0",
|
53
53
|
"colors.ts": "^1.0.20",
|
54
54
|
"marked": "^2.0.1",
|
55
|
+
"node-html-parser": "^6.1.13",
|
55
56
|
"require-runtime": "^2.0.0",
|
56
57
|
"ws": "^8.8.1"
|
57
58
|
},
|
package/src/cltool.ts
CHANGED
@@ -11,7 +11,7 @@ const { version } = require("../package.json"); /* package version number */
|
|
11
11
|
const choki = require("chokidar");
|
12
12
|
|
13
13
|
export const argParser = new ArgumentParser({
|
14
|
-
description: "Markdown bundler, with extra options",
|
14
|
+
description: "Markdown bundler, with extra options. Extension file is loaded from ./extensions.js, if it exists",
|
15
15
|
prog: "mdparse",
|
16
16
|
});
|
17
17
|
|
@@ -92,6 +92,14 @@ function main() {
|
|
92
92
|
clargs = argParser.parse_args();
|
93
93
|
}
|
94
94
|
|
95
|
+
/* if src is init, create config file and exit */
|
96
|
+
if (clargs.src == "init") {
|
97
|
+
const template = fs.readFileSync(path.join(__dirname, "..", "src", "templates", "configTemplate.json"));
|
98
|
+
fs.writeFileSync(configFileName, template);
|
99
|
+
fs.writeFileSync("main.md", "# Main\n");
|
100
|
+
return;
|
101
|
+
}
|
102
|
+
|
95
103
|
/* helper method for calling parser */
|
96
104
|
const compile = (source, output, cb?) => {
|
97
105
|
/* load data from file, if it exists,
|
@@ -140,9 +148,8 @@ function main() {
|
|
140
148
|
|
141
149
|
/* compile once */
|
142
150
|
if (!clargs.watch) compile(clargs.src, clargs.output);
|
143
|
-
|
144
151
|
/* watch the folder and recompile on change */
|
145
|
-
|
152
|
+
else {
|
146
153
|
const srcDirName = path.dirname(clargs.src);
|
147
154
|
console.log(`Watching ${srcDirName} for changes...`.yellow);
|
148
155
|
server = new WebSocketServer({ port: 7788 });
|
@@ -154,6 +161,7 @@ function main() {
|
|
154
161
|
console.log(e.message);
|
155
162
|
}
|
156
163
|
}
|
164
|
+
|
157
165
|
}
|
158
166
|
|
159
167
|
/* main entrypoint */
|
package/src/commands.ts
CHANGED
@@ -3,6 +3,7 @@ import Parser from "./parse";
|
|
3
3
|
import * as fs from "fs";
|
4
4
|
import templates, { new_template } from "./templates";
|
5
5
|
import requireRuntime from "require-runtime";
|
6
|
+
import * as nodeHtmlParser from "node-html-parser";
|
6
7
|
|
7
8
|
export class MDMError extends Error {
|
8
9
|
match: RegExpMatchArray;
|
@@ -42,7 +43,7 @@ export class Command {
|
|
42
43
|
constructor(
|
43
44
|
validator: RegExp,
|
44
45
|
acter: (match: RegExpMatchArray, parser: Parser) => string | void,
|
45
|
-
type: CommandType
|
46
|
+
type: CommandType
|
46
47
|
) {
|
47
48
|
this.type = type;
|
48
49
|
this.validator = validator;
|
@@ -71,7 +72,7 @@ export class Command {
|
|
71
72
|
new Command(
|
72
73
|
/(\s|^)<(.+)>/,
|
73
74
|
(match, parser) => `${match[1]}#mdvar<${match[2]}>`,
|
74
|
-
CommandType.PREPARSE
|
75
|
+
CommandType.PREPARSE
|
75
76
|
);
|
76
77
|
|
77
78
|
/* mddef */
|
@@ -80,7 +81,7 @@ new Command(
|
|
80
81
|
(match, parser) => {
|
81
82
|
parser.opts.defs[match[1]] = match[2].replace("_", " ");
|
82
83
|
},
|
83
|
-
CommandType.PARSE
|
84
|
+
CommandType.PARSE
|
84
85
|
);
|
85
86
|
|
86
87
|
/* mdvar */
|
@@ -92,7 +93,7 @@ new Command(
|
|
92
93
|
throw new Error(`Undefined variable: ${match[1]}`);
|
93
94
|
return (value = value || `<${match[1]}>`);
|
94
95
|
},
|
95
|
-
CommandType.PARSE
|
96
|
+
CommandType.PARSE
|
96
97
|
);
|
97
98
|
|
98
99
|
/** mdinclude */
|
@@ -107,17 +108,31 @@ new Command(
|
|
107
108
|
}
|
108
109
|
|
109
110
|
/* get the matching group */
|
110
|
-
|
111
|
+
let [_, name, condition] = match;
|
111
112
|
|
112
113
|
/* implement conditional imports */
|
113
114
|
if (condition && !parser.opts.args.includes(condition)) return;
|
114
115
|
|
116
|
+
const fsstat = fs.lstatSync(path.join(parser.wd, name));
|
117
|
+
if (fsstat.isDirectory()) {
|
118
|
+
/* check if a file with the same name of the
|
119
|
+
* exists in the folder */
|
120
|
+
|
121
|
+
if (fs.existsSync(path.join(parser.wd, name, `${name}.md`))) {
|
122
|
+
name = path.join(name, `${name}.md`);
|
123
|
+
} else {
|
124
|
+
throw new Error(
|
125
|
+
`No entry file found in folder "${name}". Looking for "${name}.md"`
|
126
|
+
);
|
127
|
+
}
|
128
|
+
}
|
129
|
+
|
115
130
|
const recursiveParser = new Parser(
|
116
131
|
path.join(parser.wd, name),
|
117
132
|
parser.opts,
|
118
133
|
{
|
119
134
|
parent: parser,
|
120
|
-
}
|
135
|
+
}
|
121
136
|
);
|
122
137
|
|
123
138
|
/* keep the options the same */
|
@@ -134,7 +149,7 @@ new Command(
|
|
134
149
|
parser.opts.depth--;
|
135
150
|
return blob;
|
136
151
|
},
|
137
|
-
CommandType.PARSE
|
152
|
+
CommandType.PARSE
|
138
153
|
);
|
139
154
|
|
140
155
|
/* mdlabel */
|
@@ -149,7 +164,7 @@ new Command(
|
|
149
164
|
parser.opts.secs.push({ level, title });
|
150
165
|
return `<span id="${link}"></span>`;
|
151
166
|
},
|
152
|
-
CommandType.PREPARSE
|
167
|
+
CommandType.PREPARSE
|
153
168
|
);
|
154
169
|
|
155
170
|
/* mdref */
|
@@ -163,7 +178,7 @@ new Command(
|
|
163
178
|
|
164
179
|
if (i === parser.opts.secs.length - 1)
|
165
180
|
throw new Error(
|
166
|
-
`Reference to [${match[1]}] could not be resolved
|
181
|
+
`Reference to [${match[1]}] could not be resolved!`
|
167
182
|
);
|
168
183
|
}
|
169
184
|
|
@@ -174,12 +189,12 @@ new Command(
|
|
174
189
|
else if (parser.opts.targetType === TargetType.MARKDOWN)
|
175
190
|
return `[${match[1]}](#${link})`;
|
176
191
|
},
|
177
|
-
CommandType.PARSE
|
192
|
+
CommandType.PARSE
|
178
193
|
);
|
179
194
|
|
180
195
|
/* mdtemplate */
|
181
196
|
new Command(
|
182
|
-
/#mdtemplate<(
|
197
|
+
/#mdtemplate<(\w+?)>/,
|
183
198
|
(match, parser) => {
|
184
199
|
const template = match[1];
|
185
200
|
const replacement = templates[template];
|
@@ -190,49 +205,92 @@ new Command(
|
|
190
205
|
throw new MDMError(`Template \"${template}\" not found!`, match);
|
191
206
|
}
|
192
207
|
},
|
193
|
-
CommandType.PARSE
|
208
|
+
CommandType.PARSE
|
194
209
|
);
|
195
210
|
|
211
|
+
/* mdmaketoc */
|
196
212
|
new Command(
|
197
213
|
/#mdmaketoc(?:<>)?/,
|
198
214
|
(match, parser) => parser.gen_toc(),
|
199
|
-
CommandType.POSTPARSE
|
215
|
+
CommandType.POSTPARSE
|
200
216
|
);
|
201
217
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
218
|
+
/* basic mdhook */
|
219
|
+
new Command(
|
220
|
+
/#mdhook<(\w+)>/,
|
221
|
+
(match, parser) => {
|
222
|
+
if (parser.opts.hooks[match[1]]) {
|
223
|
+
return parser.opts.hooks[match[1]]();
|
224
|
+
}
|
225
|
+
},
|
226
|
+
CommandType.POSTPARSE
|
227
|
+
);
|
228
|
+
|
229
|
+
/* mdadvhook */
|
230
|
+
new Command(
|
231
|
+
/\#mdadvhook<(\w+)>([\w\W]+?)\#mdendhook/,
|
232
|
+
(match, parser) => {
|
233
|
+
if (parser.opts.adv_hooks[match[1]]) {
|
234
|
+
const innerElements = match[2].trim();
|
235
|
+
/* Find tagNames in innerElements */
|
236
|
+
const re = /<(\w+)[\s=\w\"\'-]*>/g;
|
237
|
+
const tags = [];
|
238
|
+
innerElements.match(re)?.forEach((tag) => {
|
239
|
+
tags.push(tag.slice(1, -1).split(" ")[0]);
|
240
|
+
});
|
241
|
+
/* Evil type hack */
|
242
|
+
const root = nodeHtmlParser.parse(innerElements, {
|
243
|
+
voidTag: {
|
244
|
+
tags,
|
245
|
+
closingSlash: true,
|
246
|
+
},
|
247
|
+
}) as any as HTMLElement;
|
248
|
+
|
249
|
+
const helper = (node: HTMLElement) => {
|
250
|
+
/* */
|
251
|
+
const map: { [tag: string]: HTMLElement } = {};
|
252
|
+
for (let tag of tags) {
|
253
|
+
const el = node.getElementsByTagName(tag)[0];
|
254
|
+
const dataTag = el.toString().match(/data-tag="([\w-]+)"/);
|
255
|
+
if (!dataTag || dataTag[1] == undefined) continue;
|
256
|
+
el.tagName = dataTag[1];
|
257
|
+
el.removeAttribute("data-tag");
|
258
|
+
map[tag] = el;
|
259
|
+
}
|
260
|
+
return map;
|
261
|
+
};
|
262
|
+
|
263
|
+
const hooked = parser.opts.adv_hooks[match[1]](root, helper(root));
|
264
|
+
return hooked.toString();
|
265
|
+
}
|
266
|
+
},
|
267
|
+
CommandType.POSTPARSE
|
268
|
+
);
|
269
|
+
|
270
|
+
const loaded_extentions: fs.PathLike[] = [];
|
271
|
+
|
272
|
+
function load_extension(parser: Parser, file: fs.PathLike) {
|
273
|
+
if (loaded_extentions.includes(file)) return;
|
274
|
+
if (fs.existsSync(file)) {
|
275
|
+
const extensions = requireRuntime(file);
|
276
|
+
loaded_extentions.push(file);
|
207
277
|
extensions.main(new_template, new_command);
|
208
278
|
|
209
279
|
if (parser.opts.verbose)
|
210
|
-
console.log(
|
211
|
-
`Loaded global extensions from ${global_extensions_path}`
|
212
|
-
.yellow,
|
213
|
-
);
|
280
|
+
console.log(`Loaded extensions from ${file}`.yellow);
|
214
281
|
} else if (parser.opts.debug) {
|
215
|
-
console.log(
|
216
|
-
`No global extensions found at ${global_extensions_path}`.red,
|
217
|
-
);
|
282
|
+
console.log(`No extensions found at ${file}`.red);
|
218
283
|
}
|
284
|
+
}
|
285
|
+
|
286
|
+
export function load_extensions(parser: Parser) {
|
287
|
+
/* global extention */
|
288
|
+
const global_extensions_path = path.join(__dirname, "extensions.js");
|
289
|
+
load_extension(parser, global_extensions_path);
|
219
290
|
|
220
291
|
/* project extention */
|
221
292
|
const project_extensions_path = path.join(parser.wd_full, "extensions.js");
|
222
|
-
|
223
|
-
const extensions = requireRuntime(project_extensions_path);
|
224
|
-
extensions.main(new_template, new_command);
|
225
|
-
|
226
|
-
if (parser.opts.verbose)
|
227
|
-
console.log(
|
228
|
-
`Loaded project extensions from ${project_extensions_path}`
|
229
|
-
.yellow,
|
230
|
-
);
|
231
|
-
} else if (parser.opts.debug) {
|
232
|
-
console.log(
|
233
|
-
`No project extensions found at ${project_extensions_path}!`.red,
|
234
|
-
);
|
235
|
-
}
|
293
|
+
load_extension(parser, project_extensions_path);
|
236
294
|
}
|
237
295
|
|
238
296
|
/**
|
@@ -244,7 +302,7 @@ export function load_extensions(parser: Parser) {
|
|
244
302
|
export function new_command(
|
245
303
|
regex: RegExp,
|
246
304
|
acter: (match: RegExpMatchArray, parser: Parser) => string,
|
247
|
-
type?: CommandType
|
305
|
+
type?: CommandType
|
248
306
|
) {
|
249
307
|
new Command(regex, acter, type || CommandType.PARSE);
|
250
308
|
}
|
package/src/parse.ts
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
import fs from "fs"; /* for handling reading of files */
|
2
|
+
import path from "path"; /* for handling file paths */
|
3
3
|
|
4
4
|
import Colors = require("colors.ts"); /* for adding colours to strings */
|
5
5
|
Colors.enable();
|
6
|
-
|
6
|
+
import marked from "marked";
|
7
|
+
|
7
8
|
import { Command, commands, load_extensions, MDMError } from "./commands";
|
8
9
|
|
9
10
|
enum TargetType {
|
@@ -43,6 +44,13 @@ class Parser {
|
|
43
44
|
targetType: TargetType | undefined;
|
44
45
|
only_warn: boolean;
|
45
46
|
parent?: Parser;
|
47
|
+
hooks: { [key: string]: () => string };
|
48
|
+
adv_hooks: {
|
49
|
+
[key: string]: (
|
50
|
+
tree: HTMLElement,
|
51
|
+
map: { [tag: string]: HTMLElement }
|
52
|
+
) => HTMLElement;
|
53
|
+
};
|
46
54
|
isFileCallback: (s: string) => false | string;
|
47
55
|
};
|
48
56
|
raw: string;
|
@@ -89,6 +97,8 @@ class Parser {
|
|
89
97
|
targetType: undefined,
|
90
98
|
only_warn: false,
|
91
99
|
parent: undefined,
|
100
|
+
hooks: {},
|
101
|
+
adv_hooks: {},
|
92
102
|
isFileCallback: (f) => {
|
93
103
|
if (!fs.existsSync(f)) return false;
|
94
104
|
return fs.readFileSync(f, "utf-8") + "\n";
|
@@ -237,13 +247,27 @@ class Parser {
|
|
237
247
|
const link = this.titleId(title);
|
238
248
|
|
239
249
|
let __line =
|
240
|
-
hor.repeat(Math.max(sec.level - 1, 0)) +
|
250
|
+
hor.repeat(Math.max(sec.level - 1, 0)) +
|
251
|
+
beg +
|
252
|
+
`[${title}](#${link})`;
|
241
253
|
|
242
254
|
__blob.push(__line);
|
243
255
|
});
|
244
256
|
return __blob.join("\n");
|
245
257
|
}
|
246
258
|
|
259
|
+
add_hook(name: string, hook: () => string) {
|
260
|
+
if (this.opts.hooks[name] != undefined)
|
261
|
+
throw new Error(`Hook ${name} already exists!`);
|
262
|
+
this.opts.hooks[name] = hook;
|
263
|
+
}
|
264
|
+
|
265
|
+
add_adv_hook(name: string, hook: (tree: HTMLElement) => HTMLElement) {
|
266
|
+
if (this.opts.hooks[name] != undefined)
|
267
|
+
throw new Error(`Hook ${name} already exists!`);
|
268
|
+
this.opts.adv_hooks[name] = hook;
|
269
|
+
}
|
270
|
+
|
247
271
|
line_num_from_index(index: number) {
|
248
272
|
return this.raw.substring(0, index).split("\n").length + 1;
|
249
273
|
}
|
@@ -258,27 +282,21 @@ class Parser {
|
|
258
282
|
/* output the parsed document to bundle */
|
259
283
|
to(bundleName: string, callback: (fileName: string) => void) {
|
260
284
|
const dir = path.dirname(bundleName);
|
261
|
-
var called = false;
|
262
285
|
if (callback === undefined) callback = () => {};
|
263
286
|
|
264
287
|
if (!fs.existsSync(dir)) {
|
265
288
|
fs.mkdirSync(dir, { recursive: true });
|
266
289
|
}
|
267
|
-
this.get(TargetType.MARKDOWN, (blob) => {
|
268
|
-
fs.writeFile(bundleName, blob, () => {
|
269
|
-
if (!this.opts.html) {
|
270
|
-
callback(bundleName);
|
271
|
-
called = true;
|
272
|
-
}
|
273
|
-
});
|
274
|
-
});
|
275
290
|
|
276
|
-
if (this.opts.html) {
|
277
|
-
|
278
|
-
|
279
|
-
if (!called) callback(htmlFileName);
|
280
|
-
called = true;
|
291
|
+
if (!this.opts.html) {
|
292
|
+
this.get(TargetType.MARKDOWN, (blob) => {
|
293
|
+
fs.writeFile(bundleName, blob, () => callback(bundleName));
|
281
294
|
});
|
295
|
+
} else {
|
296
|
+
const htmlFileName = bundleName.replace(".md", ".html");
|
297
|
+
fs.writeFile(htmlFileName, this.html(), () =>
|
298
|
+
callback(htmlFileName)
|
299
|
+
);
|
282
300
|
}
|
283
301
|
}
|
284
302
|
|
@@ -293,7 +311,7 @@ class Parser {
|
|
293
311
|
return htmlFormatted;
|
294
312
|
}
|
295
313
|
|
296
|
-
get(targetType?: TargetType, callback
|
314
|
+
get(targetType?: TargetType, callback?: (blob: string) => void): string {
|
297
315
|
/* If target type is undefined, markdown is the default */
|
298
316
|
if (targetType === undefined) targetType = TargetType.MARKDOWN;
|
299
317
|
if (this.blobs[targetType]) {
|
@@ -309,8 +327,8 @@ class Parser {
|
|
309
327
|
if (callback) callback(blob);
|
310
328
|
return blob;
|
311
329
|
} catch (error) {
|
330
|
+
/* Compile a traceback of error */
|
312
331
|
let traceback = "";
|
313
|
-
|
314
332
|
let p: Parser = this;
|
315
333
|
|
316
334
|
do {
|
@@ -318,13 +336,15 @@ class Parser {
|
|
318
336
|
traceback += `\n...on line ${p.line_num_from_index(
|
319
337
|
error.match.index
|
320
338
|
)} in ${p.file}`.grey(15);
|
321
|
-
else
|
339
|
+
else
|
340
|
+
traceback +=
|
341
|
+
`\n...on line ${p.line_num} in ${p.file}`.grey(15);
|
322
342
|
if (p.parent) p = p.parent;
|
323
343
|
} while (p.parent);
|
324
344
|
|
325
345
|
error.message += traceback;
|
326
346
|
|
327
|
-
/* only interested in stacktrace
|
347
|
+
/* only interested in node stacktrace when debugging */
|
328
348
|
if (!this.opts.debug) error.stack = "";
|
329
349
|
|
330
350
|
if (this.opts.only_warn) console.error(error);
|
@@ -345,7 +365,7 @@ export function splice(
|
|
345
365
|
return start + newSubStr + end;
|
346
366
|
}
|
347
367
|
|
348
|
-
/* add extention to marked */
|
368
|
+
/* add extention to marked for classed blockquotes*/
|
349
369
|
marked.use({
|
350
370
|
renderer: {
|
351
371
|
blockquote(quote) {
|
@@ -359,7 +379,8 @@ marked.use({
|
|
359
379
|
const id = args.filter((arg) => arg.startsWith("#"));
|
360
380
|
|
361
381
|
const classNames = classes.map((c) => c.slice(1));
|
362
|
-
const classText =
|
382
|
+
const classText =
|
383
|
+
classes.length > 0 ? `class="${classNames.join(" ")}"` : "";
|
363
384
|
const idText = id.length > 0 ? `id="${id[0].slice(1)}"` : "";
|
364
385
|
|
365
386
|
/* remove the ending from the quote */
|
package/test/advanced.test.js
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
const util = require("./tester.test.js");
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
const putTemplate = () => {
|
4
|
+
util.put(
|
5
|
+
"module.exports = {main: (new_template, new_command) => {new_template('hi', 'hello'); new_command(/#test_cmd/, (t,p) => 'yeet', 0);}};",
|
6
|
+
"extensions.js"
|
7
|
+
);
|
8
|
+
};
|
7
9
|
|
8
10
|
describe("Use of templates", () => {
|
11
|
+
beforeEach(putTemplate);
|
9
12
|
it("should import presentation template as expected", () => {
|
13
|
+
putTemplate();
|
10
14
|
const output = new util.Parser("#mdtemplate<presentation>").get();
|
11
15
|
const template = `<style>html {width: 100vw;height: 100vh;}.slide {padding: 5%;border-radius: 25px;margin: 0;}div > .slide-num {position: absolute;top: 12.5%;right: 15%;/* font-size: 150%; */}body {margin: 5% 15%;}img {max-width: 100%;max-height: 40vh;}</style><script>document.addEventListener("DOMContentLoaded", () => {let current_slide = 0;const all_slides = document.querySelectorAll("div.slide");const num_slides = all_slides.length;all_slides.forEach((slide) => {const num_elem = document.createElement("p");num_elem.classList.add("slide-num");slide.appendChild(num_elem);});onkeydown = (ev) => {if (ev.key == "ArrowRight" && current_slide < all_slides.length - 1)update_slide(++current_slide);else if (ev.key == "ArrowLeft" && current_slide > 0)update_slide(--current_slide);};const update_slide = (index) => {all_slides.forEach((slide) => (slide.style.display = "none"));all_slides[current_slide].style.display = "block";all_slides[current_slide].lastChild.textContent = \`\${current_slide + 1} / \${num_slides}\`;};update_slide(current_slide);});</script>`;
|
12
16
|
|
@@ -14,22 +18,20 @@ describe("Use of templates", () => {
|
|
14
18
|
});
|
15
19
|
|
16
20
|
it("should use custom templates from project extensions.js file", () => {
|
21
|
+
putTemplate();
|
17
22
|
util.put("#mdtemplate<hi>", "sample1.md");
|
18
23
|
|
19
24
|
util.assert.strictEqual(
|
20
25
|
new util.Parser("test/test-files/sample1.md").get(),
|
21
26
|
"hello\n\n"
|
22
27
|
);
|
23
|
-
|
24
|
-
/* make sure to remove after, to not mess with future tests */
|
25
28
|
});
|
26
29
|
|
27
30
|
it("should use custom commands from project extensions.js file", () => {
|
31
|
+
putTemplate();
|
28
32
|
util.put("#test_cmd", "sample1.md");
|
29
33
|
|
30
34
|
const parser = new util.Parser("test/test-files/sample1.md");
|
31
35
|
util.assert.strictEqual(parser.get(), "yeet\n\n");
|
32
|
-
|
33
|
-
/* make sure to remove after, to not mess with future tests */
|
34
36
|
});
|
35
37
|
});
|
package/test/basic.test.js
CHANGED
@@ -54,4 +54,14 @@ describe("Basic features", () => {
|
|
54
54
|
|
55
55
|
util.assert.strictEqual(parser.get(), "### Title\n[Title](#title)\n\n");
|
56
56
|
});
|
57
|
+
it("should include file with same name as folder when including a folder", () => {
|
58
|
+
util.put("#mdinclude<sample_fld>", "sample1.md");
|
59
|
+
util.putDir("sample_fld");
|
60
|
+
util.put("hello", util.path.join("sample_fld", "sample_fld.md"));
|
61
|
+
|
62
|
+
const parser = new util.Parser("test/test-files/sample1.md");
|
63
|
+
const output = parser.get();
|
64
|
+
|
65
|
+
util.assert.strictEqual(output, "hello\n\n");
|
66
|
+
});
|
57
67
|
});
|
package/test/errors.test.js
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
const { Parser } = require("marked");
|
2
1
|
const util = require("./tester.test.js");
|
3
2
|
|
4
3
|
describe("Error handling", () => {
|
@@ -23,4 +22,26 @@ describe("Error handling", () => {
|
|
23
22
|
|
24
23
|
util.assert.strictEqual(e.message.replace(/(\\)+/g, "/"), answer);
|
25
24
|
});
|
25
|
+
it("should dissallow loading a folder without an entry file", () => {
|
26
|
+
util.put("#mdinclude<sample_fld>", "sample1.md");
|
27
|
+
util.putDir("sample_fld");
|
28
|
+
|
29
|
+
const parser = new util.Parser("test/test-files/sample1.md");
|
30
|
+
|
31
|
+
let e;
|
32
|
+
util.assert.throws(() => {
|
33
|
+
try {
|
34
|
+
parser.get();
|
35
|
+
} catch (_e) {
|
36
|
+
e = _e;
|
37
|
+
throw _e;
|
38
|
+
}
|
39
|
+
}, Error);
|
40
|
+
|
41
|
+
let answer =
|
42
|
+
'No entry file found in folder "sample_fld". Looking for "sample_fld.md"' +
|
43
|
+
"\n...on line 1 in test/test-files/sample1.md".grey(15);
|
44
|
+
|
45
|
+
util.assert.strictEqual(e.message.replace(/(\\)+/g, "/"), answer);
|
46
|
+
});
|
26
47
|
});
|
package/test/hooks.js
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
const util = require("./tester.test.js");
|
2
|
+
|
3
|
+
describe("Use of markdown hooks for SSR", () => {
|
4
|
+
it("should allow a simple hook to be used", () => {
|
5
|
+
util.put("#mdhook<test>", "sample1.md");
|
6
|
+
|
7
|
+
const parser = new util.Parser("test/test-files/sample1.md");
|
8
|
+
parser.add_hook("test", () => "hello");
|
9
|
+
const output = parser.get();
|
10
|
+
|
11
|
+
util.assert.strictEqual(output, "hello\n\n");
|
12
|
+
});
|
13
|
+
it("should allow advanced hooks to be used", () => {
|
14
|
+
util.put(
|
15
|
+
"#mdadvhook<test>\n<b>Bold</b>\n<p>Paragraph</p>\n#mdendhook",
|
16
|
+
"sample1.md"
|
17
|
+
);
|
18
|
+
|
19
|
+
const replacer = (arg) => {
|
20
|
+
const elem = new util.html.HTMLElement("p", {});
|
21
|
+
elem.set_content("complete");
|
22
|
+
return elem;
|
23
|
+
};
|
24
|
+
|
25
|
+
const parser = new util.Parser("test/test-files/sample1.md");
|
26
|
+
parser.opts.allow_undefined = true;
|
27
|
+
parser.add_adv_hook("test", replacer);
|
28
|
+
const output = parser.get();
|
29
|
+
|
30
|
+
util.assert.strictEqual(output, "<p>complete</p>\n\n");
|
31
|
+
});
|
32
|
+
it("should allow for hooks to be used in HTML", () => {
|
33
|
+
util.put("<html><body>#mdhook<test></body></html>", "sample1.html");
|
34
|
+
|
35
|
+
const parser = new util.Parser("test/test-files/sample1.html");
|
36
|
+
parser.opts.allow_undefined = true;
|
37
|
+
parser.add_hook("test", () => "hello");
|
38
|
+
const output = parser.get();
|
39
|
+
|
40
|
+
util.assert.strictEqual(output, "<html><body>hello</body></html>\n\n");
|
41
|
+
});
|
42
|
+
it("should allow for hooks to be used in HTML with advanced hooks", () => {
|
43
|
+
util.put(
|
44
|
+
"<html><body>#mdadvhook<test>\n<b>Bold</b>\n<p>Paragraph</p>\n#mdendhook</body></html>",
|
45
|
+
"sample1.html"
|
46
|
+
);
|
47
|
+
|
48
|
+
const replacer = (arg) => {
|
49
|
+
const elem = new util.html.HTMLElement("p", {});
|
50
|
+
elem.set_content("complete");
|
51
|
+
return elem;
|
52
|
+
};
|
53
|
+
|
54
|
+
const parser = new util.Parser("test/test-files/sample1.html");
|
55
|
+
parser.opts.allow_undefined = true;
|
56
|
+
parser.add_adv_hook("test", replacer);
|
57
|
+
const output = parser.get();
|
58
|
+
|
59
|
+
util.assert.strictEqual(
|
60
|
+
output,
|
61
|
+
"<html><body><p>complete</p></body></html>\n\n"
|
62
|
+
);
|
63
|
+
});
|
64
|
+
it("should allow for extracting a node from the document as a template manually with ids", () => {
|
65
|
+
util.put(
|
66
|
+
`<html><body>#mdadvhook<template><name id="b"></name><class id="p"></class>#mdendhook</body></html>`,
|
67
|
+
"sample1.html"
|
68
|
+
);
|
69
|
+
|
70
|
+
const replacer = (elem) => {
|
71
|
+
const nameElem = elem.getElementsByTagName("name")[0];
|
72
|
+
nameElem.tagName = nameElem.id;
|
73
|
+
nameElem.removeAttribute("id");
|
74
|
+
nameElem.set_content("bold");
|
75
|
+
const classElem = elem.getElementsByTagName("class")[0];
|
76
|
+
classElem.tagName = classElem.id;
|
77
|
+
classElem.removeAttribute("id");
|
78
|
+
classElem.set_content("paragraph");
|
79
|
+
return elem;
|
80
|
+
};
|
81
|
+
|
82
|
+
const parser = new util.Parser("test/test-files/sample1.html");
|
83
|
+
parser.opts.allow_undefined = true;
|
84
|
+
parser.add_adv_hook("template", replacer);
|
85
|
+
const output = parser.get();
|
86
|
+
|
87
|
+
util.assert.strictEqual(
|
88
|
+
output,
|
89
|
+
"<html><body><b>bold</b><p>paragraph</p></body></html>\n\n"
|
90
|
+
);
|
91
|
+
});
|
92
|
+
it("should allow for extracting a node from the document as a template using map and data-tags", () => {
|
93
|
+
util.put(
|
94
|
+
`<html><body>#mdadvhook<template><name data-tag="b"></name><class data-tag="p"></class>#mdendhook</body></html>`,
|
95
|
+
"sample1.html"
|
96
|
+
);
|
97
|
+
|
98
|
+
const replacer = (elem, map) => {
|
99
|
+
map["name"].set_content("bold");
|
100
|
+
map["class"].set_content("paragraph");
|
101
|
+
return elem;
|
102
|
+
};
|
103
|
+
|
104
|
+
const parser = new util.Parser("test/test-files/sample1.html");
|
105
|
+
parser.opts.allow_undefined = true;
|
106
|
+
parser.add_adv_hook("template", replacer);
|
107
|
+
const output = parser.get();
|
108
|
+
|
109
|
+
util.assert.strictEqual(
|
110
|
+
output,
|
111
|
+
"<html><body><b>bold</b><p>paragraph</p></body></html>\n\n"
|
112
|
+
);
|
113
|
+
});
|
114
|
+
});
|
package/test/line.test.js
CHANGED
@@ -3,7 +3,7 @@ const util = require("./tester.test");
|
|
3
3
|
describe("Managing blank lines", () => {
|
4
4
|
it("should always end with 2 blank lines, even with no input", () => {
|
5
5
|
const output = new util.Parser("").get();
|
6
|
-
util.assert.strictEqual(output, "\n\n")
|
6
|
+
util.assert.strictEqual(output, "\n\n");
|
7
7
|
});
|
8
8
|
|
9
9
|
it("should reduce blank lines to 2", () => {
|
@@ -16,8 +16,6 @@ describe("Managing blank lines", () => {
|
|
16
16
|
|
17
17
|
it("should allow words when removing blank lines", () => {
|
18
18
|
const output = new util.Parser("hii\n\n\n").get();
|
19
|
-
util.assert.strictEqual(output, "hii\n\n")
|
19
|
+
util.assert.strictEqual(output, "hii\n\n");
|
20
20
|
});
|
21
21
|
});
|
22
|
-
|
23
|
-
|
package/test/target.test.js
CHANGED
@@ -1,35 +1,43 @@
|
|
1
1
|
const util = require("./tester.test.js");
|
2
2
|
|
3
3
|
describe("Target specific functionality", () => {
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
4
|
+
describe("HTML", () => {
|
5
|
+
it("Should include `#mdlabel` command, when compiling HTML", () => {
|
6
|
+
const parser = new util.Parser("#mdlabel<0,Cool!>");
|
7
|
+
const html = parser.get(util.TargetType.HTML);
|
8
|
+
|
9
|
+
util.assert.strictEqual(html, '<span id="cool"></span>\n\n');
|
10
|
+
});
|
11
|
+
|
12
|
+
it("Should link to sections with #mdref", () => {
|
13
|
+
const parser = new util.Parser(
|
14
|
+
"#mdlabel<0,Cool!>\n#mdlabel<1,coolzz>\n#mdref<Cool!>"
|
15
|
+
);
|
16
|
+
const html = parser.get(util.TargetType.HTML);
|
17
|
+
|
18
|
+
util.assert.strictEqual(
|
19
|
+
html,
|
20
|
+
'<span id="cool"></span>\n<span id="coolzz"></span>\n<a href="#cool">Cool!</a>\n\n'
|
21
|
+
);
|
22
|
+
});
|
23
|
+
});
|
24
|
+
|
25
|
+
describe("Markdown", () => {
|
26
|
+
it("Should not include `#mdlabel` command, when compiling Markdown", () => {
|
27
|
+
const parser = new util.Parser("#mdlabel<0,Cool!>");
|
28
|
+
|
29
|
+
const md = parser.get(util.TargetType.MARKDOWN);
|
30
|
+
util.assert.strictEqual(md, "\n\n");
|
31
|
+
});
|
32
|
+
it("Should include #mdref to title elements in markdown", () => {
|
33
|
+
const output = new util.Parser(
|
34
|
+
"# Some Title!\n#mdref<Some Title!>"
|
35
|
+
).get();
|
36
|
+
|
37
|
+
util.assert.strictEqual(
|
38
|
+
output,
|
39
|
+
"# Some Title!\n[Some Title!](#some-title)\n\n"
|
40
|
+
);
|
41
|
+
});
|
42
|
+
});
|
43
|
+
});
|
package/test/tester.test.js
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
const fs = require("fs");
|
2
2
|
const assert = require("assert");
|
3
3
|
const Parser = require("../build/parse");
|
4
|
-
|
5
|
-
|
4
|
+
const path = require("path");
|
5
|
+
const html = require("node-html-parser");
|
6
6
|
|
7
7
|
/* make folder for temporary files, if it doesn't exist */
|
8
8
|
if (
|
@@ -12,8 +12,16 @@ if (
|
|
12
12
|
fs.mkdirSync("test/test-files");
|
13
13
|
}
|
14
14
|
|
15
|
+
/**
|
16
|
+
* Create a new file under `test/test-files` with the given content.
|
17
|
+
* @param {string} text
|
18
|
+
* @param {string} file
|
19
|
+
*/
|
15
20
|
function put(text, file) {
|
16
|
-
fs.writeFileSync("test
|
21
|
+
fs.writeFileSync(path.join("test", "test-files", file), text);
|
22
|
+
}
|
23
|
+
function putDir(name) {
|
24
|
+
fs.mkdirSync(path.join("test", "test-files", name));
|
17
25
|
}
|
18
26
|
|
19
27
|
const TargetType = {
|
@@ -22,9 +30,12 @@ const TargetType = {
|
|
22
30
|
};
|
23
31
|
|
24
32
|
module.exports = {
|
25
|
-
fs,
|
26
33
|
assert,
|
34
|
+
fs,
|
35
|
+
html,
|
36
|
+
path,
|
27
37
|
Parser,
|
28
38
|
put,
|
39
|
+
putDir,
|
29
40
|
TargetType,
|
30
41
|
};
|
package/test/vars.test.js
CHANGED
@@ -1,57 +1,49 @@
|
|
1
|
-
const util = require("./tester.test");
|
1
|
+
const util = require("./tester.test.js");
|
2
2
|
|
3
3
|
describe("Use variables", () => {
|
4
|
-
it(
|
4
|
+
it("should replace var with the value", () => {
|
5
5
|
const output = new util.Parser("#mddef<hi=yo>\n#mdvar<hi>").get();
|
6
6
|
|
7
|
-
util.assert.strictEqual(output, "\nyo\n\n")
|
7
|
+
util.assert.strictEqual(output, "\nyo\n\n");
|
8
8
|
});
|
9
|
-
it(
|
9
|
+
it("should use variable shorthand", () => {
|
10
10
|
const output = new util.Parser("#mddef<hi=yo>\n<hi>").get();
|
11
11
|
|
12
|
-
util.assert.strictEqual(output, "\nyo\n\n")
|
12
|
+
util.assert.strictEqual(output, "\nyo\n\n");
|
13
13
|
});
|
14
|
-
it(
|
14
|
+
it("should use variables across files", () => {
|
15
15
|
util.put("#mddef<hi=yo>\n#mdinclude<sample2.md>", "sample1.md");
|
16
16
|
util.put("<hi>", "sample2.md");
|
17
17
|
|
18
18
|
const output = new util.Parser("test/test-files/sample1.md").get();
|
19
19
|
|
20
|
-
util.assert.strictEqual(output, "\nyo\n\n")
|
21
|
-
})
|
22
|
-
it(
|
23
|
-
|
20
|
+
util.assert.strictEqual(output, "\nyo\n\n");
|
21
|
+
});
|
22
|
+
it("should throw if undefined variable", () => {
|
24
23
|
const parser = new util.Parser("<yo>");
|
25
24
|
|
26
25
|
let e;
|
27
26
|
/* should throw an error */
|
28
|
-
util.assert.throws(
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
throw _e;
|
35
|
-
}
|
27
|
+
util.assert.throws(() => {
|
28
|
+
try {
|
29
|
+
parser.get();
|
30
|
+
} catch (_e) {
|
31
|
+
e = _e;
|
32
|
+
throw _e;
|
36
33
|
}
|
37
|
-
);
|
38
|
-
|
39
|
-
|
40
|
-
|
34
|
+
});
|
41
35
|
});
|
42
36
|
|
43
|
-
it(
|
37
|
+
it("should preserve whatever comes after", () => {
|
44
38
|
const output = new util.Parser("#mddef<hi=yo>\n<hi>,").get();
|
45
39
|
util.assert.strictEqual(output, "\nyo,\n\n");
|
46
40
|
});
|
47
41
|
|
48
|
-
it(
|
49
|
-
const output = new util.Parser(
|
42
|
+
it("should replace underscore with space", () => {
|
43
|
+
const output = new util.Parser(
|
44
|
+
"#mddef<name=Mr_Sir>\n#mdvar<name>"
|
45
|
+
).get();
|
50
46
|
|
51
|
-
util.assert.strictEqual(output, "\nMr Sir\n\n")
|
47
|
+
util.assert.strictEqual(output, "\nMr Sir\n\n");
|
52
48
|
});
|
53
|
-
|
54
49
|
});
|
55
|
-
|
56
|
-
|
57
|
-
|
package/doc/.mdmconfig.json
DELETED
package/doc/main.md
DELETED
package/doc/other.md
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
From the other siiiiiide!
|