reqlang_expr/
cliutil.rs

1//! A set of utility functions to implement reglang-expr CLIs
2
3use std::{
4    error::Error,
5    fs::read_to_string,
6    io::{Read, stdin},
7};
8
9/// Unzip a vector of key-value pairs into separate vectors for keys and values.
10///
11/// ```
12/// use reqlang_expr::cliutil::unzip_key_values;
13///
14/// let keys_values: Vec<(String, String)> = vec![
15///     ("key1".to_string(), "val1".to_string()),
16///     ("key2".to_string(), "val2".to_string())
17/// ];
18///
19/// let (keys, values) = unzip_key_values(keys_values);
20///
21/// assert_eq!(vec!["key1", "key2"], keys);
22/// assert_eq!(vec!["val1", "val2"], values);
23/// ```
24///
25/// ## Usage
26///
27/// This is can be used to accept in key-value pairs from command line arguments,
28/// feeding the keys/values to the compile time and runtime envrionments
29/// respectively.
30///
31/// ```ignore
32/// let (var_keys, var_values) = unzip_key_values(args.vars);
33/// let (prompt_keys, prompt_values) = unzip_key_values(args.prompts);
34/// let (secret_keys, secret_values) = unzip_key_values(args.secrets);
35///
36/// let env = Env::new(var_keys, prompt_keys, secret_keys);
37///
38/// let runtime_env: RuntimeEnv = RuntimeEnv {
39///     vars: var_values,
40///     prompts: prompt_values,
41///     secrets: secret_values,
42/// };
43/// ```
44pub fn unzip_key_values(keys_values: Vec<(String, String)>) -> (Vec<String>, Vec<String>) {
45    let (keys, values): (Vec<String>, Vec<String>) = keys_values.into_iter().unzip();
46
47    (keys, values)
48}
49
50/// Parse a single key-value pair string. This is used to parse command line arguments.
51///
52/// ```
53/// use reqlang_expr::cliutil::parse_key_val;
54///
55/// let a = parse_key_val::<String, String>("a=1");
56///
57/// assert_eq!(("a".to_string(), "1".to_string()), a.unwrap());
58/// ```
59///
60/// Example of using parse_key_val with Clap
61///
62/// ```
63/// use clap::Parser;
64/// use reqlang_expr::cliutil::parse_key_val;
65///
66/// #[derive(Parser, Debug)]
67/// struct Args {
68///     #[arg(long, value_delimiter = ' ', num_args = 1.., value_parser = parse_key_val::<String, String>)]
69///     vars: Vec<(String, String)>
70/// }
71///
72/// let args = Args::parse_from(["test", "--vars", "key=value", "another_key=another_value"]);
73/// assert_eq!(args.vars, vec![("key".to_string(), "value".to_string()), ("another_key".to_string(), "another_value".to_string())]);
74/// ```
75///
76pub fn parse_key_val<T, U>(value: &str) -> Result<(T, U), Box<dyn Error + Send + Sync + 'static>>
77where
78    T: std::str::FromStr,
79    T::Err: Error + Send + Sync + 'static,
80    U: std::str::FromStr,
81    U::Err: Error + Send + Sync + 'static,
82{
83    let n = 2;
84
85    let parts: Vec<&str> = value.splitn(n, '=').collect();
86
87    if parts.len() != n {
88        return Err(format!("should be formatted as key=value pair: `{value}`").into());
89    }
90
91    let key = parts[0].parse()?;
92    let value = parts[1].parse()?;
93
94    Ok((key, value))
95}
96
97/// Read source code from a file at the provided path or from standard input if no path is provided.
98///
99/// # Usage
100///
101/// ## From a file:
102///
103/// ```
104/// use reqlang_expr::cliutil::read_in_source;
105///
106/// let source_code = read_in_source(Some("./spec/valid/call_id.expr".to_string()));
107///
108/// assert_eq!("(id (noop))", source_code);
109/// ```
110///
111/// ## From stdin:
112///
113/// ```ignore
114/// use reqlang_expr::cliutil::read_in_source;
115///
116/// // Assuming "(id (noop))" was passed to stdin...
117///
118/// let source_code = read_in_source(None);
119///
120/// assert_eq!("(id (noop))".to_string(), source_code);
121/// ```
122pub fn read_in_source(path: Option<String>) -> String {
123    match path {
124        Some(path) => {
125            //
126            read_to_string(path).expect("should be able to open file at path")
127        }
128        None => {
129            let mut source = String::new();
130
131            stdin().read_to_string(&mut source).unwrap();
132
133            source
134        }
135    }
136}