From 36ee956d004f815e12fa566cbae6ec39be50bfe0 Mon Sep 17 00:00:00 2001 From: Ben Sadeh <701096+bensadeh@users.noreply.github.com> Date: Sun, 13 Oct 2024 09:56:21 +0200 Subject: [PATCH] Refactor config to use PathBuf instead of String --- CHANGELOG.md | 1 + src/config/mod.rs | 180 ++++++++++++++++----------------------- src/io/reader/linemux.rs | 21 +++-- src/main.rs | 3 +- 4 files changed, 90 insertions(+), 115 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f10cc50..bf2af6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 4.1.0 - Updated shell completion commands names +- Use PathBuf instead of String for file paths ## 4.0.0 diff --git a/src/config/mod.rs b/src/config/mod.rs index 34fc6a6..6486c76 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -2,14 +2,14 @@ use crate::cli::Cli; use color_eyre::owo_colors::OwoColorize; use std::fs; use std::fs::{DirEntry, File}; -use std::io::{stdin, BufRead, IsTerminal}; -use std::path::Path; -use std::process::exit; +use std::io::{self, stdin, IsTerminal, Read}; +use std::path::{Path, PathBuf}; +use thiserror::Error; enum InputType { Stdin, Command(String), - FileOrFolder(String), + FileOrFolder(PathBuf), } enum PathType { @@ -17,20 +17,7 @@ enum PathType { Folder, } -pub fn create_config_or_exit_early(args: &Cli) -> Config { - match create_config(args) { - Ok(c) => c, - Err(e) => { - match e.exit_code { - OK => println!("{}", e.message), - _ => eprintln!("{}", e.message), - } - exit(e.exit_code); - } - } -} - -fn create_config(args: &Cli) -> Result { +pub fn create_config(args: &Cli) -> Result { let has_data_from_stdin = !stdin().is_terminal(); validate_input( @@ -58,44 +45,33 @@ fn validate_input( has_data_from_stdin: bool, has_file_or_folder_input: bool, has_follow_command_input: bool, -) -> Result<(), Error> { +) -> Result<(), ConfigError> { if !has_data_from_stdin && !has_file_or_folder_input && !has_follow_command_input { - return Err(Error { - exit_code: OK, - message: format!("Missing filename ({} for help)", "tspin --help".magenta()), - }); + return Err(ConfigError::MissingFilename("tspin --help".magenta().to_string())); } if has_file_or_folder_input && has_follow_command_input { - return Err(Error { - exit_code: MISUSE_SHELL_BUILTIN, - message: format!("Cannot read from both file and {}", "--listen-command".magenta()), - }); + return Err(ConfigError::CannotReadBothFileAndListenCommand( + "--listen-command".magenta().to_string(), + )); } Ok(()) } -fn determine_input_type(args: &Cli, has_data_from_stdin: bool) -> Result { +fn determine_input_type(args: &Cli, has_data_from_stdin: bool) -> Result { if has_data_from_stdin { - return Ok(InputType::Stdin); - } - - if let Some(command) = &args.listen_command { - return Ok(InputType::Command(command.clone())); - } - - if let Some(path) = &args.file_or_folder_path { - return Ok(InputType::FileOrFolder(path.clone())); + Ok(InputType::Stdin) + } else if let Some(command) = &args.listen_command { + Ok(InputType::Command(command.clone())) + } else if let Some(path) = &args.file_or_folder_path { + Ok(InputType::FileOrFolder(PathBuf::from(path))) + } else { + Err(ConfigError::CouldNotDetermineInputType) } - - Err(Error { - exit_code: GENERAL_ERROR, - message: "Could not determine input type".to_string(), - }) } -fn get_input(input_type: InputType) -> Result { +fn get_input(input_type: InputType) -> Result { match input_type { InputType::Stdin => Ok(Input::Stdin), InputType::Command(cmd) => Ok(Input::Command(cmd)), @@ -105,24 +81,22 @@ fn get_input(input_type: InputType) -> Result { const fn get_output(has_data_from_stdin: bool, is_print_flag: bool, suppress_output: bool) -> Output { if suppress_output { - return Output::Suppress; - } - - if has_data_from_stdin || is_print_flag { - return Output::Stdout; + Output::Suppress + } else if has_data_from_stdin || is_print_flag { + Output::Stdout + } else { + Output::TempFile } - - Output::TempFile } -fn determine_input(path: String) -> Result { +fn determine_input(path: PathBuf) -> Result { match check_path_type(&path)? { PathType::File => { - let line_count = count_lines(&path); + let line_count = count_lines(&path)?; Ok(Input::File(PathAndLineCount { path, line_count })) } PathType::Folder => { - let mut paths = list_files_in_directory(Path::new(&path))?; + let mut paths = list_files_in_directory(&path)?; paths.sort(); Ok(Input::Folder(FolderInfo { @@ -133,52 +107,36 @@ fn determine_input(path: String) -> Result { } } -fn check_path_type>(path: P) -> Result { - let metadata = fs::metadata(path.as_ref()).map_err(|_| Error { - exit_code: GENERAL_ERROR, - message: format!("{}: No such file or directory", path.as_ref().display().red()), - })?; +fn check_path_type(path: &Path) -> Result { + let metadata = fs::metadata(path).map_err(|_| ConfigError::NoSuchFileOrDirectory(path.display().to_string()))?; if metadata.is_file() { Ok(PathType::File) } else if metadata.is_dir() { Ok(PathType::Folder) } else { - Err(Error { - exit_code: GENERAL_ERROR, - message: "Path is neither a file nor a directory".into(), - }) + Err(ConfigError::PathNotFileNorDirectory) } } const fn should_follow(follow: bool, has_follow_command: bool, input: &Input) -> bool { - if has_follow_command { - return true; - } - - if matches!(input, Input::Folder(_)) { - return true; + if has_follow_command || matches!(input, Input::Folder(_)) { + true + } else { + follow } - - follow } -fn list_files_in_directory(path: &Path) -> Result, Error> { +fn list_files_in_directory(path: &Path) -> Result, ConfigError> { if !path.is_dir() { - return Err(Error { - exit_code: GENERAL_ERROR, - message: "Path is not a directory".into(), - }); + return Err(ConfigError::PathNotDirectory); } fs::read_dir(path) - .map_err(|_| Error { - exit_code: GENERAL_ERROR, - message: "Unable to read directory".into(), - })? + .map_err(|_| ConfigError::UnableToReadDirectory)? .filter_map(Result::ok) .filter(is_normal_file) - .map(entry_to_string) + .map(|entry| Ok(entry.path())) .collect() } @@ -192,31 +150,41 @@ fn is_normal_file(entry: &DirEntry) -> bool { .unwrap_or(false) } -fn entry_to_string(entry: DirEntry) -> Result { - entry - .path() - .to_str() - .ok_or(Error { - exit_code: GENERAL_ERROR, - message: "Non-UTF8 filename".into(), - }) - .map(|s| s.to_string()) -} - -fn count_lines>(file_path: P) -> usize { - let file = File::open(file_path).expect("Could not open file"); - let reader = std::io::BufReader::new(file); - - reader.lines().count() -} +fn count_lines>(file_path: P) -> Result { + let file = File::open(file_path)?; + let mut reader = io::BufReader::new(file); -pub const OK: i32 = 0; -pub const GENERAL_ERROR: i32 = 1; -pub const MISUSE_SHELL_BUILTIN: i32 = 2; + let mut count = 0; + let mut buffer = [0; 8192]; // 8 KB buffer + loop { + let bytes_read = reader.read(&mut buffer)?; + if bytes_read == 0 { + break; + } + count += buffer[..bytes_read].iter().filter(|&&c| c == b'\n').count(); + } -pub struct Error { - pub exit_code: i32, - pub message: String, + Ok(count) +} + +#[derive(Debug, Error)] +pub enum ConfigError { + #[error("Missing filename ({0} for help)")] + MissingFilename(String), + #[error("Cannot read from both file and {0}")] + CannotReadBothFileAndListenCommand(String), + #[error("Could not determine input type")] + CouldNotDetermineInputType, + #[error("{0}: No such file or directory")] + NoSuchFileOrDirectory(String), + #[error("Path is neither a file nor a directory")] + PathNotFileNorDirectory, + #[error("Path is not a directory")] + PathNotDirectory, + #[error("Unable to read directory")] + UnableToReadDirectory, + #[error("I/O Error: {0}")] + Io(#[from] io::Error), } pub struct Config { @@ -227,13 +195,13 @@ pub struct Config { } pub struct PathAndLineCount { - pub path: String, + pub path: PathBuf, pub line_count: usize, } pub struct FolderInfo { - pub folder_name: String, - pub file_paths: Vec, + pub folder_name: PathBuf, + pub file_paths: Vec, } pub enum Input { diff --git a/src/io/reader/linemux.rs b/src/io/reader/linemux.rs index 4f4d89e..cd60bf7 100644 --- a/src/io/reader/linemux.rs +++ b/src/io/reader/linemux.rs @@ -4,6 +4,7 @@ use color_eyre::owo_colors::OwoColorize; use linemux::MuxedLines; use std::cmp::min; use std::io; +use std::path::PathBuf; use terminal_size::{terminal_size, Height, Width}; use tokio::sync::oneshot::Sender; @@ -19,7 +20,7 @@ pub struct Linemux { impl Linemux { pub async fn get_reader_single( - file_path: String, + file_path: PathBuf, number_of_lines: usize, start_at_end: bool, mut reached_eof_tx: Option>, @@ -55,12 +56,10 @@ impl Linemux { } pub async fn get_reader_multiple( - folder_name: String, - file_paths: Vec, + folder: PathBuf, + file_paths: Vec, mut reached_eof_tx: Option>, ) -> Box { - use std::path::Path; - if let Some(reached_eof) = reached_eof_tx.take() { reached_eof .send(()) @@ -73,10 +72,11 @@ impl Linemux { .iter() .enumerate() .map(|(index, path)| { - let file_name = Path::new(path) + let file_name = path .file_name() - .and_then(|name| name.to_str()) - .unwrap_or(path); + .expect("Could not get file name") + .to_str() + .expect("Could not convert file name to string"); if index == file_paths.len() - 1 { format!(" └─ {}", file_name.bold()) @@ -87,6 +87,11 @@ impl Linemux { .collect::>() .join("\n"); + let folder_name = folder + .file_name() + .expect("Could not get folder name") + .to_str() + .expect("Could not convert folder name to string"); let separator = get_separator(); let dimmed_separator = separator.dimmed(); let startup_message = format!( diff --git a/src/main.rs b/src/main.rs index 1376a41..54f3296 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,7 +26,8 @@ async fn main() -> Result<()> { let cli = Cli::parse(); - let config = config::create_config_or_exit_early(&cli); + // let config = config::create_config_or_exit_early(&cli); + let config = config::create_config(&cli)?; let highlighter_groups = groups::get_highlighter_groups(&cli.enable, &cli.disable)?;