use crate::error::PromptError;
use crate::parser::{parse, Token};
use crate::renderer::RenderBuilder;
pub struct PromptTemplate {
pub(crate) tokens: Vec<Token>,
pub(crate) system_tokens: Option<Vec<Token>>,
pub(crate) required_vars: Vec<String>,
}
impl PromptTemplate {
pub fn new(template: &str) -> Result<Self, PromptError> {
let tokens = parse(template)?;
let required_vars = extract_required_vars(&tokens);
Ok(Self {
tokens,
system_tokens: None,
required_vars,
})
}
pub fn new_with_system(user: &str, system: Option<&str>) -> Result<Self, PromptError> {
let tokens = parse(user)?;
let mut required_vars = extract_required_vars(&tokens);
let system_tokens = match system {
Some(sys) => {
let sys_tokens = parse(sys)?;
for v in extract_required_vars(&sys_tokens) {
if !required_vars.contains(&v) {
required_vars.push(v);
}
}
Some(sys_tokens)
}
None => None,
};
Ok(Self {
tokens,
system_tokens,
required_vars,
})
}
#[doc(hidden)]
pub fn new_validated(template: &str, vars: &[&str]) -> Self {
let tokens = parse(template).expect("proc-macro validated template");
Self {
tokens,
system_tokens: None,
required_vars: vars.iter().map(|v| (*v).to_string()).collect(),
}
}
pub fn render(&self) -> RenderBuilder<'_> {
RenderBuilder::new(self)
}
}
fn extract_required_vars(tokens: &[Token]) -> Vec<String> {
let mut vars: Vec<String> = Vec::new();
let mut depth: u32 = 0;
for token in tokens {
match token {
Token::IfStart(_) => depth += 1,
Token::IfEnd => depth = depth.saturating_sub(1),
Token::Variable(name) if depth == 0 => {
if !vars.contains(name) {
vars.push(name.clone());
}
}
_ => {}
}
}
vars
}