From 0109bf6c6fe3789abc6a65dc72c0f1a7f4d590fa Mon Sep 17 00:00:00 2001 From: Steppy Date: Mon, 3 Feb 2025 16:54:42 +0100 Subject: [PATCH 1/7] Add TODO for bug --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index 24fc799..0e3150c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -299,6 +299,7 @@ fn main() -> Result<(), String> { cmd } ServerAddress::Localhost => { + //TODO don't use shell command on localhost, this will fail on windows let mut cmd = ShellCmd::new("ls"); cmd.arg(&working_directory); cmd From faf4e47cac1a1c93bc15429c6890ec930b23f126 Mon Sep 17 00:00:00 2001 From: Steppy Date: Mon, 3 Feb 2025 17:55:34 +0100 Subject: [PATCH 2/7] Don't use shell command on local machine --- src/file.rs | 4 ++- src/main.rs | 99 +++++++++++++++++++++++++++++++++-------------------- 2 files changed, 65 insertions(+), 38 deletions(-) diff --git a/src/file.rs b/src/file.rs index 986a110..2e9d9db 100644 --- a/src/file.rs +++ b/src/file.rs @@ -2,6 +2,7 @@ use std::error::Error; use std::fmt::{Display, Formatter}; use std::path::PathBuf; +//TODO this whole structure should probably use OsString instead of String #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct FileNameInfo { pub name: String, @@ -82,6 +83,7 @@ impl Display for FileInfoError { impl Error for FileInfoError {} +//TODO this structure should probably work with OsString instead of String #[derive(Debug, Clone)] pub struct FileMatcher { name: String, @@ -108,7 +110,7 @@ impl FileMatcher { ..self } } - + pub fn matches(&self, file_name: &str) -> bool { file_name.starts_with(&self.name) && self diff --git a/src/main.rs b/src/main.rs index 0e3150c..ff06a4a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ use clap::{Parser, Subcommand, ValueEnum}; use lazy_regex::{lazy_regex, Lazy, Regex}; use server::{Server, ServerReference}; use std::cell::LazyCell; -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; use std::hash::Hash; use std::io::Write; use std::iter::once; @@ -292,23 +292,35 @@ fn main() -> Result<(), String> { Ok(ServerActions { server, actions: { - let mut ls_command = match &server.address { - ServerAddress::Ssh { ssh_address } => { - let mut cmd = ShellCmd::new("ssh"); - cmd.arg(ssh_address).arg(osf!("ls ") + &working_directory); - cmd - } - ServerAddress::Localhost => { - //TODO don't use shell command on localhost, this will fail on windows - let mut cmd = ShellCmd::new("ls"); - cmd.arg(&working_directory); - cmd - } + let present_file_names: Vec = match &server.address { + ServerAddress::Ssh { ssh_address } => ShellCmd::new("ls") + .arg(ssh_address) + .arg(osf!("ls ") + &working_directory) + .collect_output() + .map_err(|e| { + format!( + "Failed to query present files on server {}: {e}", + server.get_name() + ) + })? + .stdout + .split(|&b| b == b'\n') + .map(|bytes| OsStr::from_bytes(bytes).to_os_string()) + .collect(), + ServerAddress::Localhost => fs::read_dir(&working_directory) + .map_err(|e| format!("Failed to get files in working directory: {e}"))? + .map(|entry| entry.map_err(|e| format!("Failed to access directory entry: {e}"))) + .collect::, _>>()? + .into_iter() + .filter_map(|entry| { + if entry.path().is_file() { + Some(entry.file_name()) + } else { + None + } + }) + .collect(), }; - let ls_output = ls_command - .collect_output() - .map_err(|e| format!("failed to query files: {e}"))?; - let ls_output = String::from_utf8_lossy(&ls_output.stdout); file_details .iter() @@ -319,43 +331,56 @@ fn main() -> Result<(), String> { file_matcher = file_matcher.and_extension(extension); } - let file_name = file_name_info.to_full_file_name(); + let file_name = OsString::from(file_name_info.to_full_file_name()); let add_action = FileAction::new(file, Action::Add).expect("path points to file"); - let mut ls_lines = ls_output.lines(); - - if pure && ls_lines.clone().any(|file| file == file_name) { - log!(logger, debug, "file is already present on {}: {}", server.get_name(), file_name); + if pure && present_file_names.iter().any(|file| *file == file_name) { + log!( + logger, + debug, + "file is already present on {}: {}", + server.get_name(), + file_name.to_string_lossy() + ); return vec![]; //ignore that file, since it is already present } match old_version_policy { OldVersionPolicy::Ignore => { - if !ls_lines.any(|file| file == file_name) { + if !present_file_names.iter().any(|file| *file == file_name) { vec![add_action] //file doesn't exist yet } else { vec![FileAction::new(&file_name, Action::Replace) .expect("path points to file")] } } - OldVersionPolicy::Archive => ls_lines - .filter(|file| file_matcher.matches(file)) - .map(|file| { - FileAction::new( - file, - Action::rename(format!("{file}{}", file.chars().last().unwrap_or('1'))), - ) - .expect("path points to file") - }) - .chain(once(add_action)) - .collect(), + OldVersionPolicy::Archive => { + //TODO avoid lossy match + present_file_names + .iter() + .filter(|file| file_matcher.matches(&file.to_string_lossy())) + .map(|file| { + FileAction::new( + file, + Action::rename(format!( + "{}{}", + file.to_string_lossy(), + file.to_string_lossy().chars().last().unwrap_or('1') + )), + ) + .expect("path points to file") + }) + .chain(once(add_action)) + .collect() + }, OldVersionPolicy::Delete => { - let mut actions = ls_lines - .filter(|file| file_matcher.matches(file)) + //TODO avoid lossy match + let mut actions = present_file_names.iter() + .filter(|file| file_matcher.matches(&file.to_string_lossy())) .map(|file| { //special case -> file has the same name as current file, then we just need to replace it - if file == file_name { + if *file == file_name { FileAction::new(file, Action::Replace).expect("path points to file") } else { FileAction::new(file, Action::Delete).expect("path points to file") From d1924aa758c53f1de37ceef3f90dc69be90779d7 Mon Sep 17 00:00:00 2001 From: Steppy Date: Mon, 3 Feb 2025 18:01:54 +0100 Subject: [PATCH 3/7] Refactor FileMatcher to use OsString --- src/file.rs | 22 ++++++++++++---------- src/main.rs | 4 ++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/file.rs b/src/file.rs index 2e9d9db..6de8cd1 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,5 +1,7 @@ use std::error::Error; +use std::ffi::{OsStr, OsString}; use std::fmt::{Display, Formatter}; +use std::os::unix::ffi::OsStrExt; use std::path::PathBuf; //TODO this whole structure should probably use OsString instead of String @@ -83,40 +85,40 @@ impl Display for FileInfoError { impl Error for FileInfoError {} -//TODO this structure should probably work with OsString instead of String #[derive(Debug, Clone)] pub struct FileMatcher { - name: String, - extension: Option, + name: OsString, + extension: Option, } impl FileMatcher { pub fn from(name: S) -> Self where - S: ToString, + S: AsRef, { Self { - name: name.to_string(), + name: name.as_ref().to_owned(), extension: None, } } pub fn and_extension(self, extension: S) -> Self where - S: ToString, + S: AsRef, { Self { - extension: Some(extension.to_string()), + extension: Some(extension.as_ref().to_owned()), ..self } } - pub fn matches(&self, file_name: &str) -> bool { - file_name.starts_with(&self.name) + pub fn matches(&self, file_name: S) -> bool where S: AsRef { + let file_name = file_name.as_ref(); + file_name.as_bytes().starts_with(self.name.as_bytes()) && self .extension .as_ref() - .is_none_or(|extension| file_name.ends_with(extension)) + .is_none_or(|extension| file_name.as_bytes().ends_with(extension.as_bytes())) } } diff --git a/src/main.rs b/src/main.rs index ff06a4a..585af87 100644 --- a/src/main.rs +++ b/src/main.rs @@ -359,7 +359,7 @@ fn main() -> Result<(), String> { //TODO avoid lossy match present_file_names .iter() - .filter(|file| file_matcher.matches(&file.to_string_lossy())) + .filter(|file| file_matcher.matches(file)) .map(|file| { FileAction::new( file, @@ -377,7 +377,7 @@ fn main() -> Result<(), String> { OldVersionPolicy::Delete => { //TODO avoid lossy match let mut actions = present_file_names.iter() - .filter(|file| file_matcher.matches(&file.to_string_lossy())) + .filter(|file| file_matcher.matches(file)) .map(|file| { //special case -> file has the same name as current file, then we just need to replace it if *file == file_name { From c143b29a3dcc787d0a1bda3a289c1b9b2c2fbdbb Mon Sep 17 00:00:00 2001 From: Steppy Date: Mon, 3 Feb 2025 18:06:57 +0100 Subject: [PATCH 4/7] Avoid lossy strings when renaming and matching files --- src/action.rs | 9 +++++++-- src/main.rs | 43 ++++++++++++++++++++++--------------------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/action.rs b/src/action.rs index c493fd0..af879f1 100644 --- a/src/action.rs +++ b/src/action.rs @@ -72,7 +72,12 @@ pub enum Action { } impl Action { - pub fn rename(new_name: S) -> Self where S: AsRef { - Self::Rename {new_name: new_name.as_ref().to_owned()} + pub fn rename(new_name: S) -> Self + where + S: AsRef, + { + Self::Rename { + new_name: new_name.as_ref().to_owned(), + } } } diff --git a/src/main.rs b/src/main.rs index 585af87..75fe835 100644 --- a/src/main.rs +++ b/src/main.rs @@ -355,28 +355,29 @@ fn main() -> Result<(), String> { .expect("path points to file")] } } - OldVersionPolicy::Archive => { - //TODO avoid lossy match - present_file_names - .iter() - .filter(|file| file_matcher.matches(file)) - .map(|file| { - FileAction::new( - file, - Action::rename(format!( - "{}{}", - file.to_string_lossy(), - file.to_string_lossy().chars().last().unwrap_or('1') - )), - ) - .expect("path points to file") - }) - .chain(once(add_action)) - .collect() - }, + OldVersionPolicy::Archive => present_file_names + .iter() + .filter(|file| file_matcher.matches(file)) + .map(|file| { + FileAction::new( + file, + Action::rename( + osf!(file) + + file + .to_string_lossy() + .chars() + .last() + .unwrap_or('1') + .to_string(), + ), + ) + .expect("path points to file") + }) + .chain(once(add_action)) + .collect(), OldVersionPolicy::Delete => { - //TODO avoid lossy match - let mut actions = present_file_names.iter() + let mut actions = present_file_names + .iter() .filter(|file| file_matcher.matches(file)) .map(|file| { //special case -> file has the same name as current file, then we just need to replace it From ad710581b3698d1b2634a9098635bc6d9d05ea49 Mon Sep 17 00:00:00 2001 From: Steppy Date: Mon, 3 Feb 2025 22:11:25 +0100 Subject: [PATCH 5/7] Add os_string_extension.rs and refactor FileNameInfo --- src/file.rs | 87 ++++++++++++++++++++------------------ src/main.rs | 68 +++++++++++++++++++---------- src/os_string_extension.rs | 82 +++++++++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+), 63 deletions(-) create mode 100644 src/os_string_extension.rs diff --git a/src/file.rs b/src/file.rs index 6de8cd1..4fbc969 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,20 +1,32 @@ +use crate::os_string_extension::OsStrExtension; +use crate::osf; use std::error::Error; use std::ffi::{OsStr, OsString}; use std::fmt::{Display, Formatter}; use std::os::unix::ffi::OsStrExt; use std::path::PathBuf; -//TODO this whole structure should probably use OsString instead of String #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct FileNameInfo { - pub name: String, - pub version: Option, - pub extension: Option, + pub name: OsString, + pub version: Option, + pub extension: Option, } impl FileNameInfo { - pub fn to_full_file_name(&self) -> String { - self.to_string() + pub fn to_full_file_name(&self) -> OsString { + (osf!(&self.name) + + self + .version + .as_ref() + .map(|v| osf!("-") + v) + .unwrap_or_default() + + self + .extension + .as_ref() + .map(|extension| osf!(".") + extension) + .unwrap_or_default()) + .build() } } @@ -23,25 +35,27 @@ impl TryFrom for FileNameInfo { fn try_from(file: PathBuf) -> Result { let file_name = file.file_name().ok_or(FileInfoError::NotAFile)?; - let file_name = file_name - .to_str() - .ok_or(FileInfoError::InvalidCharactersInFileName)?; - let (file_name_without_version, extension) = - match file_name.rsplit_once('.').filter(|(_, ending)| { - ending.parse::().is_err() //there are usually no file extensions which are just a number, but rather versions + let (file_name_without_extension, extension) = + match file_name.rsplit_once(b'.').filter(|(_, ending)| { + //there are usually no file extensions which are just a number, but rather versions + // -> don't use split if ending is number + match ending.to_str() { + Some(ending) if ending.parse::().is_ok() => false, + _ => true, + } }) { - Some((name, ending)) => (name, Some(ending.to_string())), + Some((name, ending)) => (name, Some(ending.to_os_string())), None => (file_name, None), }; - let (name, version) = match file_name_without_version.split_once('-') { - Some((name, version)) => (name, Some(version.to_string())), - None => (file_name_without_version, None), + let (name, version) = match file_name_without_extension.split_once(b'-') { + Some((name, version)) => (name, Some(version.to_os_string())), + None => (file_name_without_extension, None), }; Ok(Self { - name: name.to_string(), + name: name.to_os_string(), version, extension, }) @@ -52,18 +66,8 @@ impl Display for FileNameInfo { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, - "{}{}{}", - self.name, - self - .version - .as_ref() - .map(|v| format!("-{v}")) - .unwrap_or_default(), - self - .extension - .as_ref() - .map(|extension| format!(".{extension}")) - .unwrap_or_default() + "{}", + self.to_full_file_name().to_string_lossy() ) } } @@ -111,8 +115,11 @@ impl FileMatcher { ..self } } - - pub fn matches(&self, file_name: S) -> bool where S: AsRef { + + pub fn matches(&self, file_name: S) -> bool + where + S: AsRef, + { let file_name = file_name.as_ref(); file_name.as_bytes().starts_with(self.name.as_bytes()) && self @@ -131,9 +138,9 @@ mod test_file_name_info { fn test_from_plugin() { assert_eq!( FileNameInfo { - name: "TestPlugin".to_string(), - version: Some("1.0.0".to_string()), - extension: Some("jar".to_string()), + name: "TestPlugin".into(), + version: Some("1.0.0".into()), + extension: Some("jar".into()), }, FileNameInfo::try_from(PathBuf::from("test-ressources/files/TestPlugin-1.0.0.jar")) .expect("valid file") @@ -144,9 +151,9 @@ mod test_file_name_info { fn test_from_unversioned() { assert_eq!( FileNameInfo { - name: "unversioned".to_string(), + name: "unversioned".into(), version: None, - extension: Some("jar".to_string()), + extension: Some("jar".into()), }, FileNameInfo::try_from(PathBuf::from("test-ressources/files/unversioned.jar")) .expect("valid file") @@ -157,8 +164,8 @@ mod test_file_name_info { fn test_from_versioned_bin() { assert_eq!( FileNameInfo { - name: "bin".to_string(), - version: Some("7.3".to_string()), + name: "bin".into(), + version: Some("7.3".into()), extension: None, }, FileNameInfo::try_from(PathBuf::from("test-ressources/files/bin-7.3")).expect("valid file") @@ -169,7 +176,7 @@ mod test_file_name_info { fn test_from_unversioned_bin() { assert_eq!( FileNameInfo { - name: "test".to_string(), + name: "test".into(), version: None, extension: None, }, @@ -189,7 +196,7 @@ mod test_file_matcher { assert!(matcher.matches("test-1.3.0.jar")); assert!(!matcher.matches("test")); } - + #[test] fn test_match_without_extension() { let matcher = FileMatcher::from("test"); diff --git a/src/main.rs b/src/main.rs index 75fe835..154de71 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod command; mod file; mod logger; mod os_string_builder; +mod os_string_extension; mod server; use crate::action::{Action, FileAction, ServerActions}; @@ -10,16 +11,16 @@ use crate::command::{CommandSpecificError, ExecutionError, LogRunnable}; use crate::file::{FileMatcher, FileNameInfo}; use crate::logger::{LogLevel, Logger}; use crate::os_string_builder::ReplaceWithOsStr; +use crate::os_string_extension::OsStrExtension; use crate::server::{RelativeLocalPathAnker, ServerAddress}; use clap::{Parser, Subcommand, ValueEnum}; use lazy_regex::{lazy_regex, Lazy, Regex}; use server::{Server, ServerReference}; use std::cell::LazyCell; -use std::ffi::{OsStr, OsString}; +use std::ffi::OsString; use std::hash::Hash; use std::io::Write; use std::iter::once; -use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{env, fs, io}; @@ -232,11 +233,11 @@ fn main() -> Result<(), String> { ))?; } - let denoted_files = output - .stdout - .split(|&b| b == b'\n') //split at line breaks + let denoted_files = osstring_from_ssh_output(output.stdout) + .split(b'\n') //split at line breaks + .into_iter() .filter(|bytes| !bytes.is_empty()) //needed since realpath sometimes gives us empty lines - .map(|bytes| PathBuf::from(OsStr::from_bytes(bytes))) + .map(PathBuf::from) .collect::>(); Ok(denoted_files) @@ -293,20 +294,23 @@ fn main() -> Result<(), String> { server, actions: { let present_file_names: Vec = match &server.address { - ServerAddress::Ssh { ssh_address } => ShellCmd::new("ls") - .arg(ssh_address) - .arg(osf!("ls ") + &working_directory) - .collect_output() - .map_err(|e| { - format!( - "Failed to query present files on server {}: {e}", - server.get_name() - ) - })? - .stdout - .split(|&b| b == b'\n') - .map(|bytes| OsStr::from_bytes(bytes).to_os_string()) - .collect(), + ServerAddress::Ssh { ssh_address } => osstring_from_ssh_output( + ShellCmd::new("ls") + .arg(ssh_address) + .arg(osf!("ls ") + &working_directory) + .collect_output() + .map_err(|e| { + format!( + "Failed to query present files on server {}: {e}", + server.get_name() + ) + })? + .stdout, + ) + .split(b'\n') + .into_iter() + .map(OsString::from) + .collect(), ServerAddress::Localhost => fs::read_dir(&working_directory) .map_err(|e| format!("Failed to get files in working directory: {e}"))? .map(|entry| entry.map_err(|e| format!("Failed to access directory entry: {e}"))) @@ -325,13 +329,17 @@ fn main() -> Result<(), String> { file_details .iter() .flat_map(|(file, file_name_info)| { - let mut file_matcher = - FileMatcher::from(file_name.as_ref().unwrap_or(&file_name_info.name)); + let mut file_matcher = FileMatcher::from( + file_name + .as_ref() + .map(OsString::from) + .unwrap_or(file_name_info.name.to_os_string()), + ); if let Some(extension) = file_name_info.extension.as_ref() { file_matcher = file_matcher.and_extension(extension); } - let file_name = OsString::from(file_name_info.to_full_file_name()); + let file_name = file_name_info.to_full_file_name(); let add_action = FileAction::new(file, Action::Add).expect("path points to file"); @@ -616,6 +624,20 @@ fn main() -> Result<(), String> { Ok(()) } +fn osstring_from_ssh_output(output: Vec) -> OsString { + #[cfg(unix)] + { + use std::os::unix::ffi::OsStringExt; + OsString::from_vec(output) + } + + #[cfg(windows)] + { + use std::os::windows::ffi::OsStringExt; + OsString::from_wide(output.iter().map(|&b| b as u16).collect()) + } +} + fn check_local_file_exists

(path: P) -> Result<(), String> where P: AsRef, diff --git a/src/os_string_extension.rs b/src/os_string_extension.rs new file mode 100644 index 0000000..ae1e907 --- /dev/null +++ b/src/os_string_extension.rs @@ -0,0 +1,82 @@ +use std::ffi::OsStr; + +pub trait OsStrExtension { + fn starts_with(&self, prefix: S) -> bool + where + S: AsRef; + fn ends_with(&self, suffix: S) -> bool + where + S: AsRef; + fn split(&self, separator: u8) -> Vec<&OsStr>; + fn splitn(&self, n: usize, separator: u8) -> Vec<&OsStr>; + fn split_once(&self, separator: u8) -> Option<(&OsStr, &OsStr)>; + fn rsplitn(&self, n: usize, separator: u8) -> Vec<&OsStr>; + fn rsplit_once(&self, separator: u8) -> Option<(&OsStr, &OsStr)>; +} + +impl OsStrExtension for T +where + T: AsRef, +{ + fn starts_with(&self, prefix: S) -> bool + where + S: AsRef, + { + self + .as_ref() + .as_encoded_bytes() + .starts_with(prefix.as_ref().as_encoded_bytes()) + } + + fn ends_with(&self, suffix: S) -> bool + where + S: AsRef, + { + self + .as_ref() + .as_encoded_bytes() + .ends_with(suffix.as_ref().as_encoded_bytes()) + } + + fn split(&self, separator: u8) -> Vec<&OsStr> { + self + .as_ref() + .as_encoded_bytes() + .split(|&b| b == separator) + .map(|bytes| unsafe { OsStr::from_encoded_bytes_unchecked(bytes) }) + .collect() + } + + fn splitn(&self, n: usize, separator: u8) -> Vec<&OsStr> { + self + .as_ref() + .as_encoded_bytes() + .splitn(n, |&b| b == separator) + .map(|bytes| unsafe { OsStr::from_encoded_bytes_unchecked(bytes) }) + .collect() + } + + fn split_once(&self, separator: u8) -> Option<(&OsStr, &OsStr)> { + let mut iter = self.splitn(2, separator).into_iter(); + Some((iter.next()?, iter.next()?)) + } + + fn rsplitn(&self, n: usize, separator: u8) -> Vec<&OsStr> { + self + .as_ref() + .as_encoded_bytes() + .rsplitn(n, |&b| b == separator) + .map(|bytes| unsafe { OsStr::from_encoded_bytes_unchecked(bytes) }) + .collect::>() + .into_iter() + .rev() + .collect() + } + + fn rsplit_once(&self, separator: u8) -> Option<(&OsStr, &OsStr)> { + let mut iter = self.rsplitn(2, separator).into_iter(); + Some((iter.next()?, iter.next()?)) + } +} + +//TODO unit test! \ No newline at end of file From 67eca84f9dde59d9754610e3f314331dc73d21b2 Mon Sep 17 00:00:00 2001 From: Steppy Date: Mon, 3 Feb 2025 22:32:47 +0100 Subject: [PATCH 6/7] Add unit tests for osstring extensions --- src/os_string_extension.rs | 43 +++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/os_string_extension.rs b/src/os_string_extension.rs index ae1e907..37a8e74 100644 --- a/src/os_string_extension.rs +++ b/src/os_string_extension.rs @@ -79,4 +79,45 @@ where } } -//TODO unit test! \ No newline at end of file +#[cfg(test)] +mod test { + use crate::os_string_extension::OsStrExtension; + use std::ffi::OsString; + + #[test] + fn test_starts_with() { + assert!(OsString::from("Hello world").starts_with("Hell")); + assert!(!OsString::from("Hello world").starts_with("world")); + } + + #[test] + fn test_ends_with() { + assert!(OsString::from("Hello world").ends_with("ld")); + assert!(!OsString::from("Hello world").ends_with("Hello")); + } + + #[test] + fn test_split() { + assert_eq!(vec![OsString::from("Hello"), OsString::from("world")], OsString::from("Hello world").split(b' ')); + } + + #[test] + fn test_splitn() { + assert_eq!(vec![OsString::from("a"), OsString::from("b"), OsString::from("c d")], OsString::from("a b c d").splitn(3, b' ')); + } + + #[test] + fn test_split_once() { + assert_eq!(Some((OsString::from("a").as_ref(), OsString::from("b c").as_ref())), OsString::from("a b c").split_once(b' ')); + } + + #[test] + fn test_rsplitn() { + assert_eq!(vec![OsString::from("a b"), OsString::from("c"), OsString::from("d")], OsString::from("a b c d").rsplitn(3, b' ')); + } + + #[test] + fn test_rsplit_once() { + assert_eq!(Some((OsString::from("a b").as_ref(), OsString::from("c").as_ref())), OsString::from("a b c").rsplit_once(b' ')); + } +} From 02619870b9be5004062a6552840ea2b4d86f52be Mon Sep 17 00:00:00 2001 From: Steppy Date: Mon, 3 Feb 2025 22:33:30 +0100 Subject: [PATCH 7/7] Rename os_string_extension.rs to os_str_extension.rs --- src/file.rs | 2 +- src/main.rs | 4 ++-- src/{os_string_extension.rs => os_str_extension.rs} | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/{os_string_extension.rs => os_str_extension.rs} (98%) diff --git a/src/file.rs b/src/file.rs index 4fbc969..3c03231 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,4 +1,4 @@ -use crate::os_string_extension::OsStrExtension; +use crate::os_str_extension::OsStrExtension; use crate::osf; use std::error::Error; use std::ffi::{OsStr, OsString}; diff --git a/src/main.rs b/src/main.rs index 154de71..fbd5aae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,15 +3,15 @@ mod command; mod file; mod logger; mod os_string_builder; -mod os_string_extension; +mod os_str_extension; mod server; use crate::action::{Action, FileAction, ServerActions}; use crate::command::{CommandSpecificError, ExecutionError, LogRunnable}; use crate::file::{FileMatcher, FileNameInfo}; use crate::logger::{LogLevel, Logger}; +use crate::os_str_extension::OsStrExtension; use crate::os_string_builder::ReplaceWithOsStr; -use crate::os_string_extension::OsStrExtension; use crate::server::{RelativeLocalPathAnker, ServerAddress}; use clap::{Parser, Subcommand, ValueEnum}; use lazy_regex::{lazy_regex, Lazy, Regex}; diff --git a/src/os_string_extension.rs b/src/os_str_extension.rs similarity index 98% rename from src/os_string_extension.rs rename to src/os_str_extension.rs index 37a8e74..e3068d7 100644 --- a/src/os_string_extension.rs +++ b/src/os_str_extension.rs @@ -81,7 +81,7 @@ where #[cfg(test)] mod test { - use crate::os_string_extension::OsStrExtension; + use crate::os_str_extension::OsStrExtension; use std::ffi::OsString; #[test]