Initial Commit
This commit is contained in:
commit
7a06c4cea7
14 changed files with 1064 additions and 0 deletions
14
src/args.rs
Normal file
14
src/args.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
#[clap(long, value_parser)]
|
||||
pub no_emoji: bool,
|
||||
|
||||
#[clap(long, value_parser)]
|
||||
pub show_success: bool,
|
||||
|
||||
#[clap(long, value_parser)]
|
||||
pub path: String,
|
||||
}
|
120
src/main.rs
Normal file
120
src/main.rs
Normal file
|
@ -0,0 +1,120 @@
|
|||
mod args;
|
||||
mod rules;
|
||||
mod utils;
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
use eyre::Result;
|
||||
use clap::Parser;
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
use args::Args;
|
||||
use rules::*;
|
||||
use utils::status_icon::*;
|
||||
|
||||
const VIDEO_EXTENSIONS: [&str; 14] = ["mkv", "mp4", "avi", "webm", "mov", "wmv", "flv", "ogg", "ogv", "yuv", "amv", "mpg", "mpeg", "m4v"];
|
||||
const SERIES_REGEX: &str = r"^(?P<title>.+)\sS(?P<season>\d\d)E(?P<episode>\d\d\d?)(E(?P<episode2>\d\d\d?))?((?P<nameSeparator>\s-\s)?(?P<name>.+))?\.(?P<ext>...)$";
|
||||
// const FILE_EXT_REGEX: &str = r"^(?P<title>.+)\.(?P<ext>...)$";
|
||||
// const MOVIE_REGEX: &str = r"^(?P<title>.+)\s(?P<year>\(\d{4}\))\s(?P<resolution>\[.+\])\.(?P<ext>...)$";
|
||||
|
||||
struct Stats {
|
||||
success: usize,
|
||||
warning: usize,
|
||||
error: usize,
|
||||
files: usize,
|
||||
}
|
||||
|
||||
impl Display for Stats {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Files: {} - Success: {} - Warnings: {} - Errors: {}",
|
||||
self.files,
|
||||
self.success,
|
||||
self.warning,
|
||||
self.error)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let params = Args::parse();
|
||||
|
||||
println!("{}", params.path);
|
||||
|
||||
let mut stats = Stats {
|
||||
files: 0,
|
||||
success: 0,
|
||||
warning: 0,
|
||||
error: 0,
|
||||
};
|
||||
|
||||
for entry in WalkDir::new(params.path) {
|
||||
if let Ok(file) = entry {
|
||||
check_file(file, &mut stats, params.no_emoji, params.show_success);
|
||||
}
|
||||
}
|
||||
|
||||
println!("{}", stats);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_file(file: DirEntry, stats: &mut Stats, no_emoji: bool, show_success: bool) -> () {
|
||||
let file_type = file.file_type();
|
||||
if file_type.is_file() {
|
||||
stats.files += 1;
|
||||
let filename = file.file_name().to_str().unwrap();
|
||||
match lint_file_name(&file, filename) {
|
||||
ComplianceStatus::NotMatched => {
|
||||
stats.error += 1;
|
||||
println!("{} {} --- (Failed to match)", get_status_icon(StatusIcon::Failure, no_emoji), filename)
|
||||
}
|
||||
ComplianceStatus::NonCompliant(reason) => {
|
||||
stats.warning += 1;
|
||||
println!("{} {} --- ({})", get_status_icon(StatusIcon::Warning, no_emoji), filename, reason);
|
||||
}
|
||||
ComplianceStatus::Compliant => {
|
||||
stats.success += 1;
|
||||
if show_success {
|
||||
println!("{} {}", get_status_icon(StatusIcon::Success, no_emoji), filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if file_type.is_dir() {
|
||||
println!("\n\n=== {} {}", get_status_icon(StatusIcon::Directory, no_emoji), file.path().display())
|
||||
}
|
||||
}
|
||||
|
||||
fn lint_file_name(file: &DirEntry, filename: &str) -> ComplianceStatus {
|
||||
if let Some(ext) = file.path().extension() {
|
||||
if !VIDEO_EXTENSIONS.contains(&ext.to_str().unwrap()) {
|
||||
return ComplianceStatus::Compliant;
|
||||
}
|
||||
}
|
||||
|
||||
let captures = regex::Regex::new(SERIES_REGEX).unwrap().captures(filename);
|
||||
|
||||
if captures.is_none() {
|
||||
return ComplianceStatus::NotMatched;
|
||||
}
|
||||
|
||||
let captures = captures.unwrap();
|
||||
|
||||
if let Some(reason) = HasEpisodeName::check(filename, &captures) {
|
||||
return ComplianceStatus::NonCompliant(reason);
|
||||
}
|
||||
|
||||
if let Some(reason) = MissingSeparator::check(filename, &captures) {
|
||||
return ComplianceStatus::NonCompliant(reason);
|
||||
}
|
||||
|
||||
if let Some(reason) = DashInTitle::check(filename, &captures) {
|
||||
return ComplianceStatus::NonCompliant(reason);
|
||||
}
|
||||
|
||||
if let Some(reason) = HasFluff::check(filename, &captures) {
|
||||
return ComplianceStatus::NonCompliant(reason);
|
||||
}
|
||||
|
||||
ComplianceStatus::Compliant
|
||||
}
|
20
src/rules/has_dash_in_title.rs
Normal file
20
src/rules/has_dash_in_title.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
use regex::Captures;
|
||||
use eyre::Result;
|
||||
use super::*;
|
||||
|
||||
pub struct DashInTitle {}
|
||||
impl Rule for DashInTitle {
|
||||
fn check(_filename: &str, captures: &Captures) -> Option<NonCompliantReason> {
|
||||
let title = captures.name("title").unwrap();
|
||||
|
||||
if title.as_str().contains("-") {
|
||||
return Some(NonCompliantReason::DashInTitle)
|
||||
}
|
||||
|
||||
return None
|
||||
}
|
||||
|
||||
fn fix(_filename: &str, _captures: &Captures) -> Result<FixStatus> {
|
||||
return Ok(FixStatus::NotImplemented)
|
||||
}
|
||||
}
|
18
src/rules/has_episode_name.rs
Normal file
18
src/rules/has_episode_name.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use regex::Captures;
|
||||
use eyre::Result;
|
||||
use super::*;
|
||||
|
||||
pub struct HasEpisodeName {}
|
||||
impl Rule for HasEpisodeName {
|
||||
fn check(_filename: &str, captures: &Captures) -> Option<NonCompliantReason> {
|
||||
if captures.name("name").is_none() {
|
||||
return Some(NonCompliantReason::MissingName)
|
||||
}
|
||||
|
||||
return None
|
||||
}
|
||||
|
||||
fn fix(_filename: &str, _captures: &Captures) -> Result<FixStatus> {
|
||||
return Ok(FixStatus::NotImplemented)
|
||||
}
|
||||
}
|
24
src/rules/has_fluff.rs
Normal file
24
src/rules/has_fluff.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
use regex::Captures;
|
||||
use eyre::Result;
|
||||
use super::*;
|
||||
|
||||
const KNOWN_FLUFF: [&str; 7] = ["[", "xvid", "BluRay", "1080p", "264", "dvdrip", "web-dl"];
|
||||
|
||||
pub struct HasFluff {}
|
||||
impl Rule for HasFluff {
|
||||
fn check(filename: &str, _captures: &Captures) -> Option<NonCompliantReason> {
|
||||
let lowered_filename = filename.to_lowercase();
|
||||
for fluff in KNOWN_FLUFF {
|
||||
if lowered_filename.contains(fluff) {
|
||||
return Some(NonCompliantReason::HasFluff)
|
||||
}
|
||||
}
|
||||
|
||||
return None
|
||||
}
|
||||
|
||||
fn fix(_filename: &str, _captures: &Captures) -> Result<FixStatus> {
|
||||
return Ok(FixStatus::NotImplemented)
|
||||
}
|
||||
}
|
||||
|
18
src/rules/missing_separator.rs
Normal file
18
src/rules/missing_separator.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use regex::Captures;
|
||||
use eyre::Result;
|
||||
use super::*;
|
||||
|
||||
pub struct MissingSeparator {}
|
||||
impl Rule for MissingSeparator {
|
||||
fn check(_filename: &str, captures: &Captures) -> Option<NonCompliantReason> {
|
||||
if captures.name("nameSeparator").is_none() {
|
||||
return Some(NonCompliantReason::MissingNameSeparator)
|
||||
}
|
||||
|
||||
return None
|
||||
}
|
||||
|
||||
fn fix(_filename: &str, _captures: &Captures) -> Result<FixStatus> {
|
||||
return Ok(FixStatus::NotImplemented)
|
||||
}
|
||||
}
|
48
src/rules/mod.rs
Normal file
48
src/rules/mod.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
use std::fmt::{Display, Formatter};
|
||||
use regex::Captures;
|
||||
use eyre::Result;
|
||||
|
||||
mod has_fluff;
|
||||
mod has_dash_in_title;
|
||||
mod has_episode_name;
|
||||
mod missing_separator;
|
||||
|
||||
pub use has_fluff::HasFluff;
|
||||
pub use has_episode_name::HasEpisodeName;
|
||||
pub use has_dash_in_title::DashInTitle;
|
||||
pub use missing_separator::MissingSeparator;
|
||||
|
||||
pub enum NonCompliantReason {
|
||||
DashInTitle,
|
||||
MissingName,
|
||||
HasFluff,
|
||||
MissingNameSeparator
|
||||
}
|
||||
|
||||
impl Display for NonCompliantReason {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
NonCompliantReason::DashInTitle => write!(f, "Title contains dash"),
|
||||
NonCompliantReason::MissingName => write!(f, "Missing episode name"),
|
||||
NonCompliantReason::HasFluff => write!(f, "Has fluff"),
|
||||
NonCompliantReason::MissingNameSeparator => write!(f, "Missing episode name separator"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ComplianceStatus {
|
||||
NonCompliant(NonCompliantReason),
|
||||
NotMatched,
|
||||
Compliant,
|
||||
}
|
||||
|
||||
pub enum FixStatus {
|
||||
// Fixed,
|
||||
NotImplemented,
|
||||
// NotFixable
|
||||
}
|
||||
|
||||
pub trait Rule {
|
||||
fn check(filename: &str, captures: &Captures) -> Option<NonCompliantReason>;
|
||||
fn fix(filename: &str, captures: &Captures) -> Result<FixStatus>;
|
||||
}
|
4
src/utils/constructors.rs
Normal file
4
src/utils/constructors.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
#[allow(dead_code)]
|
||||
pub fn episode_name(title: String, season: String, episode: String, name: String, format: String) -> String {
|
||||
format!("${title} S${season}E${episode} - ${name}.${format}")
|
||||
}
|
2
src/utils/mod.rs
Normal file
2
src/utils/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod constructors;
|
||||
pub mod status_icon;
|
15
src/utils/status_icon.rs
Normal file
15
src/utils/status_icon.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
pub enum StatusIcon {
|
||||
Failure,
|
||||
Warning,
|
||||
Success,
|
||||
Directory,
|
||||
}
|
||||
|
||||
pub fn get_status_icon(icon: StatusIcon, no_emoji: bool) -> String {
|
||||
match icon {
|
||||
StatusIcon::Failure => { if no_emoji { "F -".to_string() } else { "❌".to_string() } }
|
||||
StatusIcon::Warning => { if no_emoji { "W -".to_string() } else { "⚠️".to_string() } }
|
||||
StatusIcon::Success => { if no_emoji { "S -".to_string() } else { "✔️".to_string() } }
|
||||
StatusIcon::Directory => { if no_emoji { "".to_string() } else { "📂".to_string() } }
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue