Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .clippy.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
doc-valid-idents = ["PowerShell"]
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
"dtolnay",
"execing",
"getconf",
"idents",
"metacharacters",
"POSIX",
"pwsh",
"Rustfmt",
"sout",
"UXXXXXXXX"
Expand Down
20 changes: 13 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,19 @@ version = "0.7.2"
include = ["LICENSE", "README.md", "src/**/*.rs"]

[features]
default = ["bstr", "bash", "sh", "fish"]
default = ["bstr", "bash", "pwsh", "sh", "fish"]
bash = []
fish = []
pwsh = []
sh = []

[dependencies]
bstr = { version = "1", optional = true }

[dev-dependencies]
criterion = { version = "^0.5.1", features = ["html_reports"] }
criterion = { version = "^0.6.0", features = ["html_reports"] }
lenient_semver = "0.4.2"
semver = "1.0.23"
semver = "1.0.26"
test-case = "3.3.1"

[[bench]]
Expand All @@ -33,11 +34,16 @@ harness = false
required-features = ["bash"]

[[bench]]
name = "sh"
name = "fish"
harness = false
required-features = ["sh"]
required-features = ["fish"]

[[bench]]
name = "fish"
name = "pwsh"
harness = false
required-features = ["fish"]
required-features = ["pwsh"]

[[bench]]
name = "sh"
harness = false
required-features = ["sh"]
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
[`Dash`]: https://docs.rs/shell-quote/latest/shell_quote/struct.Dash.html
[`Bash`]: https://docs.rs/shell-quote/latest/shell_quote/struct.Bash.html
[`Fish`]: https://docs.rs/shell-quote/latest/shell_quote/struct.Fish.html
[`Pwsh`]: https://docs.rs/shell-quote/latest/shell_quote/struct.Pwsh.html
[`Zsh`]: https://docs.rs/shell-quote/latest/shell_quote/struct.Zsh.html
[`QuoteRefExt`]: https://docs.rs/shell-quote/latest/shell_quote/trait.QuoteRefExt.html
[`QuoteRefExt::quoted`]: https://docs.rs/shell-quote/latest/shell_quote/trait.QuoteRefExt.html#tymethod.quoted
Expand All @@ -37,11 +38,12 @@ metacharacters, function calls, or other syntax. This is frequently not as
simple as wrapping a string in quotes.

This package implements escaping for [GNU Bash][gnu-bash], [Z Shell][z-shell],
[fish][], and `/bin/sh`-like shells including [Dash][dash].
[fish][], [PowerShell][pwsh], and `/bin/sh`-like shells including [Dash][dash].

[dash]: https://en.wikipedia.org/wiki/Almquist_shell#dash
[gnu-bash]: https://www.gnu.org/software/bash/
[z-shell]: https://zsh.sourceforge.io/
[pwsh]: https://learn.microsoft.com/powershell/
[fish]: https://fishshell.com/

It can take as input many different string and byte string types:
Expand All @@ -66,29 +68,31 @@ Inspired by the Haskell [shell-escape][] package.
## Examples

When quoting using raw bytes it can be convenient to call [`Sh`]'s, [`Dash`]'s,
[`Bash`]'s, [`Fish`]'s, and [`Zsh`]'s associated functions directly:
[`Bash`]'s, [`Fish`]'s, [`Pwsh`]'s, and [`Zsh`]'s associated functions directly:

```rust
use shell_quote::{Bash, Dash, Fish, Sh, Zsh};
use shell_quote::{Bash, Dash, Fish, Pwsh, Sh, Zsh};
// No quoting is necessary for simple strings.
assert_eq!(Sh::quote_vec("foobar"), b"foobar");
assert_eq!(Dash::quote_vec("foobar"), b"foobar"); // `Dash` is an alias for `Sh`
assert_eq!(Bash::quote_vec("foobar"), b"foobar");
assert_eq!(Zsh::quote_vec("foobar"), b"foobar"); // `Zsh` is an alias for `Bash`
assert_eq!(Fish::quote_vec("foobar"), b"foobar");
assert_eq!(Pwsh::quote_vec("foobar"), b"'foobar'");
// In all shells, quoting is necessary for strings with spaces.
assert_eq!(Sh::quote_vec("foo bar"), b"foo' bar'");
assert_eq!(Dash::quote_vec("foo bar"), b"foo' bar'");
assert_eq!(Bash::quote_vec("foo bar"), b"$'foo bar'");
assert_eq!(Zsh::quote_vec("foo bar"), b"$'foo bar'");
assert_eq!(Fish::quote_vec("foo bar"), b"foo' bar'");
assert_eq!(Pwsh::quote_vec("foo bar"), b"'foo bar'");
```

It's also possible to use the extension trait [`QuoteRefExt`] which provides a
[`quoted`][`QuoteRefExt::quoted`] function:

