aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/app/impl_self.rs83
-rw-r--r--src/app/impl_view.rs13
-rw-r--r--src/command.rs95
-rw-r--r--src/main.rs18
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;
11use cursive::Vec2; 11use cursive::Vec2;
12use notify::{watcher, RecursiveMode, Watcher}; 12use notify::{watcher, RecursiveMode, Watcher};
13 13
14use crate::command::{Command, CommandLineError};
14use crate::habit::{Bit, Count, HabitWrapper, TrackEvent, ViewMode}; 15use crate::habit::{Bit, Count, HabitWrapper, TrackEvent, ViewMode};
15use crate::utils; 16use crate::utils;
16use crate::Command;
17use crate::CONFIGURATION; 17use crate::CONFIGURATION;
18 18
19use crate::app::{App, StatusLine}; 19use crate::app::{App, Message, MessageKind, StatusLine};
20 20
21impl App { 21impl 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
6use cursive::direction::{Absolute, Direction}; 6use cursive::direction::{Absolute, Direction};
7use cursive::event::{Event, EventResult, Key}; 7use cursive::event::{Event, EventResult, Key};
8use cursive::theme::{Color, Style};
8use cursive::view::View; 9use cursive::view::View;
9use cursive::{Printer, Vec2}; 10use cursive::{Printer, Vec2};
10use notify::DebouncedEvent; 11use notify::DebouncedEvent;
11 12
12use crate::app::App; 13use crate::app::{App, MessageKind};
13use crate::habit::{HabitWrapper, ViewMode}; 14use crate::habit::{HabitWrapper, ViewMode};
14use crate::utils; 15use crate::utils;
15use crate::CONFIGURATION; 16use 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 @@
1use std::fmt;
2
1use cursive::view::Resizable; 3use cursive::view::Resizable;
2use cursive::views::{Dialog, EditView}; 4use cursive::views::{Dialog, EditView};
3use cursive::Cursive; 5use 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)]
30pub enum Command { 32pub 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)]
44pub enum CommandLineError {
45 InvalidCommand(String),
46 InvalidArg(u32), // position
47 NotEnoughArgs(String, u32),
48}
49
50impl std::error::Error for CommandLineError {}
51
52impl 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
64type Result<T> = std::result::Result<T, CommandLineError>;
65
41impl Command { 66impl 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();