t-rex 0.1.1

A script for inserting a new page in a number-indexed Docusaurus directory
Documentation
use std::{
    cmp::max,
    fs::{rename, DirEntry},
    io,
    path::Path,
};

use anyhow::{anyhow, bail, Result};

pub fn get_sorted_files_in_dir(directory_path: &Path) -> Result<Vec<DirEntry>> {
    if directory_path.is_file() {
        bail!(
            "Invalid directory path ('{}' is a file)!",
            directory_path.to_string_lossy()
        );
    }

    let mut files: Vec<DirEntry> = directory_path
        .read_dir()?
        .into_iter()
        .filter(|entry| {
            if let Ok(entry) = entry {
                is_valid_name(&entry.file_name().to_string_lossy())
            } else {
                false
            }
        })
        .collect::<Result<Vec<DirEntry>, io::Error>>()?;

    files.sort_by(|f1, f2| {
        num_prefix(&entry_to_file_name(f1))
            .expect("Not valid but already checked is_valid")
            .cmp(
                &num_prefix(&entry_to_file_name(f2))
                    .expect("Not valid but already checked is_valid"),
            )
    });

    Ok(files)
}

pub fn is_valid_name(file_name: &str) -> bool {
    is_numeric(&file_prefix(file_name))
}

pub fn file_prefix(file_name: &str) -> String {
    file_name
        .to_string()
        .split_once('-')
        .map_or(file_name.to_string(), |(start, _)| start.to_string())
}

pub fn is_numeric(file_prefix: &str) -> bool {
    file_prefix.chars().all(|c| c.is_ascii_digit())
}

pub fn num_prefix(file_name: &str) -> Result<usize> {
    let prefix = file_prefix(file_name);
    if is_numeric(&prefix) {
        Ok(prefix.parse()?)
    } else {
        Err(anyhow!("'{}' is an invalid file name!", file_name))
    }
}

pub fn entry_to_file_name(entry: &DirEntry) -> String {
    entry.file_name().to_string_lossy().to_string()
}

pub fn replaced_index_name_unchecked(name: &str, new_index: usize, padding: usize) -> String {
    let mut new_name = {
        let mut index = format!("{:0width$}", new_index, width = padding);
        index.push('-');
        index
    };
    let (_, old_end): (&str, &str) = name
        .split_once('-')
        .expect(&*format!("'{}' is an invalid file name!", name));
    new_name.push_str(old_end);

    new_name
}

pub fn rename_files_from_index(
    mut entries: Vec<DirEntry>,
    start_index: Option<usize>,
    parent_dir: &Path,
    padding: usize,
) -> Result<usize> {
    let mut new_names: Vec<String> = Vec::new();
    for (current_index, entry) in entries.iter().enumerate() {
        let file_name = entry_to_file_name(entry);
        let expected_index = if let Some(start_index) = &start_index {
            current_index + *start_index + 1
        } else {
            current_index
        };

        let new_name = if num_prefix(&file_name)? != expected_index {
            replaced_index_name_unchecked(&file_name, expected_index, padding)
        } else {
            file_name
        };

        new_names.push(new_name);
    }

    entries.reverse();
    new_names.reverse();

    let mut rename_count = 0;
    entries
        .into_iter()
        .enumerate()
        .try_for_each(|(index, entry)| {
            let entry_name = &entry_to_file_name(&entry);
            let new_name = &new_names[index];
            if entry_name != new_name {
                let new_path = parent_dir.join(new_name);
                rename_count += 1;
                rename(entry.path(), new_path).map_err(|err| {
                    anyhow!(
                        "Error renaming file '{}' to '{}' - {}",
                        entry_name,
                        new_name,
                        err
                    )
                })
            } else {
                Ok(())
            }
        })?;
    Ok(rename_count)
}

pub fn get_padding_digits(all_files: &[DirEntry]) -> (usize, usize) {
    let digits_needed = max((all_files.len() as f64).log10().ceil() as usize, 2);
    let digits_used = if let Some(file) = all_files.get(0) {
        file_prefix(&entry_to_file_name(file)).len()
    } else {
        0
    };
    (digits_used, digits_needed)
}