lib/config/cli/
mod.rs

1mod internal;
2
3use crate::common::{util, Platform};
4use std::{ffi::OsString, path::PathBuf};
5use structopt::StructOpt;
6
7/// The portion of the configuration read from CLI arguments
8#[derive(Debug, Clone)]
9pub struct Config {
10    /// Enables verbose output.
11    pub verbose: bool,
12
13    /// Paths (relative to the dotfiles folder) of items to be excluded.
14    /// This is in addition to any excludes defined in your dotrc.
15    /// Globs are accepted - just make sure to enclose them in single quotes to
16    /// avoid your shell trying to expand them.
17    pub excludes: Vec<PathBuf>,
18
19    /// Tags to enable. This is in addition to any tags enabled in your dotrc.
20    pub tags: Vec<String>,
21
22    /// The folder in which to search for dotfiles. The default is ~/.dotfiles.
23    pub dotfiles_path: Option<PathBuf>,
24
25    /// The hostname to use. The default is the system hostname.
26    pub hostname: Option<String>,
27
28    /// The platform to use. The default is the actual platform.
29    /// Valid values are macos, windows, linux, and wsl.
30    pub platform: Option<Platform>,
31
32    pub command: Command,
33}
34
35impl Config {
36    pub fn get() -> Self {
37        let app = internal::RawConfig::clap();
38        let raw_config = internal::RawConfig::from_clap(&app.get_matches());
39
40        let (command, command_options) = match raw_config.command {
41            internal::Command::Ls { options } => (Ls, options),
42            internal::Command::Link { dry_run, options } => (Link { dry_run }, options),
43        };
44
45        let verbose = raw_config.options.verbose || command_options.verbose;
46        let excludes = util::append_vecs(raw_config.options.excludes, command_options.excludes);
47        let tags = util::append_vecs(raw_config.options.tags, command_options.tags);
48
49        /// Given the name of an argument which should be unique, tries to get
50        /// it from either the main command or a subcommand. If it is
51        /// provided multiple times in a way that `clap` won't catch
52        /// (e.g. given once to the main command and again to a subcommand),
53        /// produces an appropriate `clap` error and exits.
54        macro_rules! get_unique_arg {
55            ($name: ident) => {
56                match (raw_config.options.$name, command_options.$name) {
57                    (None, None) => None,
58                    (Some($name), None) | (None, Some($name)) => Some($name),
59                    (Some(_), Some(_)) => {
60                        // We simply append an extra usage of --$name onto the existing args, then
61                        // try to parse them again. This should trigger an error about
62                        // $name appearing twice, which we display then exit.
63                        //
64                        // This should make this particular hack transparent to the user, since the
65                        // error is just like if `clap` had caught the error.
66
67                        let mut args: Vec<OsString> = std::env::args_os().collect();
68                        args.push(OsString::from(concat!("--", stringify!($name))));
69
70                        internal::RawConfig::from_iter_safe(args.iter())
71                            .expect_err("This argument should not allow duplicates")
72                            .exit()
73                    },
74                }
75            };
76        }
77
78        let dotfiles_path = get_unique_arg!(dotfiles_path);
79        let hostname = get_unique_arg!(hostname);
80        let platform = get_unique_arg!(platform);
81
82        let res = Config {
83            verbose,
84            excludes,
85            tags,
86            dotfiles_path,
87            hostname,
88            platform,
89            command,
90        };
91
92        util::set_verbosity(res.verbose);
93
94        res
95    }
96}
97
98#[derive(Debug, Clone, Copy)]
99pub enum Command {
100    /// Lists the active dotfiles
101    Ls,
102
103    /// Links all active dotfiles
104    Link {
105        /// Skips the actual linking step. Everything else (e.g. errors and
106        /// prompts) remains unchanged.
107        dry_run: bool,
108    },
109}
110use Command::*;