Skip to content

Commit d7491bb

Browse files
authored
Merge pull request #30 from cooklang/feat/servings
feat: enabled scaling
2 parents 7fcf655 + e76eecd commit d7491bb

File tree

11 files changed

+144
-398
lines changed

11 files changed

+144
-398
lines changed

bindings/src/lib.rs

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
use std::sync::Arc;
22

33
use cooklang::aisle::parse as parse_aisle_config_original;
4-
use cooklang::analysis::parse_events;
5-
use cooklang::parser::PullParser;
6-
use cooklang::{Converter, Extensions};
74

85
pub mod aisle;
96
pub mod model;
@@ -12,43 +9,29 @@ use aisle::*;
129
use model::*;
1310

1411
#[uniffi::export]
15-
pub fn parse_recipe(input: String) -> CooklangRecipe {
16-
let extensions = Extensions::empty();
17-
let converter = Converter::empty();
18-
19-
let mut parser = PullParser::new(&input, extensions);
20-
let parsed = parse_events(
21-
&mut parser,
22-
&input,
23-
extensions,
24-
&converter,
25-
Default::default(),
26-
)
27-
.unwrap_output();
28-
29-
into_simple_recipe(&parsed)
12+
pub fn parse_recipe(input: String, scaling_factor: u32) -> CooklangRecipe {
13+
let parser = cooklang::CooklangParser::canonical();
14+
15+
let (parsed, _warnings) = parser.parse(&input).into_result().unwrap();
16+
17+
let scaled = parsed.scale(scaling_factor, parser.converter());
18+
19+
into_simple_recipe(&scaled)
3020
}
3121

3222
#[uniffi::export]
33-
pub fn parse_metadata(input: String) -> CooklangMetadata {
23+
pub fn parse_metadata(input: String, scaling_factor: u32) -> CooklangMetadata {
3424
let mut metadata = CooklangMetadata::new();
35-
let extensions = Extensions::empty();
36-
let converter = Converter::empty();
25+
let parser = cooklang::CooklangParser::canonical();
3726

38-
let parser = PullParser::new(&input, extensions);
27+
let (parsed, _warnings) = parser.parse(&input)
28+
.into_result()
29+
.unwrap();
3930

40-
let parsed = parse_events(
41-
parser.into_meta_iter(),
42-
&input,
43-
extensions,
44-
&converter,
45-
Default::default(),
46-
)
47-
.map(|c| c.metadata.map)
48-
.unwrap_output();
31+
let scaled = parsed.scale(scaling_factor, parser.converter());
4932

5033
// converting IndexMap into HashMap
51-
let _ = &(parsed).iter().for_each(|(key, value)| {
34+
let _ = &(scaled.metadata.map).iter().for_each(|(key, value)| {
5235
if let (Some(key), Some(value)) = (key.as_str(), value.as_str()) {
5336
metadata.insert(key.to_string(), value.to_string());
5437
}
@@ -116,7 +99,7 @@ pub fn parse_aisle_config(input: String) -> Arc<AisleConf> {
11699
}
117100

118101
#[uniffi::export]
119-
pub fn combine_ingredients(ingredients: &Vec<Ingredient>) -> IngredientList {
102+
pub fn combine_ingredients(ingredients: &[Ingredient]) -> IngredientList {
120103
let indices = (0..ingredients.len()).map(|i| i as u32).collect();
121104
combine_ingredients_selected(ingredients, &indices)
122105
}
@@ -149,6 +132,7 @@ mod tests {
149132
a test @step @salt{1%mg} more text
150133
"#
151134
.to_string(),
135+
1
152136
);
153137

154138
assert_eq!(
@@ -225,6 +209,7 @@ source: https://google.com
225209
a test @step @salt{1%mg} more text
226210
"#
227211
.to_string(),
212+
1
228213
);
229214

230215
assert_eq!(
@@ -372,6 +357,7 @@ dried oregano
372357
Cook @onions{3%large} until brown
373358
"#
374359
.to_string(),
360+
1
375361
);
376362

377363
let first_section = recipe
@@ -430,6 +416,7 @@ add @tomatoes{400%g}
430416
simmer for 10 minutes
431417
"#
432418
.to_string(),
419+
1
433420
);
434421
let first_section = recipe
435422
.sections
@@ -494,6 +481,7 @@ Mix @flour{200%g} and @water{50%ml} together until smooth.
494481
Combine @cheese{100%g} and @spinach{50%g}, then season to taste.
495482
"#
496483
.to_string(),
484+
1
497485
);
498486

499487
let mut sections = recipe.sections.into_iter();

bindings/src/model.rs

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ use std::collections::HashMap;
22

33
use cooklang::model::Item as OriginalItem;
44
use cooklang::quantity::{
5-
Quantity as OriginalQuantity, ScalableValue as OriginalScalableValue, Value as OriginalValue,
5+
Quantity as OriginalQuantity, Value as OriginalValue
66
};
7-
use cooklang::ScalableRecipe as OriginalRecipe;
7+
use cooklang::ScaledRecipe as OriginalRecipe;
88

99
#[derive(uniffi::Record, Debug)]
1010
pub struct CooklangRecipe {
@@ -174,19 +174,19 @@ trait Amountable {
174174
fn extract_amount(&self) -> Amount;
175175
}
176176

177-
impl Amountable for OriginalQuantity<OriginalScalableValue> {
177+
impl Amountable for OriginalQuantity<OriginalValue> {
178178
fn extract_amount(&self) -> Amount {
179-
let quantity = extract_quantity(self.value());
179+
let quantity = extract_value(self.value());
180180

181181
let units = self.unit().as_ref().map(|u| u.to_string());
182182

183183
Amount { quantity, units }
184184
}
185185
}
186186

187-
impl Amountable for OriginalScalableValue {
187+
impl Amountable for OriginalValue {
188188
fn extract_amount(&self) -> Amount {
189-
let quantity = extract_quantity(self);
189+
let quantity = extract_value(self);
190190

191191
Amount {
192192
quantity,
@@ -195,14 +195,6 @@ impl Amountable for OriginalScalableValue {
195195
}
196196
}
197197

198-
fn extract_quantity(value: &OriginalScalableValue) -> Value {
199-
match value {
200-
OriginalScalableValue::Fixed(value) => extract_value(value),
201-
OriginalScalableValue::Linear(value) => extract_value(value),
202-
OriginalScalableValue::ByServings(values) => extract_value(values.first().unwrap()),
203-
}
204-
}
205-
206198
fn extract_value(value: &OriginalValue) -> Value {
207199
match value {
208200
OriginalValue::Number(num) => Value::Number { value: num.value() },
@@ -427,8 +419,8 @@ pub(crate) fn into_simple_recipe(recipe: &OriginalRecipe) -> CooklangRecipe {
427419
}
428420
}
429421

430-
impl From<&cooklang::Ingredient<OriginalScalableValue>> for Ingredient {
431-
fn from(ingredient: &cooklang::Ingredient<OriginalScalableValue>) -> Self {
422+
impl From<&cooklang::Ingredient<OriginalValue>> for Ingredient {
423+
fn from(ingredient: &cooklang::Ingredient<OriginalValue>) -> Self {
432424
Ingredient {
433425
name: ingredient.name.clone(),
434426
amount: ingredient.quantity.as_ref().map(|q| q.extract_amount()),
@@ -437,17 +429,17 @@ impl From<&cooklang::Ingredient<OriginalScalableValue>> for Ingredient {
437429
}
438430
}
439431

440-
impl From<&cooklang::Cookware<OriginalScalableValue>> for Cookware {
441-
fn from(cookware: &cooklang::Cookware<OriginalScalableValue>) -> Self {
432+
impl From<&cooklang::Cookware<OriginalValue>> for Cookware {
433+
fn from(cookware: &cooklang::Cookware<OriginalValue>) -> Self {
442434
Cookware {
443435
name: cookware.name.clone(),
444436
amount: cookware.quantity.as_ref().map(|q| q.extract_amount()),
445437
}
446438
}
447439
}
448440

449-
impl From<&cooklang::Timer<OriginalScalableValue>> for Timer {
450-
fn from(timer: &cooklang::Timer<OriginalScalableValue>) -> Self {
441+
impl From<&cooklang::Timer<OriginalValue>> for Timer {
442+
fn from(timer: &cooklang::Timer<OriginalValue>) -> Self {
451443
Timer {
452444
name: Some(timer.name.clone().unwrap_or_default()),
453445
amount: timer.quantity.as_ref().map(|q| q.extract_amount()),

extensions.md

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -112,21 +112,6 @@ special keys are between square brackets.
112112
>> [duplicate]: default
113113
@water{1} @&water{2}
114114
```
115-
- `auto scale` | `auto_scale`
116-
- `true`. All quantities in ingredients have the implicit auto scale
117-
marker[^1] (`*`). This does not apply when the quantity has a text value,
118-
because text can't be scaled automatically.
119-
```cooklang
120-
>> [auto scale]: true
121-
@water{1}
122-
-- is the same as
123-
>> [auto scale]: false
124-
@water{1*}
125-
```
126-
127-
Note that ingredients with fixed scaling for each serving size[^1] are not
128-
affected by the auto scale mode.
129-
- `false` | `default`. The default cooklang behaviour.
130115
131116
## Temperature
132117
Find temperatures in the text, without any markers. In the future this may be

src/analysis/event_consumer.rs

Lines changed: 23 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ pub fn parse_events<'i, 'c>(
7474

7575
define_mode: DefineMode::All,
7676
duplicate_mode: DuplicateMode::New,
77-
auto_scale_ingredients: false,
7877
old_style_metadata: true,
7978
old_style_metadata_used: vec![],
8079
ctx: SourceReport::empty(),
@@ -96,7 +95,6 @@ struct RecipeCollector<'i, 'c> {
9695

9796
define_mode: DefineMode,
9897
duplicate_mode: DuplicateMode,
99-
auto_scale_ingredients: bool,
10098
old_style_metadata: bool,
10199
old_style_metadata_used: Vec<Span>,
102100
ctx: SourceReport,
@@ -360,19 +358,14 @@ impl<'i> RecipeCollector<'i, '_> {
360358
"reference" | "ref" => self.duplicate_mode = DuplicateMode::Reference,
361359
_ => self.ctx.error(invalid_value(vec!["new", "reference"])),
362360
},
363-
"auto scale" | "auto_scale" => match value_t.as_ref() {
364-
"true" => self.auto_scale_ingredients = true,
365-
"false" | "default" => self.auto_scale_ingredients = false,
366-
_ => self.ctx.error(invalid_value(vec!["true", "false"])),
367-
},
368361
_ => {
369362
self.ctx.warn(
370363
warning!(
371364
format!("Unknown config metadata key: {key_t}"),
372365
label!(key.span())
373366
)
374367
.hint(
375-
"Possible config keys are '[mode]', '[duplicate]' and '[auto scale]'",
368+
"Possible config keys are '[mode]' and '[duplicate]''",
376369
),
377370
);
378371
if self.old_style_metadata {
@@ -1030,77 +1023,33 @@ impl<'i> RecipeCollector<'i, '_> {
10301023
}
10311024

10321025
fn value(&mut self, value: parser::QuantityValue, is_ingredient: bool) -> ScalableValue {
1033-
let mut marker_span = None;
1034-
match &value {
1035-
parser::QuantityValue::Single {
1036-
value,
1037-
auto_scale: Some(auto_scale_marker),
1038-
} => {
1039-
marker_span = Some(*auto_scale_marker);
1040-
if value.is_text() {
1041-
self.ctx.error(
1042-
error!(
1043-
"Text value with auto scale marker",
1044-
label!(auto_scale_marker, "remove this")
1045-
)
1046-
.hint("Text cannot be scaled"),
1047-
);
1048-
}
1049-
}
1050-
parser::QuantityValue::Many(v) => {
1051-
const CONFLICT: &str = "Many values conflict";
1052-
if let crate::scale::Servings(Some(s)) = &self.content.data {
1053-
if s.len() != v.len() {
1054-
let mut err = error!(
1055-
format!(
1056-
"{CONFLICT}: {} servings defined but {} values in the quantity",
1057-
s.len(),
1058-
v.len()
1059-
),
1060-
label!(value.span(), "number of values do not match servings")
1061-
);
1026+
let parser::QuantityValue { value, scaling_lock } = value;
1027+
let has_scaling_lock = scaling_lock.is_some();
1028+
let is_text = value.is_text();
10621029

1063-
let meta_span = self
1064-
.locations
1065-
.metadata
1066-
.get(&StdKey::Servings)
1067-
.map(|(_, value)| value.span());
1068-
if let Some(meta_span) = meta_span {
1069-
err = err.label(label!(meta_span, "servings defined here"))
1070-
}
1071-
self.ctx.error(err);
1072-
}
1073-
} else {
1074-
self.ctx.error(error!(
1075-
format!(
1076-
"{CONFLICT}: no servings defined but {} values in the quantity",
1077-
v.len()
1078-
),
1079-
label!(value.span())
1080-
));
1081-
}
1082-
}
1083-
_ => {}
1030+
// For ingredients without text values and without scaling lock, use Linear
1031+
if is_ingredient && !is_text && !has_scaling_lock {
1032+
return ScalableValue::Linear(value.into_inner());
10841033
}
1085-
let mut v = ScalableValue::from_ast(value);
10861034

1087-
if is_ingredient && self.auto_scale_ingredients {
1088-
match v {
1089-
ScalableValue::Fixed(value) if !value.is_text() => v = ScalableValue::Linear(value),
1090-
ScalableValue::Linear(_) => {
1091-
self.ctx.warn(
1092-
warning!(
1093-
"Redundant auto scale marker",
1094-
label!(marker_span.unwrap(), "remove this")
1095-
)
1096-
.hint("Every ingredient is already marked to auto scale"),
1097-
);
1098-
}
1099-
_ => {}
1100-
};
1035+
// Warn if scaling lock is used unnecessarily (on non-ingredients or text values)
1036+
if has_scaling_lock {
1037+
let mut warning = warning!(
1038+
"Unnecessary scaling lock modifier",
1039+
label!(value.span(), "this scaling lock has no effect")
1040+
);
1041+
1042+
if !is_ingredient {
1043+
warning.add_hint("Only ingredients can be scaled, scaling lock is not needed here");
1044+
} else if is_text {
1045+
warning.add_hint("Text values cannot be scaled, scaling lock is not needed here");
1046+
}
1047+
1048+
self.ctx.warn(warning);
11011049
}
11021050

1103-
v
1051+
// Everything else uses Fixed
1052+
ScalableValue::Fixed(value.into_inner())
11041053
}
11051054

11061055
fn resolve_reference<C: RefComponent>(

src/parser/mod.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,12 @@
2929
//!
3030
//! note = OpenParen (!CloseParen ANY)* CloseParen
3131
//!
32-
//! quantity = num_val Whitespace !(unit_sep | auto_scale | val_sep) unit
33-
//! | val (val_sep val)* auto_scale? (unit_sep unit)?
32+
//! quantity = sc_lock? num_val Whitespace !unit_sep unit
33+
//! | val (unit_sep unit)?
34+
//! sc_lock = Whitespace Eq Whitespace
3435
//!
3536
//! unit = (!CloseBrace ANY)*
3637
//!
37-
//! val_sep = Whitespace Or Whitespace
38-
//! auto_scale = Whitespace Star Whitespace
3938
//! unit_sep = Whitespace Percent Whitespace
4039
//!
4140
//! val = num_val | text_val

0 commit comments

Comments
 (0)