mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
Ingredient parsing for the happy path works
This commit is contained in:
parent
a96f175e9b
commit
821cc098fb
148
Cargo.lock
generated
148
Cargo.lock
generated
@ -2,9 +2,9 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "abortable_parser"
|
name = "abortable_parser"
|
||||||
version = "0.2.4"
|
version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2febc32aeed847847255580d5938d62c8e3f6cb0a019cc0d36dc1405827d4ec"
|
checksum = "8f520551d986b2be672fac2b1d749874f914ecc03fb347c7b0b5a7e541ffe435"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
@ -88,6 +88,33 @@ dependencies = [
|
|||||||
"libloading",
|
"libloading",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-next"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"dirs-sys-next",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-sys-next"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"redox_users",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "endian-type"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fallible-iterator"
|
name = "fallible-iterator"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -100,6 +127,16 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fs2"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@ -141,6 +178,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"recipe-store",
|
"recipe-store",
|
||||||
"recipes",
|
"recipes",
|
||||||
|
"rustyline",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -183,12 +221,42 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.3.4"
|
version = "2.3.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nibble_vec"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
|
||||||
|
dependencies = [
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.20.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "5.1.2"
|
version = "5.1.2"
|
||||||
@ -271,6 +339,16 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "radix_trie"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
|
||||||
|
dependencies = [
|
||||||
|
"endian-type",
|
||||||
|
"nibble_vec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "recipe-store"
|
name = "recipe-store"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -291,6 +369,25 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_users"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"redox_syscall",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.4.5"
|
version = "1.4.5"
|
||||||
@ -329,6 +426,35 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustyline"
|
||||||
|
version = "8.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e1b597fcd1eeb1d6b25b493538e5aa19629eb08932184b85fef931ba87e893"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cfg-if",
|
||||||
|
"dirs-next",
|
||||||
|
"fs2",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"memchr",
|
||||||
|
"nix",
|
||||||
|
"radix_trie",
|
||||||
|
"scopeguard",
|
||||||
|
"smallvec",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-width",
|
||||||
|
"utf8parse",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@ -352,12 +478,30 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
|
@ -9,3 +9,4 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
recipes = {path = "../recipes" }
|
recipes = {path = "../recipes" }
|
||||||
recipe-store = {path = "../recipe-store" }
|
recipe-store = {path = "../recipe-store" }
|
||||||
|
rustyline = "~8.0.0"
|
||||||
|
@ -7,7 +7,7 @@ edition = "2018"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
abortable_parser = "~0.2.4"
|
abortable_parser = "~0.2.5"
|
||||||
chrono = "~0.4"
|
chrono = "~0.4"
|
||||||
|
|
||||||
[dependencies.uuid]
|
[dependencies.uuid]
|
||||||
|
@ -11,13 +11,16 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
mod parse;
|
||||||
pub mod unit;
|
pub mod unit;
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use uuid::{self, Uuid};
|
use uuid::{self, Uuid};
|
||||||
|
|
||||||
|
use parse::{ingredient, measure};
|
||||||
use unit::*;
|
use unit::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
@ -201,6 +204,26 @@ impl Ingredient {
|
|||||||
self.amt.measure_type(),
|
self.amt.measure_type(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse(s: &str) -> Result<Ingredient, String> {
|
||||||
|
Ok(match ingredient(abortable_parser::StrIter::new(s)) {
|
||||||
|
abortable_parser::Result::Complete(_, ing) => ing,
|
||||||
|
abortable_parser::Result::Abort(e) | abortable_parser::Result::Fail(e) => {
|
||||||
|
return Err(format!("Failed to parse as Ingredient {:?}", e))
|
||||||
|
}
|
||||||
|
abortable_parser::Result::Incomplete(_) => {
|
||||||
|
return Err(format!("Incomplete input: {}", s))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Ingredient {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ingredient::parse(s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Ingredient {
|
impl std::fmt::Display for Ingredient {
|
||||||
|
190
recipes/src/parse.rs
Normal file
190
recipes/src/parse.rs
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
// 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,
|
||||||
|
};
|
||||||
|
|
||||||
|
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)
|
||||||
|
);
|
@ -16,6 +16,7 @@ use VolumeMeasure::*;
|
|||||||
|
|
||||||
use std::convert::Into;
|
use std::convert::Into;
|
||||||
|
|
||||||
|
use abortable_parser::{Result as ParseResult, StrIter};
|
||||||
use num_rational::Ratio;
|
use num_rational::Ratio;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -233,3 +234,107 @@ fn test_ingredient_display() {
|
|||||||
assert_eq!(format!("{}", i), expected);
|
assert_eq!(format!("{}", i), expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use Measure::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ratio_parse() {
|
||||||
|
if let ParseResult::Complete(_, rat) = parse::ratio(StrIter::new("1/2")) {
|
||||||
|
assert_eq!(rat, Ratio::new(1, 2))
|
||||||
|
} else {
|
||||||
|
assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_quantity_parse() {
|
||||||
|
for (i, expected) in vec![
|
||||||
|
("1 ", Quantity::Whole(1)),
|
||||||
|
("1/2 ", Quantity::Frac(Ratio::new(1, 2))),
|
||||||
|
("1 1/2 ", Quantity::Frac(Ratio::new(3, 2))),
|
||||||
|
] {
|
||||||
|
match parse::quantity(StrIter::new(i)) {
|
||||||
|
ParseResult::Complete(_, qty) => assert_eq!(qty, expected),
|
||||||
|
err => assert!(false, "{:?}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ingredient_name_parse() {
|
||||||
|
for (i, expected) in vec![("flour ", "flour"), ("flour (", "flour")] {
|
||||||
|
match parse::ingredient_name(StrIter::new(i)) {
|
||||||
|
ParseResult::Complete(_, n) => assert_eq!(n, expected),
|
||||||
|
err => assert!(false, "{:?}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ingredient_parse() {
|
||||||
|
for (i, expected) in vec![
|
||||||
|
(
|
||||||
|
"1 cup flour ",
|
||||||
|
Ingredient::new("flour", None, Volume(Cup(Quantity::Whole(1))), ""),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"\t1 cup flour ",
|
||||||
|
Ingredient::new("flour", None, Volume(Cup(Quantity::Whole(1))), ""),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"1 cup apple (chopped)",
|
||||||
|
Ingredient::new(
|
||||||
|
"apple",
|
||||||
|
Some("chopped".to_owned()),
|
||||||
|
Volume(Cup(Quantity::Whole(1))),
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"1 cup apple (chopped) ",
|
||||||
|
Ingredient::new(
|
||||||
|
"apple",
|
||||||
|
Some("chopped".to_owned()),
|
||||||
|
Volume(Cup(Quantity::Whole(1))),
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
match ingredient(StrIter::new(i)) {
|
||||||
|
ParseResult::Complete(_, ing) => assert_eq!(ing, expected),
|
||||||
|
err => assert!(false, "{:?}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ingredient_list_parse() {
|
||||||
|
for (i, expected) in vec![
|
||||||
|
(
|
||||||
|
"1 cup flour ",
|
||||||
|
vec![Ingredient::new(
|
||||||
|
"flour",
|
||||||
|
None,
|
||||||
|
Volume(Cup(Quantity::Whole(1))),
|
||||||
|
"",
|
||||||
|
)],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"1 cup flour \n1/2 tsp butter ",
|
||||||
|
vec![
|
||||||
|
Ingredient::new("flour", None, Volume(Cup(Quantity::Whole(1))), ""),
|
||||||
|
Ingredient::new(
|
||||||
|
"butter",
|
||||||
|
None,
|
||||||
|
Volume(Tsp(Quantity::Frac(Ratio::new(1, 2)))),
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
match ingredient_list(StrIter::new(i)) {
|
||||||
|
ParseResult::Complete(_, ing) => assert_eq!(ing, expected),
|
||||||
|
err => assert!(false, "{:?}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -22,15 +22,15 @@ use std::{
|
|||||||
convert::TryFrom,
|
convert::TryFrom,
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
ops::{Add, Div, Mul, Sub},
|
ops::{Add, Div, Mul, Sub},
|
||||||
str::FromStr,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use abortable_parser::{
|
use abortable_parser::{
|
||||||
self, consume_all, do_each, either, make_fn, not, optional, peek, run, text_token, trap,
|
consume_all, do_each, either, make_fn, not, optional, peek, text_token, trap, Result, StrIter,
|
||||||
Result, StrIter,
|
|
||||||
};
|
};
|
||||||
use num_rational::Ratio;
|
use num_rational::Ratio;
|
||||||
|
|
||||||
|
use crate::parse::measure;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
/// Volume Measurements for ingredients in a recipe.
|
/// Volume Measurements for ingredients in a recipe.
|
||||||
pub enum VolumeMeasure {
|
pub enum VolumeMeasure {
|
||||||
@ -285,21 +285,7 @@ impl Measure {
|
|||||||
|
|
||||||
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(_, (qty, Some(unit))) => match unit {
|
Result::Complete(i, measure) => measure,
|
||||||
"tsp" => Volume(Tsp(qty)),
|
|
||||||
"tbsp" => Volume(Tbsp(qty)),
|
|
||||||
"floz" => Volume(Floz(qty)),
|
|
||||||
"ml" => Volume(ML(qty)),
|
|
||||||
"ltr" => Volume(Ltr(qty)),
|
|
||||||
"cup" => Volume(Cup(qty)),
|
|
||||||
"qrt" => Volume(Qrt(qty)),
|
|
||||||
"pint" | "pnt" => Volume(Pint(qty)),
|
|
||||||
"cnt" => Count(qty),
|
|
||||||
"g" => Gram(qty),
|
|
||||||
"gram" => Gram(qty),
|
|
||||||
u => return Err(format!("Invalid unit {}", u)),
|
|
||||||
},
|
|
||||||
Result::Complete(_, (qty, None)) => Count(qty),
|
|
||||||
Result::Abort(e) | Result::Fail(e) => {
|
Result::Abort(e) | Result::Fail(e) => {
|
||||||
return Err(format!("Failed to parse as Measure {:?}", e))
|
return Err(format!("Failed to parse as Measure {:?}", e))
|
||||||
}
|
}
|
||||||
@ -468,98 +454,3 @@ impl Display for Quantity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
make_fn!(nonzero<StrIter, ()>,
|
|
||||||
peek!(not!(do_each!(
|
|
||||||
n => consume_all!(text_token!("0")),
|
|
||||||
_ => optional!(ws),
|
|
||||||
(n)
|
|
||||||
)))
|
|
||||||
);
|
|
||||||
|
|
||||||
make_fn!(num<StrIter, u32>,
|
|
||||||
do_each!(
|
|
||||||
n => consume_all!(either!(
|
|
||||||
text_token!("0"),
|
|
||||||
text_token!("1"),
|
|
||||||
text_token!("2"),
|
|
||||||
text_token!("3"),
|
|
||||||
text_token!("4"),
|
|
||||||
text_token!("5"),
|
|
||||||
text_token!("6"),
|
|
||||||
text_token!("7"),
|
|
||||||
text_token!("8"),
|
|
||||||
text_token!("9")
|
|
||||||
)),
|
|
||||||
(u32::from_str(n).unwrap())
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
make_fn!(ws<StrIter, &str>,
|
|
||||||
consume_all!(either!(
|
|
||||||
text_token!(" "),
|
|
||||||
text_token!("\t")
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
make_fn!(ratio<StrIter, Ratio<u32>>,
|
|
||||||
do_each!(
|
|
||||||
// First we assert non-zero numerator
|
|
||||||
_ => nonzero,
|
|
||||||
numer => num,
|
|
||||||
_ => optional!(ws),
|
|
||||||
_ => text_token!("/"),
|
|
||||||
_ => optional!(ws),
|
|
||||||
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!(
|
|
||||||
quantity<StrIter, Quantity>,
|
|
||||||
either!(
|
|
||||||
do_each!(
|
|
||||||
whole => num,
|
|
||||||
frac => ratio,
|
|
||||||
(Quantity::Whole(whole) + Quantity::Frac(frac))
|
|
||||||
),
|
|
||||||
do_each!(
|
|
||||||
frac => ratio,
|
|
||||||
(Quantity::Frac(frac))
|
|
||||||
),
|
|
||||||
do_each!(
|
|
||||||
whole => num,
|
|
||||||
(Quantity::whole(whole))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
make_fn!(
|
|
||||||
measure<StrIter, (Quantity, Option<&str>)>,
|
|
||||||
do_each!(
|
|
||||||
qty => quantity,
|
|
||||||
_ => optional!(ws),
|
|
||||||
unit => optional!(unit),
|
|
||||||
((qty, unit))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user