use std::borrow::Cow;
use std::env::var_os;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use anyhow::Result;
use reqwest::blocking::Request;
use url::Url;
pub fn unescape(text: &str, special_chars: &'static str) -> String {
let mut out = String::new();
let mut chars = text.chars();
while let Some(ch) = chars.next() {
if ch == '\\' {
match chars.next() {
Some(next) if special_chars.contains(next) => {
out.push(next);
}
Some(next) => {
out.push(ch);
out.push(next);
}
None => {
out.push(ch);
}
}
} else {
out.push(ch);
}
}
out
}
pub fn clone_request(request: &mut Request) -> Result<Request> {
if let Some(b) = request.body_mut().as_mut() {
b.buffer()?;
}
Ok(request.try_clone().unwrap()) }
pub fn test_mode() -> bool {
cfg!(test) || var_os("XH_TEST_MODE").is_some()
}
pub fn test_pretend_term() -> bool {
var_os("XH_TEST_MODE_TERM").is_some()
}
pub fn test_default_color() -> bool {
var_os("XH_TEST_MODE_COLOR").is_some()
}
#[cfg(test)]
pub fn random_string() -> String {
use rand::Rng;
rand::thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.take(10)
.map(char::from)
.collect()
}
pub fn config_dir() -> Option<PathBuf> {
if let Some(dir) = std::env::var_os("XH_CONFIG_DIR") {
Some(dir.into())
} else {
dirs::config_dir().map(|dir| dir.join("xh"))
}
}
pub fn get_home_dir() -> Option<PathBuf> {
#[cfg(target_os = "windows")]
if let Some(path) = std::env::var_os("XH_TEST_MODE_WIN_HOME_DIR") {
return Some(PathBuf::from(path));
}
dirs::home_dir()
}
pub fn expand_tilde(path: impl AsRef<Path>) -> PathBuf {
if let Ok(path) = path.as_ref().strip_prefix("~") {
let mut expanded_path = PathBuf::new();
expanded_path.push(get_home_dir().unwrap_or_else(|| "~".into()));
expanded_path.push(path);
expanded_path
} else {
path.as_ref().into()
}
}
pub fn url_with_query(mut url: Url, query: &[(&str, Cow<str>)]) -> Url {
if !query.is_empty() {
let mut pairs = url.query_pairs_mut();
for (name, value) in query {
pairs.append_pair(name, value);
}
}
url
}
#[macro_export]
macro_rules! vec_of_strings {
($($str:expr),*) => ({
vec![$(String::from($str),)*] as Vec<String>
});
}
#[macro_export]
macro_rules! regex {
($name:ident = $($re:expr)+) => {
static $name: once_cell::sync::Lazy<regex::Regex> =
once_cell::sync::Lazy::new(|| regex::Regex::new(concat!($($re,)+)).unwrap());
};
($($re:expr)+) => {{
static RE: once_cell::sync::Lazy<regex::Regex> =
once_cell::sync::Lazy::new(|| regex::Regex::new(concat!($($re,)+)).unwrap());
&RE
}};
}
pub const BUFFER_SIZE: usize = 128 * 1024;
pub fn copy_largebuf(
reader: &mut impl io::Read,
writer: &mut impl Write,
flush: bool,
) -> io::Result<()> {
let mut buf = vec![0; BUFFER_SIZE];
loop {
match reader.read(&mut buf) {
Ok(0) => return Ok(()),
Ok(len) => {
writer.write_all(&buf[..len])?;
if flush {
writer.flush()?;
}
}
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}
}
}