markdown-maker 1.10.2 → 1.10.3

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/src/parser.ts CHANGED
@@ -1,370 +1,398 @@
1
1
  import fs from "fs"; /* for handling reading of files */
2
2
  import path from "path"; /* for handling file paths */
3
3
 
4
- import Colors = require("colors.ts"); /* for adding colours to strings */
5
- Colors.enable();
6
4
  import marked from "marked";
7
5
 
8
- import { Command, commands, load_extensions, MDMError } from "./commands";
9
- import { argParser, CLArgs as CommandLineArgs, ParserOptions } from "./cltool";
10
-
11
- enum TargetType {
12
- HTML,
13
- MARKDOWN,
14
- }
6
+ import { Command, commands, load_extensions } from "./commands";
7
+ import {
8
+ argParser,
9
+ IncompleteCommandLineArgs,
10
+ IncompleteParserOptions,
11
+ ParserOptions,
12
+ } from "./cltool";
13
+ import { MDMError, MDMNonParserError, MDMWarning } from "./errors";
14
+ import { CommandGroupType, TaggedElement, TargetType } from "./types";
15
15
 
16
16
  /* parse some md
17
17
  * recursively with extra options */
18
18
  class Parser {
19
- file: string;
20
- parent?: Parser;
21
- line_num: number;
22
- wd: string;
23
- wd_full: string;
24
- blobs: {
25
- [key: number]: string | undefined;
26
- };
27
- opts: ParserOptions;
28
- clargs: CommandLineArgs;
29
- raw: string;
30
-
31
- static TOKEN = "#md";
32
-
33
- constructor(
34
- filename: string,
35
- clargs?: CommandLineArgs,
36
- opts?: {
37
- parent?: Parser;
38
- isFileCallback?: (s: string) => false | string;
39
- }
40
- ) {
41
- /* this.working_directory */
42
- this.file = filename;
43
-
44
- if (!opts) opts = {};
45
-
46
- /* the parent parser */
47
- this.parent = opts.parent;
48
-
49
- this.line_num = 0;
50
- this.wd = path.dirname(filename);
51
- this.wd_full = path.resolve(this.wd);
52
-
53
- /* finished blob */
54
- this.blobs = {};
55
-
56
- /* all options */
57
- this.clargs = clargs;
58
- this.opts = {
59
- defs: {},
60
- secs: [],
61
- args: [],
62
- depth: 0,
63
- verbose: false,
64
- debug: false,
65
- max_depth: 5,
66
- use_underscore: false,
67
- toc_level: 3,
68
- allow_undefined: false,
69
- html: false,
70
- watch: false,
71
- targetType: undefined,
72
- only_warn: false,
73
- parent: undefined,
74
- hooks: {},
75
- adv_hooks: {},
76
- isFileCallback: (f) => {
77
- if (!fs.existsSync(f)) return false;
78
- return fs.readFileSync(f, "utf-8") + "\n";
79
- },
80
- };
81
-
82
- if (!clargs) {
83
- clargs = argParser.parse_args([filename]);
84
- }
85
-
86
- /* append all commandline arguments to this */
87
- Object.assign(this.opts, clargs);
88
- Object.assign(this.opts, opts);
89
-
90
- this.raw = this.opts.isFileCallback(filename) || filename;
91
- }
92
-
93
- /**
94
- * parse wrapper for handling
95
- * preprocessing, parsing and postprocess
96
- **/
97
- parse() {
98
- load_extensions(this);
99
- if (this.opts.verbose || this.opts.debug) {
100
- console.log(
101
- Colors.colors(
102
- "magenta",
103
- "parsing " + this.file + ": depth=" + this.opts.depth
104
- )
105
- );
106
- }
107
-
108
- if (this.opts.debug) {
109
- console.log("Parsing options:");
110
- console.log(this.opts);
111
- }
112
-
113
- /* reset sections for beginning parse */
114
- if (this.opts.depth === 0) this.opts.secs = [];
115
- let __blob;
116
-
117
- /* apply preproccessing to raw file */
118
- __blob = this.preprocess(this.raw);
119
-
120
- /* main parser instance call */
121
- __blob = this.mainparse(__blob);
122
-
123
- /**
124
- * apply postprocessing after */
125
- __blob = this.postprocess(__blob);
126
-
127
- return __blob;
128
- }
129
-
130
- mainparse(blob: string) {
131
- if (this.opts.verbose || this.opts.debug) {
132
- console.debug(`beginning mainparse of '${this.file}'`.blue);
133
- }
134
-
135
- /* main parser instance loop */
136
- blob.split("\n").forEach((line, lnum) => {
137
- this.line_num = lnum;
138
-
139
- /* if line looks like a title */
140
- const titleMatch = line.trim().match(/^(#+) (.+)$/);
141
-
142
- if (titleMatch) {
143
- if (this.opts.verbose || this.opts.debug)
144
- console.log("found toc element: " + line);
145
-
146
- /* implement toc level */
147
- let level = titleMatch[1].length;
148
- let title = titleMatch[2];
149
-
150
- this.opts.secs.push({ level, title });
151
-
152
- if (this.opts.debug) {
153
- console.log("updated sections:", { level, title });
154
- }
155
- }
156
- });
157
-
158
- return this.parse_commands(blob, commands.parse);
159
- }
160
-
161
- preprocess(blob: string) {
162
- if (this.opts.verbose || this.opts.debug) {
163
- console.debug(`beginning preprocess of '${this.file}'`.blue);
164
- }
165
-
166
- return this.parse_commands(blob, commands.preparse);
167
- }
168
-
169
- postprocess(blob: string) {
170
- if (this.opts.verbose || this.opts.debug) {
171
- console.debug(`beginning postprocess of '${this.file}'`.blue);
172
- }
173
-
174
- blob = this.parse_commands(blob, commands.postparse);
175
-
176
- /* remove double empty lines */
177
- blob = this.remove_double_blank_lines(blob);
178
- blob = blob.trimEnd() + "\n\n";
179
- return blob;
180
- }
181
-
182
- parse_commands(blob: string, commands: Command[]) {
183
- commands.forEach((command) => {
184
- /* Add global flag to RegExp */
185
- const re = new RegExp(
186
- command.validator.source,
187
- (command.validator.flags || "") + "g"
188
- );
189
- blob = blob.replace(re, (...args) => command.act(args, this) || "");
190
- });
191
- return blob;
192
- }
193
-
194
- parse_all_commands(blob: string, commands: { [key: string]: Command[] }) {
195
- Object.keys(commands).forEach((key) => {
196
- blob = this.parse_commands(blob, commands[key]);
197
- });
198
- return blob;
199
- }
200
-
201
- titleId(title: string) {
202
- const sep = this.opts.use_underscore ? "_" : "-";
203
-
204
- title = title
205
- .toLowerCase()
206
- .replace(/[^\w\s]+/g, "")
207
- .replace(/[\s_]+/g, sep);
208
- return title;
209
- }
210
-
211
- gen_toc() {
212
- let __blob = [];
213
- let tabSize = 2;
214
- const beg = "* ";
215
- const hor = " ".repeat(tabSize);
216
-
217
- this.opts.secs.forEach((sec) => {
218
- if (sec.level > this.opts.toc_level) return;
219
- let title = sec.title.replace(/_/g, " ");
220
- title = this.parse_all_commands(title, commands);
221
- const link = this.titleId(title);
222
-
223
- let __line =
224
- hor.repeat(Math.max(sec.level - 1, 0)) +
225
- beg +
226
- `[${title}](#${link})`;
227
-
228
- __blob.push(__line);
229
- });
230
- return __blob.join("\n");
231
- }
232
-
233
- add_hook(name: string, hook: () => string) {
234
- if (this.opts.hooks[name] != undefined)
235
- throw new Error(`Hook ${name} already exists!`);
236
- this.opts.hooks[name] = hook;
237
- }
238
-
239
- add_adv_hook(name: string, hook: (tree: HTMLElement) => HTMLElement) {
240
- if (this.opts.hooks[name] != undefined)
241
- throw new Error(`Hook ${name} already exists!`);
242
- this.opts.adv_hooks[name] = hook;
243
- }
244
-
245
- line_num_from_index(index: number) {
246
- return this.raw.substring(0, index).split("\n").length + 1;
247
- }
248
-
249
- remove_double_blank_lines(blob) {
250
- /* replace all triple newlines, and EOF by double newline */
251
- blob = blob.replace(/(\r\n|\n){3,}/g, "\n\n");
252
-
253
- return blob;
254
- }
255
-
256
- /* output the parsed document to bundle */
257
- to(bundleName: string, callback: (fileName: string) => void) {
258
- const dir = path.dirname(bundleName);
259
- if (callback === undefined) callback = () => {};
260
-
261
- if (!fs.existsSync(dir)) {
262
- fs.mkdirSync(dir, { recursive: true });
263
- }
264
-
265
- if (!this.opts.html) {
266
- this.get(TargetType.MARKDOWN, (blob) => {
267
- fs.writeFile(bundleName, blob, () => callback(bundleName));
268
- });
269
- } else {
270
- const htmlFileName = bundleName.replace(".md", ".html");
271
- fs.writeFile(htmlFileName, this.html(), () =>
272
- callback(htmlFileName)
273
- );
274
- }
275
- }
276
-
277
- html() {
278
- const htmlFormatted = marked(this.get(TargetType.HTML));
279
- if (this.opts.watch) {
280
- return (
281
- `<script>w=new WebSocket("ws:localhost:7788");w.addEventListener("message",(e)=>{if(e.data=="refresh")location.reload();});</script>\n` +
282
- htmlFormatted
283
- );
284
- }
285
- return htmlFormatted;
286
- }
287
-
288
- get(targetType?: TargetType, callback?: (blob: string) => void): string {
289
- /* If target type is undefined, markdown is the default */
290
- if (targetType === undefined) targetType = TargetType.MARKDOWN;
291
- if (this.blobs[targetType]) {
292
- if (callback) {
293
- callback(this.blobs[targetType]);
294
- }
295
- return this.blobs[targetType];
296
- } else {
297
- try {
298
- this.opts.targetType = targetType;
299
- let blob = this.parse();
300
- this.opts.targetType = undefined;
301
- if (callback) callback(blob);
302
- return blob;
303
- } catch (error) {
304
- /* Compile a traceback of error */
305
- let traceback = "";
306
- let p: Parser = this;
307
-
308
- do {
309
- if (error instanceof MDMError)
310
- traceback += `\n...on line ${p.line_num_from_index(
311
- error.match.index
312
- )} in ${p.file}`.grey(15);
313
- else
314
- traceback +=
315
- `\n...on line ${p.line_num} in ${p.file}`.grey(15);
316
- if (p.parent) p = p.parent;
317
- } while (p.parent);
318
-
319
- error.message += traceback;
320
-
321
- /* only interested in node stacktrace when debugging */
322
- if (!this.opts.debug) error.stack = "";
323
-
324
- if (this.opts.only_warn) console.error(error);
325
- else throw error;
326
- }
327
- }
328
- }
19
+ file: string;
20
+ parent?: Parser;
21
+ line_num: number;
22
+ wd: string;
23
+ wd_full: string;
24
+ blobs: {
25
+ [key: number]: string | undefined;
26
+ };
27
+ opts: ParserOptions;
28
+ raw: string;
29
+
30
+ static TOKEN = "#md";
31
+
32
+ constructor(
33
+ filename: string,
34
+ clargs?: IncompleteCommandLineArgs,
35
+ opts?: IncompleteParserOptions
36
+ ) {
37
+ /* this.working_directory */
38
+ this.file = filename;
39
+
40
+ this.line_num = 0;
41
+ this.wd = path.dirname(filename);
42
+ this.wd_full = path.resolve(this.wd);
43
+
44
+ /* finished blob */
45
+ this.blobs = {};
46
+
47
+ if (!clargs) {
48
+ clargs = argParser.parse_args([filename]);
49
+ }
50
+
51
+ /* get default options, and overwrite with the ones present
52
+ in the arguments */
53
+ this.opts = defaultParserOptions();
54
+ Object.assign(this.opts, clargs);
55
+ Object.assign(this.opts, opts);
56
+
57
+ this.raw = this.opts.isFileCallback(filename) || filename;
58
+ }
59
+
60
+ private parse() {
61
+ load_extensions(this);
62
+ if (this.opts.verbose || this.opts.debug) {
63
+ console.log(
64
+ `parsing ${this.file}: depth=${this.opts.depth}`.magenta
65
+ );
66
+ }
67
+
68
+ if (this.opts.debug) {
69
+ console.log("Parsing options:");
70
+ console.log(this.opts);
71
+ }
72
+
73
+ /* reset sections for beginning parse */
74
+ if (this.opts.depth === 0) this.opts.secs = [];
75
+ let __blob = this.raw;
76
+
77
+ /* apply preproccessing to raw file */
78
+ __blob = this.preprocess(__blob);
79
+
80
+ /* main parser instance call */
81
+ __blob = this.mainparse(__blob);
82
+
83
+ /**
84
+ * apply postprocessing after */
85
+ __blob = this.postprocess(__blob);
86
+
87
+ return __blob;
88
+ }
89
+
90
+ private mainparse(blob: string) {
91
+ if (this.opts.verbose || this.opts.debug) {
92
+ console.debug(`beginning mainparse of '${this.file}'`.blue);
93
+ }
94
+
95
+ /* main parser instance loop */
96
+ blob.split("\n").forEach((line, lnum) => {
97
+ this.line_num = lnum;
98
+
99
+ /* if line looks like a title */
100
+ const titleMatch = line.trim().match(/^(#+) (.+)$/);
101
+
102
+ if (titleMatch) {
103
+ if (this.opts.verbose || this.opts.debug)
104
+ console.log("found toc element: " + line);
105
+
106
+ /* implement toc level */
107
+ let level = titleMatch[1].length;
108
+ let title = titleMatch[2];
109
+
110
+ this.opts.secs.push({ level, title });
111
+
112
+ if (this.opts.debug) {
113
+ console.log("updated sections:", { level, title });
114
+ }
115
+ }
116
+ });
117
+
118
+ return this.parse_commands(blob, commands.parse);
119
+ }
120
+
121
+ private preprocess(blob: string) {
122
+ if (this.opts.verbose || this.opts.debug) {
123
+ console.debug(`beginning preprocess of '${this.file}'`.blue);
124
+ }
125
+
126
+ return this.parse_commands(blob, commands.preparse);
127
+ }
128
+
129
+ private postprocess(blob: string) {
130
+ if (this.opts.verbose || this.opts.debug) {
131
+ console.debug(`beginning postprocess of '${this.file}'`.blue);
132
+ }
133
+
134
+ blob = this.parse_commands(blob, commands.postparse);
135
+
136
+ /* remove double empty lines */
137
+ blob = this.remove_double_blank_lines(blob);
138
+ blob = blob.trimEnd() + "\n\n";
139
+ return blob;
140
+ }
141
+
142
+ private parse_commands(blob: string, commands: Command[]) {
143
+ commands.forEach((command) => {
144
+ /* Add global flag to RegExp */
145
+ const re = new RegExp(
146
+ command.validator.source,
147
+ (command.validator.flags || "") + "g"
148
+ );
149
+
150
+ const replacer = (args: RegExpExecArray) => {
151
+ try {
152
+ return command.act(args, this) || "";
153
+ } catch (error) {
154
+ switch (true) {
155
+ case error instanceof MDMError:
156
+ throw error;
157
+ case error instanceof MDMWarning:
158
+ console.warn(error.message);
159
+ return `**Warning: ${error.message}**`;
160
+ default:
161
+ console.error(error);
162
+ throw error;
163
+ }
164
+ }
165
+ };
166
+
167
+ /* */
168
+
169
+ let match: RegExpExecArray | null;
170
+ while ((match = re.exec(blob)) !== null) {
171
+ blob =
172
+ blob.slice(0, match.index) +
173
+ replacer(match) +
174
+ blob.slice(match.index + match[0].length);
175
+ }
176
+ });
177
+ return blob;
178
+ }
179
+
180
+ /* Parse all commands sequentially on a sub-blob */
181
+ private parse_all_commands(blob: string, commands: CommandGroupType) {
182
+ blob = this.parse_commands(blob, commands.preparse);
183
+ blob = this.parse_commands(blob, commands.parse);
184
+ blob = this.parse_commands(blob, commands.postparse);
185
+ return blob;
186
+ }
187
+
188
+ titleId(title: string) {
189
+ const sep = this.opts.use_underscore ? "_" : "-";
190
+
191
+ title = title
192
+ .toLowerCase()
193
+ .replace(/[^\w\s]+/g, "")
194
+ .replace(/[\s_]+/g, sep);
195
+ return title;
196
+ }
197
+
198
+ get_toc() {
199
+ let __blob = [];
200
+ let tabSize = 2;
201
+ const beg = "* ";
202
+ const hor = " ".repeat(tabSize);
203
+
204
+ this.opts.secs.forEach((sec) => {
205
+ if (sec.level > this.opts.toc_level) return;
206
+ let title = sec.title.replace(/_/g, " ");
207
+ title = this.parse_all_commands(title, commands);
208
+ const link = this.titleId(title);
209
+
210
+ let __line =
211
+ hor.repeat(Math.max(sec.level - 1, 0)) +
212
+ beg +
213
+ `[${title}](#${link})`;
214
+
215
+ __blob.push(__line);
216
+ });
217
+ return __blob.join("\n");
218
+ }
219
+
220
+ add_hook(
221
+ name: string,
222
+ hook: (map: { [key: string]: TaggedElement }) => void
223
+ ) {
224
+ if (this.opts.hooks[name] != undefined)
225
+ throw new MDMNonParserError(`Hook "${name}" already exists!`);
226
+ this.opts.hooks[name] = hook;
227
+ }
228
+
229
+ private line_num_from_index(index: number) {
230
+ return this.raw.substring(0, index).split("\n").length;
231
+ }
232
+
233
+ private remove_double_blank_lines(blob) {
234
+ /* replace all triple newlines, and EOF by double newline */
235
+ blob = blob.replace(/(\r\n|\n){3,}/g, "\n\n");
236
+
237
+ return blob;
238
+ }
239
+
240
+ /* output the parsed document to bundle */
241
+ to(bundleName: string, callback: (fileName: string) => void) {
242
+ const dir = path.dirname(bundleName);
243
+ if (callback === undefined) callback = () => {};
244
+
245
+ if (!fs.existsSync(dir)) {
246
+ fs.mkdirSync(dir, { recursive: true });
247
+ }
248
+
249
+ if (!this.opts.html) {
250
+ this.get(TargetType.MARKDOWN, (blob) => {
251
+ fs.writeFile(bundleName, blob, () => callback(bundleName));
252
+ });
253
+ } else {
254
+ const htmlFileName = bundleName.replace(".md", ".html");
255
+ fs.writeFile(htmlFileName, this.html(), () =>
256
+ callback(htmlFileName)
257
+ );
258
+ }
259
+ }
260
+
261
+ html() {
262
+ const htmlFormatted = marked
263
+ .parse(this.get(TargetType.HTML))
264
+ .toString();
265
+ if (this.opts.watch) {
266
+ return (
267
+ `<script>` +
268
+ `w=new WebSocket("ws:localhost:7788");` +
269
+ `w.addEventListener("message",(e)=>` +
270
+ ` {if(e.data=="refresh")location.reload();}` +
271
+ `);` +
272
+ `</script>\n` +
273
+ htmlFormatted
274
+ );
275
+ }
276
+ return htmlFormatted;
277
+ }
278
+
279
+ createChild(file: string) {
280
+ return new Parser(file, undefined, {
281
+ parent: this,
282
+ depth: this.opts.depth + 1,
283
+ ...this.opts,
284
+ });
285
+ }
286
+
287
+ get(targetType?: TargetType, callback?: (blob: string) => void): string {
288
+ /* If target type is undefined, markdown is the default */
289
+ if (targetType === undefined) targetType = TargetType.MARKDOWN;
290
+ if (this.blobs[targetType]) {
291
+ if (callback) {
292
+ callback(this.blobs[targetType]);
293
+ }
294
+ return this.blobs[targetType];
295
+ } else {
296
+ try {
297
+ this.opts.targetType = targetType;
298
+ let blob = this.parse();
299
+ this.opts.targetType = undefined;
300
+ if (callback) callback(blob);
301
+ return blob;
302
+ } catch (error) {
303
+ /* Compile a traceback of error */
304
+ let traceback = "";
305
+ let p: Parser = this;
306
+
307
+ do {
308
+ if (error instanceof MDMError)
309
+ traceback += `\n...on line ${p.line_num_from_index(
310
+ error.match.index
311
+ )} in ${p.file}`.grey(15);
312
+ else
313
+ traceback +=
314
+ `\n...on line ${p.line_num} in ${p.file}`.grey(15);
315
+ if (p.parent) p = p.parent;
316
+ } while (p.parent);
317
+
318
+ error.message += traceback;
319
+
320
+ /* only interested in node stacktrace when debugging */
321
+ if (!this.opts.debug) error.stack = "";
322
+
323
+ if (this.opts.only_warn) console.error(error);
324
+ else throw error;
325
+ }
326
+ }
327
+ }
328
+ }
329
+
330
+ function defaultParserOptions(): ParserOptions {
331
+ return {
332
+ defs: {},
333
+ secs: [],
334
+ args: [],
335
+ depth: 0,
336
+ verbose: false,
337
+ debug: false,
338
+ max_depth: 5,
339
+ use_underscore: false,
340
+ toc_level: 3,
341
+ allow_undefined: false,
342
+ html: false,
343
+ watch: false,
344
+ targetType: undefined,
345
+ only_warn: false,
346
+ parent: undefined,
347
+ hooks: {},
348
+ isFileCallback: (f) => {
349
+ if (!fs.existsSync(f)) return false;
350
+ return fs.readFileSync(f, "utf-8") + "\n";
351
+ },
352
+ };
329
353
  }
330
354
 
331
355
  export function splice(
332
- str: string,
333
- startIndex: number,
334
- width: number,
335
- newSubStr: string
356
+ str: string,
357
+ startIndex: number,
358
+ width: number,
359
+ newSubStr: string
336
360
  ) {
337
- const start = str.slice(0, startIndex);
338
- const end = str.slice(startIndex + width);
339
- return start + newSubStr + end;
361
+ const start = str.slice(0, startIndex);
362
+ const end = str.slice(startIndex + width);
363
+ return start + newSubStr + end;
340
364
  }
341
365
 
342
366
  /* add extention to marked for classed blockquotes*/
343
367
  marked.use({
344
- renderer: {
345
- blockquote(quote) {
346
- /* find the ending, and if not, return the default */
347
- const ending = quote.match(/\{(.+)\}\s*<\/p>/);
348
- if (!ending) return `<blockquote>${quote}</blockquote>`;
349
-
350
- const args = ending[1].split(" ");
351
-
352
- const classes = args.filter((arg) => arg.startsWith("."));
353
- const id = args.filter((arg) => arg.startsWith("#"));
354
-
355
- const classNames = classes.map((c) => c.slice(1));
356
- const classText =
357
- classes.length > 0 ? `class="${classNames.join(" ")}"` : "";
358
- const idText = id.length > 0 ? `id="${id[0].slice(1)}"` : "";
359
-
360
- /* remove the ending from the quote */
361
- quote = quote.replace(/\{(.+)\}\s*<\/p>/, "</p>");
362
-
363
- return `<blockquote ${classText} ${idText}>\n${quote.trim()}</blockquote>`;
364
- },
365
- },
368
+ renderer: {
369
+ blockquote(quote: string) {
370
+ /* find the ending, and if not, return the default */
371
+ const ending = quote.match(/\{(.+)\}\s*<\/p>/);
372
+ if (!ending) return `<blockquote>${quote}</blockquote>`;
373
+
374
+ const args = ending[1].split(" ");
375
+
376
+ const classes = args.filter((arg) => arg.startsWith("."));
377
+ const id = args.filter((arg) => arg.startsWith("#"));
378
+
379
+ const classNames = classes.map((c) => c.slice(1));
380
+ const classText =
381
+ classes.length > 0 ? `class="${classNames.join(" ")}"` : "";
382
+ const idText = id.length > 0 ? `id="${id[0].slice(1)}"` : "";
383
+
384
+ /* remove the ending from the quote */
385
+ quote = quote.replace(/\{(.+)\}\s*<\/p>/, "</p>");
386
+
387
+ return `<blockquote ${classText} ${idText}>\n${quote.trim()}</blockquote>`;
388
+ },
389
+ heading(text: string, level: number) {
390
+ /* add an id to each heading */
391
+ return `<h${level} id="${text
392
+ .replace(/ /g, "-")
393
+ .toLowerCase()}">${text}</h${level}>`;
394
+ },
395
+ },
366
396
  });
367
397
 
368
- module.exports = Parser;
369
-
370
398
  export default Parser;