markdown-patch 0.1.3 → 0.2.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/README.md +6 -107
- package/dist/cli.js +21 -0
- package/dist/constants.d.ts +0 -4
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +0 -5
- package/dist/debug.d.ts.map +1 -1
- package/dist/debug.js +12 -3
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/map.d.ts.map +1 -1
- package/dist/map.js +47 -29
- package/dist/patch.d.ts +12 -1
- package/dist/patch.d.ts.map +1 -1
- package/dist/patch.js +192 -38
- package/dist/tests/map.test.js +23 -0
- package/dist/tests/patch.test.js +271 -43
- package/dist/typeGuards.d.ts +7 -0
- package/dist/typeGuards.d.ts.map +1 -0
- package/dist/typeGuards.js +20 -0
- package/dist/types.d.ts +118 -14
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +11 -1
- package/package.json +21 -9
package/README.md
CHANGED
|
@@ -1,111 +1,10 @@
|
|
|
1
1
|
# Markdown Patch
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Markdown Patch is a patch format and tool that allows you to make
|
|
4
|
+
systematic changes to Markdown documents by allowing you to
|
|
5
|
+
alter the content of a Markdown document relative to elements
|
|
6
|
+
of that document's structure like headings or block references.
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
Have you ever needed to set up a script for modifying a markdown document and found yourself using arcane tools like `sed` before giving up entirely? This tool might be for you!
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
You can install the package via `npm`:
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm install markdown-patch
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
And if you were to create a document named `document.md` with the following content:
|
|
16
|
-
|
|
17
|
-
```markdown
|
|
18
|
-
# Noise Floor
|
|
19
|
-
|
|
20
|
-
- Some content
|
|
21
|
-
|
|
22
|
-
# Discoveries
|
|
23
|
-
|
|
24
|
-
# Events
|
|
25
|
-
|
|
26
|
-
- Checked out of my hotel
|
|
27
|
-
- Caught the flight home
|
|
28
|
-
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Then you can use the `patch` or `apply` subcommands to alter the document. For example, the following will add a new heading below the heading "Discoveries":
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
mdpatch patch append heading Discoveries ./document.md
|
|
35
|
-
|
|
36
|
-
## My discovery
|
|
37
|
-
I discovered a thing
|
|
38
|
-
|
|
39
|
-
<Ctrl+D>
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Your final document will then look like:
|
|
43
|
-
|
|
44
|
-
```markdown
|
|
45
|
-
# Noise Floor
|
|
46
|
-
|
|
47
|
-
- Some content
|
|
48
|
-
|
|
49
|
-
# Discoveries
|
|
50
|
-
|
|
51
|
-
## My discovery
|
|
52
|
-
I discovered a thing
|
|
53
|
-
|
|
54
|
-
# Events
|
|
55
|
-
|
|
56
|
-
- Checked out of my hotel
|
|
57
|
-
- Caught the flight home
|
|
58
|
-
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
See `--help` for more insight into what commands are available.
|
|
62
|
-
|
|
63
|
-
## Use as a library
|
|
64
|
-
|
|
65
|
-
```ts
|
|
66
|
-
import {PatchInstruction, applyPatch} from "markdown-patch"
|
|
67
|
-
|
|
68
|
-
const myDocument = `
|
|
69
|
-
# Noise Floor
|
|
70
|
-
|
|
71
|
-
- Some content
|
|
72
|
-
|
|
73
|
-
# Discoveries
|
|
74
|
-
|
|
75
|
-
# Events
|
|
76
|
-
|
|
77
|
-
- Checked out of my hotel
|
|
78
|
-
- Caught the flight home
|
|
79
|
-
|
|
80
|
-
`
|
|
81
|
-
|
|
82
|
-
const instruction: PatchInstruction {
|
|
83
|
-
operation: "append",
|
|
84
|
-
targetType: "heading",
|
|
85
|
-
target: "Discoveries",
|
|
86
|
-
content: "\n## My discovery\nI discovered a thing\n",
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
console.log(
|
|
90
|
-
applyPatch(myDocument, instruction)
|
|
91
|
-
)
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
and you'll see the output:
|
|
95
|
-
|
|
96
|
-
```markdown
|
|
97
|
-
# Noise Floor
|
|
98
|
-
|
|
99
|
-
- Some content
|
|
100
|
-
|
|
101
|
-
# Discoveries
|
|
102
|
-
|
|
103
|
-
## My discovery
|
|
104
|
-
I discovered a thing
|
|
105
|
-
|
|
106
|
-
# Events
|
|
107
|
-
|
|
108
|
-
- Checked out of my hotel
|
|
109
|
-
- Caught the flight home
|
|
110
|
-
|
|
111
|
-
```
|
|
10
|
+
See docs at https://coddingtonbear.github.io/markdown-patch/.
|
package/dist/cli.js
CHANGED
|
@@ -105,4 +105,25 @@ program
|
|
|
105
105
|
await fs.writeFile(options.output ? options.output : path, document);
|
|
106
106
|
}
|
|
107
107
|
});
|
|
108
|
+
program
|
|
109
|
+
.command("query")
|
|
110
|
+
.option("-o, --output <output>", "Path to write output to; use '-' for stdout. Defaults to patching in-place.")
|
|
111
|
+
.option("-d, --delimiter <delimiter>", "Heading delimiter to use in place of '::'.", "::")
|
|
112
|
+
.argument("<targetType>", "Target type ('heading', 'block', etc.)")
|
|
113
|
+
.argument("<target>", "Target ('::'-delimited by default for Headings); see `mdpatch print-map <path to document>` for options)")
|
|
114
|
+
.argument("<documentPath>", "Path to document to query from.")
|
|
115
|
+
.action(async (targetType, target, documentPath, options) => {
|
|
116
|
+
const document = await fs.readFile(documentPath, "utf-8");
|
|
117
|
+
const documentMap = getDocumentMap(document);
|
|
118
|
+
const actualTarget = targetType !== "heading"
|
|
119
|
+
? target
|
|
120
|
+
: target.split(options.delimiter).join("\u001f");
|
|
121
|
+
const value = document.slice(documentMap[targetType][actualTarget].content.start, documentMap[targetType][actualTarget].content.end);
|
|
122
|
+
if (options.output) {
|
|
123
|
+
await fs.writeFile(options.output, value);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
process.stdout.write(value);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
108
129
|
program.parse(process.argv);
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
1
|
export declare const TARGETABLE_BY_ISOLATED_BLOCK_REFERENCE: string[];
|
|
2
2
|
export declare const CAN_INCLUDE_BLOCK_REFERENCE: string[];
|
|
3
|
-
export declare enum ContentType {
|
|
4
|
-
text = "text/plain",
|
|
5
|
-
tableRows = "application/vnd.markdown-patch.table-rows+json"
|
|
6
|
-
}
|
|
7
3
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sCAAsC,UAQlD,CAAC;AAEF,eAAO,MAAM,2BAA2B,UAA6B,CAAC
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sCAAsC,UAQlD,CAAC;AAEF,eAAO,MAAM,2BAA2B,UAA6B,CAAC"}
|
package/dist/constants.js
CHANGED
|
@@ -8,8 +8,3 @@ export const TARGETABLE_BY_ISOLATED_BLOCK_REFERENCE = [
|
|
|
8
8
|
"image",
|
|
9
9
|
];
|
|
10
10
|
export const CAN_INCLUDE_BLOCK_REFERENCE = ["paragraph", "list_item"];
|
|
11
|
-
export var ContentType;
|
|
12
|
-
(function (ContentType) {
|
|
13
|
-
ContentType["text"] = "text/plain";
|
|
14
|
-
ContentType["tableRows"] = "application/vnd.markdown-patch.table-rows+json";
|
|
15
|
-
})(ContentType || (ContentType = {}));
|
package/dist/debug.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../src/debug.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,eAAO,MAAM,QAAQ,YACV,MAAM,eACF,WAAW,SACjB,MAAM,GAAG,SAAS,KACxB,
|
|
1
|
+
{"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../src/debug.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,eAAO,MAAM,QAAQ,YACV,MAAM,eACF,WAAW,SACjB,MAAM,GAAG,SAAS,KACxB,IAiFF,CAAC"}
|
package/dist/debug.js
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
export const printMap = (content, documentMap, regex) => {
|
|
3
|
-
for (const
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
for (const frontmatterField in documentMap.frontmatter) {
|
|
4
|
+
const blockName = `[${chalk.magenta("frontmatter")}] ${chalk.blueBright(frontmatterField)}`;
|
|
5
|
+
console.log("\n" + blockName + "\n");
|
|
6
|
+
console.log(JSON.stringify(documentMap.frontmatter[frontmatterField]));
|
|
7
|
+
}
|
|
8
|
+
const targetablePositions = {
|
|
9
|
+
heading: documentMap.heading,
|
|
10
|
+
block: documentMap.block,
|
|
11
|
+
};
|
|
12
|
+
for (const type in targetablePositions) {
|
|
13
|
+
for (const positionName in targetablePositions[type]) {
|
|
14
|
+
const position = targetablePositions[type][positionName];
|
|
6
15
|
const blockName = `[${chalk.magenta(type)}] ${positionName
|
|
7
16
|
.split("\u001f")
|
|
8
17
|
.map((pos) => chalk.blueBright(pos))
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,UAAU,EACV,kBAAkB,EAClB,UAAU,GACX,MAAM,YAAY,CAAC;AAEpB,cAAc,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,UAAU,EACV,kBAAkB,EAClB,UAAU,GACX,MAAM,YAAY,CAAC;AAEpB,cAAc,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/map.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"map.d.ts","sourceRoot":"","sources":["../src/map.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"map.d.ts","sourceRoot":"","sources":["../src/map.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,WAAW,EAIZ,MAAM,YAAY,CAAC;AA2MpB,eAAO,MAAM,cAAc,aAAc,MAAM,KAAG,WAejD,CAAC"}
|
package/dist/map.js
CHANGED
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
import * as marked from "marked";
|
|
2
|
+
import { parse as parseYaml } from "yaml";
|
|
2
3
|
import { CAN_INCLUDE_BLOCK_REFERENCE, TARGETABLE_BY_ISOLATED_BLOCK_REFERENCE, } from "./constants.js";
|
|
3
|
-
function getHeadingPositions(document, tokens) {
|
|
4
|
-
// If the document starts with frontmatter, figure out where
|
|
5
|
-
// the frontmatter ends so we can know where the text of the
|
|
6
|
-
// document begins
|
|
7
|
-
let documentStart = 0;
|
|
8
|
-
if (tokens[0].type === "hr") {
|
|
9
|
-
documentStart = tokens[0].raw.length + 1;
|
|
10
|
-
for (const token of tokens.slice(1)) {
|
|
11
|
-
documentStart += token.raw.length;
|
|
12
|
-
if (token.type === "hr") {
|
|
13
|
-
break;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
}
|
|
4
|
+
function getHeadingPositions(document, tokens, contentOffset) {
|
|
17
5
|
const positions = {
|
|
18
6
|
"": {
|
|
19
7
|
content: {
|
|
20
|
-
start:
|
|
21
|
-
end: document.length,
|
|
8
|
+
start: contentOffset,
|
|
9
|
+
end: document.length + contentOffset,
|
|
22
10
|
},
|
|
23
11
|
marker: {
|
|
24
12
|
start: 0,
|
|
@@ -51,16 +39,16 @@ function getHeadingPositions(document, tokens) {
|
|
|
51
39
|
}
|
|
52
40
|
const currentHeading = {
|
|
53
41
|
content: {
|
|
54
|
-
start: startContent,
|
|
55
|
-
end: endContent,
|
|
42
|
+
start: startContent + contentOffset,
|
|
43
|
+
end: endContent + contentOffset,
|
|
56
44
|
},
|
|
57
45
|
marker: {
|
|
58
|
-
start: startHeading,
|
|
59
|
-
end: endHeading,
|
|
46
|
+
start: startHeading + contentOffset,
|
|
47
|
+
end: endHeading + contentOffset,
|
|
60
48
|
},
|
|
61
49
|
level: headingLevel,
|
|
62
50
|
};
|
|
63
|
-
// Build the full heading path with parent headings separated by
|
|
51
|
+
// Build the full heading path with parent headings separated by \u001f
|
|
64
52
|
let fullHeadingPath = headingToken.text.trim();
|
|
65
53
|
while (stack.length &&
|
|
66
54
|
stack[stack.length - 1].position.level >= headingLevel) {
|
|
@@ -68,7 +56,7 @@ function getHeadingPositions(document, tokens) {
|
|
|
68
56
|
}
|
|
69
57
|
if (stack.length) {
|
|
70
58
|
const parent = stack[stack.length - 1];
|
|
71
|
-
parent.position.content.end = endContent;
|
|
59
|
+
parent.position.content.end = endContent + contentOffset;
|
|
72
60
|
fullHeadingPath = `${parent.heading}\u001f${fullHeadingPath}`;
|
|
73
61
|
}
|
|
74
62
|
positions[fullHeadingPath] = currentHeading;
|
|
@@ -78,7 +66,7 @@ function getHeadingPositions(document, tokens) {
|
|
|
78
66
|
});
|
|
79
67
|
return positions;
|
|
80
68
|
}
|
|
81
|
-
function getBlockPositions(document, tokens) {
|
|
69
|
+
function getBlockPositions(document, tokens, contentOffset) {
|
|
82
70
|
const positions = {};
|
|
83
71
|
let lastBlockDetails = undefined;
|
|
84
72
|
let startContent = 0;
|
|
@@ -117,10 +105,13 @@ function getBlockPositions(document, tokens) {
|
|
|
117
105
|
finalStartContent.end = lastBlockDetails.end;
|
|
118
106
|
}
|
|
119
107
|
positions[name] = {
|
|
120
|
-
content:
|
|
108
|
+
content: {
|
|
109
|
+
start: finalStartContent.start + contentOffset,
|
|
110
|
+
end: finalStartContent.end + contentOffset,
|
|
111
|
+
},
|
|
121
112
|
marker: {
|
|
122
|
-
start: startMarker,
|
|
123
|
-
end: endMarker,
|
|
113
|
+
start: startMarker + contentOffset,
|
|
114
|
+
end: endMarker + contentOffset,
|
|
124
115
|
},
|
|
125
116
|
};
|
|
126
117
|
}
|
|
@@ -134,11 +125,38 @@ function getBlockPositions(document, tokens) {
|
|
|
134
125
|
});
|
|
135
126
|
return positions;
|
|
136
127
|
}
|
|
128
|
+
function preProcess(document) {
|
|
129
|
+
const frontmatterRegex = /^---(?:\r\n|\r|\n)([\s\S]*?)(?:\r\n|\r|\n)---(?:\r\n|\r|\n|$)/;
|
|
130
|
+
let content;
|
|
131
|
+
let contentOffset = 0;
|
|
132
|
+
let frontmatter;
|
|
133
|
+
const match = frontmatterRegex.exec(document);
|
|
134
|
+
if (match) {
|
|
135
|
+
const frontmatterText = match[1].trim(); // Captured frontmatter content
|
|
136
|
+
contentOffset = match[0].length; // Length of the entire frontmatter section including delimiters
|
|
137
|
+
frontmatter = parseYaml(frontmatterText);
|
|
138
|
+
content = document.slice(contentOffset);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
content = document;
|
|
142
|
+
frontmatter = {};
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
content,
|
|
146
|
+
contentOffset,
|
|
147
|
+
frontmatter,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
137
150
|
export const getDocumentMap = (document) => {
|
|
151
|
+
const { frontmatter, contentOffset, content } = preProcess(document);
|
|
138
152
|
const lexer = new marked.Lexer();
|
|
139
|
-
const tokens = lexer.lex(
|
|
153
|
+
const tokens = lexer.lex(content);
|
|
154
|
+
const lineEnding = document.indexOf("\r\n") > -1 ? "\r\n" : "\n";
|
|
140
155
|
return {
|
|
141
|
-
heading: getHeadingPositions(
|
|
142
|
-
block: getBlockPositions(
|
|
156
|
+
heading: getHeadingPositions(content, tokens, contentOffset),
|
|
157
|
+
block: getBlockPositions(content, tokens, contentOffset),
|
|
158
|
+
frontmatter: frontmatter,
|
|
159
|
+
contentOffset: contentOffset,
|
|
160
|
+
lineEnding,
|
|
143
161
|
};
|
|
144
162
|
};
|
package/dist/patch.d.ts
CHANGED
|
@@ -3,7 +3,9 @@ export declare enum PatchFailureReason {
|
|
|
3
3
|
InvalidTarget = "invalid-target",
|
|
4
4
|
ContentAlreadyPreexistsInTarget = "content-already-preexists-in-target",
|
|
5
5
|
TableContentIncorrectColumnCount = "table-content-incorrect-column-count",
|
|
6
|
-
|
|
6
|
+
ContentTypeInvalid = "content-type-invalid",
|
|
7
|
+
ContentTypeInvalidForTarget = "content-type-invalid-for-target",
|
|
8
|
+
ContentNotMergeable = "content-not-mergeable"
|
|
7
9
|
}
|
|
8
10
|
export declare class PatchFailed extends Error {
|
|
9
11
|
reason: PatchFailureReason;
|
|
@@ -13,7 +15,16 @@ export declare class PatchFailed extends Error {
|
|
|
13
15
|
}
|
|
14
16
|
export declare class PatchError extends Error {
|
|
15
17
|
}
|
|
18
|
+
export declare class MergeNotPossible extends Error {
|
|
19
|
+
}
|
|
16
20
|
export declare class TablePartsNotFound extends Error {
|
|
17
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Applies a patch to the specified document.
|
|
24
|
+
*
|
|
25
|
+
* @param document The document to apply the patch to.
|
|
26
|
+
* @param instruction The patch to apply.
|
|
27
|
+
* @returns The patched document
|
|
28
|
+
*/
|
|
18
29
|
export declare const applyPatch: (document: string, instruction: PatchInstruction) => string;
|
|
19
30
|
//# sourceMappingURL=patch.d.ts.map
|
package/dist/patch.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"patch.d.ts","sourceRoot":"","sources":["../src/patch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"patch.d.ts","sourceRoot":"","sources":["../src/patch.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,4BAA4B,EAE5B,gBAAgB,EAKjB,MAAM,YAAY,CAAC;AAUpB,oBAAY,kBAAkB;IAC5B,aAAa,mBAAmB;IAChC,+BAA+B,wCAAwC;IACvE,gCAAgC,yCAAyC;IACzE,kBAAkB,yBAAyB;IAC3C,2BAA2B,oCAAoC;IAC/D,mBAAmB,0BAA0B;CAC9C;AAED,qBAAa,WAAY,SAAQ,KAAK;IAC7B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,WAAW,EAAE,gBAAgB,CAAC;IAC9B,SAAS,EAAE,4BAA4B,GAAG,IAAI,CAAC;gBAGpD,MAAM,EAAE,kBAAkB,EAC1B,WAAW,EAAE,gBAAgB,EAC7B,SAAS,EAAE,4BAA4B,GAAG,IAAI;CAUjD;AAED,qBAAa,UAAW,SAAQ,KAAK;CAAG;AAExC,qBAAa,gBAAiB,SAAQ,KAAK;CAAG;AA0C9C,qBAAa,kBAAmB,SAAQ,KAAK;CAAG;AAqWhD;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,aACX,MAAM,eACH,gBAAgB,KAC5B,MAoGF,CAAC"}
|