markdown-patch 0.1.0 → 0.1.2
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 +111 -0
- package/dist/cli.js +108 -0
- package/{src/constants.ts → dist/constants.js} +7 -8
- package/dist/debug.js +50 -0
- package/dist/index.js +1 -75
- package/dist/map.js +144 -0
- package/dist/patch.js +191 -0
- package/dist/tests/map.test.js +202 -0
- package/dist/tests/patch.test.js +222 -0
- package/dist/types.js +1 -0
- package/package.json +9 -4
- package/.tool-versions +0 -1
- package/.vscode/launch.json +0 -21
- package/document.md +0 -11
- package/document.mdpatch.json +0 -8
- package/jest.config.ts +0 -9
- package/src/debug.ts +0 -75
- package/src/index.ts +0 -88
- package/src/map.ts +0 -200
- package/src/patch.ts +0 -326
- package/src/tests/map.test.ts +0 -212
- package/src/tests/patch.test.ts +0 -297
- package/src/tests/sample.md +0 -81
- package/src/tests/sample.patch.block.append.md +0 -82
- package/src/tests/sample.patch.block.prepend.md +0 -82
- package/src/tests/sample.patch.block.replace.md +0 -81
- package/src/tests/sample.patch.block.targetBlockTypeBehavior.table.append.md +0 -82
- package/src/tests/sample.patch.block.targetBlockTypeBehavior.table.prepend.md +0 -82
- package/src/tests/sample.patch.block.targetBlockTypeBehavior.table.replace.md +0 -77
- package/src/tests/sample.patch.heading.append.md +0 -82
- package/src/tests/sample.patch.heading.document.append.md +0 -82
- package/src/tests/sample.patch.heading.document.prepend.md +0 -82
- package/src/tests/sample.patch.heading.prepend.md +0 -82
- package/src/tests/sample.patch.heading.replace.md +0 -81
- package/src/tests/sample.patch.heading.trimTargetWhitespace.append.md +0 -80
- package/src/tests/sample.patch.heading.trimTargetWhitespace.prepend.md +0 -80
- package/src/types.ts +0 -155
- package/tsconfig.json +0 -18
package/jest.config.ts
DELETED
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,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/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
|
-
};
|
package/src/patch.ts
DELETED
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
import { getDocumentMap } from "./map.js";
|
|
2
|
-
import * as marked from "marked";
|
|
3
|
-
import {
|
|
4
|
-
AppendTableRowsBlockPatchInstruction,
|
|
5
|
-
PrependTableRowsBlockPatchInstruction,
|
|
6
|
-
DocumentMap,
|
|
7
|
-
DocumentMapMarkerContentPair,
|
|
8
|
-
ExtendingPatchInstruction,
|
|
9
|
-
PatchInstruction,
|
|
10
|
-
ReplaceTableRowsBlockPatchInstruction,
|
|
11
|
-
} from "./types.js";
|
|
12
|
-
|
|
13
|
-
enum PatchFailureReason {
|
|
14
|
-
InvalidTarget = "invalid-target",
|
|
15
|
-
ContentAlreadyPreexistsInTarget = "content-already-preexists-in-target",
|
|
16
|
-
TableContentIncorrectColumnCount = "table-content-incorrect-column-count",
|
|
17
|
-
RequestedBlockTypeBehaviorUnavailable = "requested-block-type-behavior-unavailable",
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export class PatchFailed extends Error {
|
|
21
|
-
public reason: PatchFailureReason;
|
|
22
|
-
public instruction: PatchInstruction;
|
|
23
|
-
public targetMap: DocumentMapMarkerContentPair | null;
|
|
24
|
-
|
|
25
|
-
constructor(
|
|
26
|
-
reason: PatchFailureReason,
|
|
27
|
-
instruction: PatchInstruction,
|
|
28
|
-
targetMap: DocumentMapMarkerContentPair | null
|
|
29
|
-
) {
|
|
30
|
-
super();
|
|
31
|
-
this.reason = reason;
|
|
32
|
-
this.instruction = instruction;
|
|
33
|
-
this.targetMap = targetMap;
|
|
34
|
-
this.name = "PatchFailed";
|
|
35
|
-
|
|
36
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export class PatchError extends Error {}
|
|
41
|
-
|
|
42
|
-
const replaceText = (
|
|
43
|
-
document: string,
|
|
44
|
-
instruction: PatchInstruction,
|
|
45
|
-
target: DocumentMapMarkerContentPair
|
|
46
|
-
): string => {
|
|
47
|
-
return [
|
|
48
|
-
document.slice(0, target.content.start),
|
|
49
|
-
instruction.content,
|
|
50
|
-
document.slice(target.content.end),
|
|
51
|
-
].join("");
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const prependText = (
|
|
55
|
-
document: string,
|
|
56
|
-
instruction: ExtendingPatchInstruction & PatchInstruction,
|
|
57
|
-
target: DocumentMapMarkerContentPair
|
|
58
|
-
): string => {
|
|
59
|
-
return [
|
|
60
|
-
document.slice(0, target.content.start),
|
|
61
|
-
instruction.content,
|
|
62
|
-
instruction.trimTargetWhitespace
|
|
63
|
-
? document.slice(target.content.start).trimStart()
|
|
64
|
-
: document.slice(target.content.start),
|
|
65
|
-
].join("");
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const appendText = (
|
|
69
|
-
document: string,
|
|
70
|
-
instruction: ExtendingPatchInstruction & PatchInstruction,
|
|
71
|
-
target: DocumentMapMarkerContentPair
|
|
72
|
-
): string => {
|
|
73
|
-
return [
|
|
74
|
-
instruction.trimTargetWhitespace
|
|
75
|
-
? document.slice(0, target.content.end).trimEnd()
|
|
76
|
-
: document.slice(0, target.content.end),
|
|
77
|
-
instruction.content,
|
|
78
|
-
document.slice(target.content.end),
|
|
79
|
-
].join("");
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
export class TablePartsNotFound extends Error {}
|
|
83
|
-
|
|
84
|
-
const _getTableData = (
|
|
85
|
-
document: string,
|
|
86
|
-
target: DocumentMapMarkerContentPair
|
|
87
|
-
): {
|
|
88
|
-
token: marked.Tokens.Table;
|
|
89
|
-
lineEnding: string;
|
|
90
|
-
headerParts: string;
|
|
91
|
-
contentParts: string;
|
|
92
|
-
} => {
|
|
93
|
-
const targetTable = document.slice(target.content.start, target.content.end);
|
|
94
|
-
const tableToken = marked.lexer(targetTable)[0];
|
|
95
|
-
const match = /^(.*?)(?:\r?\n)(.*?)(\r?\n)/.exec(targetTable);
|
|
96
|
-
if (!(tableToken.type === "table") || !match) {
|
|
97
|
-
throw new TablePartsNotFound();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const lineEnding = match[3];
|
|
101
|
-
return {
|
|
102
|
-
token: tableToken as marked.Tokens.Table,
|
|
103
|
-
lineEnding: match[3],
|
|
104
|
-
headerParts: match[1] + lineEnding + match[2] + lineEnding,
|
|
105
|
-
contentParts: targetTable.slice(match[0].length),
|
|
106
|
-
};
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const replaceTable = (
|
|
110
|
-
document: string,
|
|
111
|
-
instruction: ReplaceTableRowsBlockPatchInstruction,
|
|
112
|
-
target: DocumentMapMarkerContentPair
|
|
113
|
-
): string => {
|
|
114
|
-
try {
|
|
115
|
-
const table = _getTableData(document, target);
|
|
116
|
-
const tableRows: string[] = [table.headerParts];
|
|
117
|
-
for (const row of instruction.content) {
|
|
118
|
-
if (row.length !== table.token.header.length || typeof row === "string") {
|
|
119
|
-
throw new PatchFailed(
|
|
120
|
-
PatchFailureReason.TableContentIncorrectColumnCount,
|
|
121
|
-
instruction,
|
|
122
|
-
target
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
tableRows.push("| " + row.join(" | ") + " |" + table.lineEnding);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return [
|
|
130
|
-
document.slice(0, target.content.start),
|
|
131
|
-
tableRows.join(""),
|
|
132
|
-
document.slice(target.content.end),
|
|
133
|
-
].join("");
|
|
134
|
-
} catch (TablePartsNotFound) {
|
|
135
|
-
throw new PatchFailed(
|
|
136
|
-
PatchFailureReason.RequestedBlockTypeBehaviorUnavailable,
|
|
137
|
-
instruction,
|
|
138
|
-
target
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const prependTable = (
|
|
144
|
-
document: string,
|
|
145
|
-
instruction: PrependTableRowsBlockPatchInstruction,
|
|
146
|
-
target: DocumentMapMarkerContentPair
|
|
147
|
-
): string => {
|
|
148
|
-
try {
|
|
149
|
-
const table = _getTableData(document, target);
|
|
150
|
-
const tableRows: string[] = [table.headerParts];
|
|
151
|
-
for (const row of instruction.content) {
|
|
152
|
-
if (row.length !== table.token.header.length || typeof row === "string") {
|
|
153
|
-
throw new PatchFailed(
|
|
154
|
-
PatchFailureReason.TableContentIncorrectColumnCount,
|
|
155
|
-
instruction,
|
|
156
|
-
target
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
tableRows.push("| " + row.join(" | ") + " |" + table.lineEnding);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
tableRows.push(table.contentParts);
|
|
164
|
-
|
|
165
|
-
return [
|
|
166
|
-
document.slice(0, target.content.start),
|
|
167
|
-
tableRows.join(""),
|
|
168
|
-
document.slice(target.content.end),
|
|
169
|
-
].join("");
|
|
170
|
-
} catch (TablePartsNotFound) {
|
|
171
|
-
throw new PatchFailed(
|
|
172
|
-
PatchFailureReason.RequestedBlockTypeBehaviorUnavailable,
|
|
173
|
-
instruction,
|
|
174
|
-
target
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
const appendTable = (
|
|
180
|
-
document: string,
|
|
181
|
-
instruction: AppendTableRowsBlockPatchInstruction,
|
|
182
|
-
target: DocumentMapMarkerContentPair
|
|
183
|
-
): string => {
|
|
184
|
-
try {
|
|
185
|
-
const table = _getTableData(document, target);
|
|
186
|
-
const tableRows: string[] = [table.headerParts, table.contentParts];
|
|
187
|
-
for (const row of instruction.content) {
|
|
188
|
-
if (row.length !== table.token.header.length || typeof row === "string") {
|
|
189
|
-
throw new PatchFailed(
|
|
190
|
-
PatchFailureReason.TableContentIncorrectColumnCount,
|
|
191
|
-
instruction,
|
|
192
|
-
target
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
tableRows.push("| " + row.join(" | ") + " |" + table.lineEnding);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return [
|
|
200
|
-
document.slice(0, target.content.start),
|
|
201
|
-
tableRows.join(""),
|
|
202
|
-
document.slice(target.content.end),
|
|
203
|
-
].join("");
|
|
204
|
-
} catch (TablePartsNotFound) {
|
|
205
|
-
throw new PatchFailed(
|
|
206
|
-
PatchFailureReason.RequestedBlockTypeBehaviorUnavailable,
|
|
207
|
-
instruction,
|
|
208
|
-
target
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
const replace = (
|
|
214
|
-
document: string,
|
|
215
|
-
instruction: PatchInstruction,
|
|
216
|
-
target: DocumentMapMarkerContentPair
|
|
217
|
-
): string => {
|
|
218
|
-
const targetBlockTypeBehavior =
|
|
219
|
-
"targetBlockTypeBehavior" in instruction
|
|
220
|
-
? instruction.targetBlockTypeBehavior
|
|
221
|
-
: "text";
|
|
222
|
-
|
|
223
|
-
switch (targetBlockTypeBehavior) {
|
|
224
|
-
case "text":
|
|
225
|
-
return replaceText(document, instruction, target);
|
|
226
|
-
case "table":
|
|
227
|
-
return replaceTable(
|
|
228
|
-
document,
|
|
229
|
-
instruction as ReplaceTableRowsBlockPatchInstruction,
|
|
230
|
-
target
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
const prepend = (
|
|
236
|
-
document: string,
|
|
237
|
-
instruction: ExtendingPatchInstruction & PatchInstruction,
|
|
238
|
-
target: DocumentMapMarkerContentPair
|
|
239
|
-
): string => {
|
|
240
|
-
const targetBlockTypeBehavior =
|
|
241
|
-
"targetBlockTypeBehavior" in instruction
|
|
242
|
-
? instruction.targetBlockTypeBehavior
|
|
243
|
-
: "text";
|
|
244
|
-
|
|
245
|
-
switch (targetBlockTypeBehavior) {
|
|
246
|
-
case "text":
|
|
247
|
-
return prependText(document, instruction, target);
|
|
248
|
-
case "table":
|
|
249
|
-
return prependTable(
|
|
250
|
-
document,
|
|
251
|
-
instruction as PrependTableRowsBlockPatchInstruction,
|
|
252
|
-
target
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
const append = (
|
|
258
|
-
document: string,
|
|
259
|
-
instruction: ExtendingPatchInstruction & PatchInstruction,
|
|
260
|
-
target: DocumentMapMarkerContentPair
|
|
261
|
-
): string => {
|
|
262
|
-
const targetBlockTypeBehavior =
|
|
263
|
-
"targetBlockTypeBehavior" in instruction
|
|
264
|
-
? instruction.targetBlockTypeBehavior
|
|
265
|
-
: "text";
|
|
266
|
-
|
|
267
|
-
switch (targetBlockTypeBehavior) {
|
|
268
|
-
case "text":
|
|
269
|
-
return appendText(document, instruction, target);
|
|
270
|
-
case "table":
|
|
271
|
-
return appendTable(
|
|
272
|
-
document,
|
|
273
|
-
instruction as AppendTableRowsBlockPatchInstruction,
|
|
274
|
-
target
|
|
275
|
-
);
|
|
276
|
-
}
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
const getTarget = (
|
|
280
|
-
map: DocumentMap,
|
|
281
|
-
instruction: PatchInstruction
|
|
282
|
-
): DocumentMapMarkerContentPair | undefined => {
|
|
283
|
-
switch (instruction.targetType) {
|
|
284
|
-
case "heading":
|
|
285
|
-
return map.heading[
|
|
286
|
-
instruction.target ? instruction.target.join("\u001f") : ""
|
|
287
|
-
];
|
|
288
|
-
case "block":
|
|
289
|
-
return map.block[instruction.target];
|
|
290
|
-
}
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
export const applyPatch = (
|
|
294
|
-
document: string,
|
|
295
|
-
instruction: PatchInstruction
|
|
296
|
-
): string => {
|
|
297
|
-
const map = getDocumentMap(document);
|
|
298
|
-
const target = getTarget(map, instruction);
|
|
299
|
-
|
|
300
|
-
if (!target) {
|
|
301
|
-
throw new PatchFailed(PatchFailureReason.InvalidTarget, instruction, null);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (
|
|
305
|
-
(!("applyIfContentPreexists" in instruction) ||
|
|
306
|
-
!instruction.applyIfContentPreexists) &&
|
|
307
|
-
typeof instruction.content === "string" &&
|
|
308
|
-
document
|
|
309
|
-
.slice(target.content.start, target.content.end)
|
|
310
|
-
.includes(instruction.content.trim())
|
|
311
|
-
) {
|
|
312
|
-
throw new PatchFailed(
|
|
313
|
-
PatchFailureReason.ContentAlreadyPreexistsInTarget,
|
|
314
|
-
instruction,
|
|
315
|
-
target
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
switch (instruction.operation) {
|
|
319
|
-
case "append":
|
|
320
|
-
return append(document, instruction, target);
|
|
321
|
-
case "prepend":
|
|
322
|
-
return prepend(document, instruction, target);
|
|
323
|
-
case "replace":
|
|
324
|
-
return replace(document, instruction, target);
|
|
325
|
-
}
|
|
326
|
-
};
|