mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-08 03:10:26 +00:00
Add $$ escape mechanism for variable expansion
Allows literal $ signs in prompts and DOT files by writing $$. Also refactors VariableExpansionTransform to use expand_vars instead of string replace, fixing a substring-matching bug with $goal. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4605f821e8
commit
69a203c2a8
3 changed files with 68 additions and 6 deletions
|
|
@ -189,7 +189,10 @@ pub fn expand_vars(source: &str, vars: &HashMap<String, String>) -> anyhow::Resu
|
|||
while i < len {
|
||||
if bytes[i] == b'$' {
|
||||
let start = i + 1;
|
||||
if start < len && (bytes[start].is_ascii_alphabetic() || bytes[start] == b'_') {
|
||||
if start < len && bytes[start] == b'$' {
|
||||
result.push('$');
|
||||
i = start + 1;
|
||||
} else if start < len && (bytes[start].is_ascii_alphabetic() || bytes[start] == b'_') {
|
||||
let mut end = start + 1;
|
||||
while end < len && (bytes[end].is_ascii_alphanumeric() || bytes[end] == b'_') {
|
||||
end += 1;
|
||||
|
|
@ -315,6 +318,27 @@ language = "python"
|
|||
assert_eq!(expand_vars("costs $5", &vars).unwrap(), "costs $5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expand_escaped_dollar() {
|
||||
let vars = HashMap::from([("name".to_string(), "world".to_string())]);
|
||||
assert_eq!(
|
||||
expand_vars("literal $$name here", &vars).unwrap(),
|
||||
"literal $name here"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expand_escaped_dollar_at_end() {
|
||||
let vars = HashMap::new();
|
||||
assert_eq!(expand_vars("trailing $$", &vars).unwrap(), "trailing $");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expand_escaped_dollar_before_non_ident() {
|
||||
let vars = HashMap::new();
|
||||
assert_eq!(expand_vars("price is $$5", &vars).unwrap(), "price is $5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_toml_with_sandbox() {
|
||||
let toml = r#"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::graph::{AttrValue, Edge, Graph, Node};
|
||||
use crate::stylesheet::{apply_stylesheet, parse_stylesheet};
|
||||
|
||||
|
|
@ -12,12 +14,14 @@ pub struct VariableExpansionTransform;
|
|||
impl Transform for VariableExpansionTransform {
|
||||
fn apply(&self, graph: &mut Graph) {
|
||||
let goal = graph.goal().to_string();
|
||||
let vars = HashMap::from([("goal".to_string(), goal)]);
|
||||
for node in graph.nodes.values_mut() {
|
||||
if let Some(AttrValue::String(prompt)) = node.attrs.get("prompt") {
|
||||
if prompt.contains("$goal") {
|
||||
let expanded = prompt.replace("$goal", &goal);
|
||||
node.attrs
|
||||
.insert("prompt".to_string(), AttrValue::String(expanded));
|
||||
if let Ok(expanded) = crate::cli::run_config::expand_vars(prompt, &vars) {
|
||||
if expanded != *prompt {
|
||||
node.attrs
|
||||
.insert("prompt".to_string(), AttrValue::String(expanded));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -192,6 +196,32 @@ mod tests {
|
|||
assert!(!graph.nodes["plan"].attrs.contains_key("prompt"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variable_expansion_escaped_dollar_goal() {
|
||||
let mut graph = Graph::new("test");
|
||||
graph.attrs.insert(
|
||||
"goal".to_string(),
|
||||
AttrValue::String("Fix bugs".to_string()),
|
||||
);
|
||||
|
||||
let mut node = Node::new("plan");
|
||||
node.attrs.insert(
|
||||
"prompt".to_string(),
|
||||
AttrValue::String("literal $$goal here".to_string()),
|
||||
);
|
||||
graph.nodes.insert("plan".to_string(), node);
|
||||
|
||||
let transform = VariableExpansionTransform;
|
||||
transform.apply(&mut graph);
|
||||
|
||||
let prompt = graph.nodes["plan"]
|
||||
.attrs
|
||||
.get("prompt")
|
||||
.and_then(AttrValue::as_str)
|
||||
.unwrap();
|
||||
assert_eq!(prompt, "literal $goal here");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stylesheet_transform_empty_stylesheet() {
|
||||
let mut graph = Graph::new("test");
|
||||
|
|
|
|||
|
|
@ -42,7 +42,15 @@ When launched with `arc run start run.toml`, Arc replaces `$repo_name`, `$repo_u
|
|||
|
||||
If a `$variable` in the DOT file has no matching entry in `[vars]`, Arc raises an error. This catches typos early — a misspelled `$langauge` fails immediately rather than passing a literal `$langauge` to the LLM.
|
||||
|
||||
A bare `$` not followed by an identifier character (e.g. `costs $5`) is left as-is.
|
||||
### Escaping `$`
|
||||
|
||||
To include a literal `$` in the output, write `$$`:
|
||||
|
||||
```dot
|
||||
test [prompt="The env var is $$HOME"]
|
||||
```
|
||||
|
||||
This produces `The env var is $HOME` without treating `$HOME` as a variable reference. A bare `$` not followed by an identifier character (e.g. `costs $5`) does not need escaping.
|
||||
|
||||
## The `$goal` variable
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue