@hardlydifficult/text 1.0.30 → 1.0.31
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 +209 -231
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @hardlydifficult/text
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Collection of text utility functions for error formatting, template replacement, chunking, slugification, duration formatting, YAML/JSON conversion, link generation, and file tree rendering.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -12,77 +12,89 @@ npm install @hardlydifficult/text
|
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
14
|
import {
|
|
15
|
+
formatError,
|
|
15
16
|
replaceTemplate,
|
|
16
17
|
chunkText,
|
|
17
18
|
slugify,
|
|
18
19
|
formatDuration,
|
|
20
|
+
formatWithLineNumbers,
|
|
19
21
|
buildFileTree,
|
|
20
22
|
convertFormat,
|
|
21
|
-
createLinker,
|
|
22
23
|
healYaml,
|
|
24
|
+
createLinker,
|
|
23
25
|
escapeFence,
|
|
26
|
+
stripAnsi
|
|
24
27
|
} from "@hardlydifficult/text";
|
|
25
28
|
|
|
29
|
+
// Format an error with context
|
|
30
|
+
formatError(new Error("File not found"), "Failed to load");
|
|
31
|
+
// "Failed to load: File not found"
|
|
32
|
+
|
|
26
33
|
// Replace template placeholders
|
|
27
34
|
replaceTemplate("Hello {{name}}!", { name: "World" });
|
|
28
35
|
// "Hello World!"
|
|
29
36
|
|
|
30
|
-
// Split
|
|
31
|
-
chunkText("
|
|
32
|
-
// ["
|
|
37
|
+
// Split text into chunks
|
|
38
|
+
chunkText("line1\nline2\nline3", 10);
|
|
39
|
+
// ["line1\nline2", "line3"]
|
|
33
40
|
|
|
34
|
-
//
|
|
35
|
-
slugify("My Feature Name!");
|
|
36
|
-
// "my-feature
|
|
41
|
+
// Create URL-safe slugs
|
|
42
|
+
slugify("My Feature Name!", 10);
|
|
43
|
+
// "my-feature"
|
|
37
44
|
|
|
38
|
-
// Format
|
|
45
|
+
// Format duration as human-readable string
|
|
39
46
|
formatDuration(125_000);
|
|
40
47
|
// "2m 5s"
|
|
41
48
|
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
// "
|
|
49
|
+
// Add line numbers to text
|
|
50
|
+
formatWithLineNumbers("foo\nbar", 10);
|
|
51
|
+
// "10: foo\n11: bar"
|
|
45
52
|
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
// "
|
|
49
|
-
convertFormat("name: Alice", "json");
|
|
50
|
-
// "{\n \"name\": \"Alice\"\n}"
|
|
53
|
+
// Build a file tree from paths
|
|
54
|
+
buildFileTree(["src/index.ts", "src/utils.ts"], { format: "plain" });
|
|
55
|
+
// "src/\n index.ts\n utils.ts"
|
|
51
56
|
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// "Fix [ENG-533](https://linear.app/my-org/issue/ENG-533)"
|
|
57
|
+
// Convert between JSON and YAML
|
|
58
|
+
convertFormat('{"key": "value"}', "yaml");
|
|
59
|
+
// "key: value\n"
|
|
56
60
|
|
|
57
61
|
// Heal malformed YAML
|
|
58
|
-
healYaml("
|
|
59
|
-
// "
|
|
62
|
+
healYaml('description: "Text with: colon"');
|
|
63
|
+
// 'description: "Text with: colon"'
|
|
64
|
+
|
|
65
|
+
// Linkify issue references
|
|
66
|
+
const linker = createLinker().linear("fairmint");
|
|
67
|
+
linker.apply("Fix ENG-533", { format: "slack" });
|
|
68
|
+
// "Fix <https://linear.app/fairmint/issue/ENG-533|ENG-533>"
|
|
69
|
+
|
|
70
|
+
// Escape markdown fences
|
|
71
|
+
escapeFence("code with ``` backticks").fence;
|
|
72
|
+
// "````"
|
|
73
|
+
|
|
74
|
+
// Strip ANSI codes
|
|
75
|
+
stripAnsi("\x1b[31mRed text\x1b[0m");
|
|
76
|
+
// "Red text"
|
|
60
77
|
```
|
|
61
78
|
|
|
62
|
-
## Error
|
|
79
|
+
## Error Handling
|
|
63
80
|
|
|
64
|
-
Consistent error
|
|
81
|
+
Consistent error message extraction and formatting utilities for user-facing and logging contexts.
|
|
65
82
|
|
|
66
|
-
###
|
|
83
|
+
### getErrorMessage
|
|
67
84
|
|
|
68
|
-
|
|
85
|
+
Extracts a string message from any error-like value.
|
|
69
86
|
|
|
70
87
|
```typescript
|
|
71
88
|
import { getErrorMessage } from "@hardlydifficult/text";
|
|
72
89
|
|
|
73
|
-
getErrorMessage(new Error("
|
|
74
|
-
// "
|
|
75
|
-
|
|
76
|
-
getErrorMessage("plain string error");
|
|
77
|
-
// "plain string error"
|
|
78
|
-
|
|
79
|
-
getErrorMessage(42);
|
|
80
|
-
// "42"
|
|
90
|
+
getErrorMessage(new Error("Oops")); // "Oops"
|
|
91
|
+
getErrorMessage("plain string"); // "plain string"
|
|
92
|
+
getErrorMessage(42); // "42"
|
|
81
93
|
```
|
|
82
94
|
|
|
83
|
-
###
|
|
95
|
+
### formatError
|
|
84
96
|
|
|
85
|
-
|
|
97
|
+
Formats an error with optional context prefix.
|
|
86
98
|
|
|
87
99
|
```typescript
|
|
88
100
|
import { formatError } from "@hardlydifficult/text";
|
|
@@ -94,27 +106,24 @@ formatError(new Error("disk full"), "Failed to save");
|
|
|
94
106
|
// "Failed to save: disk full"
|
|
95
107
|
```
|
|
96
108
|
|
|
97
|
-
###
|
|
109
|
+
### formatErrorForLog
|
|
98
110
|
|
|
99
|
-
|
|
111
|
+
Formats an error for logging (returns message for Error instances, stringifies others).
|
|
100
112
|
|
|
101
113
|
```typescript
|
|
102
114
|
import { formatErrorForLog } from "@hardlydifficult/text";
|
|
103
115
|
|
|
104
|
-
formatErrorForLog(new Error("timeout"));
|
|
105
|
-
// "
|
|
106
|
-
|
|
107
|
-
formatErrorForLog({ code: 500 });
|
|
108
|
-
// "[object Object]"
|
|
116
|
+
formatErrorForLog(new Error("timeout")); // "timeout"
|
|
117
|
+
formatErrorForLog({ code: 500 }); // "[object Object]"
|
|
109
118
|
```
|
|
110
119
|
|
|
111
120
|
## Template Replacement
|
|
112
121
|
|
|
113
|
-
Simple
|
|
122
|
+
Simple template utility for placeholder replacement using `{{variable}}` syntax.
|
|
114
123
|
|
|
115
|
-
###
|
|
124
|
+
### replaceTemplate
|
|
116
125
|
|
|
117
|
-
|
|
126
|
+
Replaces `{{variable}}` placeholders with provided values.
|
|
118
127
|
|
|
119
128
|
```typescript
|
|
120
129
|
import { replaceTemplate } from "@hardlydifficult/text";
|
|
@@ -122,176 +131,173 @@ import { replaceTemplate } from "@hardlydifficult/text";
|
|
|
122
131
|
replaceTemplate("Hello {{name}}!", { name: "World" });
|
|
123
132
|
// "Hello World!"
|
|
124
133
|
|
|
125
|
-
replaceTemplate("{{greeting}}, {{name}}!", {
|
|
126
|
-
|
|
127
|
-
name: "Alice",
|
|
128
|
-
});
|
|
129
|
-
// "Hi, Alice!"
|
|
130
|
-
|
|
131
|
-
replaceTemplate("Hello {{name}}!", {});
|
|
132
|
-
// "Hello {{name}}!"
|
|
134
|
+
replaceTemplate("{{greeting}}, {{name}}!", { greeting: "Hi" });
|
|
135
|
+
// "Hi, {{name}}!" // missing key leaves placeholder unchanged
|
|
133
136
|
```
|
|
134
137
|
|
|
135
|
-
###
|
|
138
|
+
### extractPlaceholders
|
|
136
139
|
|
|
137
|
-
|
|
140
|
+
Extracts unique placeholder names from a template.
|
|
138
141
|
|
|
139
142
|
```typescript
|
|
140
143
|
import { extractPlaceholders } from "@hardlydifficult/text";
|
|
141
144
|
|
|
142
145
|
extractPlaceholders("{{a}} and {{b}} and {{a}} again");
|
|
143
146
|
// ["a", "b"]
|
|
144
|
-
|
|
145
|
-
extractPlaceholders("no placeholders here");
|
|
146
|
-
// []
|
|
147
147
|
```
|
|
148
148
|
|
|
149
149
|
## Text Chunking
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
Splits long text into manageable chunks, preferring natural breaks.
|
|
152
|
+
|
|
153
|
+
### chunkText
|
|
154
|
+
|
|
155
|
+
Splits text at line breaks or spaces, falling back to hard breaks when necessary.
|
|
152
156
|
|
|
153
157
|
```typescript
|
|
154
158
|
import { chunkText } from "@hardlydifficult/text";
|
|
155
159
|
|
|
156
|
-
chunkText("
|
|
157
|
-
// ["
|
|
160
|
+
chunkText("word1 word2 word3", 12);
|
|
161
|
+
// ["word1 word2", "word3"]
|
|
158
162
|
|
|
159
|
-
chunkText("
|
|
160
|
-
// ["
|
|
161
|
-
|
|
162
|
-
chunkText("abcdefghijklmnopqrstuvwxyz", 10);
|
|
163
|
-
// ["abcdefghij", "klmnopqrst", "uvwxyz"]
|
|
163
|
+
chunkText("line1\nline2\nline3", 10);
|
|
164
|
+
// ["line1\nline2", "line3"]
|
|
164
165
|
```
|
|
165
166
|
|
|
166
167
|
## Slugification
|
|
167
168
|
|
|
168
|
-
|
|
169
|
+
Converts strings into URL/filename-safe slugs.
|
|
169
170
|
|
|
170
|
-
|
|
171
|
-
import { slugify } from "@hardlydifficult/text";
|
|
171
|
+
### slugify
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
// "my-feature-name"
|
|
173
|
+
Lowercases, replaces non-alphanumeric characters with hyphens, and optionally truncates at hyphen boundaries.
|
|
175
174
|
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
```typescript
|
|
176
|
+
import { slugify } from "@hardlydifficult/text";
|
|
178
177
|
|
|
179
|
-
slugify("
|
|
180
|
-
// "
|
|
178
|
+
slugify("My Feature Name!"); // "my-feature-name"
|
|
179
|
+
slugify("My Feature Name!", 10); // "my-feature"
|
|
180
|
+
slugify(" Hello World "); // "hello-world"
|
|
181
181
|
```
|
|
182
182
|
|
|
183
183
|
## Duration Formatting
|
|
184
184
|
|
|
185
|
-
|
|
185
|
+
Formats milliseconds as human-readable strings.
|
|
186
|
+
|
|
187
|
+
### formatDuration
|
|
188
|
+
|
|
189
|
+
Renders durations with up to two units, skipping trailing zeros.
|
|
186
190
|
|
|
187
191
|
```typescript
|
|
188
192
|
import { formatDuration } from "@hardlydifficult/text";
|
|
189
193
|
|
|
190
|
-
formatDuration(
|
|
191
|
-
// "2m 5s"
|
|
194
|
+
formatDuration(500); // "<1s"
|
|
195
|
+
formatDuration(125_000); // "2m 5s"
|
|
196
|
+
formatDuration(3_600_000); // "1h"
|
|
197
|
+
formatDuration(86_400_000); // "1d"
|
|
198
|
+
```
|
|
192
199
|
|
|
193
|
-
|
|
194
|
-
// "1h"
|
|
200
|
+
## Line Number Formatting
|
|
195
201
|
|
|
196
|
-
|
|
197
|
-
// "<1s"
|
|
198
|
-
```
|
|
202
|
+
Adds right-aligned line numbers to text.
|
|
199
203
|
|
|
200
|
-
|
|
204
|
+
### formatWithLineNumbers
|
|
201
205
|
|
|
202
|
-
|
|
206
|
+
Adds line numbers with configurable starting value.
|
|
203
207
|
|
|
204
208
|
```typescript
|
|
205
|
-
import {
|
|
209
|
+
import { formatWithLineNumbers } from "@hardlydifficult/text";
|
|
206
210
|
|
|
207
|
-
|
|
208
|
-
// "
|
|
211
|
+
formatWithLineNumbers("foo\nbar\nbaz");
|
|
212
|
+
// "1: foo\n2: bar\n3: baz"
|
|
213
|
+
|
|
214
|
+
formatWithLineNumbers("hello\nworld", 10);
|
|
215
|
+
// "10: hello\n11: world"
|
|
209
216
|
```
|
|
210
217
|
|
|
211
|
-
|
|
218
|
+
## File Tree Building
|
|
212
219
|
|
|
213
|
-
|
|
214
|
-
|----------------|-------------------------------------------|-----------------------------------------------------------------------------|
|
|
215
|
-
| `maxLevel2` | `number` | Maximum number of entries to show at level 2 (files in a directory) |
|
|
216
|
-
| `maxLevel3` | `number` | Maximum number of entries to show at level 3 (files in subdirectories) |
|
|
217
|
-
| `annotations` | `ReadonlyMap<string, string>` | Map of file/directory paths to annotation strings |
|
|
218
|
-
| `details` | `ReadonlyMap<string, readonly string[]>` | Map of file paths to extra detail lines to show under entries |
|
|
219
|
-
| `collapseDirs` | `readonly string[]` | Directory names to collapse with summary count |
|
|
220
|
-
| `format` | `'plain' \| 'markdown'` | Output format. Defaults to `'markdown'`, which wraps the tree in a code fence for correct markdown rendering. Use `'plain'` when the caller already provides a fence (e.g. AI prompt templates). |
|
|
220
|
+
Builds a hierarchical file tree from flat paths with depth-based truncation.
|
|
221
221
|
|
|
222
|
-
###
|
|
222
|
+
### buildFileTree
|
|
223
223
|
|
|
224
|
-
|
|
224
|
+
Renders markdown-formatted (default) or plain file trees with annotations, details, and collapsed directory summaries.
|
|
225
225
|
|
|
226
226
|
```typescript
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
["src", "Source code directory"],
|
|
230
|
-
]);
|
|
227
|
+
import { buildFileTree, FILE_TREE_DEFAULTS } from "@hardlydifficult/text";
|
|
228
|
+
import type { BuildTreeOptions } from "@hardlydifficult/text";
|
|
231
229
|
|
|
232
|
-
|
|
233
|
-
|
|
230
|
+
// Basic usage
|
|
231
|
+
buildFileTree(["src/index.ts", "README.md"]);
|
|
232
|
+
// "```\nsrc/\n index.ts\n\nREADME.md\n```"
|
|
233
|
+
|
|
234
|
+
// Options interface
|
|
235
|
+
interface BuildTreeOptions {
|
|
236
|
+
maxLevel2?: number; // Max children at depth 2 (default: 10)
|
|
237
|
+
maxLevel3?: number; // Max children at depth 3+ (default: 3)
|
|
238
|
+
annotations?: Map<string, string>;
|
|
239
|
+
details?: Map<string, readonly string[]>;
|
|
240
|
+
collapseDirs?: readonly string[];
|
|
241
|
+
lineCounts?: Map<string, number>;
|
|
242
|
+
format?: "plain" | "markdown"; // default: "markdown"
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const paths = ["src/index.ts", "src/utils.ts"];
|
|
246
|
+
buildFileTree(paths, { format: "plain" });
|
|
247
|
+
// "src/\n index.ts\n utils.ts"
|
|
248
|
+
|
|
249
|
+
// With annotations
|
|
250
|
+
const annotations = new Map([["src/index.ts", "Main entry point"]]);
|
|
251
|
+
buildFileTree(paths, { annotations, format: "plain" });
|
|
252
|
+
// "src/\n index.ts — Main entry point\n utils.ts"
|
|
234
253
|
```
|
|
235
254
|
|
|
236
|
-
|
|
255
|
+
### FILE_TREE_DEFAULTS
|
|
256
|
+
|
|
257
|
+
Default truncation limits for file tree rendering.
|
|
237
258
|
|
|
238
259
|
```typescript
|
|
239
|
-
|
|
240
|
-
["src/index.ts", ["> main (5-20): App entry point."]],
|
|
241
|
-
]);
|
|
260
|
+
import { FILE_TREE_DEFAULTS } from "@hardlydifficult/text";
|
|
242
261
|
|
|
243
|
-
|
|
244
|
-
// "```\nsrc/\n index.ts\n > main (5-20): App entry point.\n```"
|
|
262
|
+
FILE_TREE_DEFAULTS; // { maxLevel2: 10, maxLevel3: 3 }
|
|
245
263
|
```
|
|
246
264
|
|
|
247
|
-
|
|
265
|
+
## Format Conversion
|
|
248
266
|
|
|
249
|
-
|
|
250
|
-
buildFileTree(
|
|
251
|
-
["src/index.ts", "test/unit/a.test.ts", "test/unit/b.test.ts"],
|
|
252
|
-
{ collapseDirs: ["test"] }
|
|
253
|
-
);
|
|
254
|
-
// "```\nsrc/\n index.ts\n\ntest/\n (2 files)\n```"
|
|
255
|
-
```
|
|
267
|
+
Bidirectional conversion between JSON and YAML.
|
|
256
268
|
|
|
257
|
-
|
|
269
|
+
### convertFormat
|
|
258
270
|
|
|
259
|
-
|
|
271
|
+
Auto-detects input format and converts to the requested format.
|
|
260
272
|
|
|
261
273
|
```typescript
|
|
262
|
-
import { convertFormat } from "@hardlydifficult/text";
|
|
274
|
+
import { convertFormat, type TextFormat } from "@hardlydifficult/text";
|
|
263
275
|
|
|
264
|
-
convertFormat('{"name":"Alice"
|
|
265
|
-
// "name: Alice\
|
|
276
|
+
convertFormat('{"name": "Alice"}', "yaml");
|
|
277
|
+
// "name: Alice\n"
|
|
266
278
|
|
|
267
279
|
convertFormat("name: Alice\nage: 30", "json");
|
|
268
280
|
// "{\n \"name\": \"Alice\",\n \"age\": 30\n}"
|
|
269
281
|
```
|
|
270
282
|
|
|
271
|
-
|
|
283
|
+
## YAML Utilities
|
|
272
284
|
|
|
273
|
-
|
|
285
|
+
Serialization and repair utilities for YAML.
|
|
274
286
|
|
|
275
|
-
|
|
287
|
+
### formatYaml
|
|
276
288
|
|
|
277
|
-
|
|
289
|
+
Serializes data to clean YAML with block literals for long strings containing `": "`.
|
|
278
290
|
|
|
279
291
|
```typescript
|
|
280
292
|
import { formatYaml } from "@hardlydifficult/text";
|
|
281
293
|
|
|
282
|
-
formatYaml({
|
|
283
|
-
|
|
284
|
-
"Core AI SDK implementation: LLM integrations (Anthropic Claude, Ollama), agent orchestration with streaming.",
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
// Uses block literal (|) for long strings containing ": "
|
|
288
|
-
// purpose: |
|
|
289
|
-
// Core AI SDK implementation: LLM integrations (Anthropic Claude, Ollama), agent orchestration with streaming.
|
|
294
|
+
formatYaml({ purpose: "Core AI SDK: LLM integrations." });
|
|
295
|
+
// "purpose: |\n Core AI SDK: LLM integrations.\n"
|
|
290
296
|
```
|
|
291
297
|
|
|
292
|
-
|
|
298
|
+
### healYaml
|
|
293
299
|
|
|
294
|
-
|
|
300
|
+
Strips markdown fences and quotes scalar values containing colons.
|
|
295
301
|
|
|
296
302
|
```typescript
|
|
297
303
|
import { healYaml } from "@hardlydifficult/text";
|
|
@@ -299,128 +305,100 @@ import { healYaml } from "@hardlydifficult/text";
|
|
|
299
305
|
healYaml("```yaml\nkey: value\n```");
|
|
300
306
|
// "key: value"
|
|
301
307
|
|
|
302
|
-
healYaml(
|
|
303
|
-
// 'description: "
|
|
308
|
+
healYaml("description: Text: with colons");
|
|
309
|
+
// 'description: "Text: with colons"'
|
|
304
310
|
```
|
|
305
311
|
|
|
306
|
-
##
|
|
312
|
+
## Linker (Text Linkification)
|
|
307
313
|
|
|
308
|
-
|
|
314
|
+
Transforms text by replacing issue/PR references with formatted links.
|
|
309
315
|
|
|
310
|
-
###
|
|
316
|
+
### Linker
|
|
311
317
|
|
|
312
|
-
|
|
318
|
+
Stateful linker with configurable rules, idempotent linkification, and multi-platform support.
|
|
313
319
|
|
|
314
320
|
```typescript
|
|
315
|
-
import { createLinker } from "@hardlydifficult/text";
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
//
|
|
321
|
+
import { createLinker, LinkerPlatform, LinkerApplyOptions } from "@hardlydifficult/text";
|
|
322
|
+
|
|
323
|
+
interface LinkerApplyOptions {
|
|
324
|
+
format?: LinkerPlatform; // "slack" | "discord" | "markdown" | "plaintext"
|
|
325
|
+
platform?: LinkerPlatform;
|
|
326
|
+
skipCode?: boolean; // default: true
|
|
327
|
+
skipExistingLinks?: boolean; // default: true
|
|
328
|
+
linkifyPlainHref?: boolean; // default: false
|
|
329
|
+
}
|
|
323
330
|
```
|
|
324
331
|
|
|
325
|
-
###
|
|
326
|
-
|
|
327
|
-
Stateful linker that applies configured rules to text.
|
|
328
|
-
|
|
329
|
-
**Methods:**
|
|
330
|
-
|
|
331
|
-
| Method | Description |
|
|
332
|
-
|----------------|------------------------------------------------------------------------|
|
|
333
|
-
| `addRule(rule)`| Add a custom link rule |
|
|
334
|
-
| `rule(...)` | Add a rule (supports fluent and named forms) |
|
|
335
|
-
| `custom(...)` | Add a custom rule with regex pattern and href builder |
|
|
336
|
-
| `linear(...)` | Add Linear issue reference rule (e.g., `ENG-533`) |
|
|
337
|
-
| `githubPr(...)`| Add GitHub PR reference rule (e.g., `PR#42`) |
|
|
338
|
-
| `apply(...)` | Apply linkification to text with options |
|
|
339
|
-
| `linkText(...)`| Alias for `apply` (same behavior) |
|
|
332
|
+
### createLinker
|
|
340
333
|
|
|
341
|
-
|
|
334
|
+
Creates a linker with optional initial rules.
|
|
342
335
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
| `pattern` | `RegExp` | Match pattern (global flag is enforced automatically) |
|
|
346
|
-
| `href` | `string` | URL template (supports `$0`/`$&`, `$1..$N`) |
|
|
347
|
-
| `toHref` | `string \| LinkHrefBuilder`| Either href template or callback; takes precedence over `href` |
|
|
348
|
-
| `priority`| `number` | Higher priority wins for overlapping matches (default: `0`) |
|
|
349
|
-
|
|
350
|
-
### Options
|
|
336
|
+
```typescript
|
|
337
|
+
import { createLinker } from "@hardlydifficult/text";
|
|
351
338
|
|
|
352
|
-
|
|
353
|
-
|-----------------------|-----------------------------|-------------------------------------------------------------------------|
|
|
354
|
-
| `format` / `platform` | `LinkerPlatform` | Output format: `"slack"`, `"discord"`, `"markdown"`, `"plaintext"` |
|
|
355
|
-
| `skipCode` | `boolean` | Skip linkification inside code spans (default: `true`) |
|
|
356
|
-
| `skipExistingLinks` | `boolean` | Skip linkification inside existing links (default: `true`) |
|
|
339
|
+
const linker = createLinker();
|
|
357
340
|
|
|
358
|
-
|
|
341
|
+
// Fluent API
|
|
342
|
+
linker
|
|
343
|
+
.linear("fairmint")
|
|
344
|
+
.githubPr("Fairmint/api")
|
|
345
|
+
.custom(/\bINC-\d+\b/g, ({ match }) => `https://incident.io/${match}`);
|
|
359
346
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
|
363
|
-
|
|
364
|
-
| `markdown` | `[text](href)` |
|
|
365
|
-
| `plaintext` | `href` (raw URL) |
|
|
347
|
+
// Linkify text
|
|
348
|
+
linker.linkText("Fix ENG-533 PR#42 INC-99", { format: "slack" });
|
|
349
|
+
// "Fix <https://linear.app/fairmint/issue/ENG-533|ENG-533> <https://github.com/Fairmint/api/pull/42|PR#42> <https://incident.io/INC-99|INC-99>"
|
|
350
|
+
```
|
|
366
351
|
|
|
367
|
-
###
|
|
352
|
+
### Rule API
|
|
368
353
|
|
|
369
|
-
|
|
354
|
+
Custom rules support patterns, href templates or callbacks, priorities, and match metadata.
|
|
370
355
|
|
|
371
356
|
```typescript
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
357
|
+
interface LinkRule {
|
|
358
|
+
id: string;
|
|
359
|
+
priority: number;
|
|
360
|
+
pattern: RegExp;
|
|
361
|
+
href: string | ((ctx: { match: string; groups?: string[] }) => string);
|
|
362
|
+
skipCode?: boolean;
|
|
363
|
+
skipExistingLinks?: boolean;
|
|
364
|
+
}
|
|
378
365
|
```
|
|
379
366
|
|
|
380
|
-
|
|
367
|
+
### Methods
|
|
381
368
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
369
|
+
- `custom(pattern, href, options)`: Add custom rule
|
|
370
|
+
- `linear(orgOrProject)`: Link Linear issues
|
|
371
|
+
- `githubPr(repo)`: Link GitHub PRs
|
|
372
|
+
- `apply(text, options)`: Linkify and preserve format
|
|
373
|
+
- `linkText(text, options)`: Linkify plain text
|
|
374
|
+
- `linkMarkdown(text, options)`: Linkify markdown content
|
|
375
|
+
- `reset()`: Clear rules
|
|
386
376
|
|
|
387
|
-
|
|
388
|
-
// "[ENG-533](https://high.example/ENG-533) and [ENG-534](https://low.example/ENG-534)"
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
**Idempotent linkification**
|
|
377
|
+
## Markdown Utilities
|
|
392
378
|
|
|
393
|
-
|
|
394
|
-
const linker = createLinker().linear("my-org");
|
|
395
|
-
const first = linker.apply("Ship ENG-533", { format: "slack" });
|
|
396
|
-
const second = linker.apply(first, { format: "slack" });
|
|
397
|
-
// first === second (no double-linkification)
|
|
398
|
-
```
|
|
379
|
+
Tools for working with markdown fences and formatting.
|
|
399
380
|
|
|
400
|
-
|
|
381
|
+
### escapeFence
|
|
401
382
|
|
|
402
|
-
|
|
383
|
+
Selects the minimal fence length to safely escape content.
|
|
403
384
|
|
|
404
385
|
```typescript
|
|
405
|
-
import {
|
|
406
|
-
|
|
407
|
-
formatWithLineNumbers("foo\nbar\nbaz");
|
|
408
|
-
// 1: foo
|
|
409
|
-
// 2: bar
|
|
410
|
-
// 3: baz
|
|
386
|
+
import { escapeFence } from "@hardlydifficult/text";
|
|
411
387
|
|
|
412
|
-
|
|
413
|
-
//
|
|
414
|
-
// 11: world
|
|
388
|
+
escapeFence("hello"); // { fence: "```", content: "hello" }
|
|
389
|
+
escapeFence("code ``` here"); // { fence: "````", content: "code ``` here" }
|
|
415
390
|
```
|
|
416
391
|
|
|
417
|
-
|
|
392
|
+
### stripAnsi
|
|
418
393
|
|
|
419
|
-
|
|
394
|
+
Removes ANSI escape codes from strings.
|
|
420
395
|
|
|
421
396
|
```typescript
|
|
422
|
-
import {
|
|
397
|
+
import { stripAnsi } from "@hardlydifficult/text";
|
|
398
|
+
|
|
399
|
+
stripAnsi("\x1b[31mRed\x1b[0m"); // "Red"
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## License
|
|
423
403
|
|
|
424
|
-
|
|
425
|
-
// { fence: "````", content: "Content with `` backticks" }
|
|
426
|
-
```
|
|
404
|
+
MIT
|