markdown-maker 1.7.11 → 1.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,11 +3,7 @@
3
3
 
4
4
  name: Mocha
5
5
 
6
- on:
7
- push:
8
- branches: [ main ]
9
- pull_request:
10
- branches: [ main ]
6
+ on: [push, pull_request]
11
7
 
12
8
  jobs:
13
9
  build:
@@ -25,6 +21,15 @@ jobs:
25
21
  uses: actions/setup-node@v1
26
22
  with:
27
23
  node-version: ${{ matrix.node-version }}
28
- - run: npm ci
29
- - run: npm run build --if-present
30
- - run: npm test
24
+ - name: yarn install
25
+ uses: borales/actions-yarn@v3.0.0
26
+ with:
27
+ cmd: install # will run `yarn install` command
28
+ - name: yarn build
29
+ uses: borales/actions-yarn@v3.0.0
30
+ with:
31
+ cmd: build # will run `yarn build` command
32
+ - name: yarn test
33
+ uses: borales/actions-yarn@v3.0.0
34
+ with:
35
+ cmd: test # will run `yarn test` command
package/README.md CHANGED
@@ -14,6 +14,7 @@ Currently supports the following features
14
14
  * Automatic Table of Contents generation with `#mdmaketoc`
15
15
  * HTML emitting with custom styling
16
16
  * Easy extention of custom commands, see `src/commands.js` for implementations
17
+ * Usage of templates with the `#mdtemplate<...>` command.
17
18
 
18
19
  ## Usage
19
20
  Download the [latest release](https://github.com/blitzher/markdown-maker/releases), and write your document.
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "opts": {
3
3
  "src": "main.md",
4
- "use-underscore": true
4
+ "use-underscore": true,
5
+ "html": true
5
6
  }
6
- }
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markdown-maker",
3
- "version": "1.7.11",
3
+ "version": "1.9.1",
4
4
  "description": "",
5
5
  "main": "src/cltool.ts",
6
6
  "bin": {
@@ -13,8 +13,8 @@
13
13
  "node_modules/colors/*",
14
14
  "node_modules/colors./*",
15
15
  "node_modules/marked/*",
16
- "node_modules/open/*",
17
- "build/cltool.js"
16
+ "build/cltool.js",
17
+ "src/templates/*"
18
18
  ],
19
19
  "targets": [
20
20
  "node12-macos-x64",
@@ -26,7 +26,7 @@
26
26
  "test": "mocha",
27
27
  "test:yarn": "mocha",
28
28
  "bundle": "tsc --project tsconfig.json",
29
- "main": "node build/cltool.js document/main.md -o dist/bundle.md",
29
+ "main": "node build/cltool.js document/main.md -o dist/bundle.md --debug",
30
30
  "debug": "node src/cltool.js -db document/main.md -o dist/bundle.md",
31
31
  "build": "npm run bundle && pkg --output bin/mdparse .",
32
32
  "build:yarn": "yarn bundle && pkg --output bin/mdparse .",
@@ -46,18 +46,20 @@
46
46
  "@types/chokidar": "^2.1.3",
47
47
  "@types/colors": "^1.2.1",
48
48
  "@types/node": "^15.6.1",
49
+ "@types/ws": "^8.5.3",
49
50
  "argparse": "^2.0.1",
50
51
  "chokidar": "^3.5.1",
51
52
  "colors": "^1.4.0",
52
53
  "colors.ts": "^1.0.20",
53
54
  "marked": "^2.0.1",
54
- "open": "^8.0.5"
55
+ "require-runtime": "^2.0.0",
56
+ "ws": "^8.8.1"
55
57
  },
56
58
  "devDependencies": {
57
59
  "cloc": "^2.7.0",
58
60
  "mocha": "^8.3.1",
61
+ "pkg": "^4.4.9",
59
62
  "prettier": "^2.3.0",
60
- "typescript": "^4.3.2",
61
- "pkg": "^4.4.9"
63
+ "typescript": "^4.3.2"
62
64
  }
63
65
  }
package/prettierrc.yaml CHANGED
@@ -1,4 +1,4 @@
1
- trailingComma: es5
1
+ trailingComma: all
2
2
  tabWidth: 4
3
3
  singleQuote: true
4
4
  arrowParens: always
