diff --git a/Cargo.lock b/Cargo.lock index 92daa83..08301f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,13 +12,74 @@ dependencies = [ ] [[package]] -name = "ansi-to-html" +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] +name = "askama" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" +dependencies = [ + "askama_derive", + "askama_escape", +] + +[[package]] +name = "askama_derive" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" +dependencies = [ + "askama_parser", + "basic-toml", + "mime", + "mime_guess", + "proc-macro2", + "quote", + "serde", + "syn", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_parser" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d73c455ae09fa2223a75114789f30ad605e9e297f79537953523366c05995f5f" +checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" dependencies = [ - "regex", - "thiserror", + "nom", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "basic-toml" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +dependencies = [ + "serde", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", ] [[package]] @@ -45,12 +106,62 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "codesnake" version = "0.2.1" @@ -59,9 +170,7 @@ checksum = "2205f7f6d3de68ecf4c291c789b3edf07b6569268abd0188819086f71ae42225" [[package]] name = "cooklang" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4b3e629e90bfdeb6bae5551d2c254759868d36d76903c5d76eff0c84c518df0" +version = "0.14.0" dependencies = [ "bitflags", "codesnake", @@ -69,16 +178,16 @@ dependencies = [ "emojis", "enum-map", "finl_unicode", - "indexmap", "once_cell", "pest", "pest_derive", "regex", "serde", + "serde_yaml", "smallvec", "strum", "thiserror", - "toml", + "toml 0.8.19", "tracing", "unicase", "unicode-width", @@ -86,13 +195,23 @@ dependencies = [ "yansi", ] +[[package]] +name = "cooklang-bindings" +version = "0.14.2" +dependencies = [ + "anyhow", + "clap_derive", + "cooklang", + "uniffi", +] + [[package]] name = "cooklang-wasm" version = "0.1.0" dependencies = [ - "ansi-to-html", - "cooklang", - "serde_json", + "cooklang-bindings", + "serde", + "serde-wasm-bindgen", "wasm-bindgen", ] @@ -193,6 +312,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -203,6 +331,23 @@ dependencies = [ "version_check", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "goblin" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" +dependencies = [ + "log", + "plain", + "scroll", +] + [[package]] name = "hashbrown" version = "0.15.1" @@ -362,7 +507,6 @@ checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown", - "serde", ] [[package]] @@ -371,6 +515,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.162" @@ -395,12 +548,50 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -476,6 +667,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "proc-macro2" version = "1.0.89" @@ -535,6 +732,35 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "scroll" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "semver" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +dependencies = [ + "serde", +] + [[package]] name = "serde" version = "1.0.214" @@ -544,6 +770,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_derive" version = "1.0.214" @@ -576,6 +813,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha2" version = "0.10.8" @@ -599,12 +849,24 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strum" version = "0.26.3" @@ -649,6 +911,15 @@ dependencies = [ "syn", ] +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +dependencies = [ + "smawk", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -679,6 +950,15 @@ dependencies = [ "zerovec", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "toml" version = "0.8.19" @@ -774,6 +1054,128 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "uniffi" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cb08c58c7ed7033150132febe696bef553f891b1ede57424b40d87a89e3c170" +dependencies = [ + "anyhow", + "cargo_metadata", + "uniffi_bindgen", + "uniffi_core", + "uniffi_macros", +] + +[[package]] +name = "uniffi_bindgen" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cade167af943e189a55020eda2c314681e223f1e42aca7c4e52614c2b627698f" +dependencies = [ + "anyhow", + "askama", + "camino", + "cargo_metadata", + "fs-err", + "glob", + "goblin", + "heck", + "once_cell", + "paste", + "serde", + "textwrap", + "toml 0.5.11", + "uniffi_meta", + "uniffi_udl", +] + +[[package]] +name = "uniffi_checksum_derive" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "802d2051a700e3ec894c79f80d2705b69d85844dafbbe5d1a92776f8f48b563a" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "uniffi_core" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7687007d2546c454d8ae609b105daceb88175477dac280707ad6d95bcd6f1f" +dependencies = [ + "anyhow", + "bytes", + "log", + "once_cell", + "paste", + "static_assertions", +] + +[[package]] +name = "uniffi_macros" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12c65a5b12ec544ef136693af8759fb9d11aefce740fb76916721e876639033b" +dependencies = [ + "bincode", + "camino", + "fs-err", + "once_cell", + "proc-macro2", + "quote", + "serde", + "syn", + "toml 0.5.11", + "uniffi_meta", +] + +[[package]] +name = "uniffi_meta" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a74ed96c26882dac1ca9b93ca23c827e284bacbd7ec23c6f0b0372f747d59e4" +dependencies = [ + "anyhow", + "bytes", + "siphasher", + "uniffi_checksum_derive", +] + +[[package]] +name = "uniffi_testing" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6f984f0781f892cc864a62c3a5c60361b1ccbd68e538e6c9fbced5d82268ac" +dependencies = [ + "anyhow", + "camino", + "cargo_metadata", + "fs-err", + "once_cell", +] + +[[package]] +name = "uniffi_udl" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037820a4cfc4422db1eaa82f291a3863c92c7d1789dc513489c36223f9b4cdfc" +dependencies = [ + "anyhow", + "textwrap", + "uniffi_meta", + "uniffi_testing", + "weedle2", +] + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "url" version = "2.5.3" @@ -859,6 +1261,15 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +[[package]] +name = "weedle2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" +dependencies = [ + "nom", +] + [[package]] name = "winnow" version = "0.6.20" diff --git a/Cargo.toml b/Cargo.toml index d6adeaf..0854a14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,10 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] -ansi-to-html = "0.2.1" -cooklang = "0.13.3" -serde_json = "1.0.132" -wasm-bindgen = "0.2.95" +cooklang-bindings = { path = "../cooklang-rs/bindings" } +serde = { version = "1.0", features = ["derive"] } +wasm-bindgen = "0.2" +serde-wasm-bindgen = "0.5" [lib] crate-type = ["cdylib", "rlib"] diff --git a/src/index.ts b/src/index.ts index 1eb1991..086f5ce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,13 +23,6 @@ export function getImageURL(name: string, options?: ImageURLOptions) { return name + (options.step ? '.' + options.step : '') + '.' + (options.extension || 'png'); } -import Recipe from './Recipe'; -import Parser from './Parser'; -export { Recipe, Parser }; - -export * from './Recipe'; -export * from './Parser'; -export * from './cooklang'; export * from "cooklang-wasm"; diff --git a/src/lib.rs b/src/lib.rs index 7023a9e..7e09e47 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,314 +1,389 @@ use wasm_bindgen::prelude::*; +use serde::Serialize; -use std::sync::Arc; +#[wasm_bindgen] +pub fn parse_recipe(recipe: &str) -> CooklangRecipe { + cooklang_bindings::parse_recipe(recipe.to_string()).into() +} + +#[wasm_bindgen] +#[derive(Clone, Serialize)] +pub struct CooklangRecipe { + #[wasm_bindgen(skip)] + pub metadata: Vec<(String, String)>, + #[wasm_bindgen(skip)] + pub sections: Vec
, + #[wasm_bindgen(skip)] + pub ingredients: Vec, + #[wasm_bindgen(skip)] + pub cookware: Vec, + #[wasm_bindgen(skip)] + pub timers: Vec, +} -use cooklang::aisle::parse as parse_aisle_config_original; -use cooklang::analysis::parse_events; -use cooklang::parser::PullParser; -use cooklang::{Converter, Extensions}; +// Add getter methods for the fields +#[wasm_bindgen] +impl CooklangRecipe { + #[wasm_bindgen(getter)] + pub fn metadata(&self) -> JsValue { + serde_wasm_bindgen::to_value(&self.metadata).unwrap() + } -pub mod aisle; -pub mod model; + #[wasm_bindgen(getter)] + pub fn sections(&self) -> JsValue { + serde_wasm_bindgen::to_value(&self.sections).unwrap() + } -use aisle::*; -use model::*; + #[wasm_bindgen(getter)] + pub fn ingredients(&self) -> JsValue { + serde_wasm_bindgen::to_value(&self.ingredients).unwrap() + } -#[wasm_bindgen] -pub fn parse_recipe(input: String) -> CooklangRecipe { - let extensions = Extensions::empty(); - let converter = Converter::empty(); - - let mut parser = PullParser::new(&input, extensions); - let parsed = parse_events( - &mut parser, - &input, - extensions, - &converter, - Default::default(), - ) - .unwrap_output(); - - into_simple_recipe(&parsed) + #[wasm_bindgen(getter)] + pub fn cookware(&self) -> JsValue { + serde_wasm_bindgen::to_value(&self.cookware).unwrap() + } + + #[wasm_bindgen(getter)] + pub fn timers(&self) -> JsValue { + serde_wasm_bindgen::to_value(&self.timers).unwrap() + } } +pub type ComponentRef = u32; + #[wasm_bindgen] -pub fn parse_metadata(input: String) -> CooklangMetadata { - let mut metadata = CooklangMetadata::new(); - let extensions = Extensions::empty(); - let converter = Converter::empty(); - - let parser = PullParser::new(&input, extensions); - - let parsed = parse_events( - parser.into_meta_iter(), - &input, - extensions, - &converter, - Default::default(), - ) - .map(|c| c.metadata.map) - .unwrap_output(); - - // converting IndexMap into HashMap - let _ = &(parsed).iter().for_each(|(key, value)| { - metadata.insert(key.to_string(), value.to_string()); - }); - - metadata +#[derive(Clone, Serialize)] +pub struct Section { + #[wasm_bindgen(skip)] + pub title: Option, + #[wasm_bindgen(skip)] + pub blocks: Vec, + #[wasm_bindgen(skip)] + pub ingredient_refs: Vec, + #[wasm_bindgen(skip)] + pub cookware_refs: Vec, + #[wasm_bindgen(skip)] + pub timer_refs: Vec, } +// Add getters for Section #[wasm_bindgen] -pub fn parse_aisle_config(input: String) -> AisleConf { - let mut categories: Vec = Vec::new(); - let mut cache: AisleReverseCategory = AisleReverseCategory::default(); +impl Section { + #[wasm_bindgen(getter)] + pub fn title(&self) -> JsValue { + serde_wasm_bindgen::to_value(&self.title).unwrap() + } + + #[wasm_bindgen(getter)] + pub fn blocks(&self) -> JsValue { + serde_wasm_bindgen::to_value(&self.blocks).unwrap() + } + + #[wasm_bindgen(getter)] + pub fn ingredient_refs(&self) -> JsValue { + serde_wasm_bindgen::to_value(&self.ingredient_refs).unwrap() + } + + #[wasm_bindgen(getter)] + pub fn cookware_refs(&self) -> JsValue { + serde_wasm_bindgen::to_value(&self.cookware_refs).unwrap() + } - let parsed = parse_aisle_config_original(&input).unwrap(); + #[wasm_bindgen(getter)] + pub fn timer_refs(&self) -> JsValue { + serde_wasm_bindgen::to_value(&self.timer_refs).unwrap() + } +} + +#[derive(Clone, Serialize)] +pub struct Block { + pub block_type: String, + pub step: Option, + pub note: Option, +} - let _ = &(parsed).categories.iter().for_each(|c| { - let category = into_category(c); +#[derive(Clone, Serialize)] +pub struct Step { + pub items: Vec, + pub ingredient_refs: Vec, + pub cookware_refs: Vec, + pub timer_refs: Vec, +} - // building cache - category.ingredients.iter().for_each(|i| { - cache.insert(i.name.clone(), category.name.clone()); +#[derive(Clone, Serialize)] +pub struct BlockNote { + pub text: String, +} - i.aliases.iter().for_each(|a| { - cache.insert(a.to_string(), category.name.clone()); - }); - }); +#[derive(Clone, Serialize)] +pub struct Ingredient { + pub name: String, + pub amount: Option, + pub descriptor: Option, +} - categories.push(category); - }); +#[derive(Clone, Serialize)] +pub struct Cookware { + pub name: String, + pub amount: Option, +} - AisleConf { categories, cache } +#[derive(Clone, Serialize)] +pub struct Timer { + pub name: Option, + pub amount: Option, } -#[wasm_bindgen] -pub fn combine_ingredient_lists(lists: Vec) -> IngredientList { - let mut combined: IngredientList = IngredientList::default(); +#[derive(Clone, Serialize)] +pub struct Item { + pub item_type: String, + pub text_value: Option, + pub ref_index: Option, +} + +#[derive(Clone, Serialize)] +pub struct Amount { + pub(crate) quantity: Value, + pub(crate) units: Option, +} + +#[derive(Clone, Serialize)] +pub struct Value { + pub value_type: String, + pub number_value: Option, + pub range_start: Option, + pub range_end: Option, + pub text_value: Option, +} - lists - .iter() - .for_each(|l| merge_ingredient_lists(&mut combined, l)); +// Helper methods for constructing these types +impl Block { + pub fn step(step: Step) -> Self { + Block { + block_type: "step".to_string(), + step: Some(step), + note: None, + } + } - combined + pub fn note(note: BlockNote) -> Self { + Block { + block_type: "note".to_string(), + step: None, + note: Some(note), + } + } } +impl Item { + pub fn text(value: String) -> Self { + Item { + item_type: "text".to_string(), + text_value: Some(value), + ref_index: None, + } + } -#[cfg(test)] -mod tests { - - #[test] - fn test_parse_recipe() { - use crate::{parse_recipe, Amount, Item, Value}; - - let recipe = parse_recipe( - r#" -a test @step @salt{1%mg} more text -"# - .to_string(), - ); - - assert_eq!( - recipe.steps.into_iter().nth(0).unwrap().items, - vec![ - Item::Text { - value: "a test ".to_string() - }, - Item::Ingredient { - name: "step".to_string(), - amount: None - }, - Item::Text { - value: " ".to_string() - }, - Item::Ingredient { - name: "salt".to_string(), - amount: Some(Amount { - quantity: Value::Number { value: 1.0 }, - units: Some("mg".to_string()) - }) - }, - Item::Text { - value: " more text".to_string() - } - ] - ); + pub fn ingredient_ref(index: usize) -> Self { + Item { + item_type: "ingredient_ref".to_string(), + text_value: None, + ref_index: Some(index), + } } - #[test] - fn test_parse_metadata() { - use crate::parse_metadata; - use std::collections::HashMap; - - let metadata = parse_metadata( - r#" ->> source: https://google.com -a test @step @salt{1%mg} more text -"# - .to_string(), - ); - - assert_eq!( - metadata, - HashMap::from([("source".to_string(), "https://google.com".to_string())]) - ); + pub fn cookware_ref(index: usize) -> Self { + Item { + item_type: "cookware_ref".to_string(), + text_value: None, + ref_index: Some(index), + } } - #[test] - fn test_parse_aisle_config() { - use crate::parse_aisle_config; - - let config = parse_aisle_config( - r#" -[fruit and veg] -apple gala | apples -aubergine -avocado | avocados - -[milk and dairy] -butter -egg | eggs -curd cheese -cheddar cheese -feta - -[dried herbs and spices] -bay leaves -black pepper -cayenne pepper -dried oregano -"# - .to_string(), - ); - - assert_eq!( - config.category_for("bay leaves".to_string()), - Some("dried herbs and spices".to_string()) - ); - - assert_eq!( - config.category_for("eggs".to_string()), - Some("milk and dairy".to_string()) - ); - - assert_eq!( - config.category_for("some weird ingredient".to_string()), - None - ); + pub fn timer_ref(index: usize) -> Self { + Item { + item_type: "timer_ref".to_string(), + text_value: None, + ref_index: Some(index), + } } +} + +impl Value { + pub fn number(value: f64) -> Self { + Value { + value_type: "number".to_string(), + number_value: Some(value), + range_start: None, + range_end: None, + text_value: None, + } + } + + pub fn range(start: f64, end: f64) -> Self { + Value { + value_type: "range".to_string(), + number_value: None, + range_start: Some(start), + range_end: Some(end), + text_value: None, + } + } + + pub fn text(value: String) -> Self { + Value { + value_type: "text".to_string(), + number_value: None, + range_start: None, + range_end: None, + text_value: Some(value), + } + } + + pub fn empty() -> Self { + Value { + value_type: "empty".to_string(), + number_value: None, + range_start: None, + range_end: None, + text_value: None, + } + } +} + + +impl From for CooklangRecipe { + fn from(recipe: cooklang_bindings::model::CooklangRecipe) -> Self { + CooklangRecipe { + metadata: recipe.metadata.into_iter().collect(), + sections: recipe.sections.into_iter().map(|s| s.into()).collect(), + ingredients: recipe.ingredients.into_iter().map(|i| i.into()).collect(), + cookware: recipe.cookware.into_iter().map(|c| c.into()).collect(), + timers: recipe.timers.into_iter().map(|t| t.into()).collect(), + } + } +} + +impl From for Section { + fn from(section: cooklang_bindings::model::Section) -> Self { + Section { + title: section.title, + blocks: section.blocks.into_iter().map(|b| b.into()).collect(), + ingredient_refs: section.ingredient_refs, + cookware_refs: section.cookware_refs, + timer_refs: section.timer_refs, + } + } +} + +impl From for Block { + fn from(block: cooklang_bindings::model::Block) -> Self { + match block { + cooklang_bindings::model::Block::StepBlock(step) => Block::step(step.into()), + cooklang_bindings::model::Block::NoteBlock(text) => Block::note(text.into()), + } + } +} + +impl From for Step { + fn from(step: cooklang_bindings::model::Step) -> Self { + Step { + items: step.items.into_iter().map(|i| i.into()).collect(), + ingredient_refs: step.ingredient_refs, + cookware_refs: step.cookware_refs, + timer_refs: step.timer_refs, + } + } +} + +impl From for BlockNote { + fn from(note: cooklang_bindings::model::BlockNote) -> Self { + BlockNote { + text: note.text, + } + } +} + +impl From for Ingredient { + fn from(ingredient: cooklang_bindings::model::Ingredient) -> Self { + Ingredient { + name: ingredient.name, + amount: ingredient.amount.map(|a| a.into()), + descriptor: ingredient.descriptor, + } + } +} + +impl From for Cookware { + fn from(cookware: cooklang_bindings::model::Cookware) -> Self { + Cookware { + name: cookware.name, + amount: cookware.amount.map(|a| a.into()), + } + } +} + +impl From for Timer { + fn from(timer: cooklang_bindings::model::Timer) -> Self { + Timer { + name: timer.name, + amount: timer.amount.map(|a| a.into()), + } + } +} + +impl From for Item { + fn from(item: cooklang_bindings::model::Item) -> Self { + match item { + cooklang_bindings::model::Item::Text { value } => Item::text(value), + cooklang_bindings::model::Item::IngredientRef { index } => { + Item::ingredient_ref(index.try_into().unwrap()) + }, + cooklang_bindings::model::Item::CookwareRef { index } => { + Item::cookware_ref(index.try_into().unwrap()) + }, + cooklang_bindings::model::Item::TimerRef { index } => { + Item::timer_ref(index.try_into().unwrap()) + }, + } + } +} + +impl From for Amount { + fn from(amount: cooklang_bindings::model::Amount) -> Self { + Amount { + quantity: amount.quantity.into(), + units: amount.units.clone(), + } + } +} - #[test] - fn test_combine_ingredient_lists() { - use crate::{combine_ingredient_lists, GroupedQuantityKey, QuantityType, Value}; - use std::collections::HashMap; - - let combined = combine_ingredient_lists(vec![ - HashMap::from([ - ( - "salt".to_string(), - HashMap::from([ - ( - GroupedQuantityKey { - name: "g".to_string(), - unit_type: QuantityType::Number, - }, - Value::Number { value: 5.0 }, - ), - ( - GroupedQuantityKey { - name: "tsp".to_string(), - unit_type: QuantityType::Number, - }, - Value::Number { value: 1.0 }, - ), - ]), - ), - ( - "pepper".to_string(), - HashMap::from([ - ( - GroupedQuantityKey { - name: "mg".to_string(), - unit_type: QuantityType::Number, - }, - Value::Number { value: 5.0 }, - ), - ( - GroupedQuantityKey { - name: "tsp".to_string(), - unit_type: QuantityType::Number, - }, - Value::Number { value: 1.0 }, - ), - ]), - ), - ]), - HashMap::from([( - "salt".to_string(), - HashMap::from([ - ( - GroupedQuantityKey { - name: "kg".to_string(), - unit_type: QuantityType::Number, - }, - Value::Number { value: 0.005 }, - ), - ( - GroupedQuantityKey { - name: "tsp".to_string(), - unit_type: QuantityType::Number, - }, - Value::Number { value: 1.0 }, - ), - ]), - )]), - ]); - - assert_eq!( - *combined.get("salt").unwrap(), - HashMap::from([ - ( - GroupedQuantityKey { - name: "kg".to_string(), - unit_type: QuantityType::Number - }, - Value::Number { value: 0.005 } - ), - ( - GroupedQuantityKey { - name: "tsp".to_string(), - unit_type: QuantityType::Number - }, - Value::Number { value: 2.0 } - ), - ( - GroupedQuantityKey { - name: "g".to_string(), - unit_type: QuantityType::Number - }, - Value::Number { value: 5.0 } - ), - ]) - ); - - assert_eq!( - *combined.get("pepper").unwrap(), - HashMap::from([ - ( - GroupedQuantityKey { - name: "mg".to_string(), - unit_type: QuantityType::Number - }, - Value::Number { value: 5.0 } - ), - ( - GroupedQuantityKey { - name: "tsp".to_string(), - unit_type: QuantityType::Number - }, - Value::Number { value: 1.0 } - ), - ]) - ); +impl From for Value { + fn from(value: cooklang_bindings::model::Value) -> Self { + match value { + cooklang_bindings::model::Value::Number { value } => Value::number(value), + cooklang_bindings::model::Value::Range { start, end } => Value { + value_type: "range".to_string(), + number_value: None, + range_start: Some(start), + range_end: Some(end), + text_value: None, + }, + cooklang_bindings::model::Value::Text { value } => Value { + value_type: "text".to_string(), + number_value: None, + range_start: None, + range_end: None, + text_value: Some(value), + }, + cooklang_bindings::model::Value::Empty => Value { + value_type: "empty".to_string(), + number_value: None, + range_start: None, + range_end: None, + text_value: None, + }, + } } } diff --git a/src/model.rs b/src/model.rs deleted file mode 100644 index 7f62e30..0000000 --- a/src/model.rs +++ /dev/null @@ -1,493 +0,0 @@ -use wasm_bindgen::prelude::*; -use std::collections::HashMap; - -use cooklang::model::Item as OriginalItem; -use cooklang::quantity::{ - Quantity as OriginalQuantity, ScalableValue as OriginalScalableValue, Value as OriginalValue, -}; -use cooklang::ScalableRecipe as OriginalRecipe; - -#[wasm_bindgen] -pub struct ItemText { - pub value: String, -} - -#[wasm_bindgen] -pub struct ItemIngredient { - pub name: String, - pub amount: Option, -} - -#[wasm_bindgen] -pub struct ItemCookware { - pub name: String, - pub amount: Option, -} - -#[wasm_bindgen] -pub struct ItemTimer { - pub name: Option, - pub amount: Option, -} - -#[wasm_bindgen] -pub enum ItemType { - Text, - Ingredient, - Cookware, - Timer, -} - -#[wasm_bindgen] -pub struct Item { - pub item_type: ItemType, - pub text: Option, - pub ingredient: Option, - pub cookware: Option, - pub timer: Option, -} - -#[wasm_bindgen] -pub struct CooklangRecipe { - pub metadata: CooklangMetadata, - pub steps: Vec, - pub ingredients: IngredientList, - pub cookware: Vec, -} - -#[wasm_bindgen] -pub struct Step { - pub items: Vec, -} - -#[wasm_bindgen] -pub struct IngredientList { - inner: HashMap -} - -#[wasm_bindgen] -impl IngredientList { - #[wasm_bindgen(constructor)] - pub fn new() -> Self { - Self { - inner: HashMap::new() - } - } - - pub fn insert(&mut self, key: String, value: GroupedQuantity) { - self.inner.insert(key, value); - } - - pub fn get(&self, key: &str) -> Option { - self.inner.get(key).cloned() - } -} - -pub(crate) fn into_group_quantity(amount: &Option) -> GroupedQuantity { - // options here: - // - same units: - // - same value type - // - not the same value type - // - different units - // - no units - // - no amount - // - // \ - // |- => 1.2 - // |- => half - // |- <,Text> => pinch - // |- <,Empty> => Some - // - // - // TODO define rules on language spec level??? - let empty_units = "".to_string(); - - let key = if let Some(amount) = amount { - let units = amount.units.as_ref().unwrap_or(&empty_units); - - match &amount.quantity { - Value::Number { .. } => GroupedQuantityKey { - name: units.to_string(), - unit_type: QuantityType::Number, - }, - Value::Range { .. } => GroupedQuantityKey { - name: units.to_string(), - unit_type: QuantityType::Range, - }, - Value::Text { .. } => GroupedQuantityKey { - name: units.to_string(), - unit_type: QuantityType::Text, - }, - Value::Empty => GroupedQuantityKey { - name: units.to_string(), - unit_type: QuantityType::Empty, - }, - } - } else { - GroupedQuantityKey { - name: empty_units, - unit_type: QuantityType::Empty, - } - }; - - let value = if let Some(amount) = amount { - amount.quantity.clone() - } else { - Value::Empty - }; - - GroupedQuantity::from([(key, value)]) -} - -#[wasm_bindgen] -pub enum QuantityType { - Number, - Range, // how to combine ranges? - Text, - Empty, -} - -#[wasm_bindgen] -pub struct GroupedQuantityKey { - pub name: String, - pub unit_type: QuantityType, -} - -#[wasm_bindgen] -pub struct GroupedQuantity { - inner: HashMap -} - -#[wasm_bindgen] -impl GroupedQuantity { - #[wasm_bindgen(constructor)] - pub fn new() -> Self { - Self { - inner: HashMap::new() - } - } - - pub fn insert(&mut self, key: GroupedQuantityKey, value: Value) { - self.inner.insert(key, value); - } - - pub fn get(&self, key: &GroupedQuantityKey) -> Option { - self.inner.get(key).cloned() - } -} - -// Update the from implementation -impl From<[(GroupedQuantityKey, Value); 1]> for GroupedQuantity { - fn from(arr: [(GroupedQuantityKey, Value); 1]) -> Self { - let mut map = HashMap::new(); - map.insert(arr[0].0, arr[0].1); - Self { inner: map } - } -} - -#[wasm_bindgen] -pub struct Amount { - pub(crate) quantity: Value, - pub(crate) units: Option, -} - -#[wasm_bindgen] -#[derive(Clone)] -pub enum ValueType { - Number, - Range, - Text, - Empty, -} - -#[wasm_bindgen] -#[derive(Clone)] -pub struct Value { - pub value_type: ValueType, - pub number_value: Option, - pub range_start: Option, - pub range_end: Option, - pub text_value: Option, -} - -#[wasm_bindgen] -#[derive(Default)] -pub struct CooklangMetadata { - inner: HashMap, -} - -#[wasm_bindgen] -impl CooklangMetadata { - #[wasm_bindgen(constructor)] - pub fn new() -> Self { - Self { - inner: HashMap::new() - } - } - - pub fn insert(&mut self, key: String, value: String) { - self.inner.insert(key, value); - } - - pub fn get(&self, key: &str) -> Option { - self.inner.get(key).cloned() - } -} - -trait Amountable { - fn extract_amount(&self) -> Amount; -} - -impl Amountable for OriginalQuantity { - fn extract_amount(&self) -> Amount { - let quantity = extract_quantity(&self.value); - - let units = self.unit().as_ref().map(|u| u.to_string()); - - Amount { quantity, units } - } -} - -impl Amountable for OriginalScalableValue { - fn extract_amount(&self) -> Amount { - let quantity = extract_quantity(self); - - Amount { - quantity, - units: None, - } - } -} - -fn extract_quantity(value: &OriginalScalableValue) -> Value { - match value { - OriginalScalableValue::Fixed(value) => extract_value(value), - OriginalScalableValue::Linear(value) => extract_value(value), - OriginalScalableValue::ByServings(values) => extract_value(values.first().unwrap()), - } -} - -fn extract_value(value: &OriginalValue) -> Value { - match value { - OriginalValue::Number(num) => Value { - value_type: ValueType::Number, - number_value: Some(num.value()), - range_start: None, - range_end: None, - text_value: None, - }, - OriginalValue::Range { start, end } => Value { - value_type: ValueType::Range, - number_value: None, - range_start: Some(start.value()), - range_end: Some(end.value()), - text_value: None, - }, - OriginalValue::Text(value) => Value { - value_type: ValueType::Text, - number_value: None, - range_start: None, - range_end: None, - text_value: Some(value.to_string()), - }, - } -} - -// I(dubadub) haven't found a way to export these methods with mutable argument -pub fn add_to_ingredient_list( - list: &mut IngredientList, - name: &String, - quantity_to_add: &GroupedQuantity, -) { - if let Some(quantity) = list.inner.get_mut(name) { - merge_grouped_quantities(quantity, quantity_to_add); - } else { - list.inner.insert(name.to_string(), quantity_to_add.clone()); - } -} - -// O(n2)? find a better way -pub fn merge_ingredient_lists(left: &mut IngredientList, right: &IngredientList) { - right.inner - .iter() - .for_each(|(ingredient_name, grouped_quantity)| { - let quantity = left.inner - .entry(ingredient_name.to_string()) - .or_insert(GroupedQuantity::default()); - - merge_grouped_quantities(quantity, grouped_quantity); - }); -} - -// I(dubadub) haven't found a way to export these methods with mutable argument -// Right should be always smaller? -pub(crate) fn merge_grouped_quantities(left: &mut GroupedQuantity, right: &GroupedQuantity) { - // options here: - // - same units: - // - same value type - // - not the same value type - // - different units - // - no units - // - no amount - // - // \ - // |- => 1.2 litre - // |- => half litre - // |- <,Text> => pinch - // |- <,Empty> => Some - // - // - // TODO define rules on language spec level - - right.iter().for_each(|(key, value)| { - left - .entry(key.clone()) // isn't really necessary? - .and_modify(|v| { - match key.unit_type { - QuantityType::Number => { - let Value::Number { value: assignable } = value else { panic!("Unexpected type") }; - let Value::Number { value: stored } = v else { panic!("Unexpected type") }; - - *stored += assignable - }, - QuantityType::Range => { - let Value::Range { start, end } = value else { panic!("Unexpected type") }; - let Value::Range { start: s, end: e } = v else { panic!("Unexpected type") }; - - // is it even correct? - *s += start; - *e += end; - }, - QuantityType::Text => { - let Value::Text { value: ref assignable } = value else { panic!("Unexpected type") }; - let Value::Text { value: stored } = v else { panic!("Unexpected type") }; - - *stored += assignable; - }, - QuantityType::Empty => {}, // nothing is required to do, Some + Some = Some - - } - }) - .or_insert(value.clone()); - }); -} - -pub(crate) fn into_item(item: &OriginalItem, recipe: &OriginalRecipe) -> Item { - match item { - OriginalItem::Text { value } => Item { - item_type: ItemType::Text, - text: Some(ItemText { - value: value.to_string(), - }), - ingredient: None, - cookware: None, - timer: None, - }, - OriginalItem::Ingredient { index } => { - let ingredient = &recipe.ingredients[*index]; - Item { - item_type: ItemType::Ingredient, - text: None, - ingredient: Some(ItemIngredient { - name: ingredient.name.clone(), - amount: ingredient.quantity.as_ref().map(|q| q.extract_amount()), - }), - cookware: None, - timer: None, - } - } - OriginalItem::Cookware { index } => { - let cookware = &recipe.cookware[*index]; - Item { - item_type: ItemType::Cookware, - text: None, - ingredient: None, - cookware: Some(ItemCookware { - name: cookware.name.clone(), - amount: cookware.quantity.as_ref().map(|q| q.extract_amount()), - }), - timer: None, - } - } - OriginalItem::Timer { index } => { - let timer = &recipe.timers[*index]; - Item { - item_type: ItemType::Timer, - text: None, - ingredient: None, - cookware: None, - timer: Some(ItemTimer { - name: timer.name.clone(), - amount: timer.quantity.as_ref().map(|q| q.extract_amount()), - }), - } - } - OriginalItem::InlineQuantity { index: _ } => Item { - item_type: ItemType::Text, - text: Some(ItemText { - value: "".to_string(), - }), - ingredient: None, - cookware: None, - timer: None, - }, - } -} - -pub(crate) fn into_simple_recipe(recipe: &OriginalRecipe) -> CooklangRecipe { - let mut metadata = CooklangMetadata::new(); - let mut steps: Vec = Vec::new(); - let mut ingredients: IngredientList = IngredientList::new(); - let mut cookware: Vec = Vec::new(); - let mut items: Vec = Vec::new(); - - recipe.sections.iter().for_each(|section| { - section.content.iter().for_each(|content| { - if let cooklang::Content::Step(step) = content { - step.items.iter().for_each(|i| { - let item = into_item(i, recipe); - - match item { - Item { - item_type: ItemType::Ingredient, - ingredient: Some(ref ingredient), - .. - } => { - let quantity = into_group_quantity(&ingredient.amount); - - add_to_ingredient_list(&mut ingredients, &ingredient.name, &quantity); - } - Item { - item_type: ItemType::Cookware, - cookware: Some(ref cookware), - .. - } => { - cookware.push(cookware.clone()); - } - // don't need anything if timer or text - _ => (), - }; - items.push(item); - }); - // TODO: think how to make it faster as we probably - // can switch items content directly into the step object without cloning it - steps.push(Step { - items: items.clone(), - }); - - items.clear(); - } - }); - }); - - recipe.metadata.map.iter().for_each(|(key, value)| { - metadata.insert(key.to_string(), value.to_string()); - }); - - CooklangRecipe { - metadata, - steps, - ingredients, - cookware, - } -} diff --git a/tests/index.test.ts b/tests/index.test.ts index ba9ee14..b501b93 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,10 +1,7 @@ import * as fs from 'fs'; import * as yaml from 'yaml'; -import { Parser } from '../src/index'; -import { State } from '../src/index'; -import { Step } from '../src/cooklang'; +import { parse_recipe } from '../src/index'; -const parser = new Parser(); const testsPath = "./tests"; const testFiles = fs.readdirSync(testsPath).filter((f) => f.endsWith(".yaml")); @@ -17,10 +14,10 @@ testFiles.forEach((testFile) => { >; describe(testFile, () => { - Object.entries(testData).forEach(([name, testEntry]) => { + Object.entries(testData).slice(0, 3).forEach(([name, testEntry]) => { it(name, () => { const { source, result } = testEntry; - const parsed = parser.parse(source); + const parsed = parse_recipe(source); const expected = { steps: result.steps, @@ -28,15 +25,43 @@ testFiles.forEach((testFile) => { }; const actual = { - steps: parsed.steps, + steps: parsed.sections.flatMap((i: any) => i.blocks).flatMap((i: any) => i.step.items).map((i: any) => { + console.log(i); + switch (i.item_type) { + case "text": + return { + type: "text", + value: i.text_value, + } + case "ingredient_ref": + const ingredient = parsed.ingredients[i.ref_index]; + console.log(ingredient); + return { + type: "ingredient", + quantity: ingredient.amount?.quantity, + units: ingredient.amount?.quantity?.units, + name: ingredient.name, + } + case "timer_ref": + const timer = parsed.timers[i.ref_index]; + return { + type: "timer", + name: timer.name, + } + case "cookware_ref": + const cookware = parsed.cookware[i.ref_index]; + return { + type: "cookware", + name: cookware.name, + } + default: + break; + } + }), metadata: parsed.metadata, }; - const state = new State(); - const { value, error } = state.parse_full(source, false); - - - expect(value).toStrictEqual(result); + expect(expected).toStrictEqual(actual); }); }); });