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/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
- export class MDMError extends Error {
9
- match: RegExpMatchArray;
10
- constructor(message: string, match: RegExpMatchArray) {
11
- super(message);
12
- this.name = "MDMError";
13
- this.match = match;
14
- }
15
- }
16
-
17
- export const commands: {
18
- preparse: Command[];
19
- parse: Command[];
20
- postparse: Command[];
21
- } = {
22
- preparse: [],
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
- validator: RegExp;
40
- acter: (match: RegExpMatchArray, parser: Parser) => string | void;
41
- type: CommandType;
42
-
43
- constructor(
44
- validator: RegExp,
45
- acter: (match: RegExpMatchArray, parser: Parser) => string | void,
46
- type: CommandType
47
- ) {
48
- this.type = type;
49
- this.validator = validator;
50
- this.acter = acter;
51
-
52
- /* add this function to appropriate file */
53
- switch (type) {
54
- case CommandType.PARSE:
55
- commands.parse.push(this);
56
- break;
57
- case CommandType.PREPARSE:
58
- commands.preparse.push(this);
59
- break;
60
- case CommandType.POSTPARSE:
61
- commands.postparse.push(this);
62
- break;
63
- }
64
- }
65
-
66
- act(match, parser) {
67
- return this.acter(match, parser);
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
- /(\s|^)<(.+)>/,
74
- (match, parser) => `${match[1]}#mdvar<${match[2]}>`,
75
- CommandType.PREPARSE
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
- /#mddef< *(.+?) *= *(.+?) *>/ /* first .+ is lazy so as to not match following spaces */,
81
- (match, parser) => {
82
- parser.opts.defs[match[1]] = match[2].replace("_", " ");
83
- },
84
- CommandType.PARSE
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
- /#mdvar<(.+?)>/,
90
- (match, parser) => {
91
- let value = parser.opts.defs[match[1]];
92
- if (!value && !parser.opts.allow_undefined)
93
- throw new Error(`Undefined variable: ${match[1]}`);
94
- return (value = value || `<${match[1]}>`);
95
- },
96
- CommandType.PARSE
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
- /#mdinclude<([\w.\/-]+)(?:[,\s]+([\w]+))?>/,
102
- (match, parser) => {
103
- /* increase the current recursive depth */
104
- parser.opts.depth++;
105
-
106
- if (parser.opts.depth > parser.opts.max_depth) {
107
- throw new Error("max depth exceeded!");
108
- }
109
-
110
- /* get the matching group */
111
- let [_, name, condition] = match;
112
-
113
- /* implement conditional imports */
114
- if (condition && !parser.opts.args.includes(condition)) return;
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
-
130
- const recursiveParser = new Parser(
131
- path.join(parser.wd, name),
132
- parser.clargs,
133
- {
134
- parent: parser,
135
- }
136
- );
137
-
138
- /* keep the options the same */
139
- recursiveParser.opts = parser.opts;
140
- recursiveParser.parent = parser;
141
-
142
- const fileType = path.extname(recursiveParser.file);
143
-
144
- const blob =
145
- fileType === ".md"
146
- ? recursiveParser.get(parser.opts.targetType)
147
- : recursiveParser.raw;
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
- /#mdlabel<(\d+),\s?(.+)>/,
158
- (match, parser) => {
159
- if (parser.opts.targetType !== TargetType.HTML) return "";
160
-
161
- const level = Number.parseInt(match[1]);
162
- const title = match[2];
163
- const link = parser.titleId(title);
164
- parser.opts.secs.push({ level, title });
165
- return `<span id="${link}"></span>`;
166
- },
167
- CommandType.PREPARSE
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
- /#mdref<(.+)>/,
173
-
174
- (match, parser) => {
175
- for (let i = 0; i < parser.opts.secs.length; i++) {
176
- let { title } = parser.opts.secs[i];
177
- if (title === match[1]) break;
178
-
179
- if (i === parser.opts.secs.length - 1)
180
- throw new Error(
181
- `Reference to [${match[1]}] could not be resolved!`
182
- );
183
- }
184
-
185
- match[1] = match[1].replace("_", " ");
186
- const link = parser.titleId(match[1]);
187
- if (parser.opts.targetType === TargetType.HTML)
188
- return `<a href="#${link}">${match[1]}</a>`;
189
- else if (parser.opts.targetType === TargetType.MARKDOWN)
190
- return `[${match[1]}](#${link})`;
191
- },
192
- CommandType.PARSE
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
- /#mdtemplate<(\w+?)>/,
198
- (match, parser) => {
199
- const template = match[1];
200
- const replacement = templates[template];
201
-
202
- if (replacement !== undefined) {
203
- return replacement;
204
- } else {
205
- throw new MDMError(`Template \"${template}\" not found!`, match);
206
- }
207
- },
208
- CommandType.PARSE
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
- /#mdmaketoc(?:<>)?/,
214
- (match, parser) => parser.gen_toc(),
215
- CommandType.POSTPARSE
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
- /\#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
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
- if (loaded_extentions.includes(file)) return;
274
- if (fs.existsSync(file)) {
275
- const extensions = requireRuntime(file);
276
- loaded_extentions.push(file);
277
- extensions.main(new_template, new_command);
278
-
279
- if (parser.opts.verbose)
280
- console.log(`Loaded extensions from ${file}`.yellow);
281
- } else if (parser.opts.debug) {
282
- console.log(`No extensions found at ${file}`.red);
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
- /* global extention */
288
- const global_extensions_path = path.join(__dirname, "extensions.js");
289
- load_extension(parser, global_extensions_path);
281
+ /* global extention */
282
+ const global_extensions_path = path.join(__dirname, "extensions.js");
283
+ load_extension(parser, global_extensions_path);
290
284
 
291
- /* project extention */
292
- const project_extensions_path = path.join(parser.wd_full, "extensions.js");
293
- load_extension(parser, project_extensions_path);
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
- regex: RegExp,
304
- acter: (match: RegExpMatchArray, parser: Parser) => string,
305
- type?: CommandType
297
+ regex: RegExp,
298
+ acter: (match: RegExpMatchArray, parser: Parser) => string,
299
+ type?: CommandType
306
300
  ) {
307
- new Command(regex, acter, type || CommandType.PARSE);
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
+ }