summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2023-08-12 10:41:46 +0000
committerLars Wirzenius <liw@liw.fi>2023-08-12 10:41:46 +0000
commitffaaf40c145be2efc8b1ab6f687b7d34234a6c12 (patch)
tree8f110dd2f19aad292a641a6501f9352ef00387d7
parent808304c479726e165ac4ac38f4866bd28e38ac7b (diff)
parentd54f93508bf29edb9d6b5f655ab9b518f18ecaec (diff)
downloadsubplot-ffaaf40c145be2efc8b1ab6f687b7d34234a6c12.tar.gz
Merge branch 'add-location-to-rust-runner' into 'main'
Add support in rust runner for locations See merge request subplot/subplot!350
-rw-r--r--share/rust/template/macros.rs.tera2
-rw-r--r--share/rust/template/template.rs.tera2
-rw-r--r--src/bindings.rs2
-rw-r--r--src/codegen.rs17
-rw-r--r--src/html.rs2
-rw-r--r--src/matches.rs7
-rw-r--r--src/scenarios.rs4
-rw-r--r--src/steps.rs4
-rw-r--r--subplotlib-derive/src/lib.rs5
-rw-r--r--subplotlib/src/scenario.rs22
-rw-r--r--subplotlib/src/step.rs20
11 files changed, 65 insertions, 22 deletions
diff --git a/share/rust/template/macros.rs.tera b/share/rust/template/macros.rs.tera
index 104eb23..cc1d9c4 100644
--- a/share/rust/template/macros.rs.tera
+++ b/share/rust/template/macros.rs.tera
@@ -27,5 +27,5 @@
)
{% endif -%}
{% endfor -%}
- .build(format!("{} {}", "{{step.kind | lower}}", base64_decode("{{step.text | base64}}")))
+ .build(format!("{} {}", "{{step.kind | lower}}", base64_decode("{{step.text | base64}}")), {{ step.origin | location }})
{%- endmacro builder -%}
diff --git a/share/rust/template/template.rs.tera b/share/rust/template/template.rs.tera
index 65fb755..447129c 100644
--- a/share/rust/template/template.rs.tera
+++ b/share/rust/template/template.rs.tera
@@ -30,7 +30,7 @@ lazy_static! {
#[test]
#[allow(non_snake_case)]
fn {{ scenario.title | nameslug }}() {
- let mut scenario = Scenario::new(&base64_decode("{{scenario.title | base64}}"));
+ let mut scenario = Scenario::new(&base64_decode("{{scenario.title | base64}}"), {{ scenario.origin | location }});
{% for step in scenario.steps %}
let step = {{ macros::builder(stepfn=step.function, step=step) }};
{%- if step.cleanup %}
diff --git a/src/bindings.rs b/src/bindings.rs
index e94b64e..b629992 100644
--- a/src/bindings.rs
+++ b/src/bindings.rs
@@ -242,7 +242,7 @@ impl Binding {
let caps = self.regex.captures(step_text)?;
// If there is only one capture, it's the whole string.
- let mut m = MatchedStep::new(self, template);
+ let mut m = MatchedStep::new(self, template, step.origin().clone());
if caps.len() == 1 {
m.append_part(PartialStep::uncaptured(step_text));
return Some(m);
diff --git a/src/codegen.rs b/src/codegen.rs
index 41b5556..5855d8b 100644
--- a/src/codegen.rs
+++ b/src/codegen.rs
@@ -1,3 +1,4 @@
+use crate::html::Location;
use crate::{resource, Document, SubplotError, TemplateSpec};
use std::collections::HashMap;
use std::fs::File;
@@ -57,6 +58,7 @@ fn tera(tmplspec: &TemplateSpec, templatename: &str) -> Result<Tera, SubplotErro
tera.register_filter("base64", base64);
tera.register_filter("nameslug", nameslug);
tera.register_filter("commentsafe", commentsafe);
+ tera.register_filter("location", locationfilter);
let dirname = tmplspec.template_filename().parent().unwrap();
for helper in tmplspec.helpers() {
let helper_path = dirname.join(helper);
@@ -93,6 +95,21 @@ fn base64(v: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
}
}
+fn locationfilter(v: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
+ let location: Location = serde_json::from_value(v.clone())?;
+ Ok(Value::String(format!(
+ "{:?}",
+ match location {
+ Location::Known {
+ filename,
+ line,
+ col,
+ } => format!("{}:{}:{}", filename.display(), line, col),
+ Location::Unknown => "unknown".to_string(),
+ }
+ )))
+}
+
fn nameslug(name: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
match name {
Value::String(s) => {
diff --git a/src/html.rs b/src/html.rs
index c5edc5b..f863727 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -99,7 +99,7 @@ pub fn parse(filename: &Path, markdown: &str) -> Result<Element, HtmlError> {
HeadingLevel::H5 => ElementTag::H5,
HeadingLevel::H6 => ElementTag::H6,
};
- let mut h = Element::new(tag);
+ let mut h = Element::new(tag).with_location(loc);
if let Some(id) = id {
h.push_attribute(Attribute::new("id", id));
}
diff --git a/src/matches.rs b/src/matches.rs
index 9130641..f8de59a 100644
--- a/src/matches.rs
+++ b/src/matches.rs
@@ -1,3 +1,4 @@
+use crate::html::Location;
use crate::Binding;
use crate::Scenario;
use crate::StepKind;
@@ -12,6 +13,7 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct MatchedScenario {
title: String,
+ origin: Location,
steps: Vec<MatchedStep>,
}
@@ -29,6 +31,7 @@ impl MatchedScenario {
.collect();
Ok(MatchedScenario {
title: scen.title().to_string(),
+ origin: scen.origin().clone(),
steps: steps?,
})
}
@@ -73,6 +76,7 @@ pub struct MatchedStep {
kind: StepKind,
pattern: String,
text: String,
+ origin: Location,
parts: Vec<PartialStep>,
function: Option<String>,
cleanup: Option<String>,
@@ -81,12 +85,13 @@ pub struct MatchedStep {
impl MatchedStep {
/// Return a new empty match. Empty means it has no step parts.
- pub fn new(binding: &Binding, template: &str) -> MatchedStep {
+ pub fn new(binding: &Binding, template: &str, origin: Location) -> MatchedStep {
let bimpl = binding.step_impl(template);
MatchedStep {
kind: binding.kind(),
pattern: binding.pattern().to_string(),
text: "".to_string(),
+ origin,
parts: vec![],
function: bimpl.clone().map(|b| b.function().to_owned()),
cleanup: bimpl.and_then(|b| b.cleanup().map(String::from)),
diff --git a/src/scenarios.rs b/src/scenarios.rs
index 7039d98..17549d2 100644
--- a/src/scenarios.rs
+++ b/src/scenarios.rs
@@ -45,6 +45,10 @@ impl Scenario {
pub fn add(&mut self, step: &ScenarioStep) {
self.steps.push(step.clone());
}
+
+ pub(crate) fn origin(&self) -> &Location {
+ &self.origin
+ }
}
#[cfg(test)]
diff --git a/src/steps.rs b/src/steps.rs
index 43e66e2..d9d1725 100644
--- a/src/steps.rs
+++ b/src/steps.rs
@@ -84,6 +84,10 @@ impl ScenarioStep {
}
Ok(ScenarioStep::new(kind, keyword, &joined, origin))
}
+
+ pub(crate) fn origin(&self) -> &Location {
+ &self.origin
+ }
}
impl fmt::Display for ScenarioStep {
diff --git a/subplotlib-derive/src/lib.rs b/subplotlib-derive/src/lib.rs
index 83e4adf..b10d3b3 100644
--- a/subplotlib-derive/src/lib.rs
+++ b/subplotlib-derive/src/lib.rs
@@ -350,10 +350,11 @@ fn process_step(mut input: ItemFn) -> proc_macro2::TokenStream {
impl Builder {
#(#fieldfns)*
- pub fn build(self, step_text: String) -> ScenarioStep {
+ pub fn build(self, step_text: String, location: &'static str) -> ScenarioStep {
ScenarioStep::new(step_text, move |ctx, _defuse_poison|
#builder_body,
- |scenario| register_contexts(scenario)
+ |scenario| register_contexts(scenario),
+ location,
)
}
}
diff --git a/subplotlib/src/scenario.rs b/subplotlib/src/scenario.rs
index 8c734b9..9bcc43d 100644
--- a/subplotlib/src/scenario.rs
+++ b/subplotlib/src/scenario.rs
@@ -107,7 +107,7 @@ where
C: ContextElement,
{
fn new() -> Self {
- Self(PhantomData::default())
+ Self(PhantomData)
}
}
@@ -159,6 +159,7 @@ where
/// This container allows the running of code within a given scenario context.
pub struct ScenarioContext {
title: String,
+ location: &'static str,
inner: Container![],
hooks: RefCell<Vec<Box<dyn ScenarioContextHookKind>>>,
}
@@ -212,9 +213,10 @@ impl Debug for ScenarioContext {
}
impl ScenarioContext {
- fn new(title: &str) -> Self {
+ fn new(title: &str, location: &'static str) -> Self {
Self {
title: title.to_string(),
+ location,
inner: <Container![]>::new(),
hooks: RefCell::new(Vec::new()),
}
@@ -303,12 +305,12 @@ impl ScenarioContext {
/// ```
/// # use subplotlib::prelude::*;
///
-/// let mut scenario = Scenario::new("example scenario");
+/// let mut scenario = Scenario::new("example scenario", "unknown");
///
/// let run_step = subplotlib::steplibrary::runcmd::run::Builder::default()
/// .argv0("true")
/// .args("")
-/// .build("when I run true".to_string());
+/// .build("when I run true".to_string(), "unknown");
/// scenario.add_step(run_step, None);
///
/// ```
@@ -319,9 +321,9 @@ pub struct Scenario {
impl Scenario {
/// Create a new scenario with the given title
- pub fn new(title: &str) -> Self {
+ pub fn new(title: &str, location: &'static str) -> Self {
Self {
- contexts: ScenarioContext::new(title),
+ contexts: ScenarioContext::new(title, location),
steps: Vec::new(),
}
}
@@ -372,7 +374,11 @@ impl Scenario {
// Firstly, we start all the contexts
let mut ret = Ok(());
let mut highest_start = None;
- println!("scenario: {}", self.contexts.title());
+ println!(
+ "{}: scenario: {}",
+ self.contexts.location,
+ self.contexts.title()
+ );
for (i, hook) in self.contexts.hooks.borrow().iter().enumerate() {
let res = hook.scenario_starts(&self.contexts);
if res.is_err() {
@@ -387,7 +393,7 @@ impl Scenario {
if ret.is_ok() {
let mut highest = None;
for (i, step) in self.steps.iter().map(|(step, _)| step).enumerate() {
- println!(" step: {}", step.step_text());
+ println!("{}: step: {}", step.location(), step.step_text());
let mut highest_prep = None;
for (i, prep) in self.contexts.hooks.borrow().iter().enumerate() {
let res = prep.step_starts(&self.contexts, step.step_text());
diff --git a/subplotlib/src/step.rs b/subplotlib/src/step.rs
index 12b4ddf..76b9193 100644
--- a/subplotlib/src/step.rs
+++ b/subplotlib/src/step.rs
@@ -25,11 +25,12 @@ type StepFunc = dyn Fn(&ScenarioContext, bool) -> StepResult;
/// # use subplotlib::prelude::*;
///
/// let step = ScenarioStep::new(
-/// "when everything works".to_string(), |ctx, ok| Ok(()), |scen| ()
+/// "when everything works".to_string(), |ctx, ok| Ok(()), |scen| (), "unknown"
/// );
/// ```
pub struct ScenarioStep {
step_text: String,
+ location: &'static str,
func: Box<StepFunc>,
reg: Box<dyn Fn(&Scenario)>,
}
@@ -40,13 +41,14 @@ impl ScenarioStep {
/// This is used to construct a scenario step from a function which
/// takes the scenario context container. This will generally be
/// called from the generated build method for the step.
- pub fn new<F, R>(step_text: String, func: F, reg: R) -> Self
+ pub fn new<F, R>(step_text: String, func: F, reg: R, location: &'static str) -> Self
where
F: Fn(&ScenarioContext, bool) -> StepResult + 'static,
R: Fn(&Scenario) + 'static,
{
Self {
step_text,
+ location,
func: Box::new(func),
reg: Box::new(reg),
}
@@ -55,13 +57,13 @@ impl ScenarioStep {
/// Attempt to render a message.
/// If something panics with a type other than a static string or
/// a formatted string then we won't be able to render it sadly.
- fn render_panic(name: &str, err: Box<dyn Any + Send>) -> String {
+ fn render_panic(location: &str, name: &str, err: Box<dyn Any + Send>) -> String {
if let Some(msg) = err.downcast_ref::<&str>() {
- format!("step {name} panic'd: {msg}")
+ format!("{location}: step {name} panic'd: {msg}")
} else if let Some(msg) = err.downcast_ref::<String>() {
- format!("step {name} panic'd: {msg}")
+ format!("{location}: step {name} panic'd: {msg}")
} else {
- format!("step {name} panic'd")
+ format!("{location}: step {name} panic'd")
}
}
@@ -73,7 +75,7 @@ impl ScenarioStep {
// subsequent step calls may not be sound. There's not a lot we can
// do to ensure things are good except try.
let func = AssertUnwindSafe(|| (*self.func)(context, defuse_poison));
- catch_unwind(func).map_err(|e| Self::render_panic(self.step_text(), e))?
+ catch_unwind(func).map_err(|e| Self::render_panic(self.location(), self.step_text(), e))?
}
/// Return the full text of this step
@@ -85,4 +87,8 @@ impl ScenarioStep {
pub(crate) fn register_contexts(&self, scenario: &Scenario) {
(*self.reg)(scenario);
}
+
+ pub(crate) fn location(&self) -> &'static str {
+ self.location
+ }
}