use std::{ env, fs::{ self, File }, io::{ Lines, BufReader }, path::{ Path, PathBuf }, }; use crate::{ iff, util_os, util_io, util_msg, new_box_ioerror, XResult, }; pub struct JoinFilesReader { files: Vec, file_ptr: usize, file_lines: Option>>>, } fn open_file_as_lines(f: &str) -> XResult>> { let f = File::open(&f)?; let br = BufReader::new(f); use std::io::BufRead; Ok(br.lines()) } impl JoinFilesReader { pub fn new(fns: &[&str]) -> XResult { let mut files: Vec = vec![]; for f in fns { files.push(f.to_string()); } let file_ptr = 0; let mut file_lines = None; if !files.is_empty() { file_lines = Some(Box::new(open_file_as_lines(&files[0])?)); } Ok(Self { files, file_ptr, file_lines, }) } } impl Iterator for JoinFilesReader { type Item = XResult; fn next(&mut self) -> Option { loop { match self.file_lines { Some(ref mut ln) => match ln.next() { Some(r) => return Some(r.map_err(|e| e.into())), None => { self.file_ptr += 1; self.file_lines = None; if self.file_ptr >= self.files.len() { return None; } else { // if open file failed, will not continue read files self.file_lines = Some(Box::new(match open_file_as_lines(&self.files[self.file_ptr]) { Ok(ln) => ln, Err(e) => return Some(Err(e)), })); } }, }, None => return None, } if self.file_ptr >= self.files.len() { return None; } } } } pub fn find_parents_exists_file(file: &str) -> Option { let mut loop_count = 0_usize; match PathBuf::from(".").canonicalize() { Err(_) => None, Ok(mut path) => loop { loop_count += 1; if loop_count > 1000 { util_msg::print_error("Loop count more than 1000!"); return None; } if path.join(file).is_file() { return Some(path); } if !path.pop() { return None; } } } } pub fn find_parents_exists_dir(dir: &str) -> Option { let mut loop_count = 0_usize; match PathBuf::from(".").canonicalize() { Err(_) => None, Ok(mut path) => loop { loop_count += 1; if loop_count > 1000 { util_msg::print_error("Loop count more than 1000!"); return None; } if path.join(dir).is_dir() { return Some(path); } if !path.pop() { return None; } } } } #[cfg(feature = "use_serde")] pub fn read_json_config(config: Option, files: &[String]) -> XResult> { let config_path_buf_opt = read_config(config, files); match config_path_buf_opt { None => Ok(None), Some(config_path_buf) => { information!("Read config: {}", config_path_buf); let config_content = fs::read_to_string(config_path_buf)?; Ok(Some((config_path_buf, serde_json::from_str(&config_content)?))) } } } pub fn read_config(config: Option, files: &[String]) -> Option { match config { Some(config_str) => Some(PathBuf::from(config_str)), None => locate_file(files), } } pub fn locate_file(files: &[String]) -> Option { for f in files { match PathBuf::from(&resolve_file_path(f)) { pb if pb.is_file() => return Some(pb), _ => (), } } None } pub fn get_home_str() -> Option { iff!(util_os::is_macos_or_linux(), env::var("HOME").ok(), None) } pub fn resolve_file_path(path: &str) -> String { let home_path = match get_home_str() { Some(p) => p, None => return path.to_owned(), }; match path { "~" => home_path, p if p.starts_with("~/") => home_path + &path.chars().skip(1).collect::(), p => p.to_owned(), } } pub fn get_home_path() -> Option { Some(PathBuf::from(get_home_str()?)) } pub fn get_absolute_path(path: &str) -> Option { match path { "~" => Some(PathBuf::from(get_home_str()?)), path if path.starts_with("~/") => Some(PathBuf::from(&format!("{}/{}", get_home_str()?, &path[2..]))), path => fs::canonicalize(path).ok(), } } pub fn read_file_content(file: &str) -> XResult { match get_absolute_path(file) { None => Err(new_box_ioerror(&format!("File not found: {}", file))), Some(p) => util_io::read_to_string(&mut fs::File::open(p)?), } } pub fn is_symlink(path: &Path) -> bool { path.symlink_metadata().map(|meta| meta.file_type().is_symlink()).unwrap_or(false) } pub fn walk_dir(dir: &Path, func_walk_error: &FError, func_process_file: &FProcess, func_filter_dir: &FFilter) -> XResult<()> where FError: Fn(&Path, Box), FProcess: Fn(&Path), FFilter: Fn(&Path) -> bool { walk_dir_with_depth_check(&mut 0u32, dir, func_walk_error, func_process_file, func_filter_dir) } fn walk_dir_with_depth_check(depth: &mut u32, dir: &Path, func_walk_error: &FError, func_process_file: &FProcess, func_filter_dir: &FFilter) -> XResult<()> where FError: Fn(&Path, Box), FProcess: Fn(&Path), FFilter: Fn(&Path) -> bool { if *depth > 100u32 { return Err(new_box_ioerror(&format!("Depth exceed, depth: {}, path: {:?}", *depth, dir))); } let read_dir = match dir.read_dir() { Ok(rd) => rd, Err(err) => { func_walk_error(&dir, Box::new(err)); return Ok(()); }, }; for dir_entry_item in read_dir { let dir_entry = match dir_entry_item { Ok(item) => item, Err(err) => { func_walk_error(&dir, Box::new(err)); continue; // Ok? }, }; let path_buf = dir_entry.path(); let sub_dir = path_buf.as_path(); if sub_dir.is_file() { func_process_file(&sub_dir); } else if sub_dir.is_dir() && func_filter_dir(&sub_dir) { *depth += 1; if let Err(err) = walk_dir_with_depth_check(depth, &sub_dir, func_walk_error, func_process_file, func_filter_dir) { func_walk_error(&sub_dir, err); } *depth -= 1; } // should process else ? not file, dir } Ok(()) }