cookbook icon indicating copy to clipboard operation
cookbook copied to clipboard

ingredient amounts given as intervals are not treated properly

Open mkaut opened this issue 1 year ago β€’ 8 comments

In many recipes, amounts of some ingredients are specified as an interval, such as "4-5 eggs". If I do this in the Cookbook, the upper bound is completely ignored, i.e., it shows as "4 eggs". I tested this with both "-" and "–", the result is the same.

Even worse, if I use spaces around the dash ("4 - 5 eggs"), the "- 5" part is interpreted as part of the name of the ingredients, so if I ask for twice the number of portions, it would say "8 - 5 eggs".

Is this a bug, or is there some different way to specify imprecise amounts that I did not think of?

mkaut avatar Apr 07 '24 18:04 mkaut

I still have this same issue with the following ingredient given with ranges:

1-2 EL Zitronen- oder Rote-Beete-Saft

Error message is:

(i) The ingredient cannot be recalculated due to incorrect syntax. Please ensure the syntax follows this format: amount unit ingredient and that a specific number of portions is set for this function to work correctly. Examples: 200 g carrots or 1 pinch of salt.

Similar to common n/m fractions #2004 and unicode Β½ fractions #2498 Nextcloud Cookbook should also support 1-2 ranges and uncounted ingredients / without an amount #2233.

This can be eventually solved together with the alternative ingredients 1 fresh or {3} dried leaves #2581

stefan123t avatar Nov 26 '24 21:11 stefan123t

@j0hannesr0th here is some problem with the indredient recalculator. Can you give your advice here?

christianlupus avatar Dec 02 '24 15:12 christianlupus

Hi @christianlupus

Good question! I don't have a solution for this, but here are some thoughts:

  • If you've made a recipe and know whether it's better with 1 or 2 EL Zitronen- oder Rote-Beete-Saft, just remove the other number, and you're good to go.
  • No matter how many edge cases we resolve, users will always find more:
    • 6 servings = β…ž-1 pt 3 Mile Island Iced Tea or Β½ fl oz 7 and 7
    • 10 servings = 1.46-1.67 pt 3 Mile Island Iced Tea or 0.83 fl oz 7 and 7
    • 12 servings = 1.75-2 pt 3 Mile Island Iced Tea or 1 fl oz 7 and 7
    • I can calculate it by hand, but the regex for this would be really ugly.
  • Where do you want to draw the line on what’s supported and what’s not?

j0hannesr0th avatar Dec 02 '24 18:12 j0hannesr0th

I can calculate it by hand, but the regex for this would be really ugly.

@j0hannesr0th your comment made me smile and thus got me interested into the challenge 😺

I have been checking the current regex expressions and tried to yield something more general:

https://github.com/nextcloud/cookbook/blob/993b6a1368346bd32bf8f410fc3c998e82eec6ec/src/js/yieldCalculator.js#L1-L33

