t_rex/
utils.rs

1use std::{
2    cmp::max,
3    fs::{rename, DirEntry},
4    io,
5    path::Path,
6};
7
8use anyhow::{anyhow, bail, Result};
9
10pub fn get_sorted_files_in_dir(directory_path: &Path) -> Result<Vec<DirEntry>> {
11    if directory_path.is_file() {
12        bail!(
13            "Invalid directory path ('{}' is a file)!",
14            directory_path.to_string_lossy()
15        );
16    }
17
18    let mut files: Vec<DirEntry> = directory_path
19        .read_dir()?
20        .into_iter()
21        .filter(|entry| {
22            if let Ok(entry) = entry {
23                is_valid_name(&entry.file_name().to_string_lossy())
24            } else {
25                false
26            }
27        })
28        .collect::<Result<Vec<DirEntry>, io::Error>>()?;
29
30    files.sort_by(|f1, f2| {
31        num_prefix(&entry_to_file_name(f1))
32            .expect("Not valid but already checked is_valid")
33            .cmp(
34                &num_prefix(&entry_to_file_name(f2))
35                    .expect("Not valid but already checked is_valid"),
36            )
37    });
38
39    Ok(files)
40}
41
42pub fn is_valid_name(file_name: &str) -> bool {
43    is_numeric(&file_prefix(file_name))
44}
45
46pub fn file_prefix(file_name: &str) -> String {
47    file_name
48        .to_string()
49        .split_once('-')
50        .map_or(file_name.to_string(), |(start, _)| start.to_string())
51}
52
53pub fn is_numeric(file_prefix: &str) -> bool {
54    file_prefix.chars().all(|c| c.is_ascii_digit())
55}
56
57pub fn num_prefix(file_name: &str) -> Result<usize> {
58    let prefix = file_prefix(file_name);
59    if is_numeric(&prefix) {
60        Ok(prefix.parse()?)
61    } else {
62        Err(anyhow!("'{}' is an invalid file name!", file_name))
63    }
64}
65
66pub fn entry_to_file_name(entry: &DirEntry) -> String {
67    entry.file_name().to_string_lossy().to_string()
68}
69
70pub fn replaced_index_name_unchecked(name: &str, new_index: usize, padding: usize) -> String {
71    let mut new_name = {
72        let mut index = format!("{:0width$}", new_index, width = padding);
73        index.push('-');
74        index
75    };
76    let (_, old_end): (&str, &str) = name
77        .split_once('-')
78        .expect(&*format!("'{}' is an invalid file name!", name));
79    new_name.push_str(old_end);
80
81    new_name
82}
83
84pub fn rename_files_from_index(
85    mut entries: Vec<DirEntry>,
86    start_index: Option<usize>,
87    parent_dir: &Path,
88    padding: usize,
89) -> Result<usize> {
90    let mut new_names: Vec<String> = Vec::new();
91    for (current_index, entry) in entries.iter().enumerate() {
92        let file_name = entry_to_file_name(entry);
93        let expected_index = if let Some(start_index) = &start_index {
94            current_index + *start_index + 1
95        } else {
96            current_index
97        };
98
99        let new_name = if num_prefix(&file_name)? != expected_index {
100            replaced_index_name_unchecked(&file_name, expected_index, padding)
101        } else {
102            file_name
103        };
104
105        new_names.push(new_name);
106    }
107
108    entries.reverse();
109    new_names.reverse();
110
111    let mut rename_count = 0;
112    entries
113        .into_iter()
114        .enumerate()
115        .try_for_each(|(index, entry)| {
116            let entry_name = &entry_to_file_name(&entry);
117            let new_name = &new_names[index];
118            if entry_name != new_name {
119                let new_path = parent_dir.join(new_name);
120                rename_count += 1;
121                rename(entry.path(), new_path).map_err(|err| {
122                    anyhow!(
123                        "Error renaming file '{}' to '{}' - {}",
124                        entry_name,
125                        new_name,
126                        err
127                    )
128                })
129            } else {
130                Ok(())
131            }
132        })?;
133    Ok(rename_count)
134}
135
136pub fn get_padding_digits(all_files: &[DirEntry]) -> (usize, usize) {
137    let digits_needed = max((all_files.len() as f64).log10().ceil() as usize, 2);
138    let digits_used = if let Some(file) = all_files.get(0) {
139        file_prefix(&entry_to_file_name(file)).len()
140    } else {
141        0
142    };
143    (digits_used, digits_needed)
144}