Have a packaging unit for measures

This commit is contained in:
Jeremy Wall 2024-01-03 14:02:48 -05:00
parent 94e1987f09
commit 9022503e76
4 changed files with 148 additions and 38 deletions

View File

@ -156,12 +156,20 @@ impl IngredientAccumulator {
set.insert(recipe_title.clone()); set.insert(recipe_title.clone());
self.inner.insert(key, (i.clone(), set)); self.inner.insert(key, (i.clone(), set));
} else { } else {
let amt = match (self.inner[&key].0.amt, i.amt) { let amts = match (&self.inner[&key].0.amt, &i.amt) {
(Volume(rvm), Volume(lvm)) => Volume(lvm + rvm), (Volume(rvm), Volume(lvm)) => vec![Volume(lvm + rvm)],
(Count(lqty), Count(rqty)) => Count(lqty + rqty), (Count(lqty), Count(rqty)) => vec![Count(lqty + rqty)],
(Weight(lqty), Weight(rqty)) => Weight(lqty + rqty), (Weight(lqty), Weight(rqty)) => vec![Weight(lqty + rqty)],
(Package(lnm, lqty), Package(rnm, rqty)) => {
if lnm == rnm {
vec![Package(lnm.clone(), lqty + rqty)]
} else {
vec![Package(lnm.clone(), lqty.clone()), Package(rnm.clone(), rqty.clone())]
}
}
_ => unreachable!(), _ => unreachable!(),
}; };
for amt in amts {
self.inner.get_mut(&key).map(|(i, set)| { self.inner.get_mut(&key).map(|(i, set)| {
i.amt = amt; i.amt = amt;
set.insert(recipe_title.clone()); set.insert(recipe_title.clone());
@ -169,6 +177,7 @@ impl IngredientAccumulator {
} }
} }
} }
}
pub fn accumulate_from(&mut self, r: &Recipe) { pub fn accumulate_from(&mut self, r: &Recipe) {
self.accumulate_ingredients_for( self.accumulate_ingredients_for(

View File

@ -334,7 +334,14 @@ make_fn!(unit<StrIter, String>,
text_token!("kg"), text_token!("kg"),
text_token!("grams"), text_token!("grams"),
text_token!("gram"), text_token!("gram"),
text_token!("g")), text_token!("g"),
text_token!("pkg"),
text_token!("package"),
text_token!("bottle"),
text_token!("bot"),
text_token!("bag"),
text_token!("can")
),
_ => ws, _ => ws,
(u.to_lowercase().to_singular()) (u.to_lowercase().to_singular())
) )
@ -393,6 +400,7 @@ pub fn measure(i: StrIter) -> abortable_parser::Result<StrIter, Measure> {
"oz" => Weight(Oz(qty)), "oz" => Weight(Oz(qty)),
"kg" | "kilogram" => Weight(Kilogram(qty)), "kg" | "kilogram" => Weight(Kilogram(qty)),
"g" | "gram" => Weight(Gram(qty)), "g" | "gram" => Weight(Gram(qty)),
"pkg" | "package" | "can" | "bag" | "bottle" | "bot" => Measure::pkg(s, qty),
_u => { _u => {
eprintln!("Invalid unit: {}", _u); eprintln!("Invalid unit: {}", _u);
unreachable!() unreachable!()
@ -411,6 +419,8 @@ pub fn measure(i: StrIter) -> abortable_parser::Result<StrIter, Measure> {
} }
} }
// TODO(jwall): I think this is a mistake. We should rethink what noralizing means or we should
// remove it.
pub fn normalize_name(name: &str) -> String { pub fn normalize_name(name: &str) -> String {
let parts: Vec<&str> = name.split_whitespace().collect(); let parts: Vec<&str> = name.split_whitespace().collect();
if parts.len() >= 2 { if parts.len() >= 2 {

View File

@ -235,32 +235,30 @@ fn test_ingredient_name_parse() {
#[test] #[test]
fn test_ingredient_parse() { fn test_ingredient_parse() {
for (i, expected) in vec![ for (i, expected) in vec![
//( (
// "1 cup flour ", "1 cup flour ",
// Ingredient::new("flour", None, Volume(Cup(Quantity::Whole(1))), ""), Ingredient::new("flour", None, Volume(Cup(Quantity::Whole(1)))),
//), ),
//( (
// "\t1 cup flour ", "\t1 cup flour ",
// Ingredient::new("flour", None, Volume(Cup(Quantity::Whole(1))), ""), Ingredient::new("flour", None, Volume(Cup(Quantity::Whole(1)))),
//), ),
//( (
// "1 cup apple (chopped)", "1 cup apple (chopped)",
// Ingredient::new( Ingredient::new(
// "apple", "apple",
// Some("chopped".to_owned()), Some("chopped".to_owned()),
// Volume(Cup(Quantity::Whole(1))), Volume(Cup(Quantity::Whole(1))),
// "", ),
// ), ),
//), (
//( "1 cup apple (chopped) ",
// "1 cup apple (chopped) ", Ingredient::new(
// Ingredient::new( "apple",
// "apple", Some("chopped".to_owned()),
// Some("chopped".to_owned()), Volume(Cup(Quantity::Whole(1))),
// Volume(Cup(Quantity::Whole(1))), ),
// "", ),
// ),
//),
( (
"1 green bell pepper (chopped) ", "1 green bell pepper (chopped) ",
Ingredient::new( Ingredient::new(
@ -269,6 +267,46 @@ fn test_ingredient_parse() {
Count(Quantity::Whole(1)), Count(Quantity::Whole(1)),
), ),
), ),
(
"1 pkg green onion",
Ingredient::new(
"green onion",
None,
Package("pkg".into(), Quantity::Whole(1)),
),
),
(
"1 bottle green onion",
Ingredient::new(
"green onion",
None,
Package("bottle".into(), Quantity::Whole(1)),
),
),
(
"1 bot green onion",
Ingredient::new(
"green onion",
None,
Package("bot".into(), Quantity::Whole(1)),
),
),
(
"1 bag green onion",
Ingredient::new(
"green onion",
None,
Package("bag".into(), Quantity::Whole(1)),
),
),
(
"1 can baked beans",
Ingredient::new(
"baked bean",
None,
Package("can".into(), Quantity::Whole(1)),
),
),
] { ] {
match parse::ingredient(StrIter::new(i)) { match parse::ingredient(StrIter::new(i)) {
ParseResult::Complete(_, ing) => assert_eq!(ing, expected), ParseResult::Complete(_, ing) => assert_eq!(ing, expected),

View File

@ -21,7 +21,7 @@ use std::{
cmp::{Ordering, PartialEq, PartialOrd}, cmp::{Ordering, PartialEq, PartialOrd},
convert::TryFrom, convert::TryFrom,
fmt::Display, fmt::Display,
ops::{Add, Div, Mul, Sub}, ops::{Add, Div, Mul, Sub}, rc::Rc,
}; };
use num_rational::Ratio; use num_rational::Ratio;
@ -179,6 +179,20 @@ impl VolumeMeasure {
macro_rules! volume_op { macro_rules! volume_op {
($trait:ident, $method:ident) => { ($trait:ident, $method:ident) => {
impl $trait for &VolumeMeasure {
type Output = VolumeMeasure;
fn $method(self, lhs: Self) -> Self::Output {
let (l, r) = (self.get_ml(), lhs.get_ml());
let result = ML($trait::$method(l, r));
if self.metric() {
result.normalize()
} else {
result.into_tsp().normalize()
}
}
}
impl $trait for VolumeMeasure { impl $trait for VolumeMeasure {
type Output = Self; type Output = Self;
@ -293,6 +307,20 @@ impl WeightMeasure {
macro_rules! weight_op { macro_rules! weight_op {
($trait:ident, $method:ident) => { ($trait:ident, $method:ident) => {
impl $trait for &WeightMeasure {
type Output = WeightMeasure;
fn $method(self, lhs: Self) -> Self::Output {
let (l, r) = (self.get_grams(), lhs.get_grams());
let result = WeightMeasure::Gram($trait::$method(l, r));
if self.metric() {
result.normalize()
} else {
result.into_oz().normalize()
}
}
}
impl $trait for WeightMeasure { impl $trait for WeightMeasure {
type Output = Self; type Output = Self;
@ -335,18 +363,19 @@ impl Display for WeightMeasure {
use WeightMeasure::{Gram, Kilogram, Oz, Pound}; use WeightMeasure::{Gram, Kilogram, Oz, Pound};
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord)] #[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord)]
/// Measurements in a Recipe with associated units for them. /// Measurements in a Recipe with associated units for them.
pub enum Measure { pub enum Measure {
/// Volume measurements as meter cubed base unit /// Volume measurements as meter cubed base unit
Volume(VolumeMeasure), Volume(VolumeMeasure),
/// Simple count of items /// Simple count of items
Count(Quantity), Count(Quantity),
Package(Rc<str>, Quantity),
/// Weight measure as Grams base unit /// Weight measure as Grams base unit
Weight(WeightMeasure), Weight(WeightMeasure),
} }
use Measure::{Count, Volume, Weight}; use Measure::{Count, Volume, Weight, Package};
impl Measure { impl Measure {
pub fn tsp(qty: Quantity) -> Self { pub fn tsp(qty: Quantity) -> Self {
@ -407,11 +436,16 @@ impl Measure {
Weight(Oz(qty)) Weight(Oz(qty))
} }
pub fn pkg<S: Into<Rc<str>>>(name: S, qty: Quantity) -> Self {
Package(name.into(), qty)
}
pub fn measure_type(&self) -> String { pub fn measure_type(&self) -> String {
match self { match self {
Volume(_) => "Volume", Volume(_) => "Volume",
Count(_) => "Count", Count(_) => "Count",
Weight(_) => "Weight", Weight(_) => "Weight",
Package(_, _) => "Package",
} }
.to_owned() .to_owned()
} }
@ -421,6 +455,7 @@ impl Measure {
Volume(vm) => vm.plural(), Volume(vm) => vm.plural(),
Count(qty) => qty.plural(), Count(qty) => qty.plural(),
Weight(wm) => wm.plural(), Weight(wm) => wm.plural(),
Package(_, qty) => qty.plural(),
} }
} }
@ -429,6 +464,7 @@ impl Measure {
Volume(vm) => Volume(vm.normalize()), Volume(vm) => Volume(vm.normalize()),
Count(qty) => Count(qty.clone()), Count(qty) => Count(qty.clone()),
Weight(wm) => Weight(wm.normalize()), Weight(wm) => Weight(wm.normalize()),
Package(nm, qty) => Package(nm.clone(), qty.clone()),
} }
} }
} }
@ -439,6 +475,7 @@ impl Display for Measure {
Volume(vm) => write!(w, "{}", vm), Volume(vm) => write!(w, "{}", vm),
Count(qty) => write!(w, "{}", qty), Count(qty) => write!(w, "{}", qty),
Weight(wm) => write!(w, "{}", wm), Weight(wm) => write!(w, "{}", wm),
Package(nm, qty) => write!(w, "{} {}", qty, nm),
} }
} }
} }
@ -533,6 +570,22 @@ impl TryFrom<f32> for Quantity {
macro_rules! quantity_op { macro_rules! quantity_op {
($trait:ident, $method:ident) => { ($trait:ident, $method:ident) => {
impl $trait for &Quantity {
type Output = Quantity;
fn $method(self, lhs: Self) -> Self::Output {
match (self, lhs) {
(Whole(rhs), Whole(lhs)) => Frac($trait::$method(
Ratio::from_integer(*rhs),
Ratio::from_integer(*lhs),
)),
(Frac(rhs), Frac(lhs)) => Frac($trait::$method(rhs, lhs)),
(Whole(rhs), Frac(lhs)) => Frac($trait::$method(Ratio::from_integer(*rhs), lhs)),
(Frac(rhs), Whole(lhs)) => Frac($trait::$method(rhs, Ratio::from_integer(*lhs))),
}
}
}
impl $trait for Quantity { impl $trait for Quantity {
type Output = Self; type Output = Self;