term, cli: `winsplit` over `shlex` on Windows
Command line parsing differs on Windows vs. POSIX compliant operating systems (just consider the differences in handling `\` for paths vs. as a marker for escape sequences). Thus, on Windows use `winsplit` to split command arguments instead of `shlex` for Unix-like OSes. `winsplit` is a small crate with no other dependencies.
This commit is contained in:
parent
537eaba8d1
commit
930ec175f1
|
|
@ -20,6 +20,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## Fixed Bugs
|
## Fixed Bugs
|
||||||
|
|
||||||
|
- When preparing commands to execute, the `shlex` crate was used on all platforms.
|
||||||
|
The semantics on Windows are different (e.g. '\' is a path separator on Windows
|
||||||
|
but marks an escape sequence on Unix-like systems), which lead to issues when
|
||||||
|
attempting to execute child processes.
|
||||||
|
This is fixed by using `winsplit` on Windows instead.
|
||||||
|
|
||||||
## Deprecations
|
## Deprecations
|
||||||
|
|
||||||
- The `rad fork` command was confusing, and mislead users as to what its purpose
|
- The `rad fork` command was confusing, and mislead users as to what its purpose
|
||||||
|
|
|
||||||
|
|
@ -2888,6 +2888,7 @@ dependencies = [
|
||||||
"tree-sitter-rust",
|
"tree-sitter-rust",
|
||||||
"tree-sitter-toml-ng",
|
"tree-sitter-toml-ng",
|
||||||
"tree-sitter-typescript",
|
"tree-sitter-typescript",
|
||||||
|
"winsplit",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2902,6 +2903,7 @@ dependencies = [
|
||||||
"shlex",
|
"shlex",
|
||||||
"snapbox",
|
"snapbox",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
|
"winsplit",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3201,6 +3203,7 @@ dependencies = [
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
"unicode-display-width",
|
"unicode-display-width",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
|
"winsplit",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -5107,6 +5110,12 @@ dependencies = [
|
||||||
"windows",
|
"windows",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winsplit"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ab703352da6a72f35c39a533526393725640575bb211f61987a2748323ad956"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen-rt"
|
name = "wit-bindgen-rt"
|
||||||
version = "0.39.0"
|
version = "0.39.0"
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ sqlite = "0.32.0"
|
||||||
tempfile = "3.3.0"
|
tempfile = "3.3.0"
|
||||||
thiserror = { version = "2", default-features = false }
|
thiserror = { version = "2", default-features = false }
|
||||||
winpipe = "0.1.1"
|
winpipe = "0.1.1"
|
||||||
|
winsplit = "0.1.0"
|
||||||
zeroize = "1.5.7"
|
zeroize = "1.5.7"
|
||||||
|
|
||||||
# Crates from the "radicle-git" workspace. These should be synced manually.
|
# Crates from the "radicle-git" workspace. These should be synced manually.
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,11 @@ escargot = "0.5.7"
|
||||||
log = { workspace = true, features = ["std"] }
|
log = { workspace = true, features = ["std"] }
|
||||||
pretty_assertions = { workspace = true }
|
pretty_assertions = { workspace = true }
|
||||||
radicle = { workspace = true, features = ["logger", "test"]}
|
radicle = { workspace = true, features = ["logger", "test"]}
|
||||||
shlex = { workspace = true }
|
|
||||||
snapbox = { workspace = true }
|
snapbox = { workspace = true }
|
||||||
thiserror = { workspace = true, default-features = true }
|
thiserror = { workspace = true, default-features = true }
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
shlex = { workspace = true }
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
winsplit = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -353,7 +353,13 @@ impl TestFormula {
|
||||||
content.push('\n');
|
content.push('\n');
|
||||||
} else if let Some(line) = line.strip_prefix('$') {
|
} else if let Some(line) = line.strip_prefix('$') {
|
||||||
let line = line.trim();
|
let line = line.trim();
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
let parts = shlex::split(line).ok_or(Error::Parse)?;
|
let parts = shlex::split(line).ok_or(Error::Parse)?;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let parts = winsplit::split(line);
|
||||||
|
|
||||||
let (cmd, args) = parts.split_first().ok_or(Error::Parse)?;
|
let (cmd, args) = parts.split_first().ok_or(Error::Parse)?;
|
||||||
|
|
||||||
test.assertions.push(Assertion {
|
test.assertions.push(Assertion {
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ radicle-term = { workspace = true }
|
||||||
schemars = { workspace = true }
|
schemars = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
shlex = { workspace = true }
|
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
thiserror = { workspace = true, default-features = true }
|
thiserror = { workspace = true, default-features = true }
|
||||||
timeago = { version = "0.4.2", default-features = false }
|
timeago = { version = "0.4.2", default-features = false }
|
||||||
|
|
@ -53,6 +52,12 @@ tree-sitter-toml-ng = "0.6.0"
|
||||||
tree-sitter-typescript = "0.23.2"
|
tree-sitter-typescript = "0.23.2"
|
||||||
zeroize = { workspace = true }
|
zeroize = { workspace = true }
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
shlex = { workspace = true }
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
winsplit = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = { workspace = true }
|
pretty_assertions = { workspace = true }
|
||||||
radicle = { workspace = true, features = ["test"] }
|
radicle = { workspace = true, features = ["test"] }
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,14 @@ pub fn run(elem: impl Element) -> io::Result<()> {
|
||||||
let Some(pager) = radicle::profile::env::pager() else {
|
let Some(pager) = radicle::profile::env::pager() else {
|
||||||
return elem.write(Constraint::UNBOUNDED);
|
return elem.write(Constraint::UNBOUNDED);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
let Some(parts) = shlex::split(&pager) else {
|
let Some(parts) = shlex::split(&pager) else {
|
||||||
return elem.write(Constraint::UNBOUNDED);
|
return elem.write(Constraint::UNBOUNDED);
|
||||||
};
|
};
|
||||||
|
#[cfg(windows)]
|
||||||
|
let parts = winsplit::split(&pager);
|
||||||
|
|
||||||
let Some((program, args)) = parts.split_first() else {
|
let Some((program, args)) = parts.split_first() else {
|
||||||
return elem.write(Constraint::UNBOUNDED);
|
return elem.write(Constraint::UNBOUNDED);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,15 @@ unicode-display-width = "0.3.0"
|
||||||
unicode-segmentation = "1.7.1"
|
unicode-segmentation = "1.7.1"
|
||||||
zeroize = { workspace = true }
|
zeroize = { workspace = true }
|
||||||
git2 = { workspace = true, optional = true }
|
git2 = { workspace = true, optional = true }
|
||||||
shlex = { workspace = true }
|
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
crossbeam-channel = { workspace = true }
|
crossbeam-channel = { workspace = true }
|
||||||
libc = { workspace = true }
|
libc = { workspace = true }
|
||||||
radicle-signals = { workspace = true }
|
radicle-signals = { workspace = true }
|
||||||
|
shlex = { workspace = true }
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
winsplit = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = { workspace = true }
|
pretty_assertions = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -110,12 +110,20 @@ impl Editor {
|
||||||
"editor not configured: the `EDITOR` environment variable is not set",
|
"editor not configured: the `EDITOR` environment variable is not set",
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
let Some(parts) = shlex::split(cmd.to_string_lossy().as_ref()) else {
|
|
||||||
|
let lossy = cmd.to_string_lossy();
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
let Some(parts) = shlex::split(&lossy) else {
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
io::ErrorKind::InvalidInput,
|
io::ErrorKind::InvalidInput,
|
||||||
format!("invalid editor command {cmd:?}"),
|
format!("invalid editor command {cmd:?}"),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
let parts = winsplit::split(&lossy);
|
||||||
|
|
||||||
let Some((program, args)) = parts.split_first() else {
|
let Some((program, args)) = parts.split_first() else {
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
io::ErrorKind::InvalidInput,
|
io::ErrorKind::InvalidInput,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue