use std::{
collections::HashMap,
sync::{OnceLock, RwLock},
};
use pest::Parser;
use pest_derive::Parser;
#[derive(Parser)]
#[grammar = "src/parser/context.pest"]
struct VarPlaceholderParser;
pub fn inject_from_prompt(input: &str) -> String {
let mut output = String::new();
let mut pairs = VarPlaceholderParser::parse(Rule::text, input).unwrap();
let pair = pairs.next().unwrap();
for inner_pair in pair.into_inner() {
match inner_pair.as_rule() {
Rule::word => {
output.push_str(inner_pair.as_str());
}
Rule::input => {
let (key, default) = parse_input_pair(inner_pair.as_str());
if let Some(value) = prompt_inputs().read().unwrap().get(&key).cloned() {
output.push_str(value.as_str());
continue;
}
let prompt = match &default {
Some(def) => format!("Provide a value for \"{}\" (default: {})", key, def),
None => format!("Provide a value for \"{}\"", key),
};
let mut dialog = dialoguer::Input::new().with_prompt(prompt);
if let Some(def) = &default {
dialog = dialog.default(def.to_string());
}
let input: String = dialog.interact().unwrap();
prompt_inputs()
.write()
.unwrap()
.insert(key.clone(), input.clone());
output.push_str(input.as_str());
}
Rule::var => {
output.push_str(format!("{{{{{}}}}}", inner_pair.as_str()).as_str());
}
_ => {
unreachable!("unexpected rule: {:?}", inner_pair.as_rule());
}
}
}
output
}
pub fn inject_from_variable(input: &str, context: &HashMap<String, String>) -> String {
let mut output = String::new();
let mut pairs = VarPlaceholderParser::parse(Rule::text, input).unwrap();
let pair = pairs.next().unwrap();
for inner_pair in pair.into_inner() {
match inner_pair.as_rule() {
Rule::word => {
output.push_str(inner_pair.as_str());
}
Rule::var => {
let key = inner_pair.as_str().to_string();
if let Some(value) = context.get(&key) {
output.push_str(value);
} else {
output.push_str(format!("{{{{{}}}}}", key).as_str());
}
}
Rule::input => {
output.push_str(format!("[[{}]]", inner_pair.as_str()).as_str());
}
_ => {
unreachable!("unexpected rule: {:?}", inner_pair.as_rule());
}
}
}
output
}
pub fn set_prompt_inputs(inputs: HashMap<String, String>) {
let mut map = prompt_inputs().write().unwrap();
map.clear();
map.extend(inputs);
}
fn prompt_inputs() -> &'static RwLock<HashMap<String, String>> {
static PROMPT_INPUTS: OnceLock<RwLock<HashMap<String, String>>> = OnceLock::new();
PROMPT_INPUTS.get_or_init(|| RwLock::new(HashMap::new()))
}
fn parse_input_pair(raw: &str) -> (String, Option<String>) {
let mut parts = raw.splitn(2, '=');
let key = parts.next().unwrap().trim().to_string();
let default = parts.next().map(|value| value.trim().to_string());
(key, default)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_replace_variables() {
let input = "this is a test with a {{variable}}";
let mut context = HashMap::new();
context.insert("variable".to_string(), "value".to_string());
let output = inject_from_variable(input, &context);
assert_eq!(output, "this is a test with a value");
}
#[test]
fn should_use_provided_prompt_inputs() {
let mut inputs = HashMap::new();
inputs.insert("foo".to_string(), "bar".to_string());
set_prompt_inputs(inputs);
let output = inject_from_prompt("value [[ foo ]]");
assert_eq!(output, "value bar");
set_prompt_inputs(HashMap::new());
}
}