mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-21 19:29:49 -04:00
Basic cli skeleton in place with clap
This commit is contained in:
parent
d261850bd6
commit
9c5bef632d
155
Cargo.lock
generated
155
Cargo.lock
generated
@ -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"
|
||||
|
@ -8,4 +8,7 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
recipes = {path = "../recipes" }
|
||||
rustyline = "~8.0.0"
|
||||
|
||||
[dependencies.clap]
|
||||
version = "2.33"
|
||||
default-features = false
|
||||
|
@ -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.
|
@ -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)?)
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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!(
|
||||
|
@ -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))
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user