diff --git a/Cargo.lock b/Cargo.lock index 15dbe26..9b6ca77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,12 +18,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -[[package]] -name = "cc" -version = "1.0.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" - [[package]] name = "cfg-if" version = "1.0.0" @@ -44,40 +38,14 @@ dependencies = [ ] [[package]] -name = "dirs-next" -version = "2.0.0" +name = "clap" +version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", + "bitflags", + "textwrap", + "unicode-width", ] [[package]] @@ -95,8 +63,8 @@ dependencies = [ name = "kitchen" version = "0.1.0" dependencies = [ + "clap", "recipes", - "rustyline", ] [[package]] @@ -105,42 +73,6 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" -[[package]] -name = "log" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "memchr" -version = "2.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" - -[[package]] -name = "nibble_vec" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" -dependencies = [ - "smallvec", -] - -[[package]] -name = "nix" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" -dependencies = [ - "bitflags", - "cc", - "cfg-if", - "libc", -] - [[package]] name = "num-bigint" version = "0.4.0" @@ -183,16 +115,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - [[package]] name = "recipes" version = "0.1.0" @@ -204,59 +126,14 @@ dependencies = [ ] [[package]] -name = "redox_syscall" -version = "0.2.8" +name = "textwrap" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_users" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" -dependencies = [ - "getrandom", - "redox_syscall", -] - -[[package]] -name = "rustyline" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e1b597fcd1eeb1d6b25b493538e5aa19629eb08932184b85fef931ba87e893" -dependencies = [ - "bitflags", - "cfg-if", - "dirs-next", - "fs2", - "libc", - "log", - "memchr", - "nix", - "radix_trie", - "scopeguard", - "smallvec", - "unicode-segmentation", "unicode-width", - "utf8parse", - "winapi", ] -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "smallvec" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" - [[package]] name = "time" version = "0.1.44" @@ -268,24 +145,12 @@ dependencies = [ "winapi", ] -[[package]] -name = "unicode-segmentation" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" - [[package]] name = "unicode-width" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" -[[package]] -name = "utf8parse" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" - [[package]] name = "uuid" version = "0.8.2" diff --git a/kitchen/Cargo.toml b/kitchen/Cargo.toml index fcc09b6..ca02e0e 100644 --- a/kitchen/Cargo.toml +++ b/kitchen/Cargo.toml @@ -8,4 +8,7 @@ edition = "2018" [dependencies] recipes = {path = "../recipes" } -rustyline = "~8.0.0" + +[dependencies.clap] +version = "2.33" +default-features = false diff --git a/kitchen/src/api.rs b/kitchen/src/api.rs deleted file mode 100644 index 7fe5621..0000000 --- a/kitchen/src/api.rs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2021 Jeremy Wall -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. diff --git a/kitchen/src/cli.rs b/kitchen/src/cli.rs index a3941ed..e0c262d 100644 --- a/kitchen/src/cli.rs +++ b/kitchen/src/cli.rs @@ -11,127 +11,38 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use std::convert::Into; -use std::convert::TryInto; +use std::fs::File; +use std::io::BufReader; +use std::io::Read; +use std::path::Path; -use rustyline::error::ReadlineError; -use rustyline::Editor; +use recipes::{parse, Recipe}; -use recipes::{Ingredient, Recipe, Step}; - -pub enum CliResponse { - Interrupt, - EndOfInput, - ReadItem(T), -} -use CliResponse::*; - -macro_rules! try_cli_resp { - ($res:expr) => { - match $res { - ReadItem(item) => item, - EndOfInput => return Ok(EndOfInput), - Interrupt => return Ok(Interrupt), - } - }; +#[derive(Debug)] +pub enum ParseError { + IO(std::io::Error), + Syntax(String), } -macro_rules! handle_yes_no { - ($rl:expr, $( $rest:tt )+) => { - match try_cli_resp!(read_prompt($rl, "Y/n: ")?).as_str() { - "y" | "Y" | "yes" | "" => { - $( $rest )* - } - _ => { - break - } - } - }; -} - -fn read_prompt(rl: &mut Editor<()>, prompt: &str) -> Result, String> { - Ok(match rl.readline(prompt) { - Ok(line) => ReadItem(line), - Err(ReadlineError::Interrupted) => Interrupt, - Err(ReadlineError::Eof) => EndOfInput, - Err(e) => return Err(e.to_string()), - }) -} - -fn read_new_ingredient(rl: &mut Editor<()>) -> Result>, String> { - let read_item = try_cli_resp!(read_prompt(rl, "> ")?); - Ok(if read_item.is_empty() { - ReadItem(None) - } else { - ReadItem(Some(Ingredient::parse(&read_item)?)) - }) -} - -fn read_ingredients(rl: &mut Editor<()>) -> Result>, String> { - println!("Enter Ingredients in the following form below: [(modifier)]"); - println!(" or enter an empty line to stop entering ingredients"); - let mut ingredient_list = Vec::new(); - loop { - match read_new_ingredient(rl)? { - Interrupt => break, - EndOfInput => return Ok(EndOfInput), - ReadItem(None) => break, - ReadItem(Some(ingredient)) => ingredient_list.push(ingredient), - } +impl From for ParseError { + fn from(err: std::io::Error) -> Self { + ParseError::IO(err) } - Ok(ReadItem(ingredient_list)) } -fn read_new_step(rl: &mut Editor<()>) -> Result, String> { - println!("Enter Recipe Step details below"); - let instructions = try_cli_resp!(read_prompt(rl, "Step Instructions: ")?); - let ingredients = try_cli_resp!(read_ingredients(rl)?); - let mut step = Step::new(None, instructions); - step.add_ingredients(ingredients); - Ok(ReadItem(step)) -} - -fn read_steps(rl: &mut Editor<()>) -> Result>, String> { - let mut steps = Vec::new(); - loop { - println!("Enter a recipe step?"); - handle_yes_no! {rl, - let step = try_cli_resp!(read_new_step(rl)?); - steps.push(step); - }; +impl From for ParseError { + fn from(s: String) -> Self { + ParseError::Syntax(s) } - Ok(ReadItem(steps)) } -//pub fn read_new_recipe(rl: &mut Editor<()>) -> Result, String> { -// println!("Enter recipe details below."); -// let title = try_cli_resp!(read_prompt(rl, "Title: ")?); -// let desc = try_cli_resp!(read_prompt(rl, "Description: ")?); -// let steps = try_cli_resp!(read_steps(rl)?); -// let mut recipe = Recipe::new(title, desc); -// recipe.add_steps(steps); -// Ok(ReadItem(recipe)) -//} - -//fn read_loop(rl: &mut Editor<()>, store: S) -> Result, String> { -// loop { -// println!("Enter a recipe?"); -// handle_yes_no! {rl, -// let recipe = try_cli_resp!(read_new_recipe(rl)?); -// // TODO Store this recipe -// store.store_recipe(&recipe)?; -// }; -// } -// Ok(ReadItem(())) -//} - -//pub fn main_impl(factory: Factory) -//where -// Factory: TryInto, -// Err: std::fmt::Debug, -//{ -// let mut rl = Editor::<()>::new(); -// let store = factory.try_into().unwrap(); -// // TODO(jwall): handle history in a cross platform way? -// //read_loop(&mut rl, store).unwrap(); -//} +pub fn parse_recipe

