2023-02-20 05:38:01 +08:00
|
|
|
use crate::ffi;
|
2023-02-25 00:00:05 +08:00
|
|
|
use crate::wchar_ext::WExt;
|
2023-02-24 23:44:39 +08:00
|
|
|
use crate::wchar_ffi::c_str;
|
2023-02-20 05:38:01 +08:00
|
|
|
use crate::wchar_ffi::{wstr, WCharFromFFI, WString};
|
2023-02-24 23:44:39 +08:00
|
|
|
use std::{ffi::c_uint, mem};
|
2023-02-11 20:31:42 +08:00
|
|
|
|
|
|
|
/// A scoped manager to save the current value of some variable, and optionally set it to a new
|
|
|
|
/// value. When dropped, it restores the variable to its old value.
|
|
|
|
///
|
|
|
|
/// This can be handy when there are multiple code paths to exit a block.
|
|
|
|
pub struct ScopedPush<'a, T> {
|
|
|
|
var: &'a mut T,
|
|
|
|
saved_value: Option<T>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, T> ScopedPush<'a, T> {
|
|
|
|
pub fn new(var: &'a mut T, new_value: T) -> Self {
|
|
|
|
let saved_value = mem::replace(var, new_value);
|
|
|
|
|
|
|
|
Self {
|
|
|
|
var,
|
|
|
|
saved_value: Some(saved_value),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn restore(&mut self) {
|
|
|
|
if let Some(saved_value) = self.saved_value.take() {
|
|
|
|
*self.var = saved_value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, T> Drop for ScopedPush<'a, T> {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
self.restore()
|
|
|
|
}
|
|
|
|
}
|
2023-02-11 23:51:43 +08:00
|
|
|
|
2023-02-24 23:44:39 +08:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
2023-02-11 23:51:43 +08:00
|
|
|
pub enum EscapeStringStyle {
|
|
|
|
Script(EscapeFlags),
|
|
|
|
Url,
|
|
|
|
Var,
|
|
|
|
Regex,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Flags for the [`escape_string()`] function. These are only applicable when the escape style is
|
|
|
|
/// [`EscapeStringStyle::Script`].
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
|
|
pub struct EscapeFlags {
|
|
|
|
/// Do not escape special fish syntax characters like the semicolon. Only escape non-printable
|
|
|
|
/// characters and backslashes.
|
|
|
|
pub no_printables: bool,
|
|
|
|
/// Do not try to use 'simplified' quoted escapes, and do not use empty quotes as the empty
|
|
|
|
/// string.
|
|
|
|
pub no_quoted: bool,
|
|
|
|
/// Do not escape tildes.
|
|
|
|
pub no_tilde: bool,
|
|
|
|
/// Replace non-printable control characters with Unicode symbols.
|
|
|
|
pub symbolic: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Replace special characters with backslash escape sequences. Newline is replaced with `\n`, etc.
|
|
|
|
pub fn escape_string(s: &wstr, style: EscapeStringStyle) -> WString {
|
|
|
|
let mut flags_int = 0;
|
|
|
|
|
|
|
|
let style = match style {
|
|
|
|
EscapeStringStyle::Script(flags) => {
|
|
|
|
const ESCAPE_NO_PRINTABLES: c_uint = 1 << 0;
|
|
|
|
const ESCAPE_NO_QUOTED: c_uint = 1 << 1;
|
|
|
|
const ESCAPE_NO_TILDE: c_uint = 1 << 2;
|
|
|
|
const ESCAPE_SYMBOLIC: c_uint = 1 << 3;
|
|
|
|
|
|
|
|
if flags.no_printables {
|
|
|
|
flags_int |= ESCAPE_NO_PRINTABLES;
|
|
|
|
}
|
|
|
|
if flags.no_quoted {
|
|
|
|
flags_int |= ESCAPE_NO_QUOTED;
|
|
|
|
}
|
|
|
|
if flags.no_tilde {
|
|
|
|
flags_int |= ESCAPE_NO_TILDE;
|
|
|
|
}
|
|
|
|
if flags.symbolic {
|
|
|
|
flags_int |= ESCAPE_SYMBOLIC;
|
|
|
|
}
|
|
|
|
|
|
|
|
ffi::escape_string_style_t::STRING_STYLE_SCRIPT
|
|
|
|
}
|
|
|
|
EscapeStringStyle::Url => ffi::escape_string_style_t::STRING_STYLE_URL,
|
|
|
|
EscapeStringStyle::Var => ffi::escape_string_style_t::STRING_STYLE_VAR,
|
|
|
|
EscapeStringStyle::Regex => ffi::escape_string_style_t::STRING_STYLE_REGEX,
|
|
|
|
};
|
|
|
|
|
2023-02-24 23:44:39 +08:00
|
|
|
ffi::escape_string(c_str!(s), flags_int.into(), style).from_ffi()
|
2023-02-11 23:51:43 +08:00
|
|
|
}
|
2023-02-25 00:00:05 +08:00
|
|
|
|
|
|
|
/// Test if the string is a valid function name.
|
|
|
|
pub fn valid_func_name(name: &wstr) -> bool {
|
|
|
|
if name.is_empty() {
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
if name.char_at(0) == '-' {
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
// A function name needs to be a valid path, so no / and no NULL.
|
|
|
|
if name.find_char('/').is_some() {
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
if name.find_char('\0').is_some() {
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
true
|
|
|
|
}
|
2023-02-26 06:42:45 +08:00
|
|
|
|
2023-02-27 03:20:20 +08:00
|
|
|
pub const fn assert_send<T: Send>() {}
|
2023-02-26 06:42:45 +08:00
|
|
|
|
2023-02-27 03:20:20 +08:00
|
|
|
pub const fn assert_sync<T: Sync>() {}
|
2023-02-26 23:34:03 +08:00
|
|
|
|
|
|
|
/// Asserts that a slice is alphabetically sorted by a [`&wstr`] `name` field.
|
|
|
|
///
|
|
|
|
/// Mainly useful for static asserts/const eval.
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// This function panics if the given slice is unsorted.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// const COLORS: &[(&wstr, u32)] = &[
|
|
|
|
/// // must be in alphabetical order
|
|
|
|
/// (L!("blue"), 0x0000ff),
|
|
|
|
/// (L!("green"), 0x00ff00),
|
|
|
|
/// (L!("red"), 0xff0000),
|
|
|
|
/// ];
|
|
|
|
///
|
|
|
|
/// assert_sorted_by_name!(COLORS, 0);
|
|
|
|
/// ```
|
|
|
|
macro_rules! assert_sorted_by_name {
|
|
|
|
($slice:expr, $field:tt) => {
|
|
|
|
const _: () = {
|
|
|
|
use std::cmp::Ordering;
|
|
|
|
|
|
|
|
// ugly const eval workarounds below.
|
|
|
|
const fn cmp_slice(s1: &[char], s2: &[char]) -> Ordering {
|
|
|
|
let mut i = 0;
|
|
|
|
while i < s1.len() {
|
|
|
|
if s2.len() <= i {
|
|
|
|
return Ordering::Greater;
|
|
|
|
}
|
|
|
|
if s1[i] < s2[i] {
|
|
|
|
return Ordering::Less;
|
|
|
|
} else if s1[i] > s2[i] {
|
|
|
|
return Ordering::Greater;
|
|
|
|
}
|
|
|
|
i += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if s1.len() < s2.len() {
|
|
|
|
Ordering::Less
|
|
|
|
} else {
|
|
|
|
Ordering::Equal
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut i = 0;
|
|
|
|
let mut prev: Option<&wstr> = None;
|
|
|
|
while i < $slice.len() {
|
|
|
|
let cur = $slice[i].$field;
|
|
|
|
if let Some(prev) = prev {
|
|
|
|
assert!(
|
|
|
|
matches!(
|
|
|
|
cmp_slice(prev.as_char_slice(), cur.as_char_slice()),
|
|
|
|
Ordering::Equal | Ordering::Less
|
|
|
|
),
|
|
|
|
"array must be sorted"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
prev = Some(cur);
|
|
|
|
|
|
|
|
i += 1;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
($slice:expr) => {
|
|
|
|
assert_sorted_by_name!($slice, name);
|
|
|
|
};
|
|
|
|
}
|
2023-03-03 04:39:21 +08:00
|
|
|
mod tests {
|
|
|
|
use crate::{
|
|
|
|
common::{escape_string, EscapeStringStyle},
|
|
|
|
wchar::widestrs,
|
|
|
|
};
|
|
|
|
|
|
|
|
#[widestrs]
|
|
|
|
pub fn test_escape_string() {
|
|
|
|
let regex = |input| escape_string(input, EscapeStringStyle::Regex);
|
|
|
|
|
|
|
|
// plain text should not be needlessly escaped
|
|
|
|
assert_eq!(regex("hello world!"L), "hello world!"L);
|
|
|
|
|
|
|
|
// all the following are intended to be ultimately matched literally - even if they don't look
|
|
|
|
// like that's the intent - so we escape them.
|
|
|
|
assert_eq!(regex(".ext"L), "\\.ext"L);
|
|
|
|
assert_eq!(regex("{word}"L), "\\{word\\}"L);
|
|
|
|
assert_eq!(regex("hola-mundo"L), "hola\\-mundo"L);
|
|
|
|
assert_eq!(
|
|
|
|
regex("$17.42 is your total?"L),
|
|
|
|
"\\$17\\.42 is your total\\?"L
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
regex("not really escaped\\?"L),
|
|
|
|
"not really escaped\\\\\\?"L
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
crate::ffi_tests::add_test!("escape_string", tests::test_escape_string);
|