@ai-ecoverse/slicc-man 1.1.0 → 1.1.2
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 +1 -1
- package/host/main.mjs +2 -0
- package/lib/man.mjs +8 -1
- package/lib/site.mjs +17 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -134,7 +134,7 @@ Pages, the sitemap and the topic index are cached:
|
|
|
134
134
|
|
|
135
135
|
Each site has its own directory in the cache, named by the base URL's host and port (`www.sliccy.com`, `127.0.0.1-8080`), with one JSON file per page, `sitemap.json` and `index.json`. A cached copy is fresh for the response's `Cache-Control` `max-age` (the site sends 4 hours), and 4 hours without one. A stale copy is revalidated with `If-Modified-Since` (the response's `Last-Modified`): `304` keeps it and makes it fresh again, `200` replaces it, and `404` drops it, so the topic has no page. When the site can't be reached, or answers 5xx, a cached copy is shown however old, after one line on stderr, `<cmd>: <url>: <reason>; showing the copy cached <date>`. With nothing cached, the command prints `<cmd>: <url>: <reason>` and exits 2. The cache is written atomically (a temporary file renamed into place), so two commands at once never see half a page; a cache that can't be written is skipped without a word.
|
|
136
136
|
|
|
137
|
-
`apropos` and `whatis` read the index: every `/man/<topic>` entry of the sitemap, with its description, the first paragraph of its page, rendered as plain text on one line. The index is built from the cached sitemap and pages, fetching what is missing or stale (pages through the same cache, several at a time), and cached itself under the same rules. A page that fails to load is listed without a description.
|
|
137
|
+
`apropos` and `whatis` read the index: every `/man/<topic>` entry of the sitemap, with its description, the first paragraph of its page, rendered as plain text on one line. The index is built from the cached sitemap and pages, fetching what is missing or stale (pages through the same cache, several at a time), and cached itself under the same rules. A page that fails to load is listed without a description, and an index with a failed page isn't cached, so the next command tries that page again. After an HTTP error (a 404 or a 5xx), the other pages are still fetched; after a network error or a timeout, no more pages are fetched, and the rest come from the cache.
|
|
138
138
|
|
|
139
139
|
`SLICC_MAN_URL` overrides the base URL `https://www.sliccy.com`: pages are `$SLICC_MAN_URL/man/<topic>.md` and the sitemap `$SLICC_MAN_URL/sitemap.xml`, whose entries count by their path, whatever their host. The tests point it at a local server.
|
|
140
140
|
|
package/host/main.mjs
CHANGED
|
@@ -32,6 +32,7 @@ export async function main({
|
|
|
32
32
|
columns,
|
|
33
33
|
fetch = globalThis.fetch,
|
|
34
34
|
now,
|
|
35
|
+
timeout,
|
|
35
36
|
}) {
|
|
36
37
|
const child = { ...env };
|
|
37
38
|
delete child.SCMAN_INSTALL_DIR;
|
|
@@ -41,6 +42,7 @@ export async function main({
|
|
|
41
42
|
columns,
|
|
42
43
|
fetch,
|
|
43
44
|
now,
|
|
45
|
+
timeout,
|
|
44
46
|
version: version(),
|
|
45
47
|
out: (text) => say(fds[1], text),
|
|
46
48
|
err: (text) => say(fds[2], text),
|
package/lib/man.mjs
CHANGED
|
@@ -201,7 +201,14 @@ export async function run({ kind = 'man', name, argv, env, io }) {
|
|
|
201
201
|
values: parsed.values,
|
|
202
202
|
width: width(env, io),
|
|
203
203
|
styled: Boolean(io.tty) && !env.NO_COLOR,
|
|
204
|
-
site: site({
|
|
204
|
+
site: site({
|
|
205
|
+
url,
|
|
206
|
+
store: io.store(folder(url)),
|
|
207
|
+
fetch: io.fetch,
|
|
208
|
+
now: io.now,
|
|
209
|
+
name,
|
|
210
|
+
timeout: io.timeout,
|
|
211
|
+
}),
|
|
205
212
|
};
|
|
206
213
|
if (kind !== 'man') return keywords(kind, ctx, parsed);
|
|
207
214
|
if (parsed.flags.apropos) return keywords('apropos', ctx, parsed);
|
package/lib/site.mjs
CHANGED
|
@@ -69,7 +69,7 @@ export function topics(xml) {
|
|
|
69
69
|
return out;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
export function site({ url, store, fetch, now = Date.now, name }) {
|
|
72
|
+
export function site({ url, store, fetch, now = Date.now, name, timeout = TIMEOUT }) {
|
|
73
73
|
const root = url.replace(/\/+$/, '');
|
|
74
74
|
|
|
75
75
|
async function load(id) {
|
|
@@ -101,7 +101,7 @@ export function site({ url, store, fetch, now = Date.now, name }) {
|
|
|
101
101
|
async function request(address, id, entry) {
|
|
102
102
|
const headers = {};
|
|
103
103
|
if (entry?.lastModified) headers['if-modified-since'] = entry.lastModified;
|
|
104
|
-
const response = await fetch(address, { headers, signal: AbortSignal.timeout(
|
|
104
|
+
const response = await fetch(address, { headers, signal: AbortSignal.timeout(timeout) });
|
|
105
105
|
if (response.status === 304 && typeof entry?.body === 'string') {
|
|
106
106
|
await response.body?.cancel();
|
|
107
107
|
await save(id, { ...entry, fetched: now(), maxAge: age(response.headers) ?? entry.maxAge });
|
|
@@ -131,12 +131,20 @@ export function site({ url, store, fetch, now = Date.now, name }) {
|
|
|
131
131
|
try {
|
|
132
132
|
found = await request(address, id, entry);
|
|
133
133
|
} catch (error) {
|
|
134
|
-
found = { why: reason(error) };
|
|
134
|
+
found = { why: reason(error), transport: true };
|
|
135
135
|
}
|
|
136
136
|
if (found.status) return found;
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
const transport = found.transport ? { transport: true } : {};
|
|
138
|
+
if (!kept) {
|
|
139
|
+
return {
|
|
140
|
+
status: 'error',
|
|
141
|
+
message: `${name}: ${address}: ${found.why}`,
|
|
142
|
+
offline: true,
|
|
143
|
+
...transport,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
139
146
|
return {
|
|
147
|
+
...transport,
|
|
140
148
|
status: 'ok',
|
|
141
149
|
body: entry.body,
|
|
142
150
|
stale: true,
|
|
@@ -154,12 +162,14 @@ export function site({ url, store, fetch, now = Date.now, name }) {
|
|
|
154
162
|
async function build(list, offline) {
|
|
155
163
|
const entries = new Array(list.length);
|
|
156
164
|
let next = 0;
|
|
157
|
-
let down =
|
|
165
|
+
let down = false;
|
|
166
|
+
let broken = offline;
|
|
158
167
|
async function worker() {
|
|
159
168
|
while (next < list.length) {
|
|
160
169
|
const at = next++;
|
|
161
170
|
const topic = list[at];
|
|
162
|
-
const found = await page(topic, { offline:
|
|
171
|
+
const found = await page(topic, { offline: broken });
|
|
172
|
+
if (found.transport) broken = true;
|
|
163
173
|
if (found.stale || found.offline) down = true;
|
|
164
174
|
entries[at] = { topic, description: found.status === 'ok' ? describe(found.body) : '' };
|
|
165
175
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/slicc-man",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "man, apropos and whatis for SLICC, and scman for SLICC, macOS, Linux and Windows, reading pages from www.sliccy.com/man",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|