#[macro_export]
macro_rules! match_tree {
($($tt:tt)*) => {
$crate::codegen::builder::match_rule($crate::__match_entries!($($tt)*))
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __match_entries {
() => { vec![] };
(($($key:literal),+ $(,)?) => { $($inner:tt)* } $(, $($rest:tt)*)?) => {{
let mut v = vec![(
$crate::codegen::builder::MatchKey::Tuple(vec![$($key.to_owned()),+]),
$crate::codegen::builder::MatchValue::Nested($crate::__match_entries!($($inner)*)),
)];
$(v.extend($crate::__match_entries!($($rest)*));)?
v
}};
(($($key:literal),+ $(,)?) => $effect:expr $(, $($rest:tt)*)?) => {{
let mut v = vec![(
$crate::codegen::builder::MatchKey::Tuple(vec![$($key.to_owned()),+]),
$crate::codegen::builder::MatchValue::Effect($effect),
)];
$(v.extend($crate::__match_entries!($($rest)*));)?
v
}};
(Mode($name:expr) => { $($inner:tt)* } $(, $($rest:tt)*)?) => {{
let mut v = vec![(
$crate::codegen::builder::MatchKey::Mode($name.to_owned()),
$crate::codegen::builder::MatchValue::Nested($crate::__match_entries!($($inner)*)),
)];
$(v.extend($crate::__match_entries!($($rest)*));)?
v
}};
(Mode($name:expr) => $effect:expr $(, $($rest:tt)*)?) => {{
let mut v = vec![(
$crate::codegen::builder::MatchKey::Mode($name.to_owned()),
$crate::codegen::builder::MatchValue::Effect($effect),
)];
$(v.extend($crate::__match_entries!($($rest)*));)?
v
}};
(Tool($name:expr) => { $($inner:tt)* } $(, $($rest:tt)*)?) => {{
let mut v = vec![(
$crate::codegen::builder::MatchKey::Tool($name.to_owned()),
$crate::codegen::builder::MatchValue::Nested($crate::__match_entries!($($inner)*)),
)];
$(v.extend($crate::__match_entries!($($rest)*));)?
v
}};
(Tool($name:expr) => $effect:expr $(, $($rest:tt)*)?) => {{
let mut v = vec![(
$crate::codegen::builder::MatchKey::Tool($name.to_owned()),
$crate::codegen::builder::MatchValue::Effect($effect),
)];
$(v.extend($crate::__match_entries!($($rest)*));)?
v
}};
($key:expr => { $($inner:tt)* } $(, $($rest:tt)*)?) => {{
let mut v = vec![(
$crate::codegen::builder::MatchKey::from($key),
$crate::codegen::builder::MatchValue::Nested($crate::__match_entries!($($inner)*)),
)];
$(v.extend($crate::__match_entries!($($rest)*));)?
v
}};
($key:expr => $effect:expr $(, $($rest:tt)*)?) => {{
let mut v = vec![(
$crate::codegen::builder::MatchKey::from($key),
$crate::codegen::builder::MatchValue::Effect($effect),
)];
$(v.extend($crate::__match_entries!($($rest)*));)?
v
}};
}
#[macro_export]
macro_rules! kwargs {
($($key:ident = $val:expr),* $(,)?) => {
vec![$((stringify!($key), <$crate::codegen::ast::Expr as From<_>>::from($val))),*]
};
}
#[cfg(test)]
mod tests {
use crate::codegen::ast::{Expr, Stmt};
use crate::codegen::builder::*;
use crate::codegen::serialize::serialize;
#[test]
fn match_tree_simple() {
let expr = match_tree! {
"Bash" => allow(),
};
let src = serialize(&[Stmt::Expr(expr)]);
assert_eq!(src, "{tool(\"Bash\"): allow()}\n");
}
#[test]
fn match_tree_nested() {
let expr = match_tree! {
"Bash" => {
"git" => {
"push" => {
"--force" => deny(),
},
},
},
};
let src = serialize(&[Stmt::Expr(expr)]);
assert!(src.contains("\"Bash\""));
assert!(src.contains("\"git\""));
assert!(src.contains("\"--force\": deny()"));
}
#[test]
fn match_tree_multiple_entries() {
let expr = match_tree! {
"Bash" => {
"push" => {
"--force" => deny(),
"--force-with-lease" => deny(),
},
"reset" => {
"--hard" => deny(),
},
},
};
let src = serialize(&[Stmt::Expr(expr)]);
assert!(src.contains("\"--force\": deny()"));
assert!(src.contains("\"--force-with-lease\": deny()"));
assert!(src.contains("\"--hard\": deny()"));
}
#[test]
fn match_tree_tuple_key() {
let expr = match_tree! {
"Bash" => {
("git", "cargo") => allow(),
},
};
let src = serialize(&[Stmt::Expr(expr)]);
assert!(src.contains("(\"git\", \"cargo\"): allow()"));
}
#[test]
fn kwargs_bools() {
let kw: Vec<(&str, Expr)> = kwargs!(read = true, write = false);
assert_eq!(kw.len(), 2);
assert_eq!(kw[0], ("read", Expr::Bool(true)));
assert_eq!(kw[1], ("write", Expr::Bool(false)));
}
#[test]
fn kwargs_strings() {
let kw: Vec<(&str, Expr)> = kwargs!(name = "cwd");
assert_eq!(kw[0], ("name", Expr::String("cwd".to_owned())));
}
#[test]
fn kwargs_exprs() {
let kw: Vec<(&str, Expr)> = kwargs!(default = deny(), sandbox = allow());
assert_eq!(kw.len(), 2);
}
#[test]
fn match_tree_runtime_key() {
let bin_name = "git";
let expr = match_tree! {
"Bash" => {
bin_name => allow(),
},
};
let src = serialize(&[Stmt::Expr(expr)]);
assert!(src.contains("\"git\": allow()"));
}
#[test]
fn full_example_with_macros() {
let stmts = vec![
load_std(&["when", "tool", "policy", "settings", "allow", "deny", "ask"]),
Stmt::Blank,
Stmt::Expr(settings(ask(), None)),
Stmt::Blank,
Stmt::Expr(policy(
"test",
ask(),
vec![
match_tree! {
"Bash" => {
("git", "cargo") => allow(),
},
},
tool_match(&["Read"], allow()),
],
None,
)),
];
let src = serialize(&stmts);
assert!(src.contains("(\"git\", \"cargo\"): allow()"));
assert!(src.contains("tool(\"Read\"): allow()"));
}
#[test]
fn match_tree_mode_key() {
let expr = match_tree! {
Mode("plan") => {
Tool("Read") => allow(),
Tool("ExitPlanMode") => allow(),
},
};
let src = serialize(&[Stmt::Expr(expr)]);
assert!(src.contains("Mode(\"plan\")"));
assert!(src.contains("tool(\"Read\"): allow()"));
assert!(src.contains("tool(\"ExitPlanMode\"): allow()"));
}
#[test]
fn match_tree_mixed_keys() {
let expr = match_tree! {
Mode("plan") => {
Tool("Read") => allow(),
},
"Bash" => {
"git" => allow(),
},
};
let src = serialize(&[Stmt::Expr(expr)]);
assert!(src.contains("Mode(\"plan\")"));
assert!(src.contains("\"Bash\""));
}
}