markdown-patch 0.1.1 → 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.
Files changed (58) hide show
  1. package/README.md +111 -0
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +108 -0
  5. package/dist/constants.d.ts +7 -0
  6. package/dist/constants.d.ts.map +1 -0
  7. package/dist/constants.js +15 -0
  8. package/dist/debug.d.ts +3 -0
  9. package/dist/debug.d.ts.map +1 -0
  10. package/dist/debug.js +50 -0
  11. package/dist/index.d.ts +3 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +2 -0
  14. package/dist/map.d.ts +3 -0
  15. package/dist/map.d.ts.map +1 -0
  16. package/dist/map.js +144 -0
  17. package/dist/patch.d.ts +19 -0
  18. package/dist/patch.d.ts.map +1 -0
  19. package/dist/patch.js +189 -0
  20. package/dist/tests/map.test.d.ts +2 -0
  21. package/dist/tests/map.test.d.ts.map +1 -0
  22. package/dist/tests/map.test.js +202 -0
  23. package/dist/tests/patch.test.d.ts +2 -0
  24. package/dist/tests/patch.test.d.ts.map +1 -0
  25. package/dist/tests/patch.test.js +223 -0
  26. package/dist/types.d.ts +95 -0
  27. package/dist/types.d.ts.map +1 -0
  28. package/dist/types.js +1 -0
  29. package/package.json +23 -3
  30. package/.tool-versions +0 -1
  31. package/.vscode/launch.json +0 -21
  32. package/document.md +0 -11
  33. package/document.mdpatch.json +0 -8
  34. package/jest.config.ts +0 -9
  35. package/src/cli.ts +0 -88
  36. package/src/constants.ts +0 -11
  37. package/src/debug.ts +0 -75
  38. package/src/index.ts +0 -9
  39. package/src/map.ts +0 -200
  40. package/src/patch.ts +0 -326
  41. package/src/tests/map.test.ts +0 -212
  42. package/src/tests/patch.test.ts +0 -297
  43. package/src/tests/sample.md +0 -81
  44. package/src/tests/sample.patch.block.append.md +0 -82
  45. package/src/tests/sample.patch.block.prepend.md +0 -82
  46. package/src/tests/sample.patch.block.replace.md +0 -81
  47. package/src/tests/sample.patch.block.targetBlockTypeBehavior.table.append.md +0 -82
  48. package/src/tests/sample.patch.block.targetBlockTypeBehavior.table.prepend.md +0 -82
  49. package/src/tests/sample.patch.block.targetBlockTypeBehavior.table.replace.md +0 -77
  50. package/src/tests/sample.patch.heading.append.md +0 -82
  51. package/src/tests/sample.patch.heading.document.append.md +0 -82
  52. package/src/tests/sample.patch.heading.document.prepend.md +0 -82
  53. package/src/tests/sample.patch.heading.prepend.md +0 -82
  54. package/src/tests/sample.patch.heading.replace.md +0 -81
  55. package/src/tests/sample.patch.heading.trimTargetWhitespace.append.md +0 -80
  56. package/src/tests/sample.patch.heading.trimTargetWhitespace.prepend.md +0 -80
  57. package/src/types.ts +0 -155
  58. package/tsconfig.json +0 -18