package/re-test.js ADDED
@@ -0,0 +1,10 @@
1
+ const s = "hello1, hello2, hello3";
2
+
3
+ const re = /hello(?<digit>\d)/g;
4
+
5
+ let m = s.replace(re, (match, ...args) => {
6
+ console.log(args);
7
+ return "world";
8
+ });
9
+
10
+ console.log({ s, m });
package/src/cltool.ts CHANGED
@@ -2,8 +2,8 @@ const fs = require("fs"); /* for handling reading of files */
2
2
  const path = require("path"); /* for handling file paths */
3
3
 
4
4
  import Colors = require("colors.ts"); /* for adding colours to strings */
5
- import { symlinkSync } from "fs";
6
5
  import Parser from "./parse";
6
+ import { WebSocketServer } from "ws";
7
7
 
8
8
  Colors.enable();
9
9
  const { ArgumentParser } = require("argparse"); /* for parsing clargs */
@@ -12,7 +12,7 @@ const choki = require("chokidar");
12
12
 
13
13
  export const argParser = new ArgumentParser({
14
14
  description: "Markdown bundler, with extra options",
15
- prog: process.argv[0].split(path.sep).pop(),
15
+ prog: "mdparse",
16
16
  });
17
17
 
18
18
  const configFileName = ".mdmconfig.json";
@@ -26,7 +26,7 @@ argParser.add_argument("-v", "--verbose", {
26
26
  action: "store_true",
27
27
  help: "enable verbose output",
28
28
  });
