mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
Better unit handling for weight measures
This commit is contained in:
parent
8e0e66e8b8
commit
91c3ad603b
16
examples/meatloaf.txt
Normal file
16
examples/meatloaf.txt
Normal file
@ -0,0 +1,16 @@
|
||||
title: Meatloaf
|
||||
|
||||
Good old fashioned meatloaf.
|
||||
|
||||
step:
|
||||
|
||||
1 lb ground beef
|
||||
1 onion (chopped)
|
||||
1 cup oatmeal
|
||||
2 tbsp garlic powder
|
||||
1 egg
|
||||
2 tbsp salt
|
||||
1/2 cup ketchup
|
||||
|
||||
Mix ingredients excluding the ketchup together thoroughly. Bake in oven for 35 minutes at 350.
|
||||
Cover with ketchup and cook for an additional 10 minutes. Cut into slices and serve.
|
@ -108,7 +108,7 @@ impl Recipe {
|
||||
/// Get entire ingredients list for each step of the recipe. With duplicate
|
||||
/// ingredients added together.
|
||||
pub fn get_ingredients(&self) -> BTreeMap<IngredientKey, Ingredient> {
|
||||
use Measure::{Count, Gram, Volume};
|
||||
use Measure::{Count, Volume, Weight};
|
||||
self.steps
|
||||
.iter()
|
||||
.map(|s| s.ingredients.iter())
|
||||
@ -121,7 +121,7 @@ impl Recipe {
|
||||
let amt = match (acc[&key].amt, i.amt) {
|
||||
(Volume(rvm), Volume(lvm)) => Volume(lvm + rvm),
|
||||
(Count(lqty), Count(rqty)) => Count(lqty + rqty),
|
||||
(Gram(lqty), Gram(rqty)) => Gram(lqty + rqty),
|
||||
(Weight(lqty), Weight(rqty)) => Weight(lqty + rqty),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
acc.get_mut(&key).map(|i| i.amt = amt);
|
||||
|
@ -14,13 +14,13 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use abortable_parser::{
|
||||
ascii_digit, ascii_ws, consume_all, discard, do_each, either, eoi, make_fn, must, not,
|
||||
optional, peek, repeat, separated, text_token, trap, until, Result, StrIter,
|
||||
ascii_digit, consume_all, discard, do_each, either, eoi, make_fn, must, not, optional, peek,
|
||||
repeat, separated, text_token, trap, until, Result, StrIter,
|
||||
};
|
||||
use num_rational::Ratio;
|
||||
|
||||
use crate::{
|
||||
unit::{Measure, Measure::*, Quantity, VolumeMeasure::*},
|
||||
unit::{Measure, Measure::*, Quantity, VolumeMeasure::*, WeightMeasure::*},
|
||||
Ingredient, Recipe, Step,
|
||||
};
|
||||
|
||||
@ -222,11 +222,10 @@ pub fn measure(i: StrIter) -> abortable_parser::Result<StrIter, Measure> {
|
||||
Some("qrt") | Some("quart") => Volume(Qrt(qty)),
|
||||
Some("pint") | Some("pnt") => Volume(Pint(qty)),
|
||||
Some("cnt") | Some("count") => Count(qty),
|
||||
Some("lb") => Measure::lb(qty),
|
||||
Some("oz") => Measure::oz(qty),
|
||||
Some("kg") => Measure::kilogram(qty),
|
||||
Some("g") => Gram(qty),
|
||||
Some("gram") => Gram(qty),
|
||||
Some("lb") => Weight(Pound(qty)),
|
||||
Some("oz") => Weight(Oz(qty)),
|
||||
Some("kg") => Weight(Kilogram(qty)),
|
||||
Some("g") | Some("gram") => Weight(Gram(qty)),
|
||||
Some(u) => {
|
||||
return Result::Abort(abortable_parser::Error::new(
|
||||
format!("Invalid Unit {}", u),
|
||||
@ -254,7 +253,7 @@ make_fn!(
|
||||
discard!(text_token!("\n")),
|
||||
eoi,
|
||||
discard!(text_token!("(")))),
|
||||
(name)
|
||||
(name.trim())
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -65,6 +65,11 @@ const QRT: Quantity = Quantity::Whole(960);
|
||||
const LTR: Quantity = Quantity::Whole(1000);
|
||||
const GAL: Quantity = Quantity::Whole(3840);
|
||||
|
||||
// multiplier constants for various units into grams
|
||||
const LB: Quantity = Quantity::Frac(Ratio::new_raw(4535924, 10000));
|
||||
const OZ: Quantity = Quantity::Frac(Ratio::new_raw(2834952, 100000));
|
||||
const KG: Quantity = Quantity::Whole(1000);
|
||||
|
||||
const ONE: Quantity = Quantity::Whole(1);
|
||||
|
||||
impl VolumeMeasure {
|
||||
@ -205,6 +210,102 @@ impl Display for VolumeMeasure {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum WeightMeasure {
|
||||
Gram(Quantity),
|
||||
Kilogram(Quantity),
|
||||
Pound(Quantity),
|
||||
Oz(Quantity),
|
||||
}
|
||||
|
||||
impl WeightMeasure {
|
||||
pub fn get_grams(&self) -> Quantity {
|
||||
match self {
|
||||
&Self::Gram(ref qty) => *qty,
|
||||
&Self::Kilogram(ref qty) => *qty * KG,
|
||||
&Self::Pound(ref qty) => *qty * LB,
|
||||
&Self::Oz(ref qty) => *qty * OZ,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plural(&self) -> bool {
|
||||
match self {
|
||||
&Self::Gram(qty) | &Self::Kilogram(qty) | &Self::Pound(qty) | &Self::Oz(qty) => {
|
||||
qty.plural()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_gram(self) -> Self {
|
||||
Self::Gram(self.get_grams())
|
||||
}
|
||||
|
||||
pub fn into_kilo(self) -> Self {
|
||||
Self::Kilogram(self.get_grams() / KG)
|
||||
}
|
||||
|
||||
pub fn into_pound(self) -> Self {
|
||||
Self::Pound(self.get_grams() / LB)
|
||||
}
|
||||
|
||||
pub fn into_oz(self) -> Self {
|
||||
Self::Oz(self.get_grams() / OZ)
|
||||
}
|
||||
|
||||
pub fn normalize(self) -> Self {
|
||||
let grams = self.get_grams();
|
||||
if (grams / KG) >= ONE {
|
||||
return self.into_kilo();
|
||||
}
|
||||
if (grams / LB) >= ONE {
|
||||
return self.into_pound();
|
||||
}
|
||||
if (grams / OZ) >= ONE {
|
||||
return self.into_oz();
|
||||
}
|
||||
return self.into_gram();
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! weight_op {
|
||||
($trait:ident, $method:ident) => {
|
||||
impl $trait for WeightMeasure {
|
||||
type Output = Self;
|
||||
|
||||
fn $method(self, lhs: Self) -> Self::Output {
|
||||
let (l, r) = (self.get_grams(), lhs.get_grams());
|
||||
WeightMeasure::Gram($trait::$method(l, r))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
weight_op!(Add, add);
|
||||
weight_op!(Sub, sub);
|
||||
|
||||
impl PartialEq for WeightMeasure {
|
||||
fn eq(&self, lhs: &Self) -> bool {
|
||||
let rhs = self.get_grams();
|
||||
let lhs = lhs.get_grams();
|
||||
PartialEq::eq(&rhs, &lhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for WeightMeasure {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
&Self::Gram(qty) => write!(f, "{} gram{}", qty, if qty.plural() { "s" } else { "" }),
|
||||
&Self::Kilogram(qty) => {
|
||||
write!(f, "{} kilogram{}", qty, if qty.plural() { "s" } else { "" })
|
||||
}
|
||||
&Self::Pound(qty) => write!(f, "{} lb{}", qty, if qty.plural() { "s" } else { "" }),
|
||||
&Self::Oz(qty) => write!(f, "{} oz", qty),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use WeightMeasure::{Gram, Kilogram, Oz, Pound};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
/// Measurements in a Recipe with associated units for them.
|
||||
pub enum Measure {
|
||||
@ -213,10 +314,10 @@ pub enum Measure {
|
||||
/// Simple count of items
|
||||
Count(Quantity),
|
||||
/// Weight measure as Grams base unit
|
||||
Gram(Quantity),
|
||||
Weight(WeightMeasure),
|
||||
}
|
||||
|
||||
use Measure::{Count, Gram, Volume};
|
||||
use Measure::{Count, Volume, Weight};
|
||||
|
||||
impl Measure {
|
||||
pub fn tsp(qty: Quantity) -> Self {
|
||||
@ -260,29 +361,28 @@ impl Measure {
|
||||
}
|
||||
|
||||
pub fn gram(qty: Quantity) -> Self {
|
||||
Gram(qty)
|
||||
Weight(Gram(qty))
|
||||
}
|
||||
|
||||
pub fn kilogram(qty: Quantity) -> Self {
|
||||
Gram(qty * Quantity::Whole(1000))
|
||||
Weight(Kilogram(qty))
|
||||
}
|
||||
|
||||
// TODO(jwall): Should these be separate units with conversions?
|
||||
pub fn lb(qty: Quantity) -> Self {
|
||||
// This is an approximation obviously
|
||||
Gram(qty * (Quantity::Whole(453) + Quantity::Frac(Ratio::new(6, 10))))
|
||||
// This is an approximation
|
||||
Weight(Pound(qty))
|
||||
}
|
||||
|
||||
pub fn oz(qty: Quantity) -> Self {
|
||||
// This is an approximation obviously
|
||||
Gram(qty * (Quantity::Whole(28) + Quantity::Frac(Ratio::new(4, 10))))
|
||||
// This is an approximation
|
||||
Weight(Oz(qty))
|
||||
}
|
||||
|
||||
pub fn measure_type(&self) -> String {
|
||||
match self {
|
||||
Volume(_) => "Volume",
|
||||
Count(_) => "Count",
|
||||
Gram(_) => "Weight",
|
||||
Weight(_) => "Weight",
|
||||
}
|
||||
.to_owned()
|
||||
}
|
||||
@ -290,12 +390,12 @@ impl Measure {
|
||||
pub fn plural(&self) -> bool {
|
||||
match self {
|
||||
Volume(vm) => vm.plural(),
|
||||
Count(qty) | Gram(qty) => qty.plural(),
|
||||
Count(qty) => qty.plural(),
|
||||
Weight(wm) => wm.plural(),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(jwall): parse from string.
|
||||
|
||||
// TODO(jwall): Remove this it's unnecessary
|
||||
pub fn parse(input: &str) -> std::result::Result<Self, String> {
|
||||
Ok(match measure(StrIter::new(input)) {
|
||||
Result::Complete(_, measure) => measure,
|
||||
@ -312,7 +412,8 @@ impl Display for Measure {
|
||||
match self {
|
||||
Volume(vm) => write!(w, "{}", vm),
|
||||
Count(qty) => write!(w, "{}", qty),
|
||||
Gram(qty) => write!(w, "{} gram{}", qty, if qty.plural() { "s" } else { "" }),
|
||||
// TODO(jwall): Should I allow auto convert upwards for the grams to kgs?
|
||||
Weight(wm) => write!(w, "{}", wm),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user