@rokelamen/md2html 0.1.2 → 0.1.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/README.md +4 -2
- package/bin/cli.cjs +107 -7
- package/dist/index.js +103 -3
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
A simple markdown-html conventer written in Typescript.
|
|
4
4
|
|
|
5
|
+
> I create this project for learning TS and node dev, not for the purpose of building another better markdown parse engine.
|
|
6
|
+
|
|
5
7
|
## Development Log
|
|
6
8
|
|
|
7
|
-
Why I choose to use `rollup
|
|
9
|
+
Why I choose to use [`rollup`](https://rollupjs.org/)?
|
|
8
10
|
|
|
9
|
-
A:
|
|
11
|
+
A: Every time I `import` a module, I have to add extension to the module file and it is probably a `.js` ranther than `.ts`. It's wired that I must `import` a future JS file. So I decided to use a build tool(`rollup` of course) to pack all files together, which eliminates all `import` statements.
|
|
10
12
|
|
package/bin/cli.cjs
CHANGED
|
@@ -4336,16 +4336,116 @@ const {
|
|
|
4336
4336
|
Help,
|
|
4337
4337
|
} = commander;
|
|
4338
4338
|
|
|
4339
|
+
const headerReg = /^\s*(#{1,6})(?:\s+|$)(.*)$/;
|
|
4340
|
+
|
|
4341
|
+
/**
|
|
4342
|
+
* Since AST-based parsing is too complex and not
|
|
4343
|
+
* suitable for a personal typescript learning project,
|
|
4344
|
+
* So I just let my markdown rendering engine to
|
|
4345
|
+
* receive a line and analysis which type it is,
|
|
4346
|
+
* and then output the corresponding format(with tags).
|
|
4347
|
+
* That means it's just a simple line-by-line parsing.
|
|
4348
|
+
*
|
|
4349
|
+
* Some special scenes still need to be handled carefully:
|
|
4350
|
+
* plain text -> <p>plain text</p>
|
|
4351
|
+
* need to consider whether there is a following empty line.
|
|
4352
|
+
* Cause
|
|
4353
|
+
* ```
|
|
4354
|
+
* plain text 1
|
|
4355
|
+
* plain text 2
|
|
4356
|
+
* ```
|
|
4357
|
+
* is one paragraph NOT two.
|
|
4358
|
+
* The same happens with quote text:
|
|
4359
|
+
* ```
|
|
4360
|
+
* > quote text
|
|
4361
|
+
* plain text
|
|
4362
|
+
* ```
|
|
4363
|
+
* is one paragraph as well.
|
|
4364
|
+
*/
|
|
4339
4365
|
/* The main parse logic */
|
|
4340
|
-
function parse(
|
|
4341
|
-
|
|
4342
|
-
|
|
4366
|
+
function parse(markdown) {
|
|
4367
|
+
/* Split markdown content to many lines */
|
|
4368
|
+
const crlfReg = /\r?\n/;
|
|
4369
|
+
const lines = markdown.split(crlfReg);
|
|
4370
|
+
// console.log(lines);
|
|
4371
|
+
const mdBlocks = parseToBlocks(lines);
|
|
4372
|
+
// console.log(mdBlocks);
|
|
4373
|
+
const html = handleTags(mdBlocks);
|
|
4374
|
+
return html;
|
|
4343
4375
|
}
|
|
4376
|
+
/**
|
|
4377
|
+
* Traverse lines to turn to blocks with different types
|
|
4378
|
+
*/
|
|
4379
|
+
function parseToBlocks(lines) {
|
|
4380
|
+
let lastTextQuoteBlock = { content: '', type: 'text' };
|
|
4381
|
+
let pushed = true;
|
|
4382
|
+
const mdBlocks = [];
|
|
4383
|
+
for (const line of lines) {
|
|
4384
|
+
// Empty line
|
|
4385
|
+
if (!Boolean(line.trim())) {
|
|
4386
|
+
if (!pushed) {
|
|
4387
|
+
mdBlocks.push(lastTextQuoteBlock);
|
|
4388
|
+
pushed = true;
|
|
4389
|
+
}
|
|
4390
|
+
continue;
|
|
4391
|
+
}
|
|
4392
|
+
// Header
|
|
4393
|
+
const headerM = line.match(headerReg);
|
|
4394
|
+
if (headerM) {
|
|
4395
|
+
mdBlocks.push({
|
|
4396
|
+
type: 'header',
|
|
4397
|
+
level: headerM[1].length,
|
|
4398
|
+
content: headerM[2].trim()
|
|
4399
|
+
});
|
|
4400
|
+
continue;
|
|
4401
|
+
}
|
|
4402
|
+
// Fall back to plain text
|
|
4403
|
+
if (!pushed) {
|
|
4404
|
+
/* last line is also text */
|
|
4405
|
+
lastTextQuoteBlock.content += ' ' + line.trim();
|
|
4406
|
+
}
|
|
4407
|
+
else {
|
|
4408
|
+
lastTextQuoteBlock = {
|
|
4409
|
+
type: 'text',
|
|
4410
|
+
content: line.trim()
|
|
4411
|
+
};
|
|
4412
|
+
pushed = false;
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4415
|
+
// Avoid the last block is omitted
|
|
4416
|
+
if (!pushed) {
|
|
4417
|
+
mdBlocks.push(lastTextQuoteBlock);
|
|
4418
|
+
pushed = true;
|
|
4419
|
+
}
|
|
4420
|
+
return mdBlocks;
|
|
4421
|
+
}
|
|
4422
|
+
/* traverse markdown content blocks and wrap text with tags at proper positions. */
|
|
4423
|
+
function handleTags(mdBlocks) {
|
|
4424
|
+
let result = '';
|
|
4425
|
+
for (const block of mdBlocks) {
|
|
4426
|
+
const type = block.type;
|
|
4427
|
+
const content = tagSwtich(block);
|
|
4428
|
+
switch (type) {
|
|
4429
|
+
case "text":
|
|
4430
|
+
result += `<p>${content}</p>` +
|
|
4431
|
+
'\n';
|
|
4432
|
+
break;
|
|
4433
|
+
case "header":
|
|
4434
|
+
result += `<h${block.level}>${content}</h${block.level}>` +
|
|
4435
|
+
'\n';
|
|
4436
|
+
break;
|
|
4437
|
+
}
|
|
4438
|
+
}
|
|
4439
|
+
return result;
|
|
4440
|
+
}
|
|
4441
|
+
function tagSwtich(block) {
|
|
4442
|
+
return block.content;
|
|
4443
|
+
}
|
|
4444
|
+
|
|
4445
|
+
var name = "@rokelamen/md2html";
|
|
4446
|
+
var version = "0.1.3";
|
|
4447
|
+
var description = "A simple tool to convert markdown content to html";
|
|
4344
4448
|
|
|
4345
|
-
// Import these from package.json in the future
|
|
4346
|
-
const name = 'md2html';
|
|
4347
|
-
const version = '0.1.2';
|
|
4348
|
-
const description = 'A simple tool to convert markdown content to html';
|
|
4349
4449
|
/* Command-line tool logic */
|
|
4350
4450
|
function command() {
|
|
4351
4451
|
/* Set basic info */
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,107 @@
|
|
|
1
|
+
const headerReg = /^\s*(#{1,6})(?:\s+|$)(.*)$/;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Since AST-based parsing is too complex and not
|
|
5
|
+
* suitable for a personal typescript learning project,
|
|
6
|
+
* So I just let my markdown rendering engine to
|
|
7
|
+
* receive a line and analysis which type it is,
|
|
8
|
+
* and then output the corresponding format(with tags).
|
|
9
|
+
* That means it's just a simple line-by-line parsing.
|
|
10
|
+
*
|
|
11
|
+
* Some special scenes still need to be handled carefully:
|
|
12
|
+
* plain text -> <p>plain text</p>
|
|
13
|
+
* need to consider whether there is a following empty line.
|
|
14
|
+
* Cause
|
|
15
|
+
* ```
|
|
16
|
+
* plain text 1
|
|
17
|
+
* plain text 2
|
|
18
|
+
* ```
|
|
19
|
+
* is one paragraph NOT two.
|
|
20
|
+
* The same happens with quote text:
|
|
21
|
+
* ```
|
|
22
|
+
* > quote text
|
|
23
|
+
* plain text
|
|
24
|
+
* ```
|
|
25
|
+
* is one paragraph as well.
|
|
26
|
+
*/
|
|
1
27
|
/* The main parse logic */
|
|
2
|
-
function parse(
|
|
3
|
-
|
|
4
|
-
|
|
28
|
+
function parse(markdown) {
|
|
29
|
+
/* Split markdown content to many lines */
|
|
30
|
+
const crlfReg = /\r?\n/;
|
|
31
|
+
const lines = markdown.split(crlfReg);
|
|
32
|
+
// console.log(lines);
|
|
33
|
+
const mdBlocks = parseToBlocks(lines);
|
|
34
|
+
// console.log(mdBlocks);
|
|
35
|
+
const html = handleTags(mdBlocks);
|
|
36
|
+
return html;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Traverse lines to turn to blocks with different types
|
|
40
|
+
*/
|
|
41
|
+
function parseToBlocks(lines) {
|
|
42
|
+
let lastTextQuoteBlock = { content: '', type: 'text' };
|
|
43
|
+
let pushed = true;
|
|
44
|
+
const mdBlocks = [];
|
|
45
|
+
for (const line of lines) {
|
|
46
|
+
// Empty line
|
|
47
|
+
if (!Boolean(line.trim())) {
|
|
48
|
+
if (!pushed) {
|
|
49
|
+
mdBlocks.push(lastTextQuoteBlock);
|
|
50
|
+
pushed = true;
|
|
51
|
+
}
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
// Header
|
|
55
|
+
const headerM = line.match(headerReg);
|
|
56
|
+
if (headerM) {
|
|
57
|
+
mdBlocks.push({
|
|
58
|
+
type: 'header',
|
|
59
|
+
level: headerM[1].length,
|
|
60
|
+
content: headerM[2].trim()
|
|
61
|
+
});
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
// Fall back to plain text
|
|
65
|
+
if (!pushed) {
|
|
66
|
+
/* last line is also text */
|
|
67
|
+
lastTextQuoteBlock.content += ' ' + line.trim();
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
lastTextQuoteBlock = {
|
|
71
|
+
type: 'text',
|
|
72
|
+
content: line.trim()
|
|
73
|
+
};
|
|
74
|
+
pushed = false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Avoid the last block is omitted
|
|
78
|
+
if (!pushed) {
|
|
79
|
+
mdBlocks.push(lastTextQuoteBlock);
|
|
80
|
+
pushed = true;
|
|
81
|
+
}
|
|
82
|
+
return mdBlocks;
|
|
83
|
+
}
|
|
84
|
+
/* traverse markdown content blocks and wrap text with tags at proper positions. */
|
|
85
|
+
function handleTags(mdBlocks) {
|
|
86
|
+
let result = '';
|
|
87
|
+
for (const block of mdBlocks) {
|
|
88
|
+
const type = block.type;
|
|
89
|
+
const content = tagSwtich(block);
|
|
90
|
+
switch (type) {
|
|
91
|
+
case "text":
|
|
92
|
+
result += `<p>${content}</p>` +
|
|
93
|
+
'\n';
|
|
94
|
+
break;
|
|
95
|
+
case "header":
|
|
96
|
+
result += `<h${block.level}>${content}</h${block.level}>` +
|
|
97
|
+
'\n';
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
function tagSwtich(block) {
|
|
104
|
+
return block.content;
|
|
5
105
|
}
|
|
6
106
|
|
|
7
107
|
export { parse };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rokelamen/md2html",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.3",
|
|
5
5
|
"description": "A simple tool to convert markdown content to html",
|
|
6
6
|
"author": "rokelamen <rogerskelamen@gmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
],
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@rollup/plugin-commonjs": "^29.0.0",
|
|
35
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
35
36
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
36
37
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
37
38
|
"@types/commander": "^2.12.0",
|