use super::*;
#[derive(Clone, Default)]
pub struct RunOptions {
pub now: Option<(i64, u32)>,
pub allow_shell: bool,
pub quantifier_bound: Option<usize>,
pub reproducible: bool,
pub ref_targets: std::collections::HashMap<String, NodeId>,
pub collation: Option<String>,
pub bonds: Option<std::rc::Rc<dyn crate::SentenceBonds>>,
pub budget: Option<crate::plan::Budget>,
pub plan: bool,
pub environments: Vec<crate::env::Environment>,
pub libraries: Vec<std::sync::Arc<dyn crate::library::CommandLibrary>>,
}
impl RunOptions {
pub fn shell_allowed(&self) -> bool {
self.allow_shell
|| self
.environments
.iter()
.any(|e| e.kind == crate::env::EnvKind::Shell)
}
pub fn environment(&self, alias: &str) -> Option<&crate::env::Environment> {
self.environments.iter().find(|e| e.alias == alias)
}
pub fn library(&self, prefix: &str) -> Option<&dyn crate::library::CommandLibrary> {
self.libraries
.iter()
.find(|l| l.prefix() == prefix)
.map(|l| l.as_ref())
}
}
#[derive(Default)]
pub(super) struct RunState {
pub(super) options: RunOptions,
pub(super) refusal: Option<String>,
pub(super) enclosing_topic: Vec<Value>,
pub(super) refs: std::collections::BTreeSet<String>,
pub(super) reads: Vec<Origins>,
pub(super) command: u32,
pub(super) scopes: HashMap<(usize, String), std::rc::Rc<ScopeMemo>>,
pub(super) last_out: LastLinks,
pub(super) last_in: LastLinks,
pub(super) children: HashMap<NodeId, std::rc::Rc<Vec<NodeId>>>,
}
thread_local! {
static RUNS: std::cell::RefCell<Vec<RunState>> = const { std::cell::RefCell::new(Vec::new()) };
static FINISHED: std::cell::RefCell<RunState> = std::cell::RefCell::new(RunState::default());
}
pub(super) fn with_run<R>(f: impl FnOnce(&mut RunState) -> R) -> R {
RUNS.with(|runs| {
let mut runs = runs.borrow_mut();
match runs.last_mut() {
Some(top) => f(top),
None => FINISHED.with(|fin| f(&mut fin.borrow_mut())),
}
})
}
pub(crate) struct RunGuard;
pub(crate) fn begin_run(options: Option<RunOptions>) -> RunGuard {
RUNS.with(|runs| {
let mut runs = runs.borrow_mut();
let options = options
.or_else(|| runs.last().map(|r| r.options.clone()))
.unwrap_or_default();
runs.push(RunState {
options,
..RunState::default()
});
});
RunGuard
}
pub(crate) fn in_run() -> bool {
RUNS.with(|runs| !runs.borrow().is_empty())
}
pub(crate) fn run_option<R>(f: impl FnOnce(&RunOptions) -> R) -> R {
with_run(|s| f(&s.options))
}
impl Drop for RunGuard {
fn drop(&mut self) {
if let Some(done) = RUNS.with(|runs| runs.borrow_mut().pop()) {
FINISHED.with(|fin| *fin.borrow_mut() = done);
}
}
}
pub(super) fn with_enclosing_topic<R>(scope: &Scope<'_>, f: impl FnOnce() -> R) -> R {
let carried = scope.topic.is_some();
if let Some(t) = scope.topic {
with_run(|s| s.enclosing_topic.push(t.clone()));
}
let out = f();
if carried {
with_run(|s| {
s.enclosing_topic.pop();
});
}
out
}
pub(super) fn capture<T>(f: impl FnOnce() -> T) -> (T, Origins) {
with_run(|s| s.reads.push(Origins::default()));
let out = f();
let origins = with_run(|s| s.reads.pop()).unwrap_or_default();
(out, origins)
}
pub(super) fn discard<T>(f: impl FnOnce() -> T) -> T {
with_run(|s| s.reads.push(Origins::default()));
let out = f();
with_run(|s| s.reads.pop());
out
}
pub(super) fn note(node: NodeId) {
with_run(|s| {
let command = s.command;
if let Some(top) = s.reads.last_mut() {
top.insert(Origin {
node,
command: command,
});
}
});
}
pub(super) fn note_all(o: &Origins) {
with_run(|s| {
if let Some(top) = s.reads.last_mut() {
top.union(o);
}
});
}
pub(super) fn command_now() -> u32 {
with_run(|s| s.command)
}
pub(super) fn record_ref(id: String) {
with_run(|s| {
s.refs.insert(id);
});
}
pub(super) fn ref_target(id: &str) -> Option<NodeId> {
run_option(|o| o.ref_targets.get(id).copied())
}
pub(crate) fn take_refs() -> Vec<String> {
FINISHED.with(|f| {
std::mem::take(&mut f.borrow_mut().refs)
.into_iter()
.collect()
})
}
pub fn record_refusal(msg: String) {
with_run(|s| {
if s.refusal.is_none() {
s.refusal = Some(msg);
}
});
}
pub fn fetched<T: Default, E: std::fmt::Display>(
result: Result<T, E>,
what: impl FnOnce() -> String,
) -> T {
match result {
Ok(v) => v,
Err(e) => {
record_refusal(format!("{}: {e}", what()));
T::default()
}
}
}
pub fn fetched_opt<T, E: std::fmt::Display>(
result: Result<T, E>,
what: impl FnOnce() -> String,
) -> Option<T> {
match result {
Ok(v) => Some(v),
Err(e) => {
record_refusal(format!("{}: {e}", what()));
None
}
}
}
pub fn take_refusal() -> Option<String> {
FINISHED.with(|f| f.borrow_mut().refusal.take())
}
pub(crate) fn quantifier_bound(adapter: &impl AstAdapter) -> usize {
run_option(|o| o.quantifier_bound).unwrap_or_else(|| adapter.quantifier_bound())
}
pub(crate) fn gate(query: &Query, run: &RunOptions) -> crate::Result<()> {
if uses_shell_query(query) && !run.shell_allowed() {
return Err(crate::QuarbError::unsupported(
"the sh(...) command runs external commands; pass --allow-shell",
));
}
let mut checked: Vec<String> = Vec::new();
for (call, agg) in prefixed_calls(query) {
let (prefix, name) = crate::env::split_env(&call.name).expect("prefixed");
if let Some(lib) = run.library(prefix) {
if !checked.iter().any(|p| p == prefix) {
lib.available().map_err(|why| {
crate::QuarbError::unsupported(format!(
"library '{prefix}' is not usable here: {why}"
))
})?;
checked.push(prefix.to_string());
}
crate::library::check(lib, name, &call, agg).map_err(crate::QuarbError::unsupported)?;
} else if run.environment(prefix).is_none() {
return Err(crate::QuarbError::unsupported(format!(
"environment '{prefix}' is not mounted; mount it from the command line \
('{prefix}=exec:DIR', '{prefix}=shell:')"
)));
}
}
Ok(())
}
pub(super) fn prefixed_calls(q: &Query) -> Vec<(FnCall, bool)> {
struct Calls(Vec<(FnCall, bool)>);
impl visit::Visitor for Calls {
fn command(&mut self, s: &Command) {
match s {
Command::Func(c) if crate::env::split_env(&c.name).is_some() => {
self.0.push((c.clone(), false))
}
Command::Agg(c) if crate::env::split_env(&c.name).is_some() => {
self.0.push((c.clone(), true))
}
_ => {}
}
visit::walk_command(self, s);
}
}
let mut v = Calls(Vec::new());
v.query(q);
v.0
}
pub(super) fn uses_shell_query(q: &Query) -> bool {
struct Shell(bool);
impl visit::Visitor for Shell {
fn command(&mut self, s: &Command) {
if matches!(s, Command::Func(c) | Command::Agg(c) if c.name == "sh") {
self.0 = true;
}
visit::walk_command(self, s);
}
}
let mut v = Shell(false);
v.query(q);
v.0
}