From 37493a8a30dc5b616047f5368af6c32694632e56 Mon Sep 17 00:00:00 2001 From: "Francisco J. Sanchez" Date: Sun, 19 Nov 2023 17:25:45 +0100 Subject: [PATCH] components in text mode are now text --- CHANGELOG.md | 1 + bindings/src/lib.rs | 16 ++- src/analysis/event_consumer.rs | 210 +++++++++++++++++---------------- src/lib.rs | 11 +- 4 files changed, 132 insertions(+), 106 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b998893..c92df29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ vice versa. - Added `ScaledRecipe::group_cookware`. - Rework `GroupedQuantity` API and add `GroupedValue`. +- Ignored ingredients in text mode are now added as text. ### Fixed - Text steps were ignored in `components` mode. diff --git a/bindings/src/lib.rs b/bindings/src/lib.rs index 074345e..cbe087a 100644 --- a/bindings/src/lib.rs +++ b/bindings/src/lib.rs @@ -17,7 +17,7 @@ pub fn parse_recipe(input: String) -> CooklangRecipe { let converter = Converter::empty(); let mut parser = PullParser::new(&input, extensions); - let parsed = parse_events(&mut parser, extensions, &converter, None) + let parsed = parse_events(&mut parser, &input, extensions, &converter, None) .take_output() .unwrap(); @@ -32,10 +32,16 @@ pub fn parse_metadata(input: String) -> CooklangMetadata { let parser = PullParser::new(&input, extensions); - let parsed = parse_events(parser.into_meta_iter(), extensions, &converter, None) - .map(|c| c.metadata.map) - .take_output() - .unwrap(); + let parsed = parse_events( + parser.into_meta_iter(), + &input, + extensions, + &converter, + None, + ) + .map(|c| c.metadata.map) + .take_output() + .unwrap(); // converting IndexMap into HashMap let _ = &(parsed).iter().for_each(|(key, value)| { diff --git a/src/analysis/event_consumer.rs b/src/analysis/event_consumer.rs index 07c24f3..217dfcc 100644 --- a/src/analysis/event_consumer.rs +++ b/src/analysis/event_consumer.rs @@ -33,6 +33,7 @@ macro_rules! warning { #[tracing::instrument(level = "debug", skip_all, target = "cooklang::analysis")] pub fn parse_events<'i>( events: impl Iterator>, + input: &'i str, extensions: Extensions, converter: &Converter, recipe_ref_checker: Option, @@ -57,6 +58,7 @@ pub fn parse_events<'i>( .flatten(); let col = RecipeCollector { + input, extensions, temperature_regex, converter, @@ -85,6 +87,7 @@ pub fn parse_events<'i>( } struct RecipeCollector<'i, 'c> { + input: &'i str, extensions: Extensions, temperature_regex: Option<&'c Regex>, converter: &'c Converter, @@ -113,7 +116,12 @@ const IMPLICIT_REF_WARN: &str = "The reference (&) is implicit"; impl<'i, 'c> RecipeCollector<'i, 'c> { fn parse_events(mut self, mut events: impl Iterator>) -> AnalysisResult { - let mut items = Vec::new(); + enum BlockBuffer { + Step(Vec), + Text(String), + } + let mut current_block = None; + let events = events.by_ref(); while let Some(event) = events.next() { match event { @@ -126,14 +134,33 @@ impl<'i, 'c> RecipeCollector<'i, 'c> { self.current_section = Section::new(name.map(|t| t.text_trimmed().into_owned())); } - Event::Start(_kind) => items.clear(), + Event::Start(kind) => { + let buffer = if self.define_mode == DefineMode::Text { + BlockBuffer::Text(String::new()) + } else { + match kind { + BlockKind::Step => BlockBuffer::Step(Vec::new()), + BlockKind::Text => BlockBuffer::Text(String::new()), + } + }; + current_block = Some(buffer) + } Event::End(kind) => { - let new_content = match kind { - _ if self.define_mode == DefineMode::Text => { - Content::Text(self.text_block(items)) + let new_content = match current_block { + Some(BlockBuffer::Step(items)) => { + assert_eq!(kind, BlockKind::Step); + Content::Step(Step { + items, + number: self.step_counter, + }) + } + Some(BlockBuffer::Text(text)) => { + assert!( + kind == BlockKind::Text || self.define_mode == DefineMode::Text, + ); + Content::Text(text) } - BlockKind::Step => Content::Step(self.step(items)), - BlockKind::Text => Content::Text(self.text_block(items)), + None => panic!("End event without Start"), }; // If define mode is ingredients, don't add the @@ -146,12 +173,16 @@ impl<'i, 'c> RecipeCollector<'i, 'c> { self.current_section.content.push(new_content); } - items = Vec::new(); + current_block = None; } item @ (Event::Text(_) | Event::Ingredient(_) | Event::Cookware(_) - | Event::Timer(_)) => items.push(item), + | Event::Timer(_)) => match &mut current_block { + Some(BlockBuffer::Step(items)) => self.in_step(item, items), + Some(BlockBuffer::Text(text)) => self.in_text(item, text), + None => panic!("Content outside block"), + }, Event::Error(e) => { // on a parser error, collect all other parser errors and @@ -316,110 +347,91 @@ impl<'i, 'c> RecipeCollector<'i, 'c> { self.ctx.warn(warn); } - fn step(&mut self, items: Vec>) -> Step { - let mut new_items = Vec::new(); - - for item in items { - match item { - Event::Text(text) => { - let t = text.text(); - if self.define_mode == DefineMode::Components { - // only issue warnings for alphanumeric characters - // so that the user can format the text with spaces, - // hypens or whatever. - if t.contains(|c: char| c.is_alphanumeric()) { - self.ctx.warn(warning!( - "Ignoring text in define components mode", - label!(text.span()) - )); - } - continue; // ignore text + fn in_step(&mut self, item: Event<'i>, items: &mut Vec) { + match item { + Event::Text(text) => { + let t = text.text(); + if self.define_mode == DefineMode::Components { + // only issue warnings for alphanumeric characters + // so that the user can format the text with spaces, + // hypens or whatever. + if t.contains(|c: char| c.is_alphanumeric()) { + self.ctx.warn(warning!( + "Ignoring text in define components mode", + label!(text.span()) + )); } + return; // ignore text + } - // it's only some if the extension is enabled - if let Some(re) = &self.temperature_regex { - debug_assert!(self.extensions.contains(Extensions::TEMPERATURE)); - - let mut haystack = t.as_ref(); - while let Some((before, temperature, after)) = - find_temperature(haystack, re) - { - if !before.is_empty() { - new_items.push(Item::Text { - value: before.to_string(), - }); - } + // it's only some if the extension is enabled + if let Some(re) = &self.temperature_regex { + debug_assert!(self.extensions.contains(Extensions::TEMPERATURE)); - new_items.push(Item::InlineQuantity { - index: self.content.inline_quantities.len(), - }); - self.content.inline_quantities.push(temperature); - - haystack = after; - } - if !haystack.is_empty() { - new_items.push(Item::Text { - value: haystack.to_string(), + let mut haystack = t.as_ref(); + while let Some((before, temperature, after)) = find_temperature(haystack, re) { + if !before.is_empty() { + items.push(Item::Text { + value: before.to_string(), }); } - } else { - new_items.push(Item::Text { - value: t.into_owned(), + + items.push(Item::InlineQuantity { + index: self.content.inline_quantities.len(), }); - } - } + self.content.inline_quantities.push(temperature); - Event::Ingredient(..) | Event::Cookware(..) | Event::Timer(..) => { - let new_component = match item { - Event::Ingredient(i) => Item::Ingredient { - index: self.ingredient(i), - }, - Event::Cookware(c) => Item::Cookware { - index: self.cookware(c), - }, - Event::Timer(t) => Item::Timer { - index: self.timer(t), - }, - _ => unreachable!(), - }; - new_items.push(new_component); + haystack = after; + } + if !haystack.is_empty() { + items.push(Item::Text { + value: haystack.to_string(), + }); + } + } else { + items.push(Item::Text { + value: t.into_owned(), + }); } - _ => panic!("Unexpected event in step: {item:?}"), - }; - } + } - Step { - items: new_items, - number: self.step_counter, - } + Event::Ingredient(i) => items.push(Item::Ingredient { + index: self.ingredient(i), + }), + Event::Cookware(i) => items.push(Item::Cookware { + index: self.cookware(i), + }), + Event::Timer(i) => items.push(Item::Timer { + index: self.timer(i), + }), + + _ => panic!("Unexpected event in step: {item:?}"), + }; } - fn text_block(&mut self, items: Vec>) -> String { - let mut s = String::new(); - for ev in items { - match ev { - Event::Text(t) => s += t.text().as_ref(), - Event::Ingredient(_) | Event::Cookware(_) | Event::Timer(_) => { - assert_eq!( - self.define_mode, - DefineMode::Text, - "Non text event in text block outside define mode text" - ); + fn in_text(&mut self, ev: Event<'i>, s: &mut String) { + match ev { + Event::Text(t) => s.push_str(t.text().as_ref()), + Event::Ingredient(_) | Event::Cookware(_) | Event::Timer(_) => { + assert_eq!( + self.define_mode, + DefineMode::Text, + "Non text event in text block outside define mode text" + ); - // ignore component - let (c, s) = match ev { - Event::Ingredient(i) => ("ingredient", i.span()), - Event::Cookware(c) => ("cookware", c.span()), - Event::Timer(t) => ("timer", t.span()), - _ => unreachable!(), - }; - self.ctx - .warn(warning!(format!("Ignoring {c} in text mode"), label!(s))); - } - _ => panic!("Unexpected event in text block: {ev:?}"), + // ignore component + let (c, span) = match ev { + Event::Ingredient(i) => ("ingredient", i.span()), + Event::Cookware(c) => ("cookware", c.span()), + Event::Timer(t) => ("timer", t.span()), + _ => unreachable!(), + }; + self.ctx + .warn(warning!(format!("Ignoring {c} in text mode"), label!(span))); + s.push_str(&self.input[span.range()]); } + _ => panic!("Unexpected event in text block: {ev:?}"), } - s } fn ingredient(&mut self, ingredient: Located>) -> usize { diff --git a/src/lib.rs b/src/lib.rs index ecc7ca2..39bf9f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -263,6 +263,7 @@ impl CooklangParser { let mut parser = parser::PullParser::new(input, self.extensions); analysis::parse_events( &mut parser, + input, self.extensions, &self.converter, recipe_ref_checker, @@ -276,8 +277,14 @@ impl CooklangParser { pub fn parse_metadata(&self, input: &str) -> MetadataResult { let parser = parser::PullParser::new(input, self.extensions); let meta_events = parser.into_meta_iter(); - analysis::parse_events(meta_events, Extensions::empty(), &self.converter, None) - .map(|c| c.metadata) + analysis::parse_events( + meta_events, + input, + Extensions::empty(), + &self.converter, + None, + ) + .map(|c| c.metadata) } }