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}