From 0b4af96a515d51c409c6dafef406542dee9da3d4 Mon Sep 17 00:00:00 2001 From: Akshay Date: Sat, 18 Jul 2020 21:06:40 +0530 Subject: add smooth error handling --- src/app/impl_self.rs | 83 +++++++++++++++++++++++---------------------- src/app/impl_view.rs | 13 ++++++- src/command.rs | 95 ++++++++++++++++++++++++++++++++++++++++------------ src/main.rs | 18 ++++++---- 4 files changed, 140 insertions(+), 69 deletions(-) diff --git a/src/app/impl_self.rs b/src/app/impl_self.rs index efed4e0..7bf9ab0 100644 --- a/src/app/impl_self.rs +++ b/src/app/impl_self.rs @@ -11,12 +11,12 @@ use cursive::direction::Absolute; use cursive::Vec2; use notify::{watcher, RecursiveMode, Watcher}; +use crate::command::{Command, CommandLineError}; use crate::habit::{Bit, Count, HabitWrapper, TrackEvent, ViewMode}; use crate::utils; -use crate::Command; use crate::CONFIGURATION; -use crate::app::{App, StatusLine}; +use crate::app::{App, Message, MessageKind, StatusLine}; impl App { pub fn new() -> Self { @@ -33,6 +33,7 @@ impl App { _file_watcher: watcher, file_event_recv: rx, view_month_offset: 0, + message: Message::default(), }; } @@ -200,47 +201,49 @@ impl App { write_to_file(auto, auto_f); } - pub fn parse_command(&mut self, c: Command) { - match c { - Command::Add(name, goal, auto) => { - let kind = if goal == Some(1) { "bit" } else { "count" }; - if kind == "count" { - self.add_habit(Box::new(Count::new( - name, - goal.unwrap_or(0), - auto.unwrap_or(false), - ))); - } else if kind == "bit" { - self.add_habit(Box::new(Bit::new(name, auto.unwrap_or(false)))); + pub fn parse_command(&mut self, result: Result) { + match result { + Ok(c) => match c { + Command::Add(name, goal, auto) => { + let kind = if goal == Some(1) { "bit" } else { "count" }; + if kind == "count" { + self.add_habit(Box::new(Count::new(name, goal.unwrap_or(0), auto))); + } else if kind == "bit" { + self.add_habit(Box::new(Bit::new(name, auto))); + } } - } - Command::Delete(name) => { - self.delete_by_name(&name); - self.focus = 0; - } - Command::TrackUp(name) => { - let target_habit = self - .habits - .iter_mut() - .find(|x| x.name() == name && x.is_auto()); - if let Some(h) = target_habit { - h.modify(Local::now().naive_utc().date(), TrackEvent::Increment); + Command::Delete(name) => { + self.delete_by_name(&name); + self.focus = 0; } - } - Command::TrackDown(name) => { - let target_habit = self - .habits - .iter_mut() - .find(|x| x.name() == name && x.is_auto()); - if let Some(h) = target_habit { - h.modify(Local::now().naive_utc().date(), TrackEvent::Decrement); + Command::TrackUp(name) => { + let target_habit = self + .habits + .iter_mut() + .find(|x| x.name() == name && x.is_auto()); + if let Some(h) = target_habit { + h.modify(Local::now().naive_utc().date(), TrackEvent::Increment); + } } - } - Command::Quit => self.save_state(), - Command::MonthNext => self.sift_forward(), - Command::MonthPrev => self.sift_backward(), - _ => { - eprintln!("UNKNOWN COMMAND!"); + Command::TrackDown(name) => { + let target_habit = self + .habits + .iter_mut() + .find(|x| x.name() == name && x.is_auto()); + if let Some(h) = target_habit { + h.modify(Local::now().naive_utc().date(), TrackEvent::Decrement); + } + } + Command::Quit => self.save_state(), + Command::MonthNext => self.sift_forward(), + Command::MonthPrev => self.sift_backward(), + _ => { + eprintln!("UNKNOWN COMMAND!"); + } + }, + Err(e) => { + self.message.set_message(e.to_string()); + self.message.set_kind(MessageKind::Error); } } } diff --git a/src/app/impl_view.rs b/src/app/impl_view.rs index 904403b..0a6bce6 100644 --- a/src/app/impl_view.rs +++ b/src/app/impl_view.rs @@ -5,11 +5,12 @@ use std::path::PathBuf; use cursive::direction::{Absolute, Direction}; use cursive::event::{Event, EventResult, Key}; +use cursive::theme::{Color, Style}; use cursive::view::View; use cursive::{Printer, Vec2}; use notify::DebouncedEvent; -use crate::app::App; +use crate::app::{App, MessageKind}; use crate::habit::{HabitWrapper, ViewMode}; use crate::utils; use crate::CONFIGURATION; @@ -36,6 +37,11 @@ impl View for App { let full = self.max_size().x; offset = offset.map_x(|_| full - status.1.len()); printer.print(offset, &status.1); // right status + + offset = offset.map_x(|_| 0).map_y(|_| self.max_size().y - 1); + printer.with_style(Color::from(self.message.kind()), |p| { + p.print(offset, self.message.contents()) + }); } fn required_size(&mut self, _: Vec2) -> Vec2 { @@ -158,6 +164,11 @@ impl View for App { self.set_view_month_offset(0); return EventResult::Consumed(None); } + Event::CtrlChar('l') => { + self.message.clear(); + self.message.set_kind(MessageKind::Info); + return EventResult::Consumed(None); + } /* Every keybind that is not caught by App trickles * down to the focused habit. We sift back to today diff --git a/src/command.rs b/src/command.rs index 79d0fe5..d285138 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,3 +1,5 @@ +use std::fmt; + use cursive::view::Resizable; use cursive::views::{Dialog, EditView}; use cursive::Cursive; @@ -19,7 +21,7 @@ fn call_on_app(s: &mut Cursive, input: &str) { // our main cursive object, has to be parsed again // here // TODO: fix this somehow - if Command::from_string(input) == Command::Quit { + if let Ok(Command::Quit) = Command::from_string(input) { s.quit(); } @@ -28,7 +30,7 @@ fn call_on_app(s: &mut Cursive, input: &str) { #[derive(PartialEq)] pub enum Command { - Add(String, Option, Option), // habit name, habit type, optional goal, auto tracked + Add(String, Option, bool), MonthPrev, MonthNext, Delete(String), @@ -38,46 +40,95 @@ pub enum Command { Blank, } +#[derive(Debug)] +pub enum CommandLineError { + InvalidCommand(String), + InvalidArg(u32), // position + NotEnoughArgs(String, u32), +} + +impl std::error::Error for CommandLineError {} + +impl fmt::Display for CommandLineError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + CommandLineError::InvalidCommand(s) => write!(f, "Invalid command: `{}`", s), + CommandLineError::InvalidArg(p) => write!(f, "Invalid argument at position {}", p), + CommandLineError::NotEnoughArgs(s, n) => { + write!(f, "Command `{}` requires atleast {} argument(s)!", s, n) + } + } + } +} + +type Result = std::result::Result; + impl Command { - pub fn from_string>(input: P) -> Self { + pub fn from_string>(input: P) -> Result { let mut strings: Vec<&str> = input.as_ref().trim().split(' ').collect(); if strings.is_empty() { - return Command::Blank; + return Ok(Command::Blank); } let first = strings.first().unwrap().to_string(); let mut args: Vec = strings.iter_mut().skip(1).map(|s| s.to_string()).collect(); match first.as_ref() { "add" | "a" => { - if args.len() < 2 { - return Command::Blank; + if args.is_empty() { + return Err(CommandLineError::NotEnoughArgs(first, 1)); + } + let goal = args + .get(1) + .map(|x| { + x.parse::() + .map_err(|_| CommandLineError::InvalidArg(1)) + }) + .transpose()?; + return Ok(Command::Add( + args.get_mut(0).unwrap().to_string(), + goal, + false, + )); + } + "add-auto" | "aa" => { + if args.is_empty() { + return Err(CommandLineError::NotEnoughArgs(first, 1)); } - let goal = args.get(1).map(|g| g.parse::().ok()).flatten(); - let auto = args.get(2).map(|g| if g == "auto" { true } else { false }); - return Command::Add(args.get_mut(0).unwrap().to_string(), goal, auto); + let goal = args + .get(1) + .map(|x| { + x.parse::() + .map_err(|_| CommandLineError::InvalidArg(1)) + }) + .transpose()?; + return Ok(Command::Add( + args.get_mut(0).unwrap().to_string(), + goal, + true, + )); } "delete" | "d" => { - if args.len() < 1 { - return Command::Blank; + if args.is_empty() { + return Err(CommandLineError::NotEnoughArgs(first, 1)); } - return Command::Delete(args[0].to_string()); + return Ok(Command::Delete(args[0].to_string())); } "track-up" | "tup" => { - if args.len() < 1 { - return Command::Blank; + if args.is_empty() { + return Err(CommandLineError::NotEnoughArgs(first, 1)); } - return Command::TrackUp(args[0].to_string()); + return Ok(Command::TrackUp(args[0].to_string())); } "track-down" | "tdown" => { - if args.len() < 1 { - return Command::Blank; + if args.is_empty() { + return Err(CommandLineError::NotEnoughArgs(first, 1)); } - return Command::TrackDown(args[0].to_string()); + return Ok(Command::TrackDown(args[0].to_string())); } - "mprev" | "month-prev" => return Command::MonthPrev, - "mnext" | "month-next" => return Command::MonthNext, - "q" | "quit" => return Command::Quit, - _ => return Command::Blank, + "mprev" | "month-prev" => return Ok(Command::MonthPrev), + "mnext" | "month-next" => return Ok(Command::MonthNext), + "q" | "quit" => return Ok(Command::Quit), + s => return Err(CommandLineError::InvalidCommand(s.into())), } } } diff --git a/src/main.rs b/src/main.rs index dc6081d..f83fc83 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,12 +36,18 @@ fn main() { .get_matches(); if let Some(c) = matches.value_of("command") { let command = Command::from_string(c); - if matches!(command, Command::TrackUp(_) | Command::TrackDown(_)) { - let mut app = App::load_state(); - app.parse_command(command); - app.save_state(); - } else { - eprintln!("Invalid or unsupported command!"); + match command { + Ok(Command::TrackUp(_)) | Ok(Command::TrackDown(_)) => { + let mut app = App::load_state(); + app.parse_command(command); + app.save_state(); + } + Err(e) => { + eprintln!("{}", e); + } + _ => eprintln!( + "Commands other than `track-up` and `track-down` are currently not supported!" + ), } } else { let mut s = termion().unwrap(); -- cgit v1.2.3