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
|
/// Get entire ingredients list for each step of the recipe. With duplicate
|
||||||
/// ingredients added together.
|
/// ingredients added together.
|
||||||
pub fn get_ingredients(&self) -> BTreeMap<IngredientKey, Ingredient> {
|
pub fn get_ingredients(&self) -> BTreeMap<IngredientKey, Ingredient> {
|
||||||
use Measure::{Count, Gram, Volume};
|
use Measure::{Count, Volume, Weight};
|
||||||
self.steps
|
self.steps
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| s.ingredients.iter())
|
.map(|s| s.ingredients.iter())
|
||||||
@ -121,7 +121,7 @@ impl Recipe {
|
|||||||
let amt = match (acc[&key].amt, i.amt) {
|
let amt = match (acc[&key].amt, i.amt) {
|
||||||
(Volume(rvm), Volume(lvm)) => Volume(lvm + rvm),
|
(Volume(rvm), Volume(lvm)) => Volume(lvm + rvm),
|
||||||
(Count(lqty), Count(rqty)) => Count(lqty + rqty),
|
(Count(lqty), Count(rqty)) => Count(lqty + rqty),
|
||||||
(Gram(lqty), Gram(rqty)) => Gram(lqty + rqty),
|
(Weight(lqty), Weight(rqty)) => Weight(lqty + rqty),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
acc.get_mut(&key).map(|i| i.amt = amt);
|
acc.get_mut(&key).map(|i| i.amt = amt);
|
||||||
|
@ -14,13 +14,13 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use abortable_parser::{
|
use abortable_parser::{
|
||||||
ascii_digit, ascii_ws, consume_all, discard, do_each, either, eoi, make_fn, must, not,
|
ascii_digit, consume_all, discard, do_each, either, eoi, make_fn, must, not, optional, peek,
|
||||||
optional, peek, repeat, separated, text_token, trap, until, Result, StrIter,
|
repeat, separated, text_token, trap, until, Result, StrIter,
|
||||||
};
|
};
|
||||||
use num_rational::Ratio;
|
use num_rational::Ratio;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
unit::{Measure, Measure::*, Quantity, VolumeMeasure::*},
|
unit::{Measure, Measure::*, Quantity, VolumeMeasure::*, WeightMeasure::*},
|
||||||
Ingredient, Recipe, Step,
|
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("qrt") | Some("quart") => Volume(Qrt(qty)),
|
||||||
Some("pint") | Some("pnt") => Volume(Pint(qty)),
|
Some("pint") | Some("pnt") => Volume(Pint(qty)),
|
||||||
Some("cnt") | Some("count") => Count(qty),
|
Some("cnt") | Some("count") => Count(qty),
|
||||||
Some("lb") => Measure::lb(qty),
|
Some("lb") => Weight(Pound(qty)),
|
||||||
Some("oz") => Measure::oz(qty),
|
Some("oz") => Weight(Oz(qty)),
|
||||||
Some("kg") => Measure::kilogram(qty),
|
Some("kg") => Weight(Kilogram(qty)),
|
||||||
Some("g") => Gram(qty),
|
Some("g") | Some("gram") => Weight(Gram(qty)),
|
||||||
Some("gram") => Gram(qty),
|
|
||||||
Some(u) => {
|
Some(u) => {
|
||||||
return Result::Abort(abortable_parser::Error::new(
|
return Result::Abort(abortable_parser::Error::new(
|
||||||
format!("Invalid Unit {}", u),
|
format!("Invalid Unit {}", u),
|
||||||
@ -254,7 +253,7 @@ make_fn!(
|
|||||||
discard!(text_token!("\n")),
|
discard!(text_token!("\n")),
|
||||||
eoi,
|
eoi,
|
||||||
discard!(text_token!("(")))),
|
discard!(text_token!("(")))),
|
||||||
(name)
|
(name.trim())
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -65,6 +65,11 @@ const QRT: Quantity = Quantity::Whole(960);
|
|||||||
const LTR: Quantity = Quantity::Whole(1000);
|
const LTR: Quantity = Quantity::Whole(1000);
|
||||||
const GAL: Quantity = Quantity::Whole(3840);
|
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);
|
const ONE: Quantity = Quantity::Whole(1);
|
||||||
|
|
||||||
impl VolumeMeasure {
|
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)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
/// Measurements in a Recipe with associated units for them.
|
/// Measurements in a Recipe with associated units for them.
|
||||||
pub enum Measure {
|
pub enum Measure {
|
||||||
@ -213,10 +314,10 @@ pub enum Measure {
|
|||||||
/// Simple count of items
|
/// Simple count of items
|
||||||
Count(Quantity),
|
Count(Quantity),
|
||||||
/// Weight measure as Grams base unit
|
/// Weight measure as Grams base unit
|
||||||
Gram(Quantity),
|
Weight(WeightMeasure),
|
||||||
}
|
}
|
||||||
|
|
||||||
use Measure::{Count, Gram, Volume};
|
use Measure::{Count, Volume, Weight};
|
||||||
|
|
||||||
impl Measure {
|
impl Measure {
|
||||||
pub fn tsp(qty: Quantity) -> Self {
|
pub fn tsp(qty: Quantity) -> Self {
|
||||||
@ -260,29 +361,28 @@ impl Measure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn gram(qty: Quantity) -> Self {
|
pub fn gram(qty: Quantity) -> Self {
|
||||||
Gram(qty)
|
Weight(Gram(qty))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kilogram(qty: Quantity) -> Self {
|
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 {
|
pub fn lb(qty: Quantity) -> Self {
|
||||||
// This is an approximation obviously
|
// This is an approximation
|
||||||
Gram(qty * (Quantity::Whole(453) + Quantity::Frac(Ratio::new(6, 10))))
|
Weight(Pound(qty))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn oz(qty: Quantity) -> Self {
|
pub fn oz(qty: Quantity) -> Self {
|
||||||
// This is an approximation obviously
|
// This is an approximation
|
||||||
Gram(qty * (Quantity::Whole(28) + Quantity::Frac(Ratio::new(4, 10))))
|
Weight(Oz(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",
|
||||||
Gram(_) => "Weight",
|
Weight(_) => "Weight",
|
||||||
}
|
}
|
||||||
.to_owned()
|
.to_owned()
|
||||||
}
|
}
|
||||||
@ -290,12 +390,12 @@ impl Measure {
|
|||||||
pub fn plural(&self) -> bool {
|
pub fn plural(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Volume(vm) => vm.plural(),
|
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> {
|
pub fn parse(input: &str) -> std::result::Result<Self, String> {
|
||||||
Ok(match measure(StrIter::new(input)) {
|
Ok(match measure(StrIter::new(input)) {
|
||||||
Result::Complete(_, measure) => measure,
|
Result::Complete(_, measure) => measure,
|
||||||
@ -312,7 +412,8 @@ impl Display for Measure {
|
|||||||
match self {
|
match self {
|
||||||
Volume(vm) => write!(w, "{}", vm),
|
Volume(vm) => write!(w, "{}", vm),
|
||||||
Count(qty) => write!(w, "{}", qty),
|
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