reups_lib/list.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
6use crate::argparse;
7use crate::db;
8use fnv::{FnvHashMap, FnvHashSet};
9use std::env;
10
11/**
12 * Lists info about products defined in the product database
13 *
14 * This function takes two arguments, one for command specific arguments,
15 * and one for program general options. These arguments are parsed from the
16 * command line, packaged and sent here. The arguments are defined in the
17 * argparse module.
18 */
19pub fn list_command(sub_args: &argparse::ArgMatches, _main_args: &argparse::ArgMatches) {
20 let mut lister = ListImpl::new(sub_args, _main_args);
21 lister.run();
22}
23
24/// This enum controls if the list command shows tags, versions, or both
25#[derive(Clone)]
26enum OnlyPrint {
27 Tags,
28 Versions,
29 All,
30}
31
32/**
33 * The Listimpl structure is responsible for implementing the list subcomand functionality
34 * It is created with argument matche from the command line in the new method. This method
35 * prepopulates the database and some system variables. The sub command is executed with the run
36 * command.
37 */
38struct ListImpl<'a> {
39 sub_args: &'a argparse::ArgMatches<'a>,
40 _main_args: &'a argparse::ArgMatches<'a>,
41 output_string: String,
42 current_products: FnvHashSet<(String, String)>,
43 local_setups: FnvHashMap<String, String>,
44 db: db::DB,
45 tags: Option<Vec<String>>,
46}
47
48impl<'a> ListImpl<'a> {
49 /** Creates a LIstImpl struct given argument matches from the command line
50 */
51 fn new(
52 sub_args: &'a argparse::ArgMatches<'a>,
53 _main_args: &'a argparse::ArgMatches<'a>,
54 ) -> ListImpl<'a> {
55 // Here we will process any of the global arguments in the future but for now there is
56 // nothing so we do nothing but create the database. The global arguments might affect
57 // construction in the future
58
59 // cheat and look at the sub_args here to see if all products are listing all products or not. If
60 // not, dont preload tag files in the database as this will slow things down
61 let preload = if sub_args.is_present("product")
62 || sub_args.is_present("setup")
63 || sub_args.is_present("tags")
64 || sub_args.is_present("onlyTags")
65 || sub_args.is_present("onlyVers")
66 {
67 None
68 } else {
69 // Mark the database to preload all the tag files off disk
70 Some(db::DBLoadControl::Tags)
71 };
72 let db = db::DB::new(None, None, None, preload);
73 // get any products that are currently setup
74 let (current_products, local_setups) = find_setup_products();
75 // String to hold the output
76 let output_string = String::from("");
77 // Hold tag information
78 let tags = None;
79 // create the object
80 ListImpl {
81 sub_args,
82 _main_args,
83 output_string,
84 current_products,
85 local_setups,
86 db,
87 tags,
88 }
89 }
90
91 /// Runs the ListImpl over arguments given on the command line, and information
92 /// gained from environment variables. Its result is the requested information is
93 /// printed out to the user in the console.
94 fn run(&mut self) {
95 // If the user specified a specific product only generate output for that product
96 let mut product_vec = if self.sub_args.is_present("product") {
97 vec![self.sub_args.value_of("product").unwrap().to_string()]
98 }
99 // If the user specifed they want only setup products, get the list of those to display
100 else if self.sub_args.is_present("setup") {
101 self.current_products
102 .iter()
103 .map(|tup| tup.0.clone())
104 .collect()
105 }
106 // If the user wants only products that have been locally setup, get the list of those
107 // products
108 else if self.sub_args.is_present("local") {
109 self.local_setups.keys().map(|k| k.clone()).collect()
110 }
111 // Baring any input from the user, list all products found in the user and system databases
112 else {
113 self.db.get_all_products()
114 };
115
116 // check if we should restrict printing
117 let select_printing = if self.sub_args.is_present("onlyTags") {
118 OnlyPrint::Tags
119 } else if self.sub_args.is_present("onlyVers") {
120 OnlyPrint::Versions
121 } else {
122 OnlyPrint::All
123 };
124
125 // Read any tags the user supplied, to restrict printing to only products
126 // with those tags
127 let mut tags_vec = vec![];
128 if self.sub_args.is_present("tags") {
129 for t in self.sub_args.values_of("tags").unwrap() {
130 tags_vec.push(t.to_string());
131 }
132 self.tags = Some(tags_vec);
133 }
134
135 // Sort the products to be listed so that the results come out deterministically and in
136 // lexographic order
137 product_vec.sort();
138 // Loop over all products and print the information about that product.
139 for product in product_vec.iter() {
140 self.print_product(product, select_printing.clone());
141 }
142 println!("{}", self.output_string.trim_right_matches("\n\n"));
143 }
144
145 /**
146 * Given a product to print, and printing options, this function retrieves
147 * all the information required about the product from the database, formats
148 * it, and appends it to the output string.
149 */
150 fn print_product(&mut self, product: &String, select_printing: OnlyPrint) {
151 // If the user supplied tags, use those when determining the tags and
152 // versions to print, else grab all tags associated with the given product
153 let tags = if self.tags.is_some() {
154 self.tags.as_ref().unwrap().clone()
155 } else {
156 self.db.product_tags(product)
157 };
158
159 // Switch on which printing is to be done, only tags, only versons, or all
160 match select_printing {
161 OnlyPrint::All => {
162 // This builds an association between versions of a product and
163 // what tags point to that version. Unfortunately this must
164 // open and read a lot of files to do this.
165 let mut version_to_tags = FnvHashMap::default();
166 // dont accumulate versions if only locals are to be listed
167 if !self.sub_args.is_present("local") {
168 for tag in tags.iter() {
169 if let OnlyPrint::All = select_printing {
170 let versions = self.db.get_versions_from_tag(product, vec![tag]);
171 for v in versions {
172 version_to_tags.entry(v).or_insert(vec![]).push(tag);
173 }
174 }
175 }
176 }
177 // look for any local version that might be setup
178 if let Some(local) = self.local_setups.get(product) {
179 version_to_tags.entry(local.clone()).or_insert(vec![]);
180 }
181
182 // Turn the hashmap into a vector
183 let mut version_to_tags_vec: Vec<(String, Vec<&String>)> =
184 version_to_tags.into_iter().collect();
185 // Sort the versions vector by version
186 version_to_tags_vec.sort_by(|tup1, tup2| tup1.0.cmp(&tup2.0));
187 // Iterate over and print results
188 for (ver, tags) in version_to_tags_vec {
189 self.output_string.push_str(
190 format!(
191 "{:25}{:>25}{:10}{}]",
192 product,
193 ver,
194 "",
195 tags.iter()
196 .fold(String::from("["), |acc, &x| {
197 // if the tag is current, color the string
198 let name = if *x == "current" {
199 "\x1b[96mcurrent\x1b[0m".to_owned()
200 } else {
201 (*x).clone()
202 };
203 acc + &name + ", "
204 })
205 .trim_right_matches(", ")
206 ).as_str()
207 .trim(),
208 );
209 // Check if this product and version match any that are setup,
210 // and if so add a colored setup string
211 if self.current_products.contains(&(product.clone(), ver)) {
212 self.output_string.push_str(" \x1b[92mSetup\x1b[0m");
213 }
214 self.output_string.push_str("\n\n");
215 }
216 }
217 OnlyPrint::Tags => {
218 self.output_string.push_str(
219 format!(
220 "{:25}{:10}{}]",
221 product,
222 "",
223 tags.iter()
224 .fold(String::from("["), |acc, x| {
225 let name = if x == "current" {
226 "\x1b[96mcurrent\x1b[0m"
227 } else {
228 &x
229 };
230 acc + name + ", "
231 })
232 .trim_right_matches(", ")
233 ).as_str()
234 .trim(),
235 );
236 self.output_string.push_str("\n\n");
237 }
238 OnlyPrint::Versions => {
239 let mut versions = if !self.sub_args.is_present("local") {
240 self.db.product_versions(product)
241 } else {
242 vec![]
243 };
244 if let Some(local) = self.local_setups.get(product) {
245 versions.push(local.clone());
246 }
247 self.output_string
248 .push_str(format!("{:25}{:10}", product, "").as_str());
249 self.output_string.push_str("[");
250 for version in versions {
251 if self
252 .current_products
253 .contains(&(product.clone(), version.clone()))
254 {
255 self.output_string
256 .push_str(format!("\x1b[92m{}\x1b[0m", version).as_str());
257 } else {
258 self.output_string.push_str(version.as_str());
259 }
260 self.output_string.push_str(", ");
261 }
262 // This removes the last space and comma added
263 self.output_string.pop();
264 self.output_string.pop();
265 self.output_string.push_str("]");
266 self.output_string.push_str("\n\n");
267 }
268 }
269 }
270}
271
272/**
273 * Read the environment variable and find all products that have previously been setup.
274 *
275 * Returns a tuple where the first element is a hash set of (product, version) tuples. The second
276 * element is a hashmap of locally setup product names as keys, and their local setup path.
277 */
278fn find_setup_products() -> (FnvHashSet<(String, String)>, FnvHashMap<String, String>) {
279 let mut product_set = FnvHashSet::default();
280 let mut local_products = FnvHashMap::default();
281 for (var, value) in env::vars() {
282 if var.starts_with("SETUP_") {
283 let value_vec: Vec<&str> = value.split(" ").collect();
284 if value_vec.len() < 2 {
285 // The value corresponding to a setup product should at least have
286 // a Name and a version, if not there was an issue with that variable
287 eprintln!("Warning, problem parsing {} skipping", var);
288 continue;
289 }
290 // the first element is the product that is setup, the second is version
291 product_set.insert((value_vec[0].to_string(), value_vec[1].to_string()));
292 // Check if the product is a local setup. Track these differently, as
293 // these versions will be the setup version, but not have a corresponding
294 // version string in any database. This hashmap lets us display or append
295 // local results onto results from databases
296 if value_vec[1].starts_with("LOCAL") {
297 local_products.insert(value_vec[0].to_string(), value_vec[1].to_string());
298 }
299 }
300 }
301 (product_set, local_products)
302}