frs/
cli.rs

1use async_std::path::PathBuf;
2use bool_ext::BoolExt;
3use std::ops::Not;
4use structopt::{clap::AppSettings, StructOpt};
5
6#[cfg(test)]
7#[path = "./cli_test.rs"]
8pub mod cli_test;
9
10#[derive(thiserror::Error, Debug)]
11pub enum Error {
12    #[error("Multiple operation modes specified")]
13    MultipleOperationModes,
14    #[error("Unknown content `{content}` of environment variable `{var_name}`")]
15    UnknownEnvVarContent { var_name: String, content: String },
16}
17
18#[derive(Debug, StructOpt)]
19#[structopt(setting(AppSettings::ColoredHelp))]
20pub struct Cli {
21    /// This is the default and lets you run it without the actual operation
22    #[structopt(short = "n", long)]
23    pub dry_run: bool,
24    /// Actually running the rename operation.
25    /// If you want to set this as default, set the environment variable `FRS_DEFAULT_OP` to `RUN`
26    #[structopt(short, long)]
27    pub run: bool,
28
29    /// Set the verbosity. In a dry-run its automatically set to 1
30    #[structopt(short, long, parse(from_occurrences))]
31    pub verbose: u8,
32
33    #[structopt(short, long)]
34    pub continue_on_error: bool,
35
36    /// This traverses the Directory Tree.
37    /// If set, the renaming of directories will be disabled by default, to prevent the renaming of
38    /// a directory and its inner files
39    #[structopt(short = "T", long)]
40    pub traverse_tree: bool,
41
42    /// Rename all matching files. If no type is set, then everything will be renamed
43    #[structopt(short, long)]
44    pub file: bool,
45
46    /// Rename all matching directories. If no type is set, then everything will be renamed
47    #[structopt(short, long)]
48    pub directory: bool,
49
50    /// Rename all matching symlinks. If no type is set, then everything will be renamed
51    #[structopt(short, long)]
52    pub symlink: bool,
53
54    #[structopt(short = "i", long)]
55    pub case_insensetive: bool,
56
57    #[structopt(
58        long,
59        default_value = "true",
60        env = "FRS_SHOW_ICONS",
61        parse(try_from_str)
62    )]
63    pub icons: bool,
64
65    pub search_pattern: String,
66    pub replace_pattern: String,
67
68    #[structopt(default_value = ".")]
69    pub base_path: PathBuf,
70}
71
72impl Cli {
73    /// does all the automations after clap
74    pub fn post_automations(&mut self) -> Result<(), Error> {
75        self.set_operation_mode()?;
76        self.set_verbosity();
77        self.set_types();
78        Ok(())
79    }
80
81    /// checks and changes the running option according the environment varaiable
82    fn set_operation_mode(&mut self) -> Result<(), Error> {
83        (self.run && self.dry_run)
84            .not()
85            .err(Error::MultipleOperationModes)?;
86
87        let do_var_name = "FRS_DEFAULT_OP";
88        match std::env::var(do_var_name)
89            .unwrap_or_else(|_| "DRY-RUN".to_string())
90            .to_uppercase()
91            .as_str()
92        {
93            "RUN" => {
94                if !self.dry_run {
95                    self.run = true
96                }
97            }
98            "DRY-RUN" => {
99                if !self.run {
100                    self.dry_run = true
101                }
102            }
103            invalid_do => {
104                return Err(Error::UnknownEnvVarContent {
105                    var_name: do_var_name.to_string(),
106                    content: invalid_do.to_string(),
107                })
108            }
109        }
110        Ok(())
111    }
112
113    /// dry-run sets automatically a minimal verbosity of one
114    fn set_verbosity(&mut self) {
115        self.verbose = self.verbose.max(self.dry_run as u8);
116    }
117
118    /// if no type is selected, all are selected
119    fn set_types(&mut self) {
120        let no_type_selected = !(self.file || self.directory || self.symlink);
121        self.file |= no_type_selected;
122        self.directory |= no_type_selected && !self.traverse_tree;
123        self.symlink |= no_type_selected;
124    }
125}