1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
pub mod args;
pub mod errors;
pub mod file_renamer;

mod term_utils;

use errors::{InputError, RenameError};
use file_renamer::{FileRenamer, IncrementPosition};
use std::collections::HashSet;
use term_utils::{ask_for_confirmation, log};

pub fn run(opts: args::Options) -> Result<(), RenameError> {
    if opts.force && opts.interactive {
        return Err(RenameError::InputError(InputError::ForceAndInteractive));
    }

    let verbose = opts.verbose || opts.dry_run;

    // Collect all patterns. The mandatory first and extras in order of input.
    let patterns = {
        let mut p = Vec::with_capacity(1 + opts.patterns.len());

        p.push(opts.pattern);
        p.extend(opts.patterns);

        p
    };

    // Dry run: keep track of unavailable file names.
    let mut paths = HashSet::new();

    // The counter used for increment operations. Incremented for every iteration where a (dry) rename happened.
    let mut count = 0;

    for path in &opts.files {
        if path.is_file() {
            // Apply all renaming operations using a builder.
            let renamed = {
                let mut r = FileRenamer::new(path);

                r.apply_patterns(opts.global, &patterns)?;

                if let Some(prefix_increment) = opts.prefix_increment {
                    r.increment(IncrementPosition::Prefix, prefix_increment, count)?;
                }

                if let Some(suffix_increment) = opts.suffix_increment {
                    r.increment(IncrementPosition::Suffix, suffix_increment, count)?;
                }

                r.finish()
            };

            if path == &renamed {
                if verbose {
                    log(opts.dry_run, format!("No patterns match {:?}", path));
                }

                continue;
            }

            if let Some(name) = renamed.file_stem() {
                let was_hidden = path
                    .file_stem()
                    .unwrap_or_else(|| panic!("No file stem for {:?}?", path))
                    .to_string_lossy()
                    .starts_with('.');

                if !was_hidden && name.to_string_lossy().starts_with('.') {
                    log(
                        opts.dry_run,
                        format!("WARN: {:?} got prefix '.' and might be hidden.", renamed),
                    );
                }
            } else {
                return Err(RenameError::InputError(InputError::InvalidRename(
                    path.to_owned(),
                    renamed,
                )));
            }

            if renamed.is_dir() {
                return Err(RenameError::InputError(
                    InputError::CannotRenameFileToDirectory(path.to_owned(), renamed),
                ));
            }

            if renamed.is_file() || paths.contains(&renamed) {
                if opts.interactive {
                    if !ask_for_confirmation(format!("Overwrite {:?}?", renamed))? {
                        continue;
                    }
                } else if !opts.force {
                    return Err(RenameError::InputError(InputError::SkippingOverwrite(
                        path.to_owned(),
                        renamed,
                    )));
                }
            }

            if verbose {
                log(opts.dry_run, format!("{:?} -> {:?}", path, renamed));
            }

            if opts.dry_run {
                paths.insert(renamed);
            } else {
                std::fs::rename(path, renamed)?;
            }

            count += 1;
        } else if opts.ignore_invalid_files {
            if verbose {
                log(opts.dry_run, format!("Ignoring {:?}", path));
            }
        } else {
            // path is not a file. It might not be a directory either.
            let current = std::env::current_dir()?.join(path);
            return Err(RenameError::InputError(InputError::InvalidFile(current)));
        }
    }

    Ok(())
}