fren_date/lib.rs
1//! `fren` - file renamer that understands dates.
2//!
3//! Library crate. The CLI binary lives in `fren-cli` and is a thin consumer
4//! of this library.
5//!
6//! ## Quick example
7//!
8//! ```
9//! use fren_date::{slugify_camel_iso_with_year, SlugOpts};
10//! use slug_preserve::CaseMode;
11//!
12//! let legacy_python = SlugOpts {
13//! separator: '_',
14//! case: CaseMode::Capitalize,
15//! split_camel: true,
16//! };
17//! let out = slugify_camel_iso_with_year("Hello World 2024-01-15.txt", &legacy_python, 2024);
18//! assert!(out.starts_with("Hello"));
19//! ```
20//!
21//! ## Library discipline
22//!
23//! This crate must obey:
24//! - No `panic!` on user input - return `Err(FrenError::*)`
25//! - No `println!`/`eprintln!` - output flows through trait objects
26//! - No global state - all configuration via opts structs
27
28#![deny(missing_docs)]
29#![deny(rustdoc::broken_intra_doc_links)]
30
31pub use slug_preserve::{CaseMode, SlugOpts};
32
33mod error;
34pub use error::FrenError;
35
36mod opts;
37pub use opts::{ConflictPolicy, PlanOpts, RenameOpts};
38
39mod plan_types;
40pub use plan_types::{DateKind, DetectedDate, ItemKind, RenamePlan};
41
42mod report;
43pub use report::ExecutionReport;
44
45mod date;
46mod execute;
47mod log;
48mod merge;
49mod plan;
50mod slugify;
51
52pub use execute::{
53 execute, execute_with_log, execute_with_progress, NullProgressSink, ProgressSink,
54};
55pub use log::{JsonlLogSink, LogRecord, LogSink, NullLogSink};
56pub use merge::{merge_directories, unique_file_name, MergeMove, MergeReport};
57pub use plan::{plan, plan_with_year, sort_bottom_up};
58pub use slugify::{slugify_camel_iso, slugify_camel_iso_with_year};
59
60use std::path::Path;
61
62/// High-level entry point: plan + (optionally) execute a rename batch.
63///
64/// If `opts.apply` is `false`, returns the plan vector wrapped in an
65/// `ExecutionReport` with `applied = 0`; the caller is responsible for
66/// displaying the dry-run preview.
67///
68/// If `opts.apply` is `true`, executes the plan and returns a real report.
69pub fn rename(
70 roots: &[&Path],
71 opts: &RenameOpts,
72) -> Result<(Vec<RenamePlan>, ExecutionReport), FrenError> {
73 let plans = plan(roots, &opts.slugify, &opts.plan)?;
74 let report = if opts.apply {
75 execute(&plans)?
76 } else {
77 ExecutionReport {
78 applied: 0,
79 skipped: 0,
80 errors: Vec::new(),
81 batch_id: plans
82 .first()
83 .map(|p| p.batch_id)
84 .unwrap_or_else(uuid::Uuid::nil),
85 log_path: None,
86 }
87 };
88 Ok((plans, report))
89}
90
91/// Slugify a single string with the given options.
92///
93/// Wrapper around [`slug_preserve::slugify`]. See that crate's docs for
94/// details. This is re-exported so library users don't need to depend on
95/// `slug-preserve` directly.
96#[must_use]
97pub fn slugify_str(input: &str, opts: &SlugOpts) -> String {
98 slug_preserve::slugify(input, opts)
99}