mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
271 lines
6.7 KiB
Rust
271 lines
6.7 KiB
Rust
// Copyright 2021 Jeremy Wall
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
use std::str::FromStr;
|
|
|
|
use abortable_parser::{
|
|
ascii_digit, ascii_ws, consume_all, discard, do_each, either, eoi, make_fn, not, optional,
|
|
peek, repeat, separated, text_token, trap, until, Result, StrIter,
|
|
};
|
|
use num_rational::Ratio;
|
|
|
|
use crate::{
|
|
unit::{Measure, Measure::*, Quantity, VolumeMeasure::*},
|
|
Ingredient, Recipe, Step,
|
|
};
|
|
|
|
pub fn as_recipe(i: &str) -> std::result::Result<Recipe, String> {
|
|
match recipe(StrIter::new(i)) {
|
|
Result::Abort(e) | Result::Fail(e) => Err(format!("Parse Failure: {:?}", e)),
|
|
Result::Incomplete(_) => Err(format!("Incomplete recipe can not parse")),
|
|
Result::Complete(_, r) => Ok(r),
|
|
}
|
|
}
|
|
|
|
make_fn!(
|
|
pub recipe<StrIter, Recipe>,
|
|
do_each!(
|
|
title => title,
|
|
_ => optional!(para_separator),
|
|
desc => optional!(do_each!(
|
|
_ => peek!(not!(step_prefix)),
|
|
desc => description,
|
|
(desc)
|
|
)),
|
|
_ => optional!(para_separator),
|
|
steps => step_list,
|
|
(Recipe::new(title, desc).with_steps(steps))
|
|
)
|
|
);
|
|
|
|
make_fn!(
|
|
pub title<StrIter, &str>,
|
|
do_each!(
|
|
_ => text_token!("title:"),
|
|
_ => optional!(ws),
|
|
title => until!(text_token!("\n")),
|
|
_ => text_token!("\n"),
|
|
(title)
|
|
)
|
|
);
|
|
|
|
make_fn!(
|
|
para_separator<StrIter, &str>,
|
|
do_each!(
|
|
_ => text_token!("\n"),
|
|
_ => optional!(ws),
|
|
_ => text_token!("\n"),
|
|
("")
|
|
)
|
|
);
|
|
|
|
make_fn!(
|
|
pub description<StrIter, &str>,
|
|
until!(either!(
|
|
discard!(para_separator),
|
|
eoi,
|
|
))
|
|
);
|
|
|
|
make_fn!(
|
|
pub step_prefix<StrIter, &str>,
|
|
do_each!(
|
|
_ => text_token!("step:"),
|
|
_ => optional!(ws),
|
|
_ => para_separator,
|
|
("")
|
|
)
|
|
);
|
|
|
|
make_fn!(
|
|
pub step<StrIter, Step>,
|
|
do_each!(
|
|
_ => step_prefix,
|
|
ingredients => ingredient_list,
|
|
_ => para_separator,
|
|
desc => description,
|
|
_ => either!(discard!(para_separator), eoi),
|
|
(Step::new(None, desc).with_ingredients(ingredients))
|
|
)
|
|
);
|
|
|
|
make_fn!(
|
|
pub step_list<StrIter, Vec<Step>>,
|
|
repeat!(step)
|
|
);
|
|
|
|
make_fn!(ws<StrIter, &str>,
|
|
consume_all!(either!(
|
|
text_token!(" "),
|
|
text_token!("\t"),
|
|
text_token!("\r")
|
|
))
|
|
);
|
|
|
|
make_fn!(nonzero<StrIter, ()>,
|
|
peek!(not!(do_each!(
|
|
n => consume_all!(text_token!("0")),
|
|
_ => ws,
|
|
(n)
|
|
)))
|
|
);
|
|
|
|
make_fn!(num<StrIter, u32>,
|
|
do_each!(
|
|
_ => peek!(ascii_digit),
|
|
n => consume_all!(ascii_digit),
|
|
(u32::from_str(n).unwrap())
|
|
)
|
|
);
|
|
|
|
make_fn!(
|
|
pub ratio<StrIter, Ratio<u32>>,
|
|
do_each!(
|
|
// First we assert non-zero numerator
|
|
//_ => nonzero,
|
|
numer => num,
|
|
_ => text_token!("/"),
|
|
denom => num,
|
|
(Ratio::new(numer, denom))
|
|
)
|
|
);
|
|
|
|
make_fn!(unit<StrIter, &str>,
|
|
do_each!(
|
|
u => either!(
|
|
text_token!("tsp"),
|
|
text_token!("tbsp"),
|
|
text_token!("floz"),
|
|
text_token!("ml"),
|
|
text_token!("ltr"),
|
|
text_token!("cup"),
|
|
text_token!("qrt"),
|
|
text_token!("pint"),
|
|
text_token!("pnt"),
|
|
text_token!("gal"),
|
|
text_token!("gal"),
|
|
text_token!("cnt"),
|
|
text_token!("g"),
|
|
text_token!("gram")),
|
|
(u))
|
|
);
|
|
|
|
make_fn!(
|
|
pub quantity<StrIter, Quantity>,
|
|
either!(
|
|
do_each!(
|
|
whole => num,
|
|
_ => ws,
|
|
frac => ratio,
|
|
_ => ws,
|
|
(Quantity::Whole(whole) + Quantity::Frac(frac))
|
|
),
|
|
do_each!(
|
|
frac => ratio,
|
|
_ => ws,
|
|
(Quantity::Frac(frac))
|
|
),
|
|
do_each!(
|
|
whole => num,
|
|
_ => ws,
|
|
(Quantity::whole(whole))
|
|
)
|
|
)
|
|
);
|
|
|
|
make_fn!(
|
|
pub measure_parts<StrIter, (Quantity, Option<&str>)>,
|
|
do_each!(
|
|
qty => quantity,
|
|
unit => optional!(do_each!(
|
|
_ => ws,
|
|
unit => unit,
|
|
(unit)
|
|
)),
|
|
_ => ws,
|
|
((qty, unit))
|
|
)
|
|
);
|
|
|
|
pub fn measure(i: StrIter) -> abortable_parser::Result<StrIter, Measure> {
|
|
match measure_parts(i) {
|
|
Result::Complete(i, (qty, unit)) => {
|
|
return Result::Complete(
|
|
i.clone(),
|
|
match unit {
|
|
Some("tsp") => Volume(Tsp(qty)),
|
|
Some("tbsp") => Volume(Tbsp(qty)),
|
|
Some("floz") => Volume(Floz(qty)),
|
|
Some("ml") => Volume(ML(qty)),
|
|
Some("ltr") | Some("liter") => Volume(Ltr(qty)),
|
|
Some("cup") | Some("cp") => Volume(Cup(qty)),
|
|
Some("qrt") | Some("quart") => Volume(Qrt(qty)),
|
|
Some("pint") | Some("pnt") => Volume(Pint(qty)),
|
|
Some("cnt") | Some("count") => Count(qty),
|
|
Some("g") => Gram(qty),
|
|
Some("gram") => Gram(qty),
|
|
Some(u) => {
|
|
return Result::Abort(abortable_parser::Error::new(
|
|
format!("Invalid Unit {}", u),
|
|
Box::new(i),
|
|
))
|
|
}
|
|
None => Count(qty),
|
|
},
|
|
)
|
|
}
|
|
Result::Fail(e) => {
|
|
return Result::Fail(e);
|
|
}
|
|
Result::Abort(e) => {
|
|
return Result::Abort(e);
|
|
}
|
|
Result::Incomplete(i) => return Result::Incomplete(i),
|
|
}
|
|
}
|
|
|
|
make_fn!(
|
|
pub ingredient_name<StrIter, &str>,
|
|
do_each!(
|
|
name => until!(ascii_ws),
|
|
_ => ws,
|
|
(name)
|
|
)
|
|
);
|
|
|
|
make_fn!(
|
|
ingredient_modifier<StrIter, &str>,
|
|
do_each!(
|
|
_ => text_token!("("),
|
|
modifier => until!(text_token!(")")),
|
|
_ => text_token!(")"),
|
|
(modifier)
|
|
)
|
|
);
|
|
|
|
make_fn!(
|
|
pub ingredient<StrIter, Ingredient>,
|
|
do_each!(
|
|
_ => optional!(ws),
|
|
measure => measure,
|
|
name => ingredient_name,
|
|
modifier => optional!(ingredient_modifier),
|
|
(Ingredient::new(name, modifier.map(|s| s.to_owned()), measure, ""))
|
|
)
|
|
);
|
|
|
|
make_fn!(
|
|
pub ingredient_list<StrIter, Vec<Ingredient>>,
|
|
separated!(text_token!("\n"), ingredient)
|
|
);
|