Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 19 additions & 124 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub fn run_with_flags(args: cli::Flags) -> io::Result<()> {
run_multi(&patterns, &params)
}

fn patterns_from_args(paths: Vec<String>) -> Vec<String> {
pub(crate) fn patterns_from_args(paths: Vec<String>) -> Vec<String> {
if paths.is_empty() {
vec![String::from(".")]
} else {
Expand All @@ -25,11 +25,7 @@ fn patterns_from_args(paths: Vec<String>) -> Vec<String> {
}

fn run_multi(patterns: &[String], params: &Params) -> io::Result<()> {
let mut all_file_info = Vec::new();

for pattern in patterns {
append_pattern_matches(&mut all_file_info, pattern, params)?;
}
let all_file_info = collect_matches(patterns, params)?;

if params.long_format {
utils::render::display_long_format(&all_file_info, params)
Expand All @@ -38,6 +34,23 @@ fn run_multi(patterns: &[String], params: &Params) -> io::Result<()> {
}
}

pub(crate) fn collect_matches(
patterns: &[String],
params: &Params,
) -> io::Result<Vec<crate::FileInfo>> {
if patterns.is_empty() {
return Ok(Vec::new());
}

let mut all_file_info = Vec::new();

for pattern in patterns {
append_pattern_matches(&mut all_file_info, pattern, params)?;
}

Ok(all_file_info)
}

fn append_pattern_matches(
all_file_info: &mut Vec<crate::FileInfo>,
pattern: &str,
Expand Down Expand Up @@ -70,121 +83,3 @@ fn append_paths(

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::os::unix::fs::PermissionsExt;
use tempfile::tempdir;

#[test]
fn test_run_multi() -> io::Result<()> {
let temp_dir = tempdir()?;

File::create(temp_dir.path().join("test1.txt"))?;
File::create(temp_dir.path().join("test2.txt"))?;
std::fs::create_dir(temp_dir.path().join("testdir"))?;

let params = Params::default();
let patterns = vec![temp_dir.path().to_string_lossy().to_string()];

assert!(run_multi(&patterns, &params).is_ok());
Ok(())
}

#[test]
fn test_run_multi_nonexistent() {
let params = Params::default();
let patterns = vec![String::from("/nonexistent/path")];

let result = run_multi(&patterns, &params);
assert!(result.is_ok());
}

#[test]
fn test_append_pattern_matches_with_glob() -> io::Result<()> {
let temp_dir = tempdir()?;

File::create(temp_dir.path().join("test1.txt"))?;
File::create(temp_dir.path().join("test2.txt"))?;
File::create(temp_dir.path().join("test.rs"))?;

let params = Params::default();
let pattern = format!("{}/*.txt", temp_dir.path().to_string_lossy());
let mut file_info = Vec::new();

append_pattern_matches(&mut file_info, &pattern, &params)?;

assert_eq!(file_info.len(), 2);
Ok(())
}

#[test]
fn test_patterns_from_args_defaults_to_current_directory() {
assert_eq!(patterns_from_args(Vec::new()), vec![String::from(".")]);
}

#[test]
fn test_patterns_from_args_preserves_explicit_paths() {
let paths = vec![String::from("left"), String::from("right")];

assert_eq!(patterns_from_args(paths.clone()), paths);
}

#[test]
fn test_run_multi_glob_error() {
let params = Params::default();
let invalid_pattern = vec![String::from("[invalid-glob-pattern")];

run_multi(&invalid_pattern, &params).unwrap();
}

#[test]
fn test_append_pattern_matches_with_empty_glob() -> io::Result<()> {
let params = Params::default();
let mut file_info = Vec::new();

append_pattern_matches(
&mut file_info,
"**/nonexistent_pattern_*.xyz",
&params,
)?;

assert!(file_info.is_empty());
Ok(())
}

#[test]
fn test_run_multi_error_handling() {
let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("no_read.txt");
std::fs::write(&test_file, "test").unwrap();
std::fs::set_permissions(
&test_file,
std::fs::Permissions::from_mode(0o000),
)
.unwrap();

let params = Params::default();
let pattern = vec![test_file.to_string_lossy().to_string()];

let result = run_multi(&pattern, &params);
assert!(result.is_ok());

std::fs::set_permissions(
&test_file,
std::fs::Permissions::from_mode(0o644),
)
.unwrap();
}

#[test]
fn test_run_multi_empty_pattern() {
let params = Params::default();
let pattern = vec![String::from("**/nonexistent_pattern_*.xyz")];

let result = run_multi(&pattern, &params);
assert!(result.is_ok());
}
}
127 changes: 10 additions & 117 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,11 @@ pub struct Flags {
pub fuzzy_time: bool,
}

pub fn version_info() -> String {
let version = env!("CARGO_PKG_VERSION");
let authors = env!("CARGO_PKG_AUTHORS");
let description = env!("CARGO_PKG_DESCRIPTION");

// Provide default values if fields are empty
pub(crate) fn format_version_info(
version: &str,
authors: &str,
description: &str,
) -> String {
let authors = if authors.is_empty() {
"Unknown"
} else {
Expand All @@ -102,116 +101,10 @@ pub fn version_info() -> String {
)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_default_flags() {
let args = Flags::parse_from(["lsplus"]);
assert!(!args.show_all);
assert!(!args.almost_all);
assert!(!args.long);
assert!(!args.human_readable);
assert!(!args.slash);
assert!(!args.dirs_first);
assert!(!args.no_icons);
assert!(!args.gitignore);
assert!(!args.version);
assert!(!args.fuzzy_time);
assert_eq!(args.paths, vec![String::from(".")]);
}

#[test]
fn test_multiple_paths() {
let args = Flags::parse_from(["lsplus", "path1", "path2"]);
assert_eq!(
args.paths,
vec![String::from("path1"), String::from("path2")]
);
}

#[test]
fn test_all_flags() {
let args = Flags::parse_from([
"lsplus",
"-a",
"-A",
"-l",
"-h",
"-p",
"--sort-dirs",
"--no-icons",
"--gitignore",
"--fuzzy-time",
]);
assert!(args.show_all);
assert!(args.almost_all);
assert!(args.long);
assert!(args.human_readable);
assert!(args.slash);
assert!(args.dirs_first);
assert!(args.no_icons);
assert!(args.gitignore);
assert!(args.fuzzy_time);
}

#[test]
fn test_version_flag() {
let args = Flags::parse_from(["lsplus", "--version"]);
assert!(args.version);
}

#[test]
fn test_version_info() {
let info = version_info();
assert!(info.contains("lsplus v"));
assert!(info.contains("Released under the MIT license by"));
assert!(info.contains(env!("CARGO_PKG_AUTHORS")));
assert!(info.contains(env!("CARGO_PKG_DESCRIPTION")));
}

#[test]
fn test_version_info_empty() {
// This test is just to verify the code paths for empty fields
// The actual env vars cannot be modified at runtime
let version_info = version_info();
assert!(version_info.contains("lsplus v"));
assert!(version_info.contains("Released under the MIT license by"));
}

#[test]
fn test_version_info_with_empty_env() {
// We can't modify the env vars at compile time, but we can test the format
let info = version_info();
assert!(info.contains("lsplus v"));
assert!(info.contains("Released under the MIT license by"));

// Verify the format is correct even if env vars were empty
let formatted = format!(
"lsplus v{}\n\
\n{}\n\
\nReleased under the MIT license by {}\n",
env!("CARGO_PKG_VERSION"),
if env!("CARGO_PKG_DESCRIPTION").is_empty() {
"No description provided"
} else {
env!("CARGO_PKG_DESCRIPTION")
},
if env!("CARGO_PKG_AUTHORS").is_empty() {
"Unknown"
} else {
env!("CARGO_PKG_AUTHORS")
}
);
assert_eq!(info, formatted);
}
pub fn version_info() -> String {
let version = env!("CARGO_PKG_VERSION");
let authors = env!("CARGO_PKG_AUTHORS");
let description = env!("CARGO_PKG_DESCRIPTION");

#[test]
fn test_help_flag() {
let result = Flags::try_parse_from(["lsplus", "--help"]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("Usage:"));
}
format_version_info(version, authors, description)
}
22 changes: 22 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,25 @@ pub mod structs;
pub mod utils;

pub use structs::{FileInfo, Params};

#[cfg(test)]
#[path = "../tests/crate/app.rs"]
mod app_tests;
#[cfg(test)]
#[path = "../tests/crate/cli.rs"]
mod cli_tests;
#[cfg(test)]
#[path = "../tests/crate/file.rs"]
mod file_tests;
#[cfg(test)]
#[path = "../tests/crate/gitignore.rs"]
mod gitignore_tests;
#[cfg(test)]
#[path = "../tests/crate/icons.rs"]
mod icons_tests;
#[cfg(test)]
#[path = "../tests/crate/render.rs"]
mod render_tests;
#[cfg(test)]
#[path = "../tests/crate/settings.rs"]
mod settings_tests;
43 changes: 6 additions & 37 deletions src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ use dirs_next::home_dir;
use crate::Params;

fn config_path() -> Option<PathBuf> {
let mut path = home_dir()?;
config_path_from_home(home_dir())
}

pub(crate) fn config_path_from_home(home: Option<PathBuf>) -> Option<PathBuf> {
let mut path = home?;
path.push(".config/lsplus/config.toml");
Some(path)
}
Expand All @@ -15,7 +19,7 @@ pub fn load_config() -> Params {
load_config_from_path(config_path())
}

fn load_config_from_path(config_path: Option<PathBuf>) -> Params {
pub(crate) fn load_config_from_path(config_path: Option<PathBuf>) -> Params {
let Some(config_path) = config_path else {
return Params::default();
};
Expand All @@ -36,38 +40,3 @@ fn load_config_from_path(config_path: Option<PathBuf>) -> Params {
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;

#[test]
fn test_load_config_default() {
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("missing-config.toml");

let config = load_config_from_path(Some(config_path));

assert_eq!(config, Params::default());
}

#[test]
fn test_load_config_error() {
let config = load_config_from_path(None);

assert_eq!(config, Params::default());
}

#[test]
fn test_load_config_error_other() {
let temp_dir = tempfile::tempdir().unwrap();
let config_path = temp_dir.path().join(".config/lsplus/config.toml");
std::fs::create_dir_all(config_path.parent().unwrap()).unwrap();
std::fs::write(&config_path, "invalid = toml [ content").unwrap();

let config = load_config_from_path(Some(config_path));

assert_eq!(config, Params::default());
}
}
Loading