diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b90a75..90a424b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,40 @@ # Changelog +## [0.13.0](https://github.com/cooklang/cookcli/compare/v0.12.1...v0.13.0) (2025-05-27) + + +### ⚠ BREAKING CHANGES + +* use : as scaling factor delimiter + +### Features + +* add base path for shopping lists so we can lookup references ([a5c1a42](https://github.com/cooklang/cookcli/commit/a5c1a42fd3dfb1c3ca63cfb6454ca47110789853)) +* detect cycle references ([3d9144f](https://github.com/cooklang/cookcli/commit/3d9144f66af5420440921a9e52883c9a8fc09b3c)) +* highlight sections in shopping list ([342db6c](https://github.com/cooklang/cookcli/commit/342db6c6b2ceb6fcc478476a00f49ec3b36adfad)) +* implement basic reference scaling ([f193203](https://github.com/cooklang/cookcli/commit/f193203ed65b26b4f58c2cde4d244135b8996d7e)) +* recursively get ingredients for referenced recipes ([bdc71cd](https://github.com/cooklang/cookcli/commit/bdc71cdc5df0b09651d953a724a165cb14844c4a)) +* support references in read command ([fc50c00](https://github.com/cooklang/cookcli/commit/fc50c0026cba26cd6866ce4587fd9762a9e65c03)) +* support references in shopping list command ([9a959d4](https://github.com/cooklang/cookcli/commit/9a959d40a1c19f3b4f8b40ecce2ce7d1f5bfe049)) +* support references in UI recipe screen ([50867de](https://github.com/cooklang/cookcli/commit/50867de9a99d26d2c98aa3b58cb01181ee5853af)) +* support references in UI shopping list ([5090b83](https://github.com/cooklang/cookcli/commit/5090b83e0e2637092f056a52006acc29dc0becd0)) + + +### Bug Fixes + +* brew install cmd ([adf9bce](https://github.com/cooklang/cookcli/commit/adf9bced58e1f102a18371a698cd0a03cebff0f7)) +* cargo publish to include ui ([0807f55](https://github.com/cooklang/cookcli/commit/0807f55c633ac9932a81f5b32e32b6a6052061e8)) + + +### Miscellaneous Chores + +* release 0.13.0 ([60b3ee5](https://github.com/cooklang/cookcli/commit/60b3ee5e9f1c4fabf95e5c7d15b1cc836632772d)) + + +### Code Refactoring + +* use : as scaling factor delimiter ([6b2251c](https://github.com/cooklang/cookcli/commit/6b2251cc3ef16bb321d703658e2e67c99e0e9a33)) + ## [0.12.1](https://github.com/cooklang/cookcli/compare/v0.12.0...v0.12.1) (2025-05-22) diff --git a/Cargo.lock b/Cargo.lock index 10dc7b1..f554bde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,12 +98,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - [[package]] name = "autocfg" version = "1.4.0" @@ -176,15 +170,9 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-targets", ] -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "bitflags" version = "2.9.1" @@ -203,12 +191,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bumpalo" -version = "3.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" - [[package]] name = "bytes" version = "1.10.1" @@ -226,9 +208,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.23" +version = "1.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" +checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" dependencies = [ "shlex", ] @@ -293,7 +275,7 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "cookcli" -version = "0.12.1" +version = "0.13.0" dependencies = [ "anstream", "anstyle", @@ -305,14 +287,12 @@ dependencies = [ "cooklang", "cooklang-find", "directories", - "futures", "humantime", "mime_guess", "once_cell", "open", "openssl", "regex", - "reqwest", "rust-embed", "serde", "serde_json", @@ -329,9 +309,9 @@ dependencies = [ [[package]] name = "cooklang" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9880b378844ef7a5c501da90b0b908cd668af23ee007f07699966f7810907e33" +checksum = "82648f1a35e96041d44f9ab0842fcafc7faf79d8a03f5fbca335fce1adc045cd" dependencies = [ "bitflags", "codesnake", @@ -355,9 +335,9 @@ dependencies = [ [[package]] name = "cooklang-find" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b394d0cea162a6a12375f34a3a2bf47e87cd2bd9a275a44f81d28344fb61ae" +checksum = "48abd0021a0b530aa5efe89bc529d2f8f3bec14f975cb4f76eb644b2ed2ebfac" dependencies = [ "camino", "cooklang", @@ -366,22 +346,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - [[package]] name = "cpufeatures" version = "0.2.17" @@ -432,26 +396,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - [[package]] name = "enum-map" version = "2.7.3" @@ -489,12 +433,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - [[package]] name = "finl_unicode" version = "1.3.0" @@ -531,21 +469,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" version = "0.3.31" @@ -553,7 +476,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", - "futures-sink", ] [[package]] @@ -562,34 +484,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "futures-sink" version = "0.3.31" @@ -608,16 +502,10 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "futures-channel", "futures-core", - "futures-io", - "futures-macro", - "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", - "slab", ] [[package]] @@ -638,19 +526,7 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi", ] [[package]] @@ -665,25 +541,6 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" -[[package]] -name = "h2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "hashbrown" version = "0.15.3" @@ -763,7 +620,6 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2", "http", "http-body", "httparse", @@ -772,40 +628,6 @@ dependencies = [ "pin-project-lite", "smallvec", "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" -dependencies = [ - "futures-util", - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", ] [[package]] @@ -815,124 +637,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710" dependencies = [ "bytes", - "futures-channel", "futures-util", "http", "http-body", "hyper", - "libc", "pin-project-lite", - "socket2", "tokio", "tower-service", - "tracing", -] - -[[package]] -name = "icu_collections" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" - -[[package]] -name = "icu_properties" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "potential_utf", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" - -[[package]] -name = "icu_provider" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" -dependencies = [ - "displaydoc", - "icu_locale_core", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", ] [[package]] @@ -945,12 +656,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - [[package]] name = "is-docker" version = "0.2.0" @@ -982,16 +687,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -1020,12 +715,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" -[[package]] -name = "litemap" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" - [[package]] name = "lock_api" version = "0.4.12" @@ -1095,27 +784,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.52.0", ] -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1178,12 +850,6 @@ dependencies = [ "syn", ] -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - [[package]] name = "openssl-src" version = "300.5.0+3.5.0" @@ -1238,7 +904,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -1271,15 +937,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "potential_utf" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" -dependencies = [ - "zerovec", -] - [[package]] name = "prettyplease" version = "0.2.32" @@ -1308,12 +965,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" - [[package]] name = "redox_syscall" version = "0.5.12" @@ -1329,7 +980,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ - "getrandom 0.2.16", + "getrandom", "libredox", "thiserror", ] @@ -1378,65 +1029,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "reqwest" -version = "0.12.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-native-tls", - "tower", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-registry", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.16", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - [[package]] name = "rust-embed" version = "8.7.2" @@ -1491,58 +1083,16 @@ dependencies = [ ] [[package]] -name = "rustls" -version = "0.23.27" +name = "rustversion" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" -dependencies = [ - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] -name = "rustls-pemfile" -version = "2.2.0" +name = "ryu" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" -dependencies = [ - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -1553,44 +1103,12 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "serde" version = "1.0.219" @@ -1702,15 +1220,6 @@ dependencies = [ "libc", ] -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - [[package]] name = "smallvec" version = "1.15.0" @@ -1733,12 +1242,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "strip-ansi-escapes" version = "0.1.1" @@ -1776,12 +1279,6 @@ dependencies = [ "syn", ] -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - [[package]] name = "syn" version = "2.0.101" @@ -1798,41 +1295,6 @@ name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] [[package]] name = "tabular" @@ -1844,19 +1306,6 @@ dependencies = [ "unicode-width 0.1.14", ] -[[package]] -name = "tempfile" -version = "3.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" -dependencies = [ - "fastrand", - "getrandom 0.3.3", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - [[package]] name = "terminal_size" version = "0.4.2" @@ -1909,16 +1358,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "tinystr" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" -dependencies = [ - "displaydoc", - "zerovec", -] - [[package]] name = "tokio" version = "1.45.0" @@ -1948,26 +1387,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" -dependencies = [ - "rustls", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.15" @@ -2137,12 +1556,6 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - [[package]] name = "typenum" version = "1.18.0" @@ -2185,29 +1598,6 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "utf8parse" version = "0.2.2" @@ -2263,111 +1653,12 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "web-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "winapi" version = "0.3.9" @@ -2399,48 +1690,13 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-link" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" - -[[package]] -name = "windows-registry" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" -dependencies = [ - "windows-result", - "windows-strings", - "windows-targets 0.53.0", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -2449,7 +1705,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -2458,30 +1714,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -2490,96 +1730,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "winnow" version = "0.7.10" @@ -2589,107 +1781,8 @@ dependencies = [ "memchr", ] -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] - -[[package]] -name = "writeable" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" - [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - -[[package]] -name = "yoke" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zerotrie" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/Cargo.toml b/Cargo.toml index 3c60282..b3170a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,10 @@ [package] name = "cookcli" -version = "0.12.1" +version = "0.13.0" edition = "2021" description = "A command-line interface for managing and working with Cooklang recipes" license = "MIT" +include = ["/src", "/Cargo.toml", "/Cargo.lock", "/README.md", "/ui/public"] [[bin]] name = "cook" @@ -13,7 +14,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } -cooklang = "0.16" +cooklang = { version = "0.16.1" } anyhow = "1" camino = { version = "1", features = ["serde1"] } once_cell = "1" @@ -25,16 +26,14 @@ serde = "1.0" directories = "6" serde_yaml = "0.9" rust-embed = "8" -reqwest = { version = "0.12", features = ["blocking", "json"] } tokio = { version = "1", features = ["full"] } axum = { version = "0.8" } tower = { version = "0.5", features = ["util"] } tower-http = { version = "0.6", features = ["fs", "trace", "cors"] } -futures = "0.3" mime_guess = "2.0" open = "5.3" openssl = { version = "0.10", features = ["vendored"] } -cooklang-find = { version = "0.2" } +cooklang-find = { version = "0.2.1" } textwrap = { version = "0.16", features = ["terminal_size"] } tabular = { version = "0.2", features = ["ansi-cell"] } yansi = "1" diff --git a/README.md b/README.md index f599fc0..8b220e4 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,12 @@ CookCLI provides a suite of tools to create shopping lists and maintain recipes. * [Contribution](#contribution) * [License](#license) + +> [!WARNING] +> Note: The CLI is currently undergoing significant development. +> The interface and functionality may change between versions. + + ## Example usage Add sample recipes: @@ -152,7 +158,7 @@ On Linux (or [WSL](https://docs.microsoft.com/en-us/windows/wsl/about)), this is On MacOS: brew tap cooklang/tap - brew install cooklang/tap/cook + brew install cooklang/tap/cookcli With Cargo: diff --git a/seed/Borsch.cook b/seed/Borsch.cook deleted file mode 100644 index 3a5c126..0000000 --- a/seed/Borsch.cook +++ /dev/null @@ -1,11 +0,0 @@ ->> servings: 6 - -Peel, grate and/or slice @beetroots{3%medium}, @potatoes{3%medium} and @carrots{3%items}. Keep sliced potatoes in cold water to prevent browning until ready to use then drain. - -Heat a #large soup pot{} of 5L or larger over medium/high heat and add @olive oil{2%tbsp}. Add grated beets and sauté ~{10%minutes}, stirring occasionally until beets are softened. - -Add @chicken broth{8%cups} and @water{2%cups}. Add sliced potatoes and sliced carrots then cook for 10-15 minutes or until easily pierced with a fork. - -While potatoes are cooking, place a large skillet over medium/high heat and add @olive oil{2%tbsp}. Add finally chopped @onion{1%medium}, @celery{2%items} and @bell pepper{1%small}. Saute stirring occasionally until softened and lightly golden for 7-8 minutes. Add @tomato paste{3%tbsp} and stir fry 30 seconds then transfer to the soup pot to continue cooking with the potatoes. - -When potatoes and carrots reach desired softness, add @cannellini beans{1%can} with their juice, @bay leaves{2%items}, @white vinegar{2%tbsp}, @salt{1%tsp}, @black pepper{4%tsp}, pressed @garlic{1%glove}, and chopped @dill{3%tbsp}. Simmer for an additional 2-3 minutes and add more salt and vinegar to taste. diff --git a/seed/Breakfasts/Easy Pancakes.cook b/seed/Breakfast/Easy Pancakes.cook similarity index 100% rename from seed/Breakfasts/Easy Pancakes.cook rename to seed/Breakfast/Easy Pancakes.cook diff --git a/seed/Breakfasts/Easy Pancakes.jpg b/seed/Breakfast/Easy Pancakes.jpg similarity index 100% rename from seed/Breakfasts/Easy Pancakes.jpg rename to seed/Breakfast/Easy Pancakes.jpg diff --git a/seed/Breakfast/Mexican Style Burrito.cook b/seed/Breakfast/Mexican Style Burrito.cook new file mode 100644 index 0000000..f2e2a75 --- /dev/null +++ b/seed/Breakfast/Mexican Style Burrito.cook @@ -0,0 +1,39 @@ +--- +servings: 6 +author: Jamie Oliver +--- + +Coarsely grate the @cheddar cheese{75%g}. Cut @lime{1} in half and +the other @lime{1} into six wedges. + +Loosely wrap the @tortillas{6%large} in tin foil then pop in the +hot oven to warm through, along with two plates. Finely chop the +@fresh red chilli{2} and put it aside for later. + +Make your table look respectable - get the cutlery, salt and pepper +and drinks laid out nicely. + +Put a #small non-stick saucepan{} on a low heat. Add the +@butter{30%g} and leave to melt. + +Crack the @eggs{8%large} into a #bowl, add a pinch of @salt and +@black pepper{} and beat with a fork. + +When the buter has melted, add the eggs to the pan. Stir the eggs +slowly with a #spatula, getting right into the sides of the pan. +Cook gently for 5 to 10 minutes until they just start to scramble +then turn the heat off - they'll continute to cook on their own. + +Get two plates and pop a warm tortilla on each one. Divide the +scrambled eggs between them then top with a good spoonful of you +home-made @./Shared/Red Beans{}. + +Scatter each portion with grated cheese and as much chilli as +you dare, then roll each tortilla up. + +Spoon @./Shared/Guacamole{} and @sour cream{200%ml} +on top of each one, scatter with coriander leaves and dust with +a little @smoked paprika{1%pinch}. + +Serve each portion with wedge of lime for squeezing over, and +tuck in. diff --git a/seed/Breakfasts/Mexican Style Burrito.cook b/seed/Breakfasts/Mexican Style Burrito.cook deleted file mode 100644 index 4a1d53a..0000000 --- a/seed/Breakfasts/Mexican Style Burrito.cook +++ /dev/null @@ -1,37 +0,0 @@ ---- -servings: 6 ---- - -Preheat your oven to the lowest setting. Drain the @cannellini beans{2%tins} in a sieve. Place a saucepan on a medium heat. - -Peel and dinely slice the @garlic clove{2}. add the @olive oil{1%tbsp} and sliced garlic to the hot pan. - -Crubmle the @red chilli{1%item} into the pan, then stir and fry until the grlic turns golden. - -Add the @tinned tomatoes{2%tins} and drained cannellini beans to the pan, reduce to a low heat and simmer gently for around 20 minutes, or until reduced and nice and thick. Meanwhile... - -Peel, halve and finely chop the @red onion{}. Roughly chop the @cherry tomatoes{10}. Finely chop the @coriander{1%bunch} stalks and roughly chop the leaves. - -Coarsely grate the @cheddar cheese{75%g}. Cut @lime{} in half and the other @lime{} into six wedges. - -Cut the @avocados{2} in half lengthways, use a spppon to sccoop out and dicard the stone, then scoop the fles into a bowl to make your guacamole. - -Roughly mash the avocado with the back of a fork, then add the onion, cherry tomatoes, coriander stalks and @ground cumin{1%pinch}. Season with @sea salt{} and @black pepper{} and squeeze in the juice from one of the lime halves. - -Mix well then have a taste of your guacoamole and tweak with more salt, pepper and lime jouice until you've got a good balance of flovours and its tasing delicious. Set aside. - -Loosely wrap the @tortillas{6%large} in tin foil then pop in the hot oven to warm through, along with two plates. Finely chop the @fresh red chilli{2} and put it aside for later. - -Make your table look respectable - get the cutlery, salt and pepper and drinks laid out nicely. - -By now your beans should be done, so have a taste and season with salt and pepper. Turn the heat off and pop a lid on th pan sothey stay nice and warm. - -Put a small non-stick saucepan on a low heat. Add the @butter{30%g} and leave to melt. Meanwhile... - -Crack the @eggs{8%large} into a bowl, add a pinch of @salt{} and @black pepper{} and beat with a fork. When the buter has melted, add the eggs to the pan. Stir the eggs slowly with a spatula, getting right into the sides of the pan. Cook gently for 5 to 10 minutes until they just start to scramble then turn the heat off - they'll continute to cook on their own. - -Get two plates and pop a warm tortilla on each one. Divide the scrambled eggs between them then top with a good spoonful of you home-made beans. - -Scatter each portion with grated cheese and as much chilli as youdare, then roll each tortilla up. - -Spoon guacamole and @sour cream{200%ml} on top of each one, scatter with coriander leaves and dust with a little @smoked paprika{1%pinch}. Serve each portion with wedge of lime for squeezing over, and tuck in. diff --git a/seed/Neapolitan Pizza.cook b/seed/Neapolitan Pizza.cook index d1a41b4..5eefdeb 100644 --- a/seed/Neapolitan Pizza.cook +++ b/seed/Neapolitan Pizza.cook @@ -1,13 +1,26 @@ --- servings: 6 +source: https://www.stadlermade.com/how-to-pizza-dough/neapolitan/ --- -Make 6 pizza balls using @tipo zero flour{820%g}, @water{533%ml}, @salt{24.6%g} and @fresh yeast{1.6%g}. Put in a fridge for 2 days. +Preheat your #outdoor oven{} so it’s around 450/500°C (842/932°F). -Set oven to max temperature and heat pizza stone for about 40 minutes. +Prepare your pizza toppings because from now on you wanna work +fast. Sprinkle some @semolina on your work surface. -Make some tomato sauce with @chopped tomato{3%cans} and @garlic{3%cloves} and @dried oregano{3%tbsp}. Put on a pan and leave for 15 minutes occasionally stirring. +Use a #spatula to remove the @./Shared/Pizza Dough{6%balls} from +the dough box and place it upside down in the @flour. Sprinkle the +bottom of the dough ball generously with @semolina, then +flip it over again so that the top remains the top. -Make pizzas putting some tomato sauce with spoon on top of flattened dough. Add @fresh basil{18%leaves}, @parma ham{3%packs} and @mozzarella{3%packs}. +Press with your fingertips from both hands in the center of the +dough ball towards the outer edges so that the air goes to the +crust. Rotate the dough clockwise and make the same movement again +until you have gone all around and a nice crust has formed. Now +lift the dough and place it on top of your knuckles. Gently stretch +the dough and rotate, making circular movements to stretch it into +a pizza base of about 25 cm (9.8 inches). -Put in an oven for 4 minutes. +Place the base on a lightly floured surface and spread @San Marzano tomato sauce{5%tbsp} on it. Add some fresh @basil leaves{} and fresh @mozzarella cheese{100%grams}. + +Spike up your fire by standing up some small logs of wood against the back panel, this will generate lots of heat and the fire will go up and over the pizza. Sprinkle some flour on your pizza peel and slide the pizza onto it. Use your peel to slide the pizza into your oven and bake the pizza at 450/500°C (842/932°F). Watch out; this can go fast. Rotate the pizza every couple of seconds to prevent burning. After 60 to 90 seconds, your pizza is done. Slice your pizza and enjoy! diff --git a/seed/Olivier Salad.cook b/seed/Olivier Salad.cook deleted file mode 100644 index 373526b..0000000 --- a/seed/Olivier Salad.cook +++ /dev/null @@ -1,29 +0,0 @@ ---- -servings: 6 ---- - -Zero step is cook @corn beef{1%kg}. Put into a large pan and simmer for 2 hours. - -The first step is to cook your @potatoes{3%medium} and @carrots{3%medium}. I used a steamer, but you can always go the traditional route and boil them. In either case, peel the carrots but not the potatoes. - -Steam the potatoes for 30 minutes to start with, and then add the peeled carrots. Continue steaming for 10-15 more minutes, or until the potatoes and carrots are firm but tender when poked. - -Meanwhile, cook your @frozen peas{1%cup} according to package directions. I use the kind that can be steamed in the package in the microwave. When they are done, set them aside to cool. - -When the potatoes and carrots are done, allow them to cool to the point that you can handle them easily. - -Peel the potatoes. Using your fingers or the back of a knife, gently scrape the thin layer of skin off of the potatoes. Dice them into 1cm cube-ish shapes and put them into a medium serving bowl. - -Next, dice your carrots. I've heard it said that a Soviet housewife could be judged on her housekeeping skills by how finely she could dice vegetables for her soups and salads. I, however, won't judge you. In fact, if you chop your potatoes and carrots a little larger, I would probably even thank you. I happen to like chunky salads. - -Toss the carrots and a cup of steamed peas into the bowl with the potatoes. - -Peel and dice your hardboiled @eggs{4}. Again, I know some like to have their salads with finely diced ingredients, but I don't. So dice them however you like. - -Chop @pickles{6} finely. I used small snacking dill pickles, so I needed to use six of them. If you have larger pickles, try using three and see if that is enough for you. - -Add the meat if using and mix everything together gently before you add the @mayonnaise{1%cup}. - -Stir in one cup of mayo to start with, and add more if you think that the salad needs more binding together. - -Cover the salad and chill for at least one hour or overnight to allow the flavors to come together. And of course, garnish with finelly chopped @dill{1%tbsp}. This is a Russian salad, after all. diff --git a/seed/Shared/Guacamole.cook b/seed/Shared/Guacamole.cook new file mode 100644 index 0000000..71c6a70 --- /dev/null +++ b/seed/Shared/Guacamole.cook @@ -0,0 +1,21 @@ +--- +servings: 6 +author: Jamie Oliver +--- + +Peel, halve and finely chop the @red onion{1}. Roughly chop the +@cherry tomatoes{10}. Finely chop the @coriander{1%bunch} stalks +and roughly chop the leaves. + +Cut the @avocados{2} in half lengthways, use a spoon to scoop out +and dicard the stone, then scoop the flesh into a bowl to make your +guacamole. + +Roughly mash the avocado with the back of a fork, then add +the onion, cherry tomatoes, coriander stalks and +@ground cumin{1%pinch}. Season with @sea salt{} and @black pepper{} +and squeeze in the juice from the @lime{1/2}. + +Mix well then have a taste of your guacoamole and tweak with more +salt, pepper and lime jouice until you've got a good balance of +flovours and its tasing delicious. diff --git a/seed/Shared/Pizza Dough.cook b/seed/Shared/Pizza Dough.cook new file mode 100644 index 0000000..87a9c5f --- /dev/null +++ b/seed/Shared/Pizza Dough.cook @@ -0,0 +1,34 @@ +--- +servings: 6 +yield: 6 balls +source: https://www.stadlermade.com/how-to-pizza-dough/neapolitan/ +--- + +Pour all the @water{533%ml} into a #bowl, add the @salt{24.6%g}, +and stir until it dissolves completely. Now add @tipo zero flour{80%g} +to the saltwater mixture and stir until fully incorporated. + +> The flour acts as a buffer between the yeast and salt. + +Add @fresh yeast{1.6%g} and stir until it’s fully dissolved. Add +the rest of the @tipo zero flour{760%g} and use your hand or a spoon to mix it in. + +Once all the ingredients are mixed, it’s time to knead the dough. +Knead with one hand while the dough is still in the bowl. After a +few minutes, the dough will start to come together. At this stage, +the dough can feel a bit dry or flaky, but don’t worry, it will all +come together. + +Lightly dust your work surface with flour and transfer the dough +onto it. Knead the dough on the work surface for ~{10-20%minutes}. +Form the dough into a ball. + +Lightly @oil a bowl and place the dough in it. Brush the dough ball +with some olive @oil to prevent it from drying out and cover the +bowl with cling film or a damp towel. Let the dough rest for +~{2%hours} at room temperature. + +Divide the dough into 6 pieces, each weighing 230 grams. Now shape +the dough into dough balls, and place them on a #baking tray{}, and +cover them with cling film. You can also use a dough crate for +this. Let the dough balls rise at room temperature for 4-6 hours. diff --git a/seed/Shared/Red Beans.cook b/seed/Shared/Red Beans.cook new file mode 100644 index 0000000..146219d --- /dev/null +++ b/seed/Shared/Red Beans.cook @@ -0,0 +1,21 @@ +--- +servings: 6 +author: Jamie Oliver +--- + +Preheat your oven to the lowest setting. Drain the +@cannellini beans{2%tins} in a sieve. Place a #saucepan +on a medium heat. + +Peel and dinely slice the @garlic{2%clove}, add the +@olive oil{1%tbsp} and sliced garlic to the hot pan. + +Crubmle the @red chilli{1%item} into the pan, then +stir and fry until the grlic turns golden. + +Add the @tinned tomatoes{2%tins} and drained cannellini beans +to the pan, reduce to a low heat and simmer gently for around +~{20%minutes}, or until reduced and nice and thick. + +Have a taste and season with @salt and @pepper. Turn the heat off +and pop a lid on th pan sothey stay nice and warm. diff --git a/seed/Sicilian-style Scottadito Lamb Chops.cook b/seed/Sicilian-style Scottadito Lamb Chops.cook index 676b261..c3ee3b9 100644 --- a/seed/Sicilian-style Scottadito Lamb Chops.cook +++ b/seed/Sicilian-style Scottadito Lamb Chops.cook @@ -1,23 +1,37 @@ --- servings: 4 +author: Jamie Oliver --- -Light the barbecue and let it get nice and hot. Make sure the bars of the barbecue are clean and free from any burnt on residue. +Light the barbecue and let it get nice and hot. Make sure +the bars of the barbecue are clean and free from any burnt +on residue. -Take the @lamb chops{8%large} out of the firdge and place on a plate to come up to to room temperatur. Meanwhile... +Take the @lamb chops{8%large} out of the firdge and place on +a plate to come up to to room temperatur. Meanwhile... -Peel the @garlic{2%cloves}. Bash the @fennel seeds{1%tsp} in a pestle and mortar until fine. +Peel the @garlic{2%cloves}. Bash the @fennel seeds{1%tsp} in +a pestle and mortar until fine. -Pick and add the leaves from @fresh oregano{2%springs}, the garlic, @extra virgin olive oil{1%splash} and @salt{1%pinch} and bash again. +Pick and add the leaves from @fresh oregano{2%springs}, the garlic, +@extra virgin olive oil{1%splash} and @salt{1%pinch} and bash again. -Stir in the @ground cinnamon{1%pinch}, zest the @lemon over the top and mix again. Tip into a roasting tray and spread out. +Stir in the @ground cinnamon{1%pinch}, zest the @lemon over the top +and mix again. Tip into a roasting tray and spread out. Lightly splash the fat in several places along the chops. -Place the chops in one layer on a board, season well with @salt and @black pepper{} then bash and flatten slightly with the base of a small saucepan. +Place the chops in one layer on a board, season well with @salt and +@black pepper{} then bash and flatten slightly with the base of +a small saucepan. -Transfer the chops to the roasting tray, then turn over in the marinade to coat, then leave to marinate for around 30 minutes. +Transfer the chops to the roasting tray, then turn over in the +marinade to coat, then leave to marinate for around ~{30%minutes}. -When the bars of the grill are nice and hot, drop the chops onto the hot side and sear on botth side for 2 minutes, or until browned all over (move the chops away from any flamse that appear). +When the bars of the grill are nice and hot, drop the chops onto +the hot side and sear on botth side for 2 minutes, or until browned +all over (move the chops away from any flamse that appear). -Cut the lemon in half with a clean knife, and squeeze over the chops for the last minute of cooking, then transver to a plate to rest before serving. +Cut the lemon in half with a clean knife, and squeeze over +the chops for the last minute of cooking, then transver to +a plate to rest before serving. diff --git a/seed/Snack Basket I.cook b/seed/Snack Basket I.cook index 7a611e0..8bd83c5 100644 --- a/seed/Snack Basket I.cook +++ b/seed/Snack Basket I.cook @@ -2,9 +2,10 @@ servings: 8 --- -Take @black tea{100%bags}, @water{6%bottles}, @apple lattice square{}, @profiteroles, -@biscuites with hearts{3}, @breakfast biscuites{}, @choclate bars{2}, @crisps{3}, -@apple crisps{}, @dates, @dried apricotes{}, @dried plums{}, @pistachio, @pumkin seeds{}, +Take @black tea{100%bags}, @water{6%bottles}, @apple lattice square{}, +@profiteroles, @biscuites with hearts{3}, @breakfast biscuites{}, +@choclate bars{2}, @crisps{3}, @apple crisps{}, @dates, +@dried apricotes{}, @dried plums{}, @pistachio, @pumkin seeds{}, @tinned pineapples{2%cans}, @crackers and place them on a tray. Enjoy slowly! diff --git a/seed/Snack Basket II.cook b/seed/Snack Basket II.cook index f6f1845..78fcc36 100644 --- a/seed/Snack Basket II.cook +++ b/seed/Snack Basket II.cook @@ -2,8 +2,10 @@ servings: 8 --- -Take @black tea{100%bags}, @water{6%bottles}, @tea cake{}, @doughnuts, @biscuites with hearts{3}, -@breakfast biscuites{}, @choclate bars{2}, @crisps{3}, @sesame sticks{}, @dates, @dried figs{}, -@pea crisps{}, @walnuts, @sunflower seeds{} @tinned peaches{2%cans}, @crackers and place them on a tray. +Take @black tea{100%bags}, @water{6%bottles}, @tea cake{}, +@doughnuts, @biscuites with hearts{3}, @breakfast biscuites{}, +@choclate bars{2}, @crisps{3}, @sesame sticks{}, @dates, @dried figs{}, +@pea crisps{}, @walnuts, @sunflower seeds{} @tinned peaches{2%cans}, +@crackers and place them on a tray. Enjoy slowly! diff --git a/src/main.rs b/src/main.rs index 2f503ef..d44295c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,15 +28,13 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +use crate::util::resolve_to_absolute_path; use anyhow::{bail, Context as AnyhowContext, Result}; use args::{CliArgs, Command}; use camino::{Utf8Path, Utf8PathBuf}; use clap::Parser; -use cooklang::Converter; use cooklang::CooklangParser; -use cooklang::Extensions; use once_cell::sync::OnceCell; -use crate::util::resolve_to_absolute_path; // commands mod recipe; @@ -89,6 +87,10 @@ impl Context { global.is_file().then_some(global) }) } + + fn base_path(&self) -> &Utf8PathBuf { + &self.base_path + } } fn configure_context() -> Result { @@ -97,6 +99,9 @@ fn configure_context() -> Result { Command::Server(ref server_args) => server_args .get_base_path() .unwrap_or_else(|| Utf8PathBuf::from(".")), + Command::ShoppingList(ref shopping_list_args) => shopping_list_args + .get_base_path() + .unwrap_or_else(|| Utf8PathBuf::from(".")), _ => Utf8PathBuf::from("."), }; @@ -113,10 +118,7 @@ fn configure_context() -> Result { } fn configure_parser() -> Result { - let extensions = Extensions::empty(); - let converter = Converter::empty(); - - Ok(CooklangParser::new(extensions, converter)) + Ok(CooklangParser::canonical()) } fn configure_logging() { diff --git a/src/server/mod.rs b/src/server/mod.rs index 9977d4e..8ba6407 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -28,25 +28,26 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +use crate::util::extract_ingredients; +use crate::util::resolve_to_absolute_path; use crate::Context; use anyhow::{bail, Result}; use axum::{ + body::Body, extract::{Path, Query, State}, http::{HeaderValue, Method, StatusCode, Uri}, response::Response, routing::{get, post}, Json, Router, - body::Body, }; use camino::{Utf8Component, Utf8Path, Utf8PathBuf}; use clap::Args; use cooklang::{ingredient_list::IngredientList, CooklangParser}; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; use std::{net::SocketAddr, sync::Arc}; use tower_http::cors::CorsLayer; use tracing::info; -use crate::util::split_recipe_name_and_scaling_factor; -use crate::util::resolve_to_absolute_path; #[derive(Debug, Args)] pub struct ServerArgs { @@ -256,30 +257,21 @@ async fn shopping_list( axum::extract::Json(payload): axum::extract::Json>, ) -> Result, StatusCode> { let mut list = IngredientList::new(); - let converter = state.parser.converter(); + let mut seen = BTreeMap::new(); for entry in payload { - let (name, scaling_factor) = split_recipe_name_and_scaling_factor(&entry) - .map(|(name, scaling_factor)| { - let target = scaling_factor - .parse::() - .map_err(|_| { - tracing::error!("Invalid scaling factor: {scaling_factor}"); - StatusCode::BAD_REQUEST - })?; - Ok::<_, StatusCode>((name, target)) - }) - .unwrap_or(Ok((entry.as_str(), 1.0)))?; - - let entry = cooklang_find::get_recipe(vec![&state.base_path], &Utf8PathBuf::from(name)) - .map_err(|_| { - tracing::error!("Recipe not found: {name}"); - StatusCode::NOT_FOUND - })?; - - let recipe = entry.recipe(scaling_factor); - - list.add_recipe(&recipe, converter); + extract_ingredients( + &entry, + &mut list, + &mut seen, + &state.base_path, + state.parser.converter(), + false, + ) + .map_err(|e| { + tracing::error!("Error processing recipe: {}", e); + StatusCode::BAD_REQUEST + })?; } let aisle_content = if let Some(path) = &state.aisle_path { @@ -311,17 +303,15 @@ async fn shopping_list( async fn all_recipes( State(state): State>, ) -> Result, StatusCode> { - let recipes = cooklang_find::build_tree(&state.base_path) - .map_err(|e| { - tracing::error!("Failed to build recipe tree: {:?}", e); - StatusCode::INTERNAL_SERVER_ERROR - })?; - - let recipes = serde_json::to_value(recipes) - .map_err(|e| { - tracing::error!("Failed to serialize recipes: {:?}", e); - StatusCode::INTERNAL_SERVER_ERROR - })?; + let recipes = cooklang_find::build_tree(&state.base_path).map_err(|e| { + tracing::error!("Failed to build recipe tree: {:?}", e); + StatusCode::INTERNAL_SERVER_ERROR + })?; + + let recipes = serde_json::to_value(recipes).map_err(|e| { + tracing::error!("Failed to serialize recipes: {:?}", e); + StatusCode::INTERNAL_SERVER_ERROR + })?; Ok(Json(recipes)) } diff --git a/src/shopping_list.rs b/src/shopping_list.rs index 6c95b91..677c772 100644 --- a/src/shopping_list.rs +++ b/src/shopping_list.rs @@ -31,8 +31,10 @@ use anstream::ColorChoice; use anyhow::{bail, Context as _, Result}; use camino::Utf8PathBuf; -use clap::{Args, CommandFactory, ValueEnum}; +use clap::{Args, ValueEnum}; +use std::collections::BTreeMap; use tracing::warn; +use yansi::Paint; use cooklang::{ aisle::AisleConf, @@ -40,11 +42,10 @@ use cooklang::{ quantity::{GroupedQuantity, Quantity, Value}, ScaledQuantity, }; -use cooklang_find::RecipeEntry; use serde::Serialize; use crate::{ - util::{split_recipe_name_and_scaling_factor, write_to_output}, + util::{extract_ingredients, write_to_output}, Context, }; @@ -57,6 +58,10 @@ pub struct ShoppingListArgs { /// To use a custom scaling, add `@` at the end. recipes: Vec, + /// Base path to search for recipes + #[arg(short, long)] + base_path: Option, + /// Output file, none for stdout. #[arg(short, long)] output: Option, @@ -78,6 +83,16 @@ pub struct ShoppingListArgs { /// Load aisle conf file #[arg(short, long)] aisle: Option, + + /// Don't expand referenced recipes + #[arg(short, long)] + ignore_references: bool, +} + +impl ShoppingListArgs { + pub fn get_base_path(&self) -> Option { + self.base_path.clone() + } } #[derive(Debug, Clone, Copy, ValueEnum)] @@ -122,8 +137,19 @@ pub fn run(ctx: &Context, args: ShoppingListArgs) -> Result<()> { // retrieve, scale and merge ingredients let mut list = IngredientList::new(); + let mut seen = BTreeMap::new(); + + let ignore_references = args.ignore_references; + for entry in args.recipes { - extract_ingredients(&entry, &mut list, ctx)?; + extract_ingredients( + &entry, + &mut list, + &mut seen, + ctx.base_path(), + ctx.parser()?.converter(), + ignore_references, + )?; } write_to_output(args.output.as_deref(), |mut w| { @@ -150,41 +176,6 @@ pub fn run(ctx: &Context, args: ShoppingListArgs) -> Result<()> { }) } -fn extract_ingredients(entry: &str, list: &mut IngredientList, ctx: &Context) -> Result<()> { - let converter = ctx.parser()?.converter(); - - // split into name and servings - let (name, scaling_factor) = split_recipe_name_and_scaling_factor(entry) - .map(|(name, scaling_factor)| { - let target = scaling_factor.parse::().unwrap_or_else(|err| { - let mut cmd = crate::CliArgs::command(); - cmd.error( - clap::error::ErrorKind::InvalidValue, - format!("Invalid scaling target for '{name}': {err}"), - ) - .exit() - }); - (name, target) - }) - .unwrap_or((entry, 1.0)); - - let entry = get_recipe(ctx, name)?; - - let recipe = entry.recipe(scaling_factor); - - // Add ingredients to the list - list.add_recipe(&recipe, converter); - - Ok(()) -} - -fn get_recipe(ctx: &Context, name: &str) -> Result { - Ok(cooklang_find::get_recipe( - vec![ctx.base_path.clone()], - name.into(), - )?) -} - fn total_quantity_fmt(qty: &GroupedQuantity, row: &mut tabular::Row) { let content = qty .iter() @@ -213,7 +204,7 @@ fn build_human_table(list: IngredientList, aisle: &AisleConf, plain: bool) -> ta } else { let categories = list.categorize(aisle); for (cat, items) in categories { - table.add_heading(format!("[{}]", cat)); + table.add_heading(format!("[{}]", cat.green())); for (igr, q) in items { let mut row = tabular::Row::new().with_cell(igr); total_quantity_fmt(&q, &mut row); diff --git a/src/util/cooklang_to_cooklang.rs b/src/util/cooklang_to_cooklang.rs index 385d816..3b54c19 100644 --- a/src/util/cooklang_to_cooklang.rs +++ b/src/util/cooklang_to_cooklang.rs @@ -34,7 +34,7 @@ use std::{fmt::Write, io}; use anyhow::{Context, Result}; use cooklang::{ - metadata::{CooklangValueExt, Metadata}, + metadata::Metadata, model::{Item, Section, Step}, parser::Modifiers, quantity::{Quantity, QuantityValue}, @@ -58,14 +58,16 @@ pub fn print_cooklang( fn metadata(w: &mut impl io::Write, metadata: &Metadata) -> Result<()> { // TODO if the recipe has been scaled and multiple servings are defined // it can lead to the recipe not parsing. - - for (key, value) in &metadata.map { - if let Some(key) = key.as_str() { - if let Some(val) = value.as_str_like() { - writeln!(w, ">> {key}: {val}").context("Failed to write metadata line")?; - } - } + if metadata.map.is_empty() { + return Ok(()); } + + let map = metadata.map.clone(); + + const FRONTMATTER_FENCE: &str = "---"; + writeln!(w, "{}", FRONTMATTER_FENCE).context("Failed to write frontmatter start")?; + serde_yaml::to_writer(&mut *w, &map).context("Failed to serialize frontmatter")?; + writeln!(w, "{}\n", FRONTMATTER_FENCE).context("Failed to write frontmatter end")?; Ok(()) } @@ -113,10 +115,16 @@ fn w_step( &Item::Ingredient { index } => { let igr = &recipe.ingredients[index]; + let name = if let Some(reference) = &igr.reference { + format!("./{}/{}", reference.components.join("/"), &igr.name) + } else { + igr.name.clone() + }; + ComponentFormatter { kind: ComponentKind::Ingredient, modifiers: igr.modifiers(), - name: Some(&igr.name), + name: Some(&name), alias: igr.alias.as_deref(), quantity: igr.quantity.as_ref(), note: igr.note.as_deref(), diff --git a/src/util/cooklang_to_human.rs b/src/util/cooklang_to_human.rs index 39905c2..a4db826 100644 --- a/src/util/cooklang_to_human.rs +++ b/src/util/cooklang_to_human.rs @@ -114,6 +114,7 @@ mod style { pub timer: Style = Style::new().fg_color(color!(Cyan)), pub inline_quantity: Style = Style::new().fg_color(color!(BrightRed)), pub opt_marker: Style = Style::new().fg_color(color!(BrightCyan)).italic(), + pub reference_marker: Style = Style::new().fg_color(color!(Blue)).italic(), pub section_name: Style = Style::new().bold().underline(), pub step_igr_quantity: Style = Style::new().dimmed(), } @@ -275,7 +276,7 @@ fn ingredients(w: &mut impl io::Write, recipe: &ScaledRecipe, converter: &Conver return Ok(()); } writeln!(w, "Ingredients:")?; - let mut table = Table::new(" {:<} {:<} {:<} {:<}"); + let mut table = Table::new(" {:<} {:<} {:<} {:<} {:<}"); let mut there_is_fixed = false; let mut there_is_err = false; let trinagle = " \u{26a0}"; @@ -287,9 +288,7 @@ fn ingredients(w: &mut impl io::Write, recipe: &ScaledRecipe, converter: &Conver outcome, .. } = entry; - if !igr.modifiers().should_be_listed() { - continue; - } + let mut is_fixed = false; let mut is_err = false; let (outcome_style, outcome_char) = outcome @@ -307,17 +306,30 @@ fn ingredients(w: &mut impl io::Write, recipe: &ScaledRecipe, converter: &Conver ScaleOutcome::Scaled | ScaleOutcome::NoQuantity => (yansi::Style::new(), ""), }) .unwrap_or_default(); + let mut row = Row::new().with_cell(igr.display_name()); + + if igr.reference.is_some() { + let path = igr.reference.as_ref().unwrap().components.join("/"); + row.add_ansi_cell( + format!("(recipe: {}/{})", path, igr.name).paint(styles().reference_marker), + ); + } else { + row.add_cell(""); + } + if igr.modifiers().is_optional() { row.add_ansi_cell("(optional)".paint(styles().opt_marker)); } else { row.add_cell(""); } + let content = quantity .iter() .map(|q| quantity_fmt(q).paint(outcome_style).to_string()) .reduce(|s, q| format!("{s}, {q}")) .unwrap_or_default(); + row.add_ansi_cell(format!("{content}{}", outcome_char.paint(outcome_style))); if let Some(note) = &igr.note { @@ -325,6 +337,7 @@ fn ingredients(w: &mut impl io::Write, recipe: &ScaledRecipe, converter: &Conver } else { row.add_cell(""); } + table.add_row(row); } write!(w, "{table}")?; diff --git a/src/util/cooklang_to_md.rs b/src/util/cooklang_to_md.rs index 41b8d89..f097090 100644 --- a/src/util/cooklang_to_md.rs +++ b/src/util/cooklang_to_md.rs @@ -332,7 +332,20 @@ fn ingredients( } } - write!(w, "{}", ingredient.display_name()).context("Failed to write ingredient name")?; + if ingredient.reference.is_some() { + let path = ingredient.reference.as_ref().unwrap().components.join("/"); + write!( + w, + "[{}]({}/{})", + ingredient.display_name(), + path, + ingredient.name + ) + .context("Failed to write reference")?; + } else { + write!(w, "{}", ingredient.display_name()) + .context("Failed to write ingredient name")?; + } if ingredient.modifiers().is_optional() { write!(w, " {}", opts.optional_marker).context("Failed to write optional marker")?; diff --git a/src/util/mod.rs b/src/util/mod.rs index 6c1b7cb..8772a67 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -33,9 +33,13 @@ pub mod cooklang_to_human; pub mod cooklang_to_md; use anyhow::{Context as _, Result}; +use camino::{Utf8Path, Utf8PathBuf}; +use clap::CommandFactory; +use cooklang::{ingredient_list::IngredientList, quantity::Value, Converter}; +use cooklang_find::RecipeEntry; +use std::collections::BTreeMap; -use camino::Utf8Path; -use camino::Utf8PathBuf; +pub const RECIPE_SCALING_DELIMITER: char = ':'; pub fn write_to_output(output: Option<&Utf8Path>, f: F) -> Result<()> where @@ -53,7 +57,7 @@ where } pub fn split_recipe_name_and_scaling_factor(query: &str) -> Option<(&str, &str)> { - query.trim().rsplit_once('@') + query.trim().rsplit_once(RECIPE_SCALING_DELIMITER) } /// Resolves a path to an absolute path. If the input path is already absolute, @@ -88,3 +92,88 @@ pub fn resolve_to_absolute_path(path: &Utf8Path) -> anyhow::Result anyhow::anyhow!("Failed to convert canonicalized path to UTF-8") }) } + +pub fn extract_ingredients( + entry: &str, + list: &mut IngredientList, + seen: &mut BTreeMap, + base_path: &Utf8PathBuf, + converter: &Converter, + ignore_references: bool, +) -> Result<()> { + if seen.contains_key(entry) { + return Err(anyhow::anyhow!( + "Circular dependency found: {} -> {}", + seen.keys().cloned().collect::>().join(" -> "), + entry + )); + } + + seen.insert(entry.to_string(), seen.len()); + + // split into name and servings + let (name, scaling_factor) = split_recipe_name_and_scaling_factor(entry) + .map(|(name, scaling_factor)| { + let target = scaling_factor.parse::().unwrap_or_else(|err| { + let mut cmd = crate::CliArgs::command(); + cmd.error( + clap::error::ErrorKind::InvalidValue, + format!("Invalid scaling target for '{name}': {err}"), + ) + .exit() + }); + (name, target) + }) + .unwrap_or((entry, 1.0)); + + let recipe_entry = get_recipe(base_path, name)?; + let recipe = recipe_entry.recipe(scaling_factor); + let ref_indices = list.add_recipe(&recipe, converter, ignore_references); + + if !ignore_references { + for ref_index in ref_indices { + let ingredient = &recipe.ingredients[ref_index]; + let reference = ingredient.reference.as_ref().unwrap(); + + let suffix = match ingredient.quantity.as_ref() { + Some(quantity) => { + if quantity.unit().is_some() { + return Err(anyhow::anyhow!( + "Unit not supported for referenced ingredients: {}({}). See https://github.com/cooklang/cookcli/issues/137", + ingredient.name, + quantity + )); + } else { + match quantity.value() { + Value::Number(value) => value.to_string(), + _ => String::from(""), + } + } + } + None => scaling_factor.to_string(), + }; + + let path = reference.path("/") + ":" + &suffix; + + extract_ingredients( + path.as_str(), + list, + seen, + base_path, + converter, + ignore_references, + )?; + } + } + + seen.remove(entry); + + Ok(()) +} + +pub fn get_recipe(base_path: &Utf8PathBuf, name: &str) -> Result { + Ok(cooklang_find::get_recipe( + vec![base_path.clone()], + name.into(), + )?) +} diff --git a/ui/src/Ingredients.svelte b/ui/src/Ingredients.svelte index 0da238e..1d9c68b 100644 --- a/ui/src/Ingredients.svelte +++ b/ui/src/Ingredients.svelte @@ -1,5 +1,6 @@ {#each ingredients.sort(sorted) as ingredient}
- {ingredient.name} + {#if ingredient.reference} + {ingredient.name} + {:else} + {ingredient.name} + {/if} {#if $showIngredientNotes && ingredient.note}
{ingredient.note} {/if} diff --git a/ui/src/Recipe.svelte b/ui/src/Recipe.svelte index ecc2b86..48afde7 100644 --- a/ui/src/Recipe.svelte +++ b/ui/src/Recipe.svelte @@ -22,7 +22,7 @@ async function onAddToShoppingList() { buttonDisabled = true; await new Promise(r => setTimeout(r, 500)); - shoppingListPaths.add(`${recipePath}@${scaleFactor}`); + shoppingListPaths.add(`${recipePath}:${scaleFactor}`); isAddedToShoppingListToastOpen = true buttonDisabled = false; } @@ -31,6 +31,7 @@ grouped.forEach((ingredient) => { ingredient.name = flat[ingredient.index].name; ingredient.note = flat[ingredient.index].note; + ingredient.reference = flat[ingredient.index].reference; }); return grouped;