(path: P) -> Result +where + P: AsRef, +{ + let mut br = BufReader::new(File::open(path)?); + let mut buf = Vec::new(); + br.read_to_end(&mut buf)?; + let i = String::from_utf8_lossy(&buf).to_string(); + Ok(parse::as_recipe(&i)?) +} diff --git a/kitchen/src/main.rs b/kitchen/src/main.rs index 07d8a7e..dceff6a 100644 --- a/kitchen/src/main.rs +++ b/kitchen/src/main.rs @@ -11,9 +11,54 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -mod api; mod cli; -fn main() { - //cli::main_impl(); +use recipes::Recipe; + +use clap; +use clap::{clap_app, crate_authors, crate_version}; + +fn create_app<'a, 'b>() -> clap::App<'a, 'b> +where + 'a: 'b, +{ + clap_app!(kitchen => + (version: crate_version!()) + (author: crate_authors!()) + (about: "Kitchen Management CLI") + (@subcommand recipe => + (about: "parse a recipe file and output info about it") + (@arg ingredients: -i --ingredients "Output the ingredients list.") + (@arg INPUT: +required "Input recipe file to parse") + ) + ) +} + +fn output_recipe_info(r: Recipe, print_ingredients: bool) { + println!("Title: {}", r.title); + println!(""); + println!("Ingredients:"); + if print_ingredients { + for (_, ing) in r.get_ingredients() { + print!("\t* {}", ing.amt); + println!(" {}", ing.name); + } + } +} + +fn main() { + let matches = create_app().get_matches(); + if let Some(matches) = matches.subcommand_matches("recipe") { + // The input argument is required so if we made it here then it's safe to unrwap this value. + let recipe_file = matches.value_of("INPUT").unwrap(); + match cli::parse_recipe(recipe_file) { + Ok(r) => { + // TODO(jwall): handle our recipe dump + output_recipe_info(r, matches.is_present("ingredients")); + } + Err(e) => { + eprintln!("{:?}", e); + } + } + } } diff --git a/recipes/src/parse.rs b/recipes/src/parse.rs index 8813089..f28fd2c 100644 --- a/recipes/src/parse.rs +++ b/recipes/src/parse.rs @@ -24,6 +24,14 @@ use crate::{ Ingredient, Recipe, Step, }; +pub fn as_recipe(i: &str) -> std::result::Result { + match recipe(StrIter::new(i)) { + Result::Abort(e) | Result::Fail(e) => Err(format!("Parse Failure: {:?}", e)), + Result::Incomplete(_) => Err(format!("Incomplete recipe can not parse")), + Result::Complete(_, r) => Ok(r), + } +} + make_fn!( pub recipe, do_each!( diff --git a/recipes/src/unit.rs b/recipes/src/unit.rs index 596a6fe..44b8734 100644 --- a/recipes/src/unit.rs +++ b/recipes/src/unit.rs @@ -24,9 +24,7 @@ use std::{ ops::{Add, Div, Mul, Sub}, }; -use abortable_parser::{ - consume_all, do_each, either, make_fn, not, optional, peek, text_token, trap, Result, StrIter, -}; +use abortable_parser::{Result, StrIter}; use num_rational::Ratio; use crate::parse::measure; @@ -285,7 +283,7 @@ impl Measure { pub fn parse(input: &str) -> std::result::Result { Ok(match measure(StrIter::new(input)) { - Result::Complete(i, measure) => measure, + Result::Complete(_, measure) => measure, Result::Abort(e) | Result::Fail(e) => { return Err(format!("Failed to parse as Measure {:?}", e)) }