markdown-maker 1.7.10 → 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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "opts": {
3
+ "src": "main.md",
4
+ "use-underscore": true,
5
+ "html": true
6
+ }
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markdown-maker",
3
- "version": "1.7.10",
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
@@ -3,6 +3,7 @@ const path = require("path"); /* for handling file paths */
3
3
 
4
4
  import Colors = require("colors.ts"); /* for adding colours to strings */
5
5
  import Parser from "./parse";
6
+ import { WebSocketServer } from "ws";
6
7
 
7
8
  Colors.enable();
8
9
  const { ArgumentParser } = require("argparse"); /* for parsing clargs */
@@ -11,8 +12,11 @@ const choki = require("chokidar");
11
12
 
12
13
  export const argParser = new ArgumentParser({
13
14
  description: "Markdown bundler, with extra options",
15
+ prog: "mdparse",
14
16
  });
15
17
 
18
+ const configFileName = ".mdmconfig.json";
19
+
16
20
  //#region command line args
17
21
  argParser.add_argument("src", {
18
22
  help: "file to be parsed. If this is a directory, it looks for entry point in the directory, see --entry",
@@ -22,7 +26,7 @@ argParser.add_argument("-v", "--verbose", {
22
26
  action: "store_true",
23
27
  help: "enable verbose output",
24
28
  });
25
- argParser.add_argument("-db", "--debug", {
29
+ argParser.add_argument("-D", "--debug", {
26
30
  action: "store_true",
27
31
  help: "enable debugging information",
28
32
  });
@@ -43,28 +47,50 @@ argParser.add_argument("-w", "--watch", {
43
47
  action: "store_true",
44
48
  help: "recompile after a change in target target file or directory.",
45
49
  });
46
- argParser.add_argument("-uu", "--use-underscore", {
50
+ argParser.add_argument("-u", "--use-underscore", {
47
51
  action: "store_true",
48
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.",
49
53
  });
50
- argParser.add_argument("--toc-level", {
54
+ argParser.add_argument("-t", "--toc-level", {
51
55
  help: "the section level of the table of contents, by default is 3",
52
56
  default: 3,
53
57
  type: "int",
54
58
  });
55
- argParser.add_argument("--html", {
59
+ argParser.add_argument("-H", "--html", {
56
60
  action: "store_true",
57
61
  help: "compile HTML from the parsed markdown",
58
62
  });
59
- argParser.add_argument("--allow-undef", "-au", {
63
+ argParser.add_argument("--allow-undefined", "-A", {
60
64
  action: "store_true",
61
- 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",
62
66
  });
63
67
  //#endregion
64
68
 
65
69
  function main() {
66
- // var server: refreshServer | undefined;
67
- const clargs = argParser.parse_args();
70
+ let clargs, fileargs;
71
+ let server: WebSocketServer | undefined;
72
+
73
+ /* Read config file or parse args from cmd-line */
74
+ if (fs.existsSync(configFileName)) {
75
+ let data: { [key: string]: string | boolean | number } = JSON.parse(fs.readFileSync(configFileName)).opts;
76
+
77
+ let args: (string | number)[] = [];
78
+ Object.entries(data).forEach(([key, value]) => {
79
+ if (key != "src" && value !== false) {
80
+ args.push("--" + key);
81
+ }
82
+ if (typeof value != "boolean") {
83
+ args.push(value);
84
+ }
85
+ });
86
+
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
+ }
68
94
 
69
95
  /* helper method for calling parser */
70
96
  const compile = (source, output, cb?) => {
@@ -93,7 +119,10 @@ function main() {
93
119
 
94
120
  try {
95
121
  compile(clargs.src, clargs.output, () => {
96
- // 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
+ });
97
126
  });
98
127
  } catch (e) {
99
128
  console.log(e.message);
@@ -107,16 +136,16 @@ function main() {
107
136
  clargs.src = path.join(clargs.src, clargs.entry);
108
137
  }
109
138
 
110
- const srcDirName = path.dirname(clargs.src);
111
-
112
139
  if (clargs.debug) console.dir(clargs);
113
140
 
141
+ /* compile once */
114
142
  if (!clargs.watch) compile(clargs.src, clargs.output);
115
143
 
144
+ /* watch the folder and recompile on change */
116
145
  if (clargs.watch) {
117
- /* watch the folder of entry */
118
- // server = wsServer();
146
+ const srcDirName = path.dirname(clargs.src);
119
147
  console.log(`Watching ${srcDirName} for changes...`.yellow);
148
+ server = new WebSocketServer({ port: 7788 });
120
149
 
121
150
  const _watcher = choki.watch(srcDirName).on("all", watcher);
122
151
  try {
package/src/commands.ts CHANGED
@@ -1,36 +1,48 @@
1
1
  import * as path from "path";
2
- import { title } from "process";
3
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
+ }
4
15
 
5
- const commands = {
16
+ export const commands: {
17
+ preparse: Command[];
18
+ parse: Command[];
19
+ postparse: Command[];
20
+ } = {
6
21
  preparse: [],
7
22
  parse: [],
8
23
  postparse: [],
9
24
  };
10
25
 
11
- const CommandType = {
12
- PREPARSE: 0,
13
- PARSE: 1,
14
- POSTPARSE: 2,
15
- };
26
+ export enum CommandType {
27
+ PREPARSE,
28
+ PARSE,
29
+ POSTPARSE,
30
+ }
16
31
 
17
- enum TargetType {
32
+ export enum TargetType {
18
33
  HTML,
19
34
  MARKDOWN,
20
35
  }
21
36
 
22
- class Command {
23
- type: number;
24
- validator: (token: string, parser: Parser) => boolean | RegExpMatchArray;
25
- acter: (token: string, parser: Parser) => string | void;
37
+ export class Command {
38
+ validator: RegExp;
39
+ acter: (match: RegExpMatchArray, parser: Parser) => string | void;
40
+ type: CommandType;
26
41
 
27
42
  constructor(
28
- type,
29
- validator: (
30
- token: string,
31
- parser: Parser
32
- ) => boolean | RegExpMatchArray,
33
- acter: (token: string, parser: Parser) => string | void
43
+ validator: RegExp,
44
+ acter: (match: RegExpMatchArray, parser: Parser) => string | void,
45
+ type: CommandType,
34
46
  ) {
35
47
  this.type = type;
36
48
  this.validator = validator;
@@ -50,131 +62,191 @@ class Command {
50
62
  }
51
63
  }
52
64
 
53
- valid(token, parser) {
54
- return this.validator(token, parser);
55
- }
56
-
57
- act(token, parser) {
58
- return this.acter(token, parser);
65
+ act(match, parser) {
66
+ return this.acter(match, parser);
59
67
  }
60
68
  }
61
69
 
62
70
  /* variable shorthand */
63
71
  new Command(
72
+ /(\s|^)<(.+)>/,
73
+ (match, parser) => `${match[1]}#mdvar<${match[2]}>`,
64
74
  CommandType.PREPARSE,
65
- (t, p) => t.match(/(?:\s|^)<\w+>/),
66
- (t, p) => `#mdvar` + t
67
75
  );
68
76
 
69
77
  /* mddef */
70
78
  new Command(
79
+ /#mddef< *(.+?) *= *(.+?) *>/ /* first .+ is lazy so as to not match following spaces */,
80
+ (match, parser) => {
81
+ parser.opts.defs[match[1]] = match[2].replace("_", " ");
82
+ },
71
83
  CommandType.PARSE,
72
- (t, p) => t.match(/^#mddef<(\w+)=(\w+)>/),
73
- (t, p) => {
74
- const m = t.match(/^#mddef<(\w+)=(\w+)>/);
75
- p.opts.defs[m[1]] = m[2];
76
- }
77
84
  );
78
85
 
79
86
  /* mdvar */
80
87
  new Command(
81
- CommandType.PARSE,
82
- (t, p) => t.match(/^#mdvar<(\w+)>/) || t.match(/^<(\w+)>/),
83
- (t, p) => {
84
- const match = t.match(/#mdvar<(\w+)>/);
85
- let value = p.opts.defs[match[1]];
86
- 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)
87
92
  throw new Error(`Undefined variable: ${match[1]}`);
88
- value = value || `<${match[1]}>`;
89
- return t.replace(match[0], value.replace("_", " "));
90
- }
93
+ return (value = value || `<${match[1]}>`);
94
+ },
95
+ CommandType.PARSE,
91
96
  );
92
97
 
93
98
  /** mdinclude */
94
99
  new Command(
95
- CommandType.PARSE,
96
- (t, p) => t.match(/^#mdinclude<([\w.\/-]+)(?:[,\s]+([\w]+))?>/),
97
- (t, p) => {
100
+ /#mdinclude<([\w.\/-]+)(?:[,\s]+([\w]+))?>/,
101
+ (match, parser) => {
98
102
  /* increase the current recursive depth */
99
- p.opts.depth++;
103
+ parser.opts.depth++;
100
104
 
101
- if (p.opts.depth > p.opts.max_depth) {
105
+ if (parser.opts.depth > parser.opts.max_depth) {
102
106
  throw new Error("max depth exceeded!");
103
107
  }
104
108
 
105
109
  /* get the matching group */
106
- const match = t.match(/^#mdinclude<([\w.\/-]+)(?:[,\s]+([\w]+))?>/);
107
-
108
110
  const [_, name, condition] = match;
109
111
 
110
112
  /* implement conditional imports */
111
- if (condition && !p.opts.args.includes(condition)) return;
113
+ if (condition && !parser.opts.args.includes(condition)) return;
112
114
 
113
- const recursiveParser = new Parser(path.join(p.wd, name), p.opts, {
114
- parent: p,
115
- });
115
+ const recursiveParser = new Parser(
116
+ path.join(parser.wd, name),
117
+ parser.opts,
118
+ {
119
+ parent: parser,
120
+ },
121
+ );
116
122
 
117
123
  /* keep the options the same */
118
- recursiveParser.opts = p.opts;
119
- recursiveParser.parent = p;
124
+ recursiveParser.opts = parser.opts;
125
+ recursiveParser.parent = parser;
120
126
 
121
127
  const fileType = path.extname(recursiveParser.file);
122
128
 
123
129
  const blob =
124
130
  fileType === ".md"
125
- ? recursiveParser.get(p.opts.targetType)
131
+ ? recursiveParser.get(parser.opts.targetType)
126
132
  : recursiveParser.raw;
127
133
 
128
- p.opts.depth--;
134
+ parser.opts.depth--;
129
135
  return blob;
130
- }
136
+ },
137
+ CommandType.PARSE,
131
138
  );
132
139
 
140
+ /* mdlabel */
133
141
  new Command(
134
- CommandType.PREPARSE,
135
- (t, p) => t.match(/#mdlabel<(\d+),([\w\W]+)>/),
136
- (t, p) => {
137
- if (p.opts.targetType !== TargetType.HTML) return;
142
+ /#mdlabel<(\d+),\s?(.+)>/,
143
+ (match, parser) => {
144
+ if (parser.opts.targetType !== TargetType.HTML) return "";
138
145
 
139
- const match = t.match(/#mdlabel<([\d]+),([\w\W]+)>/);
140
146
  const level = Number.parseInt(match[1]);
141
147
  const title = match[2];
142
- const link = p.titleId(title);
143
- p.opts.secs.push({ level, title });
148
+ const link = parser.titleId(title);
149
+ parser.opts.secs.push({ level, title });
144
150
  return `<span id="${link}"></span>`;
145
- }
151
+ },
152
+ CommandType.PREPARSE,
146
153
  );
147
154
 
155
+ /* mdref */
148
156
  new Command(
149
- CommandType.PARSE,
150
- (t, p) => t.match(/#mdref<([\w\W]+)>/),
151
-
152
- (t, p) => {
153
- const match = t.match(/#mdref<([\w\W]+)>/);
157
+ /#mdref<(.+)>/,
154
158
 
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
- `Reference to [${match[1]}] could not be resolved!`
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 };