use log::{debug, warn};
use memchr::memchr;
use rand::Rng;
use std::io::Write;
pub fn chunks_to_lines<'a, I, E>(mut chunks: I) -> impl Iterator<Item = Result<Vec<u8>, E>>
where
I: Iterator<Item = Result<&'a [u8], E>> + 'a,
E: std::fmt::Debug,
{
let mut tail: Option<Vec<u8>> = None;
std::iter::from_fn(move || -> Option<Result<Vec<u8>, E>> {
loop {
if let Some(mut chunk) = tail.take() {
if let Some(newline) = memchr(b'\n', &chunk) {
if newline == chunk.len() - 1 {
assert!(!chunk.is_empty());
return Some(Ok(chunk));
} else {
let line = chunk[..=newline].to_vec();
assert!(!chunk.is_empty());
tail = Some(chunk[newline + 1..].to_vec());
return Some(Ok(line));
}
} else {
if let Some(next_chunk) = chunks.next() {
if let Err(e) = next_chunk {
return Some(Err(e));
}
chunk.extend_from_slice(next_chunk.unwrap());
} else {
assert!(!chunk.is_empty());
return Some(Ok(chunk));
}
if !chunk.is_empty() {
tail = Some(chunk);
}
}
} else if let Some(next_chunk) = chunks.next() {
if let Err(e) = next_chunk {
return Some(Err(e));
}
let next_chunk = next_chunk.unwrap();
if !next_chunk.is_empty() {
tail = Some(next_chunk.to_vec());
}
} else {
return None;
}
}
})
}
pub fn set_or_unset_env(
env_variable: &str,
value: Option<&str>,
) -> Result<Option<String>, std::env::VarError> {
let orig_val = std::env::var(env_variable);
let ret: Option<String>;
if let Err(std::env::VarError::NotPresent) = orig_val {
ret = None;
if let Some(value) = value {
std::env::set_var(env_variable, value);
}
} else if let Err(e) = orig_val {
return Err(e);
} else {
assert!(orig_val.is_ok());
ret = Some(orig_val.unwrap());
match value {
None => std::env::remove_var(env_variable),
Some(val) => std::env::set_var(env_variable, val),
}
}
Ok(ret)
}
const ALNUM: &str = "0123456789abcdefghijklmnopqrstuvwxyz";
pub fn rand_chars(num: usize) -> String {
let mut rng = rand::thread_rng();
let mut s = String::new();
for _ in 0..num {
let raw_byte = rng.gen_range(0..256);
s.push(ALNUM.chars().nth(raw_byte % 36).unwrap());
}
s
}
#[cfg(unix)]
use nix::sys::stat::{umask, Mode};
#[cfg(unix)]
pub fn get_umask() -> Mode {
let mask = umask(Mode::empty());
umask(mask);
mask
}
#[derive(PartialEq)]
pub enum Kind {
File,
Directory,
Symlink,
TreeReference,
}
impl Kind {
pub fn marker(&self) -> &'static str {
match self {
Kind::File => "",
Kind::Directory => "/",
Kind::Symlink => "@",
Kind::TreeReference => "+",
}
}
pub fn to_string(&self) -> &'static str {
match self {
Kind::File => "file",
Kind::Directory => "directory",
Kind::Symlink => "symlink",
Kind::TreeReference => "tree-reference",
}
}
}
pub fn get_host_name() -> std::io::Result<String> {
hostname::get().map(|h| h.to_string_lossy().to_string())
}
pub fn local_concurrency(use_cache: bool) -> usize {
unsafe {
static mut _CACHED_LOCAL_CONCURRENCY: Option<usize> = None;
if use_cache {
if let Some(concurrency) = _CACHED_LOCAL_CONCURRENCY {
return concurrency;
}
}
let concurrency = std::env::var("BRZ_CONCURRENCY")
.map(|s| s.parse::<usize>().unwrap_or(1))
.unwrap_or_else(|_| num_cpus::get());
if use_cache {
_CACHED_LOCAL_CONCURRENCY = Some(concurrency);
}
concurrency
}
}
pub fn pumpfile(
mut reader: impl std::io::Read,
mut writer: impl std::io::Write,
num_bytes: Option<u64>,
) -> std::io::Result<u64> {
Ok(if let Some(num_bytes) = num_bytes {
std::io::copy(&mut reader.take(num_bytes), &mut writer)?
} else {
std::io::copy(&mut reader, &mut writer)?
})
}
pub fn pump_string_file(
data: &[u8],
mut file_handle: impl std::io::Write,
segment_size: Option<usize>,
) -> std::io::Result<()> {
let segment_size = segment_size.unwrap_or(5_242_880); let chunks = data.chunks(segment_size);
for chunk in chunks {
file_handle.write_all(chunk)?;
}
Ok(())
}
pub fn contains_whitespace(s: &str) -> bool {
let ws = " \t\n\r\u{000B}\u{000C}";
for ch in ws.chars() {
if s.contains(ch) {
return true;
}
}
false
}
pub fn contains_whitespace_bytes(s: &[u8]) -> bool {
let ws = b" \t\n\r\x0C\x0B";
for ch in ws {
if s.contains(ch) {
return true;
}
}
false
}
pub fn contains_linebreaks(s: &str) -> bool {
for ch in "\n\r\x0C".chars() {
if s.contains(ch) {
return true;
}
}
false
}
pub fn get_home_dir() -> Option<std::path::PathBuf> {
dirs::home_dir()
}
fn _get_user_encoding() -> Option<String> {
unsafe {
let codeset = nix::libc::nl_langinfo(nix::libc::CODESET);
if codeset.is_null() {
return None;
}
let codeset_str = std::ffi::CStr::from_ptr(codeset);
Some(codeset_str.to_string_lossy().to_string())
}
}
pub fn get_user_encoding() -> Option<String> {
let encoding = _get_user_encoding()?;
match encoding_rs::Encoding::for_label(encoding.as_bytes()) {
Some(enc) => Some(enc.name().to_string()),
_ => {
warn!(
"brz: warning: unknown encoding {}. Defaulting to ASCII.",
encoding
);
Some("ASCII".to_string())
}
}
}
pub mod file;
pub mod iterablefile;
pub mod path;
pub mod sha;
pub mod textfile;
pub mod time;
#[cfg(unix)]
#[path = "mounts-unix.rs"]
pub mod mounts;
#[cfg(windows)]
#[path = "mounts-win32.rs"]
pub mod mounts;
#[cfg(test)]
mod tests;
pub mod terminal;
#[cfg(unix)]
pub fn is_local_pid_dead(pid: i32) -> bool {
use nix::sys::signal::kill;
use nix::unistd::Pid;
match kill(Pid::from_raw(pid), None) {
Ok(_) => false, Err(nix::Error::ESRCH) => true, Err(nix::Error::EPERM) => false, Err(err) => {
debug!("kill({:?}, 0) failed: {}", pid, err);
false }
}
}
pub fn get_user_name() -> String {
for name in &["LOGNAME", "USER", "LNAME", "USERNAME"] {
if let Ok(user) = std::env::var(name) {
return user;
}
}
whoami::username()
}