diff --git a/crates/runtime-factors/src/lib.rs b/crates/runtime-factors/src/lib.rs index c0b8baf4d4..4e2ef09d02 100644 --- a/crates/runtime-factors/src/lib.rs +++ b/crates/runtime-factors/src/lib.rs @@ -110,14 +110,15 @@ pub struct TriggerAppArgs { /// Variable(s) to be passed to the app /// - /// A single key-value pair can be passed as `key=value`. Alternatively, the - /// path to a JSON or TOML file may be given as `@file.json` or + /// A single key-value pair can be passed as `key=value`, or `key=@file` to + /// read the value from a text file. Alternatively, any number of key-value + /// pairs may be passed via a JSON or TOML file using the syntax `@file.json` or /// `@file.toml`. /// /// This option may be repeated. If the same key is specified multiple times /// the last value will be used. #[clap(long, value_parser = clap::value_parser!(VariableSource), - value_name = "KEY=VALUE | @FILE.json | @FILE.toml")] + value_name = "KEY=VALUE | KEY=@FILE | @FILE.json | @FILE.toml")] pub variable: Vec, /// Cache variables to avoid reading files twice diff --git a/crates/variables-static/src/source.rs b/crates/variables-static/src/source.rs index 642f51aea3..de342aa185 100644 --- a/crates/variables-static/src/source.rs +++ b/crates/variables-static/src/source.rs @@ -4,8 +4,13 @@ use std::{collections::HashMap, path::PathBuf, str::FromStr}; #[derive(Clone, Debug)] pub enum VariableSource { + /// The value of the given variable name is the given string Literal(String, String), + /// The value of the given variable name is the content of the given file (as a string) + FileContent(String, PathBuf), + /// The file contains a map of variable names to (string) values JsonFile(PathBuf), + /// The file contains a map of variable names to (string) values TomlFile(PathBuf), } @@ -13,6 +18,11 @@ impl VariableSource { pub fn get_variables(&self) -> anyhow::Result> { match self { VariableSource::Literal(key, val) => Ok([(key.to_string(), val.to_string())].into()), + VariableSource::FileContent(key, path) => { + let val = std::fs::read_to_string(path) + .with_context(|| format!("Failed to read {}.", quoted_path(path)))?; + Ok([(key.to_string(), val)].into()) + } VariableSource::JsonFile(path) => { let json_bytes = std::fs::read(path) .with_context(|| format!("Failed to read {}.", quoted_path(path)))?; @@ -43,7 +53,14 @@ impl FromStr for VariableSource { _ => bail!("variable files must end in .json or .toml"), } } else if let Some((key, val)) = s.split_once('=') { - Ok(VariableSource::Literal(key.to_string(), val.to_string())) + if let Some(path) = val.strip_prefix('@') { + Ok(VariableSource::FileContent( + key.to_string(), + PathBuf::from(path), + )) + } else { + Ok(VariableSource::Literal(key.to_string(), val.to_string())) + } } else { bail!("variables must be in the form 'key=value' or '@file'") } @@ -66,6 +83,14 @@ mod tests { Ok(other) => panic!("wrong variant {other:?}"), Err(err) => panic!("{err:?}"), } + match "k=@v.txt".parse() { + Ok(VariableSource::FileContent(key, path)) => { + assert_eq!(key, "k"); + assert_eq!(path, PathBuf::from("v.txt")); + } + Ok(other) => panic!("wrong variant {other:?}"), + Err(err) => panic!("{err:?}"), + } match "@file.json".parse() { Ok(VariableSource::JsonFile(_)) => {} Ok(other) => panic!("wrong variant {other:?}"), @@ -93,6 +118,17 @@ mod tests { assert_eq!(vars["k"], "v"); } + #[test] + fn file_content_get_variables() { + let mut file = tempfile::NamedTempFile::with_suffix(".txt").unwrap(); + file.write_all(br#"sausage time!"#).unwrap(); + let path = file.into_temp_path(); + let vars = VariableSource::FileContent("k".to_string(), path.to_path_buf()) + .get_variables() + .unwrap(); + assert_eq!(vars["k"], "sausage time!"); + } + #[test] fn json_get_variables() { let mut json_file = tempfile::NamedTempFile::with_suffix(".json").unwrap();