```rust
use shell_quote::{Bash, Sh, Fish, QuoteRefExt};
use shell_quote::{Bash, Sh, Fish, Pwsh, QuoteRefExt};
let quoted: String = "foo bar".quoted(Bash);
assert_eq!(quoted, "$'foo bar'");
let quoted: Vec<u8> = "foo bar".quoted(Sh);
Expand Down Expand Up @@ -159,7 +163,8 @@ assert_eq!(&data_iso_8859_1_quoted, b"$'caf\\xE9'"); // ISO-8859-1: 1 byte, hex

[`Sh`] can serve as a lowest common denominator for Bash, Z Shell, and
`/bin/sh`-like shells like Dash. However, fish's quoting rules are different
enough that you must use [`Fish`] for fish scripts.
enough that you must use [`Fish`] for fish scripts, and PowerShells's rules are
different enough again that you must use [`Pwsh`] for PowerShell scripts.

Note that using [`Sh`] as a lowest common denominator brings with it other
issues; read its documentation carefully to understand the limitations.
Expand All @@ -171,6 +176,7 @@ The following are all enabled by default:
- `bstr`: Support [`bstr::BStr`] and [`bstr::BString`].
- `bash`: Support [Bash][gnu-bash] and [Z Shell][z-shell].
- `fish`: Support [fish][].
- `pwsh`: Support [PowerShell][pwsh].
- `sh`: Support `/bin/sh`-like shells including [Dash][dash].

To limit support to specific shells, you must disable this crate's default
Expand Down
4 changes: 3 additions & 1 deletion benches/bash.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use std::hint::black_box;

use criterion::{criterion_group, criterion_main, Criterion};

use shell_quote::Bash;

Expand Down
4 changes: 3 additions & 1 deletion benches/fish.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use std::hint::black_box;

use criterion::{criterion_group, criterion_main, Criterion};

use shell_quote::Fish;

Expand Down
40 changes: 40 additions & 0 deletions benches/pwsh.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use std::hint::black_box;

use criterion::{criterion_group, criterion_main, Criterion};

use shell_quote::Pwsh;

fn criterion_benchmark(c: &mut Criterion) {
let empty_string = "";
c.bench_function("pwsh escape empty", |b| {
b.iter(|| Pwsh::quote_vec(black_box(empty_string)))
});

let alphanumeric_short = "abcdefghijklmnopqrstuvwxyz0123456789";
c.bench_function("pwsh escape a-z", |b| {
b.iter(|| Pwsh::quote_vec(black_box(alphanumeric_short)))
});

let alphanumeric_long = alphanumeric_short.repeat(1000);
c.bench_function("pwsh escape a-z long", |b| {
b.iter(|| Pwsh::quote_vec(black_box(&alphanumeric_long)))
});

let bytes_short = (1..=255u8).map(char::from).collect::<String>();
c.bench_function("pwsh escape bytes", |b| {
b.iter(|| Pwsh::quote_vec(black_box(&bytes_short)))
});

let bytes_long = bytes_short.repeat(1000);
c.bench_function("pwsh escape bytes long", |b| {
b.iter(|| Pwsh::quote_vec(black_box(&bytes_long)))
});

let utf8 = ('\x01'..=char::MAX).collect::<String>();
c.bench_function("pwsh escape utf-8", |b| {
b.iter(|| Pwsh::quote_vec(black_box(&utf8)))
});
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
4 changes: 3 additions & 1 deletion benches/sh.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use std::hint::black_box;

use criterion::{criterion_group, criterion_main, Criterion};

use shell_quote::Sh;

Expand Down
2 changes: 1 addition & 1 deletion src/bash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ impl QuoteInto<Vec<u8>> for Bash {

impl QuoteInto<String> for Bash {
fn quote_into<'q, S: Into<Quotable<'q>>>(s: S, out: &mut String) {
Self::quote_into_vec(s, unsafe { out.as_mut_vec() })
Self::quote_into_vec(s, unsafe { out.as_mut_vec() });
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/fish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ impl QuoteInto<Vec<u8>> for Fish {

impl QuoteInto<String> for Fish {
fn quote_into<'q, S: Into<Quotable<'q>>>(s: S, out: &mut String) {
Self::quote_into_vec(s, unsafe { out.as_mut_vec() })
Self::quote_into_vec(s, unsafe { out.as_mut_vec() });
}
}

Expand Down
26 changes: 24 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
//! [`Dash`]: `Dash`
//! [`Bash`]: `Bash`
//! [`Fish`]: `Fish`
//! [`Pwsh`]: `Pwsh`
//! [`Zsh`]: `Zsh`
//!
//! [`QuoteRefExt`]: `QuoteRefExt`
Expand All @@ -31,24 +32,45 @@
feature = "bstr",
feature = "bash",
feature = "fish",
feature = "pwsh",
feature = "sh",
),
doc = include_str!("../README.md")
)]
#![warn(clippy::unused_result_ok)]
#![warn(clippy::pedantic)]
#![allow(
clippy::default_trait_access,
clippy::enum_glob_use,
clippy::items_after_statements,
clippy::map_unwrap_or,
clippy::match_same_arms,
clippy::missing_errors_doc,
clippy::must_use_candidate,
clippy::needless_pass_by_value,
clippy::redundant_closure_for_method_calls,
clippy::struct_field_names,
clippy::too_many_lines,
clippy::unnecessary_debug_formatting,
clippy::unused_async
)]

use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};

mod ascii;
mod bash;
mod fish;
mod pwsh;
mod sh;
mod utf8;

#[cfg(feature = "bash")]
pub use bash::Bash;
#[cfg(feature = "fish")]
pub use fish::Fish;
#[cfg(feature = "pwsh")]
pub use pwsh::Pwsh;
#[cfg(feature = "sh")]
pub use sh::Sh;

Expand Down Expand Up @@ -135,12 +157,12 @@ where
/// [`PathBuf`]/[`Path`] didn't work in a natural way.
pub enum Quotable<'a> {
#[cfg_attr(
not(any(feature = "bash", feature = "fish", feature = "sh")),
not(any(feature = "bash", feature = "fish", feature = "pwsh", feature = "sh")),
allow(unused)
)]
Bytes(&'a [u8]),
#[cfg_attr(
not(any(feature = "bash", feature = "fish", feature = "sh")),
not(any(feature = "bash", feature = "fish", feature = "pwsh", feature = "sh")),
allow(unused)
)]
Text(&'a str),
Expand Down
Loading