shell_quote/
lib.rs

1//! <!-- Override references in README.md back to `cargo doc`-generated links. -->
2//!
3//! [`&str`]: `&str`
4//! [`String`]: `String`
5//! [`bstr::BStr`]: `bstr::BStr`
6//! [`bstr::BString`]: `bstr::BString`
7//! [`slice`]: `slice`
8//! [`Vec<u8>`]: `std::vec::Vec<u8>`
9//! [`OsStr`]: `std::ffi::OsStr`
10//! [`OsString`]: `std::ffi::OsString`
11//! [`Path`]: `std::path::Path`
12//! [`PathBuf`]: `std::path::PathBuf`
13//!
14//! [`Sh`]: `Sh`
15//! [`Dash`]: `Dash`
16//! [`Bash`]: `Bash`
17//! [`Fish`]: `Fish`
18//! [`Zsh`]: `Zsh`
19//!
20//! [`QuoteRefExt`]: `QuoteRefExt`
21//! [`QuoteRefExt::quoted`]: `QuoteRefExt::quoted`
22//! [`QuoteExt`]: `QuoteExt`
23//!
24//! <style>
25//!   .readme-only { display: none; }
26//! </style>
27//!
28
29#![cfg_attr(
30    all(
31        feature = "bstr",
32        feature = "bash",
33        feature = "fish",
34        feature = "sh",
35    ),
36    doc = include_str!("../README.md")
37)]
38
39use std::ffi::{OsStr, OsString};
40use std::path::{Path, PathBuf};
41
42mod ascii;
43mod bash;
44mod fish;
45mod sh;
46mod utf8;
47
48#[cfg(feature = "bash")]
49pub use bash::Bash;
50#[cfg(feature = "fish")]
51pub use fish::Fish;
52#[cfg(feature = "sh")]
53pub use sh::Sh;
54
55/// Dash accepts the same quoted/escaped strings as `/bin/sh` – indeed, on many
56/// systems, `dash` _is_ `/bin/sh` – hence this is an alias for [`Sh`].
57#[cfg(feature = "sh")]
58pub type Dash = sh::Sh;
59
60/// Zsh accepts the same quoted/escaped strings as Bash, hence this is an alias
61/// for [`Bash`].
62#[cfg(feature = "bash")]
63pub type Zsh = bash::Bash;
64
65// ----------------------------------------------------------------------------
66
67/// Quoting/escaping a string of bytes into a shell-safe form.
68pub trait QuoteInto<OUT: ?Sized> {
69    /// Quote/escape a string of bytes into an existing container.
70    fn quote_into<'q, S: Into<Quotable<'q>>>(s: S, out: &mut OUT);
71}
72
73/// Quoting/escaping a string of bytes into a shell-safe form.
74pub trait Quote<OUT: Default>: QuoteInto<OUT> {
75    /// Quote/escape a string of bytes into a new container.
76    fn quote<'q, S: Into<Quotable<'q>>>(s: S) -> OUT {
77        let mut out = OUT::default();
78        Self::quote_into(s, &mut out);
79        out
80    }
81}
82
83/// Blanket [`Quote`] impl for anything that has a [`QuoteInto`] impl.
84impl<T: QuoteInto<OUT>, OUT: Default> Quote<OUT> for T {}
85
86// ----------------------------------------------------------------------------
87
88/// Extension trait for pushing shell quoted byte slices, e.g. `&[u8]`, [`&str`]
89/// – anything that's [`Quotable`] – into container types like [`Vec<u8>`],
90/// [`String`], [`OsString`] on Unix, and [`bstr::BString`] if it's enabled.
91pub trait QuoteExt {
92    fn push_quoted<'q, Q, S>(&mut self, _q: Q, s: S)
93    where
94        Q: QuoteInto<Self>,
95        S: Into<Quotable<'q>>;
96}
97
98impl<T: ?Sized> QuoteExt for T {
99    fn push_quoted<'q, Q, S>(&mut self, _q: Q, s: S)
100    where
101        Q: QuoteInto<Self>,
102        S: Into<Quotable<'q>>,
103    {
104        Q::quote_into(s, self);
105    }
106}
107
108// ----------------------------------------------------------------------------
109
110/// Extension trait for shell quoting many different owned and reference types,
111/// e.g. `&[u8]`, [`&str`] – anything that's [`Quotable`] – into owned container
112/// types like [`Vec<u8>`], [`String`], [`OsString`] on Unix, and
113/// [`bstr::BString`] if it's enabled.
114pub trait QuoteRefExt<Output: Default> {
115    fn quoted<Q: Quote<Output>>(self, q: Q) -> Output;
116}
117
118impl<'a, S, OUT: Default> QuoteRefExt<OUT> for S
119where
120    S: Into<Quotable<'a>>,
121{
122    fn quoted<Q: Quote<OUT>>(self, _q: Q) -> OUT {
123        Q::quote(self)
124    }
125}
126
127// ----------------------------------------------------------------------------
128
129/// A string of bytes that can be quoted/escaped.
130///
131/// This is used by many methods in this crate as a generic
132/// [`Into<Quotable>`][`Into`] constraint. Why not accept
133/// [`AsRef<[u8]>`][`AsRef`] instead? The ergonomics of that approach were not
134/// so good. For example, quoting [`OsString`]/[`OsStr`] and
135/// [`PathBuf`]/[`Path`] didn't work in a natural way.
136pub enum Quotable<'a> {
137    #[cfg_attr(
138        not(any(feature = "bash", feature = "fish", feature = "sh")),
139        allow(unused)
140    )]
141    Bytes(&'a [u8]),
142    #[cfg_attr(
143        not(any(feature = "bash", feature = "fish", feature = "sh")),
144        allow(unused)
145    )]
146    Text(&'a str),
147}
148
149impl<'a> From<&'a [u8]> for Quotable<'a> {
150    fn from(source: &'a [u8]) -> Quotable<'a> {
151        Quotable::Bytes(source)
152    }
153}
154
155impl<'a, const N: usize> From<&'a [u8; N]> for Quotable<'a> {
156    fn from(source: &'a [u8; N]) -> Quotable<'a> {
157        Quotable::Bytes(&source[..])
158    }
159}
160
161impl<'a> From<&'a Vec<u8>> for Quotable<'a> {
162    fn from(source: &'a Vec<u8>) -> Quotable<'a> {
163        Quotable::Bytes(source)
164    }
165}
166
167impl<'a> From<&'a str> for Quotable<'a> {
168    fn from(source: &'a str) -> Quotable<'a> {
169        Quotable::Text(source)
170    }
171}
172
173impl<'a> From<&'a String> for Quotable<'a> {
174    fn from(source: &'a String) -> Quotable<'a> {
175        Quotable::Text(source)
176    }
177}
178
179#[cfg(unix)]
180impl<'a> From<&'a OsStr> for Quotable<'a> {
181    fn from(source: &'a OsStr) -> Quotable<'a> {
182        use std::os::unix::ffi::OsStrExt;
183        source.as_bytes().into()
184    }
185}
186
187#[cfg(unix)]
188impl<'a> From<&'a OsString> for Quotable<'a> {
189    fn from(source: &'a OsString) -> Quotable<'a> {
190        use std::os::unix::ffi::OsStrExt;
191        source.as_bytes().into()
192    }
193}
194
195#[cfg(feature = "bstr")]
196impl<'a> From<&'a bstr::BStr> for Quotable<'a> {
197    fn from(source: &'a bstr::BStr) -> Quotable<'a> {
198        let bytes: &[u8] = source.as_ref();
199        bytes.into()
200    }
201}
202
203#[cfg(feature = "bstr")]
204impl<'a> From<&'a bstr::BString> for Quotable<'a> {
205    fn from(source: &'a bstr::BString) -> Quotable<'a> {
206        let bytes: &[u8] = source.as_ref();
207        bytes.into()
208    }
209}
210
211#[cfg(unix)]
212impl<'a> From<&'a Path> for Quotable<'a> {
213    fn from(source: &'a Path) -> Quotable<'a> {
214        source.as_os_str().into()
215    }
216}
217
218#[cfg(unix)]
219impl<'a> From<&'a PathBuf> for Quotable<'a> {
220    fn from(source: &'a PathBuf) -> Quotable<'a> {
221        source.as_os_str().into()
222    }
223}