use std::path::{Path, PathBuf};
use std::process::Command;
fn fixture() -> PathBuf {
let test = std::thread::current().name().unwrap_or("t").replace(':', "-");
let dir = std::env::temp_dir().join(format!("quarb-cursor-{}-{test}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(
dir.join("nest.json"),
r#"{"a":[{"k":1,"b":[{"v":10},{"v":20}]},{"k":2,"b":[{"v":30}]}]}"#,
)
.unwrap();
dir
}
fn qua(dir: &Path, args: &[&str]) -> (String, String, bool) {
let out = Command::new(env!("CARGO_BIN_EXE_qua"))
.current_dir(dir)
.env("QUARB_LOCALE", "C")
.env("HOME", dir)
.args(args)
.output()
.unwrap();
(
String::from_utf8_lossy(&out.stdout).trim().to_string(),
String::from_utf8_lossy(&out.stderr).trim().to_string(),
out.status.success(),
)
}
fn ok(dir: &Path, query: &str) -> String {
let (out, err, good) = qua(dir, &[query, "nest.json"]);
assert!(good, "{query}: {err}");
out
}
fn err(dir: &Path, query: &str) -> String {
let (out, err, good) = qua(dir, &[query, "nest.json"]);
assert!(!good, "{query} succeeded: {out}");
err
}
#[test]
fn the_position_is_a_reference() {
let d = fixture();
assert_eq!(
ok(&d, "/a/* | %(me = $$_; k = $$_::k)"),
"%(me = /a/0; k = 1)\n%(me = /a/1; k = 2)"
);
assert_eq!(ok(&d, "/a/* | $$_ | type"), "node\nnode");
assert_eq!(ok(&d, "/a/* | %(me = ((_)))"), "%(me = /a/0)\n%(me = /a/1)");
assert_eq!(ok(&d, "/a/*[$$_::k = 2]::k"), "2");
assert_eq!(ok(&d, "/a/*[$$_ = $$_]::k"), "1\n2");
assert_eq!(
ok(&d, "/a/* | %(me = $$_) | json"),
"{\"me\": \"/a/0\"}\n{\"me\": \"/a/1\"}"
);
}
#[test]
fn the_invoking_position() {
let d = fixture();
assert_eq!(
ok(&d, "/a/* | .s(/b/* | %(v = ::v; up = ($$__::k))) | $.s"),
"@%(@(v; up); %(10; 1); %(20; 1))\n%(v = 30; up = 2)"
);
assert_eq!(ok(&d, "/a/* | .s(/b/*[$$__::k = 1]::v) | $.s"), "@(10; 20)");
assert_eq!(ok(&d, "/a/* | .s(/b/* | .t(((__))::k) | $.t) | $.s"), "@(1; 1)\n2");
assert_eq!(ok(&d, "/a/* | ($$__::k ?? \"none\")"), "none\nnone");
assert!(err(&d, "/a/* | .s(/b/* | %(up = _::k))").contains("'_' is the cursor"));
}
#[test]
fn the_driver() {
let d = fixture();
assert_eq!(
ok(
&d,
"/a/* <=> /a/*/b/*[::v > 10 && $$0::k = 1] | %(k = ::k; v = $$1::v)"
),
"%(k = 1; v = 20)"
);
assert_eq!(
ok(&d, "/a/* <=> /a/*/b/*[::v = 30 && ((0))::k = 2] | $$1::v"),
"30"
);
assert!(err(&d, "/a/* | %(n = (/b/*[$$0::k = 2]::v))").contains("names the driver"));
assert!(err(&d, "/a/* | $$0::k").contains("names the driver"));
}
#[test]
fn the_driver_in_a_fragment_body() {
let d = fixture();
std::fs::write(
d.join("defs.quarb"),
"def &under: /a/*/b/*[::v > 10 && $$0::k = 1] ;\n\
def &twice: /a/*/b/*[::v > 10] | [$$0::k = 1] ;\n\
def &viaunder: &under ;\n\
macro &gen: ^ | \"/a/*/b/*[::v = 30 && $$0::k = 2]\" ;\n",
)
.unwrap();
let run = |q: &str| qua(&d, &["--defs", "defs.quarb", q, "nest.json"]);
let (out, err, good) = run("/a/*::k");
assert!(good, "{err}");
assert_eq!(out, "1\n2");
let (out, err, good) = run("/a/* <=> &under | %(k = ::k; v = $$1::v)");
assert!(good, "{err}");
assert_eq!(out, "%(k = 1; v = 20)");
let (out, err, good) = run("/a/* <=> &viaunder | $$1::v");
assert!(good, "{err}");
assert_eq!(out, "20");
let (out, err, good) = run("&under::v");
assert!(!good, "{out}");
assert!(err.contains("in expansion of '&under'") && err.contains("names the driver"), "{err}");
let (_, err, good) = run("/a/* | %(n = (&twice::v))");
assert!(!good);
assert!(err.contains("in expansion of '&twice'"), "{err}");
let (_, err, good) = run("&viaunder::v");
assert!(!good);
assert!(err.contains("in expansion of '&viaunder'"), "{err}");
let (out, err, good) = run("/a/* <=> &gen | $$1::v");
assert!(good, "{err}");
assert_eq!(out, "30");
let (_, err, good) = run("&gen::v");
assert!(!good);
assert!(err.contains("in expansion of '&gen'") && err.contains("names the driver"), "{err}");
}
#[test]
fn marks_are_regulae() {
let d = fixture();
let q = "/a/*[::k = 1] | .A | /b/* | .B | .v(::v) | ";
assert_eq!(
ok(&d, &format!("{q}@@.")),
"@(/a/0; /a/0/b/0)\n@(/a/0; /a/0/b/1)"
);
assert_eq!(
ok(&d, &format!("{q}%%.")),
"%(A = /a/0; B = /a/0/b/0)\n%(A = /a/0; B = /a/0/b/1)"
);
assert_eq!(
ok(&d, &format!("{q}%%.*")),
"%(A = /a/0; B = /a/0/b/0)\n%(A = /a/0; B = /a/0/b/1)"
);
assert_eq!(ok(&d, &format!("{q}@.")), "@(10)\n@(20)");
assert_eq!(ok(&d, &format!("{q}%.")), "%(v = 10)\n%(v = 20)");
assert_eq!(ok(&d, &format!("{q}%.*")), "%(v = 10)\n%(v = 20)");
assert_eq!(ok(&d, &format!("{q}$.1")), "10\n20");
assert_eq!(ok(&d, &format!("{q}$$.1::k")), "1\n1");
assert_eq!(ok(&d, &format!("{q}$$.2::v")), "10\n20");
assert_eq!(ok(&d, "/a/*[::k = 1] | .A | /b/* | $.A"), "/a/0\n/a/0");
assert_eq!(ok(&d, "/a/*[::k = 1] | .A | /b/* | $.A | type"), "node\nnode");
assert_eq!(ok(&d, "/a/*[::k = 1] | .A | /b/* | $.A-->::k"), "1\n1");
assert_eq!(
ok(&d, "/a/*[::k = 1] | .A | /b/* | ((A))/b/*::v @| count"),
"2"
);
assert_eq!(ok(&d, "/a/* | .n(::k) | .n(7) | $$.n::k @| count"), "0");
assert_eq!(ok(&d, "/a/* | .M($$_) | /b/* | $$.M::k"), "1\n2");
assert_eq!(ok(&d, "/a/* | .A | /b/*[$$_ != $$.A] @| count"), "3");
assert_eq!(ok(&d, "/a/* | .A | /b/*[$$.A = $$__] @| count"), "0");
}