@p-buddy/parkdown 0.0.0
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/.assets/api-note.md +3 -0
- package/.assets/api.md +34 -0
- package/.assets/authoring.md +69 -0
- package/.assets/code/depopulate.ts +6 -0
- package/.assets/code/inclusions.ts +6 -0
- package/.assets/depopulated.md +25 -0
- package/.assets/invocation.md +16 -0
- package/.assets/populated/block.md +9 -0
- package/.assets/populated/inline.multi.md +5 -0
- package/.assets/populated/inline.single.md +3 -0
- package/.assets/query.md +73 -0
- package/.assets/remap-imports.md +0 -0
- package/.assets/unpopulated/block.md +5 -0
- package/.assets/unpopulated/inline.multi.md +3 -0
- package/.assets/unpopulated/inline.single.md +1 -0
- package/.devcontainer/Dockerfile +16 -0
- package/.devcontainer/devcontainer.json +35 -0
- package/LICENSE +21 -0
- package/README.md +418 -0
- package/dist/cli.js +14 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +396 -0
- package/dist/index.umd.cjs +15 -0
- package/package.json +42 -0
- package/src/api/index.test.ts +32 -0
- package/src/api/index.ts +8 -0
- package/src/api/types.ts +78 -0
- package/src/api/utils.test.ts +132 -0
- package/src/api/utils.ts +161 -0
- package/src/cli.ts +31 -0
- package/src/include.test.ts +369 -0
- package/src/include.ts +252 -0
- package/src/index.ts +35 -0
- package/src/region.test.ts +145 -0
- package/src/region.ts +138 -0
- package/src/remap.test.ts +37 -0
- package/src/remap.ts +72 -0
- package/src/utils.test.ts +238 -0
- package/src/utils.ts +184 -0
- package/src/wrap.ts +61 -0
- package/tsconfig.json +5 -0
- package/vite.cli.config.ts +23 -0
- package/vite.config.ts +20 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { parse, nodeSort, getAllPositionNodes, extractContent, replaceWithContent, getContentInBetween, } from "./utils";
|
|
3
|
+
|
|
4
|
+
describe(nodeSort.name, () => {
|
|
5
|
+
test("should sort nodes by line number", () => {
|
|
6
|
+
const ast = parse.md(lorem.md[0]);
|
|
7
|
+
let previousLine = 0;
|
|
8
|
+
let previousColumn = 0;
|
|
9
|
+
for (const node of getAllPositionNodes(ast).sort(nodeSort)) {
|
|
10
|
+
const lineIncreased = node.position.start.line > previousLine;
|
|
11
|
+
const lineStayedTheSame = node.position.start.line === previousLine;
|
|
12
|
+
const columnIncreased = node.position.start.column > previousColumn;
|
|
13
|
+
const columnStayedTheSame = node.position.start.column === previousColumn;
|
|
14
|
+
expect(lineIncreased || (lineStayedTheSame && (columnIncreased || columnStayedTheSame))).toBe(true);
|
|
15
|
+
previousLine = node.position.start.line;
|
|
16
|
+
previousColumn = node.position.start.column;
|
|
17
|
+
}
|
|
18
|
+
previousLine += 1;
|
|
19
|
+
previousColumn = 0;
|
|
20
|
+
for (const node of getAllPositionNodes(ast).sort(nodeSort.reverse)) {
|
|
21
|
+
const lineDecreased = node.position.start.line < previousLine;
|
|
22
|
+
const lineStayedTheSame = node.position.start.line === previousLine;
|
|
23
|
+
const columnDecreased = node.position.start.column < previousColumn;
|
|
24
|
+
const columnStayedTheSame = node.position.start.column === previousColumn;
|
|
25
|
+
expect(lineDecreased || (lineStayedTheSame && (columnDecreased || columnStayedTheSame))).toBe(true);
|
|
26
|
+
previousLine = node.position.start.line;
|
|
27
|
+
previousColumn = node.position.start.column;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const getLinkAndCommentAst = (markdown: string) => {
|
|
33
|
+
const ast = parse.md(markdown);
|
|
34
|
+
const links = getAllPositionNodes(ast, "link");
|
|
35
|
+
expect(links.length).toBe(1);
|
|
36
|
+
const comments = getAllPositionNodes(ast, "html");
|
|
37
|
+
expect(comments.length).toBe(1);
|
|
38
|
+
return { link: links[0], comment: comments[0], ast, markdown };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe(extractContent.name, () => {
|
|
42
|
+
test("should extract content from nodes", () => {
|
|
43
|
+
const { link, comment, ast, markdown } = getLinkAndCommentAst(`
|
|
44
|
+
# Title
|
|
45
|
+
|
|
46
|
+
[link](http://example.com)
|
|
47
|
+
hello
|
|
48
|
+
<!-- comment -->`
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
let content = extractContent(markdown, link);
|
|
52
|
+
expect(content).toBe("[link](http://example.com)");
|
|
53
|
+
|
|
54
|
+
content = extractContent(markdown, comment);
|
|
55
|
+
expect(content).toBe("<!-- comment -->");
|
|
56
|
+
|
|
57
|
+
content = extractContent(markdown, link, comment);
|
|
58
|
+
expect(content).toBe("[link](http://example.com)\nhello\n<!-- comment -->");
|
|
59
|
+
expect(content).toBe(extractContent(markdown, comment, link));
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe(replaceWithContent.name, () => {
|
|
64
|
+
test("should replace content with new content", () => {
|
|
65
|
+
const { link, comment, ast, markdown } = getLinkAndCommentAst(`
|
|
66
|
+
# Title
|
|
67
|
+
|
|
68
|
+
[link](http://example.com)
|
|
69
|
+
hello
|
|
70
|
+
<!-- comment -->
|
|
71
|
+
|
|
72
|
+
ahoy!`
|
|
73
|
+
);
|
|
74
|
+
const content = replaceWithContent(markdown, "new content", link, comment);
|
|
75
|
+
expect(content).toBe("\n# Title\n\nnew content\n\nahoy!");
|
|
76
|
+
})
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe(getContentInBetween.name, () => {
|
|
80
|
+
test("should get content in between two multiline nodes", () => {
|
|
81
|
+
const { link, comment, ast, markdown } = getLinkAndCommentAst(`
|
|
82
|
+
# Title
|
|
83
|
+
|
|
84
|
+
[link](http://example.com)
|
|
85
|
+
hello
|
|
86
|
+
<!-- comment -->`
|
|
87
|
+
);
|
|
88
|
+
const content = getContentInBetween(markdown, link, comment);
|
|
89
|
+
expect(content).toBe("\nhello\n");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("should get content in between two singleline nodes", () => {
|
|
93
|
+
const { link, comment, ast, markdown } = getLinkAndCommentAst(`
|
|
94
|
+
# Title
|
|
95
|
+
|
|
96
|
+
[link](http://example.com) hello <!-- comment -->`
|
|
97
|
+
);
|
|
98
|
+
const content = getContentInBetween(markdown, link, comment);
|
|
99
|
+
expect(content).toBe(" hello ");
|
|
100
|
+
})
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
interface PsuedoDir {
|
|
104
|
+
[key: string]: PsuedoDir | string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export class PsuedoFilesystem {
|
|
108
|
+
constructor(readonly root: PsuedoDir, options?: { setContentToPath?: boolean }) {
|
|
109
|
+
const { setContentToPath = false } = options ?? {};
|
|
110
|
+
if (setContentToPath) PsuedoFilesystem.SetAllFileContentToPath(this.root);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
getFileFromAbsolutePath(path: string) {
|
|
114
|
+
return path.split("/").reduce((acc, part) => (acc as Record<string, PsuedoDir>)[part], this.root) as any as string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
static SetAllFileContentToPath(root: PsuedoDir, prefix?: string) {
|
|
118
|
+
for (const key in root) {
|
|
119
|
+
const value = root[key];
|
|
120
|
+
const path = prefix ? `${prefix}/${key}` : key;
|
|
121
|
+
if (typeof value === "string") root[key] = path;
|
|
122
|
+
else this.SetAllFileContentToPath(value, path);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const lorem = {
|
|
128
|
+
md: [
|
|
129
|
+
`# Vi nactusque pelle at floribus irata quamvis
|
|
130
|
+
|
|
131
|
+
## Moenibus voluptas ludit
|
|
132
|
+
|
|
133
|
+
Lorem markdownum cornua, iter quiete, officiumque arbor vocisque, [alti
|
|
134
|
+
lumina](http://fundae.io/illa.aspx) Agenore. Vendit ob meos, mihi monitis saxum
|
|
135
|
+
noster, est eandem ante, tuos sopitus scopulis volentem. Rege semper iaculo
|
|
136
|
+
protinus poenae curribus increpat Stygias scire: prohibent, et regis in.
|
|
137
|
+
Profanos mecum muneris, dum iudicis eurus non sua accepit auras utque staret.
|
|
138
|
+
|
|
139
|
+
## Filius virgo culpa reliquit
|
|
140
|
+
|
|
141
|
+
Illa moenia vepre clauso o **praemia** fluidoque primo est, modo tamen tumultu
|
|
142
|
+
adorat: rogumque ursa **in**. Solum consensistis illis, Ithacis cuncti ver vidit
|
|
143
|
+
carbasa fluctibus ratione eundem mihi. Vineta *unda*, nec victricia, nullaque,
|
|
144
|
+
inploravere *poteram quae erat* et videt summas regia ferunt se, se?
|
|
145
|
+
|
|
146
|
+
## Illa nuncupat ante proxima habenti prodit
|
|
147
|
+
|
|
148
|
+
Sua qui passis barbam *mira*: adfer pericula; aut. Tua purpuraque passim
|
|
149
|
+
attulerat lanas monitae Turnus patrium cuius fuerat stupet exercent sine.
|
|
150
|
+
Incaluitque premebat ad elisa ut meruisse dum *solutis*, damnare. Texit Libycas,
|
|
151
|
+
est nunc Phoebus. Dominaeque meriti caligine vestigia *extentam* citra tecto
|
|
152
|
+
undas impetus alma, quam radix.
|
|
153
|
+
|
|
154
|
+
1. Umbras laudare per telo lacrimis
|
|
155
|
+
2. Saturno Andraemon Iovem
|
|
156
|
+
3. Cum eadem
|
|
157
|
+
4. Vacent Britannos neque quae rupit socialia pulcherrime
|
|
158
|
+
5. Vidit morsu
|
|
159
|
+
|
|
160
|
+
## Aut visam
|
|
161
|
+
|
|
162
|
+
Micantes *flecti*. Capitolia et aut haec *Latoius manet submersum* et non tumens
|
|
163
|
+
paternis. Ope cornua calidumque artes. Quoque forma, quae gemitus sanguine per
|
|
164
|
+
cunctos hanc est haec abstulit acumine morte hoc fui.
|
|
165
|
+
|
|
166
|
+
> Trepidare cum expellere pectus Ismenus tempora fulminis pater; coniunctaque
|
|
167
|
+
> vocabis placandam et ebori separat. Regna inpensius pater accipienda epytus
|
|
168
|
+
> *Phryges cum angustis* vehit; nec summo excutit Aulidaque partibus texitur
|
|
169
|
+
> perque indomitas frater. Sua ferens discedet et quae, sonantia, comminus
|
|
170
|
+
> *ego*, auras. Dives **ille dubitate eum** poterit adest marito bracchia nec
|
|
171
|
+
> tune, discordemque tanti credas caede hactenus, dumque. At et agros Laiades,
|
|
172
|
+
> illa virtute adorat, est mox palmiferos robore flere ubera.
|
|
173
|
+
|
|
174
|
+
Color genuumque natis Pactolides plangore concipiunt proxima est, aliquid,
|
|
175
|
+
iraque ad natus quoque? **Quoque et** et classe fidemque incepta qui cumque
|
|
176
|
+
latitans ac [vestrum](http://fallitquevolucris.org/fuerant-eris).`,
|
|
177
|
+
`# Ipse oculis praecipitata nostro
|
|
178
|
+
|
|
179
|
+
## Victis ferroque umbram mors plenis
|
|
180
|
+
|
|
181
|
+
Lorem markdownum, angues nec pecudis ponat dabitur qua resedit. Genitor tellus
|
|
182
|
+
et loqui hac: et nullae regnaque, est. Durescit videri. Nunc navita cruento,
|
|
183
|
+
cum, puerilibus aequor. Pro saepe [iamque](http://saturnoquod.com/nullum),
|
|
184
|
+
statione noverat, simul teneri hoc idem opem: Peleus.
|
|
185
|
+
|
|
186
|
+
## Pro nisi vaticinos posse
|
|
187
|
+
|
|
188
|
+
Clymenen nec caesa reddi. Vocat cum, spectare in tamen te fugacibus, haut. Solus
|
|
189
|
+
extulit insistere pugnas praestatque modo purpureis venenata [tumet
|
|
190
|
+
sed](http://www.mihi-duc.com/ferro) curru sanguine levatus magnanimo. Dulichium
|
|
191
|
+
indulsit.
|
|
192
|
+
|
|
193
|
+
## Humano Gorgoneum portus nil pavens laboriferi rapui
|
|
194
|
+
|
|
195
|
+
Faciles non, Iris vero [medeatur reclusis](http://www.tendere.io/somnus) digna
|
|
196
|
+
et sumptis est feres viae hic huc barbae salus laetos. Et ante! Quid lumen Isi:
|
|
197
|
+
nec Rhenum, profecturas priorum aegide medias in coniugis cinctaque ad ignis
|
|
198
|
+
posito. Nubila in alternaque Procnes terrae adferre sentit postquam cui rerum
|
|
199
|
+
nubilus fulvas iam illis cum virgultis, unda [ipse
|
|
200
|
+
tamen](http://www.terra.org/nam.html).
|
|
201
|
+
|
|
202
|
+
var port = uncForumRt(ccd * dpi_udp(1, 1, 52557),
|
|
203
|
+
pppoe_scraping_switch.passive(adf_domain, floating +
|
|
204
|
+
standaloneDnsPppoe, trashFileLed / safe_error_recursive), 2);
|
|
205
|
+
if (-2) {
|
|
206
|
+
bpsPOn = hardwareIso(1);
|
|
207
|
+
vaporware_biometrics.wddm_spool_compile += multimediaStation(
|
|
208
|
+
drop_property_boot, sms, crop_excel(2, snmp_truncate_inkjet));
|
|
209
|
+
}
|
|
210
|
+
softMetadata.installer = 4;
|
|
211
|
+
var hdtv = printer_json_southbridge;
|
|
212
|
+
|
|
213
|
+
## Multorum pellant famularia praeterea humo
|
|
214
|
+
|
|
215
|
+
Carmine demisi super nantis **telo**! Dicimus requievit, lurida extenuant
|
|
216
|
+
**diverso paventis venter** ore medio deposuitque ex fons: Iuno latratibus.
|
|
217
|
+
Mediaque tum *Eurus*, unam nympha casu ille licet sinu est modo, celasse tamen.
|
|
218
|
+
|
|
219
|
+
## Quem puto suis semper expers hominis Placatus
|
|
220
|
+
|
|
221
|
+
**Sidera arma**; Iuventae loca victis: necis acies ducunt ipse non **precesque
|
|
222
|
+
petit demptos** effetus: oculis mittunt. In nam vox regia sustinet nervosque
|
|
223
|
+
obsceno Delphi haec genetrix Nereus [versasque
|
|
224
|
+
quaeratur](http://te-urbem.net/aqua). Terga deae natalis *Aetnen* ingemuit, cum
|
|
225
|
+
arserunt vertice **egimus** fama visa illic ipsamque.
|
|
226
|
+
|
|
227
|
+
1. Post petit unum accepisse obsequio populator praesagia
|
|
228
|
+
2. Terrena iam mora libera
|
|
229
|
+
3. Suas astra inflata litore crimine
|
|
230
|
+
|
|
231
|
+
Quibus viribus referam **est posse** Iphis extulerat oscula, clivum, tantum
|
|
232
|
+
patres formam villis [hic pruinas
|
|
233
|
+
remisit](http://quaeebur.com/condi-summas.aspx). In qui cum, **lac fugavi
|
|
234
|
+
Perseus**. Me iusta habitabat stabat tam locum similes pulsant; manu palantesque
|
|
235
|
+
deae cohaesit, nec ut opiferque fugientem eurus. Eodem vivit Aiaci minas non,
|
|
236
|
+
radices in petent audacia volabat pro dedit ducibusque et vertice abstinuit.`
|
|
237
|
+
] as const
|
|
238
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { unified, type Plugin } from 'unified';
|
|
2
|
+
import remarkParse from 'remark-parse';
|
|
3
|
+
import { visit } from 'unist-util-visit';
|
|
4
|
+
import hash from 'stable-hash';
|
|
5
|
+
|
|
6
|
+
export type AstRoot = typeof remarkParse extends Plugin<any, any, infer Root>
|
|
7
|
+
/**/ ? Root
|
|
8
|
+
/**/ : never;
|
|
9
|
+
|
|
10
|
+
export type MarkdownNode = AstRoot["children"][number];
|
|
11
|
+
export type NodeType = MarkdownNode["type"];
|
|
12
|
+
export type SpecificNode<T extends NodeType> = MarkdownNode & { type: T };
|
|
13
|
+
|
|
14
|
+
type RequiredDeep<T> = {
|
|
15
|
+
[P in keyof T]-?: T[P] extends object | undefined ? RequiredDeep<T[P]> : T[P];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type HasPosition = RequiredDeep<Pick<MarkdownNode, "position">>;
|
|
19
|
+
export type Position = HasPosition['position'];
|
|
20
|
+
export type Point = Position['start' | 'end'];
|
|
21
|
+
|
|
22
|
+
export const nodeSort = (a: HasPosition, b: HasPosition) => a.position.start.offset - b.position.start.offset;
|
|
23
|
+
|
|
24
|
+
nodeSort.reverse = (a: HasPosition, b: HasPosition) => nodeSort(b, a);
|
|
25
|
+
|
|
26
|
+
export const hasPosition = <T extends MarkdownNode>(node: T): node is T & HasPosition =>
|
|
27
|
+
node.position !== undefined && node.position.start.offset !== undefined && node.position.end.offset !== undefined;
|
|
28
|
+
|
|
29
|
+
const processor = unified().use(remarkParse);
|
|
30
|
+
|
|
31
|
+
export const parse = {
|
|
32
|
+
md: (markdown: string) => processor.parse(markdown) satisfies AstRoot
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
export const getAllPositionNodes = <T extends NodeType>(ast: AstRoot, type?: T) => {
|
|
36
|
+
type Node = SpecificNode<T> & HasPosition & { parentID: string; siblingIndex: number; siblingCount: number };
|
|
37
|
+
const nodes: Node[] = [];
|
|
38
|
+
visit(ast, (node, siblingIndex, parent) => {
|
|
39
|
+
if (node.type === "root") return;
|
|
40
|
+
else if (type && node.type !== type) return;
|
|
41
|
+
else if (hasPosition(node)) {
|
|
42
|
+
const parentID = hash(parent);
|
|
43
|
+
const siblingCount = (parent?.children.length ?? 0) - 1;
|
|
44
|
+
nodes.push({ ...node, parentID, siblingIndex, siblingCount } as Node);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return nodes;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type PositionNode<T extends NodeType> = ReturnType<typeof getAllPositionNodes<T>>[number];
|
|
51
|
+
|
|
52
|
+
export type Link = PositionNode<"link">;
|
|
53
|
+
export type Html = PositionNode<"html">;
|
|
54
|
+
|
|
55
|
+
export const linkHasNoText = (node: Link) => node.children.length === 0;
|
|
56
|
+
|
|
57
|
+
export const extractContent = (markdown: string, ...nodes: HasPosition[]) => {
|
|
58
|
+
if (nodes.length === 0) throw new Error("No nodes to extract content from");
|
|
59
|
+
nodes.sort(nodeSort);
|
|
60
|
+
const head = nodes.at(0)!;
|
|
61
|
+
const tail = nodes.at(-1)!;
|
|
62
|
+
return markdown.slice(head.position.start.offset, tail.position.end.offset);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const replaceWithContent = (markdown: string, content: string, ...nodes: HasPosition[]) => {
|
|
66
|
+
if (nodes.length === 0) throw new Error("No nodes to replace content from");
|
|
67
|
+
nodes.sort(nodeSort);
|
|
68
|
+
const head = nodes.at(0)!;
|
|
69
|
+
const tail = nodes.at(-1)!;
|
|
70
|
+
return markdown.slice(0, head.position.start.offset) + content + markdown.slice(tail.position.end.offset);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const getContentInBetween = (markdown: string, a: HasPosition, b: HasPosition) => {
|
|
74
|
+
const head = Math.min(a.position.end.offset, b.position.end.offset);
|
|
75
|
+
const tail = Math.max(a.position.start.offset, b.position.start.offset);
|
|
76
|
+
return markdown.slice(head, tail);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type Join<T extends string[], D extends string> =
|
|
80
|
+
T extends []
|
|
81
|
+
/**/ ? ''
|
|
82
|
+
/**/ : T extends [infer F extends string]
|
|
83
|
+
/**/ ? F
|
|
84
|
+
/**/ : T extends [infer F extends string, ...infer R extends string[]]
|
|
85
|
+
/**/ ? `${F}${D}${Join<R, D>}`
|
|
86
|
+
/**/ : string;
|
|
87
|
+
|
|
88
|
+
export const spaced = <T extends string[]>(...args: T) => args.join(" ") as Join<T, " ">;
|
|
89
|
+
export const lined = <T extends string[]>(...args: T) => args.join("\n") as Join<T, "\n">;
|
|
90
|
+
|
|
91
|
+
export const start = ({ position: { start } }: HasPosition) => start;
|
|
92
|
+
|
|
93
|
+
const offsetIndex = ({ start, end }: Position, offset: number) =>
|
|
94
|
+
({ start: { line: start.line + offset, column: start.column + offset }, end: { line: end.line + offset, column: end.column + offset } });
|
|
95
|
+
|
|
96
|
+
export const zeroIndexed = (position: Position) => offsetIndex(position, -1);
|
|
97
|
+
export const oneIndexed = (position: Position) => offsetIndex(position, 1);
|
|
98
|
+
|
|
99
|
+
export const seperateQueryParams = (path: string): [lhs: string, query: string] => {
|
|
100
|
+
const parts = path.split("?");
|
|
101
|
+
return parts.length > 1 ? [parts.slice(0, -1).join("?"), parts.at(-1)!] : [path, ""];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const getQueryParams = (path: string) => seperateQueryParams(path)[1];
|
|
105
|
+
|
|
106
|
+
export const removeQueryParams = (path: string) => seperateQueryParams(path)[0];
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
export class Intervals {
|
|
110
|
+
private intervals: Array<[number, number]> = [];
|
|
111
|
+
|
|
112
|
+
push(start: number, end: number) {
|
|
113
|
+
this.intervals.push([Math.min(start, end), Math.max(start, end)]);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
combine(rhs: Intervals) {
|
|
117
|
+
this.intervals.push(...rhs.intervals);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
collapse() {
|
|
121
|
+
const { intervals } = this;
|
|
122
|
+
if (!intervals.length) return (this.intervals = []);
|
|
123
|
+
|
|
124
|
+
intervals.sort((a, b) => a[0] - b[0]);
|
|
125
|
+
|
|
126
|
+
const result: typeof this.intervals = [];
|
|
127
|
+
let [currStart, currEnd] = intervals[0];
|
|
128
|
+
|
|
129
|
+
for (let i = 1; i < intervals.length; i++) {
|
|
130
|
+
const [start, end] = intervals[i];
|
|
131
|
+
if (start <= currEnd) currEnd = Math.max(currEnd, end);
|
|
132
|
+
else {
|
|
133
|
+
result.push([currStart, currEnd]);
|
|
134
|
+
currStart = start;
|
|
135
|
+
currEnd = end;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
result.push([currStart, currEnd]);
|
|
139
|
+
|
|
140
|
+
return (this.intervals = result);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
subtract(rhs: Intervals) {
|
|
144
|
+
const { intervals } = this;
|
|
145
|
+
const { intervals: remove } = rhs;
|
|
146
|
+
|
|
147
|
+
if (!intervals.length || !remove.length) return intervals;
|
|
148
|
+
|
|
149
|
+
let result = [...intervals];
|
|
150
|
+
for (const [removeStart, removeEnd] of remove) {
|
|
151
|
+
const updated: typeof this.intervals = [];
|
|
152
|
+
|
|
153
|
+
for (const [start, end] of result) {
|
|
154
|
+
if (removeEnd <= start || removeStart >= end) {
|
|
155
|
+
updated.push([start, end]);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (removeStart > start) updated.push([start, removeStart]);
|
|
160
|
+
if (removeEnd < end) updated.push([removeEnd, end]);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
result = updated;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return (this.intervals = result);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const COMMA_NOT_IN_PARENTHESIS = /,\s*(?![^()]*\))/;
|
|
171
|
+
|
|
172
|
+
/** p▼: sanitize */
|
|
173
|
+
const sanitizations: [from: RegExp | string, to: string][] = [
|
|
174
|
+
[/'''/g, `"`],
|
|
175
|
+
[/''/g, `'`],
|
|
176
|
+
[/parkdown:\s+/g, ``],
|
|
177
|
+
[/p▼:\s+/g, ``],
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
export const sanitize = (replacement: string, space: string = "-") => {
|
|
181
|
+
const sanitized = sanitizations.reduce((acc, [from, to]) => acc.replaceAll(from, to), replacement)
|
|
182
|
+
return space ? sanitized.replaceAll(space, " ") : sanitized;
|
|
183
|
+
}
|
|
184
|
+
/** p▼: sanitize */
|
package/src/wrap.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createParser, type MethodDefinition } from "./api/";
|
|
2
|
+
|
|
3
|
+
/** p▼: definition */
|
|
4
|
+
const definitions = [
|
|
5
|
+
/**
|
|
6
|
+
* Wraps the content in a markdown-formatted code block.
|
|
7
|
+
* @param lang The language of the code block (defaults to the file extension).
|
|
8
|
+
* @param meta Additional metadata to include in the top line of the code block (i.e. to the right of the `lang`).
|
|
9
|
+
* @example [](<url>?wrap=code)
|
|
10
|
+
* @example [](<url>?wrap=code())
|
|
11
|
+
* @example [](<url>?wrap=code(ts))
|
|
12
|
+
* @example [](<url>?wrap=code(,some-meta))
|
|
13
|
+
*/
|
|
14
|
+
"code(lang?: string, meta?: string)",
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Wraps the content in a markdown-formatted blockquote
|
|
18
|
+
* (using the `>` character if the content is a single line,
|
|
19
|
+
* or the `<blockquote>` tag if the content is a multi-line block).
|
|
20
|
+
* @example [](<url>?wrap=quote)
|
|
21
|
+
* @example [](<url>?wrap=quote())
|
|
22
|
+
* @example [](<url>?wrap=quote(,))
|
|
23
|
+
*/
|
|
24
|
+
"quote()",
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Wraps the content in a markdown-formatted dropdown (using the `<details>` and `<summary>` tags).
|
|
28
|
+
* @param summary The summary text of the dropdown.
|
|
29
|
+
* @param open Whether the dropdown should be open by default.
|
|
30
|
+
* @param space The space character to use between words in the summary (defaults to `-`).
|
|
31
|
+
* @example [](<url>?wrap=dropdown(hello-world))
|
|
32
|
+
* @example [](<url>?wrap=dropdown('hello,-world',true))
|
|
33
|
+
* @example [](<url>?wrap=dropdown(hello_world,,_))
|
|
34
|
+
*/
|
|
35
|
+
"dropdown(summary: string, open?: boolean, space?: string)",
|
|
36
|
+
|
|
37
|
+
] /** p▼: definition */ satisfies (MethodDefinition)[];
|
|
38
|
+
|
|
39
|
+
/** p▼: Default-Space */
|
|
40
|
+
const DEFAULT_SPACE = "-";
|
|
41
|
+
/** p▼: Default-Space */
|
|
42
|
+
|
|
43
|
+
const parse = createParser(definitions);
|
|
44
|
+
|
|
45
|
+
export const wrap = (content: string, query: string, details?: { extension: string, inline: boolean }): string => {
|
|
46
|
+
const parsed = parse(query);
|
|
47
|
+
const isSingleLine = details?.inline && !content.includes("\n\n");
|
|
48
|
+
|
|
49
|
+
switch (parsed.name) {
|
|
50
|
+
case "code":
|
|
51
|
+
const lang = parsed.lang ?? details?.extension ?? "";
|
|
52
|
+
const meta = parsed.meta ? ` ${parsed.meta}` : "";
|
|
53
|
+
return `\`\`\`${lang}${meta}\n${content}\n\`\`\``;
|
|
54
|
+
case "quote":
|
|
55
|
+
return isSingleLine ? `> ${content}` : `<blockquote>\n\n${content}\n\n</blockquote>\n`;
|
|
56
|
+
case "dropdown":
|
|
57
|
+
const head = `<details${parsed.open ? " open" : ""}>`;
|
|
58
|
+
const summary = `<summary>${parsed.summary.split(parsed.space ?? DEFAULT_SPACE).join(" ")}</summary>`;
|
|
59
|
+
return ["", head, summary, "", content, "</details>", ""].join("\n");
|
|
60
|
+
}
|
|
61
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { externalizeDeps } from 'vite-plugin-externalize-deps'
|
|
4
|
+
import { name as packageName } from './package.json';
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
resolve: {
|
|
8
|
+
alias: {
|
|
9
|
+
'.': packageName,
|
|
10
|
+
'./': packageName,
|
|
11
|
+
'./index': packageName,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
plugins: [externalizeDeps({ nodeBuiltins: true, include: [packageName] })],
|
|
15
|
+
build: {
|
|
16
|
+
lib: {
|
|
17
|
+
fileName: 'cli',
|
|
18
|
+
entry: resolve(__dirname, 'src/cli.ts'),
|
|
19
|
+
formats: ['es']
|
|
20
|
+
},
|
|
21
|
+
emptyOutDir: false,
|
|
22
|
+
}
|
|
23
|
+
});
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { defineConfig } from 'vitest/config';
|
|
3
|
+
import dts from 'vite-plugin-dts';
|
|
4
|
+
import { externalizeDeps } from 'vite-plugin-externalize-deps';
|
|
5
|
+
|
|
6
|
+
const testPattern = "src/**/*.{test,spec}.{js,ts}"
|
|
7
|
+
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
plugins: [dts({ exclude: testPattern, rollupTypes: true }), externalizeDeps({ nodeBuiltins: true })],
|
|
10
|
+
build: {
|
|
11
|
+
lib: {
|
|
12
|
+
name: 'index',
|
|
13
|
+
fileName: 'index',
|
|
14
|
+
entry: resolve(__dirname, 'src/index.ts'),
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
test: {
|
|
18
|
+
include: [testPattern],
|
|
19
|
+
},
|
|
20
|
+
})
|