Formatting
This commit is contained in:
@@ -7,7 +7,10 @@ use shanty_db::Database;
|
||||
use shanty_org::{DEFAULT_FORMAT, OrgConfig, organize_from_db, organize_from_directory};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "shanty-org", about = "Organize music files into a clean directory structure")]
|
||||
#[command(
|
||||
name = "shanty-org",
|
||||
about = "Organize music files into a clean directory structure"
|
||||
)]
|
||||
struct Cli {
|
||||
/// Source directory of music files (standalone mode, reads tags from files).
|
||||
#[arg(long)]
|
||||
|
||||
@@ -51,9 +51,7 @@ impl TrackMetadata {
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
let tagged_file = Probe::open(path)?
|
||||
.options(ParseOptions::default())
|
||||
.read()?;
|
||||
let tagged_file = Probe::open(path)?.options(ParseOptions::default()).read()?;
|
||||
|
||||
let tag = tagged_file
|
||||
.primary_tag()
|
||||
|
||||
@@ -56,10 +56,7 @@ fn resolve_target(target: &Path) -> PathBuf {
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("file");
|
||||
let ext = target
|
||||
.extension()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("");
|
||||
let ext = target.extension().and_then(|s| s.to_str()).unwrap_or("");
|
||||
let parent = target.parent().unwrap_or(Path::new("."));
|
||||
|
||||
for i in 2..=999 {
|
||||
@@ -235,10 +232,7 @@ pub async fn organize_from_db(
|
||||
}
|
||||
|
||||
/// Organize music files from a source directory (standalone, no database).
|
||||
pub async fn organize_from_directory(
|
||||
source_dir: &Path,
|
||||
config: &OrgConfig,
|
||||
) -> OrgResult<OrgStats> {
|
||||
pub async fn organize_from_directory(source_dir: &Path, config: &OrgConfig) -> OrgResult<OrgStats> {
|
||||
let mut stats = OrgStats::default();
|
||||
let source_root = source_dir
|
||||
.canonicalize()
|
||||
@@ -344,12 +338,11 @@ fn cleanup_empty_dirs_recursive(root: &Path) {
|
||||
if dir == root {
|
||||
continue;
|
||||
}
|
||||
if let Ok(mut entries) = std::fs::read_dir(&dir) {
|
||||
if entries.next().is_none() {
|
||||
if std::fs::remove_dir(&dir).is_ok() {
|
||||
tracing::debug!(path = %dir.display(), "removed empty directory");
|
||||
}
|
||||
}
|
||||
if let Ok(mut entries) = std::fs::read_dir(&dir)
|
||||
&& entries.next().is_none()
|
||||
&& std::fs::remove_dir(&dir).is_ok()
|
||||
{
|
||||
tracing::debug!(path = %dir.display(), "removed empty directory");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ pub fn sanitize_component(s: &str) -> String {
|
||||
.collect();
|
||||
|
||||
// Trim leading/trailing dots and spaces (problematic on Windows and some Linux tools)
|
||||
result = result.trim_matches(|c: char| c == '.' || c == ' ').to_string();
|
||||
result = result
|
||||
.trim_matches(|c: char| c == '.' || c == ' ')
|
||||
.to_string();
|
||||
|
||||
// Collapse consecutive underscores
|
||||
while result.contains("__") {
|
||||
|
||||
@@ -49,7 +49,7 @@ pub fn render(template: &str, meta: &TrackMetadata) -> String {
|
||||
|
||||
// Split template by "/" so we can sanitize each path component individually.
|
||||
// The extension is special — it's part of the filename, not sanitized separately.
|
||||
let result = template
|
||||
template
|
||||
.replace("{artist}", &sanitize_component(artist))
|
||||
.replace("{album_artist}", &sanitize_component(album_artist))
|
||||
.replace("{album}", &sanitize_component(album))
|
||||
@@ -58,9 +58,7 @@ pub fn render(template: &str, meta: &TrackMetadata) -> String {
|
||||
.replace("{disc_number}", &disc_number)
|
||||
.replace("{year}", &year)
|
||||
.replace("{genre}", &sanitize_component(genre))
|
||||
.replace("{ext}", ext);
|
||||
|
||||
result
|
||||
.replace("{ext}", ext)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -85,14 +83,23 @@ mod tests {
|
||||
fn test_render_default_format() {
|
||||
let meta = test_meta();
|
||||
let result = render(DEFAULT_FORMAT, &meta);
|
||||
assert_eq!(result, "Pink Floyd/The Dark Side of the Moon/03 - Time.flac");
|
||||
assert_eq!(
|
||||
result,
|
||||
"Pink Floyd/The Dark Side of the Moon/03 - Time.flac"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_custom_format() {
|
||||
let meta = test_meta();
|
||||
let result = render("{artist} - {album}/{disc_number}-{track_number} {title}.{ext}", &meta);
|
||||
assert_eq!(result, "Pink Floyd - The Dark Side of the Moon/1-03 Time.flac");
|
||||
let result = render(
|
||||
"{artist} - {album}/{disc_number}-{track_number} {title}.{ext}",
|
||||
&meta,
|
||||
);
|
||||
assert_eq!(
|
||||
result,
|
||||
"Pink Floyd - The Dark Side of the Moon/1-03 Time.flac"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -102,7 +109,10 @@ mod tests {
|
||||
..Default::default()
|
||||
};
|
||||
let result = render(DEFAULT_FORMAT, &meta);
|
||||
assert_eq!(result, "Unknown Artist/Unknown Album/00 - Unknown Title.mp3");
|
||||
assert_eq!(
|
||||
result,
|
||||
"Unknown Artist/Unknown Album/00 - Unknown Title.mp3"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -87,8 +87,16 @@ async fn test_organize_from_directory() {
|
||||
let money_path = target
|
||||
.path()
|
||||
.join("Pink Floyd/The Dark Side of the Moon/06 - Money.mp3");
|
||||
assert!(time_path.exists(), "Time should exist at {}", time_path.display());
|
||||
assert!(money_path.exists(), "Money should exist at {}", money_path.display());
|
||||
assert!(
|
||||
time_path.exists(),
|
||||
"Time should exist at {}",
|
||||
time_path.display()
|
||||
);
|
||||
assert!(
|
||||
money_path.exists(),
|
||||
"Money should exist at {}",
|
||||
money_path.display()
|
||||
);
|
||||
|
||||
// Source files should be gone (moved, not copied)
|
||||
assert!(!source.path().join("song1.mp3").exists());
|
||||
@@ -124,7 +132,12 @@ async fn test_organize_copy_mode() {
|
||||
// Source should still exist (copy mode)
|
||||
assert!(source.path().join("song.mp3").exists());
|
||||
// Target should exist too
|
||||
assert!(target.path().join("Pink Floyd/DSOTM/01 - Time.mp3").exists());
|
||||
assert!(
|
||||
target
|
||||
.path()
|
||||
.join("Pink Floyd/DSOTM/01 - Time.mp3")
|
||||
.exists()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -237,8 +250,10 @@ async fn test_organize_missing_metadata() {
|
||||
assert_eq!(stats.files_organized, 1);
|
||||
|
||||
// Should use "Unknown" fallbacks
|
||||
assert!(target
|
||||
.path()
|
||||
.join("Unknown Artist/Unknown Album/00 - Unknown Title.mp3")
|
||||
.exists());
|
||||
assert!(
|
||||
target
|
||||
.path()
|
||||
.join("Unknown Artist/Unknown Album/00 - Unknown Title.mp3")
|
||||
.exists()
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user