I noticed that A-z in [a-zA-z] matches some overlapping character ranges/classes (including [\]^_ and backtick `), it might therefor be better to use \p{L} instead:

  • a-z matches a single character in the range between a (index 97) and z (index 122) (case sensitive)
  • A-z matches a single character in the range between A (index 65) and z (index 122) (case sensitive)

Actually I am unsure whether it will work with Latin-1 characters like in french Γ©, etc. ?

I could come up with the following regex that should match a basic grammar for <amount> ( fractions, numerator(s) / dividend(s) and/or range(s) ) with <unit>, <ingredient> and <optional> / <alternative> suffixes as suggested in #2581:

^(?<amount>(?<range_from>(?<numerator_from>\d+(?:\.(?:\d+))?|\p{N}+)(?:\/(?<dividend_from>\d+(?:\.(?:\d+))?|\p{N}+))?)(?:(?:-)(?<range_to>(?<numerator_to>\d+(?:\.(?:\d+))?|\p{N}+)(?:\/(?<dividend_to>\d+(?:\.(?:\d+))?|\p{N}+))?))?)(?:\s)(?<unit>\p{L}*)\s(?<ingredient>.*?)(?:\s(?<option>\(optional\)))?(?:\s(?<alternative>(?:or|oder|ou)))?$

You can test it on the below regex101 instance against the following set of examples: https://regex101.com/r/2dAXUG/4

1 pt 3 Mile Island Iced Tea
1 fl oz 7 and 7 or
Β½ fl oz 7 and 7
1.46 pt 3 Mile Island Iced Tea
0.83 fl oz 7 and 7
β…ž pt 3 Mile Island Iced Tea or
1.3/2.4 things to do
1.3-2.4 things to do
⁹⁹/₁₀₀ pt Fractions
β…ž-1 pt 3 Mile Island Iced Tea or
1.46-1.67 pt 3 Mile Island Iced Tea or
1.75-2 pt 3 Mile Island Iced Tea or
Β²/3 pt 3 Mile Island Iced Tea
3/β‚„ pt 3 Mile Island Iced Tea
Β²/3-3/β‚„ pt 3 Mile Island Iced Tea
1 ingredient fresh (optional) or
3-7 ingredient dried
5 other things or
3 other stuff or
1 other jiffy
7/8 Prisen Salz oder
β·β·β„β‚‡β‚ˆ Prisen Salz oder
1 Prise Salz
1 ingredient fresh or
3-5 ingredient dried
5 other things or
3 other stuff or
1 other jiffy
1 ingredient fresh (optional) or
3 ingredient dried (optional)
5 other things (optional) or
3 other stuff (optional)

stefan123t avatar Dec 03 '24 00:12 stefan123t

@stefan123t as I said: not impossible but kind of ugly but not that easy to understand.

And a few weeks later some other edge case, which we haven't considered, comes up.

For me it's fine if you want to commit your code.

j0hannesr0th avatar Dec 03 '24 05:12 j0hannesr0th

Yeah, no one will be able to read this as suggested nor maintain in case of bugs/problems.

Can't we split up the RegEx into building blocks and define something like with BNF typically done? I am thinking along this lines:

const reInteger = '([0-9]+)'
const reFloat = '([0-9]*\\.[0-9]+)'
const reNumber = `(${reInteger}|${reFloat})`
// ...
const re = new Regex(...)

The problem with is kind of parsing is that there are many groups and you have to be super careful to not get caught in assembling the actual numeric value. Also, I am thinking if it makes sense to define various functions that try to parse a certain structure (e.g. only decimal numbers) and will throw a defined exception or return a defined value to indicate that parsing failed. Then, one could just iterate a list of functions, try each entry and return the first matching result. If no result was found, parsing actually failed. This would allow to give human readable names on the functions and also keep the complexity at bay. This can be combined with the RegEx building blocks as defined above.

Do you think this was resulting in a cleaner structure and more maintainability?

christianlupus avatar Dec 03 '24 09:12 christianlupus

@christianlupus splitting the rather unwieldy RegEx I construed into BNF form would be a really good idea as it also allows to reuse and eventually even optimize some of the building blocks!

Do you think the named capture groups (?<name>...) I provided are generally useful, or would you rather do away with them and use the anonymous capture groups (?:...) which are assigned to speaking / named variables here anyway ?

https://github.com/nextcloud/cookbook/blob/993b6a1368346bd32bf8f410fc3c998e82eec6ec/src/js/yieldCalculator.js#L1-L5 https://github.com/nextcloud/cookbook/blob/993b6a1368346bd32bf8f410fc3c998e82eec6ec/src/js/yieldCalculator.js#L49-L59

Note: I have not been able to include the ⁄ or \x{2044} for #2498 as in β·β·β„β‚‡β‚ˆ Prisen Salz or β·β·β„β‚‡β‚ˆ Prisen Salz into the RegEx yet.

stefan123t avatar Dec 03 '24 15:12 stefan123t

Could it make sense to split the ingredient-fied into two parts - amount/volume/whatever and ingredient? This would allow for simpler parsing of the amount-field, and you would avoid all the warnings in the recipes when there are lines with e.g. "Salt to taste" or "Basil for garnish" etc.

Simple javascript could use a similar regexp as today when you read the recipe from JSON or paste data into the ingredient field, to auto-fill the "amount" field - which can then be manually modified if the parser is incorrect somehow.

Olen avatar Jan 09 '25 11:01 Olen