use crate::{ShellWordError, is, whilst, write_at};
#[doc = crate::_tags!(lang text)]
#[doc = crate::_doc_meta!{location("lang/prog/script/shell")}]
#[must_use]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct ShellQuote {
reject_control: bool,
}
impl ShellQuote {
pub const fn new() -> Self {
Self { reject_control: false }
}
pub const fn reject_control(mut self, reject: bool) -> Self {
self.reject_control = reject;
self
}
pub const fn quoted_len(self, word: &[u8]) -> Result<usize, ShellWordError> {
is! { word.is_empty(), return Ok(2) } let mut needs_quote = false;
let mut quoted_len = 2; whilst! { i in 0..word.len(); {
let byte = word[i];
is! { byte == b'\0', return Err(ShellWordError::Nul) }
if self.reject_control && is_control_byte(byte) {
return Err(ShellWordError::Control { byte });
}
is! { !is_unquoted_shell_byte(byte), needs_quote = true; }
quoted_len += is![byte == b'\'', 4, 1];
}}
is! { needs_quote, Ok(quoted_len), Ok(word.len()) }
}
pub const fn quote_to(self, word: &[u8], out: &mut [u8]) -> Result<usize, ShellWordError> {
let needed = match self.quoted_len(word) {
Ok(needed) => needed,
Err(err) => return Err(err),
};
is! { out.len() < needed, return Err(ShellWordError::OutputTooSmall { needed }) }
is! { word.is_empty(), return Ok(write_at![out, 0, b'\'', b'\'']) }
if needed == word.len() {
whilst! { i in 0..word.len(); { out[i] = word[i]; }}
return Ok(needed);
}
let mut pos = 0;
write_at![out, +=pos, b'\''];
whilst! { i in 0..word.len(); {
let byte = word[i];
if byte == b'\'' {
write_at![out, +=pos, b'\'', b'\\', b'\'', b'\''];
} else {
write_at![out, +=pos, byte];
}
}}
out[pos] = b'\'';
Ok(needed)
}
}
const fn is_control_byte(byte: u8) -> bool {
byte <= 0x1F || byte == 0x7F
}
const fn is_unquoted_shell_byte(byte: u8) -> bool {
matches!(byte, b'+' | b'-' | b'.' | b'/' | b':' | b'@' | b']' | b'_'
| b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z')
}