pantsdown 1.0.1

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/.eslintrc.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "root": true,
3
+ "parser": "@typescript-eslint/parser",
4
+ "parserOptions": {
5
+ "ecmaVersion": "latest",
6
+ "sourceType": "module",
7
+ "project": ["tsconfig.json"]
8
+ },
9
+ "extends": [
10
+ "eslint:recommended",
11
+ "plugin:@typescript-eslint/strict-type-checked",
12
+ "plugin:@typescript-eslint/stylistic-type-checked"
13
+ ],
14
+ "rules": {
15
+ "@typescript-eslint/no-non-null-assertion": "off",
16
+ "@typescript-eslint/consistent-type-definitions": ["error", "type"],
17
+ "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
18
+ "@typescript-eslint/consistent-type-imports": [
19
+ "warn",
20
+ { "prefer": "type-imports", "fixStyle": "inline-type-imports" }
21
+ ],
22
+ "no-useless-escape": "off",
23
+ "no-control-regex": "off",
24
+ "no-cond-assign": "off"
25
+ }
26
+ }
@@ -0,0 +1,44 @@
1
+ name: Release
2
+ on:
3
+ push:
4
+ branches:
5
+ - main
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ Test:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - name: Checkout repo
15
+ uses: actions/checkout@v4
16
+ - name: Install bun
17
+ uses: oven-sh/setup-bun@v1
18
+ - name: Install dependencies
19
+ run: bun install
20
+ - name: Lint
21
+ run: bun run lint
22
+ - name: Test
23
+ run: bun test
24
+
25
+ Release:
26
+ permissions:
27
+ contents: write
28
+ needs: [Test]
29
+ if: |
30
+ github.ref == 'refs/heads/main' &&
31
+ github.event.repository.fork == false
32
+ runs-on: ubuntu-latest
33
+ steps:
34
+ - name: Checkout Code
35
+ uses: actions/checkout@v4
36
+ - name: Install bun
37
+ uses: oven-sh/setup-bun@v1
38
+ - name: Install dependencies
39
+ run: bun install
40
+ - name: Release
41
+ env:
42
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
43
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
44
+ run: bun run semantic-release
@@ -0,0 +1,11 @@
1
+ {
2
+ "tabWidth": 4,
3
+ "printWidth": 100,
4
+ "plugins": ["prettier-plugin-organize-imports"],
5
+ "overrides": [
6
+ {
7
+ "files": ["*.json"],
8
+ "options": { "tabWidth": 2 }
9
+ }
10
+ ]
11
+ }
package/LICENSE.md ADDED
@@ -0,0 +1,67 @@
1
+ # License information
2
+
3
+ ## Contribution License Agreement
4
+
5
+ If you contribute code to this project, you are implicitly allowing your code
6
+ to be distributed under the MIT license. You are also implicitly verifying that
7
+ all code is your original work.
8
+
9
+ ## Pantsdown
10
+
11
+ Copyright (c) 2023+, Wallpants (https://github.com/wallpants/)
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in
21
+ all copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29
+ THE SOFTWARE.
30
+
31
+ ## Marked
32
+
33
+ Copyright (c) 2018+, MarkedJS (https://github.com/markedjs/)
34
+
35
+ Copyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/)
36
+
37
+ Permission is hereby granted, free of charge, to any person obtaining a copy
38
+ of this software and associated documentation files (the "Software"), to deal
39
+ in the Software without restriction, including without limitation the rights
40
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
41
+ copies of the Software, and to permit persons to whom the Software is
42
+ furnished to do so, subject to the following conditions:
43
+
44
+ The above copyright notice and this permission notice shall be included in
45
+ all copies or substantial portions of the Software.
46
+
47
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
48
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
49
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
50
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
51
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
52
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
53
+ THE SOFTWARE.
54
+
55
+ ## Markdown
56
+
57
+ Copyright © 2004, John Gruber
58
+ http://daringfireball.net/
59
+ All rights reserved.
60
+
61
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
62
+
63
+ - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
64
+ - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
65
+ - Neither the name “Markdown” nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
66
+
67
+ This software is provided by the copyright holders and contributors “as is” and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the copyright owner or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Pantsdown
2
+
3
+ <!-- Up to https://github.com/markedjs/marked/commit/f90ae0692c772fee55182216b7158f4df5b37bb9 -->
4
+
5
+ The Markdown parser.
package/bun.lockb ADDED
Binary file
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "pantsdown",
3
+ "version": "1.0.1",
4
+ "description": "The markdown parser",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git://github.com/wallpants/pantsdown.git"
8
+ },
9
+ "license": "MIT",
10
+ "type": "module",
11
+ "exports": {
12
+ "types": "./src/index.ts",
13
+ "bun": "./src/index.ts",
14
+ "import": "./src/index.ts"
15
+ },
16
+ "config": {
17
+ "commitizen": {
18
+ "path": "cz-conventional-changelog"
19
+ }
20
+ },
21
+ "scripts": {
22
+ "format": "prettier **/*{ts,js} --write",
23
+ "lint": "tsc && eslint . --ext ts --report-unused-disable-directives --max-warnings 0",
24
+ "commit": "cz"
25
+ },
26
+ "release": {
27
+ "branches": [
28
+ "main"
29
+ ]
30
+ },
31
+ "devDependencies": {
32
+ "@typescript-eslint/eslint-plugin": "^6.7.3",
33
+ "@typescript-eslint/parser": "^6.7.3",
34
+ "bun-types": "latest",
35
+ "commitizen": "^4.3.0",
36
+ "cz-conventional-changelog": "^3.3.0",
37
+ "eslint": "^8.50.0",
38
+ "prettier": "^3.0.2",
39
+ "prettier-plugin-organize-imports": "^3.2.3",
40
+ "semantic-release": "^22.0.5",
41
+ "typescript": "5.2.2"
42
+ },
43
+ "dependencies": {
44
+ "highlight.js": "^11.9.0"
45
+ }
46
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { Pantsdown } from "./pantsdown.ts";
2
+ export * from "./types.ts";
package/src/lexer.ts ADDED
@@ -0,0 +1,369 @@
1
+ import { inline } from "./rules/inline.ts";
2
+ import { Tokenizer } from "./tokenizer.ts";
3
+ import { type Links, type Token } from "./types.ts";
4
+
5
+ export class Lexer {
6
+ private tokenizer: Tokenizer;
7
+ private inlineQueue: { src: string; tokens: Token[] }[];
8
+ private tokens: Token[] = [];
9
+ private links: Links = {};
10
+
11
+ state = {
12
+ inLink: false,
13
+ inRawBlock: false,
14
+ top: true,
15
+ };
16
+
17
+ constructor() {
18
+ this.tokenizer = new Tokenizer(this);
19
+ this.inlineQueue = [];
20
+ }
21
+
22
+ /**
23
+ * Preprocessing
24
+ */
25
+ lex(src: string) {
26
+ // reset values from previous parse
27
+ this.tokens = [];
28
+ this.links = {};
29
+
30
+ src = src.replace(/\r\n|\r/g, "\n");
31
+
32
+ this.blockTokens(src, this.tokens);
33
+
34
+ let next;
35
+ while ((next = this.inlineQueue.shift())) {
36
+ this.inlineTokens(next.src, next.tokens);
37
+ }
38
+
39
+ return this.tokens;
40
+ }
41
+
42
+ /**
43
+ * Lexing
44
+ */
45
+ blockTokens(src: string, tokens: Token[]): Token[] {
46
+ src = src.replace(/^( *)(\t+)/gm, (_, leading, tabs: string) => {
47
+ return leading + " ".repeat(tabs.length);
48
+ });
49
+
50
+ let token: Token | undefined;
51
+ let lastToken: Token | undefined;
52
+ let cutSrc;
53
+ let lastParagraphClipped;
54
+
55
+ while (src) {
56
+ // newline
57
+ if ((token = this.tokenizer.space(src))) {
58
+ src = src.substring(token.raw.length);
59
+ if (token.raw.length === 1 && tokens.length > 0) {
60
+ // if there's a single \n as a spacer, it's terminating the last line,
61
+ // so move it there so that we don't get unecessary paragraph tags
62
+ const last = tokens[tokens.length - 1];
63
+ if (last) last.raw += "\n";
64
+ } else {
65
+ tokens.push(token);
66
+ }
67
+ continue;
68
+ }
69
+
70
+ // code
71
+ if ((token = this.tokenizer.code(src))) {
72
+ src = src.substring(token.raw.length);
73
+ lastToken = tokens[tokens.length - 1];
74
+ // An indented code block cannot interrupt a paragraph.
75
+ if (lastToken && (lastToken.type === "paragraph" || lastToken.type === "text")) {
76
+ lastToken.raw += "\n" + token.raw;
77
+ lastToken.text += "\n" + token.text;
78
+ const lastInline = this.inlineQueue[this.inlineQueue.length - 1];
79
+ if (lastInline) lastInline.src = lastToken.text;
80
+ } else {
81
+ tokens.push(token);
82
+ }
83
+ continue;
84
+ }
85
+
86
+ // fences
87
+ if ((token = this.tokenizer.fences(src))) {
88
+ src = src.substring(token.raw.length);
89
+ tokens.push(token);
90
+ continue;
91
+ }
92
+
93
+ // heading
94
+ if ((token = this.tokenizer.heading(src))) {
95
+ src = src.substring(token.raw.length);
96
+ tokens.push(token);
97
+ continue;
98
+ }
99
+
100
+ // hr
101
+ if ((token = this.tokenizer.hr(src))) {
102
+ src = src.substring(token.raw.length);
103
+ tokens.push(token);
104
+ continue;
105
+ }
106
+
107
+ // blockquote
108
+ if ((token = this.tokenizer.blockquote(src))) {
109
+ src = src.substring(token.raw.length);
110
+ tokens.push(token);
111
+ continue;
112
+ }
113
+
114
+ // list
115
+ if ((token = this.tokenizer.list(src))) {
116
+ src = src.substring(token.raw.length);
117
+ tokens.push(token);
118
+ continue;
119
+ }
120
+
121
+ // html
122
+ if ((token = this.tokenizer.html(src))) {
123
+ src = src.substring(token.raw.length);
124
+ tokens.push(token);
125
+ continue;
126
+ }
127
+
128
+ // def
129
+ if ((token = this.tokenizer.def(src))) {
130
+ src = src.substring(token.raw.length);
131
+ lastToken = tokens[tokens.length - 1];
132
+ if (lastToken && (lastToken.type === "paragraph" || lastToken.type === "text")) {
133
+ lastToken.raw += "\n" + token.raw;
134
+ lastToken.text += "\n" + token.raw;
135
+ const lastInline = this.inlineQueue[this.inlineQueue.length - 1];
136
+ if (lastInline) lastInline.src = lastToken.text;
137
+ } else if (!this.links[token.tag]) {
138
+ this.links[token.tag] = {
139
+ href: token.href,
140
+ title: token.title,
141
+ };
142
+ }
143
+ continue;
144
+ }
145
+
146
+ // table (gfm)
147
+ if ((token = this.tokenizer.table(src))) {
148
+ src = src.substring(token.raw.length);
149
+ tokens.push(token);
150
+ continue;
151
+ }
152
+
153
+ // lheading
154
+ if ((token = this.tokenizer.lheading(src))) {
155
+ src = src.substring(token.raw.length);
156
+ tokens.push(token);
157
+ continue;
158
+ }
159
+
160
+ // top-level paragraph
161
+ // prevent paragraph consuming extensions by clipping 'src' to extension start
162
+ cutSrc = src;
163
+ if (this.state.top && (token = this.tokenizer.paragraph(cutSrc))) {
164
+ lastToken = tokens[tokens.length - 1];
165
+ if (lastParagraphClipped && lastToken?.type === "paragraph") {
166
+ lastToken.raw += "\n" + token.raw;
167
+ lastToken.text += "\n" + token.text;
168
+ this.inlineQueue.pop();
169
+ const lastInline = this.inlineQueue[this.inlineQueue.length - 1];
170
+ if (lastInline) lastInline.src = lastToken.text;
171
+ } else {
172
+ tokens.push(token);
173
+ }
174
+ lastParagraphClipped = cutSrc.length !== src.length;
175
+ src = src.substring(token.raw.length);
176
+ continue;
177
+ }
178
+
179
+ // text
180
+ if ((token = this.tokenizer.text(src))) {
181
+ src = src.substring(token.raw.length);
182
+ lastToken = tokens[tokens.length - 1];
183
+ if (lastToken && lastToken.type === "text") {
184
+ lastToken.raw += "\n" + token.raw;
185
+ lastToken.text += "\n" + token.text;
186
+ this.inlineQueue.pop();
187
+ const lastInline = this.inlineQueue[this.inlineQueue.length - 1];
188
+ if (lastInline) lastInline.src = lastToken.text;
189
+ } else {
190
+ tokens.push(token);
191
+ }
192
+ continue;
193
+ }
194
+
195
+ if (src) {
196
+ const errMsg = "Infinite loop on byte: " + src.charCodeAt(0);
197
+ throw new Error(errMsg);
198
+ }
199
+ }
200
+
201
+ this.state.top = true;
202
+ return tokens;
203
+ }
204
+
205
+ inline(src: string, tokens: Token[] = []) {
206
+ this.inlineQueue.push({ src, tokens });
207
+ return tokens;
208
+ }
209
+
210
+ /**
211
+ * Lexing/Compiling
212
+ */
213
+ inlineTokens(src: string, tokens: Token[] = []): Token[] {
214
+ let token, lastToken, cutSrc;
215
+
216
+ // String with links masked to avoid interference with em and strong
217
+ let maskedSrc = src;
218
+ let match;
219
+ let keepPrevChar, prevChar;
220
+
221
+ // Mask out reflinks
222
+ const links = Object.keys(this.links);
223
+ if (links.length > 0) {
224
+ while ((match = inline.reflinkSearch.exec(maskedSrc)) != null) {
225
+ if (links.includes(match[0].slice(match[0].lastIndexOf("[") + 1, -1))) {
226
+ maskedSrc =
227
+ maskedSrc.slice(0, match.index) +
228
+ "[" +
229
+ "a".repeat(match[0].length - 2) +
230
+ "]" +
231
+ maskedSrc.slice(inline.reflinkSearch.lastIndex);
232
+ }
233
+ }
234
+ }
235
+ // Mask out other blocks
236
+ while ((match = inline.blockSkip.exec(maskedSrc)) != null) {
237
+ maskedSrc =
238
+ maskedSrc.slice(0, match.index) +
239
+ "[" +
240
+ "a".repeat(match[0].length - 2) +
241
+ "]" +
242
+ maskedSrc.slice(inline.blockSkip.lastIndex);
243
+ }
244
+
245
+ // Mask out escaped characters
246
+ while ((match = inline.anyPunctuation.exec(maskedSrc)) != null) {
247
+ maskedSrc =
248
+ maskedSrc.slice(0, match.index) +
249
+ "++" +
250
+ maskedSrc.slice(inline.anyPunctuation.lastIndex);
251
+ }
252
+
253
+ while (src) {
254
+ if (!keepPrevChar) {
255
+ prevChar = "";
256
+ }
257
+ keepPrevChar = false;
258
+
259
+ // escape
260
+ if ((token = this.tokenizer.escape(src))) {
261
+ src = src.substring(token.raw.length);
262
+ tokens.push(token);
263
+ continue;
264
+ }
265
+
266
+ // tag
267
+ if ((token = this.tokenizer.tag(src))) {
268
+ src = src.substring(token.raw.length);
269
+ lastToken = tokens[tokens.length - 1];
270
+ if (lastToken && token.type === "text" && lastToken.type === "text") {
271
+ lastToken.raw += token.raw;
272
+ lastToken.text += token.text;
273
+ } else {
274
+ tokens.push(token);
275
+ }
276
+ continue;
277
+ }
278
+
279
+ // link
280
+ if ((token = this.tokenizer.link(src))) {
281
+ src = src.substring(token.raw.length);
282
+ tokens.push(token);
283
+ continue;
284
+ }
285
+
286
+ // reflink, nolink
287
+ if ((token = this.tokenizer.reflink(src, this.links))) {
288
+ src = src.substring(token.raw.length);
289
+ lastToken = tokens[tokens.length - 1];
290
+ if (lastToken && token.type === "text" && lastToken.type === "text") {
291
+ lastToken.raw += token.raw;
292
+ lastToken.text += token.text;
293
+ } else {
294
+ tokens.push(token);
295
+ }
296
+ continue;
297
+ }
298
+
299
+ // em & strong
300
+ if ((token = this.tokenizer.emStrong(src, maskedSrc, prevChar))) {
301
+ src = src.substring(token.raw.length);
302
+ tokens.push(token);
303
+ continue;
304
+ }
305
+
306
+ // code
307
+ if ((token = this.tokenizer.codespan(src))) {
308
+ src = src.substring(token.raw.length);
309
+ tokens.push(token);
310
+ continue;
311
+ }
312
+
313
+ // br
314
+ if ((token = this.tokenizer.br(src))) {
315
+ src = src.substring(token.raw.length);
316
+ tokens.push(token);
317
+ continue;
318
+ }
319
+
320
+ // del (gfm)
321
+ if ((token = this.tokenizer.del(src))) {
322
+ src = src.substring(token.raw.length);
323
+ tokens.push(token);
324
+ continue;
325
+ }
326
+
327
+ // autolink
328
+ if ((token = this.tokenizer.autolink(src))) {
329
+ src = src.substring(token.raw.length);
330
+ tokens.push(token);
331
+ continue;
332
+ }
333
+
334
+ // url (gfm)
335
+ if (!this.state.inLink && (token = this.tokenizer.url(src))) {
336
+ src = src.substring(token.raw.length);
337
+ tokens.push(token);
338
+ continue;
339
+ }
340
+
341
+ // text
342
+ // prevent inlineText consuming extensions by clipping 'src' to extension start
343
+ cutSrc = src;
344
+ if ((token = this.tokenizer.inlineText(cutSrc))) {
345
+ src = src.substring(token.raw.length);
346
+ if (!token.raw.endsWith("_")) {
347
+ // Track prevChar before string of ____ started
348
+ prevChar = token.raw.slice(-1);
349
+ }
350
+ keepPrevChar = true;
351
+ lastToken = tokens[tokens.length - 1];
352
+ if (lastToken && lastToken.type === "text") {
353
+ lastToken.raw += token.raw;
354
+ lastToken.text += token.text;
355
+ } else {
356
+ tokens.push(token);
357
+ }
358
+ continue;
359
+ }
360
+
361
+ if (src) {
362
+ const errMsg = "Infinite loop on byte: " + src.charCodeAt(0);
363
+ throw new Error(errMsg);
364
+ }
365
+ }
366
+
367
+ return tokens;
368
+ }
369
+ }
@@ -0,0 +1,19 @@
1
+ import { Lexer } from "./lexer.ts";
2
+ import { Parser } from "./parser.ts";
3
+ import { type RendererProps } from "./types.ts";
4
+
5
+ export class Pantsdown {
6
+ private lexer: Lexer;
7
+ private parser: Parser;
8
+
9
+ constructor({ renderer }: { renderer: RendererProps }) {
10
+ this.lexer = new Lexer();
11
+ this.parser = new Parser({ renderer });
12
+ }
13
+
14
+ parse(src: string): string {
15
+ const tokens = this.lexer.lex(src);
16
+ const html = this.parser.parse(tokens);
17
+ return html;
18
+ }
19
+ }