dsh-ops 0.0.0-stage → 0.2.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/CHANGELOG.md +202 -0
- package/LICENSE +30 -0
- package/NOTICE +106 -0
- package/PROVENANCE.md +435 -0
- package/README.en.md +126 -0
- package/README.md +115 -2
- package/README.zh.md +116 -0
- package/bin/dsh-ops.mjs +1216 -0
- package/cordis.patch.yml +160 -0
- package/docs/manual-validation.md +53 -0
- package/docs/release-0.2.1.md +72 -0
- package/docs/schema-baseline.json +64 -0
- package/docs/schema-current.json +84 -0
- package/docs/schema-measurement.md +17 -0
- package/dsh-plugin.json +88 -0
- package/icon.svg +12 -0
- package/lib/binary.js +409 -0
- package/lib/config.js +198 -0
- package/lib/handshake.js +252 -0
- package/lib/index.js +108 -0
- package/lib/jobs.js +42 -0
- package/lib/policy.js +64 -0
- package/lib/presentation.js +63 -0
- package/lib/profile-install.js +61 -0
- package/lib/rust.js +194 -0
- package/lib/session-shells.js +78 -0
- package/lib/shells.js +998 -0
- package/lib/tools.js +657 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +114 -4
- package/vendor/fastctx/Cargo.lock +3210 -0
- package/vendor/fastctx/Cargo.toml +94 -0
- package/vendor/fastctx/FORK.md +119 -0
- package/vendor/fastctx/LICENSE-APACHE +201 -0
- package/vendor/fastctx/NOTICE +40 -0
- package/vendor/fastctx/README.md +439 -0
- package/vendor/fastctx/THIRD_PARTY_LICENSES.md +17 -0
- package/vendor/fastctx/THIRD_PARTY_LICENSES_RUST.md +7914 -0
- package/vendor/fastctx/UPSTREAM.md +49 -0
- package/vendor/fastctx/build.rs +413 -0
- package/vendor/fastctx/src/background_status.rs +403 -0
- package/vendor/fastctx/src/binary.rs +75 -0
- package/vendor/fastctx/src/bounded_sort.rs +500 -0
- package/vendor/fastctx/src/budget.rs +781 -0
- package/vendor/fastctx/src/cli/mod.rs +110 -0
- package/vendor/fastctx/src/context_guard.rs +289 -0
- package/vendor/fastctx/src/control/mod.rs +6 -0
- package/vendor/fastctx/src/control/paths.rs +49 -0
- package/vendor/fastctx/src/control/settings.rs +753 -0
- package/vendor/fastctx/src/control/transaction.rs +531 -0
- package/vendor/fastctx/src/edit/document.rs +535 -0
- package/vendor/fastctx/src/edit/locks.rs +371 -0
- package/vendor/fastctx/src/edit/mod.rs +213 -0
- package/vendor/fastctx/src/edit/private_storage/unix.rs +315 -0
- package/vendor/fastctx/src/edit/private_storage/windows.rs +793 -0
- package/vendor/fastctx/src/edit/private_storage.rs +234 -0
- package/vendor/fastctx/src/edit/replace.rs +1030 -0
- package/vendor/fastctx/src/edit_server.rs +53 -0
- package/vendor/fastctx/src/encoding/reference_v011.rs +587 -0
- package/vendor/fastctx/src/encoding/snapshot_pipeline.rs +1678 -0
- package/vendor/fastctx/src/encoding.rs +1118 -0
- package/vendor/fastctx/src/file_executor.rs +1151 -0
- package/vendor/fastctx/src/file_snapshot.rs +1491 -0
- package/vendor/fastctx/src/glob_filter.rs +98 -0
- package/vendor/fastctx/src/glob_tool.rs +653 -0
- package/vendor/fastctx/src/grep_sink.rs +1162 -0
- package/vendor/fastctx/src/grep_tool.rs +2449 -0
- package/vendor/fastctx/src/lib.rs +45 -0
- package/vendor/fastctx/src/main.rs +15 -0
- package/vendor/fastctx/src/model.rs +51 -0
- package/vendor/fastctx/src/model_guidance.rs +62 -0
- package/vendor/fastctx/src/operation.rs +356 -0
- package/vendor/fastctx/src/ordered_window.rs +1235 -0
- package/vendor/fastctx/src/os_environment.rs +414 -0
- package/vendor/fastctx/src/path_codec.rs +850 -0
- package/vendor/fastctx/src/paths.rs +244 -0
- package/vendor/fastctx/src/process_identity.rs +763 -0
- package/vendor/fastctx/src/process_policy.rs +74 -0
- package/vendor/fastctx/src/read_tool/batch.rs +496 -0
- package/vendor/fastctx/src/read_tool/hex_file.rs +141 -0
- package/vendor/fastctx/src/read_tool/image_file.rs +88 -0
- package/vendor/fastctx/src/read_tool/mod.rs +245 -0
- package/vendor/fastctx/src/read_tool/pdf.rs +470 -0
- package/vendor/fastctx/src/read_tool/pdf_disabled.rs +47 -0
- package/vendor/fastctx/src/read_tool/pdf_engine.rs +664 -0
- package/vendor/fastctx/src/read_tool/text_file.rs +351 -0
- package/vendor/fastctx/src/render_plan.rs +468 -0
- package/vendor/fastctx/src/runtime/activity.rs +159 -0
- package/vendor/fastctx/src/runtime/hosts.rs +99 -0
- package/vendor/fastctx/src/runtime/journal.rs +556 -0
- package/vendor/fastctx/src/runtime/local_ipc.rs +186 -0
- package/vendor/fastctx/src/runtime/mod.rs +746 -0
- package/vendor/fastctx/src/runtime/protocol.rs +296 -0
- package/vendor/fastctx/src/runtime/session.rs +536 -0
- package/vendor/fastctx/src/runtime/windows_process.rs +66 -0
- package/vendor/fastctx/src/search_parallelism.rs +106 -0
- package/vendor/fastctx/src/search_text.rs +227 -0
- package/vendor/fastctx/src/server.rs +359 -0
- package/vendor/fastctx/src/server_manifest.rs +468 -0
- package/vendor/fastctx/src/server_support.rs +826 -0
- package/vendor/fastctx/src/session.rs +629 -0
- package/vendor/fastctx/src/shell/apply_patch_hint.rs +41 -0
- package/vendor/fastctx/src/shell/bash.rs +263 -0
- package/vendor/fastctx/src/shell/buffer.rs +108 -0
- package/vendor/fastctx/src/shell/encoding.rs +403 -0
- package/vendor/fastctx/src/shell/foreground.rs +115 -0
- package/vendor/fastctx/src/shell/jobs/admission.rs +91 -0
- package/vendor/fastctx/src/shell/jobs/background.rs +146 -0
- package/vendor/fastctx/src/shell/jobs/host.rs +830 -0
- package/vendor/fastctx/src/shell/jobs/identity.rs +29 -0
- package/vendor/fastctx/src/shell/jobs/mod.rs +1513 -0
- package/vendor/fastctx/src/shell/jobs/model.rs +244 -0
- package/vendor/fastctx/src/shell/jobs/output_log.rs +1148 -0
- package/vendor/fastctx/src/shell/jobs/store.rs +1300 -0
- package/vendor/fastctx/src/shell/mod.rs +345 -0
- package/vendor/fastctx/src/shell/normalize.rs +389 -0
- package/vendor/fastctx/src/shell/output.rs +406 -0
- package/vendor/fastctx/src/shell/process.rs +493 -0
- package/vendor/fastctx/src/shell_server.rs +156 -0
- package/vendor/fastctx/src/skip_report.rs +83 -0
- package/vendor/fastctx/src/stdio_transport.rs +177 -0
- package/vendor/fastctx/src/tool_schema.rs +204 -0
- package/vendor/fastctx/src/traversal.rs +846 -0
- package/vendor/fastctx/third-party/pdfium-7763/LICENSE +9 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/abseil.txt +202 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/agg23.txt +14 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/fast_float.txt +27 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/freetype.txt +169 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/icu.txt +542 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/lcms.txt +27 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libjpeg_turbo.ijg +260 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libjpeg_turbo.md +135 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libopenjpeg.txt +32 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libpng.txt +134 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libtiff.txt +21 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/llvm-libc.txt +278 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/pdfium.txt +230 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/simdutf.txt +18 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/zlib.txt +29 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
//! Cross-platform input parsing, output normalization, and filesystem error translation.
|
|
2
|
+
|
|
3
|
+
use std::fs;
|
|
4
|
+
use std::io;
|
|
5
|
+
use std::path::{Path, PathBuf};
|
|
6
|
+
|
|
7
|
+
/// Converts either user-facing separator style into a path the current platform can parse.
|
|
8
|
+
pub fn parse_input_path(input: &str) -> PathBuf {
|
|
9
|
+
if std::path::MAIN_SEPARATOR == '/' {
|
|
10
|
+
PathBuf::from(input.replace('\\', "/"))
|
|
11
|
+
} else {
|
|
12
|
+
PathBuf::from(input)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/// Accepts either a plain path or a strictly local file URI and returns a native path.
|
|
17
|
+
///
|
|
18
|
+
/// URI normalization is deliberately limited to the `file` scheme. Other schemes and
|
|
19
|
+
/// ambiguous file-URI shapes fail before filesystem lookup so callers never guess a path.
|
|
20
|
+
pub(crate) fn parse_local_path_input(input: &str) -> Result<PathBuf, String> {
|
|
21
|
+
let Some(scheme) = uri_scheme(input) else {
|
|
22
|
+
return Ok(parse_input_path(input));
|
|
23
|
+
};
|
|
24
|
+
if !scheme.eq_ignore_ascii_case("file") {
|
|
25
|
+
return Err(format!(
|
|
26
|
+
"Unsupported URI scheme \"{}\" for a local filesystem path.",
|
|
27
|
+
scheme.to_ascii_lowercase()
|
|
28
|
+
));
|
|
29
|
+
}
|
|
30
|
+
if !input[scheme.len() + 1..].starts_with("//") {
|
|
31
|
+
return Err(
|
|
32
|
+
"Invalid local file URI: expected an absolute URI with a local authority.".to_string(),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
if !has_valid_percent_escapes(input) {
|
|
36
|
+
return Err("Invalid local file URI: malformed percent escape.".to_string());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let authority = input[scheme.len() + 3..]
|
|
40
|
+
.split(['/', '\\', '?', '#'])
|
|
41
|
+
.next()
|
|
42
|
+
.unwrap_or_default();
|
|
43
|
+
|
|
44
|
+
let uri =
|
|
45
|
+
url::Url::parse(input).map_err(|error| format!("Invalid local file URI: {error}."))?;
|
|
46
|
+
if !uri.username().is_empty() || uri.password().is_some() || uri.port().is_some() {
|
|
47
|
+
return Err(
|
|
48
|
+
"Invalid local file URI: user information and ports are not supported.".to_string(),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if uri.query().is_some() || uri.fragment().is_some() {
|
|
52
|
+
return Err(
|
|
53
|
+
"Invalid local file URI: query and fragment components are not supported.".to_string(),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
// url intentionally erases a file URL's host when the path begins with a Windows drive
|
|
57
|
+
// designator. Inspect the original authority so a remote URI can never become a local path.
|
|
58
|
+
if (!authority.is_empty() && !authority.eq_ignore_ascii_case("localhost"))
|
|
59
|
+
|| uri
|
|
60
|
+
.host_str()
|
|
61
|
+
.is_some_and(|host| !host.eq_ignore_ascii_case("localhost"))
|
|
62
|
+
{
|
|
63
|
+
return Err("Invalid local file URI: remote authorities are not supported.".to_string());
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let path = uri.to_file_path().map_err(|_| {
|
|
67
|
+
"Invalid local file URI: it cannot be represented as a local filesystem path.".to_string()
|
|
68
|
+
})?;
|
|
69
|
+
if path.as_os_str().to_string_lossy().contains('\0') {
|
|
70
|
+
return Err("Invalid local file URI: the decoded path contains a NUL byte.".to_string());
|
|
71
|
+
}
|
|
72
|
+
if !path.is_absolute() {
|
|
73
|
+
return Err("Invalid local file URI: the decoded path is not absolute.".to_string());
|
|
74
|
+
}
|
|
75
|
+
Ok(path)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
pub(crate) fn is_local_file_uri_input(input: &str) -> bool {
|
|
79
|
+
uri_scheme(input).is_some_and(|scheme| scheme.eq_ignore_ascii_case("file"))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// Returns the RFC 3986 scheme of an input that cannot instead be a Windows drive designator.
|
|
83
|
+
///
|
|
84
|
+
/// One-character schemes are reported as absent: no registered scheme is that short, while
|
|
85
|
+
/// `C:\repo`, `C:/repo`, and the drive-relative `C:repo` all open with one. Reading those as
|
|
86
|
+
/// schemes would reject real paths as unsupported protocols.
|
|
87
|
+
fn uri_scheme(input: &str) -> Option<&str> {
|
|
88
|
+
let end = input.find(':')?;
|
|
89
|
+
let scheme = &input[..end];
|
|
90
|
+
if scheme.len() < 2 {
|
|
91
|
+
return None;
|
|
92
|
+
}
|
|
93
|
+
let mut bytes = scheme.bytes();
|
|
94
|
+
bytes.next()?.is_ascii_alphabetic().then_some(())?;
|
|
95
|
+
bytes
|
|
96
|
+
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'+' | b'-' | b'.'))
|
|
97
|
+
.then_some(scheme)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fn has_valid_percent_escapes(input: &str) -> bool {
|
|
101
|
+
let bytes = input.as_bytes();
|
|
102
|
+
let mut index = 0;
|
|
103
|
+
while index < bytes.len() {
|
|
104
|
+
if bytes[index] != b'%' {
|
|
105
|
+
index += 1;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
let Some(pair) = bytes.get(index + 1..index + 3) else {
|
|
109
|
+
return false;
|
|
110
|
+
};
|
|
111
|
+
if !pair.iter().all(u8::is_ascii_hexdigit) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
index += 3;
|
|
115
|
+
}
|
|
116
|
+
true
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// Returns an absolute display path that never depends on platform backslashes.
|
|
120
|
+
pub fn display_path(path: &Path) -> String {
|
|
121
|
+
let mut value = path.to_string_lossy().replace('\\', "/");
|
|
122
|
+
if let Some(rest) = value.strip_prefix("//?/UNC/") {
|
|
123
|
+
value = format!("//{rest}");
|
|
124
|
+
} else if let Some(rest) = value.strip_prefix("//?/") {
|
|
125
|
+
value = rest.to_string();
|
|
126
|
+
}
|
|
127
|
+
value
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// Canonicalizes an existing path to a stable absolute form without the Windows `\\?\` prefix.
|
|
131
|
+
pub fn canonical_existing(path: &Path) -> io::Result<PathBuf> {
|
|
132
|
+
dunce::canonicalize(path)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// Normalized display value of the current session working directory.
|
|
136
|
+
pub fn current_dir_display() -> String {
|
|
137
|
+
crate::session::current_dir()
|
|
138
|
+
.ok()
|
|
139
|
+
.and_then(|path| canonical_existing(&path).ok().or(Some(path)))
|
|
140
|
+
.map(|path| display_path(&path))
|
|
141
|
+
.unwrap_or_else(|| ".".to_string())
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/// Translates open/read failures into the frozen permission or lock messages.
|
|
145
|
+
pub fn io_error_message(path: &Path, error: &io::Error) -> String {
|
|
146
|
+
let display = display_path(path);
|
|
147
|
+
#[cfg(windows)]
|
|
148
|
+
if matches!(error.raw_os_error(), Some(32 | 33)) {
|
|
149
|
+
return format!("Cannot open file (locked by another process): {display}");
|
|
150
|
+
}
|
|
151
|
+
if error.kind() == io::ErrorKind::PermissionDenied {
|
|
152
|
+
return format!("Permission denied: {display}");
|
|
153
|
+
}
|
|
154
|
+
format!("Cannot open file: {display} ({error})")
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// Selects a high-confidence sibling candidate for a missing file.
|
|
158
|
+
pub fn nearest_existing_name(path: &Path) -> Option<PathBuf> {
|
|
159
|
+
let parent = path.parent()?;
|
|
160
|
+
let wanted = path.file_name()?.to_string_lossy();
|
|
161
|
+
let mut candidates = fs::read_dir(parent)
|
|
162
|
+
.ok()?
|
|
163
|
+
.filter_map(Result::ok)
|
|
164
|
+
.map(|entry| {
|
|
165
|
+
let name = entry.file_name().to_string_lossy().into_owned();
|
|
166
|
+
let score = strsim::jaro_winkler(&wanted, &name);
|
|
167
|
+
(score, name, entry.path())
|
|
168
|
+
})
|
|
169
|
+
.filter(|(_, name, _)| name != wanted.as_ref())
|
|
170
|
+
.filter(|(score, _, _)| *score >= 0.80)
|
|
171
|
+
.collect::<Vec<_>>();
|
|
172
|
+
candidates.sort_by(|left, right| {
|
|
173
|
+
right
|
|
174
|
+
.0
|
|
175
|
+
.total_cmp(&left.0)
|
|
176
|
+
.then_with(|| left.1.as_bytes().cmp(right.1.as_bytes()))
|
|
177
|
+
});
|
|
178
|
+
candidates.into_iter().next().map(|(_, _, path)| path)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/// Builds the read error for missing or relative paths, including a recovery step when possible.
|
|
182
|
+
pub fn missing_file_message(input: &str) -> String {
|
|
183
|
+
let cwd = crate::session::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
|
184
|
+
let parsed = parse_input_path(input);
|
|
185
|
+
let resolved = if parsed.is_absolute() {
|
|
186
|
+
parsed.clone()
|
|
187
|
+
} else {
|
|
188
|
+
cwd.join(&parsed)
|
|
189
|
+
};
|
|
190
|
+
let requested = if parsed.is_absolute() {
|
|
191
|
+
display_path(&parsed)
|
|
192
|
+
} else {
|
|
193
|
+
input.replace('\\', "/")
|
|
194
|
+
};
|
|
195
|
+
let cwd_display = current_dir_display();
|
|
196
|
+
let mut note = format!("Note: the session working directory is {cwd_display}.");
|
|
197
|
+
if !parsed.is_absolute() && resolved.exists() {
|
|
198
|
+
let resolved_display = canonical_existing(&resolved).unwrap_or_else(|_| resolved.clone());
|
|
199
|
+
note.push_str(&format!(
|
|
200
|
+
" Use the absolute path {}.",
|
|
201
|
+
display_path(&resolved_display)
|
|
202
|
+
));
|
|
203
|
+
}
|
|
204
|
+
let mut message = format!("File does not exist: {requested}\n{note}");
|
|
205
|
+
if let Some(candidate) = nearest_existing_name(&resolved) {
|
|
206
|
+
let candidate = canonical_existing(&candidate).unwrap_or(candidate);
|
|
207
|
+
message.push_str(&format!("\nDid you mean: {}?", display_path(&candidate)));
|
|
208
|
+
}
|
|
209
|
+
message
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/// Builds read's missing-file error and explains why a lossy U+FFFD filename cannot round-trip as text.
|
|
213
|
+
pub fn missing_read_file_message(input: &str) -> String {
|
|
214
|
+
let mut message = missing_file_message(input);
|
|
215
|
+
if input.contains('\u{FFFD}') {
|
|
216
|
+
message.push_str(
|
|
217
|
+
"\nNote: this path contains U+FFFD (a placeholder for bytes that are not valid text); it looks like the lossy rendering of a filename that cannot be represented as text and cannot be opened by name.",
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
message
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/// Builds the missing-path error shared by grep and glob.
|
|
224
|
+
pub fn missing_search_path_message(input: &str) -> String {
|
|
225
|
+
let cwd = crate::session::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
|
226
|
+
let parsed = parse_input_path(input);
|
|
227
|
+
let resolved = if parsed.is_absolute() {
|
|
228
|
+
parsed
|
|
229
|
+
} else {
|
|
230
|
+
cwd.join(&parsed)
|
|
231
|
+
};
|
|
232
|
+
let mut note = format!(
|
|
233
|
+
"Note: the session working directory is {}.",
|
|
234
|
+
current_dir_display()
|
|
235
|
+
);
|
|
236
|
+
if !Path::new(input).is_absolute() && resolved.exists() {
|
|
237
|
+
let absolute = canonical_existing(&resolved).unwrap_or(resolved);
|
|
238
|
+
note.push_str(&format!(
|
|
239
|
+
" Use the absolute path {}.",
|
|
240
|
+
display_path(&absolute)
|
|
241
|
+
));
|
|
242
|
+
}
|
|
243
|
+
format!("Path does not exist: {}\n{note}", input.replace('\\', "/"))
|
|
244
|
+
}
|