reups_lib/
env.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 * Copyright Nate Lust 2018*/
5
6/**!
7 * This module manages saving and restoring a users environment as it is setup. The idea behind
8 * this module is that a user may issue several different calls to rsetup (reups setup) to
9 * configure the exact packages active. The user can then save this setup to restore into a
10 * different shell. This is not meant as a complete replacement for tagging product directories,
11 * but as a sort of save your work buffer for setups that are not intended to live for a long
12 * time. As a caveat this also replays the commands issued exactly, so if anything changed (such as
13 * the current tag changing) commands will not necessarily recreate the exact environment at the
14 * time of saving, but will reconstruct an environment as if the current (r)eups environment was
15 * the environment when the commands were first issued.
16 *
17 **/
18use crate::argparse;
19use crate::logger;
20use preferences;
21use preferences::Preferences;
22use std::env as stdEnv;
23use std::io::stdin;
24
25// This is the information used to differentiate this application to the preferences crate and is
26// used to determine what path the settings will be saved to.
27const APP_INFO: preferences::AppInfo = preferences::AppInfo {
28    name: "reups",
29    author: "Reups Community",
30};
31
32// This determines the exact location within the app's configuration space the environments will be
33// saved in
34const PREF_KEY: &str = "saved/environments";
35
36/**
37 * This is the main entry point for the env sub command. This command is used to save and restore
38 * the (r)eups managed environment that is setup in the current shell. This function has different
39 * effects based on the sub command argument supplied. The save argument will write out the current
40 * environment either named default, or with the optinally supplied name. A convienence shell
41 * function called rsave is supplied by reups prep to do the same task with less typing. The list subcommand will
42 * list all the environments previously saved. If the delete argument is supplied, the given named
43 * environment will be discarded, note the default environment cannot be deleted. The restore
44 * argument is used by this program to reconstruct the chosen environment. Because of the
45 * limitations of working with shells, the user should interact with this though the shell function
46 * rrestore that is supplied with the reups prep command.
47 *
48 * * sub_args - Arguments matched from the command line to the given sub command
49 * * _main_args - Arguments matched from the command line to the main reups executable,
50 *                global arguments
51 **/
52pub fn env_command(sub_args: &argparse::ArgMatches, _main_args: &argparse::ArgMatches) {
53    let mut env_command = EnvCommandImpl::new(sub_args, _main_args);
54    env_command.run();
55}
56
57/**
58 * This is the internal implementation of the env sub command
59 */
60struct EnvCommandImpl<'a> {
61    sub_args: &'a argparse::ArgMatches<'a>,
62    _main_args: &'a argparse::ArgMatches<'a>,
63    current_commands: Vec<String>,
64    name: String,
65    saved_envs: preferences::PreferencesMap<Vec<String>>,
66}
67
68impl<'a> EnvCommandImpl<'a> {
69    /**
70     * Creates a new EnvCommandImpl. The function uses the supplied argument matches, looks up the
71     * commands that were executed in the current environment, parses the name to use in
72     * processing, and then looks up if there was an existing environment store, creating one if
73     * there was none present.
74     **/
75    fn new(
76        sub_args: &'a argparse::ArgMatches<'a>,
77        _main_args: &'a argparse::ArgMatches<'a>,
78    ) -> EnvCommandImpl<'a> {
79        // make a logger object
80        logger::build_logger(sub_args, false);
81        // Get the environment variable
82        let current_commands = match stdEnv::var("REUPS_HISTORY") {
83            Ok(existing) => existing.split("|").map(|x| String::from(x)).collect(),
84            _ => vec![],
85        };
86
87        // Get a name to consider if one is supplied, otherwise use default
88        let name = {
89            if sub_args.is_present("name") {
90                String::from(sub_args.value_of("name").unwrap())
91            } else {
92                String::from("default")
93            }
94        };
95
96        // Load in an existing save environment
97        let saved_envs = preferences::PreferencesMap::<Vec<String>>::load(&APP_INFO, PREF_KEY);
98        // Check that there was an existing environment, otherwise create one.
99        let saved_envs = {
100            if saved_envs.is_ok() {
101                crate::debug!("saved_envs loaded existing env");
102                saved_envs.unwrap()
103            } else {
104                // there is no existing preferences
105                crate::debug!("Existing env was not loaded, create and use new env store");
106                crate::warn!("No existing env store could be found create a new one? (y/N)");
107                let mut s = String::new();
108                stdin()
109                    .read_line(&mut s)
110                    .expect("Did not enter a correct option");
111                if let Some('\n') = s.chars().next_back() {
112                    s.pop();
113                }
114                if let Some('\r') = s.chars().next_back() {
115                    s.pop();
116                }
117                if s == "y" || s == "Y" {
118                    crate::warn!("Creating new env store");
119                    preferences::PreferencesMap::<Vec<String>>::new()
120                } else {
121                    exit_with_message!("No env store found or created, exiting");
122                }
123            }
124        };
125
126        // initialize and return a new struct
127        EnvCommandImpl {
128            sub_args,
129            _main_args,
130            current_commands,
131            name,
132            saved_envs: saved_envs,
133        }
134    }
135
136    /** The man entry point for running this command. The function looks at what action argument
137     * was provided, and dispatches to the corresponding functionality
138     **/
139    fn run(&mut self) {
140        // look at what the current command is
141        match self.sub_args.value_of("command").unwrap() {
142            "save" => self.run_save(),
143            "restore" => self.run_restore(),
144            "delete" => self.run_delete(),
145            "list" => self.run_list(),
146            _ => (),
147        }
148    }
149
150    /** Saves any commands that were executed in the current environment
151     **/
152    fn run_save(&mut self) {
153        self.saved_envs
154            .insert(self.name.clone(), self.current_commands.clone());
155        let save_result = self.saved_envs.save(&APP_INFO, PREF_KEY);
156        save_result.expect("There was a problem saving the current env");
157    }
158
159    /** Restores a given environment. This action is most likely to be activated by the rrestore
160     * shell function provided by reups prep. Direct invocation is most likely only for debug
161     * reasons. This is a limitation of modifying shell environments.
162     **/
163    fn run_restore(&self) {
164        // get env to restore from the supplied name
165        let env_list_option = &self.saved_envs.get(&self.name);
166        // Verify the environment could be found, otherwise exit
167        let env_list = match env_list_option {
168            Some(list) => list,
169            None => {
170                exit_with_message!(format!(
171                    "Cannot find environment {}. Use reups env list to see saved environments",
172                    &self.name
173                ));
174            }
175        };
176        // build an application object to parse the saved commands. This is done to verify that
177        // a command was indeed a setup command. This could be done in other ways by string
178        // parsing, but the overhead is so little reuse of existing code is preferable
179        let app = argparse::build_cli();
180        for command in *env_list {
181            // split a command string into a vector  and use the app to match
182            let args = app.clone().get_matches_from(command.split(" "));
183            match args.subcommand() {
184                ("setup", Some(_)) => {
185                    println!("eval $({});", command);
186                }
187                _ => {
188                    exit_with_message!(format!("Problem restoring environment {}", &self.name));
189                }
190            };
191        }
192    }
193
194    /** This function is responsible for managing the delete action. Using the name supplied the
195     * specified saved environment is discarded. It will not discard the default environment.
196     * Simply save over default if a change is desired.
197     **/
198    fn run_delete(&mut self) {
199        // Don't delete the default environment
200        if self.name == "default" {
201            exit_with_message!("Cannot delete default save");
202        }
203        self.saved_envs.remove(&self.name);
204        let save_result = self.saved_envs.save(&APP_INFO, PREF_KEY);
205        if !save_result.is_ok() {
206            exit_with_message!("There was a problem deleting the environment");
207        }
208    }
209
210    /** This function will list all named environments that have been saved in the past
211     */
212    fn run_list(&self) {
213        println!("Environments Found:");
214        for (k, v) in &self.saved_envs {
215            println!("{}", k);
216            crate::info!("{:?}", v);
217        }
218    }
219}