@effected/templates 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CommentStyle.js +104 -0
- package/LICENSE +21 -0
- package/ManagedSection.js +211 -0
- package/Section.js +126 -0
- package/SectionDialect.js +164 -0
- package/SectionDocument.js +197 -0
- package/SectionOutcome.js +20 -0
- package/index.d.ts +812 -0
- package/index.js +8 -0
- package/internal/reconcile.js +113 -0
- package/internal/scan.js +135 -0
- package/package.json +45 -0
- package/tsdoc-metadata.json +11 -0
package/index.d.ts
ADDED
|
@@ -0,0 +1,812 @@
|
|
|
1
|
+
import { Context, Data, Effect, FileSystem, Layer, Option, Result, Schema } from "effect";
|
|
2
|
+
//#region src/CommentStyle.d.ts
|
|
3
|
+
declare const CommentStyle_base: Schema.Class<CommentStyle, Schema.Struct<{
|
|
4
|
+
/** Opens the comment. Non-empty, single-line. */
|
|
5
|
+
readonly prefix: Schema.String;
|
|
6
|
+
/** Closes the comment, for wrapped styles. Omitted for line styles. */
|
|
7
|
+
readonly suffix: Schema.optionalKey<Schema.String>;
|
|
8
|
+
}>, {}>;
|
|
9
|
+
/**
|
|
10
|
+
* How a managed section's markers are commented out in a given file format.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* A **line** style carries only a `prefix` (`#`, `//`); a **wrapped** style
|
|
14
|
+
* carries a `suffix` as well (`<!--` … `-->`). The wrapped form is what makes
|
|
15
|
+
* managed sections representable in Markdown, HTML and XML, which the two-way
|
|
16
|
+
* literal union this replaces could not express.
|
|
17
|
+
*
|
|
18
|
+
* The preset set is a convenience, not a closed world: a format nobody
|
|
19
|
+
* anticipated is one `CommentStyle.make({ prefix: "%" })` away.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { CommentStyle } from "@effected/templates";
|
|
24
|
+
*
|
|
25
|
+
* CommentStyle.hash; // # …
|
|
26
|
+
* CommentStyle.html; // <!-- … -->
|
|
27
|
+
* CommentStyle.make({ prefix: "(*", suffix: "*)" }); // ML, no preset needed
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
declare class CommentStyle extends CommentStyle_base {
|
|
33
|
+
/** Shell, YAML, TOML, Python, Dockerfile, `.env`. */
|
|
34
|
+
static readonly hash: CommentStyle;
|
|
35
|
+
/** JavaScript, TypeScript, C, Go, Rust, JSONC. */
|
|
36
|
+
static readonly slash: CommentStyle;
|
|
37
|
+
/** INI, Lisp, assembly. */
|
|
38
|
+
static readonly semicolon: CommentStyle;
|
|
39
|
+
/** SQL, Lua, Haskell. */
|
|
40
|
+
static readonly dash: CommentStyle;
|
|
41
|
+
/** Markdown, HTML, XML — the wrapped style v3 could not represent. */
|
|
42
|
+
static readonly html: CommentStyle;
|
|
43
|
+
/** CSS, and the block form of every C-family language. */
|
|
44
|
+
static readonly block: CommentStyle;
|
|
45
|
+
/**
|
|
46
|
+
* Every preset, in the order a reader would expect: line styles first,
|
|
47
|
+
* then wrapped.
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* A grouped static over variants of one concept that live in one module
|
|
51
|
+
* and reach nothing heavier than each other — nothing sits behind a preset
|
|
52
|
+
* but a two-field object, so this is not the namespace-object hazard.
|
|
53
|
+
*/
|
|
54
|
+
static readonly presets: ReadonlyArray<CommentStyle>;
|
|
55
|
+
/**
|
|
56
|
+
* A stable string identity, for keying a plain `Map`.
|
|
57
|
+
*
|
|
58
|
+
* @remarks
|
|
59
|
+
* The `NUL` separator is not cosmetic: without it `{ prefix: "ab" }`
|
|
60
|
+
* and `{ prefix: "a", suffix: "b" }` would produce the same id and two
|
|
61
|
+
* genuinely different styles would collide in a lookup table. `NUL`
|
|
62
|
+
* cannot occur in a delimiter, which the single-line check guarantees is
|
|
63
|
+
* printable-ish, so the encoding is unambiguous.
|
|
64
|
+
*/
|
|
65
|
+
get id(): string;
|
|
66
|
+
/** True when the style wraps its content, i.e. it carries a suffix. */
|
|
67
|
+
get isWrapped(): boolean;
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/Section.d.ts
|
|
71
|
+
/**
|
|
72
|
+
* The name identifying a managed section, as it literally appears in the
|
|
73
|
+
* file's markers.
|
|
74
|
+
*
|
|
75
|
+
* @remarks
|
|
76
|
+
* Keys are **case-sensitive** and rendered verbatim: the key a consumer
|
|
77
|
+
* declares is the key the marker carries. A file already containing
|
|
78
|
+
* `SAVVY-LINT` markers is managed by declaring the key `"SAVVY-LINT"`.
|
|
79
|
+
*
|
|
80
|
+
* The character set is bounded so a key can always be read back out of a
|
|
81
|
+
* marker unambiguously — whitespace in particular would make the scanner's
|
|
82
|
+
* key capture ambiguous against the marker phrase that follows it.
|
|
83
|
+
*
|
|
84
|
+
* This is a checked string rather than a branded one **deliberately**: a
|
|
85
|
+
* brand would force every call site through a decode step to spell
|
|
86
|
+
* `SectionId.make({ key: "example-tool", … })`, and the validation a brand
|
|
87
|
+
* would carry is already enforced by the check at construction.
|
|
88
|
+
*
|
|
89
|
+
* @public
|
|
90
|
+
*/
|
|
91
|
+
declare const SectionKey: Schema.String;
|
|
92
|
+
declare const SectionId_base: Schema.Class<SectionId, Schema.Struct<{
|
|
93
|
+
/** The section's name, exactly as it appears in the markers. */
|
|
94
|
+
readonly key: Schema.String;
|
|
95
|
+
/** How this section's markers are commented out. */
|
|
96
|
+
readonly commentStyle: typeof CommentStyle;
|
|
97
|
+
}>, {}>;
|
|
98
|
+
/**
|
|
99
|
+
* What identifies a managed section inside a document: its key and the
|
|
100
|
+
* comment style its markers are written in.
|
|
101
|
+
*
|
|
102
|
+
* @remarks
|
|
103
|
+
* `commentStyle` is **required, with no default**. A defaulted style is how a
|
|
104
|
+
* caller who forgets the argument writes `#` markers into a TypeScript file —
|
|
105
|
+
* a syntax error in the user's own source, produced silently by an omission.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* import { CommentStyle, SectionId } from "@effected/templates";
|
|
110
|
+
*
|
|
111
|
+
* const ToolSection = SectionId.make({
|
|
112
|
+
* key: "example-tool",
|
|
113
|
+
* commentStyle: CommentStyle.hash,
|
|
114
|
+
* });
|
|
115
|
+
*
|
|
116
|
+
* const block = ToolSection.section("echo hello");
|
|
117
|
+
* ```
|
|
118
|
+
*
|
|
119
|
+
* @public
|
|
120
|
+
*/
|
|
121
|
+
declare class SectionId extends SectionId_base {
|
|
122
|
+
/** Pair this identity with the content a tool wants inside it. */
|
|
123
|
+
section(content: string): Section;
|
|
124
|
+
}
|
|
125
|
+
declare const Section_base: Schema.Class<Section, Schema.Struct<{
|
|
126
|
+
/** The section's name, exactly as it appears in the markers. */
|
|
127
|
+
readonly key: Schema.String;
|
|
128
|
+
/** How this section's markers are commented out. */
|
|
129
|
+
readonly commentStyle: typeof CommentStyle;
|
|
130
|
+
/** Everything between the markers, exclusive of the boundary line breaks. */
|
|
131
|
+
readonly content: Schema.String;
|
|
132
|
+
}>, {}>;
|
|
133
|
+
/**
|
|
134
|
+
* A managed section: an identity plus the content its owner wants between the
|
|
135
|
+
* markers.
|
|
136
|
+
*
|
|
137
|
+
* @remarks
|
|
138
|
+
* Equality is **structural and whitespace-significant**. The v3 model this
|
|
139
|
+
* replaces compared trimmed, whitespace-collapsed content, which silently
|
|
140
|
+
* swallowed any template change that altered only indentation — the change
|
|
141
|
+
* compared equal, reported `Unchanged`, and never reached the file. The one
|
|
142
|
+
* normalization this package applies is to line endings, and it happens at
|
|
143
|
+
* parse time rather than inside equality, so `Equal.equals` stays honest for
|
|
144
|
+
* a consumer comparing two sections directly.
|
|
145
|
+
*
|
|
146
|
+
* @public
|
|
147
|
+
*/
|
|
148
|
+
declare class Section extends Section_base {
|
|
149
|
+
/** This section's identity, without its content. */
|
|
150
|
+
get id(): SectionId;
|
|
151
|
+
/** The same section carrying different content. */
|
|
152
|
+
withContent(content: string): Section;
|
|
153
|
+
}
|
|
154
|
+
declare const PlacedSection_base: Schema.Class<PlacedSection, Schema.Struct<{
|
|
155
|
+
/** The section, with line endings already normalized to `\n`. */
|
|
156
|
+
readonly section: typeof Section;
|
|
157
|
+
/** Offset of the begin marker's first character. */
|
|
158
|
+
readonly start: Schema.Number;
|
|
159
|
+
/** Offset one past the end marker's last character. */
|
|
160
|
+
readonly end: Schema.Number;
|
|
161
|
+
/** 1-based line of the begin marker. */
|
|
162
|
+
readonly line: Schema.Number;
|
|
163
|
+
}>, {}>;
|
|
164
|
+
/**
|
|
165
|
+
* A managed section as found in a document, carrying the span it occupies.
|
|
166
|
+
*
|
|
167
|
+
* @remarks
|
|
168
|
+
* `start` and `end` bound the **whole** block, from the first character of
|
|
169
|
+
* the begin marker to one past the last character of the end marker, so
|
|
170
|
+
* `text.slice(start, end)` is exactly the block as written. `line` is 1-based
|
|
171
|
+
* and points at the begin marker, which is what a diagnostic needs.
|
|
172
|
+
*
|
|
173
|
+
* @public
|
|
174
|
+
*/
|
|
175
|
+
declare class PlacedSection extends PlacedSection_base {}
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/SectionDialect.d.ts
|
|
178
|
+
/**
|
|
179
|
+
* The line ending a document uses.
|
|
180
|
+
*
|
|
181
|
+
* @public
|
|
182
|
+
*/
|
|
183
|
+
type Eol = "\n" | "\r\n";
|
|
184
|
+
declare const SectionRenderError_base: Schema.Class<SectionRenderError, Schema.TaggedStruct<"SectionRenderError", {
|
|
185
|
+
/**
|
|
186
|
+
* `markerInContent` — the content carries a line the scanner would read as
|
|
187
|
+
* a marker, which would move the block boundary and let the next sync
|
|
188
|
+
* consume user text. `unknownCommentStyle` — the section's comment style
|
|
189
|
+
* is not in the dialect's set, so the block would be written into a
|
|
190
|
+
* document where the scanner could never find it again, growing a
|
|
191
|
+
* duplicate on every run. `duplicateDeclaration` — the same identity was
|
|
192
|
+
* declared twice in one call, so the caller stated two intentions for one
|
|
193
|
+
* block and any choice between them would be a guess; this is the
|
|
194
|
+
* caller-side twin of the document-side `duplicateSection`.
|
|
195
|
+
*/
|
|
196
|
+
readonly reason: Schema.Literals<readonly ["markerInContent", "unknownCommentStyle", "duplicateDeclaration"]>;
|
|
197
|
+
/** The key of the section that could not be rendered. */
|
|
198
|
+
readonly key: Schema.String;
|
|
199
|
+
}>, import("effect/Cause").YieldableError>;
|
|
200
|
+
/**
|
|
201
|
+
* Raised when a section cannot be turned into marker-delimited text.
|
|
202
|
+
*
|
|
203
|
+
* @remarks
|
|
204
|
+
* Both reasons describe a document this package would be unable to read back
|
|
205
|
+
* correctly, so rendering refuses rather than writing something it cannot
|
|
206
|
+
* re-parse.
|
|
207
|
+
*
|
|
208
|
+
* @public
|
|
209
|
+
*/
|
|
210
|
+
declare class SectionRenderError extends SectionRenderError_base {
|
|
211
|
+
get message(): string;
|
|
212
|
+
}
|
|
213
|
+
declare const SectionDialect_base: Schema.Class<SectionDialect, Schema.Struct<{
|
|
214
|
+
/**
|
|
215
|
+
* The phrase between the key and the closing rule.
|
|
216
|
+
*
|
|
217
|
+
* @remarks
|
|
218
|
+
* Letters, digits, spaces and underscores only. Dashes are excluded so a
|
|
219
|
+
* phrase can never contain the `---` rule and make a marker ambiguous
|
|
220
|
+
* against itself.
|
|
221
|
+
*/
|
|
222
|
+
readonly phrase: Schema.String;
|
|
223
|
+
/** Which comment styles the document scanner recognizes. At least one. */
|
|
224
|
+
readonly styles: Schema.$Array<typeof CommentStyle>;
|
|
225
|
+
}>, {}>;
|
|
226
|
+
/**
|
|
227
|
+
* The marker vocabulary: what phrase delimits a managed section, and which
|
|
228
|
+
* comment styles a document is scanned for.
|
|
229
|
+
*
|
|
230
|
+
* @remarks
|
|
231
|
+
* `styles` exists because reconciliation must **recognize sections it does
|
|
232
|
+
* not own** — a foreign tool's block in the same file is preserved verbatim
|
|
233
|
+
* and must never be mistaken for prose. That set cannot be derived from the
|
|
234
|
+
* caller's own sections, because a foreign block's style may appear nowhere
|
|
235
|
+
* in them.
|
|
236
|
+
*
|
|
237
|
+
* One phrase per dialect is deliberate: two marker families in one document
|
|
238
|
+
* make parsing ambiguous for no benefit anyone has asked for.
|
|
239
|
+
*
|
|
240
|
+
* @public
|
|
241
|
+
*/
|
|
242
|
+
declare class SectionDialect extends SectionDialect_base {
|
|
243
|
+
/**
|
|
244
|
+
* The zero-configuration dialect: the phrase `MANAGED SECTION` and every
|
|
245
|
+
* preset comment style.
|
|
246
|
+
*/
|
|
247
|
+
static readonly default: SectionDialect;
|
|
248
|
+
/** True when a section written in this style can be scanned back. */
|
|
249
|
+
recognizes(style: CommentStyle): boolean;
|
|
250
|
+
/** The opening marker line for an identity, without a line break. */
|
|
251
|
+
beginMarker(id: SectionId): string;
|
|
252
|
+
/** The closing marker line for an identity, without a line break. */
|
|
253
|
+
endMarker(id: SectionId): string;
|
|
254
|
+
/**
|
|
255
|
+
* A section as marker-delimited text, or a typed refusal.
|
|
256
|
+
*
|
|
257
|
+
* @remarks
|
|
258
|
+
* Fails rather than producing a document it could not read back: see
|
|
259
|
+
* {@link SectionRenderError}. Content is emitted with `eol` throughout,
|
|
260
|
+
* so a section rendered into a CRLF document stays CRLF.
|
|
261
|
+
*/
|
|
262
|
+
render(section: Section, eol?: Eol): Result.Result<string, SectionRenderError>;
|
|
263
|
+
/**
|
|
264
|
+
* True when `text` contains a line this dialect would read as a marker.
|
|
265
|
+
*
|
|
266
|
+
* @internal
|
|
267
|
+
*/
|
|
268
|
+
containsMarker(text: string): boolean;
|
|
269
|
+
/**
|
|
270
|
+
* One compiled scanner per recognized comment style, memoized.
|
|
271
|
+
*
|
|
272
|
+
* @remarks
|
|
273
|
+
* Each pattern is anchored per line, bounds the key with an explicit
|
|
274
|
+
* character class, and carries no nested quantifier, so scanning is linear
|
|
275
|
+
* in document length. Every caller-supplied fragment — prefix, suffix,
|
|
276
|
+
* phrase — is regex-escaped before interpolation.
|
|
277
|
+
*
|
|
278
|
+
* @internal
|
|
279
|
+
*/
|
|
280
|
+
matchers(): ReadonlyArray<{
|
|
281
|
+
readonly style: CommentStyle;
|
|
282
|
+
readonly regex: RegExp;
|
|
283
|
+
}>;
|
|
284
|
+
private marker;
|
|
285
|
+
}
|
|
286
|
+
//#endregion
|
|
287
|
+
//#region src/SectionOutcome.d.ts
|
|
288
|
+
/**
|
|
289
|
+
* What a sync did to one declared section.
|
|
290
|
+
*
|
|
291
|
+
* @remarks
|
|
292
|
+
* `Updated` carries **both sides** rather than a diff. The model this replaces
|
|
293
|
+
* computed a set difference over lines, which deduplicated, ignored order, and
|
|
294
|
+
* reported a pure line reordering as no change at all — a diff that could say
|
|
295
|
+
* "nothing changed" about changed content. Every consumer of that model read
|
|
296
|
+
* only the tag, so the honest primitive is the pair; a caller wanting a
|
|
297
|
+
* rendering diffs `before.content` against `after.content` with the diff
|
|
298
|
+
* library of its choice.
|
|
299
|
+
*
|
|
300
|
+
* @public
|
|
301
|
+
*/
|
|
302
|
+
type SyncOutcome = Data.TaggedEnum<{
|
|
303
|
+
/** The section was not in the document and has been added. */
|
|
304
|
+
readonly Created: {
|
|
305
|
+
readonly section: Section;
|
|
306
|
+
};
|
|
307
|
+
/** The section was present with different content and has been rewritten. */
|
|
308
|
+
readonly Updated: {
|
|
309
|
+
readonly before: Section;
|
|
310
|
+
readonly after: Section;
|
|
311
|
+
};
|
|
312
|
+
/** The section was already exactly as declared. */
|
|
313
|
+
readonly Unchanged: {
|
|
314
|
+
readonly section: Section;
|
|
315
|
+
};
|
|
316
|
+
}>;
|
|
317
|
+
/**
|
|
318
|
+
* Constructors and matchers for `SyncOutcome` — e.g.
|
|
319
|
+
* `SyncOutcome.Created({ section })`, `SyncOutcome.$is("Updated")`.
|
|
320
|
+
*
|
|
321
|
+
* @public
|
|
322
|
+
*/
|
|
323
|
+
declare const SyncOutcome: {
|
|
324
|
+
readonly $is: <Tag extends "Created" | "Unchanged" | "Updated">(tag: Tag) => (u: unknown) => u is Extract<{
|
|
325
|
+
readonly _tag: "Created";
|
|
326
|
+
readonly section: Section;
|
|
327
|
+
}, {
|
|
328
|
+
readonly _tag: Tag;
|
|
329
|
+
}> | Extract<{
|
|
330
|
+
readonly _tag: "Unchanged";
|
|
331
|
+
readonly section: Section;
|
|
332
|
+
}, {
|
|
333
|
+
readonly _tag: Tag;
|
|
334
|
+
}> | Extract<{
|
|
335
|
+
readonly _tag: "Updated";
|
|
336
|
+
readonly before: Section;
|
|
337
|
+
readonly after: Section;
|
|
338
|
+
}, {
|
|
339
|
+
readonly _tag: Tag;
|
|
340
|
+
}>;
|
|
341
|
+
readonly $match: {
|
|
342
|
+
<Cases extends {
|
|
343
|
+
readonly Created: (args: {
|
|
344
|
+
readonly _tag: "Created";
|
|
345
|
+
readonly section: Section;
|
|
346
|
+
}) => any;
|
|
347
|
+
readonly Unchanged: (args: {
|
|
348
|
+
readonly _tag: "Unchanged";
|
|
349
|
+
readonly section: Section;
|
|
350
|
+
}) => any;
|
|
351
|
+
readonly Updated: (args: {
|
|
352
|
+
readonly _tag: "Updated";
|
|
353
|
+
readonly before: Section;
|
|
354
|
+
readonly after: Section;
|
|
355
|
+
}) => any;
|
|
356
|
+
}>(cases: Cases): (value: {
|
|
357
|
+
readonly _tag: "Created";
|
|
358
|
+
readonly section: Section;
|
|
359
|
+
} | {
|
|
360
|
+
readonly _tag: "Unchanged";
|
|
361
|
+
readonly section: Section;
|
|
362
|
+
} | {
|
|
363
|
+
readonly _tag: "Updated";
|
|
364
|
+
readonly before: Section;
|
|
365
|
+
readonly after: Section;
|
|
366
|
+
}) => import("effect/Unify").Unify<ReturnType<Cases["Created" | "Unchanged" | "Updated"]>>;
|
|
367
|
+
<Cases extends {
|
|
368
|
+
readonly Created: (args: {
|
|
369
|
+
readonly _tag: "Created";
|
|
370
|
+
readonly section: Section;
|
|
371
|
+
}) => any;
|
|
372
|
+
readonly Unchanged: (args: {
|
|
373
|
+
readonly _tag: "Unchanged";
|
|
374
|
+
readonly section: Section;
|
|
375
|
+
}) => any;
|
|
376
|
+
readonly Updated: (args: {
|
|
377
|
+
readonly _tag: "Updated";
|
|
378
|
+
readonly before: Section;
|
|
379
|
+
readonly after: Section;
|
|
380
|
+
}) => any;
|
|
381
|
+
}>(value: {
|
|
382
|
+
readonly _tag: "Created";
|
|
383
|
+
readonly section: Section;
|
|
384
|
+
} | {
|
|
385
|
+
readonly _tag: "Unchanged";
|
|
386
|
+
readonly section: Section;
|
|
387
|
+
} | {
|
|
388
|
+
readonly _tag: "Updated";
|
|
389
|
+
readonly before: Section;
|
|
390
|
+
readonly after: Section;
|
|
391
|
+
}, cases: Cases): import("effect/Unify").Unify<ReturnType<Cases["Created" | "Unchanged" | "Updated"]>>;
|
|
392
|
+
};
|
|
393
|
+
readonly Created: Data.TaggedEnum.ConstructorFrom<{
|
|
394
|
+
readonly _tag: "Created";
|
|
395
|
+
readonly section: Section;
|
|
396
|
+
}, "_tag">;
|
|
397
|
+
readonly Unchanged: Data.TaggedEnum.ConstructorFrom<{
|
|
398
|
+
readonly _tag: "Unchanged";
|
|
399
|
+
readonly section: Section;
|
|
400
|
+
}, "_tag">;
|
|
401
|
+
readonly Updated: Data.TaggedEnum.ConstructorFrom<{
|
|
402
|
+
readonly _tag: "Updated";
|
|
403
|
+
readonly before: Section;
|
|
404
|
+
readonly after: Section;
|
|
405
|
+
}, "_tag">;
|
|
406
|
+
};
|
|
407
|
+
/**
|
|
408
|
+
* What a check found, without touching the document.
|
|
409
|
+
*
|
|
410
|
+
* @remarks
|
|
411
|
+
* Three flat variants rather than the `Found({ isUpToDate, diff })` +
|
|
412
|
+
* `NotFound` pair they replace: a boolean beside a diff that already encodes
|
|
413
|
+
* the same fact is two sources of truth for one question, and it forces every
|
|
414
|
+
* caller through a nested branch.
|
|
415
|
+
*
|
|
416
|
+
* @public
|
|
417
|
+
*/
|
|
418
|
+
type CheckOutcome = Data.TaggedEnum<{
|
|
419
|
+
/** No section with this identity is in the document. */
|
|
420
|
+
readonly Absent: {
|
|
421
|
+
readonly id: SectionId;
|
|
422
|
+
};
|
|
423
|
+
/** The document already says exactly what was declared. */
|
|
424
|
+
readonly UpToDate: {
|
|
425
|
+
readonly section: Section;
|
|
426
|
+
};
|
|
427
|
+
/** The document has this section, with different content. */
|
|
428
|
+
readonly Drifted: {
|
|
429
|
+
readonly onDisk: Section;
|
|
430
|
+
readonly expected: Section;
|
|
431
|
+
};
|
|
432
|
+
}>;
|
|
433
|
+
/**
|
|
434
|
+
* Constructors and matchers for `CheckOutcome` — e.g.
|
|
435
|
+
* `CheckOutcome.Absent({ id })`, `CheckOutcome.$is("Drifted")`.
|
|
436
|
+
*
|
|
437
|
+
* @public
|
|
438
|
+
*/
|
|
439
|
+
declare const CheckOutcome: {
|
|
440
|
+
readonly $is: <Tag extends "Absent" | "Drifted" | "UpToDate">(tag: Tag) => (u: unknown) => u is Extract<{
|
|
441
|
+
readonly _tag: "Absent";
|
|
442
|
+
readonly id: SectionId;
|
|
443
|
+
}, {
|
|
444
|
+
readonly _tag: Tag;
|
|
445
|
+
}> | Extract<{
|
|
446
|
+
readonly _tag: "Drifted";
|
|
447
|
+
readonly onDisk: Section;
|
|
448
|
+
readonly expected: Section;
|
|
449
|
+
}, {
|
|
450
|
+
readonly _tag: Tag;
|
|
451
|
+
}> | Extract<{
|
|
452
|
+
readonly _tag: "UpToDate";
|
|
453
|
+
readonly section: Section;
|
|
454
|
+
}, {
|
|
455
|
+
readonly _tag: Tag;
|
|
456
|
+
}>;
|
|
457
|
+
readonly $match: {
|
|
458
|
+
<Cases extends {
|
|
459
|
+
readonly Absent: (args: {
|
|
460
|
+
readonly _tag: "Absent";
|
|
461
|
+
readonly id: SectionId;
|
|
462
|
+
}) => any;
|
|
463
|
+
readonly Drifted: (args: {
|
|
464
|
+
readonly _tag: "Drifted";
|
|
465
|
+
readonly onDisk: Section;
|
|
466
|
+
readonly expected: Section;
|
|
467
|
+
}) => any;
|
|
468
|
+
readonly UpToDate: (args: {
|
|
469
|
+
readonly _tag: "UpToDate";
|
|
470
|
+
readonly section: Section;
|
|
471
|
+
}) => any;
|
|
472
|
+
}>(cases: Cases): (value: {
|
|
473
|
+
readonly _tag: "Absent";
|
|
474
|
+
readonly id: SectionId;
|
|
475
|
+
} | {
|
|
476
|
+
readonly _tag: "Drifted";
|
|
477
|
+
readonly onDisk: Section;
|
|
478
|
+
readonly expected: Section;
|
|
479
|
+
} | {
|
|
480
|
+
readonly _tag: "UpToDate";
|
|
481
|
+
readonly section: Section;
|
|
482
|
+
}) => import("effect/Unify").Unify<ReturnType<Cases["Absent" | "Drifted" | "UpToDate"]>>;
|
|
483
|
+
<Cases extends {
|
|
484
|
+
readonly Absent: (args: {
|
|
485
|
+
readonly _tag: "Absent";
|
|
486
|
+
readonly id: SectionId;
|
|
487
|
+
}) => any;
|
|
488
|
+
readonly Drifted: (args: {
|
|
489
|
+
readonly _tag: "Drifted";
|
|
490
|
+
readonly onDisk: Section;
|
|
491
|
+
readonly expected: Section;
|
|
492
|
+
}) => any;
|
|
493
|
+
readonly UpToDate: (args: {
|
|
494
|
+
readonly _tag: "UpToDate";
|
|
495
|
+
readonly section: Section;
|
|
496
|
+
}) => any;
|
|
497
|
+
}>(value: {
|
|
498
|
+
readonly _tag: "Absent";
|
|
499
|
+
readonly id: SectionId;
|
|
500
|
+
} | {
|
|
501
|
+
readonly _tag: "Drifted";
|
|
502
|
+
readonly onDisk: Section;
|
|
503
|
+
readonly expected: Section;
|
|
504
|
+
} | {
|
|
505
|
+
readonly _tag: "UpToDate";
|
|
506
|
+
readonly section: Section;
|
|
507
|
+
}, cases: Cases): import("effect/Unify").Unify<ReturnType<Cases["Absent" | "Drifted" | "UpToDate"]>>;
|
|
508
|
+
};
|
|
509
|
+
readonly Absent: Data.TaggedEnum.ConstructorFrom<{
|
|
510
|
+
readonly _tag: "Absent";
|
|
511
|
+
readonly id: SectionId;
|
|
512
|
+
}, "_tag">;
|
|
513
|
+
readonly Drifted: Data.TaggedEnum.ConstructorFrom<{
|
|
514
|
+
readonly _tag: "Drifted";
|
|
515
|
+
readonly onDisk: Section;
|
|
516
|
+
readonly expected: Section;
|
|
517
|
+
}, "_tag">;
|
|
518
|
+
readonly UpToDate: Data.TaggedEnum.ConstructorFrom<{
|
|
519
|
+
readonly _tag: "UpToDate";
|
|
520
|
+
readonly section: Section;
|
|
521
|
+
}, "_tag">;
|
|
522
|
+
};
|
|
523
|
+
//#endregion
|
|
524
|
+
//#region src/SectionDocument.d.ts
|
|
525
|
+
declare const SectionParseError_base: Schema.Class<SectionParseError, Schema.TaggedStruct<"SectionParseError", {
|
|
526
|
+
/** Which ambiguity was found. */
|
|
527
|
+
readonly reason: Schema.Literals<readonly ["unterminatedSection", "orphanedEnd", "overlappingSections", "duplicateSection"]>;
|
|
528
|
+
/** 1-based line of the offending marker. */
|
|
529
|
+
readonly line: Schema.Number;
|
|
530
|
+
/** The section key involved, when the failure names one. */
|
|
531
|
+
readonly key: Schema.optionalKey<Schema.String>;
|
|
532
|
+
/** The file the document came from. Absent for a document parsed from a string. */
|
|
533
|
+
readonly path: Schema.optionalKey<Schema.String>;
|
|
534
|
+
}>, import("effect/Cause").YieldableError>;
|
|
535
|
+
/**
|
|
536
|
+
* Raised when a document's managed-section structure cannot be read without
|
|
537
|
+
* guessing.
|
|
538
|
+
*
|
|
539
|
+
* @remarks
|
|
540
|
+
* Every reason names an ambiguity whose silent resolution corrupts a file.
|
|
541
|
+
* A skipped unterminated marker makes the next write append a **second** copy
|
|
542
|
+
* of the section; a duplicate means every sync updates the first copy while
|
|
543
|
+
* the stale second one lives on disk forever. Malformed input therefore fails
|
|
544
|
+
* through the typed channel with the line that identifies it, and the user
|
|
545
|
+
* fixes their file.
|
|
546
|
+
*
|
|
547
|
+
* @public
|
|
548
|
+
*/
|
|
549
|
+
declare class SectionParseError extends SectionParseError_base {
|
|
550
|
+
get message(): string;
|
|
551
|
+
/**
|
|
552
|
+
* The same failure, attributed to a file.
|
|
553
|
+
*
|
|
554
|
+
* @remarks
|
|
555
|
+
* The pure core has no path to report; the service that read the file
|
|
556
|
+
* attaches one on the way out.
|
|
557
|
+
*/
|
|
558
|
+
static at(path: string, error: SectionParseError): SectionParseError;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* The result of reconciling a declared set of sections against a document.
|
|
562
|
+
*
|
|
563
|
+
* @remarks
|
|
564
|
+
* A plain interface rather than a `Schema.Class`: `outcomes` holds
|
|
565
|
+
* `SyncOutcome` values, which are a `Data.TaggedEnum` and therefore not
|
|
566
|
+
* a schema, so the class form would be schema-shaped in name only.
|
|
567
|
+
*
|
|
568
|
+
* @public
|
|
569
|
+
*/
|
|
570
|
+
interface SectionReconciliation {
|
|
571
|
+
/** The document as it should now read. */
|
|
572
|
+
readonly text: string;
|
|
573
|
+
/** One outcome per declared section, in declared order. */
|
|
574
|
+
readonly outcomes: ReadonlyArray<SyncOutcome>;
|
|
575
|
+
/** Whether `text` differs from the document it was reconciled against. */
|
|
576
|
+
readonly changed: boolean;
|
|
577
|
+
}
|
|
578
|
+
declare const SectionDocument_base: Schema.Class<SectionDocument, Schema.Struct<{
|
|
579
|
+
/** The document's source text, exactly as parsed. */
|
|
580
|
+
readonly text: Schema.String;
|
|
581
|
+
/** The marker vocabulary this document was read with. */
|
|
582
|
+
readonly dialect: typeof SectionDialect;
|
|
583
|
+
/** Every managed section found, in document order. */
|
|
584
|
+
readonly sections: Schema.$Array<typeof PlacedSection>;
|
|
585
|
+
/** The document's dominant line ending. */
|
|
586
|
+
readonly eol: Schema.Literals<readonly ["\n", "\r\n"]>;
|
|
587
|
+
}>, {}>;
|
|
588
|
+
/**
|
|
589
|
+
* A parsed document: its text, the dialect it was read with, and every
|
|
590
|
+
* managed section found in it.
|
|
591
|
+
*
|
|
592
|
+
* @remarks
|
|
593
|
+
* This is the package's pure core. Parsing, inspection, reconciliation and
|
|
594
|
+
* removal are all string-to-string, so every interesting invariant —
|
|
595
|
+
* idempotency, text preservation, ordering normalization, line-ending
|
|
596
|
+
* handling, marker-injection refusal — is assertable from a string literal
|
|
597
|
+
* with no layer, no runtime and no filesystem.
|
|
598
|
+
*
|
|
599
|
+
* @example
|
|
600
|
+
* ```ts
|
|
601
|
+
* import { CommentStyle, SectionDocument, SectionId } from "@effected/templates";
|
|
602
|
+
* import { Result } from "effect";
|
|
603
|
+
*
|
|
604
|
+
* const doc = SectionDocument.parseResult(source);
|
|
605
|
+
* if (Result.isSuccess(doc)) {
|
|
606
|
+
* const id = SectionId.make({ key: "example-tool", commentStyle: CommentStyle.hash });
|
|
607
|
+
* const next = doc.success.reconcile([id.section("echo hello")]);
|
|
608
|
+
* }
|
|
609
|
+
* ```
|
|
610
|
+
*
|
|
611
|
+
* @public
|
|
612
|
+
*/
|
|
613
|
+
declare class SectionDocument extends SectionDocument_base {
|
|
614
|
+
/**
|
|
615
|
+
* Parse a document. The synchronous primitive.
|
|
616
|
+
*
|
|
617
|
+
* @remarks
|
|
618
|
+
* Pure computation exposes the sync form as the primitive; {@link SectionDocument.parse}
|
|
619
|
+
* derives from this and adds only the tracing span, so the two cannot drift.
|
|
620
|
+
* Synchronous callers — a lint hook, a build plugin — use this directly and
|
|
621
|
+
* never build an Effect runtime.
|
|
622
|
+
*/
|
|
623
|
+
static parseResult(text: string, dialect?: SectionDialect): Result.Result<SectionDocument, SectionParseError>;
|
|
624
|
+
/**
|
|
625
|
+
* Parse a document, in `Effect`.
|
|
626
|
+
*
|
|
627
|
+
* @remarks
|
|
628
|
+
* Defined in terms of {@link SectionDocument.parseResult} — synchronous
|
|
629
|
+
* callers can use that variant directly.
|
|
630
|
+
*/
|
|
631
|
+
static readonly parse: (text: string, dialect?: SectionDialect | undefined) => Effect.Effect<SectionDocument, SectionParseError, never>;
|
|
632
|
+
/** The section with this identity, if the document has one. */
|
|
633
|
+
read(id: SectionId): Option.Option<Section>;
|
|
634
|
+
/** Whether the document carries a section with this identity. */
|
|
635
|
+
has(id: SectionId): boolean;
|
|
636
|
+
/**
|
|
637
|
+
* Compare a declared section against the document, changing nothing.
|
|
638
|
+
*
|
|
639
|
+
* @remarks
|
|
640
|
+
* Total: there is no way for a comparison to fail. Line endings are
|
|
641
|
+
* normalized on both sides, so a CRLF document does not report drift
|
|
642
|
+
* against LF content forever.
|
|
643
|
+
*/
|
|
644
|
+
check(section: Section): CheckOutcome;
|
|
645
|
+
/**
|
|
646
|
+
* Fit a declared set of sections into this document.
|
|
647
|
+
*
|
|
648
|
+
* @remarks
|
|
649
|
+
* Declared sections come back **in declared order** — the ordering
|
|
650
|
+
* normalization is a guarantee consumers depend on, not a side effect.
|
|
651
|
+
* Text outside a managed span and sections this dialect does not own are
|
|
652
|
+
* preserved byte-for-byte. Nothing is rendered into the output until every
|
|
653
|
+
* declared section has rendered successfully, so a refusal leaves the
|
|
654
|
+
* document untouched.
|
|
655
|
+
*/
|
|
656
|
+
reconcile(sections: ReadonlyArray<Section>): Result.Result<SectionReconciliation, SectionRenderError>;
|
|
657
|
+
/**
|
|
658
|
+
* The document with this section removed, or `none` if it was not there.
|
|
659
|
+
*
|
|
660
|
+
* @remarks
|
|
661
|
+
* The blank lines around the removed block collapse into a single
|
|
662
|
+
* separator, so repeated removals never accumulate gaps.
|
|
663
|
+
*/
|
|
664
|
+
remove(id: SectionId): Option.Option<string>;
|
|
665
|
+
}
|
|
666
|
+
//#endregion
|
|
667
|
+
//#region src/ManagedSection.d.ts
|
|
668
|
+
declare const SectionFileError_base: Schema.Class<SectionFileError, Schema.TaggedStruct<"SectionFileError", {
|
|
669
|
+
/** The file the operation was against. */
|
|
670
|
+
readonly path: Schema.String;
|
|
671
|
+
/** Which half of the read-modify-write failed. */
|
|
672
|
+
readonly operation: Schema.Literals<readonly ["read", "write"]>;
|
|
673
|
+
/** The underlying failure, preserved structurally. */
|
|
674
|
+
readonly cause: Schema.Defect;
|
|
675
|
+
}>, import("effect/Cause").YieldableError>;
|
|
676
|
+
/**
|
|
677
|
+
* Raised when the file behind a managed-section operation could not be read or
|
|
678
|
+
* written.
|
|
679
|
+
*
|
|
680
|
+
* @remarks
|
|
681
|
+
* The underlying `PlatformError` is preserved structurally in `cause` rather
|
|
682
|
+
* than stringified. `operation` is what a bare platform error cannot tell a
|
|
683
|
+
* caller: these operations are read-modify-write, so "it failed" is ambiguous
|
|
684
|
+
* about whether the document was even seen.
|
|
685
|
+
*
|
|
686
|
+
* A **missing** file is not an error — it reads as a document with no
|
|
687
|
+
* sections, and a sync creates it.
|
|
688
|
+
*
|
|
689
|
+
* @public
|
|
690
|
+
*/
|
|
691
|
+
declare class SectionFileError extends SectionFileError_base {
|
|
692
|
+
get message(): string;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* The {@link ManagedSection} service shape.
|
|
696
|
+
*
|
|
697
|
+
* @remarks
|
|
698
|
+
* Exported so a consumer can type a function against the shape without naming
|
|
699
|
+
* the service class, and so the surface is a reviewable declaration rather
|
|
700
|
+
* than whatever the implementation happened to return.
|
|
701
|
+
*
|
|
702
|
+
* Every member is **data-first**: the path comes first and nothing is dual.
|
|
703
|
+
* The model this replaces made all seven members dual, which doubled the
|
|
704
|
+
* declared surface for a data-last form no call site used — and a dual member
|
|
705
|
+
* cannot be stubbed with a one-line override in {@link ManagedSection.layerTest},
|
|
706
|
+
* because the override has to satisfy both overloads.
|
|
707
|
+
*
|
|
708
|
+
* @public
|
|
709
|
+
*/
|
|
710
|
+
interface ManagedSectionShape {
|
|
711
|
+
/** The section with this identity, if the file has one. */
|
|
712
|
+
readonly read: (path: string, id: SectionId) => Effect.Effect<Option.Option<Section>, SectionParseError | SectionFileError>;
|
|
713
|
+
/** Every managed section in the file, in document order. */
|
|
714
|
+
readonly readAll: (path: string) => Effect.Effect<ReadonlyArray<Section>, SectionParseError | SectionFileError>;
|
|
715
|
+
/** Whether the file carries a section with this identity. */
|
|
716
|
+
readonly isManaged: (path: string, id: SectionId) => Effect.Effect<boolean, SectionParseError | SectionFileError>;
|
|
717
|
+
/** Make one section say what it should, writing only if that changes the file. */
|
|
718
|
+
readonly sync: (path: string, section: Section) => Effect.Effect<SyncOutcome, SectionParseError | SectionRenderError | SectionFileError>;
|
|
719
|
+
/**
|
|
720
|
+
* Make a whole set of sections say what they should, in declared order.
|
|
721
|
+
*
|
|
722
|
+
* @remarks
|
|
723
|
+
* The declared order is written into the file — see
|
|
724
|
+
* {@link SectionDocument.reconcile}. Writes only if the text changes.
|
|
725
|
+
*/
|
|
726
|
+
readonly syncAll: (path: string, sections: ReadonlyArray<Section>) => Effect.Effect<ReadonlyArray<SyncOutcome>, SectionParseError | SectionRenderError | SectionFileError>;
|
|
727
|
+
/** Compare one section against the file, changing nothing. */
|
|
728
|
+
readonly check: (path: string, section: Section) => Effect.Effect<CheckOutcome, SectionParseError | SectionFileError>;
|
|
729
|
+
/** Compare a set of sections against the file from a single read. */
|
|
730
|
+
readonly checkAll: (path: string, sections: ReadonlyArray<Section>) => Effect.Effect<ReadonlyArray<CheckOutcome>, SectionParseError | SectionFileError>;
|
|
731
|
+
/** Remove a section. `false` when it was not there. */
|
|
732
|
+
readonly remove: (path: string, id: SectionId) => Effect.Effect<boolean, SectionParseError | SectionFileError>;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* How a `ManagedSection` layer reads and writes markers.
|
|
736
|
+
*
|
|
737
|
+
* @public
|
|
738
|
+
*/
|
|
739
|
+
interface ManagedSectionOptions {
|
|
740
|
+
/** Defaults to {@link SectionDialect.default}. */
|
|
741
|
+
readonly dialect?: SectionDialect;
|
|
742
|
+
}
|
|
743
|
+
declare const ManagedSection_base: Context.ServiceClass<ManagedSection, "@effected/templates/ManagedSection", ManagedSectionShape>;
|
|
744
|
+
/**
|
|
745
|
+
* Managed sections in files: read, compare, sync and remove delimited blocks
|
|
746
|
+
* whose surrounding content belongs to the user.
|
|
747
|
+
*
|
|
748
|
+
* @remarks
|
|
749
|
+
* A thin shell over {@link SectionDocument}: every member reads the file, runs
|
|
750
|
+
* the pure core, and writes back only when the text actually changed. All the
|
|
751
|
+
* interesting behavior — and all the interesting tests — live in the pure core.
|
|
752
|
+
*
|
|
753
|
+
* `FileSystem` is required in `R` and discharged by the consumer's platform
|
|
754
|
+
* layer at the edge. No `Path` is required: paths are handed to `FileSystem`
|
|
755
|
+
* untouched.
|
|
756
|
+
*
|
|
757
|
+
* @example
|
|
758
|
+
* ```ts
|
|
759
|
+
* import { CommentStyle, ManagedSection, SectionId } from "@effected/templates";
|
|
760
|
+
* import { Effect } from "effect";
|
|
761
|
+
*
|
|
762
|
+
* const Base = SectionId.make({ key: "base", commentStyle: CommentStyle.hash });
|
|
763
|
+
* const Tool = SectionId.make({ key: "tool", commentStyle: CommentStyle.hash });
|
|
764
|
+
*
|
|
765
|
+
* const program = Effect.gen(function* () {
|
|
766
|
+
* const sections = yield* ManagedSection;
|
|
767
|
+
* // Declared order is written into the file, so `base` precedes `tool`.
|
|
768
|
+
* return yield* sections.syncAll(".husky/pre-commit", [
|
|
769
|
+
* Base.section("preamble"),
|
|
770
|
+
* Tool.section("run-the-tool"),
|
|
771
|
+
* ]);
|
|
772
|
+
* });
|
|
773
|
+
* ```
|
|
774
|
+
*
|
|
775
|
+
* @public
|
|
776
|
+
*/
|
|
777
|
+
declare class ManagedSection extends ManagedSection_base {
|
|
778
|
+
/**
|
|
779
|
+
* The default layer, reading and writing {@link SectionDialect.default}
|
|
780
|
+
* markers.
|
|
781
|
+
*
|
|
782
|
+
* @remarks
|
|
783
|
+
* A bound `const`, not a function, so it memoizes by reference.
|
|
784
|
+
*/
|
|
785
|
+
static readonly layer: Layer.Layer<ManagedSection, never, FileSystem.FileSystem>;
|
|
786
|
+
/**
|
|
787
|
+
* A layer with a custom marker dialect.
|
|
788
|
+
*
|
|
789
|
+
* @remarks
|
|
790
|
+
* A parameterized layer factory mints a **fresh reference per call**, and
|
|
791
|
+
* layers memoize by reference — bind the result to a `const` and reuse it
|
|
792
|
+
* rather than calling `layerWith(...)` at each composition site.
|
|
793
|
+
*/
|
|
794
|
+
static readonly layerWith: (options: ManagedSectionOptions) => Layer.Layer<ManagedSection, never, FileSystem.FileSystem>;
|
|
795
|
+
/**
|
|
796
|
+
* A test double. Members a suite does not stub **die when called**, rather
|
|
797
|
+
* than returning a plausible-looking answer that makes a wrong test pass.
|
|
798
|
+
*/
|
|
799
|
+
static readonly makeTest: (overrides?: Partial<ManagedSectionShape>) => ManagedSectionShape;
|
|
800
|
+
/**
|
|
801
|
+
* The test layer: {@link ManagedSection.makeTest} behind `Layer.succeed`, so
|
|
802
|
+
* a suite provides only the members it exercises.
|
|
803
|
+
*
|
|
804
|
+
* @remarks
|
|
805
|
+
* A parameterized layer factory mints a fresh reference per call — bind it
|
|
806
|
+
* to a `const` rather than calling it at each composition site.
|
|
807
|
+
*/
|
|
808
|
+
static readonly layerTest: (overrides?: Partial<ManagedSectionShape>) => Layer.Layer<ManagedSection>;
|
|
809
|
+
}
|
|
810
|
+
//#endregion
|
|
811
|
+
export { CheckOutcome, CommentStyle, type Eol, ManagedSection, type ManagedSectionOptions, type ManagedSectionShape, PlacedSection, Section, SectionDialect, SectionDocument, SectionFileError, SectionId, SectionKey, SectionParseError, type SectionReconciliation, SectionRenderError, SyncOutcome };
|
|
812
|
+
//# sourceMappingURL=index.d.ts.map
|