diff options
-rw-r--r-- | src/app/impl_self.rs | 83 | ||||
-rw-r--r-- | src/app/impl_view.rs | 13 | ||||
-rw-r--r-- | src/command.rs | 95 | ||||
-rw-r--r-- | 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; | |||
11 | use cursive::Vec2; | 11 | use cursive::Vec2; |
12 | use notify::{watcher, RecursiveMode, Watcher}; | 12 | use notify::{watcher, RecursiveMode, Watcher}; |
13 | 13 | ||
14 | use crate::command::{Command, CommandLineError}; | ||
14 | use crate::habit::{Bit, Count, HabitWrapper, TrackEvent, ViewMode}; | 15 | use crate::habit::{Bit, Count, HabitWrapper, TrackEvent, ViewMode}; |
15 | use crate::utils; | 16 | use crate::utils; |
16 | use crate::Command; | ||
17 | use crate::CONFIGURATION; | 17 | use crate::CONFIGURATION; |
18 | 18 | ||
19 | use crate::app::{App, StatusLine}; | 19 | use crate::app::{App, Message, MessageKind, StatusLine}; |
20 | 20 | ||
21 | impl App { | 21 | impl App { |
22 | pub fn new() -> Self { | 22 | pub fn new() -> Self { |
@@ -33,6 +33,7 @@ impl App { | |||
33 | _file_watcher: watcher, | 33 | _file_watcher: watcher, |
34 | file_event_recv: rx, | 34 | file_event_recv: rx, |
35 | view_month_offset: 0, | 35 | view_month_offset: 0, |
36 | message: Message::default(), | ||
36 | }; | 37 | }; |
37 | } | 38 | } |
38 | 39 | ||
@@ -200,47 +201,49 @@ impl App { | |||
200 | write_to_file(auto, auto_f); | 201 | write_to_file(auto, auto_f); |
201 | } | 202 | } |
202 | 203 | ||
203 | pub fn parse_command(&mut self, c: Command) { | 204 | pub fn parse_command(&mut self, result: Result<Command, CommandLineError>) { |
204 | match c { | 205 | match result { |
205 | Command::Add(name, goal, auto) => { | 206 | Ok(c) => match c { |
206 | let kind = if goal == Some(1) { "bit" } else { "count" }; | 207 | Command::Add(name, goal, auto) => { |
207 | if kind == "count" { | 208 | let kind = if goal == Some(1) { "bit" } else { "count" }; |
208 | self.add_habit(Box::new(Count::new( | 209 | if kind == "count" { |
209 | name, | 210 | self.add_habit(Box::new(Count::new(name, goal.unwrap_or(0), auto))); |
210 | goal.unwrap_or(0), | 211 | } else if kind == "bit" { |
211 | auto.unwrap_or(false), | 212 | self.add_habit(Box::new(Bit::new(name, auto))); |
212 | ))); | 213 | } |
213 | } else if kind == "bit" { | ||
214 | self.add_habit(Box::new(Bit::new(name, auto.unwrap_or(false)))); | ||
215 | } | 214 | } |
216 | } | 215 | Command::Delete(name) => { |
217 | Command::Delete(name) => { | 216 | self.delete_by_name(&name); |
218 | self.delete_by_name(&name); | 217 | self.focus = 0; |
219 | self.focus = 0; | ||
220 | } | ||
221 | Command::TrackUp(name) => { | ||
222 | let target_habit = self | ||
223 | .habits | ||
224 | .iter_mut() | ||
225 | .find(|x| x.name() == name && x.is_auto()); | ||
226 | if let Some(h) = target_habit { | ||
227 | h.modify(Local::now().naive_utc().date(), TrackEvent::Increment); | ||
228 | } | 218 | } |
229 | } | 219 | Command::TrackUp(name) => { |
230 | Command::TrackDown(name) => { | 220 | let target_habit = self |
231 | let target_habit = self | 221 | .habits |
232 | .habits | 222 | .iter_mut() |
233 | .iter_mut() | 223 | .find(|x| x.name() == name && x.is_auto()); |
234 | .find(|x| x.name() == name && x.is_auto()); | 224 | if let Some(h) = target_habit { |
235 | if let Some(h) = target_habit { | 225 | h.modify(Local::now().naive_utc().date(), TrackEvent::Increment); |
236 | h.modify(Local::now().naive_utc().date(), TrackEvent::Decrement); | 226 | } |
237 | } | 227 | } |
238 | } | 228 | Command::TrackDown(name) => { |
239 | Command::Quit => self.save_state(), | 229 | let target_habit = self |
240 | Command::MonthNext => self.sift_forward(), | 230 | .habits |
241 | Command::MonthPrev => self.sift_backward(), | 231 | .iter_mut() |
242 | _ => { | 232 | .find(|x| x.name() == name && x.is_auto()); |
243 | eprintln!("UNKNOWN COMMAND!"); | 233 | if let Some(h) = target_habit { |
234 | h.modify(Local::now().naive_utc().date(), TrackEvent::Decrement); | ||
235 | } | ||
236 | } | ||
237 | Command::Quit => self.save_state(), | ||
238 | Command::MonthNext => self.sift_forward(), | ||
239 | Command::MonthPrev => self.sift_backward(), | ||
240 | _ => { | ||
241 | eprintln!("UNKNOWN COMMAND!"); | ||
242 | } | ||
243 | }, | ||
244 | Err(e) => { | ||
245 | self.message.set_message(e.to_string()); | ||
246 | self.message.set_kind(MessageKind::Error); | ||
244 | } | 247 | } |
245 | } | 248 | } |
246 | } | 249 | } |
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; | |||
5 | 5 | ||
6 | use cursive::direction::{Absolute, Direction}; | 6 | use cursive::direction::{Absolute, Direction}; |
7 | use cursive::event::{Event, EventResult, Key}; | 7 | use cursive::event::{Event, EventResult, Key}; |
8 | use cursive::theme::{Color, Style}; | ||
8 | use cursive::view::View; | 9 | use cursive::view::View; |
9 | use cursive::{Printer, Vec2}; | 10 | use cursive::{Printer, Vec2}; |
10 | use notify::DebouncedEvent; | 11 | use notify::DebouncedEvent; |
11 | 12 | ||
12 | use crate::app::App; | 13 | use crate::app::{App, MessageKind}; |
13 | use crate::habit::{HabitWrapper, ViewMode}; | 14 | use crate::habit::{HabitWrapper, ViewMode}; |
14 | use crate::utils; | 15 | use crate::utils; |
15 | use crate::CONFIGURATION; | 16 | use crate::CONFIGURATION; |
@@ -36,6 +37,11 @@ impl View for App { | |||
36 | let full = self.max_size().x; | 37 | let full = self.max_size().x; |
37 | offset = offset.map_x(|_| full - status.1.len()); | 38 | offset = offset.map_x(|_| full - status.1.len()); |
38 | printer.print(offset, &status.1); // right status | 39 | printer.print(offset, &status.1); // right status |
40 | |||
41 | offset = offset.map_x(|_| 0).map_y(|_| self.max_size().y - 1); | ||
42 | printer.with_style(Color::from(self.message.kind()), |p| { | ||
43 | p.print(offset, self.message.contents()) | ||
44 | }); | ||
39 | } | 45 | } |
40 | 46 | ||
41 | fn required_size(&mut self, _: Vec2) -> Vec2 { | 47 | fn required_size(&mut self, _: Vec2) -> Vec2 { |
@@ -158,6 +164,11 @@ impl View for App { | |||
158 | self.set_view_month_offset(0); | 164 | self.set_view_month_offset(0); |
159 | return EventResult::Consumed(None); | 165 | return EventResult::Consumed(None); |
160 | } | 166 | } |
167 | Event::CtrlChar('l') => { | ||
168 | self.message.clear(); | ||
169 | self.message.set_kind(MessageKind::Info); | ||
170 | return EventResult::Consumed(None); | ||
171 | } | ||
161 | 172 | ||
162 | /* Every keybind that is not caught by App trickles | 173 | /* Every keybind that is not caught by App trickles |
163 | * down to the focused habit. We sift back to today | 174 | * 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 @@ | |||
1 | use std::fmt; | ||
2 | |||
1 | use cursive::view::Resizable; | 3 | use cursive::view::Resizable; |
2 | use cursive::views::{Dialog, EditView}; | 4 | use cursive::views::{Dialog, EditView}; |
3 | use cursive::Cursive; | 5 | use cursive::Cursive; |
@@ -19,7 +21,7 @@ fn call_on_app(s: &mut Cursive, input: &str) { | |||
19 | // our main cursive object, has to be parsed again | 21 | // our main cursive object, has to be parsed again |
20 | // here | 22 | // here |
21 | // TODO: fix this somehow | 23 | // TODO: fix this somehow |
22 | if Command::from_string(input) == Command::Quit { | 24 | if let Ok(Command::Quit) = Command::from_string(input) { |
23 | s.quit(); | 25 | s.quit(); |
24 | } | 26 | } |
25 | 27 | ||
@@ -28,7 +30,7 @@ fn call_on_app(s: &mut Cursive, input: &str) { | |||
28 | 30 | ||
29 | #[derive(PartialEq)] | 31 | #[derive(PartialEq)] |
30 | pub enum Command { | 32 | pub enum Command { |
31 | Add(String, Option<u32>, Option<bool>), // habit name, habit type, optional goal, auto tracked | 33 | Add(String, Option<u32>, bool), |
32 | MonthPrev, | 34 | MonthPrev, |
33 | MonthNext, | 35 | MonthNext, |
34 | Delete(String), | 36 | Delete(String), |
@@ -38,46 +40,95 @@ pub enum Command { | |||
38 | Blank, | 40 | Blank, |
39 | } | 41 | } |
40 | 42 | ||
43 | #[derive(Debug)] | ||
44 | pub enum CommandLineError { | ||
45 | InvalidCommand(String), | ||
46 | InvalidArg(u32), // position | ||
47 | NotEnoughArgs(String, u32), | ||
48 | } | ||
49 | |||
50 | impl std::error::Error for CommandLineError {} | ||
51 | |||
52 | impl fmt::Display for CommandLineError { | ||
53 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
54 | match self { | ||
55 | CommandLineError::InvalidCommand(s) => write!(f, "Invalid command: `{}`", s), | ||
56 | CommandLineError::InvalidArg(p) => write!(f, "Invalid argument at position {}", p), | ||
57 | CommandLineError::NotEnoughArgs(s, n) => { | ||
58 | write!(f, "Command `{}` requires atleast {} argument(s)!", s, n) | ||
59 | } | ||
60 | } | ||
61 | } | ||
62 | } | ||
63 | |||
64 | type Result<T> = std::result::Result<T, CommandLineError>; | ||
65 | |||
41 | impl Command { | 66 | impl Command { |
42 | pub fn from_string<P: AsRef<str>>(input: P) -> Self { | 67 | pub fn from_string<P: AsRef<str>>(input: P) -> Result<Command> { |
43 | let mut strings: Vec<&str> = input.as_ref().trim().split(' ').collect(); | 68 | let mut strings: Vec<&str> = input.as_ref().trim().split(' ').collect(); |
44 | if strings.is_empty() { | 69 | if strings.is_empty() { |
45 | return Command::Blank; | 70 | return Ok(Command::Blank); |
46 | } | 71 | } |
47 | 72 | ||
48 | let first = strings.first().unwrap().to_string(); | 73 | let first = strings.first().unwrap().to_string(); |
49 | let mut args: Vec<String> = strings.iter_mut().skip(1).map(|s| s.to_string()).collect(); | 74 | let mut args: Vec<String> = strings.iter_mut().skip(1).map(|s| s.to_string()).collect(); |
50 | match first.as_ref() { | 75 | match first.as_ref() { |
51 | "add" | "a" => { | 76 | "add" | "a" => { |
52 | if args.len() < 2 { | 77 | if args.is_empty() { |
53 | return Command::Blank; | 78 | return Err(CommandLineError::NotEnoughArgs(first, 1)); |
79 | } | ||
80 | let goal = args | ||
81 | .get(1) | ||
82 | .map(|x| { | ||
83 | x.parse::<u32>() | ||
84 | .map_err(|_| CommandLineError::InvalidArg(1)) | ||
85 | }) | ||
86 | .transpose()?; | ||
87 | return Ok(Command::Add( | ||
88 | args.get_mut(0).unwrap().to_string(), | ||
89 | goal, | ||
90 | false, | ||
91 | )); | ||
92 | } | ||
93 | "add-auto" | "aa" => { | ||
94 | if args.is_empty() { | ||
95 | return Err(CommandLineError::NotEnoughArgs(first, 1)); | ||
54 | } | 96 | } |
55 | let goal = args.get(1).map(|g| g.parse::<u32>().ok()).flatten(); | 97 | let goal = args |
56 | let auto = args.get(2).map(|g| if g == "auto" { true } else { false }); | 98 | .get(1) |
57 | return Command::Add(args.get_mut(0).unwrap().to_string(), goal, auto); | 99 | .map(|x| { |
100 | x.parse::<u32>() | ||
101 | .map_err(|_| CommandLineError::InvalidArg(1)) | ||
102 | }) | ||
103 | .transpose()?; | ||
104 | return Ok(Command::Add( | ||
105 | args.get_mut(0).unwrap().to_string(), | ||
106 | goal, | ||
107 | true, | ||
108 | )); | ||
58 | } | 109 | } |
59 | "delete" | "d" => { | 110 | "delete" | "d" => { |
60 | if args.len() < 1 { | 111 | if args.is_empty() { |
61 | return Command::Blank; | 112 | return Err(CommandLineError::NotEnoughArgs(first, 1)); |
62 | } | 113 | } |
63 | return Command::Delete(args[0].to_string()); | 114 | return Ok(Command::Delete(args[0].to_string())); |
64 | } | 115 | } |
65 | "track-up" | "tup" => { | 116 | "track-up" | "tup" => { |
66 | if args.len() < 1 { | 117 | if args.is_empty() { |
67 | return Command::Blank; | 118 | return Err(CommandLineError::NotEnoughArgs(first, 1)); |
68 | } | 119 | } |
69 | return Command::TrackUp(args[0].to_string()); | 120 | return Ok(Command::TrackUp(args[0].to_string())); |
70 | } | 121 | } |
71 | "track-down" | "tdown" => { | 122 | "track-down" | "tdown" => { |
72 | if args.len() < 1 { | 123 | if args.is_empty() { |
73 | return Command::Blank; | 124 | return Err(CommandLineError::NotEnoughArgs(first, 1)); |
74 | } | 125 | } |
75 | return Command::TrackDown(args[0].to_string()); | 126 | return Ok(Command::TrackDown(args[0].to_string())); |
76 | } | 127 | } |
77 | "mprev" | "month-prev" => return Command::MonthPrev, | 128 | "mprev" | "month-prev" => return Ok(Command::MonthPrev), |
78 | "mnext" | "month-next" => return Command::MonthNext, | 129 | "mnext" | "month-next" => return Ok(Command::MonthNext), |
79 | "q" | "quit" => return Command::Quit, | 130 | "q" | "quit" => return Ok(Command::Quit), |
80 | _ => return Command::Blank, | 131 | s => return Err(CommandLineError::InvalidCommand(s.into())), |
81 | } | 132 | } |
82 | } | 133 | } |
83 | } | 134 | } |
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() { | |||
36 | .get_matches(); | 36 | .get_matches(); |
37 | if let Some(c) = matches.value_of("command") { | 37 | if let Some(c) = matches.value_of("command") { |
38 | let command = Command::from_string(c); | 38 | let command = Command::from_string(c); |
39 | if matches!(command, Command::TrackUp(_) | Command::TrackDown(_)) { | 39 | match command { |
40 | let mut app = App::load_state(); | 40 | Ok(Command::TrackUp(_)) | Ok(Command::TrackDown(_)) => { |
41 | app.parse_command(command); | 41 | let mut app = App::load_state(); |
42 | app.save_state(); | 42 | app.parse_command(command); |
43 | } else { | 43 | app.save_state(); |
44 | eprintln!("Invalid or unsupported command!"); | 44 | } |
45 | Err(e) => { | ||
46 | eprintln!("{}", e); | ||
47 | } | ||
48 | _ => eprintln!( | ||
49 | "Commands other than `track-up` and `track-down` are currently not supported!" | ||
50 | ), | ||
45 | } | 51 | } |
46 | } else { | 52 | } else { |
47 | let mut s = termion().unwrap(); | 53 | let mut s = termion().unwrap(); |