29
- argParser.add_argument("-db", "--debug", {
29
+ argParser.add_argument("-D", "--debug", {
30
30
  action: "store_true",
31
31
  help: "enable debugging information",
32
32
  });
@@ -47,40 +47,50 @@ argParser.add_argument("-w", "--watch", {
47
47
  action: "store_true",
48
48
  help: "recompile after a change in target target file or directory.",
49
49
  });
50
- argParser.add_argument("-uu", "--use-underscore", {
50
+ argParser.add_argument("-u", "--use-underscore", {
51
51
  action: "store_true",
52
52
  help: "set the parser to use '_' as seperator in ids for Table of Content. If the links in the table does not work, this is likely to be the issue.",
53
53
  });
54
- argParser.add_argument("--toc-level", {
54
+ argParser.add_argument("-t", "--toc-level", {
55
55
  help: "the section level of the table of contents, by default is 3",
56
56
  default: 3,
57
57
  type: "int",
58
58
  });
59
- argParser.add_argument("--html", {
59
+ argParser.add_argument("-H", "--html", {
60
60
  action: "store_true",
61
61
  help: "compile HTML from the parsed markdown",
62
62
  });
63
- argParser.add_argument("--allow-undef", "-au", {
63
+ argParser.add_argument("--allow-undefined", "-A", {
64
64
  action: "store_true",
65
- help: "allow undefined variables. Mostly useful for typing inline html tags, and other non-strictly markdown related uses",
65
+ help: "allow the use of the \"<thing>\" syntax, without raising an error when 'thing' is not a variable. Mostly useful when writing inline html tags, and other non-strictly markdown related uses",
66
66
  });
67
67
  //#endregion
68
68
 
69
69
  function main() {
70
- // var server: refreshServer | undefined;
71
- let clargs;
70
+ let clargs, fileargs;
71
+ let server: WebSocketServer | undefined;
72
+
73
+ /* Read config file or parse args from cmd-line */
72
74
  if (fs.existsSync(configFileName)) {
73
- let data = JSON.parse(fs.readFileSync(configFileName)).opts;
75
+ let data: { [key: string]: string | boolean | number } = JSON.parse(fs.readFileSync(configFileName)).opts;
74
76
 
75
- let args = [];
77
+ let args: (string | number)[] = [];
76
78
  Object.entries(data).forEach(([key, value]) => {
77
- if (key != "src") args.push("--" + key);
78
- if (typeof value != "boolean") args.push(value);
79
+ if (key != "src" && value !== false) {
80
+ args.push("--" + key);
81
+ }
82
+ if (typeof value != "boolean") {
83
+ args.push(value);
84
+ }
79
85
  });
80
- args.push(data.src);
81
86
 
82
- argParser.parse_args(args);
83
- } else clargs = argParser.parse_args();
87
+ /* We skip [0] and [1], as it is the binary and source file, even when compiled*/
88
+ for (let i = 2; i < process.argv.length; i++) args.push(process.argv[i]);
89
+
90
+ clargs = argParser.parse_args(args);
91
+ } else {
92
+ clargs = argParser.parse_args();
93
+ }
84
94
 
85
95
  /* helper method for calling parser */
86
96
  const compile = (source, output, cb?) => {
@@ -109,7 +119,10 @@ function main() {
109
119
 
110
120
  try {
111
121
  compile(clargs.src, clargs.output, () => {
112
- // if (server.refresh) server.refresh();
122
+ /* after compile, send refresh command to clients */
123
+ server.clients.forEach((client) => {
124
+ if (client.OPEN) client.send("refresh");
125
+ });
113
126
  });
114
127
  } catch (e) {
115
128
  console.log(e.message);
@@ -123,16 +136,16 @@ function main() {
123
136
  clargs.src = path.join(clargs.src, clargs.entry);
124
137
  }
125
138
 
126
- const srcDirName = path.dirname(clargs.src);
127
-
128
139
  if (clargs.debug) console.dir(clargs);
129
140
 
141
+ /* compile once */
130
142
  if (!clargs.watch) compile(clargs.src, clargs.output);
131
143
 
144
+ /* watch the folder and recompile on change */
132
145
  if (clargs.watch) {
133
- /* watch the folder of entry */
134
- // server = wsServer();
146
+ const srcDirName = path.dirname(clargs.src);
135
147
  console.log(`Watching ${srcDirName} for changes...`.yellow);
148
+ server = new WebSocketServer({ port: 7788 });
136
149
 
137
150
  const _watcher = choki.watch(srcDirName).on("all", watcher);
138
151
  try {
package/src/commands.ts CHANGED
@@ -1,35 +1,48 @@
1
1
  import * as path from "path";
2
2
  import Parser from "./parse";
3
+ import * as fs from "fs";
4
+ import templates, { new_template } from "./templates";
5
+ import requireRuntime from "require-runtime";
6
+
7
+ export class MDMError extends Error {
8
+ match: RegExpMatchArray;
9
+ constructor(message: string, match: RegExpMatchArray) {
10
+ super(message);
11
+ this.name = "MDMError";
12
+ this.match = match;
13
+ }
14
+ }
3
15
 
4
- const commands = {
16
+ export const commands: {
17
+ preparse: Command[];
18
+ parse: Command[];
19
+ postparse: Command[];
20
+ } = {
5
21
  preparse: [],
6
22
  parse: [],
7
23
  postparse: [],
8
24
  };
9
25
 
10
- const CommandType = {
11
- PREPARSE: 0,
12
- PARSE: 1,
13
- POSTPARSE: 2,
14
- };
26
+ export enum CommandType {
27
+ PREPARSE,
28
+ PARSE,
29
+ POSTPARSE,
30
+ }
15
31
 
16
- enum TargetType {
32
+ export enum TargetType {
17
33
  HTML,
18
34
  MARKDOWN,
19
35
  }
20
36
 
21
37
  export class Command {
22
- type: number;
23
- validator: (token: string, parser: Parser) => boolean | RegExpMatchArray;
24
- acter: (token: string, parser: Parser) => string | void;
38
+ validator: RegExp;
39
+ acter: (match: RegExpMatchArray, parser: Parser) => string | void;
40
+ type: CommandType;
25
41
 
26
42
  constructor(
27
- type,
28
- validator: (
29
- token: string,
30
- parser: Parser,
31
- ) => boolean | RegExpMatchArray,
32
- acter: (token: string, parser: Parser) => string | void,
43
+ validator: RegExp,
44
+ acter: (match: RegExpMatchArray, parser: Parser) => string | void,
45
+ type: CommandType,
33
46
  ) {
34
47
  this.type = type;
35
48
  this.validator = validator;
@@ -49,132 +62,191 @@ export class Command {
49
62
  }
50
63
  }
51
64
 
52
- valid(token, parser) {
53
- return this.validator(token, parser);
54
- }
55
-
56
- act(token, parser) {
57
- return this.acter(token, parser);
65
+ act(match, parser) {
66
+ return this.acter(match, parser);
58
67
  }
59
68
  }
60
69
 
61
70
  /* variable shorthand */
62
71
  new Command(
72
+ /(\s|^)<(.+)>/,
73
+ (match, parser) => `${match[1]}#mdvar<${match[2]}>`,
63
74
  CommandType.PREPARSE,
64
- (t, p) => t.match(/(?:\s|^)<\w+>/),
65
- (t, p) => `#mdvar` + t,
66
75
  );
67
76
 
68
77
  /* mddef */
69
78
  new Command(
70
- CommandType.PARSE,
71
- (t, p) => t.match(/^#mddef<(\w+)=(\w+)>/),
72
- (t, p) => {
73
- const m = t.match(/^#mddef<(\w+)=(\w+)>/);
74
- p.opts.defs[m[1]] = m[2];
79
+ /#mddef< *(.+?) *= *(.+?) *>/ /* first .+ is lazy so as to not match following spaces */,
80
+ (match, parser) => {
81
+ parser.opts.defs[match[1]] = match[2].replace("_", " ");
75
82
  },
83
+ CommandType.PARSE,
76
84
  );
77
85
 
78
86
  /* mdvar */
79
87
  new Command(
80
- CommandType.PARSE,
81
- (t, p) => t.match(/^#mdvar<(\w+)>/) || t.match(/^<(\w+)>/),
82
- (t, p) => {
83
- const match = t.match(/#mdvar<(\w+)>/);
84
- let value = p.opts.defs[match[1]];
85
- if (!value && !p.opts.allow_undef)
88
+ /#mdvar<(.+?)>/,
89
+ (match, parser) => {
90
+ let value = parser.opts.defs[match[1]];
91
+ if (!value && !parser.opts.allow_undefined)
86
92
  throw new Error(`Undefined variable: ${match[1]}`);
87
- value = value || `<${match[1]}>`;
88
- return t.replace(match[0], value.replace("_", " "));
93
+ return (value = value || `<${match[1]}>`);
89
94
  },
95
+ CommandType.PARSE,
90
96
  );
91
97
 
92
98
  /** mdinclude */
93
99
  new Command(
94
- CommandType.PARSE,
95
- (t, p) => t.match(/^#mdinclude<([\w.\/-]+)(?:[,\s]+([\w]+))?>/),
96
- (t, p) => {
100
+ /#mdinclude<([\w.\/-]+)(?:[,\s]+([\w]+))?>/,
101
+ (match, parser) => {
97
102
  /* increase the current recursive depth */
98
- p.opts.depth++;
103
+ parser.opts.depth++;
99
104
 
100
- if (p.opts.depth > p.opts.max_depth) {
105
+ if (parser.opts.depth > parser.opts.max_depth) {
101
106
  throw new Error("max depth exceeded!");
102
107
  }
103
108
 
104
109
  /* get the matching group */
105
- const match = t.match(/^#mdinclude<([\w.\/-]+)(?:[,\s]+([\w]+))?>/);
106
-
107
110
  const [_, name, condition] = match;
108
111
 
109
112
  /* implement conditional imports */
110
- if (condition && !p.opts.args.includes(condition)) return;
113
+ if (condition && !parser.opts.args.includes(condition)) return;
111
114
 
112
- const recursiveParser = new Parser(path.join(p.wd, name), p.opts, {
113
- parent: p,
114
- });
115
+ const recursiveParser = new Parser(
116
+ path.join(parser.wd, name),
117
+ parser.opts,
118
+ {
119
+ parent: parser,
120
+ },
121
+ );
115
122
 
116
123
  /* keep the options the same */
117
- recursiveParser.opts = p.opts;
118
- recursiveParser.parent = p;
124
+ recursiveParser.opts = parser.opts;
125
+ recursiveParser.parent = parser;
119
126
 
120
127
  const fileType = path.extname(recursiveParser.file);
121
128
 
122
129
  const blob =
123
130
  fileType === ".md"
124
- ? recursiveParser.get(p.opts.targetType)
131
+ ? recursiveParser.get(parser.opts.targetType)
125
132
  : recursiveParser.raw;
126
133
 
127
- p.opts.depth--;
134
+ parser.opts.depth--;
128
135
  return blob;
129
136
  },
137
+ CommandType.PARSE,
130
138
  );
131
139
 
140
+ /* mdlabel */
132
141
  new Command(
133
- CommandType.PREPARSE,
134
- (t, p) => t.match(/#mdlabel<(\d+),([\w\W]+)>/),
135
- (t, p) => {
136
- if (p.opts.targetType !== TargetType.HTML) return;
142
+ /#mdlabel<(\d+),\s?(.+)>/,
143
+ (match, parser) => {
144
+ if (parser.opts.targetType !== TargetType.HTML) return "";
137
145
 
138
- const match = t.match(/#mdlabel<([\d]+),([\w\W]+)>/);
139
146
  const level = Number.parseInt(match[1]);
140
147
  const title = match[2];
141
- const link = p.titleId(title);
142
- p.opts.secs.push({ level, title });
148
+ const link = parser.titleId(title);
149
+ parser.opts.secs.push({ level, title });
143
150
  return `<span id="${link}"></span>`;
144
151
  },
152
+ CommandType.PREPARSE,
145
153
  );
146
154
 
147
155
  /* mdref */
148
156
  new Command(
149
- CommandType.PARSE,
150
- (t, p) => t.match(/#mdref<([\w\W]+)>/),
157
+ /#mdref<(.+)>/,
151
158
 
152
- (t, p) => {
153
- const match = t.match(/#mdref<([\w\W]+)>/);
154
-
155
- for (let i = 0; i < p.opts.secs.length; i++) {
156
- let { title } = p.opts.secs[i];
159
+ (match, parser) => {
160
+ for (let i = 0; i < parser.opts.secs.length; i++) {
161
+ let { title } = parser.opts.secs[i];
157
162
  if (title === match[1]) break;
158
163
 
159
- if (i === p.opts.secs.length - 1)
164
+ if (i === parser.opts.secs.length - 1)
160
165
  throw new Error(
161
166
  `Reference to [${match[1]}] could not be resolved!`,
162
167
  );
163
168
  }
164
169
 
165
170
  match[1] = match[1].replace("_", " ");
166
- const link = p.titleId(match[1]);
167
- if (p.opts.targetType === TargetType.HTML)
171
+ const link = parser.titleId(match[1]);
172
+ if (parser.opts.targetType === TargetType.HTML)
168
173
  return `<a href="#${link}">${match[1]}</a>`;
169
- else if (p.opts.targetType === TargetType.MARKDOWN)
174
+ else if (parser.opts.targetType === TargetType.MARKDOWN)
170
175
  return `[${match[1]}](#${link})`;
171
176
  },
177
+ CommandType.PARSE,
178
+ );
179
+
180
+ /* mdtemplate */
181
+ new Command(
182
+ /#mdtemplate<([\w\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,
172
194
  );
173
195
 
174
196
  new Command(
197
+ /#mdmaketoc(?:<>)?/,
198
+ (match, parser) => parser.gen_toc(),
175
199
  CommandType.POSTPARSE,
176
- (t, p) => t.match(/#mdmaketoc/),
177
- (t, p) => p.gen_toc(),
178
200
  );
179
201
 
180
- module.exports = commands;
202
+ export function load_extensions(parser: Parser) {
203
+ /* global extention */
204
+ const global_extensions_path = path.join(process.cwd(), "extensions.js");
205
+ if (fs.existsSync(global_extensions_path)) {
206
+ const extensions = requireRuntime(global_extensions_path);
207
+ extensions.main(new_template, new_command);
208
+
209
+ if (parser.opts.verbose)
210
+ console.log(
211
+ `Loaded global extensions from ${global_extensions_path}`
212
+ .yellow,
213
+ );
214
+ } else if (parser.opts.debug) {
215
+ console.log(
216
+ `No global extensions found at ${global_extensions_path}`.red,
217
+ );
218
+ }
219
+
220
+ /* project extention */
221
+ const project_extensions_path = path.join(parser.wd_full, "extensions.js");
222
+ if (fs.existsSync(project_extensions_path)) {
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
+ }
236
+ }
237
+
238
+ /**
239
+ *
240
+ * @param regex The regex to match the command
241
+ * @param acter The function called when a match is found. Takes two arguments, `match` and `parser`. `match` is the result of the regex match, and `parser` is the parser instance. The function should return the replacement string.
242
+ * @param type When the command should be run. Can be `CommandType.PREPARSE`, `CommandType.PARSE`, or `CommandType.POSTPARSE`. Defaults to `CommandType.PARSE`.
243
+ */
244
+ export function new_command(
245
+ regex: RegExp,
246
+ acter: (match: RegExpMatchArray, parser: Parser) => string,
247
+ type?: CommandType,
248
+ ) {
249
+ new Command(regex, acter, type || CommandType.PARSE);
250
+ }
251
+
252
+ export default { commands, load_extensions };