@@ -0,0 +1,95 @@
1
+ import { ContentType } from "./constants";
2
+ export interface DocumentRange {
3
+ start: number;
4
+ end: number;
5
+ }
6
+ export interface DocumentMapMarkerContentPair {
7
+ marker: DocumentRange;
8
+ content: DocumentRange;
9
+ }
10
+ export interface HeadingMarkerContentPair extends DocumentMapMarkerContentPair {
11
+ level: number;
12
+ }
13
+ export interface DocumentMap {
14
+ heading: Record<string, HeadingMarkerContentPair>;
15
+ block: Record<string, DocumentMapMarkerContentPair>;
16
+ }
17
+ export type PatchTargetType = "heading" | "block";
18
+ export type PatchOperation = "replace" | "prepend" | "append";
19
+ export interface BasePatchInstructionTarget {
20
+ targetType: PatchTargetType;
21
+ target: any;
22
+ }
23
+ export interface BasePatchInstructionOperation {
24
+ operation: string;
25
+ }
26
+ export interface BaseHeadingPatchInstruction extends BasePatchInstructionTarget {
27
+ targetType: "heading";
28
+ target: string[] | null;
29
+ }
30
+ export interface BaseBlockPatchInstruction extends BasePatchInstructionTarget {
31
+ targetType: "block";
32
+ target: string;
33
+ }
34
+ export interface NonExtendingPatchInstruction extends BasePatchInstructionOperation {
35
+ }
36
+ export interface ExtendingPatchInstruction extends BasePatchInstructionOperation {
37
+ /** Trim whitepsace from target before joining with content
38
+ *
39
+ * - For `prepend`: Trims content from the beginning of
40
+ * the target content.
41
+ * - For `append`: Trims content from the end of the target
42
+ * content. Your content should probably end in a newline
43
+ * in this case, or the trailing heading will no longer
44
+ * be the start of its own line
45
+ */
46
+ trimTargetWhitespace?: boolean;
47
+ /** Apply patch even if content already exists at target
48
+ *
49
+ * By default, we will fail to apply a patch if the supplied
50
+ * content is found anywhere in your target content. If you
51
+ * would instead like the patch to occur, regardless of whether
52
+ * it appears the content is already there, you can set
53
+ * `applyIfContentPreexists` to `true`.
54
+ */
55
+ applyIfContentPreexists?: boolean;
56
+ }
57
+ export interface StringContent {
58
+ contentType?: ContentType.text;
59
+ content: string;
60
+ }
61
+ export interface TableRowsContent {
62
+ contentType: ContentType.tableRows;
63
+ content: string[][];
64
+ }
65
+ export interface PrependHeadingPatchInstruction extends ExtendingPatchInstruction, BaseHeadingPatchInstruction, StringContent {
66
+ operation: "prepend";
67
+ }
68
+ export interface AppendHeadingPatchInstruction extends ExtendingPatchInstruction, BaseHeadingPatchInstruction, StringContent {
69
+ operation: "append";
70
+ }
71
+ export interface ReplaceHeadingPatchInstruction extends NonExtendingPatchInstruction, BaseHeadingPatchInstruction, StringContent {
72
+ operation: "replace";
73
+ }
74
+ export interface PrependBlockPatchInstruction extends ExtendingPatchInstruction, BaseBlockPatchInstruction, StringContent {
75
+ operation: "prepend";
76
+ }
77
+ export interface AppendBlockPatchInstruction extends ExtendingPatchInstruction, BaseBlockPatchInstruction, StringContent {
78
+ operation: "append";
79
+ }
80
+ export interface ReplaceBlockPatchInstruction extends NonExtendingPatchInstruction, BaseBlockPatchInstruction, StringContent {
81
+ operation: "replace";
82
+ }
83
+ export interface PrependTableRowsBlockPatchInstruction extends ExtendingPatchInstruction, BaseBlockPatchInstruction, TableRowsContent {
84
+ operation: "prepend";
85
+ }
86
+ export interface AppendTableRowsBlockPatchInstruction extends ExtendingPatchInstruction, BaseBlockPatchInstruction, TableRowsContent {
87
+ operation: "append";
88
+ }
89
+ export interface ReplaceTableRowsBlockPatchInstruction extends NonExtendingPatchInstruction, BaseBlockPatchInstruction, TableRowsContent {
90
+ operation: "replace";
91
+ }
92
+ export type HeadingPatchInstruction = PrependHeadingPatchInstruction | AppendHeadingPatchInstruction | ReplaceHeadingPatchInstruction;
93
+ export type BlockPatchInstruction = PrependBlockPatchInstruction | AppendBlockPatchInstruction | ReplaceBlockPatchInstruction | PrependTableRowsBlockPatchInstruction | AppendTableRowsBlockPatchInstruction | ReplaceTableRowsBlockPatchInstruction;
94
+ export type PatchInstruction = HeadingPatchInstruction | BlockPatchInstruction;
95
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -15,10 +15,11 @@
15
15
  "typescript": "^5.5.4"
