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}