Basic cli skeleton in place with clap

This commit is contained in:
Jeremy Wall 2021-11-09 20:26:52 -05:00
parent d261850bd6
commit 9c5bef632d
7 changed files with 97 additions and 280 deletions

155
Cargo.lock generated
View File

@ -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"

View File

@ -8,4 +8,7 @@ edition = "2018"
[dependencies]
recipes = {path = "../recipes" }
rustyline = "~8.0.0"
[dependencies.clap]
version = "2.33"
default-features = false

View File

@ -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.

View File

@ -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<T> {
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<CliResponse<String>, 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<CliResponse<Option<Ingredient>>, 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<CliResponse<Vec<Ingredient>>, String> {
println!("Enter Ingredients in the following form below: <amt> <unit> <name> [(modifier)]");
println!("<Ctrl-C> 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<std::io::Error> for ParseError {
fn from(err: std::io::Error) -> Self {
ParseError::IO(err)
}
Ok(ReadItem(ingredient_list))
}
fn read_new_step(rl: &mut Editor<()>) -> Result<CliResponse<Step>, 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<CliResponse<Vec<Step>>, 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<String> for ParseError {
fn from(s: String) -> Self {
ParseError::Syntax(s)
}
Ok(ReadItem(steps))
}
//pub fn read_new_recipe(rl: &mut Editor<()>) -> Result<CliResponse<Recipe>, 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<S: RecipeStore>(rl: &mut Editor<()>, store: S) -> Result<CliResponse<()>, 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, Err>(factory: Factory)
//where
// Factory: TryInto<Error = Err>,
// 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<P>(path: P) -> Result<Recipe, ParseError>
where
P: AsRef<Path>,
{
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)?)
}

View File

@ -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);
}
}
}
}

View File

@ -24,6 +24,14 @@ use crate::{
Ingredient, Recipe, Step,
};
pub fn as_recipe(i: &str) -> std::result::Result<Recipe, String> {
match recipe(StrIter::new(i)) {
Result::Abort(e) | Result::Fail(e) => Err(format!("Parse Failure: {:?}", e)),
Result::Incomplete(_) => Err(format!("Incomplete recipe can not parse")),
Result::Complete(_, r) => Ok(r),
}
}
make_fn!(
pub recipe<StrIter, Recipe>,
do_each!(

View File

@ -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<Self, String> {
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))
}