markdown-maker 1.10.2 → 1.10.3
Sign up to get free protection for your applications and to get access to all the features.
- package/.github/workflows/node.js.yml +21 -26
- package/.vscode/extensions.json +3 -0
- package/.vscode/settings.json +8 -3
- package/.vscode/snippets.code-snippets +6 -8
- package/jest.config.js +7 -0
- package/package.json +41 -40
- package/src/cltool.ts +113 -71
- package/src/commands.ts +237 -243
- package/src/errors.ts +26 -0
- package/src/main.ts +110 -109
- package/src/parser.ts +378 -350
- package/src/templates.ts +5 -1
- package/src/types.ts +33 -0
- package/tests/_test-util.ts +44 -0
- package/tests/advanced.spec.ts +92 -0
- package/tests/basic.spec.ts +68 -0
- package/tests/clargs.spec.ts +50 -0
- package/tests/errors.spec.ts +64 -0
- package/tests/html.spec.ts +23 -0
- package/tests/line.spec.ts +21 -0
- package/tests/marked.spec.ts +40 -0
- package/tests/target.spec.ts +41 -0
- package/tests/vars.spec.ts +45 -0
- package/tsconfig.json +66 -64
- package/prettierrc.yaml +0 -4
- package/test/advanced.test.js +0 -34
- package/test/basic.test.js +0 -67
- package/test/clargs.test.js +0 -51
- package/test/errors.test.js +0 -47
- package/test/hooks.js +0 -9
- package/test/hooks.test.js +0 -114
- package/test/html.test.js +0 -37
- package/test/line.test.js +0 -21
- package/test/marked.test.js +0 -43
- package/test/target.test.js +0 -43
- package/test/tester.test.js +0 -41
- package/test/vars.test.js +0 -49
package/src/commands.ts
CHANGED
@@ -4,293 +4,287 @@ import * as fs from "fs";
|
|
4
4
|
import templates, { new_template } from "./templates";
|
5
5
|
import requireRuntime from "require-runtime";
|
6
6
|
import * as nodeHtmlParser from "node-html-parser";
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
}
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
parse: [],
|
24
|
-
postparse: [],
|
7
|
+
import { HTMLElement } from "node-html-parser";
|
8
|
+
import crypto from "crypto";
|
9
|
+
import {
|
10
|
+
CommandGroupType,
|
11
|
+
CommandType,
|
12
|
+
TaggedElement,
|
13
|
+
TargetType,
|
14
|
+
} from "./types";
|
15
|
+
import { MDMError } from "./errors";
|
16
|
+
|
17
|
+
const salt = crypto.randomBytes(2).toString("hex");
|
18
|
+
|
19
|
+
export const commands: CommandGroupType = {
|
20
|
+
preparse: [],
|
21
|
+
parse: [],
|
22
|
+
postparse: [],
|
25
23
|
};
|
26
24
|
|
27
|
-
export enum CommandType {
|
28
|
-
PREPARSE,
|
29
|
-
PARSE,
|
30
|
-
POSTPARSE,
|
31
|
-
}
|
32
|
-
|
33
|
-
export enum TargetType {
|
34
|
-
HTML,
|
35
|
-
MARKDOWN,
|
36
|
-
}
|
37
|
-
|
38
25
|
export class Command {
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
26
|
+
validator: RegExp;
|
27
|
+
acter: (match: RegExpExecArray, parser: Parser) => string | void;
|
28
|
+
type: CommandType;
|
29
|
+
|
30
|
+
constructor(
|
31
|
+
validator: RegExp,
|
32
|
+
acter: (match: RegExpExecArray, parser: Parser) => string | void,
|
33
|
+
type: CommandType
|
34
|
+
) {
|
35
|
+
validator = new RegExp(validator.source, validator.flags);
|
36
|
+
this.validator = validator;
|
37
|
+
this.acter = acter;
|
38
|
+
this.type = type;
|
39
|
+
|
40
|
+
/* add this function to appropriate file */
|
41
|
+
switch (type) {
|
42
|
+
case CommandType.PARSE:
|
43
|
+
commands.parse.push(this);
|
44
|
+
break;
|
45
|
+
case CommandType.PREPARSE:
|
46
|
+
commands.preparse.push(this);
|
47
|
+
break;
|
48
|
+
case CommandType.POSTPARSE:
|
49
|
+
commands.postparse.push(this);
|
50
|
+
break;
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
act(match: RegExpExecArray, parser: Parser) {
|
55
|
+
return this.acter(match, parser);
|
56
|
+
}
|
69
57
|
}
|
70
58
|
|
71
59
|
/* variable shorthand */
|
72
60
|
new Command(
|
73
|
-
|
74
|
-
|
75
|
-
|
61
|
+
/(\s|^)!<(.+)>/,
|
62
|
+
(match, parser) => `${match[1]}#mdvar<${match[2]}>`,
|
63
|
+
CommandType.PREPARSE
|
76
64
|
);
|
77
65
|
|
78
66
|
/* mddef */
|
79
67
|
new Command(
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
68
|
+
/#mddef< *(.+?) *= *(.+?) *>/ /* first .+ is lazy so as to not match following spaces */,
|
69
|
+
(match, parser) => {
|
70
|
+
parser.opts.defs[match[1]] = match[2].replace("_", " ");
|
71
|
+
},
|
72
|
+
CommandType.PARSE
|
85
73
|
);
|
86
74
|
|
87
75
|
/* mdvar */
|
88
76
|
new Command(
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
77
|
+
/#mdvar<(.+?)>/,
|
78
|
+
(match, parser) => {
|
79
|
+
let value = parser.opts.defs[match[1]];
|
80
|
+
if (!value && !parser.opts.allow_undefined)
|
81
|
+
throw new MDMError(`Undefined variable: ${match[1]}`, match);
|
82
|
+
return (value = value || `<${match[1]}>`);
|
83
|
+
},
|
84
|
+
CommandType.PARSE
|
97
85
|
);
|
98
86
|
|
99
87
|
/** mdinclude */
|
100
88
|
new Command(
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
parser.opts.depth--;
|
150
|
-
return blob;
|
151
|
-
},
|
152
|
-
CommandType.PARSE
|
89
|
+
/#mdinclude<([\w.\/-]+)(?:[,\s]+([\w]+))?>/,
|
90
|
+
(match, parser) => {
|
91
|
+
/* increment the current recursion depth */
|
92
|
+
parser.opts.depth++;
|
93
|
+
|
94
|
+
if (parser.opts.depth > parser.opts.max_depth) {
|
95
|
+
throw new MDMError("max depth exceeded!", match);
|
96
|
+
}
|
97
|
+
|
98
|
+
/* get the matching group */
|
99
|
+
let [_, name, condition] = match;
|
100
|
+
|
101
|
+
/* implement conditional imports */
|
102
|
+
if (condition && !parser.opts.args.includes(condition)) return;
|
103
|
+
|
104
|
+
const fsstat = fs.lstatSync(path.join(parser.wd, name));
|
105
|
+
if (fsstat.isDirectory()) {
|
106
|
+
/* check if a file with the same name of the
|
107
|
+
* exists in the folder */
|
108
|
+
|
109
|
+
const entry = path.join(parser.wd, name, `${name}.md`);
|
110
|
+
if (fs.existsSync(entry)) {
|
111
|
+
name = path.join(name, `${name}.md`);
|
112
|
+
} else {
|
113
|
+
throw new MDMError(
|
114
|
+
`No entry file found in folder "${name}". Looking for "${entry}"`,
|
115
|
+
match
|
116
|
+
);
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
120
|
+
const recursiveParser = parser.createChild(path.join(parser.wd, name));
|
121
|
+
|
122
|
+
/* keep the options the same */
|
123
|
+
recursiveParser.opts = parser.opts;
|
124
|
+
recursiveParser.parent = parser;
|
125
|
+
|
126
|
+
const fileType = path.extname(recursiveParser.file);
|
127
|
+
|
128
|
+
const blob =
|
129
|
+
fileType === ".md"
|
130
|
+
? recursiveParser.get(parser.opts.targetType)
|
131
|
+
: recursiveParser.raw;
|
132
|
+
|
133
|
+
parser.opts.depth--;
|
134
|
+
return blob;
|
135
|
+
},
|
136
|
+
CommandType.PARSE
|
153
137
|
);
|
154
138
|
|
155
139
|
/* mdlabel */
|
156
140
|
new Command(
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
141
|
+
/#mdlabel<(\d+),\s?(.+)>/,
|
142
|
+
(match, parser) => {
|
143
|
+
if (parser.opts.targetType !== TargetType.HTML) return "";
|
144
|
+
|
145
|
+
const level = Number.parseInt(match[1]);
|
146
|
+
const title = match[2];
|
147
|
+
const link = parser.titleId(title);
|
148
|
+
parser.opts.secs.push({ level, title });
|
149
|
+
return `<span id="${link}"></span>`;
|
150
|
+
},
|
151
|
+
CommandType.PREPARSE
|
168
152
|
);
|
169
153
|
|
170
154
|
/* mdref */
|
171
155
|
new Command(
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
156
|
+
/#mdref<(.+)>/,
|
157
|
+
|
158
|
+
(match, parser) => {
|
159
|
+
for (let i = 0; i < parser.opts.secs.length; i++) {
|
160
|
+
let { title } = parser.opts.secs[i];
|
161
|
+
if (title === match[1]) break;
|
162
|
+
|
163
|
+
if (i === parser.opts.secs.length - 1)
|
164
|
+
throw new MDMError(
|
165
|
+
`Reference to [${match[1]}] could not be resolved!`,
|
166
|
+
match
|
167
|
+
);
|
168
|
+
}
|
169
|
+
|
170
|
+
match[1] = match[1].replace("_", " ");
|
171
|
+
const link = parser.titleId(match[1]);
|
172
|
+
if (parser.opts.targetType === TargetType.HTML)
|
173
|
+
return `<a href="#${link}">${match[1]}</a>`;
|
174
|
+
else if (parser.opts.targetType === TargetType.MARKDOWN)
|
175
|
+
return `[${match[1]}](#${link})`;
|
176
|
+
},
|
177
|
+
CommandType.PARSE
|
193
178
|
);
|
194
179
|
|
195
180
|
/* mdtemplate */
|
196
181
|
new Command(
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
182
|
+
/#mdtemplate<(\w+?)>/,
|
183
|
+
(match, parser) => {
|
184
|
+
const template = match[1];
|
185
|
+
const replacement = templates[template];
|
186
|
+
|
187
|
+
if (replacement !== undefined) {
|
188
|
+
return replacement;
|
189
|
+
} else {
|
190
|
+
throw new MDMError(`Template \"${template}\" not found!`, match);
|
191
|
+
}
|
192
|
+
},
|
193
|
+
CommandType.PARSE
|
209
194
|
);
|
210
195
|
|
211
196
|
/* mdmaketoc */
|
212
197
|
new Command(
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
);
|
217
|
-
|
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
|
198
|
+
/#mdmaketoc(?:<>)?/,
|
199
|
+
(match, parser) => parser.get_toc(),
|
200
|
+
CommandType.POSTPARSE
|
227
201
|
);
|
228
202
|
|
229
203
|
/* mdadvhook */
|
230
204
|
new Command(
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
205
|
+
/\#mdhook<(\w+)>([\w\W]+)\#mdendhook<\1>/m,
|
206
|
+
(match, parser) => {
|
207
|
+
if (!parser.opts.hooks[match[1]])
|
208
|
+
throw new MDMError(`No advanced hook found for ${match[1]}`, match);
|
209
|
+
|
210
|
+
const innerElements = match[2].trim();
|
211
|
+
|
212
|
+
/* Run the inner elements through the parser itself */
|
213
|
+
const innerParser = parser.createChild(innerElements);
|
214
|
+
|
215
|
+
/* parse the inner */
|
216
|
+
let innerBlob = innerParser.get(parser.opts.targetType).trim();
|
217
|
+
|
218
|
+
/* Find all tagged elements in inner */
|
219
|
+
/* and construct helper map */
|
220
|
+
const re = /<(\w+)::(\w+)([\s=\w\"\'-]*)>/g;
|
221
|
+
const map: { [key: string]: TaggedElement } = {};
|
222
|
+
for (let match of innerBlob.matchAll(re)) {
|
223
|
+
const args = Array.from(match[3].matchAll(/(\w+)=["'](.+?)["']/g));
|
224
|
+
const argsMap = Object.fromEntries(args.map((x) => x.slice(1)));
|
225
|
+
map[match[2]] = {
|
226
|
+
"html-tag": match[1],
|
227
|
+
"var-tag": match[2],
|
228
|
+
node: new nodeHtmlParser.HTMLElement(match[1], {}),
|
229
|
+
args: Object.fromEntries(
|
230
|
+
match[3]
|
231
|
+
.trim()
|
232
|
+
.split(" ")
|
233
|
+
.map((x) => x.split("="))
|
234
|
+
),
|
235
|
+
_raw: match[0],
|
236
|
+
};
|
237
|
+
}
|
238
|
+
|
239
|
+
/* Replace tagged elements in inner blob with their uuids */
|
240
|
+
for (let taggedElement of Object.values(map)) {
|
241
|
+
innerBlob = innerBlob.replace(
|
242
|
+
taggedElement._raw,
|
243
|
+
salt + taggedElement["var-tag"] + salt
|
244
|
+
);
|
245
|
+
}
|
246
|
+
|
247
|
+
/* Run the hook on the map*/
|
248
|
+
parser.opts.hooks[match[1]](map);
|
249
|
+
|
250
|
+
/* Replace the tagged elements with the modified nodes */
|
251
|
+
let output = innerBlob;
|
252
|
+
for (let taggedElement of Object.values(map)) {
|
253
|
+
output = output.replace(
|
254
|
+
salt + taggedElement["var-tag"] + salt,
|
255
|
+
taggedElement.node.toString()
|
256
|
+
);
|
257
|
+
}
|
258
|
+
|
259
|
+
return output;
|
260
|
+
},
|
261
|
+
CommandType.POSTPARSE
|
268
262
|
);
|
269
263
|
|
270
264
|
const loaded_extentions: fs.PathLike[] = [];
|
271
265
|
|
272
266
|
function load_extension(parser: Parser, file: fs.PathLike) {
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
267
|
+
// if (loaded_extentions.includes(file)) return;
|
268
|
+
if (fs.existsSync(file)) {
|
269
|
+
const extensions = requireRuntime(file);
|
270
|
+
loaded_extentions.push(file);
|
271
|
+
extensions.main(new_template, new_command);
|
272
|
+
|
273
|
+
if (parser.opts.verbose)
|
274
|
+
console.log(`Loaded extensions from ${file}`.yellow);
|
275
|
+
} else if (parser.opts.debug) {
|
276
|
+
console.log(`No extensions found at ${file}`.red);
|
277
|
+
}
|
284
278
|
}
|
285
279
|
|
286
280
|
export function load_extensions(parser: Parser) {
|
287
|
-
|
288
|
-
|
289
|
-
|
281
|
+
/* global extention */
|
282
|
+
const global_extensions_path = path.join(__dirname, "extensions.js");
|
283
|
+
load_extension(parser, global_extensions_path);
|
290
284
|
|
291
|
-
|
292
|
-
|
293
|
-
|
285
|
+
/* project extention */
|
286
|
+
const project_extensions_path = path.join(parser.wd_full, "extensions.js");
|
287
|
+
load_extension(parser, project_extensions_path);
|
294
288
|
}
|
295
289
|
|
296
290
|
/**
|
@@ -300,11 +294,11 @@ export function load_extensions(parser: Parser) {
|
|
300
294
|
* @param type When the command should be run. Can be `CommandType.PREPARSE`, `CommandType.PARSE`, or `CommandType.POSTPARSE`. Defaults to `CommandType.PARSE`.
|
301
295
|
*/
|
302
296
|
export function new_command(
|
303
|
-
|
304
|
-
|
305
|
-
|
297
|
+
regex: RegExp,
|
298
|
+
acter: (match: RegExpMatchArray, parser: Parser) => string,
|
299
|
+
type?: CommandType
|
306
300
|
) {
|
307
|
-
|
301
|
+
new Command(regex, acter, type || CommandType.PARSE);
|
308
302
|
}
|
309
303
|
|
310
304
|
export default { commands, load_extensions };
|
package/src/errors.ts
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
export class MDMNonParserError extends Error {
|
2
|
+
constructor(message: string) {
|
3
|
+
super(message);
|
4
|
+
Object.setPrototypeOf(this, MDMNonParserError.prototype);
|
5
|
+
}
|
6
|
+
}
|
7
|
+
|
8
|
+
export class MDMError extends Error {
|
9
|
+
match: RegExpMatchArray;
|
10
|
+
constructor(message: string, match: RegExpMatchArray) {
|
11
|
+
super(message);
|
12
|
+
Object.setPrototypeOf(this, MDMError.prototype);
|
13
|
+
|
14
|
+
this.match = match;
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
export class MDMWarning extends Error {
|
19
|
+
match?: RegExpMatchArray;
|
20
|
+
constructor(message: string, match?: RegExpMatchArray) {
|
21
|
+
super(message);
|
22
|
+
Object.setPrototypeOf(this, MDMWarning.prototype);
|
23
|
+
|
24
|
+
this.match = match;
|
25
|
+
}
|
26
|
+
}
|