diff options
author | Lars Wirzenius <liw@liw.fi> | 2023-08-12 10:41:46 +0000 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2023-08-12 10:41:46 +0000 |
commit | ffaaf40c145be2efc8b1ab6f687b7d34234a6c12 (patch) | |
tree | 8f110dd2f19aad292a641a6501f9352ef00387d7 | |
parent | 808304c479726e165ac4ac38f4866bd28e38ac7b (diff) | |
parent | d54f93508bf29edb9d6b5f655ab9b518f18ecaec (diff) | |
download | subplot-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.tera | 2 | ||||
-rw-r--r-- | share/rust/template/template.rs.tera | 2 | ||||
-rw-r--r-- | src/bindings.rs | 2 | ||||
-rw-r--r-- | src/codegen.rs | 17 | ||||
-rw-r--r-- | src/html.rs | 2 | ||||
-rw-r--r-- | src/matches.rs | 7 | ||||
-rw-r--r-- | src/scenarios.rs | 4 | ||||
-rw-r--r-- | src/steps.rs | 4 | ||||
-rw-r--r-- | subplotlib-derive/src/lib.rs | 5 | ||||
-rw-r--r-- | subplotlib/src/scenario.rs | 22 | ||||
-rw-r--r-- | subplotlib/src/step.rs | 20 |
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 + } } |