diff --git a/Cargo.lock b/Cargo.lock
index 111be8d..1934e38 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -120,6 +120,12 @@ dependencies = [
"hashbrown",
]
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
[[package]]
name = "libc"
version = "0.2.132"
@@ -132,6 +138,7 @@ version = "0.1.0"
dependencies = [
"clap",
"eyre",
+ "lazy_static",
"regex",
"walkdir",
]
diff --git a/Cargo.toml b/Cargo.toml
index 2a23bbe..1675f3b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,3 +10,4 @@ eyre = "0.6"
clap = { version = "3.2", features = ["derive"] }
regex = "1.6"
walkdir = "2"
+lazy_static = "1.4"
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index a55e457..c609931 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -5,6 +5,7 @@ mod utils;
use std::fmt::{Display, Formatter};
use eyre::Result;
use clap::Parser;
+use lazy_static::lazy_static;
use regex::{Captures, Regex};
use walkdir::{DirEntry, WalkDir};
@@ -14,7 +15,12 @@ use utils::status_icon::*;
const VIDEO_EXTENSIONS: [&str; 14] = ["mkv", "mp4", "avi", "webm", "mov", "wmv", "flv", "ogg", "ogv", "yuv", "amv", "mpg", "mpeg", "m4v"];
pub(crate) const SERIES_REGEX: &str = r"^(?P
.*?)(?P\s-\s?)?(?P[Ss]|\s|\.)(?P\d{1,3})(?P[Ee]|[Xx]|[Ss])(?P\d{1,3})([Ee](?P\d{2,3}))?((?P\s-\s)?(?P.+))?\.(?P...)$";
-// const MOVIE_REGEX: &str = r"^(?P.+)\s(?P\(\d{4}\))\s(?P\[.+\])\.(?P...)$";
+pub(crate) const MOVIE_REGEX: &str = r"^(?P.*?)\s(?P\(\d{4}\))\s(?P\[.+\])\.(?P...)$";
+
+lazy_static! {
+ static ref SERIES_PARSER: Regex = Regex::new(SERIES_REGEX).unwrap();
+ static ref MOVIE_PARSER: Regex = Regex::new(MOVIE_REGEX).unwrap();
+}
struct Stats {
success: usize,
@@ -43,8 +49,6 @@ struct Context {
fn main() -> Result<()> {
let params = Args::parse();
- println!("{}", params.path);
-
let mut stats = Stats {
files: 0,
success: 0,
@@ -57,11 +61,9 @@ fn main() -> Result<()> {
has_printed_folder: false,
};
- let regex = Regex::new(SERIES_REGEX).unwrap();
-
for entry in WalkDir::new(params.path) {
if let Ok(file) = entry {
- check_file(file, &mut stats, &mut ctx, ®ex, params.no_emoji, params.show_success, params.episode_name);
+ check_file(file, &mut stats, &mut ctx, params.no_emoji, params.show_success, params.episode_name);
}
}
@@ -70,11 +72,27 @@ fn main() -> Result<()> {
Ok(())
}
-fn check_file(file: DirEntry, stats: &mut Stats, ctx: &mut Context, regex: &Regex, no_emoji: bool, show_success: bool, episode_name: bool) -> () {
+fn print_result(media_icon: MediaIcon, status_icon: StatusIcon, filename: &str, reason: Option, no_emoji: bool) {
+ match reason {
+ None => println!(
+ "{} {} {}",
+ get_media_icon(media_icon, no_emoji),
+ get_status_icon(status_icon, no_emoji),
+ filename),
+ Some(r) => println!(
+ "{} {} {} --- ({})",
+ get_media_icon(media_icon, no_emoji),
+ get_status_icon(StatusIcon::Warning, no_emoji),
+ filename,
+ r)
+ }
+}
+
+fn check_file(file: DirEntry, stats: &mut Stats, ctx: &mut Context, no_emoji: bool, show_success: bool, episode_name: bool) -> () {
let file_type = file.file_type();
if file_type.is_file() {
let filename = file.file_name().to_str().unwrap();
- match lint_file_name(&file, filename, regex, episode_name) {
+ match lint_file_name(&file, filename, episode_name) {
ComplianceStatus::NotMatched => {
stats.files += 1;
stats.error += 1;
@@ -82,22 +100,26 @@ fn check_file(file: DirEntry, stats: &mut Stats, ctx: &mut Context, regex: &Rege
println!("\n\n=== {} {}", get_status_icon(StatusIcon::Directory, no_emoji), ctx.current_folder);
ctx.has_printed_folder = true;
}
- println!("{} {} --- (Failed to match)", get_status_icon(StatusIcon::Failure, no_emoji), filename);
+ print_result(MediaIcon::Unknown, StatusIcon::Failure, filename, Some("Failed to match".to_string()), no_emoji);
}
- ComplianceStatus::NonCompliant(reason) => {
+ ComplianceStatus::NonCompliant(detected_file_type, reason) => {
stats.files += 1;
if !ctx.has_printed_folder {
println!("\n\n=== {} {}", get_status_icon(StatusIcon::Directory, no_emoji), ctx.current_folder);
ctx.has_printed_folder = true;
}
stats.warning += 1;
- println!("{} {} --- ({})", get_status_icon(StatusIcon::Warning, no_emoji), filename, reason);
+ print_result(MediaIcon::from(detected_file_type), StatusIcon::Warning, filename, Some(reason.to_string()), no_emoji);
}
- ComplianceStatus::Compliant => {
+ ComplianceStatus::Compliant(detected_file_type) => {
stats.files += 1;
stats.success += 1;
if show_success {
- println!("{} {}", get_status_icon(StatusIcon::Success, no_emoji), filename);
+ if !ctx.has_printed_folder {
+ println!("\n\n=== {} {}", get_status_icon(StatusIcon::Directory, no_emoji), ctx.current_folder);
+ ctx.has_printed_folder = true;
+ }
+ print_result(MediaIcon::from(detected_file_type), StatusIcon::Success, filename, None, no_emoji);
}
}
ComplianceStatus::Ignored => {}
@@ -108,21 +130,25 @@ fn check_file(file: DirEntry, stats: &mut Stats, ctx: &mut Context, regex: &Rege
}
}
-const SERIES_RULES_WITHOUT_NAME: [fn(&str, &Captures) -> Option; 3] = [
- DashInTitle::check,
- EpisodeMarker::check,
- HasFluff::check,
-];
+enum FileType<'lt> {
+ SeriesEpisode(Captures<'lt>),
+ Movie(Captures<'lt>),
+ Unknown
+}
-const SERIES_RULES_WITH_NAME: [fn(&str, &Captures) -> Option; 5] = [
- DashInTitle::check,
- EpisodeMarker::check,
- HasFluff::check,
- HasEpisodeName::check,
- MissingNameSeparator::check
-];
+fn parse_filename(filename: &str) -> FileType {
+ if let Some(captures) = SERIES_PARSER.captures(filename) {
+ return FileType::SeriesEpisode(captures)
+ }
-fn lint_file_name(file: &DirEntry, filename: &str, regex: &Regex, episode_name: bool) -> ComplianceStatus {
+ if let Some(captures) = MOVIE_PARSER.captures(filename) {
+ return FileType::Movie(captures)
+ }
+
+ return FileType::Unknown
+}
+
+fn lint_file_name(file: &DirEntry, filename: &str, episode_name: bool) -> ComplianceStatus {
if let Some(ext) = file.path().extension() {
if !VIDEO_EXTENSIONS.contains(&ext.to_str().unwrap()) {
return ComplianceStatus::Ignored;
@@ -131,27 +157,25 @@ fn lint_file_name(file: &DirEntry, filename: &str, regex: &Regex, episode_name:
return ComplianceStatus::Ignored;
}
- let captures = regex.captures(filename);
-
- if captures.is_none() {
- return ComplianceStatus::NotMatched;
- }
-
- let captures = captures.unwrap();
-
- if episode_name {
- check_rules(SERIES_RULES_WITH_NAME, filename, &captures)
- } else {
- check_rules(SERIES_RULES_WITHOUT_NAME, filename, &captures)
+ match parse_filename(filename) {
+ FileType::SeriesEpisode(captures) => {
+ if episode_name {
+ check_rules(SERIES_RULES_WITH_NAME, DetectedFileType::SeriesEpisode, filename, &captures)
+ } else {
+ check_rules(SERIES_RULES_WITHOUT_NAME, DetectedFileType::SeriesEpisode, filename, &captures)
+ }
+ }
+ FileType::Movie(captures) => check_rules(MOVIE_RULES, DetectedFileType::Movie, filename, &captures),
+ FileType::Unknown => ComplianceStatus::NotMatched
}
}
-fn check_rules(validation_rules: [fn(&str, &Captures) -> Option; N], filename: &str, captures: &Captures) -> ComplianceStatus {
+fn check_rules(validation_rules: [fn(&str, &Captures) -> Option; N], file_type: DetectedFileType, filename: &str, captures: &Captures) -> ComplianceStatus {
for rule in validation_rules {
if let Some(reason) = rule(filename, &captures) {
- return ComplianceStatus::NonCompliant(reason);
+ return ComplianceStatus::NonCompliant(file_type, reason);
}
}
- ComplianceStatus::Compliant
+ ComplianceStatus::Compliant(file_type)
}
\ No newline at end of file
diff --git a/src/rules/mod.rs b/src/rules/mod.rs
index 350e9b5..a6b8146 100644
--- a/src/rules/mod.rs
+++ b/src/rules/mod.rs
@@ -14,6 +14,24 @@ pub use has_episode_name::HasEpisodeName;
pub use has_dash_in_title::DashInTitle;
pub use missing_name_separator::MissingNameSeparator;
+pub const SERIES_RULES_WITHOUT_NAME: [fn(&str, &Captures) -> Option; 3] = [
+ DashInTitle::check,
+ EpisodeMarker::check,
+ HasFluff::check,
+];
+
+pub const SERIES_RULES_WITH_NAME: [fn(&str, &Captures) -> Option; 5] = [
+ DashInTitle::check,
+ EpisodeMarker::check,
+ HasFluff::check,
+ HasEpisodeName::check,
+ MissingNameSeparator::check
+];
+
+pub const MOVIE_RULES: [fn(&str, &Captures) -> Option; 1] = [
+ HasFluff::check,
+];
+
#[derive(Debug, PartialEq)]
pub enum NonCompliantReason {
DashInTitle,
@@ -62,10 +80,15 @@ impl Display for EpisodeMarkerReason {
}
}
+pub enum DetectedFileType {
+ SeriesEpisode,
+ Movie
+}
+
pub enum ComplianceStatus {
- NonCompliant(NonCompliantReason),
+ NonCompliant(DetectedFileType, NonCompliantReason),
NotMatched,
- Compliant,
+ Compliant(DetectedFileType),
Ignored
}
diff --git a/src/utils/status_icon.rs b/src/utils/status_icon.rs
index 10ba0b9..c757641 100644
--- a/src/utils/status_icon.rs
+++ b/src/utils/status_icon.rs
@@ -1,3 +1,5 @@
+use crate::DetectedFileType;
+
pub enum StatusIcon {
Failure,
Warning,
@@ -12,4 +14,28 @@ pub fn get_status_icon(icon: StatusIcon, no_emoji: bool) -> String {
StatusIcon::Success => { if no_emoji { "S -".to_string() } else { "✔️".to_string() } }
StatusIcon::Directory => { if no_emoji { "".to_string() } else { "📂".to_string() } }
}
+}
+
+
+pub enum MediaIcon {
+ SeriesEpisode,
+ Movie,
+ Unknown,
+}
+
+impl From for MediaIcon {
+ fn from(d: DetectedFileType) -> Self {
+ match d {
+ DetectedFileType::SeriesEpisode => Self::SeriesEpisode,
+ DetectedFileType::Movie => Self::Movie
+ }
+ }
+}
+
+pub fn get_media_icon(icon: MediaIcon, no_emoji: bool) -> String {
+ match icon {
+ MediaIcon::SeriesEpisode => { if no_emoji { "S -".to_string() } else { "🎞️".to_string() } }
+ MediaIcon::Movie => { if no_emoji { "M -".to_string() } else { "🎬".to_string() } }
+ MediaIcon::Unknown => { if no_emoji { "U -".to_string() } else { "❓".to_string() } }
+ }
}
\ No newline at end of file