initial project scaffold
Rust workspace with nomina-core (rename engine) and nomina-app (Tauri v2 shell). React/TypeScript frontend with tabbed rule panels, virtual-scrolled file list, and Zustand state management. All 9 rule types implemented with 25 passing tests.
This commit is contained in:
107
crates/nomina-core/src/lib.rs
Normal file
107
crates/nomina-core/src/lib.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
pub mod rules;
|
||||
pub mod pipeline;
|
||||
pub mod filter;
|
||||
pub mod metadata;
|
||||
pub mod preset;
|
||||
pub mod undo;
|
||||
pub mod scanner;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum NominaError {
|
||||
#[error("Invalid regex pattern: {pattern} - {reason}")]
|
||||
InvalidRegex { pattern: String, reason: String },
|
||||
|
||||
#[error("File not found: {path}")]
|
||||
FileNotFound { path: PathBuf },
|
||||
|
||||
#[error("Rename conflict: {count} files would produce the name '{name}'")]
|
||||
NamingConflict { name: String, count: usize },
|
||||
|
||||
#[error("Invalid filename '{name}': {reason}")]
|
||||
InvalidFilename { name: String, reason: String },
|
||||
|
||||
#[error("Filesystem error on '{path}': {source}")]
|
||||
Filesystem {
|
||||
path: PathBuf,
|
||||
#[source]
|
||||
source: std::io::Error,
|
||||
},
|
||||
|
||||
#[error("Preset parse error: {reason}")]
|
||||
PresetError { reason: String },
|
||||
|
||||
#[error("BRU import error at line {line}: {reason}")]
|
||||
BruImportError { line: usize, reason: String },
|
||||
|
||||
#[error("EXIF read error for '{path}': {reason}")]
|
||||
ExifError { path: PathBuf, reason: String },
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, NominaError>;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RenameContext {
|
||||
pub index: usize,
|
||||
pub total: usize,
|
||||
pub original_name: String,
|
||||
pub extension: String,
|
||||
pub path: PathBuf,
|
||||
pub size: u64,
|
||||
pub created: Option<DateTime<Utc>>,
|
||||
pub modified: Option<DateTime<Utc>>,
|
||||
pub date_taken: Option<DateTime<Utc>>,
|
||||
pub parent_folder: String,
|
||||
}
|
||||
|
||||
impl RenameContext {
|
||||
pub fn dummy(index: usize) -> Self {
|
||||
Self {
|
||||
index,
|
||||
total: 1,
|
||||
original_name: String::new(),
|
||||
extension: String::new(),
|
||||
path: PathBuf::new(),
|
||||
size: 0,
|
||||
created: None,
|
||||
modified: None,
|
||||
date_taken: None,
|
||||
parent_folder: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RenameRule: Send + Sync {
|
||||
fn apply(&self, filename: &str, context: &RenameContext) -> String;
|
||||
fn display_name(&self) -> &str;
|
||||
fn rule_type(&self) -> &str;
|
||||
fn is_enabled(&self) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FileEntry {
|
||||
pub path: PathBuf,
|
||||
pub name: String,
|
||||
pub stem: String,
|
||||
pub extension: String,
|
||||
pub size: u64,
|
||||
pub is_dir: bool,
|
||||
pub is_hidden: bool,
|
||||
pub created: Option<DateTime<Utc>>,
|
||||
pub modified: Option<DateTime<Utc>>,
|
||||
pub accessed: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PreviewResult {
|
||||
pub original_path: PathBuf,
|
||||
pub original_name: String,
|
||||
pub new_name: String,
|
||||
pub has_conflict: bool,
|
||||
pub has_error: bool,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
Reference in New Issue
Block a user