mdrefine 0.2.0-rc.2 → 0.2.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/CHANGELOG.md +577 -0
- package/CODE_OF_CONDUCT.md +30 -0
- package/CONTRIBUTING.md +189 -16
- package/README.md +100 -154
- package/SECURITY.md +15 -13
- package/config.schema.json +91 -0
- package/dist/cli/diff.d.ts +10 -0
- package/dist/cli/diff.js +247 -0
- package/dist/cli/diff.js.map +1 -0
- package/dist/cli/main.js +270 -150
- package/dist/cli/main.js.map +1 -1
- package/dist/config/load.js +67 -6
- package/dist/config/load.js.map +1 -1
- package/dist/config/problems.d.ts +7 -0
- package/dist/config/problems.js +208 -0
- package/dist/config/problems.js.map +1 -0
- package/dist/config/resolve.d.ts +42 -109
- package/dist/config/resolve.js +193 -52
- package/dist/config/resolve.js.map +1 -1
- package/dist/core/directives.d.ts +7 -0
- package/dist/core/directives.js +25 -13
- package/dist/core/directives.js.map +1 -1
- package/dist/core/engine.d.ts +42 -4
- package/dist/core/engine.js +518 -79
- package/dist/core/engine.js.map +1 -1
- package/dist/core/frozen.d.ts +6 -0
- package/dist/core/frozen.js +15 -0
- package/dist/core/frozen.js.map +1 -0
- package/dist/core/scope.d.ts +30 -0
- package/dist/core/scope.js +122 -0
- package/dist/core/scope.js.map +1 -0
- package/dist/core/types.d.ts +202 -23
- package/dist/core/types.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +3 -0
- package/dist/node.js +4 -0
- package/dist/node.js.map +1 -0
- package/dist/rules/blocks.d.ts +34 -0
- package/dist/rules/blocks.js +550 -0
- package/dist/rules/blocks.js.map +1 -0
- package/dist/rules/content.d.ts +2 -0
- package/dist/rules/content.js +85 -0
- package/dist/rules/content.js.map +1 -0
- package/dist/rules/dialects.js +10 -6
- package/dist/rules/dialects.js.map +1 -1
- package/dist/rules/links.js +302 -6
- package/dist/rules/links.js.map +1 -1
- package/dist/rules/lists.d.ts +2 -0
- package/dist/rules/lists.js +924 -0
- package/dist/rules/lists.js.map +1 -0
- package/dist/rules/structure.js +127 -1
- package/dist/rules/structure.js.map +1 -1
- package/dist/rules/style.d.ts +8 -2
- package/dist/rules/style.js +1137 -165
- package/dist/rules/style.js.map +1 -1
- package/dist/syntax/block-identifier.d.ts +18 -0
- package/dist/syntax/block-identifier.js +24 -0
- package/dist/syntax/block-identifier.js.map +1 -0
- package/dist/syntax/obsidian.d.ts +29 -11
- package/dist/syntax/obsidian.js +9 -10
- package/dist/syntax/obsidian.js.map +1 -1
- package/dist/syntax/parse.d.ts +87 -3
- package/dist/syntax/parse.js +247 -19
- package/dist/syntax/parse.js.map +1 -1
- package/dist/workspace/access.d.ts +12 -3
- package/dist/workspace/access.js +33 -5
- package/dist/workspace/access.js.map +1 -1
- package/dist/workspace/discovery.d.ts +85 -0
- package/dist/workspace/discovery.js +155 -0
- package/dist/workspace/discovery.js.map +1 -0
- package/dist/workspace/encoding.d.ts +24 -0
- package/dist/workspace/encoding.js +113 -0
- package/dist/workspace/encoding.js.map +1 -0
- package/dist/workspace/files.d.ts +92 -6
- package/dist/workspace/files.js +715 -59
- package/dist/workspace/files.js.map +1 -1
- package/dist/workspace/git.d.ts +54 -0
- package/dist/workspace/git.js +198 -0
- package/dist/workspace/git.js.map +1 -0
- package/dist/workspace/index.d.ts +58 -3
- package/dist/workspace/index.js +136 -37
- package/dist/workspace/index.js.map +1 -1
- package/dist/workspace/selection.d.ts +2 -2
- package/dist/workspace/selection.js +3 -3
- package/dist/workspace/selection.js.map +1 -1
- package/dist/workspace/slug.js +8 -1
- package/dist/workspace/slug.js.map +1 -1
- package/dist/workspace/vault.js +3 -1
- package/dist/workspace/vault.js.map +1 -1
- package/docs/api.md +239 -0
- package/docs/benchmarks.md +140 -200
- package/docs/changelog.md +1 -0
- package/docs/cli.md +281 -89
- package/docs/configuration.md +193 -480
- package/docs/design.md +163 -284
- package/docs/dialects.md +169 -0
- package/docs/examples.md +151 -109
- package/docs/index.md +21 -37
- package/docs/integrations.md +144 -0
- package/docs/obsidian.md +91 -115
- package/docs/plugins.md +147 -212
- package/docs/quick-start.md +68 -74
- package/docs/rules.md +1237 -0
- package/docs/versions.md +56 -0
- package/examples/README.md +17 -14
- package/examples/automation/summarize-report.mjs +5 -2
- package/examples/library/lint-folder.mjs +5 -4
- package/examples/library/lint-workspace.mjs +32 -0
- package/examples/plugin/notes/release-checklist.md +2 -2
- package/examples/plugin/rules.mjs +17 -23
- package/examples/workspaces/obsidian-vault/after/mdtools.config.jsonc +2 -2
- package/examples/workspaces/obsidian-vault/before/mdtools.config.jsonc +2 -2
- package/examples/workspaces/repository-docs/after/README.md +2 -2
- package/examples/workspaces/repository-docs/after/mdtools.config.jsonc +0 -2
- package/examples/workspaces/repository-docs/before/README.md +2 -2
- package/examples/workspaces/repository-docs/before/docs/guide.md +7 -7
- package/examples/workspaces/repository-docs/before/docs/reference.md +3 -1
- package/examples/workspaces/repository-docs/before/mdtools.config.jsonc +0 -2
- package/package.json +38 -12
- package/docs/contributing.md +0 -218
- package/docs/releases/0.1.0-beta.1.md +0 -39
- package/docs/releases/0.1.0-beta.2.md +0 -83
- package/docs/releases/0.1.0-beta.3.md +0 -61
- package/docs/releases/0.1.0-beta.4.md +0 -50
- package/docs/releases/0.1.0-rc.1.md +0 -99
- package/docs/releases/0.2.0-rc.1.md +0 -141
- package/docs/releases/0.2.0-rc.2.md +0 -225
- package/docs/releases.md +0 -144
- package/docs/testing.md +0 -93
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { Config, Dialect, Plugin, ProcessOptions } from "../core/types.js";
|
|
2
|
+
import type { Skipped } from "./files.js";
|
|
3
|
+
import type { Vault } from "./vault.js";
|
|
4
|
+
/** The options of `discoverWorkspace`. */
|
|
5
|
+
export interface DiscoverOptions {
|
|
6
|
+
/** The workspace root: the folder whose documents are processed and whose files are link targets. */
|
|
7
|
+
root: string;
|
|
8
|
+
/**
|
|
9
|
+
* Files, folders, and patterns to select, absolute or relative to the root.
|
|
10
|
+
* A folder selects its Markdown files, those named `.md` or `.markdown`; a
|
|
11
|
+
* file is selected whatever its name. An input that names no existing path
|
|
12
|
+
* and holds pattern characters (`*`, `?`, `[`, `{`) is a pattern, with `/`
|
|
13
|
+
* between folders on every system: it selects the Markdown files that it
|
|
14
|
+
* matches, and matching none is an error. Ignored files are not selected,
|
|
15
|
+
* and an ignored file or folder named here is reported in `skipped`.
|
|
16
|
+
* Omitted, every document of the workspace is selected; an empty list
|
|
17
|
+
* selects none.
|
|
18
|
+
*/
|
|
19
|
+
inputs?: string[];
|
|
20
|
+
/**
|
|
21
|
+
* Files and folders to leave out of the selection, absolute or relative to
|
|
22
|
+
* the root. They are paths, not patterns, and stay link targets.
|
|
23
|
+
*/
|
|
24
|
+
exclude?: string[];
|
|
25
|
+
/** The configuration: its `ignore` and `resolve` settings, and the dialect of each document. */
|
|
26
|
+
config?: Config;
|
|
27
|
+
/** Plugin objects, whose rules and presets the configuration can name. */
|
|
28
|
+
plugins?: Plugin[];
|
|
29
|
+
/**
|
|
30
|
+
* Documents whose text the caller supplies, such as an editor's unsaved
|
|
31
|
+
* text, by path relative to the root. They are selected, and the workspace
|
|
32
|
+
* reads this text instead of a file at that path. One whose path ignore rules
|
|
33
|
+
* leave out is not selected and is reported in `skipped`, as a named file is.
|
|
34
|
+
*/
|
|
35
|
+
documents?: Record<string, string>;
|
|
36
|
+
}
|
|
37
|
+
/** The files of a workspace that `discoverWorkspace` found, and how to process them. */
|
|
38
|
+
export interface WorkspaceDiscovery {
|
|
39
|
+
/** The workspace root's real path. */
|
|
40
|
+
readonly root: string;
|
|
41
|
+
/** The documents to process, as forward-slash paths relative to the root, sorted. */
|
|
42
|
+
readonly selected: readonly string[];
|
|
43
|
+
/** What discovery could not read or use and left out, as in the CLI's JSON report. */
|
|
44
|
+
readonly skipped: readonly Skipped[];
|
|
45
|
+
/**
|
|
46
|
+
* The text of a selected document. A file is read when it is first needed
|
|
47
|
+
* and then kept; an error with the code `EACCES`, `EPERM`, or `EBUSY` means
|
|
48
|
+
* that the system did not let it be read, and one with the code
|
|
49
|
+
* `ERR_ENCODING_INVALID_ENCODED_DATA` that its bytes are not UTF-8 text.
|
|
50
|
+
*/
|
|
51
|
+
read(path: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* The options to lint or format a selected document with, as the CLI does:
|
|
54
|
+
* its path, the configuration and plugins, a workspace for its dialect with
|
|
55
|
+
* its vault's line-break setting, and the dialect to assume for a note.
|
|
56
|
+
*/
|
|
57
|
+
options(path: string): ProcessOptions;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The dialect to assume for a document when nothing names one, or undefined for
|
|
61
|
+
* the library's own default. An Obsidian vault shows a single line break as a
|
|
62
|
+
* line break and holds wikilinks, both of which any other dialect lets reflow
|
|
63
|
+
* change, so a document that a vault holds is read as a note.
|
|
64
|
+
*/
|
|
65
|
+
export declare function assumedDialect(vault: Vault | string | undefined): Dialect | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Stops when `given` cannot serve as a workspace root, with a message that
|
|
68
|
+
* names it. Without this check the first thing to fail could be a search
|
|
69
|
+
* inside it, with a message about a path the user never named.
|
|
70
|
+
*/
|
|
71
|
+
export declare function checkRoot(given: string): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Find the files of a workspace and select documents the way the CLI does:
|
|
74
|
+
* honoring Git's ignore rules and the configuration's `ignore` and `resolve`
|
|
75
|
+
* settings, skipping links, nested repositories, and what the system does not
|
|
76
|
+
* let the tool read, and recognizing each document's Obsidian vault. Nothing
|
|
77
|
+
* is written.
|
|
78
|
+
*/
|
|
79
|
+
export declare function discoverWorkspace(options: DiscoverOptions): Promise<WorkspaceDiscovery>;
|
|
80
|
+
/**
|
|
81
|
+
* The same, with relative inputs and exclusions resolved from `base`. Without
|
|
82
|
+
* `ignoreDocuments`, ignore rules do not leave out the supplied documents, as
|
|
83
|
+
* for text from stdin without a path of its own.
|
|
84
|
+
*/
|
|
85
|
+
export declare function discoverFrom(options: DiscoverOptions, base: string, ignoreDocuments?: boolean): Promise<WorkspaceDiscovery>;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { access, stat } from "node:fs/promises";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { resolveConfig } from "../config/resolve.js";
|
|
5
|
+
import { pathError } from "./access.js";
|
|
6
|
+
import { discover, isIgnoredInput, unlistedLookup, vaultFor } from "./files.js";
|
|
7
|
+
import { createWorkspace } from "./index.js";
|
|
8
|
+
import { excludeSelection } from "./selection.js";
|
|
9
|
+
import { vaultOf } from "./vault.js";
|
|
10
|
+
/**
|
|
11
|
+
* The dialect to assume for a document when nothing names one, or undefined for
|
|
12
|
+
* the library's own default. An Obsidian vault shows a single line break as a
|
|
13
|
+
* line break and holds wikilinks, both of which any other dialect lets reflow
|
|
14
|
+
* change, so a document that a vault holds is read as a note.
|
|
15
|
+
*/
|
|
16
|
+
export function assumedDialect(vault) {
|
|
17
|
+
return vault ? "obsidian" : undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Stops when `given` cannot serve as a workspace root, with a message that
|
|
21
|
+
* names it. Without this check the first thing to fail could be a search
|
|
22
|
+
* inside it, with a message about a path the user never named.
|
|
23
|
+
*/
|
|
24
|
+
export async function checkRoot(given) {
|
|
25
|
+
const root = path.resolve(given);
|
|
26
|
+
try {
|
|
27
|
+
if (!(await stat(root)).isDirectory())
|
|
28
|
+
throw new Error(`Workspace root is not a directory: ${root}`);
|
|
29
|
+
await access(root, constants.R_OK | constants.X_OK);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
throw pathError("Workspace root", root, error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Find the files of a workspace and select documents the way the CLI does:
|
|
37
|
+
* honoring Git's ignore rules and the configuration's `ignore` and `resolve`
|
|
38
|
+
* settings, skipping links, nested repositories, and what the system does not
|
|
39
|
+
* let the tool read, and recognizing each document's Obsidian vault. Nothing
|
|
40
|
+
* is written.
|
|
41
|
+
*/
|
|
42
|
+
export function discoverWorkspace(options) {
|
|
43
|
+
return discoverFrom(options, path.resolve(options.root));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The same, with relative inputs and exclusions resolved from `base`. Without
|
|
47
|
+
* `ignoreDocuments`, ignore rules do not leave out the supplied documents, as
|
|
48
|
+
* for text from stdin without a path of its own.
|
|
49
|
+
*/
|
|
50
|
+
export async function discoverFrom(options, base, ignoreDocuments = true) {
|
|
51
|
+
await checkRoot(options.root);
|
|
52
|
+
// Which files to leave out does not depend on a document's dialect.
|
|
53
|
+
const resolved = resolveConfig({ config: options.config, plugins: options.plugins });
|
|
54
|
+
const documents = new Map();
|
|
55
|
+
for (const [given, text] of Object.entries(options.documents ?? {})) {
|
|
56
|
+
const name = path.posix.normalize(given.split(path.sep).join("/"));
|
|
57
|
+
if (name === "." || name === ".." || name.startsWith("../") || path.isAbsolute(given))
|
|
58
|
+
throw new Error(`Document path is outside the workspace: ${given}`);
|
|
59
|
+
documents.set(name, text);
|
|
60
|
+
}
|
|
61
|
+
// Discovery always visits the whole workspace, whose files are link targets;
|
|
62
|
+
// the inputs only choose what is selected.
|
|
63
|
+
const none = options.inputs?.length === 0;
|
|
64
|
+
const set = await discover(options.root, none ? [] : (options.inputs ?? []), resolved.ignore, resolved.resolve, base, { documents: ignoreDocuments ? [...documents.keys()] : [], selectNone: none });
|
|
65
|
+
set.selected = none
|
|
66
|
+
? []
|
|
67
|
+
: await excludeSelection(set.root, set.selected, options.exclude ?? [], base);
|
|
68
|
+
// A supplied document that ignore rules leave out is reported as a named
|
|
69
|
+
// file is, and not selected.
|
|
70
|
+
const ignored = new Set(set.skipped.flatMap((item) => item.type === "file" && isIgnoredInput(item) ? [item.path] : []));
|
|
71
|
+
// A supplied document can lie in a folder that discovery did not visit, so
|
|
72
|
+
// its vault is found by a search of its own.
|
|
73
|
+
const supplied = new Map();
|
|
74
|
+
for (const [name, text] of documents) {
|
|
75
|
+
set.files[name] = text;
|
|
76
|
+
if (!ignored.has(name))
|
|
77
|
+
supplied.set(name, undefined);
|
|
78
|
+
}
|
|
79
|
+
for (const name of supplied.keys())
|
|
80
|
+
supplied.set(name, await vaultFor(set, name));
|
|
81
|
+
const selected = [...new Set([...set.selected, ...supplied.keys()])].sort();
|
|
82
|
+
const chosen = new Set(selected);
|
|
83
|
+
const known = (name) => {
|
|
84
|
+
if (!chosen.has(name))
|
|
85
|
+
throw new Error(`Not a selected document: ${name}`);
|
|
86
|
+
};
|
|
87
|
+
// One index per active dialect, built when a document of the dialect is first
|
|
88
|
+
// processed, and a view of it with each vault's line-break setting.
|
|
89
|
+
const indexes = new Map();
|
|
90
|
+
const views = new Map();
|
|
91
|
+
// Links into folders that discovery does not enter are checked on disk.
|
|
92
|
+
const lookup = unlistedLookup(set);
|
|
93
|
+
function index(dialect) {
|
|
94
|
+
let workspace = indexes.get(dialect);
|
|
95
|
+
if (workspace)
|
|
96
|
+
return workspace;
|
|
97
|
+
// The vault that the root is a folder of, if it is one: links may lead out of sight.
|
|
98
|
+
const enclosing = set.vaults.find((item) => item.rootInVault);
|
|
99
|
+
workspace = createWorkspace(set.files, {
|
|
100
|
+
dialect,
|
|
101
|
+
directories: set.directories,
|
|
102
|
+
// Links into a directory that could not be read are unchecked, not missing.
|
|
103
|
+
unreadable: set.skipped.flatMap((item) => item.type === "directory" && item.code !== "IGNORED" ? [item.path] : []),
|
|
104
|
+
// The vault at or above the root holds the whole workspace and needs no entry.
|
|
105
|
+
vaults: set.vaults.flatMap((item) => (item.path ? [item.path] : [])),
|
|
106
|
+
...(enclosing?.rootInVault ? { rootInVault: enclosing.rootInVault } : {}),
|
|
107
|
+
lookup,
|
|
108
|
+
});
|
|
109
|
+
indexes.set(dialect, workspace);
|
|
110
|
+
return workspace;
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
root: set.root,
|
|
114
|
+
selected,
|
|
115
|
+
skipped: set.skipped,
|
|
116
|
+
read(name) {
|
|
117
|
+
known(name);
|
|
118
|
+
const value = set.files[name];
|
|
119
|
+
if (typeof value === "string")
|
|
120
|
+
return value;
|
|
121
|
+
if (typeof value === "function")
|
|
122
|
+
return value();
|
|
123
|
+
throw new Error(`Not a Markdown document: ${name}`);
|
|
124
|
+
},
|
|
125
|
+
options(name) {
|
|
126
|
+
known(name);
|
|
127
|
+
const vault = supplied.has(name) ? supplied.get(name) : vaultOf(set.vaults, name);
|
|
128
|
+
const assumed = assumedDialect(vault);
|
|
129
|
+
const { dialect } = resolveConfig({
|
|
130
|
+
config: options.config,
|
|
131
|
+
path: name,
|
|
132
|
+
plugins: options.plugins,
|
|
133
|
+
defaultDialect: assumed,
|
|
134
|
+
});
|
|
135
|
+
let workspace = index(dialect);
|
|
136
|
+
// Whether a note may be reflowed is a setting of its own vault.
|
|
137
|
+
if (vault?.strictLineBreaks !== undefined) {
|
|
138
|
+
let ofDialect = views.get(dialect);
|
|
139
|
+
if (!ofDialect)
|
|
140
|
+
views.set(dialect, (ofDialect = new Map()));
|
|
141
|
+
const shared = workspace;
|
|
142
|
+
workspace = ofDialect.get(vault) ?? { ...shared, strictLineBreaks: vault.strictLineBreaks };
|
|
143
|
+
ofDialect.set(vault, workspace);
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
path: name,
|
|
147
|
+
...(options.config ? { config: options.config } : {}),
|
|
148
|
+
...(options.plugins ? { plugins: options.plugins } : {}),
|
|
149
|
+
workspace,
|
|
150
|
+
...(assumed ? { defaultDialect: assumed } : {}),
|
|
151
|
+
};
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/workspace/discovery.ts"],"names":[],"mappings":"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","sourcesContent":["import { access, stat } from \"node:fs/promises\";\nimport { constants } from \"node:fs\";\nimport path from \"node:path\";\nimport { resolveConfig } from \"../config/resolve.js\";\nimport type { Config, Dialect, Plugin, ProcessOptions, Workspace } from \"../core/types.js\";\nimport { pathError } from \"./access.js\";\nimport { discover, isIgnoredInput, unlistedLookup, vaultFor } from \"./files.js\";\nimport type { Skipped } from \"./files.js\";\nimport { createWorkspace } from \"./index.js\";\nimport { excludeSelection } from \"./selection.js\";\nimport { vaultOf } from \"./vault.js\";\nimport type { Vault } from \"./vault.js\";\n\n/** The options of `discoverWorkspace`. */\nexport interface DiscoverOptions {\n /** The workspace root: the folder whose documents are processed and whose files are link targets. */\n root: string;\n /**\n * Files, folders, and patterns to select, absolute or relative to the root.\n * A folder selects its Markdown files, those named `.md` or `.markdown`; a\n * file is selected whatever its name. An input that names no existing path\n * and holds pattern characters (`*`, `?`, `[`, `{`) is a pattern, with `/`\n * between folders on every system: it selects the Markdown files that it\n * matches, and matching none is an error. Ignored files are not selected,\n * and an ignored file or folder named here is reported in `skipped`.\n * Omitted, every document of the workspace is selected; an empty list\n * selects none.\n */\n inputs?: string[];\n /**\n * Files and folders to leave out of the selection, absolute or relative to\n * the root. They are paths, not patterns, and stay link targets.\n */\n exclude?: string[];\n /** The configuration: its `ignore` and `resolve` settings, and the dialect of each document. */\n config?: Config;\n /** Plugin objects, whose rules and presets the configuration can name. */\n plugins?: Plugin[];\n /**\n * Documents whose text the caller supplies, such as an editor's unsaved\n * text, by path relative to the root. They are selected, and the workspace\n * reads this text instead of a file at that path. One whose path ignore rules\n * leave out is not selected and is reported in `skipped`, as a named file is.\n */\n documents?: Record<string, string>;\n}\n/** The files of a workspace that `discoverWorkspace` found, and how to process them. */\nexport interface WorkspaceDiscovery {\n /** The workspace root's real path. */\n readonly root: string;\n /** The documents to process, as forward-slash paths relative to the root, sorted. */\n readonly selected: readonly string[];\n /** What discovery could not read or use and left out, as in the CLI's JSON report. */\n readonly skipped: readonly Skipped[];\n /**\n * The text of a selected document. A file is read when it is first needed\n * and then kept; an error with the code `EACCES`, `EPERM`, or `EBUSY` means\n * that the system did not let it be read, and one with the code\n * `ERR_ENCODING_INVALID_ENCODED_DATA` that its bytes are not UTF-8 text.\n */\n read(path: string): string;\n /**\n * The options to lint or format a selected document with, as the CLI does:\n * its path, the configuration and plugins, a workspace for its dialect with\n * its vault's line-break setting, and the dialect to assume for a note.\n */\n options(path: string): ProcessOptions;\n}\n\n/**\n * The dialect to assume for a document when nothing names one, or undefined for\n * the library's own default. An Obsidian vault shows a single line break as a\n * line break and holds wikilinks, both of which any other dialect lets reflow\n * change, so a document that a vault holds is read as a note.\n */\nexport function assumedDialect(vault: Vault | string | undefined): Dialect | undefined {\n return vault ? \"obsidian\" : undefined;\n}\n/**\n * Stops when `given` cannot serve as a workspace root, with a message that\n * names it. Without this check the first thing to fail could be a search\n * inside it, with a message about a path the user never named.\n */\nexport async function checkRoot(given: string): Promise<void> {\n const root = path.resolve(given);\n try {\n if (!(await stat(root)).isDirectory())\n throw new Error(`Workspace root is not a directory: ${root}`);\n await access(root, constants.R_OK | constants.X_OK);\n } catch (error) {\n throw pathError(\"Workspace root\", root, error);\n }\n}\n/**\n * Find the files of a workspace and select documents the way the CLI does:\n * honoring Git's ignore rules and the configuration's `ignore` and `resolve`\n * settings, skipping links, nested repositories, and what the system does not\n * let the tool read, and recognizing each document's Obsidian vault. Nothing\n * is written.\n */\nexport function discoverWorkspace(options: DiscoverOptions): Promise<WorkspaceDiscovery> {\n return discoverFrom(options, path.resolve(options.root));\n}\n/**\n * The same, with relative inputs and exclusions resolved from `base`. Without\n * `ignoreDocuments`, ignore rules do not leave out the supplied documents, as\n * for text from stdin without a path of its own.\n */\nexport async function discoverFrom(\n options: DiscoverOptions,\n base: string,\n ignoreDocuments = true,\n): Promise<WorkspaceDiscovery> {\n await checkRoot(options.root);\n // Which files to leave out does not depend on a document's dialect.\n const resolved = resolveConfig({ config: options.config, plugins: options.plugins });\n const documents = new Map<string, string>();\n for (const [given, text] of Object.entries(options.documents ?? {})) {\n const name = path.posix.normalize(given.split(path.sep).join(\"/\"));\n if (name === \".\" || name === \"..\" || name.startsWith(\"../\") || path.isAbsolute(given))\n throw new Error(`Document path is outside the workspace: ${given}`);\n documents.set(name, text);\n }\n // Discovery always visits the whole workspace, whose files are link targets;\n // the inputs only choose what is selected.\n const none = options.inputs?.length === 0;\n const set = await discover(\n options.root,\n none ? [] : (options.inputs ?? []),\n resolved.ignore,\n resolved.resolve,\n base,\n { documents: ignoreDocuments ? [...documents.keys()] : [], selectNone: none },\n );\n set.selected = none\n ? []\n : await excludeSelection(set.root, set.selected, options.exclude ?? [], base);\n // A supplied document that ignore rules leave out is reported as a named\n // file is, and not selected.\n const ignored = new Set(\n set.skipped.flatMap((item) =>\n item.type === \"file\" && isIgnoredInput(item) ? [item.path] : [],\n ),\n );\n // A supplied document can lie in a folder that discovery did not visit, so\n // its vault is found by a search of its own.\n const supplied = new Map<string, Vault | undefined>();\n for (const [name, text] of documents) {\n set.files[name] = text;\n if (!ignored.has(name)) supplied.set(name, undefined);\n }\n for (const name of supplied.keys()) supplied.set(name, await vaultFor(set, name));\n const selected = [...new Set([...set.selected, ...supplied.keys()])].sort();\n const chosen = new Set(selected);\n const known = (name: string) => {\n if (!chosen.has(name)) throw new Error(`Not a selected document: ${name}`);\n };\n // One index per active dialect, built when a document of the dialect is first\n // processed, and a view of it with each vault's line-break setting.\n const indexes = new Map<Dialect, Workspace>();\n const views = new Map<Dialect, Map<Vault, Workspace>>();\n // Links into folders that discovery does not enter are checked on disk.\n const lookup = unlistedLookup(set);\n function index(dialect: Dialect): Workspace {\n let workspace = indexes.get(dialect);\n if (workspace) return workspace;\n // The vault that the root is a folder of, if it is one: links may lead out of sight.\n const enclosing = set.vaults.find((item) => item.rootInVault);\n workspace = createWorkspace(set.files, {\n dialect,\n directories: set.directories,\n // Links into a directory that could not be read are unchecked, not missing.\n unreadable: set.skipped.flatMap((item) =>\n item.type === \"directory\" && item.code !== \"IGNORED\" ? [item.path] : [],\n ),\n // The vault at or above the root holds the whole workspace and needs no entry.\n vaults: set.vaults.flatMap((item) => (item.path ? [item.path] : [])),\n ...(enclosing?.rootInVault ? { rootInVault: enclosing.rootInVault } : {}),\n lookup,\n });\n indexes.set(dialect, workspace);\n return workspace;\n }\n return {\n root: set.root,\n selected,\n skipped: set.skipped,\n read(name) {\n known(name);\n const value = set.files[name];\n if (typeof value === \"string\") return value;\n if (typeof value === \"function\") return value();\n throw new Error(`Not a Markdown document: ${name}`);\n },\n options(name) {\n known(name);\n const vault = supplied.has(name) ? supplied.get(name) : vaultOf(set.vaults, name);\n const assumed = assumedDialect(vault);\n const { dialect } = resolveConfig({\n config: options.config,\n path: name,\n plugins: options.plugins,\n defaultDialect: assumed,\n });\n let workspace = index(dialect);\n // Whether a note may be reflowed is a setting of its own vault.\n if (vault?.strictLineBreaks !== undefined) {\n let ofDialect = views.get(dialect);\n if (!ofDialect) views.set(dialect, (ofDialect = new Map()));\n const shared = workspace;\n workspace = ofDialect.get(vault) ?? { ...shared, strictLineBreaks: vault.strictLineBreaks };\n ofDialect.set(vault, workspace);\n }\n return {\n path: name,\n ...(options.config ? { config: options.config } : {}),\n ...(options.plugins ? { plugins: options.plugins } : {}),\n workspace,\n ...(assumed ? { defaultDialect: assumed } : {}),\n };\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Bytes that are not UTF-8 text, with what is wrong in words that do not name their source. */
|
|
2
|
+
export declare class NotUtf8Error extends Error {
|
|
3
|
+
readonly reason: string;
|
|
4
|
+
readonly code = "ERR_ENCODING_INVALID_ENCODED_DATA";
|
|
5
|
+
constructor(reason: string, message: string);
|
|
6
|
+
}
|
|
7
|
+
/** Whether an error says that bytes are not UTF-8 text, as `decodeText` and a fatal `TextDecoder` do. */
|
|
8
|
+
export declare function isNotUtf8(error: unknown): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* The offset of the first byte that does not start a valid UTF-8 sequence, or
|
|
11
|
+
* -1 when every one does. A sequence is valid as Unicode's table of
|
|
12
|
+
* well-formed byte sequences defines it: no overlong forms, no surrogates, and
|
|
13
|
+
* nothing above U+10FFFF.
|
|
14
|
+
*/
|
|
15
|
+
export declare function invalidUtf8At(bytes: Uint8Array): number;
|
|
16
|
+
/**
|
|
17
|
+
* The text of `bytes`, which must be UTF-8, with or without a byte order mark.
|
|
18
|
+
* A mark stays at the start of the text, where formatting keeps it. Text with a
|
|
19
|
+
* NUL byte is refused too: Markdown has no use for one, and UTF-16 text without
|
|
20
|
+
* a byte order mark, whose ASCII characters each have one, could otherwise pass
|
|
21
|
+
* for UTF-8 and be damaged by an edit. Bytes that are not such text throw a
|
|
22
|
+
* `NotUtf8Error` whose message names `subject`; nothing is ever replaced.
|
|
23
|
+
*/
|
|
24
|
+
export declare function decodeText(bytes: Buffer, subject: string): string;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { isUtf8 } from "node:buffer";
|
|
2
|
+
/**
|
|
3
|
+
* The code of the error for bytes that are not UTF-8 text. Node.js's
|
|
4
|
+
* `TextDecoder`, created with `fatal: true`, throws an error with the same code
|
|
5
|
+
* for bytes that are not valid in its encoding.
|
|
6
|
+
*/
|
|
7
|
+
const notUtf8Code = "ERR_ENCODING_INVALID_ENCODED_DATA";
|
|
8
|
+
/** Bytes that are not UTF-8 text, with what is wrong in words that do not name their source. */
|
|
9
|
+
export class NotUtf8Error extends Error {
|
|
10
|
+
reason;
|
|
11
|
+
code = notUtf8Code;
|
|
12
|
+
constructor(reason, message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.reason = reason;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Whether an error says that bytes are not UTF-8 text, as `decodeText` and a fatal `TextDecoder` do. */
|
|
18
|
+
export function isNotUtf8(error) {
|
|
19
|
+
return (typeof error === "object" && error !== null && "code" in error && error.code === notUtf8Code);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The byte order marks of other encodings. UTF-32LE's starts with UTF-16LE's,
|
|
23
|
+
* so it is looked for first.
|
|
24
|
+
*/
|
|
25
|
+
const marks = [
|
|
26
|
+
["UTF-32LE", [0xff, 0xfe, 0x00, 0x00]],
|
|
27
|
+
["UTF-32BE", [0x00, 0x00, 0xfe, 0xff]],
|
|
28
|
+
["UTF-16LE", [0xff, 0xfe]],
|
|
29
|
+
["UTF-16BE", [0xfe, 0xff]],
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* The offset of the first byte that does not start a valid UTF-8 sequence, or
|
|
33
|
+
* -1 when every one does. A sequence is valid as Unicode's table of
|
|
34
|
+
* well-formed byte sequences defines it: no overlong forms, no surrogates, and
|
|
35
|
+
* nothing above U+10FFFF.
|
|
36
|
+
*/
|
|
37
|
+
export function invalidUtf8At(bytes) {
|
|
38
|
+
for (let at = 0; at < bytes.length;) {
|
|
39
|
+
const lead = bytes[at];
|
|
40
|
+
if (lead < 0x80) {
|
|
41
|
+
at++;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
// The length of the sequence, and the range of its second byte.
|
|
45
|
+
let size;
|
|
46
|
+
let low = 0x80;
|
|
47
|
+
let high = 0xbf;
|
|
48
|
+
if (lead >= 0xc2 && lead <= 0xdf)
|
|
49
|
+
size = 2;
|
|
50
|
+
else if (lead >= 0xe0 && lead <= 0xef) {
|
|
51
|
+
size = 3;
|
|
52
|
+
if (lead === 0xe0)
|
|
53
|
+
low = 0xa0;
|
|
54
|
+
if (lead === 0xed)
|
|
55
|
+
high = 0x9f;
|
|
56
|
+
}
|
|
57
|
+
else if (lead >= 0xf0 && lead <= 0xf4) {
|
|
58
|
+
size = 4;
|
|
59
|
+
if (lead === 0xf0)
|
|
60
|
+
low = 0x90;
|
|
61
|
+
if (lead === 0xf4)
|
|
62
|
+
high = 0x8f;
|
|
63
|
+
}
|
|
64
|
+
else
|
|
65
|
+
return at;
|
|
66
|
+
for (let next = 1; next < size; next++) {
|
|
67
|
+
const byte = bytes[at + next];
|
|
68
|
+
if (byte === undefined ||
|
|
69
|
+
byte < (next === 1 ? low : 0x80) ||
|
|
70
|
+
byte > (next === 1 ? high : 0xbf))
|
|
71
|
+
return at;
|
|
72
|
+
}
|
|
73
|
+
at += size;
|
|
74
|
+
}
|
|
75
|
+
return -1;
|
|
76
|
+
}
|
|
77
|
+
/** The line that a byte offset lies on, counting LF, CR, and CRLF as line ends. */
|
|
78
|
+
function lineAt(bytes, offset) {
|
|
79
|
+
let line = 1;
|
|
80
|
+
for (let at = 0; at < offset; at++)
|
|
81
|
+
if (bytes[at] === 0x0a || (bytes[at] === 0x0d && bytes[at + 1] !== 0x0a))
|
|
82
|
+
line++;
|
|
83
|
+
return line;
|
|
84
|
+
}
|
|
85
|
+
/** What keeps `bytes` from being read as UTF-8 text, or undefined when nothing does. */
|
|
86
|
+
function problem(bytes) {
|
|
87
|
+
for (const [name, mark] of marks)
|
|
88
|
+
if (mark.every((byte, index) => bytes[index] === byte))
|
|
89
|
+
return `it starts with a ${name} byte order mark`;
|
|
90
|
+
const invalid = isUtf8(bytes) ? -1 : invalidUtf8At(bytes);
|
|
91
|
+
const nul = bytes.indexOf(0);
|
|
92
|
+
if (nul >= 0 && (invalid < 0 || nul < invalid))
|
|
93
|
+
return `line ${lineAt(bytes, nul)} contains a NUL byte, as UTF-16 text and binary files do`;
|
|
94
|
+
if (invalid < 0)
|
|
95
|
+
return undefined;
|
|
96
|
+
const byte = bytes[invalid].toString(16).toUpperCase().padStart(2, "0");
|
|
97
|
+
return `byte 0x${byte} on line ${lineAt(bytes, invalid)} is not valid UTF-8`;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The text of `bytes`, which must be UTF-8, with or without a byte order mark.
|
|
101
|
+
* A mark stays at the start of the text, where formatting keeps it. Text with a
|
|
102
|
+
* NUL byte is refused too: Markdown has no use for one, and UTF-16 text without
|
|
103
|
+
* a byte order mark, whose ASCII characters each have one, could otherwise pass
|
|
104
|
+
* for UTF-8 and be damaged by an edit. Bytes that are not such text throw a
|
|
105
|
+
* `NotUtf8Error` whose message names `subject`; nothing is ever replaced.
|
|
106
|
+
*/
|
|
107
|
+
export function decodeText(bytes, subject) {
|
|
108
|
+
const reason = problem(bytes);
|
|
109
|
+
if (reason)
|
|
110
|
+
throw new NotUtf8Error(reason, `Not UTF-8 text: ${subject} (${reason})`);
|
|
111
|
+
return bytes.toString("utf8");
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=encoding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encoding.js","sourceRoot":"","sources":["../../src/workspace/encoding.ts"],"names":[],"mappings":"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","sourcesContent":["import { isUtf8 } from \"node:buffer\";\n\n/**\n * The code of the error for bytes that are not UTF-8 text. Node.js's\n * `TextDecoder`, created with `fatal: true`, throws an error with the same code\n * for bytes that are not valid in its encoding.\n */\nconst notUtf8Code = \"ERR_ENCODING_INVALID_ENCODED_DATA\";\n\n/** Bytes that are not UTF-8 text, with what is wrong in words that do not name their source. */\nexport class NotUtf8Error extends Error {\n readonly code = notUtf8Code;\n constructor(\n readonly reason: string,\n message: string,\n ) {\n super(message);\n }\n}\n/** Whether an error says that bytes are not UTF-8 text, as `decodeText` and a fatal `TextDecoder` do. */\nexport function isNotUtf8(error: unknown): boolean {\n return (\n typeof error === \"object\" && error !== null && \"code\" in error && error.code === notUtf8Code\n );\n}\n/**\n * The byte order marks of other encodings. UTF-32LE's starts with UTF-16LE's,\n * so it is looked for first.\n */\nconst marks = [\n [\"UTF-32LE\", [0xff, 0xfe, 0x00, 0x00]],\n [\"UTF-32BE\", [0x00, 0x00, 0xfe, 0xff]],\n [\"UTF-16LE\", [0xff, 0xfe]],\n [\"UTF-16BE\", [0xfe, 0xff]],\n] as const;\n/**\n * The offset of the first byte that does not start a valid UTF-8 sequence, or\n * -1 when every one does. A sequence is valid as Unicode's table of\n * well-formed byte sequences defines it: no overlong forms, no surrogates, and\n * nothing above U+10FFFF.\n */\nexport function invalidUtf8At(bytes: Uint8Array): number {\n for (let at = 0; at < bytes.length;) {\n const lead = bytes[at]!;\n if (lead < 0x80) {\n at++;\n continue;\n }\n // The length of the sequence, and the range of its second byte.\n let size: number;\n let low = 0x80;\n let high = 0xbf;\n if (lead >= 0xc2 && lead <= 0xdf) size = 2;\n else if (lead >= 0xe0 && lead <= 0xef) {\n size = 3;\n if (lead === 0xe0) low = 0xa0;\n if (lead === 0xed) high = 0x9f;\n } else if (lead >= 0xf0 && lead <= 0xf4) {\n size = 4;\n if (lead === 0xf0) low = 0x90;\n if (lead === 0xf4) high = 0x8f;\n } else return at;\n for (let next = 1; next < size; next++) {\n const byte = bytes[at + next];\n if (\n byte === undefined ||\n byte < (next === 1 ? low : 0x80) ||\n byte > (next === 1 ? high : 0xbf)\n )\n return at;\n }\n at += size;\n }\n return -1;\n}\n/** The line that a byte offset lies on, counting LF, CR, and CRLF as line ends. */\nfunction lineAt(bytes: Uint8Array, offset: number): number {\n let line = 1;\n for (let at = 0; at < offset; at++)\n if (bytes[at] === 0x0a || (bytes[at] === 0x0d && bytes[at + 1] !== 0x0a)) line++;\n return line;\n}\n/** What keeps `bytes` from being read as UTF-8 text, or undefined when nothing does. */\nfunction problem(bytes: Buffer): string | undefined {\n for (const [name, mark] of marks)\n if (mark.every((byte, index) => bytes[index] === byte))\n return `it starts with a ${name} byte order mark`;\n const invalid = isUtf8(bytes) ? -1 : invalidUtf8At(bytes);\n const nul = bytes.indexOf(0);\n if (nul >= 0 && (invalid < 0 || nul < invalid))\n return `line ${lineAt(bytes, nul)} contains a NUL byte, as UTF-16 text and binary files do`;\n if (invalid < 0) return undefined;\n const byte = bytes[invalid]!.toString(16).toUpperCase().padStart(2, \"0\");\n return `byte 0x${byte} on line ${lineAt(bytes, invalid)} is not valid UTF-8`;\n}\n/**\n * The text of `bytes`, which must be UTF-8, with or without a byte order mark.\n * A mark stays at the start of the text, where formatting keeps it. Text with a\n * NUL byte is refused too: Markdown has no use for one, and UTF-16 text without\n * a byte order mark, whose ASCII characters each have one, could otherwise pass\n * for UTF-8 and be damaged by an edit. Bytes that are not such text throw a\n * `NotUtf8Error` whose message names `subject`; nothing is ever replaced.\n */\nexport function decodeText(bytes: Buffer, subject: string): string {\n const reason = problem(bytes);\n if (reason) throw new NotUtf8Error(reason, `Not UTF-8 text: ${subject} (${reason})`);\n return bytes.toString(\"utf8\");\n}\n"]}
|
|
@@ -1,21 +1,47 @@
|
|
|
1
1
|
import type { ResolveOptions } from "../core/types.js";
|
|
2
|
-
import type { WorkspaceSource } from "./index.js";
|
|
2
|
+
import type { LookupResult, WorkspaceSource } from "./index.js";
|
|
3
3
|
import type { RefusalCode } from "./access.js";
|
|
4
4
|
import type { Vault } from "./vault.js";
|
|
5
5
|
/** A path that discovery could not use and left out of the run. */
|
|
6
6
|
export interface Skipped {
|
|
7
|
-
/** The directory or
|
|
7
|
+
/** The directory or file, relative to the workspace root. */
|
|
8
8
|
path: string;
|
|
9
|
+
/** Whether a directory or a file was left out. */
|
|
9
10
|
type: "directory" | "file";
|
|
10
|
-
/**
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The system's error code, `INVALID` for a settings file with unusable
|
|
13
|
+
* content, or `IGNORED` for an input that was named but is ignored.
|
|
14
|
+
*/
|
|
15
|
+
code: RefusalCode | "INVALID" | "IGNORED";
|
|
12
16
|
/** What was skipped, why, and what follows from it. */
|
|
13
17
|
message: string;
|
|
14
18
|
}
|
|
19
|
+
/** Names a path of the workspace, given relative to the root, as a message shows it. */
|
|
20
|
+
type PathText = (file: string) => string;
|
|
21
|
+
/**
|
|
22
|
+
* The message of a skipped entry with each path of the workspace that it names
|
|
23
|
+
* given by `show`, which receives the path relative to the root. The CLI's text
|
|
24
|
+
* names them from the current directory.
|
|
25
|
+
*/
|
|
26
|
+
export declare function skippedMessage(item: Skipped, show: PathText): string;
|
|
27
|
+
/**
|
|
28
|
+
* Whether a skipped entry reports a named file or folder that discovery leaves
|
|
29
|
+
* out as ignored, a warning that the CLI's `--no-warn-ignored` leaves out. An
|
|
30
|
+
* ignored workspace root is not one: no path named it, and none of its files is
|
|
31
|
+
* processed.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isIgnoredInput(item: Skipped): boolean;
|
|
15
34
|
export interface FileSet {
|
|
16
35
|
root: string;
|
|
17
36
|
files: Record<string, WorkspaceSource>;
|
|
18
37
|
directories: string[];
|
|
38
|
+
/**
|
|
39
|
+
* The entries below the root that discovery did not enter and that Git does
|
|
40
|
+
* not ignore: folders and files of the names that it always leaves out, and
|
|
41
|
+
* symbolic links and junctions. Their files are not in `files`, but a link
|
|
42
|
+
* can name one.
|
|
43
|
+
*/
|
|
44
|
+
unlisted: string[];
|
|
19
45
|
/** What discovery could not read or use, sorted by path. */
|
|
20
46
|
skipped: Skipped[];
|
|
21
47
|
selected: string[];
|
|
@@ -26,6 +52,14 @@ export interface FileSet {
|
|
|
26
52
|
*/
|
|
27
53
|
vaults: Vault[];
|
|
28
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* The workspace's lookup of a path at or below one of the set's `unlisted`
|
|
57
|
+
* entries, on disk: a link that names such a path is checked there instead of
|
|
58
|
+
* being called missing. The workspace root still bounds what is read, so a
|
|
59
|
+
* path whose real location lies outside it, through a symbolic link or a
|
|
60
|
+
* junction, is reported as such and not read.
|
|
61
|
+
*/
|
|
62
|
+
export declare function unlistedLookup(set: FileSet): (name: string, folded: boolean) => LookupResult | undefined;
|
|
29
63
|
/**
|
|
30
64
|
* The vault that holds the document `name` of the workspace, which need not
|
|
31
65
|
* exist on disk: text read from stdin can be named into a folder that discovery
|
|
@@ -33,5 +67,57 @@ export interface FileSet {
|
|
|
33
67
|
* folder and is added to the set when it is new.
|
|
34
68
|
*/
|
|
35
69
|
export declare function vaultFor(set: FileSet, name: string): Promise<Vault | undefined>;
|
|
36
|
-
|
|
37
|
-
|
|
70
|
+
/**
|
|
71
|
+
* The files of the workspace at `rootPath`, and the documents that `supplied`
|
|
72
|
+
* files, folders, and patterns select, or all of them when `supplied` is
|
|
73
|
+
* empty. Relative inputs are resolved from `base`.
|
|
74
|
+
*
|
|
75
|
+
* A folder or pattern selects the Markdown files in it or matching it, by
|
|
76
|
+
* name (`isMarkdownName`), that nothing ignores. A file named on its own is
|
|
77
|
+
* selected whatever its name: naming it is the request. When ignore rules
|
|
78
|
+
* leave out a named file or folder, `skipped` says so and why.
|
|
79
|
+
*
|
|
80
|
+
* `documents` are paths from the root of documents whose text a caller
|
|
81
|
+
* supplies, which need not exist. They are not selected here, but ignore rules
|
|
82
|
+
* leave them out as named files, as far as their folders exist. With
|
|
83
|
+
* `selectNone`, the caller selects nothing from disk, and an ignored root is
|
|
84
|
+
* not reported.
|
|
85
|
+
*/
|
|
86
|
+
export declare function discover(rootPath: string, supplied: string[], excluded: string[], resolve?: ResolveOptions, base?: string, { documents, selectNone }?: {
|
|
87
|
+
documents?: string[];
|
|
88
|
+
selectNone?: boolean;
|
|
89
|
+
}): Promise<FileSet>;
|
|
90
|
+
/**
|
|
91
|
+
* A file that `writeFormatted` would not write, or did not write completely,
|
|
92
|
+
* with the reason in words that do not name it.
|
|
93
|
+
*/
|
|
94
|
+
export declare class ReplacementRefused extends Error {
|
|
95
|
+
readonly reason: string;
|
|
96
|
+
constructor(reason: string, message: string, options?: ErrorOptions);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Why `file` cannot be written, or undefined when nothing stands in the way
|
|
100
|
+
* that can be seen before writing: the file may not be written, or its folder
|
|
101
|
+
* does not let the trial file be created beside it. A read-only file is refused
|
|
102
|
+
* on every platform: the flag marks a file that is not to be edited.
|
|
103
|
+
*/
|
|
104
|
+
export declare function replacementProblem(file: string): Promise<string | undefined>;
|
|
105
|
+
/**
|
|
106
|
+
* What went wrong when `writeFormatted` failed for a file, in words that do
|
|
107
|
+
* not name it, or undefined for an error that is a fault of the tool.
|
|
108
|
+
*/
|
|
109
|
+
export declare function replacementFailure(error: unknown): string | undefined;
|
|
110
|
+
/**
|
|
111
|
+
* Write the formatted text `after` into `file`, whose text was read as
|
|
112
|
+
* `before`, if the file still holds exactly the bytes that were read. The file
|
|
113
|
+
* itself is written, not replaced by a new one, so that it keeps its
|
|
114
|
+
* permissions, attributes, owner, creation time, and hard links, which Node.js
|
|
115
|
+
* cannot all carry over to a new file. A trial write beside the file first shows that
|
|
116
|
+
* the new content fits on the disk. The file is then checked through the open
|
|
117
|
+
* file, just before writing. If writing fails part way, the bytes that were read
|
|
118
|
+
* are written back, and the reason says whether that worked. A file that already
|
|
119
|
+
* holds `after`, as the second name of a hard link written earlier in the batch
|
|
120
|
+
* does, is left as it is.
|
|
121
|
+
*/
|
|
122
|
+
export declare function writeFormatted(file: string, before: string, after: string): Promise<void>;
|
|
123
|
+
export {};
|