goat_cli/utils/
variables.rs

1use crate::utils::{
2    expression::Variable,
3    utils::{did_you_mean, parse_comma_separated},
4};
5use anyhow::{bail, Result};
6use std::collections::BTreeMap;
7
8/// A struct to store the variables
9/// passed in the `-v` flag on the
10/// CLI.
11pub struct Variables<'a> {
12    /// Variables which need to be parsed.
13    variables: &'a str,
14}
15
16impl<'a> Variables<'a> {
17    /// Constructor for [`Variables`].
18    pub fn new(str: &'a str) -> Self {
19        Self { variables: str }
20    }
21
22    /// Parse a single variable. Used in report
23    /// until something more sophisticated is made.
24    pub fn parse_one(
25        &self,
26        reference_data: &BTreeMap<&'static str, Variable<'static>>,
27    ) -> Result<String> {
28        let variable = self.variables;
29
30        let var_vec_check = reference_data
31            .iter()
32            .map(|(e, _)| e.to_string())
33            .collect::<Vec<String>>();
34
35        if !var_vec_check.contains(&variable.to_string()) {
36            let var_vec_mean = did_you_mean(&var_vec_check, variable);
37            if let Some(value) = var_vec_mean {
38                bail!(
39                    "In your variable (`-v`) you typed \"{}\" - did you mean \"{}\"?",
40                    variable,
41                    value
42                )
43            }
44        }
45        Ok(variable.to_string())
46    }
47
48    /// Simple parsing of a comma separated string,
49    /// which will error if the variable is not found
50    /// with a suggestion as to which one you meant.
51    pub fn parse(
52        &self,
53        reference_data: &BTreeMap<&'static str, Variable<'static>>,
54    ) -> Result<String> {
55        let base = "&fields=";
56        let delimiter = "%2C";
57
58        let mut parsed_string = String::new();
59
60        let split_vec = parse_comma_separated(self.variables);
61        // check that all the strings in split_vec are real
62        let var_vec_check = reference_data
63            .iter()
64            .map(|(e, _)| e.to_string())
65            .collect::<Vec<String>>();
66
67        for variable in &split_vec {
68            // only if we find something which does not match...
69            if !var_vec_check.contains(variable) {
70                let var_vec_mean = did_you_mean(&var_vec_check, variable);
71                if let Some(value) = var_vec_mean {
72                    bail!(
73                        "In your variable (`-v`) you typed \"{}\" - did you mean \"{}\"?",
74                        variable,
75                        value
76                    )
77                }
78            }
79        }
80
81        parsed_string += base;
82        for el in split_vec {
83            parsed_string += &el;
84            parsed_string += delimiter;
85        }
86
87        // should be okay to do an unchecked drain here
88        parsed_string.drain(parsed_string.len() - 3..);
89
90        Ok(parsed_string)
91    }
92
93    /// Parse a variable name into a string which will be entered in the final URL
94    /// to exclude missing and ancestral taxa.
95    pub fn parse_exclude(
96        &self,
97        reference_data: &BTreeMap<&'static str, Variable<'static>>,
98    ) -> Result<String> {
99        const ANCESTRAL: &str = "&excludeAncestral";
100        const MISSING: &str = "&excludeMissing";
101        const OPEN_ANGLE_BRACE: &str = "%5B";
102        const CLOSE_ANGLE_BRACE: &str = "%5D";
103
104        let mut exclusion_string = String::new();
105
106        let split_vec = parse_comma_separated(self.variables);
107        // check that all the strings in split_vec are real
108        let var_vec_check = reference_data
109            .iter()
110            .map(|(e, _)| e.to_string())
111            .collect::<Vec<String>>();
112
113        for variable in &split_vec {
114            // only if we find something which does not match...
115            if !var_vec_check.contains(variable) {
116                let var_vec_mean = did_you_mean(&var_vec_check, variable);
117                if let Some(value) = var_vec_mean {
118                    bail!(
119                        "In your variable (`-v`) you typed \"{}\" - did you mean \"{}\"?",
120                        variable,
121                        value
122                    )
123                }
124            }
125        }
126
127        for (exclude_index, field) in split_vec.into_iter().enumerate() {
128            exclusion_string += ANCESTRAL;
129            exclusion_string += OPEN_ANGLE_BRACE;
130            exclusion_string += &exclude_index.to_string();
131            exclusion_string += CLOSE_ANGLE_BRACE;
132            exclusion_string += &format!("={field}");
133
134            // add missing
135            exclusion_string += MISSING;
136            exclusion_string += OPEN_ANGLE_BRACE;
137            exclusion_string += &exclude_index.to_string();
138            exclusion_string += CLOSE_ANGLE_BRACE;
139            exclusion_string += &format!("={field}");
140
141        }
142
143        Ok(exclusion_string)
144    }
145}