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
@@ -1,297 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { PatchInstruction } from "../types";
4
- import { applyPatch, PatchFailed } from "../patch";
5
-
6
- describe("patch", () => {
7
- const sample = fs.readFileSync(path.join(__dirname, "sample.md"), "utf-8");
8
-
9
- describe("heading", () => {
10
- test("prepend", () => {
11
- const expected = fs.readFileSync(
12
- path.join(__dirname, "sample.patch.heading.prepend.md"),
13
- "utf-8"
14
- );
15
- const instruction: PatchInstruction = {
16
- targetType: "heading",
17
- target: ["Overview"],
18
- operation: "prepend",
19
- content: "Beep Boop\n",
20
- };
21
-
22
- const actualResult = applyPatch(sample, instruction);
23
- expect(actualResult).toEqual(expected);
24
- });
25
- test("append", () => {
26
- const expected = fs.readFileSync(
27
- path.join(__dirname, "sample.patch.heading.append.md"),
28
- "utf-8"
29
- );
30
- const instruction: PatchInstruction = {
31
- targetType: "heading",
32
- target: ["Overview"],
33
- operation: "append",
34
- content: "Beep Boop\n",
35
- };
36
-
37
- const actualResult = applyPatch(sample, instruction);
38
- expect(actualResult).toEqual(expected);
39
- });
40
- test("replace", () => {
41
- const expected = fs.readFileSync(
42
- path.join(__dirname, "sample.patch.heading.replace.md"),
43
- "utf-8"
44
- );
45
- const instruction: PatchInstruction = {
46
- targetType: "heading",
47
- target: ["Overview"],
48
- operation: "replace",
49
- content: "Beep Boop\n",
50
- };
51
-
52
- const actualResult = applyPatch(sample, instruction);
53
- expect(actualResult).toEqual(expected);
54
- });
55
- describe("document", () => {
56
- test("prepend", () => {
57
- const expected = fs.readFileSync(
58
- path.join(__dirname, "sample.patch.heading.document.prepend.md"),
59
- "utf-8"
60
- );
61
- const instruction: PatchInstruction = {
62
- targetType: "heading",
63
- target: null,
64
- operation: "prepend",
65
- content: "Beep Boop\n",
66
- };
67
-
68
- const actualResult = applyPatch(sample, instruction);
69
- expect(actualResult).toEqual(expected);
70
- });
71
- test("append", () => {
72
- const expected = fs.readFileSync(
73
- path.join(__dirname, "sample.patch.heading.document.append.md"),
74
- "utf-8"
75
- );
76
- const instruction: PatchInstruction = {
77
- targetType: "heading",
78
- target: null,
79
- operation: "append",
80
- content: "Beep Boop\n",
81
- };
82
-
83
- const actualResult = applyPatch(sample, instruction);
84
- expect(actualResult).toEqual(expected);
85
- });
86
- });
87
- });
88
-
89
- describe("parameter", () => {
90
- describe("trimTargetWhitespace", () => {
91
- describe("heading", () => {
92
- test("prepend", () => {
93
- const expected = fs.readFileSync(
94
- path.join(
95
- __dirname,
96
- "sample.patch.heading.trimTargetWhitespace.prepend.md"
97
- ),
98
- "utf-8"
99
- );
100
- const instruction: PatchInstruction = {
101
- targetType: "heading",
102
- target: ["Page Targets", "Document Properties (Exploratory)"],
103
- operation: "prepend",
104
- content: "Beep Boop",
105
- trimTargetWhitespace: true,
106
- };
107
-
108
- const actualResult = applyPatch(sample, instruction);
109
- expect(actualResult).toEqual(expected);
110
- });
111
- test("append", () => {
112
- const expected = fs.readFileSync(
113
- path.join(
114
- __dirname,
115
- "sample.patch.heading.trimTargetWhitespace.append.md"
116
- ),
117
- "utf-8"
118
- );
119
- const instruction: PatchInstruction = {
120
- targetType: "heading",
121
- target: ["Problems"],
122
- operation: "append",
123
- content: "Beep Boop\n",
124
- trimTargetWhitespace: true,
125
- };
126
-
127
- const actualResult = applyPatch(sample, instruction);
128
- expect(actualResult).toEqual(expected);
129
- });
130
- });
131
- });
132
-
133
- describe("applyIfContentPreexists", () => {
134
- describe("disabled (default)", () => {
135
- describe("heading", () => {
136
- test("preexists at target", () => {
137
- const instruction: PatchInstruction = {
138
- targetType: "heading",
139
- target: ["Page Targets"],
140
- operation: "append",
141
- content: "## Frontmatter Field",
142
- // applyIfContentPreexists: false, # default
143
- };
144
-
145
- expect(() => {
146
- applyPatch(sample, instruction);
147
- }).toThrow(PatchFailed);
148
- });
149
- test("does not preexist at target", () => {
150
- const instruction: PatchInstruction = {
151
- targetType: "heading",
152
- target: ["Headers"],
153
- operation: "append",
154
- content: "## Frontmatter Field",
155
- // applyIfContentPreexists: false, # default
156
- };
157
-
158
- expect(() => {
159
- applyPatch(sample, instruction);
160
- }).not.toThrow(PatchFailed);
161
- });
162
- });
163
- });
164
- describe("enabled", () => {
165
- describe("heading", () => {
166
- test("preexists at target", () => {
167
- const instruction: PatchInstruction = {
168
- targetType: "heading",
169
- target: ["Page Targets"],
170
- operation: "append",
171
- content: "## Frontmatter Field",
172
- applyIfContentPreexists: true,
173
- };
174
-
175
- expect(() => {
176
- applyPatch(sample, instruction);
177
- }).not.toThrow(PatchFailed);
178
- });
179
- });
180
- });
181
- });
182
- });
183
- describe("block", () => {
184
- test("prepend", () => {
185
- const expected = fs.readFileSync(
186
- path.join(__dirname, "sample.patch.block.prepend.md"),
187
- "utf-8"
188
- );
189
- const instruction: PatchInstruction = {
190
- targetType: "block",
191
- target: "e6068e",
192
- operation: "prepend",
193
- content: "- OK\n",
194
- };
195
-
196
- const actualResult = applyPatch(sample, instruction);
197
- expect(actualResult).toEqual(expected);
198
- });
199
- test("append", () => {
200
- const expected = fs.readFileSync(
201
- path.join(__dirname, "sample.patch.block.append.md"),
202
- "utf-8"
203
- );
204
- const instruction: PatchInstruction = {
205
- targetType: "block",
206
- target: "e6068e",
207
- operation: "append",
208
- content: "\n- OK",
209
- };
210
-
211
- const actualResult = applyPatch(sample, instruction);
212
- expect(actualResult).toEqual(expected);
213
- });
214
- test("replace", () => {
215
- const expected = fs.readFileSync(
216
- path.join(__dirname, "sample.patch.block.replace.md"),
217
- "utf-8"
218
- );
219
- const instruction: PatchInstruction = {
220
- targetType: "block",
221
- target: "259a73",
222
- operation: "replace",
223
- content: "- OK",
224
- };
225
-
226
- const actualResult = applyPatch(sample, instruction);
227
- expect(actualResult).toEqual(expected);
228
- });
229
- describe("tagetBlockTypeBehavior", () => {
230
- describe("table", () => {
231
- test("prepend", () => {
232
- const expected = fs.readFileSync(
233
- path.join(
234
- __dirname,
235
- "sample.patch.block.targetBlockTypeBehavior.table.prepend.md"
236
- ),
237
- "utf-8"
238
- );
239
- const instruction: PatchInstruction = {
240
- targetType: "block",
241
- targetBlockTypeBehavior: "table",
242
- target: "2c67a6",
243
- operation: "prepend",
244
- content: [
245
- ["`something else`", "Some other application", "✅", "✅", "✅"],
246
- ],
247
- };
248
-
249
- const actualResult = applyPatch(sample, instruction);
250
- expect(actualResult).toEqual(expected);
251
- });
252
- test("append", () => {
253
- const expected = fs.readFileSync(
254
- path.join(
255
- __dirname,
256
- "sample.patch.block.targetBlockTypeBehavior.table.append.md"
257
- ),
258
- "utf-8"
259
- );
260
- const instruction: PatchInstruction = {
261
- targetType: "block",
262
- targetBlockTypeBehavior: "table",
263
- target: "2c67a6",
264
- operation: "append",
265
- content: [
266
- ["`something else`", "Some other application", "✅", "✅", "✅"],
267
- ],
268
- };
269
-
270
- const actualResult = applyPatch(sample, instruction);
271
- expect(actualResult).toEqual(expected);
272
- });
273
- test("replace", () => {
274
- const expected = fs.readFileSync(
275
- path.join(
276
- __dirname,
277
- "sample.patch.block.targetBlockTypeBehavior.table.replace.md"
278
- ),
279
- "utf-8"
280
- );
281
- const instruction: PatchInstruction = {
282
- targetType: "block",
283
- targetBlockTypeBehavior: "table",
284
- target: "2c67a6",
285
- operation: "replace",
286
- content: [
287
- ["`something else`", "Some other application", "✅", "✅", "✅"],
288
- ],
289
- };
290
-
291
- const actualResult = applyPatch(sample, instruction);
292
- expect(actualResult).toEqual(expected);
293
- });
294
- });
295
- });
296
- });
297
- });
@@ -1,81 +0,0 @@
1
- ---
2
- aliases:
3
- - Structured Markdown Patch
4
- project-type: Technical
5
- repository: https://github.com/coddingtonbear/markdown-patch
6
- ---
7
-
8
- # Overview
9
- I came up with this project for supporting [[Obsidian Local Rest API]] (and via that [[Obsidian Web]]) because I often need to shove data into Markdown documents for posterity, but want to be able to do that programmatically with more care than just shoving content to the end of a file.
10
- # Problems
11
- - ~~It would be nice for the mechanism to be able to handle something like `upsert` for frontmatter fields. See [[#^e6068e]] in addition to what we already support [[#^259a73]].~~
12
- - This was solved by making every content block directly-addressable. All interactions treat the document as a key-value mapping.
13
- - ~~You can't use our earlier header delimiter `::` in an HTTP header; how did I not notice that? I've had a [conversation with ChatGPT](https://chatgpt.com/share/117b262a-f534-40e6-bc05-287758706f34) to land on a choice of `@#@` instead, but there aren't obvious good options. See [[#^1d6271]]~~
14
- - I changed my mind in the end and went with the more-likely-to-collide-but-at-least-not-bizarre `///`.
15
- - Should we allow partial matches? The pros are that it would make the usual, garden path of just wanting to push content into a section very easy. The cons are that it makes it kind of unclear what's going to happen when you do an `upsert` or `insert` for a particular value. (See [[#^bfec1f]] for more).
16
-
17
- # Actions
18
- | Name | Description | Heading? | Frontmatter? | Block |
19
- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------ | ------------------- |
20
- | `update` | Find the referenced `target` and replace the content at that region. | ✅ | ✅ | ✅ |
21
- | `append` | Find the referenced `target` and add content to the end of its region. | ✅ | ✅ | ✅ |
22
- | `prepend` | Find the referenced `target` and add content to the beginning of its region. | ✅ | ✅ | ✅ |
23
- | `insert` | Find the path leading to the referenced `target` and add the specified content under a the specified name. This will create a new header or frontmatter field if necessary. | ✅ | ✅ | ❌[^block-ambiguity] |
24
- | `upsert` | Find the path leading to the referenced `target` and either replace the content under the specified name, or add new content with a new header or frontmatter field if necessary. | ✅ | ✅ | ❌[^block-ambiguity] |
25
-
26
- ^2c67a6
27
-
28
- # Headers
29
- | Header | Explanation |
30
- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
31
- | `Target-Type` | `heading`, `block`, `frontmatter` |
32
- | `Target` | Name of the target:<br>- `heading`: The `///`-delimited path to the heading to replace the content of. E.g. `Page Targets///Block///Use Cases`. This value should be URL-encoded. |
33
- | `Target-Delimiter` | By default, we use `///` to delimit a `Target`, but it's remotely possible that this value might be present in a header. If it is, you can specify a different delimiter to use for `Target`. |
34
-
35
- ^1d6271
36
-
37
- # Page Targets
38
-
39
- ## Heading
40
-
41
- | Heading | Value |
42
- | ------------- | ----------------------------------------------------- |
43
- | `Target-Type` | `heading` |
44
- | `Target` | The path to the heading you would like to append to. |
45
-
46
- ^bfec1f
47
-
48
- | Position | Where |
49
- | -------- | -------------------------------------------------------------- |
50
- | Start | Beginning of line immediately following line named by heading |
51
- | End | Last newline before heading of same or higher priority or EOF. |
52
- | | |
53
- Unlike with [[#Heading]], this targets the *content* of the heading and does not include the heading iself.
54
- - ✅: ...replacing the content specified by a particular heading.
55
- - ✅: ...appending content to a block specified by a particular heading.
56
- ## Block
57
- | Position | Where |
58
- | -------- | ------------------------------------------------------ |
59
- | Start | Beginning of line for specified block. |
60
- | End | Last character (including newline) of specified block. |
61
- A "Block" in Obsidian can be any *block*-type element. This might mean a paragraph, but it could also mean a table, but how block references are marked differs in Obsidian depending upon what kind of block is being marked.
62
- ### Use Cases
63
- - ✅: ...replacing the content specified by a particular block ID.
64
- - I want to be able to replace a whole table or whole paragraph with new content.
65
- - ✅: ...appending content to a block specified by a particular block ID.
66
- - I want to be able to add new rows to an existing table.
67
- - I want to be able to add new content to the end of a line.
68
-
69
- ## Frontmatter Field
70
-
71
- | Position | Where |
72
- | -------- | ------------------------------------------------------------------------ |
73
- | Start | First character of content referenced by a particular frontmatter field. |
74
- | End | Last character of content referenced by a particular frontmatter field. |
75
- ### Use Cases
76
- - ✅: ...appending content to an existing frontmatter field.
77
- - ✅: ...replacing the content of an existing frontmatter field. ^259a73
78
- - ✅: ...adding a new frontmatter field. ^e6068e
79
- ## Document Properties (Exploratory)
80
-
81
- [^block-ambiguity]: There is currently no obvious place to plop a block were we to create a new one. So, I might implement this such that these actions *work*, but will just add the block to the end of a file. This isn't great, but it's at least consistent?
@@ -1,82 +0,0 @@
1
- ---
2
- aliases:
3
- - Structured Markdown Patch
4
- project-type: Technical
5
- repository: https://github.com/coddingtonbear/markdown-patch
6
- ---
7
-
8
- # Overview
9
- I came up with this project for supporting [[Obsidian Local Rest API]] (and via that [[Obsidian Web]]) because I often need to shove data into Markdown documents for posterity, but want to be able to do that programmatically with more care than just shoving content to the end of a file.
10
- # Problems
11
- - ~~It would be nice for the mechanism to be able to handle something like `upsert` for frontmatter fields. See [[#^e6068e]] in addition to what we already support [[#^259a73]].~~
12
- - This was solved by making every content block directly-addressable. All interactions treat the document as a key-value mapping.
13
- - ~~You can't use our earlier header delimiter `::` in an HTTP header; how did I not notice that? I've had a [conversation with ChatGPT](https://chatgpt.com/share/117b262a-f534-40e6-bc05-287758706f34) to land on a choice of `@#@` instead, but there aren't obvious good options. See [[#^1d6271]]~~
14
- - I changed my mind in the end and went with the more-likely-to-collide-but-at-least-not-bizarre `///`.
15
- - Should we allow partial matches? The pros are that it would make the usual, garden path of just wanting to push content into a section very easy. The cons are that it makes it kind of unclear what's going to happen when you do an `upsert` or `insert` for a particular value. (See [[#^bfec1f]] for more).
16
-
17
- # Actions
18
- | Name | Description | Heading? | Frontmatter? | Block |
19
- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------ | ------------------- |
20
- | `update` | Find the referenced `target` and replace the content at that region. | ✅ | ✅ | ✅ |
21
- | `append` | Find the referenced `target` and add content to the end of its region. | ✅ | ✅ | ✅ |
22
- | `prepend` | Find the referenced `target` and add content to the beginning of its region. | ✅ | ✅ | ✅ |
23
- | `insert` | Find the path leading to the referenced `target` and add the specified content under a the specified name. This will create a new header or frontmatter field if necessary. | ✅ | ✅ | ❌[^block-ambiguity] |
24
- | `upsert` | Find the path leading to the referenced `target` and either replace the content under the specified name, or add new content with a new header or frontmatter field if necessary. | ✅ | ✅ | ❌[^block-ambiguity] |
25
-
26
- ^2c67a6
27
-
28
- # Headers
29
- | Header | Explanation |
30
- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
31
- | `Target-Type` | `heading`, `block`, `frontmatter` |
32
- | `Target` | Name of the target:<br>- `heading`: The `///`-delimited path to the heading to replace the content of. E.g. `Page Targets///Block///Use Cases`. This value should be URL-encoded. |
33
- | `Target-Delimiter` | By default, we use `///` to delimit a `Target`, but it's remotely possible that this value might be present in a header. If it is, you can specify a different delimiter to use for `Target`. |
34
-
35
- ^1d6271
36
-
37
- # Page Targets
38
-
39
- ## Heading
40
-
41
- | Heading | Value |
42
- | ------------- | ----------------------------------------------------- |
43
- | `Target-Type` | `heading` |
44
- | `Target` | The path to the heading you would like to append to. |
45
-
46
- ^bfec1f
47
-
48
- | Position | Where |
49
- | -------- | -------------------------------------------------------------- |
50
- | Start | Beginning of line immediately following line named by heading |
51
- | End | Last newline before heading of same or higher priority or EOF. |
52
- | | |
53
- Unlike with [[#Heading]], this targets the *content* of the heading and does not include the heading iself.
54
- - ✅: ...replacing the content specified by a particular heading.
55
- - ✅: ...appending content to a block specified by a particular heading.
56
- ## Block
57
- | Position | Where |
58
- | -------- | ------------------------------------------------------ |
59
- | Start | Beginning of line for specified block. |
60
- | End | Last character (including newline) of specified block. |
61
- A "Block" in Obsidian can be any *block*-type element. This might mean a paragraph, but it could also mean a table, but how block references are marked differs in Obsidian depending upon what kind of block is being marked.
62
- ### Use Cases
63
- - ✅: ...replacing the content specified by a particular block ID.
64
- - I want to be able to replace a whole table or whole paragraph with new content.
65
- - ✅: ...appending content to a block specified by a particular block ID.
66
- - I want to be able to add new rows to an existing table.
67
- - I want to be able to add new content to the end of a line.
68
-
69
- ## Frontmatter Field
70
-
71
- | Position | Where |
72
- | -------- | ------------------------------------------------------------------------ |
73
- | Start | First character of content referenced by a particular frontmatter field. |
74
- | End | Last character of content referenced by a particular frontmatter field. |
75
- ### Use Cases
76
- - ✅: ...appending content to an existing frontmatter field.
77
- - ✅: ...replacing the content of an existing frontmatter field. ^259a73
78
- - ✅: ...adding a new frontmatter field.
79
- - OK ^e6068e
80
- ## Document Properties (Exploratory)
81
-
82
- [^block-ambiguity]: There is currently no obvious place to plop a block were we to create a new one. So, I might implement this such that these actions *work*, but will just add the block to the end of a file. This isn't great, but it's at least consistent?
@@ -1,82 +0,0 @@
1
- ---
2
- aliases:
3
- - Structured Markdown Patch
4
- project-type: Technical
5
- repository: https://github.com/coddingtonbear/markdown-patch
6
- ---
7
-
8
- # Overview
9
- I came up with this project for supporting [[Obsidian Local Rest API]] (and via that [[Obsidian Web]]) because I often need to shove data into Markdown documents for posterity, but want to be able to do that programmatically with more care than just shoving content to the end of a file.
10
- # Problems
11
- - ~~It would be nice for the mechanism to be able to handle something like `upsert` for frontmatter fields. See [[#^e6068e]] in addition to what we already support [[#^259a73]].~~
12
- - This was solved by making every content block directly-addressable. All interactions treat the document as a key-value mapping.
13
- - ~~You can't use our earlier header delimiter `::` in an HTTP header; how did I not notice that? I've had a [conversation with ChatGPT](https://chatgpt.com/share/117b262a-f534-40e6-bc05-287758706f34) to land on a choice of `@#@` instead, but there aren't obvious good options. See [[#^1d6271]]~~
14
- - I changed my mind in the end and went with the more-likely-to-collide-but-at-least-not-bizarre `///`.
15
- - Should we allow partial matches? The pros are that it would make the usual, garden path of just wanting to push content into a section very easy. The cons are that it makes it kind of unclear what's going to happen when you do an `upsert` or `insert` for a particular value. (See [[#^bfec1f]] for more).
16
-
17
- # Actions
18
- | Name | Description | Heading? | Frontmatter? | Block |
19
- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------ | ------------------- |
20
- | `update` | Find the referenced `target` and replace the content at that region. | ✅ | ✅ | ✅ |
21
- | `append` | Find the referenced `target` and add content to the end of its region. | ✅ | ✅ | ✅ |
22
- | `prepend` | Find the referenced `target` and add content to the beginning of its region. | ✅ | ✅ | ✅ |
23
- | `insert` | Find the path leading to the referenced `target` and add the specified content under a the specified name. This will create a new header or frontmatter field if necessary. | ✅ | ✅ | ❌[^block-ambiguity] |
24
- | `upsert` | Find the path leading to the referenced `target` and either replace the content under the specified name, or add new content with a new header or frontmatter field if necessary. | ✅ | ✅ | ❌[^block-ambiguity] |
25
-
26
- ^2c67a6
27
-
28
- # Headers
29
- | Header | Explanation |
30
- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
31
- | `Target-Type` | `heading`, `block`, `frontmatter` |
32
- | `Target` | Name of the target:<br>- `heading`: The `///`-delimited path to the heading to replace the content of. E.g. `Page Targets///Block///Use Cases`. This value should be URL-encoded. |
33
- | `Target-Delimiter` | By default, we use `///` to delimit a `Target`, but it's remotely possible that this value might be present in a header. If it is, you can specify a different delimiter to use for `Target`. |
34
-
35
- ^1d6271
36
-
37
- # Page Targets
38
-
39
- ## Heading
40
-
41
- | Heading | Value |
42
- | ------------- | ----------------------------------------------------- |
43
- | `Target-Type` | `heading` |
44
- | `Target` | The path to the heading you would like to append to. |
45
-
46
- ^bfec1f
47
-
48
- | Position | Where |
49
- | -------- | -------------------------------------------------------------- |
50
- | Start | Beginning of line immediately following line named by heading |
51
- | End | Last newline before heading of same or higher priority or EOF. |
52
- | | |
53
- Unlike with [[#Heading]], this targets the *content* of the heading and does not include the heading iself.
54
- - ✅: ...replacing the content specified by a particular heading.
55
- - ✅: ...appending content to a block specified by a particular heading.
56
- ## Block
57
- | Position | Where |
58
- | -------- | ------------------------------------------------------ |
59
- | Start | Beginning of line for specified block. |
60
- | End | Last character (including newline) of specified block. |
61
- A "Block" in Obsidian can be any *block*-type element. This might mean a paragraph, but it could also mean a table, but how block references are marked differs in Obsidian depending upon what kind of block is being marked.
62
- ### Use Cases
63
- - ✅: ...replacing the content specified by a particular block ID.
64
- - I want to be able to replace a whole table or whole paragraph with new content.
65
- - ✅: ...appending content to a block specified by a particular block ID.
66
- - I want to be able to add new rows to an existing table.
67
- - I want to be able to add new content to the end of a line.
68
-
69
- ## Frontmatter Field
70
-
71
- | Position | Where |
72
- | -------- | ------------------------------------------------------------------------ |
73
- | Start | First character of content referenced by a particular frontmatter field. |
74
- | End | Last character of content referenced by a particular frontmatter field. |
75
- ### Use Cases
76
- - ✅: ...appending content to an existing frontmatter field.
77
- - ✅: ...replacing the content of an existing frontmatter field. ^259a73
78
- - OK
79
- - ✅: ...adding a new frontmatter field. ^e6068e
80
- ## Document Properties (Exploratory)
81
-
82
- [^block-ambiguity]: There is currently no obvious place to plop a block were we to create a new one. So, I might implement this such that these actions *work*, but will just add the block to the end of a file. This isn't great, but it's at least consistent?
@@ -1,81 +0,0 @@
1
- ---
2
- aliases:
3
- - Structured Markdown Patch
4
- project-type: Technical
5
- repository: https://github.com/coddingtonbear/markdown-patch
6
- ---
7
-
8
- # Overview
9
- I came up with this project for supporting [[Obsidian Local Rest API]] (and via that [[Obsidian Web]]) because I often need to shove data into Markdown documents for posterity, but want to be able to do that programmatically with more care than just shoving content to the end of a file.
10
- # Problems
11
- - ~~It would be nice for the mechanism to be able to handle something like `upsert` for frontmatter fields. See [[#^e6068e]] in addition to what we already support [[#^259a73]].~~
12
- - This was solved by making every content block directly-addressable. All interactions treat the document as a key-value mapping.
13
- - ~~You can't use our earlier header delimiter `::` in an HTTP header; how did I not notice that? I've had a [conversation with ChatGPT](https://chatgpt.com/share/117b262a-f534-40e6-bc05-287758706f34) to land on a choice of `@#@` instead, but there aren't obvious good options. See [[#^1d6271]]~~
14
- - I changed my mind in the end and went with the more-likely-to-collide-but-at-least-not-bizarre `///`.
15
- - Should we allow partial matches? The pros are that it would make the usual, garden path of just wanting to push content into a section very easy. The cons are that it makes it kind of unclear what's going to happen when you do an `upsert` or `insert` for a particular value. (See [[#^bfec1f]] for more).
16
-
17
- # Actions
18
- | Name | Description | Heading? | Frontmatter? | Block |
19
- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------ | ------------------- |
20
- | `update` | Find the referenced `target` and replace the content at that region. | ✅ | ✅ | ✅ |
21
- | `append` | Find the referenced `target` and add content to the end of its region. | ✅ | ✅ | ✅ |
22
- | `prepend` | Find the referenced `target` and add content to the beginning of its region. | ✅ | ✅ | ✅ |
23
- | `insert` | Find the path leading to the referenced `target` and add the specified content under a the specified name. This will create a new header or frontmatter field if necessary. | ✅ | ✅ | ❌[^block-ambiguity] |
24
- | `upsert` | Find the path leading to the referenced `target` and either replace the content under the specified name, or add new content with a new header or frontmatter field if necessary. | ✅ | ✅ | ❌[^block-ambiguity] |
25
-
26
- ^2c67a6
27
-
28
- # Headers
29
- | Header | Explanation |
30
- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
31
- | `Target-Type` | `heading`, `block`, `frontmatter` |
32
- | `Target` | Name of the target:<br>- `heading`: The `///`-delimited path to the heading to replace the content of. E.g. `Page Targets///Block///Use Cases`. This value should be URL-encoded. |
33
- | `Target-Delimiter` | By default, we use `///` to delimit a `Target`, but it's remotely possible that this value might be present in a header. If it is, you can specify a different delimiter to use for `Target`. |
34
-
35
- ^1d6271
36
-
37
- # Page Targets
38
-
39
- ## Heading
40
-
41
- | Heading | Value |
42
- | ------------- | ----------------------------------------------------- |
43
- | `Target-Type` | `heading` |
44
- | `Target` | The path to the heading you would like to append to. |
45
-
46
- ^bfec1f
47
-
48
- | Position | Where |
49
- | -------- | -------------------------------------------------------------- |
50
- | Start | Beginning of line immediately following line named by heading |
51
- | End | Last newline before heading of same or higher priority or EOF. |
52
- | | |
53
- Unlike with [[#Heading]], this targets the *content* of the heading and does not include the heading iself.
54
- - ✅: ...replacing the content specified by a particular heading.
55
- - ✅: ...appending content to a block specified by a particular heading.
56
- ## Block
57
- | Position | Where |
58
- | -------- | ------------------------------------------------------ |
59
- | Start | Beginning of line for specified block. |
60
- | End | Last character (including newline) of specified block. |
61
- A "Block" in Obsidian can be any *block*-type element. This might mean a paragraph, but it could also mean a table, but how block references are marked differs in Obsidian depending upon what kind of block is being marked.
62
- ### Use Cases
63
- - ✅: ...replacing the content specified by a particular block ID.
64
- - I want to be able to replace a whole table or whole paragraph with new content.
65
- - ✅: ...appending content to a block specified by a particular block ID.
66
- - I want to be able to add new rows to an existing table.
67
- - I want to be able to add new content to the end of a line.
68
-
69
- ## Frontmatter Field
70
-
71
- | Position | Where |
72
- | -------- | ------------------------------------------------------------------------ |
73
- | Start | First character of content referenced by a particular frontmatter field. |
74
- | End | Last character of content referenced by a particular frontmatter field. |
75
- ### Use Cases
76
- - ✅: ...appending content to an existing frontmatter field.
77
- - OK ^259a73
78
- - ✅: ...adding a new frontmatter field. ^e6068e
79
- ## Document Properties (Exploratory)
80
-
81
- [^block-ambiguity]: There is currently no obvious place to plop a block were we to create a new one. So, I might implement this such that these actions *work*, but will just add the block to the end of a file. This isn't great, but it's at least consistent?