16
16
  },
17
17
  "name": "markdown-patch",
18
- "version": "0.1.1",
18
+ "version": "0.1.3",
19
19
  "main": "./dist/index.js",
20
20
  "scripts": {
21
- "test": "node node_modules/.bin/jest"
21
+ "build": "npx tsc",
22
+ "test": "npx jest"
22
23
  },
23
24
  "bin": {
24
25
  "mdpatch": "./dist/cli.js"
@@ -27,5 +28,24 @@
27
28
  "author": "",
28
29
  "license": "ISC",
29
30
  "description": "Change markdown documents by inserting or changing content relative to headings or other parts of a document's structure.",
30
- "type": "module"
31
+ "type": "module",
32
+ "files": [
33
+ "dist/",
34
+ "README.md"
35
+ ],
36
+ "jest": {
37
+ "preset": "ts-jest",
38
+ "testEnvironment": "node",
39
+ "testPathIgnorePatterns": [
40
+ "/node_modules/",
41
+ "/dist/"
42
+ ],
43
+ "transform": {
44
+ "^.+\\.ts$": ["ts-jest", {"useESM": true}]
45
+ },
46
+ "extensionsToTreatAsEsm": [".ts"],
47
+ "moduleNameMapper": {
48
+ "^(\\.{1,2}/.*)\\.js$": "$1"
49
+ }
50
+ }
31
51
  }
