@ai-ecoverse/slicc-man 0.0.0 → 1.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/LICENSE +201 -0
- package/README.md +179 -5
- package/host/cache.mjs +43 -0
- package/host/io.mjs +15 -0
- package/host/main.mjs +54 -0
- package/host/pager.mjs +38 -0
- package/host/path.mjs +33 -0
- package/host/scman.mjs +14 -0
- package/host/upgrade.mjs +93 -0
- package/lib/man.mjs +215 -0
- package/lib/markdown.mjs +456 -0
- package/lib/options.mjs +192 -0
- package/lib/render.mjs +214 -0
- package/lib/search.mjs +83 -0
- package/lib/site.mjs +205 -0
- package/package.json +47 -1
package/lib/markdown.mjs
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
const BOLD = 1;
|
|
2
|
+
const UNDERLINE = 2;
|
|
3
|
+
|
|
4
|
+
export { BOLD, UNDERLINE };
|
|
5
|
+
|
|
6
|
+
const punctuation = /^[!-/:-@[-`{-~]$/;
|
|
7
|
+
const fence = /^( {0,3})(`{3,}|~{3,})(.*)$/;
|
|
8
|
+
const closing = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
|
|
9
|
+
const atx = /^ {0,3}(#{1,6})(?:[ \t]+(.*?))?(?:[ \t]+#+)?[ \t]*$/;
|
|
10
|
+
const rule = /^ {0,3}([-*_])(?:[ \t]*\1){2,}[ \t]*$/;
|
|
11
|
+
const grid = /^ {0,3}\+[-=:+]*\+[ \t]*$/;
|
|
12
|
+
const delimiter = /^ {0,3}\|?[ \t]*:?-+:?[ \t]*(\|[ \t]*:?-+:?[ \t]*)*\|?[ \t]*$/;
|
|
13
|
+
const quote = /^ {0,3}> ?/;
|
|
14
|
+
const item = /^( {0,3})([-+*]|\d{1,9}[.)])(?:([ \t]+)(.*))?$/;
|
|
15
|
+
const setext = /^ {0,3}(=+|-+)[ \t]*$/;
|
|
16
|
+
const autolink = /^<([a-zA-Z][a-zA-Z0-9+.-]{1,31}:[^\s<>]*|[^\s<>@]+@[^\s<>@]+\.[^\s<>@]+)>/;
|
|
17
|
+
const reference = /^&(?:#(\d{1,7})|#[xX]([0-9a-fA-F]{1,6})|([a-zA-Z]+));/;
|
|
18
|
+
const entities = { amp: '&', lt: '<', gt: '>', quot: '"', apos: "'", nbsp: '\u00a0' };
|
|
19
|
+
|
|
20
|
+
function expand(line) {
|
|
21
|
+
let out = '';
|
|
22
|
+
for (const ch of line) out += ch === '\t' ? ' '.repeat(4 - (out.length % 4)) : ch;
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function clean(text) {
|
|
27
|
+
return String(text)
|
|
28
|
+
.replace(/\r\n?/g, '\n')
|
|
29
|
+
.split('\n')
|
|
30
|
+
.map(expand)
|
|
31
|
+
.join('\n')
|
|
32
|
+
.replace(/[\u0000-\u0009\u000b-\u001f\u007f-\u009f]/g, '');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const blank = (line) => /^[ \t]*$/.test(line);
|
|
36
|
+
const indentOf = (line) => line.length - line.trimStart().length;
|
|
37
|
+
|
|
38
|
+
function starts(line) {
|
|
39
|
+
return (
|
|
40
|
+
fence.test(line) ||
|
|
41
|
+
atx.test(line) ||
|
|
42
|
+
rule.test(line) ||
|
|
43
|
+
quote.test(line) ||
|
|
44
|
+
grid.test(line) ||
|
|
45
|
+
/^ {0,3}[-+*][ \t]+\S/.test(line) ||
|
|
46
|
+
/^ {0,3}1[.)][ \t]+\S/.test(line)
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function cells(line) {
|
|
51
|
+
let text = line.trim();
|
|
52
|
+
if (text.startsWith('|')) text = text.slice(1);
|
|
53
|
+
if (text.endsWith('|') && !text.endsWith('\\|')) text = text.slice(0, -1);
|
|
54
|
+
return text.split(/(?<!\\)\|/).map((cell) => cell.replaceAll('\\|', '|').trim());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function matcher(first) {
|
|
58
|
+
const bullet = /^[-+*]$/.test(first[2]);
|
|
59
|
+
const kind = bullet ? first[2] : first[2].slice(-1);
|
|
60
|
+
return (line) => {
|
|
61
|
+
const match = line === undefined ? null : item.exec(line);
|
|
62
|
+
if (!match || rule.test(line)) return undefined;
|
|
63
|
+
const ok = bullet ? match[2] === kind : /\d/.test(match[2]) && match[2].endsWith(kind);
|
|
64
|
+
return ok ? match : undefined;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function entry(lines, at, match) {
|
|
69
|
+
const spaces = match[3] ?? '';
|
|
70
|
+
const wide = spaces.length > 4;
|
|
71
|
+
const width = match[1].length + match[2].length + (wide ? 1 : spaces.length || 1);
|
|
72
|
+
const body = [wide ? spaces.slice(1) + match[4] : (match[4] ?? '')];
|
|
73
|
+
let i = at + 1;
|
|
74
|
+
let gap = false;
|
|
75
|
+
while (i < lines.length) {
|
|
76
|
+
const line = lines[i];
|
|
77
|
+
if (blank(line)) body.push('');
|
|
78
|
+
else if (indentOf(line) >= width) {
|
|
79
|
+
if (body.at(-1) === '') gap = true;
|
|
80
|
+
body.push(line.slice(width));
|
|
81
|
+
} else if (body.at(-1) !== '' && !starts(line) && !item.test(line)) body.push(line);
|
|
82
|
+
else break;
|
|
83
|
+
i++;
|
|
84
|
+
}
|
|
85
|
+
let trailing = 0;
|
|
86
|
+
while (body.length > 1 && body.at(-1) === '') {
|
|
87
|
+
body.pop();
|
|
88
|
+
trailing++;
|
|
89
|
+
}
|
|
90
|
+
return { item: { marker: match[2], blocks: blocks(body) }, next: i, gap, trailing };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function list(lines, start) {
|
|
94
|
+
const same = matcher(item.exec(lines[start]));
|
|
95
|
+
const items = [];
|
|
96
|
+
let loose = false;
|
|
97
|
+
let i = start;
|
|
98
|
+
let match = same(lines[i]);
|
|
99
|
+
while (match) {
|
|
100
|
+
const found = entry(lines, i, match);
|
|
101
|
+
items.push(found.item);
|
|
102
|
+
loose ||= found.gap;
|
|
103
|
+
i = found.next;
|
|
104
|
+
match = same(lines[i]);
|
|
105
|
+
if (found.trailing > 0 && match) loose = true;
|
|
106
|
+
if (found.trailing > 0 && !match) i -= found.trailing;
|
|
107
|
+
}
|
|
108
|
+
return { block: { type: 'list', items, loose }, next: i };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function fenced(lines, i) {
|
|
112
|
+
const open = fence.exec(lines[i]);
|
|
113
|
+
if (!open || (open[2][0] === '`' && open[3].includes('`'))) return undefined;
|
|
114
|
+
const body = [];
|
|
115
|
+
let j = i + 1;
|
|
116
|
+
while (j < lines.length) {
|
|
117
|
+
const close = closing.exec(lines[j]);
|
|
118
|
+
if (close && close[1][0] === open[2][0] && close[1].length >= open[2].length) {
|
|
119
|
+
j++;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
body.push(lines[j].slice(Math.min(indentOf(lines[j]), open[1].length)));
|
|
123
|
+
j++;
|
|
124
|
+
}
|
|
125
|
+
return { block: { type: 'code', lines: body }, next: j };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function heading(lines, i) {
|
|
129
|
+
const found = atx.exec(lines[i]);
|
|
130
|
+
if (!found) return undefined;
|
|
131
|
+
return { block: { type: 'heading', level: found[1].length, text: found[2] ?? '' }, next: i + 1 };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function ruled(lines, i) {
|
|
135
|
+
return rule.test(lines[i]) ? { block: { type: 'rule' }, next: i + 1 } : undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function gridded(lines, i) {
|
|
139
|
+
if (!grid.test(lines[i])) return undefined;
|
|
140
|
+
const body = [];
|
|
141
|
+
let j = i;
|
|
142
|
+
while (j < lines.length && /^ {0,3}[+|]/.test(lines[j])) body.push(lines[j++].trim());
|
|
143
|
+
const rows = [];
|
|
144
|
+
for (const row of body) {
|
|
145
|
+
if (row.startsWith('+')) rows.push([]);
|
|
146
|
+
else rows.at(-1).push(row);
|
|
147
|
+
}
|
|
148
|
+
const head = rows.find((row) => row.length > 0) ?? [];
|
|
149
|
+
return {
|
|
150
|
+
block: { type: 'grid', lines: body, first: head.map((row) => cells(row).join(' ')) },
|
|
151
|
+
next: j,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function piped(lines, i) {
|
|
156
|
+
if (!lines[i].includes('|') || !delimiter.test(lines[i + 1] ?? '')) return undefined;
|
|
157
|
+
const rows = [cells(lines[i])];
|
|
158
|
+
const columns = cells(lines[i + 1]).length;
|
|
159
|
+
let j = i + 2;
|
|
160
|
+
while (j < lines.length && !blank(lines[j]) && lines[j].includes('|') && !starts(lines[j])) {
|
|
161
|
+
rows.push(cells(lines[j++]));
|
|
162
|
+
}
|
|
163
|
+
return { block: { type: 'table', rows, columns }, next: j };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function quoted(lines, i) {
|
|
167
|
+
if (!quote.test(lines[i])) return undefined;
|
|
168
|
+
const body = [];
|
|
169
|
+
let j = i;
|
|
170
|
+
while (j < lines.length && !blank(lines[j])) {
|
|
171
|
+
if (quote.test(lines[j])) body.push(lines[j].replace(quote, ''));
|
|
172
|
+
else if (starts(lines[j])) break;
|
|
173
|
+
else body.push(lines[j]);
|
|
174
|
+
j++;
|
|
175
|
+
}
|
|
176
|
+
return { block: { type: 'quote', blocks: blocks(body) }, next: j };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function listed(lines, i) {
|
|
180
|
+
const match = item.exec(lines[i]);
|
|
181
|
+
if (!match || (match[3] === undefined && !/^ {0,3}[-+*]$/.test(lines[i]))) return undefined;
|
|
182
|
+
return list(lines, i);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function indented(lines, i) {
|
|
186
|
+
if (!/^ {4}/.test(lines[i])) return undefined;
|
|
187
|
+
const body = [];
|
|
188
|
+
let j = i;
|
|
189
|
+
while (j < lines.length && (/^ {4}/.test(lines[j]) || blank(lines[j]))) {
|
|
190
|
+
body.push(lines[j++].slice(4));
|
|
191
|
+
}
|
|
192
|
+
while (blank(body.at(-1))) body.pop();
|
|
193
|
+
return { block: { type: 'code', lines: body }, next: j };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function paragraph(lines, i) {
|
|
197
|
+
const body = [lines[i]];
|
|
198
|
+
let j = i + 1;
|
|
199
|
+
while (j < lines.length && !blank(lines[j])) {
|
|
200
|
+
const under = setext.exec(lines[j]);
|
|
201
|
+
if (under) {
|
|
202
|
+
const text = body.map((line) => line.trim()).join(' ');
|
|
203
|
+
const level = under[1][0] === '=' ? 1 : 2;
|
|
204
|
+
return { block: { type: 'heading', level, text }, next: j + 1 };
|
|
205
|
+
}
|
|
206
|
+
if (starts(lines[j])) break;
|
|
207
|
+
body.push(lines[j++]);
|
|
208
|
+
}
|
|
209
|
+
const text = body.map((line) => line.trimStart()).join('\n');
|
|
210
|
+
return { block: { type: 'paragraph', text }, next: j };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const parsers = [fenced, heading, ruled, gridded, piped, quoted, listed, indented, paragraph];
|
|
214
|
+
|
|
215
|
+
export function blocks(lines) {
|
|
216
|
+
const out = [];
|
|
217
|
+
let i = 0;
|
|
218
|
+
while (i < lines.length) {
|
|
219
|
+
if (blank(lines[i])) {
|
|
220
|
+
i++;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
for (const parser of parsers) {
|
|
224
|
+
const found = parser(lines, i);
|
|
225
|
+
if (found) {
|
|
226
|
+
out.push(found.block);
|
|
227
|
+
i = found.next;
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return out;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function parse(markdown) {
|
|
236
|
+
return blocks(clean(markdown).split('\n'));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function entity(text, at) {
|
|
240
|
+
const match = reference.exec(text.slice(at));
|
|
241
|
+
if (!match) return undefined;
|
|
242
|
+
let value = entities[match[3]?.toLowerCase()];
|
|
243
|
+
if (match[1] !== undefined) value = String.fromCodePoint(Math.min(Number(match[1]), 0x10ffff));
|
|
244
|
+
if (match[2] !== undefined) {
|
|
245
|
+
value = String.fromCodePoint(Math.min(Number.parseInt(match[2], 16), 0x10ffff));
|
|
246
|
+
}
|
|
247
|
+
if (value === undefined) return undefined;
|
|
248
|
+
return { value: clean(value), length: match[0].length };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function ticks(text, at) {
|
|
252
|
+
let n = 0;
|
|
253
|
+
while (text[at + n] === '`') n++;
|
|
254
|
+
return n;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function span(text, at) {
|
|
258
|
+
const n = ticks(text, at);
|
|
259
|
+
let j = at + n;
|
|
260
|
+
while (j < text.length) {
|
|
261
|
+
const m = ticks(text, j);
|
|
262
|
+
if (m === n) {
|
|
263
|
+
let code = text.slice(at + n, j).replace(/\n/g, ' ');
|
|
264
|
+
if (/^ .*[^ ].* $|^ [^ ] $/.test(code)) code = code.slice(1, -1);
|
|
265
|
+
return { code, end: j + m };
|
|
266
|
+
}
|
|
267
|
+
j += Math.max(m, 1);
|
|
268
|
+
}
|
|
269
|
+
return { end: at + n };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function skip(text, at) {
|
|
273
|
+
if (text[at] === '\\') return at + 2;
|
|
274
|
+
if (text[at] === '`') return span(text, at).end;
|
|
275
|
+
return at + 1;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function bracket(text, at) {
|
|
279
|
+
let depth = 0;
|
|
280
|
+
for (let j = at; j < text.length; j = skip(text, j)) {
|
|
281
|
+
if (text[j] === '[') depth++;
|
|
282
|
+
if (text[j] === ']' && --depth === 0) return j;
|
|
283
|
+
}
|
|
284
|
+
return -1;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function target(text, at) {
|
|
288
|
+
if (text[at] === '[') {
|
|
289
|
+
const close = text.indexOf(']', at);
|
|
290
|
+
return close === -1 ? -1 : close + 1;
|
|
291
|
+
}
|
|
292
|
+
if (text[at] !== '(') return -1;
|
|
293
|
+
let depth = 0;
|
|
294
|
+
for (let j = at; j < text.length; j += text[j] === '\\' ? 2 : 1) {
|
|
295
|
+
if (text[j] === '(') depth++;
|
|
296
|
+
if (text[j] === ')' && --depth === 0) return j + 1;
|
|
297
|
+
}
|
|
298
|
+
return -1;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const space = (ch) => ch === undefined || /\s/u.test(ch);
|
|
302
|
+
const word = (ch) => ch !== undefined && /[\p{L}\p{N}]/u.test(ch);
|
|
303
|
+
const mark = (ch) => ch !== undefined && /[\p{P}\p{S}]/u.test(ch);
|
|
304
|
+
|
|
305
|
+
function opens(text, at, n) {
|
|
306
|
+
const before = text[at - 1];
|
|
307
|
+
const after = text[at + n];
|
|
308
|
+
if (space(after)) return false;
|
|
309
|
+
if (mark(after) && !space(before) && !mark(before)) return false;
|
|
310
|
+
return !(text[at] === '_' && word(before));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function closes(text, at, n) {
|
|
314
|
+
const before = text[at - 1];
|
|
315
|
+
const after = text[at + n];
|
|
316
|
+
if (space(before)) return false;
|
|
317
|
+
if (mark(before) && !space(after) && !mark(after)) return false;
|
|
318
|
+
return !(text[at] === '_' && word(after));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function link(text, at) {
|
|
322
|
+
if (text[at] !== '[' || text[at - 1] === '!') return -1;
|
|
323
|
+
const end = bracket(text, at);
|
|
324
|
+
return end === -1 ? -1 : target(text, end + 1);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function closer(text, at, ch, n) {
|
|
328
|
+
let j = at;
|
|
329
|
+
while (j < text.length) {
|
|
330
|
+
if (text[j] === ch) {
|
|
331
|
+
const m = run(text, j, ch);
|
|
332
|
+
if (m >= n && closes(text, j, m)) return j;
|
|
333
|
+
j += m;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
const after = link(text, j);
|
|
337
|
+
j = after === -1 ? skip(text, j) : after;
|
|
338
|
+
}
|
|
339
|
+
return -1;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function run(text, at, ch) {
|
|
343
|
+
let n = 0;
|
|
344
|
+
while (text[at + n] === ch) n++;
|
|
345
|
+
return n;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function escaped(text, i, state) {
|
|
349
|
+
const next = text[i + 1];
|
|
350
|
+
if (next === '\n') return state.br(i + 2);
|
|
351
|
+
if (next !== undefined && punctuation.test(next)) return state.add(next, i + 2);
|
|
352
|
+
return state.add('\\', i + 1);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function coded(text, i, state) {
|
|
356
|
+
const found = span(text, i);
|
|
357
|
+
if (found.code === undefined) return state.add(text.slice(i, found.end), found.end);
|
|
358
|
+
return state.push({ text: found.code, style: state.style | BOLD }, found.end);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function entityRef(text, i, state) {
|
|
362
|
+
const found = entity(text, i);
|
|
363
|
+
return found ? state.add(found.value, i + found.length) : state.add('&', i + 1);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function autolinked(text, i, state) {
|
|
367
|
+
const found = autolink.exec(text.slice(i));
|
|
368
|
+
return found ? state.add(found[1], i + found[0].length) : state.add('<', i + 1);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function imaged(text, i, state) {
|
|
372
|
+
return text[i + 1] === '[' ? linked(text, i + 1, state, i) : state.add('!', i + 1);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function linked(text, i, state, from = i) {
|
|
376
|
+
const end = bracket(text, i);
|
|
377
|
+
const after = end === -1 ? -1 : target(text, end + 1);
|
|
378
|
+
if (after === -1) return state.add(text.slice(from, i + 1), i + 1);
|
|
379
|
+
state.flush();
|
|
380
|
+
inline(text.slice(i + 1, end), state.style, state.out);
|
|
381
|
+
return after;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function emphasized(text, i, state) {
|
|
385
|
+
const ch = text[i];
|
|
386
|
+
const n = run(text, i, ch);
|
|
387
|
+
let use = Math.min(n, 3);
|
|
388
|
+
let end = -1;
|
|
389
|
+
for (; use > 0 && end === -1; use--) {
|
|
390
|
+
end = opens(text, i + n - use, use) ? closer(text, i + n, ch, use) : -1;
|
|
391
|
+
}
|
|
392
|
+
use++;
|
|
393
|
+
if (end === -1) return state.add(ch.repeat(n), i + n);
|
|
394
|
+
state.add(ch.repeat(n - use), i);
|
|
395
|
+
state.flush();
|
|
396
|
+
const flag = [0, UNDERLINE, BOLD, BOLD | UNDERLINE][use];
|
|
397
|
+
inline(text.slice(i + n, end), state.style | flag, state.out);
|
|
398
|
+
return end + use;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function newline(text, i, state) {
|
|
402
|
+
if (!/ {2,}$/.test(state.buffer)) return state.add(' ', i + 1);
|
|
403
|
+
state.buffer = state.buffer.replace(/ +$/, '');
|
|
404
|
+
return state.br(i + 1);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const handlers = {
|
|
408
|
+
'\\': escaped,
|
|
409
|
+
'`': coded,
|
|
410
|
+
'&': entityRef,
|
|
411
|
+
'<': autolinked,
|
|
412
|
+
'!': imaged,
|
|
413
|
+
'[': linked,
|
|
414
|
+
'*': emphasized,
|
|
415
|
+
_: emphasized,
|
|
416
|
+
'\n': newline,
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
export function inline(text, style = 0, out = []) {
|
|
420
|
+
const state = {
|
|
421
|
+
style,
|
|
422
|
+
out,
|
|
423
|
+
buffer: '',
|
|
424
|
+
flush() {
|
|
425
|
+
if (state.buffer !== '') out.push({ text: state.buffer, style });
|
|
426
|
+
state.buffer = '';
|
|
427
|
+
},
|
|
428
|
+
add(value, next) {
|
|
429
|
+
state.buffer += value;
|
|
430
|
+
return next;
|
|
431
|
+
},
|
|
432
|
+
push(value, next) {
|
|
433
|
+
state.flush();
|
|
434
|
+
out.push(value);
|
|
435
|
+
return next;
|
|
436
|
+
},
|
|
437
|
+
br(next) {
|
|
438
|
+
return state.push({ text: '\n', style: 0, br: true }, next);
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
let i = 0;
|
|
442
|
+
while (i < text.length) {
|
|
443
|
+
const handler = handlers[text[i]];
|
|
444
|
+
i = handler ? handler(text, i, state) : state.add(text[i], i + 1);
|
|
445
|
+
}
|
|
446
|
+
state.flush();
|
|
447
|
+
return out;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export function plain(runs) {
|
|
451
|
+
return runs
|
|
452
|
+
.map((run) => (run.br ? ' ' : run.text))
|
|
453
|
+
.join('')
|
|
454
|
+
.replace(/\s+/g, ' ')
|
|
455
|
+
.trim();
|
|
456
|
+
}
|
package/lib/options.mjs
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
const MAN_UNSUPPORTED_SHORT = 'tTHXZMmLCdDWcuepREKr';
|
|
2
|
+
const MAN_UNSUPPORTED_LONG = [
|
|
3
|
+
'troff',
|
|
4
|
+
'troff-device',
|
|
5
|
+
'html',
|
|
6
|
+
'gxditview',
|
|
7
|
+
'ditroff',
|
|
8
|
+
'manpath',
|
|
9
|
+
'systems',
|
|
10
|
+
'locale',
|
|
11
|
+
'config-file',
|
|
12
|
+
'debug',
|
|
13
|
+
'default',
|
|
14
|
+
'where-cat',
|
|
15
|
+
'catman',
|
|
16
|
+
'update',
|
|
17
|
+
'extension',
|
|
18
|
+
'preprocessor',
|
|
19
|
+
'recode',
|
|
20
|
+
'encoding',
|
|
21
|
+
'global-apropos',
|
|
22
|
+
'prompt',
|
|
23
|
+
'regex',
|
|
24
|
+
'wildcard',
|
|
25
|
+
'names-only',
|
|
26
|
+
'no-subpages',
|
|
27
|
+
];
|
|
28
|
+
const SEARCH_UNSUPPORTED_SHORT = 'MmLCdv';
|
|
29
|
+
const SEARCH_UNSUPPORTED_LONG = ['manpath', 'systems', 'locale', 'config-file', 'debug', 'verbose'];
|
|
30
|
+
|
|
31
|
+
const common = { h: 'help', V: 'version' };
|
|
32
|
+
const commonLong = { help: 'help', version: 'version', upgrade: 'upgrade' };
|
|
33
|
+
|
|
34
|
+
export const tables = {
|
|
35
|
+
man: {
|
|
36
|
+
short: { ...common, k: 'apropos', f: 'whatis', a: 'all', w: 'where', l: 'local' },
|
|
37
|
+
shortExtra: { i: 'ignore', I: 'match' },
|
|
38
|
+
values: { s: 'sections', P: 'pager' },
|
|
39
|
+
long: {
|
|
40
|
+
...commonLong,
|
|
41
|
+
apropos: 'apropos',
|
|
42
|
+
whatis: 'whatis',
|
|
43
|
+
all: 'all',
|
|
44
|
+
where: 'where',
|
|
45
|
+
path: 'where',
|
|
46
|
+
location: 'where',
|
|
47
|
+
'local-file': 'local',
|
|
48
|
+
'ignore-case': 'ignore',
|
|
49
|
+
'match-case': 'match',
|
|
50
|
+
nh: 'nh',
|
|
51
|
+
'no-hyphenation': 'nh',
|
|
52
|
+
nj: 'nj',
|
|
53
|
+
'no-justification': 'nj',
|
|
54
|
+
},
|
|
55
|
+
longValues: { sections: 'sections', pager: 'pager' },
|
|
56
|
+
unsupportedShort: MAN_UNSUPPORTED_SHORT,
|
|
57
|
+
unsupportedLong: MAN_UNSUPPORTED_LONG,
|
|
58
|
+
},
|
|
59
|
+
apropos: {
|
|
60
|
+
short: { ...common, r: 'regex', w: 'wildcard', e: 'exact', a: 'and', l: 'long' },
|
|
61
|
+
shortExtra: {},
|
|
62
|
+
values: { s: 'sections' },
|
|
63
|
+
long: {
|
|
64
|
+
...commonLong,
|
|
65
|
+
regex: 'regex',
|
|
66
|
+
wildcard: 'wildcard',
|
|
67
|
+
exact: 'exact',
|
|
68
|
+
and: 'and',
|
|
69
|
+
long: 'long',
|
|
70
|
+
},
|
|
71
|
+
longValues: { sections: 'sections' },
|
|
72
|
+
unsupportedShort: SEARCH_UNSUPPORTED_SHORT,
|
|
73
|
+
unsupportedLong: SEARCH_UNSUPPORTED_LONG,
|
|
74
|
+
},
|
|
75
|
+
whatis: {
|
|
76
|
+
short: { ...common, r: 'regex', w: 'wildcard', e: 'exact', l: 'long' },
|
|
77
|
+
shortExtra: {},
|
|
78
|
+
values: { s: 'sections' },
|
|
79
|
+
long: { ...commonLong, regex: 'regex', wildcard: 'wildcard', exact: 'exact', long: 'long' },
|
|
80
|
+
longValues: { sections: 'sections' },
|
|
81
|
+
unsupportedShort: SEARCH_UNSUPPORTED_SHORT,
|
|
82
|
+
unsupportedLong: SEARCH_UNSUPPORTED_LONG,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export function usage(kind, name) {
|
|
87
|
+
if (kind === 'man') {
|
|
88
|
+
return `usage: ${name} [options] [section] topic...
|
|
89
|
+
${name} -k [options] keyword...
|
|
90
|
+
${name} -f [options] topic...
|
|
91
|
+
${name} -l [options] file...
|
|
92
|
+
|
|
93
|
+
-s, --sections=LIST look only in the sections in LIST, separated by : or ,
|
|
94
|
+
-k, --apropos search the topics and descriptions, as apropos does
|
|
95
|
+
-f, --whatis show the topics' one-line descriptions, as whatis does
|
|
96
|
+
-a, --all show every matching page
|
|
97
|
+
-w, --where print the page's URL instead of showing it
|
|
98
|
+
-P, --pager=PAGER page the output with PAGER
|
|
99
|
+
-l, --local-file render the operands, local Markdown files (- for stdin)
|
|
100
|
+
-i, --ignore-case look the topic up in lower case (the default)
|
|
101
|
+
-I, --match-case look the topic up as typed
|
|
102
|
+
--nh, --nj accepted; nothing is hyphenated or justified
|
|
103
|
+
-h, --help print this help and exit
|
|
104
|
+
-V, --version print the version and exit
|
|
105
|
+
--upgrade upgrade to the latest release
|
|
106
|
+
`;
|
|
107
|
+
}
|
|
108
|
+
const and = kind === 'apropos' ? ' -a, --and a page must match every keyword\n' : '';
|
|
109
|
+
const what = kind === 'apropos' ? 'keyword' : 'topic';
|
|
110
|
+
return `usage: ${name} [options] ${what}...
|
|
111
|
+
|
|
112
|
+
-r, --regex each ${what} is a regular expression${kind === 'apropos' ? ' (the default)' : ''}
|
|
113
|
+
-w, --wildcard each ${what} is a shell wildcard
|
|
114
|
+
-e, --exact each ${what} must match exactly${kind === 'whatis' ? ' (the default)' : ''}
|
|
115
|
+
${and} -l, --long don't cut lines to the terminal width
|
|
116
|
+
-s, --sections=LIST look only in the sections in LIST, separated by : or ,
|
|
117
|
+
-h, --help print this help and exit
|
|
118
|
+
-V, --version print the version and exit
|
|
119
|
+
--upgrade upgrade to the latest release
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const pairs = { match: 'ignore', ignore: 'match', apropos: 'whatis', whatis: 'apropos' };
|
|
124
|
+
|
|
125
|
+
function longOption(table, state, arg, argv) {
|
|
126
|
+
const eq = arg.indexOf('=');
|
|
127
|
+
const long = eq === -1 ? arg.slice(2) : arg.slice(2, eq);
|
|
128
|
+
if (table.unsupportedLong.includes(long)) return { error: `--${long}: not supported` };
|
|
129
|
+
const value = table.longValues[long];
|
|
130
|
+
if (value !== undefined) {
|
|
131
|
+
if (eq !== -1) state.values[value] = arg.slice(eq + 1);
|
|
132
|
+
else if (state.i < argv.length) state.values[value] = argv[state.i++];
|
|
133
|
+
else return { error: `--${long}: needs a value`, usage: true };
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
const flag = eq === -1 ? table.long[long] : undefined;
|
|
137
|
+
if (flag === undefined) return { error: `unknown option: ${arg}`, usage: true };
|
|
138
|
+
return state.set(flag);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function shortOptions(table, state, arg, argv) {
|
|
142
|
+
for (let j = 1; j < arg.length; j++) {
|
|
143
|
+
const ch = arg[j];
|
|
144
|
+
if (table.unsupportedShort.includes(ch)) return { error: `-${ch}: not supported` };
|
|
145
|
+
const value = table.values[ch];
|
|
146
|
+
if (value !== undefined) {
|
|
147
|
+
if (j + 1 < arg.length) state.values[value] = arg.slice(j + 1);
|
|
148
|
+
else if (state.i < argv.length) state.values[value] = argv[state.i++];
|
|
149
|
+
else return { error: `-${ch}: needs a value`, usage: true };
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
const flag = table.short[ch] ?? table.shortExtra[ch];
|
|
153
|
+
if (flag === undefined) return { error: `unknown option: -${ch}`, usage: true };
|
|
154
|
+
const stop = state.set(flag);
|
|
155
|
+
if (stop) return stop;
|
|
156
|
+
}
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function parse(kind, argv) {
|
|
161
|
+
const table = tables[kind];
|
|
162
|
+
const flags = {};
|
|
163
|
+
const values = {};
|
|
164
|
+
const operands = [];
|
|
165
|
+
const state = {
|
|
166
|
+
i: 0,
|
|
167
|
+
values,
|
|
168
|
+
set(flag) {
|
|
169
|
+
if (flag === 'help' || flag === 'version') return { [flag]: true };
|
|
170
|
+
flags[flag] = true;
|
|
171
|
+
delete flags[pairs[flag]];
|
|
172
|
+
return undefined;
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
const done = (extra) => ({ flags, values, operands, ...extra });
|
|
176
|
+
while (state.i < argv.length) {
|
|
177
|
+
const arg = argv[state.i++];
|
|
178
|
+
if (arg === '--') {
|
|
179
|
+
operands.push(...argv.slice(state.i));
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
let stop;
|
|
183
|
+
if (arg.startsWith('--')) stop = longOption(table, state, arg, argv);
|
|
184
|
+
else if (arg.startsWith('-') && arg !== '-') stop = shortOptions(table, state, arg, argv);
|
|
185
|
+
else operands.push(arg);
|
|
186
|
+
if (stop) return done(stop);
|
|
187
|
+
}
|
|
188
|
+
if (flags.upgrade && argv.length > 1) {
|
|
189
|
+
return done({ error: '--upgrade takes no other arguments', usage: true });
|
|
190
|
+
}
|
|
191
|
+
return done({});
|
|
192
|
+
}
|