use crate::doc::Doc;
use crate::{Cell, Executor};
use quarb::QueryResult;
use quarb::Value;
pub struct LocalExecutor {
doc: Doc,
url_roots: std::collections::HashMap<String, quarb::NodeId>,
refs: std::cell::RefCell<Vec<String>>,
now: (i64, u32),
allow_shell: bool,
environments: Vec<quarb::Environment>,
model: Option<quarb_model::Model>,
#[cfg(feature = "native")]
respec: Option<(Vec<crate::MountSpec>, crate::Options)>,
}
impl LocalExecutor {
pub fn new(doc: Doc, now: (i64, u32), allow_shell: bool) -> Self {
Self {
doc,
url_roots: Default::default(),
refs: Default::default(),
now,
allow_shell,
environments: Vec::new(),
#[cfg(feature = "native")]
respec: None,
model: None,
}
}
pub fn with_environments(mut self, environments: Vec<quarb::Environment>) -> Self {
if environments
.iter()
.any(|e| e.kind == quarb::EnvKind::Shell)
{
self.allow_shell = true;
}
self.environments = environments;
self
}
pub fn with_url_roots(mut self, map: std::collections::HashMap<String, quarb::NodeId>) -> Self {
self.url_roots = map;
self
}
pub fn with_model(mut self, model: Option<quarb_model::Model>) -> Self {
self.model = model;
self
}
#[cfg(feature = "native")]
pub fn with_respec(
doc: Doc,
now: (i64, u32),
allow_shell: bool,
specs: Vec<crate::MountSpec>,
opts: crate::Options,
) -> Self {
Self {
doc,
url_roots: Default::default(),
refs: Default::default(),
now,
allow_shell,
environments: Vec::new(),
respec: Some((specs, opts)),
model: None,
}
}
}
fn run_doc(doc: &Doc, query: &str, run: &quarb::Run) -> anyhow::Result<(Vec<Cell>, Vec<String>)> {
let out = doc.run(query, run).map_err(|e| anyhow::anyhow!("{e}"))?;
let cells = match out.result {
QueryResult::Nodes(nodes) => nodes
.into_iter()
.map(|n| Cell::Node(doc.render(n)))
.collect(),
QueryResult::Values(values) => values.into_iter().map(Cell::Value).collect(),
};
Ok((cells, out.refs))
}
fn run_doc_modeled(
doc: &Doc,
query: &str,
run: &quarb::Run,
model: &quarb_model::Model,
) -> anyhow::Result<(Vec<Cell>, Vec<String>)> {
let out = doc
.run_modeled(query, run, model)
.map_err(|e| anyhow::anyhow!("{e}"))?;
let cells = match out.result {
QueryResult::Nodes(nodes) => nodes
.into_iter()
.map(|n| Cell::Node(doc.render_modeled(n, model)))
.collect(),
QueryResult::Values(values) => values.into_iter().map(Cell::Value).collect(),
};
Ok((cells, out.refs))
}
impl LocalExecutor {
fn run_opts(&self) -> quarb::Run {
quarb::Run {
now: Some(self.now),
allow_shell: self.allow_shell,
environments: self.environments.clone(),
ref_targets: self.url_roots.clone(),
..quarb::Run::ambient()
}
}
}
impl Executor for LocalExecutor {
fn run(&self, query: &str) -> anyhow::Result<Vec<Cell>> {
let run = self.run_opts();
let (cells, refs) = if let Some(model) = &self.model {
run_doc_modeled(&self.doc, query, &run, model)?
} else {
run_doc(&self.doc, query, &run)?
};
*self.refs.borrow_mut() = refs;
Ok(cells)
}
fn refs(&self) -> Vec<String> {
self.refs.borrow().clone()
}
fn run_fresh(&self, query: &str) -> anyhow::Result<Vec<Cell>> {
#[cfg(feature = "native")]
if let Some((specs, opts)) = &self.respec {
let fresh = match specs.as_slice() {
[one] if one.name.is_none() => Doc::open(&one.path, opts)?,
many => Doc::mount_specs(many, opts)?,
};
return run_doc(&fresh, query, &self.run_opts()).map(|(cells, _)| cells);
}
self.run(query)
}
fn complete(&self, text: &str, cursor: usize) -> Vec<quarb::complete::Candidate> {
quarb::complete::complete_with(text, cursor, self.doc.base_dyn())
}
fn run_table(&self, query: &str) -> anyhow::Result<(Vec<Cell>, Option<String>)> {
if self.model.is_some() {
return Ok((self.run(query)?, None));
}
let run = self.run_opts();
let out = self
.doc
.run_rendering(query, &run)
.map_err(|e| anyhow::anyhow!("{e}"))?;
*self.refs.borrow_mut() = out.refs;
let Some(closing) = out.closing.filter(|c| !c.csv) else {
let cells = match out.result {
QueryResult::Nodes(nodes) => nodes
.into_iter()
.map(|n| Cell::Node(self.doc.render(n)))
.collect(),
QueryResult::Values(values) => values.into_iter().map(Cell::Value).collect(),
};
return Ok((cells, None));
};
let rows = self.doc.table_rows(out.result, &closing.columns);
let columns = (!closing.columns.is_empty()).then_some(closing.columns.as_slice());
let text = quarb::tabulate_with(&rows, columns, false, b',');
let html = quarb::table_html_plain(&rows, columns);
Ok((vec![Cell::Value(Value::Str(text))], Some(html)))
}
fn plan(
&self,
query: &str,
budget: Option<&quarb::plan::Budget>,
) -> anyhow::Result<quarb::plan::Plan> {
if self.model.is_some() {
anyhow::bail!("planning over a --model session is not supported yet");
}
Ok(self.doc.plan(query, &self.run_opts(), budget)?)
}
fn export(&self, query: &str, kind: &str) -> anyhow::Result<String> {
if self.model.is_some() {
anyhow::bail!("rendered export over a --model session is not supported yet");
}
self.doc.export(query, &self.run_opts(), kind)
}
}