package/.tool-versions DELETED
@@ -1 +0,0 @@
1
- nodejs 20.8.0
@@ -1,21 +0,0 @@
1
- {
2
- // Use IntelliSense to learn about possible attributes.
3
- // Hover to view descriptions of existing attributes.
4
- // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
- "version": "0.2.0",
6
- "configurations": [
7
- {
8
- "type": "node",
9
- "request": "launch",
10
- "name": "Jest All",
11
- //"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/jest",
12
- "args": [
13
- "--runInBand"
14
- ],
15
- "console": "integratedTerminal",
16
- "internalConsoleOptions": "neverOpen",
17
- "program": "${workspaceFolder}/node_modules/.bin/jest",
18
- "cwd": "${workspaceFolder}"
19
- }
20
- ]
21
- }
package/document.md DELETED
@@ -1,11 +0,0 @@
1
- # Main
2
-
3
- Some content
4
-
5
- ## Secondary
6
-
7
- Some more content
8
-
9
- ## Tertiary
10
-
11
- Even more content
@@ -1,8 +0,0 @@
1
- [
2
- {
3
- "operation": "append",
4
- "targetType": "heading",
5
- "target": ["Main", "Secondary"],
6
- "content": "This is a test\n"
7
- }
8
- ]
package/jest.config.ts DELETED
@@ -1,9 +0,0 @@
1
- module.exports = {
2
- preset: 'ts-jest',
3
- testEnvironment: 'node',
4
- // Include other configurations if needed
5
- testPathIgnorePatterns: [
6
- "/node_modules/",
7
- "/dist/"
8
- ]
9
- };
package/src/cli.ts DELETED
@@ -1,88 +0,0 @@
1
- #!/usr/bin/env node
2
- import { Command } from "commander";
3
- import fs from "fs/promises";
4
- import { getDocumentMap } from "./map.js";
5
- import { printMap } from "./debug.js";
6
- import { PatchInstruction } from "./types.js";
7
- import { applyPatch } from "./patch.js";
8
- import packageJson from "../package.json" assert { type: "json" };
9
-
10
- async function readStdin(): Promise<string> {
11
- return new Promise((resolve, reject) => {
12
- let data = "";
13
- process.stdin.on("data", (chunk) => (data += chunk));
14
- process.stdin.on("end", () => resolve(data));
15
- process.stdin.on("error", reject);
16
- });
17
- }
18
-
19
- const program = new Command();
20
-
21
- // Configure the CLI
22
- program
23
- .name(Object.keys(packageJson.bin)[0])
24
- .description(packageJson.description)
25
- .version(packageJson.version);
26
-
27
- program
28
- .command("print-map")
29
- .argument("<path>", "filepath to show identified patchable paths for")
30
- .argument(
31
- "[regex]",
32
- "limit displayed matches to those matching the supplied regular expression"
33
- )
34
- .action(async (path: string, regex: string | undefined) => {
35
- const document = await fs.readFile(path, "utf-8");
36
- const documentMap = getDocumentMap(document);
37
-
38
- printMap(document, documentMap, regex ? new RegExp(regex) : undefined);
39
- });
40
-
41
- program
42
- .command("apply")
43
- .argument("<path>", "file to patch")
44
- .argument("<patch>", "patch file to apply")
45
- .option(
46
- "-o, --output <output>",
47
- "write output to the specified path instead of applying in-place; use '-' for stdout"
48
- )
49
- .action(async (path: string, patch: string, options) => {
50
- let patchParsed: PatchInstruction[];
51
- let patchData: string;
52
- try {
53
- if (patch === "-") {
54
- patchData = await readStdin();
55
- } else {
56
- patchData = await fs.readFile(patch, "utf-8");
57
- }
58
- } catch (e) {
59
- console.error("Failed to read patch: ", e);
60
- process.exit(1);
61
- }
62
-
63
- try {
64
- const parsedData = JSON.parse(patchData);
65
- if (!Array.isArray(parsedData)) {
66
- patchParsed = [parsedData];
67
- } else {
68
- patchParsed = parsedData;
69
- }
70
- } catch (e) {
71
- console.error("Could not parse patch file as JSON");
72
- process.exit(1);
73
- }
74
-
75
- let document = await fs.readFile(path, "utf-8");
76
- console.log("Document", document);
77
- for (const instruction of patchParsed) {
78
- document = applyPatch(document, instruction);
79
- }
80
-
81
- if (options.output === "-") {
82
- process.stdout.write(document);
83
- } else {
84
- await fs.writeFile(options.output ? options.output : path, document);
85
- }
86
- });
87
-
88
- program.parse(process.argv);
package/src/constants.ts DELETED
@@ -1,11 +0,0 @@
1
- export const TARGETABLE_BY_ISOLATED_BLOCK_REFERENCE = [
2
- "code",
3
- "heading",
4
- "table",
5
- "blockquote",
6
- "list",
7
- "paragraph",
8
- "image",
9
- ];
10
-
11
- export const CAN_INCLUDE_BLOCK_REFERENCE = ["paragraph", "list_item"];
package/src/debug.ts DELETED
@@ -1,75 +0,0 @@
1
- import chalk from "chalk";
2
- import { DocumentMap } from "./types.js";
3
-
4
- export const printMap = (
5
- content: string,
6
- documentMap: DocumentMap,
7
- regex: RegExp | undefined
8
- ): void => {
9
- for (const type in documentMap) {
10
- for (const positionName in documentMap[type as keyof typeof documentMap]) {
11
- const position =
12
- documentMap[type as keyof typeof documentMap][positionName];
13
-
14
- const blockName = `${type} :: ${positionName.replaceAll(
15
- "\u001f",
16
- " :: "
17
- )}`;
18
- if (regex && !blockName.match(regex)) {
19
- continue;
20
- }
21
- console.log("\n" + chalk.blue(blockName));
22
- if (position.content.start < position.marker.start) {
23
- console.log(
24
- content
25
- .slice(position.content.start - 100, position.content.start)
26
- .replaceAll("\n", "\\n\n") +
27
- chalk.black.bgGreen(
28
- content
29
- .slice(position.content.start, position.content.end)
30
- .replaceAll("\n", "\\n\n")
31
- ) +
32
- content
33
- .slice(
34
- position.content.end,
35
- Math.min(position.content.end + 100, position.marker.start)
36
- )
37
- .replaceAll("\n", "\\n\n") +
38
- chalk.black.bgRed(
39
- content
40
- .slice(position.marker.start, position.marker.end)
41
- .replaceAll("\n", "\\n\n")
42
- ) +
43
- content
44
- .slice(position.marker.end, position.marker.end + 100)
45
- .replaceAll("\n", "\\n\n")
46
- );
47
- } else {
48
- console.log(
49
- content
50
- .slice(position.marker.start - 100, position.marker.start)
51
- .replaceAll("\n", "\\n\n") +
52
- chalk.black.bgRed(
53
- content
54
- .slice(position.marker.start, position.marker.end)
55
- .replaceAll("\n", "\\n\n")
56
- ) +
57
- content
58
- .slice(
59
- position.marker.end,
60
- Math.min(position.marker.end + 100, position.content.start)
61
- )
62
- .replaceAll("\n", "\\n\n") +
63
- chalk.black.bgGreen(
64
- content
65
- .slice(position.content.start, position.content.end)
66
- .replaceAll("\n", "\\n\n")
67
- ) +
68
- content
69
- .slice(position.content.end, position.content.end + 100)
70
- .replaceAll("\n", "\\n\n")
71
- );
72
- }
73
- }
74
- }
75
- };
package/src/index.ts DELETED
@@ -1,9 +0,0 @@
1
- import {
2
- PatchFailureReason,
3
- PatchFailed,
4
- PatchError,
5
- TablePartsNotFound,
6
- applyPatch,
7
- } from "./patch.js";
8
-
9
- export * from "./types.js";
package/src/map.ts DELETED
@@ -1,200 +0,0 @@
1
- import * as marked from "marked";
2
-
3
- import {
4
- DocumentMap,
5
- DocumentMapMarkerContentPair,
6
- HeadingMarkerContentPair,
7
- } from "./types.js";
8
-
9
- import {
10
- CAN_INCLUDE_BLOCK_REFERENCE,
11
- TARGETABLE_BY_ISOLATED_BLOCK_REFERENCE,
12
- } from "./constants.js";
13
-
14
- function getHeadingPositions(
15
- document: string,
16
- tokens: marked.TokensList
17
- ): Record<string, HeadingMarkerContentPair> {
18
- // If the document starts with frontmatter, figure out where
19
- // the frontmatter ends so we can know where the text of the
20
- // document begins
21
- let documentStart = 0;
22
- if (tokens[0].type === "hr") {
23
- documentStart = tokens[0].raw.length + 1;
24
- for (const token of tokens.slice(1)) {
25
- documentStart += token.raw.length;
26
- if (token.type === "hr") {
27
- break;
28
- }
29
- }
30
- }
31
-
32
- const positions: Record<string, HeadingMarkerContentPair> = {
33
- "": {
34
- content: {
35
- start: documentStart,
36
- end: document.length,
37
- },
38
- marker: {
39
- start: 0,
40
- end: 0,
41
- },
42
- level: 0,
43
- },
44
- };
45
- const stack: Array<{ heading: string; position: HeadingMarkerContentPair }> =
46
- [];
47
-
48
- let currentPosition = 0;
49
-
50
- tokens.forEach((token, index) => {
51
- if (token.type === "heading") {
52
- const headingToken = token as marked.Tokens.Heading;
53
-
54
- const startHeading = document.indexOf(
55
- headingToken.raw.trim(),
56
- currentPosition
57
- );
58
- const endHeading = startHeading + headingToken.raw.trim().length + 1;
59
- const headingLevel = headingToken.depth;
60
-
61
- // Determine the start of the content after this heading
62
- const startContent = endHeading;
63
-
64
- // Determine the end of the content before the next heading of the same or higher level, or end of document
65
- let endContent: number | undefined = undefined;
66
- for (let i = index + 1; i < tokens.length; i++) {
67
- if (
68
- tokens[i].type === "heading" &&
69
- (tokens[i] as marked.Tokens.Heading).depth <= headingLevel
70
- ) {
71
- endContent = document.indexOf(tokens[i].raw.trim(), startContent);
72
- break;
73
- }
74
- }
75
- if (endContent === undefined) {
76
- endContent = document.length;
77
- }
78
-
79
- const currentHeading: HeadingMarkerContentPair = {
80
- content: {
81
- start: startContent,
82
- end: endContent,
83
- },
84
- marker: {
85
- start: startHeading,
86
- end: endHeading,
87
- },
88
- level: headingLevel,
89
- };
90
-
91
- // Build the full heading path with parent headings separated by '\t'
92
- let fullHeadingPath = headingToken.text.trim();
93
- while (
94
- stack.length &&
95
- stack[stack.length - 1].position.level >= headingLevel
96
- ) {
97
- stack.pop();
98
- }
99
-
100
- if (stack.length) {
101
- const parent = stack[stack.length - 1];
102
- parent.position.content.end = endContent;
103
- fullHeadingPath = `${parent.heading}\u001f${fullHeadingPath}`;
104
- }
105
-
106
- positions[fullHeadingPath] = currentHeading;
107
- stack.push({ heading: fullHeadingPath, position: currentHeading });
108
-
109
- currentPosition = endHeading;
110
- }
111
- });
112
-
113
- return positions;
114
- }
115
-
116
- function getBlockPositions(
117
- document: string,
118
- tokens: marked.TokensList
119
- ): Record<string, DocumentMapMarkerContentPair> {
120
- const positions: Record<string, DocumentMapMarkerContentPair> = {};
121
-
122
- let lastBlockDetails:
123
- | {
124
- token: marked.Token;
125
- start: number;
126
- end: number;
127
- }
128
- | undefined = undefined;
129
- let startContent = 0;
130
- let endContent = 0;
131
- let endMarker = 0;
132
- marked.walkTokens(tokens, (token) => {
133
- const blockReferenceRegex = /(?:\s+|^)\^([a-zA-Z0-9_-]+)\s*$/;
134
- startContent = document.indexOf(token.raw, startContent);
135
- const match = blockReferenceRegex.exec(token.raw);
136
- endContent = startContent + (match ? match.index : token.raw.length);
137
- const startMarker = match ? startContent + match.index : -1;
138
- endMarker = startContent + token.raw.length;
139
- // The end of a list item token sometimes doesn't include the trailing
140
- // newline -- i'm honestly not sure why, but treating it as
141
- // included here would simplify my implementation
142
- if (
143
- document.slice(endMarker - 1, endMarker) !== "\n" &&
144
- document.slice(endMarker, endMarker + 1) === "\n"
145
- ) {
146
- endMarker += 1;
147
- } else if (
148
- document.slice(endMarker - 2, endMarker) !== "\r\n" &&
149
- document.slice(endMarker, endMarker + 2) === "\r\n"
150
- ) {
151
- endMarker += 2;
152
- }
153
- if (CAN_INCLUDE_BLOCK_REFERENCE.includes(token.type) && match) {
154
- const name = match[1];
155
- if (!name || match.index === undefined) {
156
- return;
157
- }
158
-
159
- const finalStartContent = {
160
- start: startContent,
161
- end: endContent,
162
- };
163
- if (
164
- finalStartContent.start === finalStartContent.end &&
165
- lastBlockDetails
166
- ) {
167
- finalStartContent.start = lastBlockDetails.start;
168
- finalStartContent.end = lastBlockDetails.end;
169
- }
170
-
171
- positions[name] = {
172
- content: finalStartContent,
173
- marker: {
174
- start: startMarker,
175
- end: endMarker,
176
- },
177
- };
178
- }
179
-
180
- if (TARGETABLE_BY_ISOLATED_BLOCK_REFERENCE.includes(token.type)) {
181
- lastBlockDetails = {
182
- token: token,
183
- start: startContent,
184
- end: endContent - 1,
185
- };
186
- }
187
- });
188
-
189
- return positions;
190
- }
191
-
192
- export const getDocumentMap = (document: string): DocumentMap => {
193
- const lexer = new marked.Lexer();
194
- const tokens = lexer.lex(document);
195
-
196
- return {
197
- heading: getHeadingPositions(document, tokens),
198
- block: getBlockPositions(document, tokens),
199
- };
200
- };