aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/main.yml119
-rw-r--r--Cargo.lock80
-rw-r--r--Cargo.toml4
-rw-r--r--readme.md7
-rw-r--r--src/app/impl_self.rs41
-rw-r--r--src/app/impl_view.rs19
-rw-r--r--src/command.rs87
-rw-r--r--src/habit/bit.rs17
-rw-r--r--src/habit/count.rs7
-rw-r--r--src/habit/prelude.rs1
-rw-r--r--src/main.rs23
-rw-r--r--src/views.rs18
12 files changed, 324 insertions, 99 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..e2995b8
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,119 @@
1name: Rust
2
3on:
4 push:
5 tags:
6 - '*'
7
8jobs:
9 rustfmt:
10 runs-on: ubuntu-latest
11 steps:
12 - uses: actions/checkout@v1
13 - run: rustup component add rustfmt
14 - run: cargo fmt -- --check
15
16 build-linux:
17 runs-on: ubuntu-latest
18
19 steps:
20 - name: Checkout
21 uses: actions/checkout@v1
22 # cache the build assets so they dont recompile every time.
23 - name: Cache Rust dependencies
24 uses: actions/[email protected]
25 with:
26 path: target
27 key: ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }}
28 restore-keys: |
29 ${{ runner.OS }}-build-
30 - name: Install latest rust toolchain
31 uses: actions-rs/toolchain@v1
32 with:
33 toolchain: beta
34 default: true
35 override: true
36 - name: Install system dependencies
37 run: |
38 sudo apt-get update \
39 && sudo apt-get install -y \
40 libdbus-1-dev
41 - name: Build
42 run: cargo build --all --release && strip target/release/dijo
43
44 - name: Upload binaries to release
45 uses: svenstaro/upload-release-action@v1-release
46 with:
47 repo_token: ${{ secrets.GITHUB_TOKEN }}
48 file: target/release/dijo
49 asset_name: dijo-x86_64-linux
50 tag: ${{ github.ref }}
51 overwrite: true
52
53 build-apple:
54 runs-on: macos-latest
55
56 steps:
57 - name: Checkout
58 uses: actions/checkout@v1
59 - name: Cache Rust dependencies
60 uses: actions/[email protected]
61 with:
62 path: target
63 key: ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }}
64 restore-keys: |
65 ${{ runner.OS }}-build-
66 - name: Install latest rust toolchain
67 uses: actions-rs/toolchain@v1
68 with:
69 toolchain: beta
70 target: x86_64-apple-darwin
71 default: true
72 override: true
73
74 - name: Build for mac
75 run: cargo build --all --release && strip target/release/dijo
76
77 - name: Upload binaries to release
78 uses: svenstaro/upload-release-action@v1-release
79 with:
80 repo_token: ${{ secrets.GITHUB_TOKEN }}
81 file: target/release/dijo
82 asset_name: dijo-x86_64-apple
83 tag: ${{ github.ref }}
84 overwrite: true
85
86 build-windows:
87 runs-on: windows-latest
88
89 steps:
90 - name: Checkout
91 uses: actions/checkout@v1
92 - name: Cache Rust dependencies
93 uses: actions/[email protected]
94 with:
95 path: target
96 key: ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }}
97 restore-keys: |
98 ${{ runner.OS }}-build-
99 - name: Install latest rust toolchain
100 uses: actions-rs/toolchain@v1
101 with:
102 toolchain: beta
103 target: x86_64-pc-windows-msvc
104 default: true
105 override: true
106
107 - name: Build for windows
108 run: |
109 cargo build --all --release
110 strip target/release/dijo
111
112 - name: Upload binaries to release
113 uses: svenstaro/upload-release-action@v1-release
114 with:
115 repo_token: ${{ secrets.GITHUB_TOKEN }}
116 file: target/release/dijo
117 asset_name: dijo-x86_64-windows
118 tag: ${{ github.ref }}
119 overwrite: true
diff --git a/Cargo.lock b/Cargo.lock
index 6ff6a8a..c4f9734 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -92,12 +92,6 @@ dependencies = [
92] 92]
93 93
94[[package]] 94[[package]]
95name = "cc"
96version = "1.0.58"
97source = "registry+https://github.com/rust-lang/crates.io-index"
98checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518"
99
100[[package]]
101name = "cfg-if" 95name = "cfg-if"
102version = "0.1.10" 96version = "0.1.10"
103source = "registry+https://github.com/rust-lang/crates.io-index" 97source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -167,12 +161,12 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
167 161
168[[package]] 162[[package]]
169name = "crossbeam-channel" 163name = "crossbeam-channel"
170version = "0.4.2" 164version = "0.4.3"
171source = "registry+https://github.com/rust-lang/crates.io-index" 165source = "registry+https://github.com/rust-lang/crates.io-index"
172checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" 166checksum = "09ee0cc8804d5393478d743b035099520087a5186f3b93fa58cec08fa62407b6"
173dependencies = [ 167dependencies = [
168 "cfg-if",
174 "crossbeam-utils", 169 "crossbeam-utils",
175 "maybe-uninit",
176] 170]
177 171
178[[package]] 172[[package]]
@@ -236,10 +230,8 @@ dependencies = [
236 "lazy_static", 230 "lazy_static",
237 "libc", 231 "libc",
238 "log", 232 "log",
239 "maplit",
240 "ncurses",
241 "signal-hook", 233 "signal-hook",
242 "term_size", 234 "termion",
243 "unicode-segmentation", 235 "unicode-segmentation",
244 "unicode-width", 236 "unicode-width",
245] 237]
@@ -303,7 +295,7 @@ dependencies = [
303 295
304[[package]] 296[[package]]
305name = "dijo" 297name = "dijo"
306version = "0.1.3" 298version = "0.2.0"
307dependencies = [ 299dependencies = [
308 "chrono", 300 "chrono",
309 "clap", 301 "clap",
@@ -583,18 +575,6 @@ dependencies = [
583] 575]
584 576
585[[package]] 577[[package]]
586name = "maplit"
587version = "1.0.2"
588source = "registry+https://github.com/rust-lang/crates.io-index"
589checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
590
591[[package]]
592name = "maybe-uninit"
593version = "2.0.0"
594source = "registry+https://github.com/rust-lang/crates.io-index"
595checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
596
597[[package]]
598name = "mio" 578name = "mio"
599version = "0.6.22" 579version = "0.6.22"
600source = "registry+https://github.com/rust-lang/crates.io-index" 580source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -662,17 +642,6 @@ dependencies = [
662] 642]
663 643
664[[package]] 644[[package]]
665name = "ncurses"
666version = "5.99.0"
667source = "registry+https://github.com/rust-lang/crates.io-index"
668checksum = "15699bee2f37e9f8828c7b35b2bc70d13846db453f2d507713b758fabe536b82"
669dependencies = [
670 "cc",
671 "libc",
672 "pkg-config",
673]
674
675[[package]]
676name = "net2" 645name = "net2"
677version = "0.2.34" 646version = "0.2.34"
678source = "registry+https://github.com/rust-lang/crates.io-index" 647source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -774,6 +743,12 @@ dependencies = [
774] 743]
775 744
776[[package]] 745[[package]]
746name = "numtoa"
747version = "0.1.0"
748source = "registry+https://github.com/rust-lang/crates.io-index"
749checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
750
751[[package]]
777name = "owning_ref" 752name = "owning_ref"
778version = "0.4.1" 753version = "0.4.1"
779source = "registry+https://github.com/rust-lang/crates.io-index" 754source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -807,12 +782,6 @@ dependencies = [
807] 782]
808 783
809[[package]] 784[[package]]
810name = "pkg-config"
811version = "0.3.18"
812source = "registry+https://github.com/rust-lang/crates.io-index"
813checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33"
814
815[[package]]
816name = "proc-macro-hack" 785name = "proc-macro-hack"
817version = "0.5.16" 786version = "0.5.16"
818source = "registry+https://github.com/rust-lang/crates.io-index" 787source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -820,9 +789,9 @@ checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4"
820 789
821[[package]] 790[[package]]
822name = "proc-macro2" 791name = "proc-macro2"
823version = "1.0.18" 792version = "1.0.19"
824source = "registry+https://github.com/rust-lang/crates.io-index" 793source = "registry+https://github.com/rust-lang/crates.io-index"
825checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" 794checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12"
826dependencies = [ 795dependencies = [
827 "unicode-xid", 796 "unicode-xid",
828] 797]
@@ -843,6 +812,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
843checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 812checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
844 813
845[[package]] 814[[package]]
815name = "redox_termios"
816version = "0.1.1"
817source = "registry+https://github.com/rust-lang/crates.io-index"
818checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
819dependencies = [
820 "redox_syscall",
821]
822
823[[package]]
846name = "redox_users" 824name = "redox_users"
847version = "0.3.4" 825version = "0.3.4"
848source = "registry+https://github.com/rust-lang/crates.io-index" 826source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -982,9 +960,9 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
982 960
983[[package]] 961[[package]]
984name = "syn" 962name = "syn"
985version = "1.0.34" 963version = "1.0.35"
986source = "registry+https://github.com/rust-lang/crates.io-index" 964source = "registry+https://github.com/rust-lang/crates.io-index"
987checksum = "936cae2873c940d92e697597c5eee105fb570cd5689c695806f672883653349b" 965checksum = "fb7f4c519df8c117855e19dd8cc851e89eb746fe7a73f0157e0d95fdec5369b0"
988dependencies = [ 966dependencies = [
989 "proc-macro2", 967 "proc-macro2",
990 "quote", 968 "quote",
@@ -992,13 +970,15 @@ dependencies = [
992] 970]
993 971
994[[package]] 972[[package]]
995name = "term_size" 973name = "termion"
996version = "0.3.2" 974version = "1.5.5"
997source = "registry+https://github.com/rust-lang/crates.io-index" 975source = "registry+https://github.com/rust-lang/crates.io-index"
998checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" 976checksum = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905"
999dependencies = [ 977dependencies = [
1000 "libc", 978 "libc",
1001 "winapi 0.3.9", 979 "numtoa",
980 "redox_syscall",
981 "redox_termios",
1002] 982]
1003 983
1004[[package]] 984[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 4b35c96..a0872ce 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
1[package] 1[package]
2name = "dijo" 2name = "dijo"
3version = "0.1.3" 3version = "0.2.0"
4authors = ["Akshay <[email protected]>"] 4authors = ["Akshay <[email protected]>"]
5edition = "2018" 5edition = "2018"
6description = "Scriptable, curses-based, digital habit tracker" 6description = "Scriptable, curses-based, digital habit tracker"
@@ -23,7 +23,7 @@ notify = "4.0"
23[dependencies.cursive] 23[dependencies.cursive]
24version = "0.15" 24version = "0.15"
25default-features = false 25default-features = false
26features = ["crossterm-backend"] 26features = ["termion-backend", "crossterm-backend"]
27 27
28[dependencies.chrono] 28[dependencies.chrono]
29version = "0.4" 29version = "0.4"
diff --git a/readme.md b/readme.md
index c407fae..714234e 100644
--- a/readme.md
+++ b/readme.md
@@ -19,13 +19,16 @@ much like a certain text editor.
19 - **vim like command mode**: add with `:add`, delete with 19 - **vim like command mode**: add with `:add`, delete with
20 `:delete` and above all, quit with `:q`!. 20 `:delete` and above all, quit with `:q`!.
21 - **fully scriptable**: [configure `dijo` to 21 - **fully scriptable**: [configure `dijo` to
22 track your `git` commits](./Auto-Habits)! 22 track your `git` commits](https://github.com/NerdyPepper/dijo/wiki/Auto-Habits)!
23 23
24### Install 24### Install
25 25
26Get `dijo` by running the following at the nearest prompt: 26Get `dijo` by running the following at the nearest prompt:
27 27
28``` 28```shell
29# dijo requires rustc >= v1.42
30$ rustup update
31
29$ cargo install dijo 32$ cargo install dijo
30``` 33```
31 34
diff --git a/src/app/impl_self.rs b/src/app/impl_self.rs
index 744f906..a806dc5 100644
--- a/src/app/impl_self.rs
+++ b/src/app/impl_self.rs
@@ -37,6 +37,10 @@ impl App {
37 self.habits.push(h); 37 self.habits.push(h);
38 } 38 }
39 39
40 pub fn list_habits(&self) -> Vec<String> {
41 self.habits.iter().map(|x| x.name()).collect::<Vec<_>>()
42 }
43
40 pub fn delete_by_name(&mut self, name: &str) { 44 pub fn delete_by_name(&mut self, name: &str) {
41 let old_len = self.habits.len(); 45 let old_len = self.habits.len();
42 self.habits.retain(|h| h.name() != name); 46 self.habits.retain(|h| h.name() != name);
@@ -118,13 +122,13 @@ impl App {
118 } 122 }
119 123
120 pub fn status(&self) -> StatusLine { 124 pub fn status(&self) -> StatusLine {
121 let today = chrono::Local::now().naive_utc().date(); 125 let today = chrono::Local::now().naive_local().date();
122 let remaining = self.habits.iter().map(|h| h.remaining(today)).sum::<u32>(); 126 let remaining = self.habits.iter().map(|h| h.remaining(today)).sum::<u32>();
123 let total = self.habits.iter().map(|h| h.goal()).sum::<u32>(); 127 let total = self.habits.iter().map(|h| h.goal()).sum::<u32>();
124 let completed = total - remaining; 128 let completed = total - remaining;
125 129
126 let timestamp = if self.view_month_offset == 0 { 130 let timestamp = if self.view_month_offset == 0 {
127 format!("{}", Local::now().date().format("%d/%b/%y"),) 131 format!("{}", Local::now().naive_local().date().format("%d/%b/%y"),)
128 } else { 132 } else {
129 let months = self.view_month_offset; 133 let months = self.view_month_offset;
130 format!("{}", format!("{} months ago", months),) 134 format!("{}", format!("{} months ago", months),)
@@ -207,12 +211,18 @@ impl App {
207 .iter_mut() 211 .iter_mut()
208 .find(|x| x.name() == name && x.is_auto()); 212 .find(|x| x.name() == name && x.is_auto());
209 if let Some(h) = target_habit { 213 if let Some(h) = target_habit {
210 h.modify(Local::now().naive_utc().date(), event); 214 h.modify(Local::now().naive_local().date(), event);
211 } 215 }
212 }; 216 };
213 match result { 217 match result {
214 Ok(c) => match c { 218 Ok(c) => match c {
215 Command::Add(name, goal, auto) => { 219 Command::Add(name, goal, auto) => {
220 if let Some(_) = self.habits.iter().find(|x| x.name() == name) {
221 self.message.set_kind(MessageKind::Error);
222 self.message
223 .set_message(format!("Habit `{}` already exist", &name));
224 return;
225 }
216 let kind = if goal == Some(1) { "bit" } else { "count" }; 226 let kind = if goal == Some(1) { "bit" } else { "count" };
217 if kind == "count" { 227 if kind == "count" {
218 self.add_habit(Box::new(Count::new(name, goal.unwrap_or(0), auto))); 228 self.add_habit(Box::new(Count::new(name, goal.unwrap_or(0), auto)));
@@ -230,7 +240,30 @@ impl App {
230 Command::TrackDown(name) => { 240 Command::TrackDown(name) => {
231 _track(&name, TrackEvent::Decrement); 241 _track(&name, TrackEvent::Decrement);
232 } 242 }
233 Command::Quit => self.save_state(), 243 Command::Help(input) => {
244 if let Some(topic) = input.as_ref().map(String::as_ref) {
245 self.message.set_message(
246 match topic {
247 "a" | "add" => "add <habit-name> [goal] (alias: a)",
248 "aa" | "add-auto" => "add-auto <habit-name> [goal] (alias: aa)",
249 "d" | "delete" => "delete <habit-name> (alias: d)",
250 "mprev" | "month-prev" => "month-prev (alias: mprev)",
251 "mnext" | "month-next" => "month-next (alias: mnext)",
252 "tup" | "track-up" => "track-up <auto-habit-name> (alias: tup)",
253 "tdown" | "track-down" => "track-down <auto-habit-name> (alias: tdown)",
254 "q" | "quit" => "quit",
255 "h"|"?" | "help" => "help [<command>|commands|keys] (aliases: h, ?)",
256 "cmds" | "commands" => "add, add-auto, delete, month-{prev,next}, track-{up,down}, help, quit",
257 "keys" => "TODO", // TODO (view?)
258 _ => "unknown command or help topic.",
259 }
260 )
261 } else {
262 // TODO (view?)
263 self.message.set_message("help <command>|commands|keys")
264 }
265 }
266 Command::Quit | Command::Write => self.save_state(),
234 Command::MonthNext => self.sift_forward(), 267 Command::MonthNext => self.sift_forward(),
235 Command::MonthPrev => self.sift_backward(), 268 Command::MonthPrev => self.sift_backward(),
236 Command::Blank => {} 269 Command::Blank => {}
diff --git a/src/app/impl_view.rs b/src/app/impl_view.rs
index 892b00c..0dfd20b 100644
--- a/src/app/impl_view.rs
+++ b/src/app/impl_view.rs
@@ -102,25 +102,6 @@ impl View for App {
102 self.set_focus(Absolute::Down); 102 self.set_focus(Absolute::Down);
103 return EventResult::Consumed(None); 103 return EventResult::Consumed(None);
104 } 104 }
105 Event::Char('d') => {
106 if self.habits.is_empty() {
107 return EventResult::Consumed(None);
108 }
109 self.habits.remove(self.focus);
110 self.focus = self.focus.checked_sub(1).unwrap_or(0);
111 return EventResult::Consumed(None);
112 }
113 Event::Char('w') => {
114 // helper bind to test write to file
115 let j = serde_json::to_string_pretty(&self.habits).unwrap();
116 let mut file = File::create("foo.txt").unwrap();
117 file.write_all(j.as_bytes()).unwrap();
118 return EventResult::Consumed(None);
119 }
120 Event::Char('q') => {
121 self.save_state();
122 return EventResult::with_cb(|s| s.quit());
123 }
124 Event::Char('v') => { 105 Event::Char('v') => {
125 if self.habits.is_empty() { 106 if self.habits.is_empty() {
126 return EventResult::Consumed(None); 107 return EventResult::Consumed(None);
diff --git a/src/command.rs b/src/command.rs
index f856b00..38d48e9 100644
--- a/src/command.rs
+++ b/src/command.rs
@@ -1,21 +1,75 @@
1use std::fmt; 1use std::fmt;
2 2
3use cursive::event::{Event, EventResult, Key};
3use cursive::theme::{BaseColor, Color, ColorStyle}; 4use cursive::theme::{BaseColor, Color, ColorStyle};
4use cursive::view::Resizable; 5use cursive::view::Resizable;
5use cursive::views::{EditView, LinearLayout, TextView}; 6use cursive::views::{EditView, LinearLayout, OnEventView, TextView};
6use cursive::Cursive; 7use cursive::Cursive;
7 8
8use crate::{app::App, CONFIGURATION}; 9use crate::{app::App, CONFIGURATION};
9 10
11static COMMANDS: &'static [&'static str] = &[
12 "add",
13 "add-auto",
14 "delete",
15 "track-up",
16 "track-down",
17 "month-prev",
18 "month-next",
19 "quit",
20 "write",
21 "help",
22];
23
24fn get_command_completion(prefix: &str) -> Option<String> {
25 let first_match = COMMANDS.iter().filter(|&x| x.starts_with(prefix)).next();
26 return first_match.map(|&x| x.into());
27}
28
29fn get_habit_completion(prefix: &str, habit_names: &[String]) -> Option<String> {
30 let first_match = habit_names.iter().filter(|&x| x.starts_with(prefix)).next();
31 eprintln!("{:?}| {:?}", prefix, first_match);
32 return first_match.map(|x| x.into());
33}
34
10pub fn open_command_window(s: &mut Cursive) { 35pub fn open_command_window(s: &mut Cursive) {
11 let command_window = EditView::new() 36 let habit_list: Vec<String> = s
12 .filler(" ") 37 .call_on_name("Main", |view: &mut App| {
13 .on_submit(call_on_app) 38 return view.list_habits();
14 .style(ColorStyle::new( 39 })
15 Color::Dark(BaseColor::Black), 40 .unwrap();
16 Color::Dark(BaseColor::White), 41 let style = ColorStyle::new(Color::Dark(BaseColor::Black), Color::Dark(BaseColor::White));
17 )) 42 let command_window = OnEventView::new(
18 .fixed_width(CONFIGURATION.view_width * CONFIGURATION.grid_width); 43 EditView::new()
44 .filler(" ")
45 .on_submit(call_on_app)
46 .style(style),
47 )
48 .on_event_inner(
49 Event::Key(Key::Tab),
50 move |view: &mut EditView, _: &Event| {
51 let contents = view.get_content();
52 if !contents.contains(" ") {
53 let completion = get_command_completion(&*contents);
54 if let Some(c) = completion {
55 let cb = view.set_content(c);
56 return Some(EventResult::Consumed(Some(cb)));
57 };
58 return None;
59 } else {
60 let word = contents.split(' ').last().unwrap();
61 let completion = get_habit_completion(word, &habit_list);
62 eprintln!("{:?} | {:?}", completion, contents);
63 if let Some(c) = completion {
64 let cb =
65 view.set_content(format!("{}", contents) + c.strip_prefix(word).unwrap());
66 return Some(EventResult::Consumed(Some(cb)));
67 };
68 return None;
69 }
70 },
71 )
72 .fixed_width(CONFIGURATION.view_width * CONFIGURATION.grid_width);
19 s.call_on_name("Frame", |view: &mut LinearLayout| { 73 s.call_on_name("Frame", |view: &mut LinearLayout| {
20 let mut commandline = LinearLayout::horizontal() 74 let mut commandline = LinearLayout::horizontal()
21 .child(TextView::new(":")) 75 .child(TextView::new(":"))
@@ -59,15 +113,17 @@ pub enum Command {
59 Delete(String), 113 Delete(String),
60 TrackUp(String), 114 TrackUp(String),
61 TrackDown(String), 115 TrackDown(String),
116 Help(Option<String>),
117 Write,
62 Quit, 118 Quit,
63 Blank, 119 Blank,
64} 120}
65 121
66#[derive(Debug)] 122#[derive(Debug)]
67pub enum CommandLineError { 123pub enum CommandLineError {
68 InvalidCommand(String), 124 InvalidCommand(String), // command name
69 InvalidArg(u32), // position 125 InvalidArg(u32), // position
70 NotEnoughArgs(String, u32), 126 NotEnoughArgs(String, u32), // command name, required no. of args
71} 127}
72 128
73impl std::error::Error for CommandLineError {} 129impl std::error::Error for CommandLineError {}
@@ -134,9 +190,16 @@ impl Command {
134 } 190 }
135 return Ok(Command::TrackDown(args[0].to_string())); 191 return Ok(Command::TrackDown(args[0].to_string()));
136 } 192 }
193 "h" | "?" | "help" => {
194 if args.is_empty() {
195 return Ok(Command::Help(None));
196 }
197 return Ok(Command::Help(Some(args[0].to_string())));
198 }
137 "mprev" | "month-prev" => return Ok(Command::MonthPrev), 199 "mprev" | "month-prev" => return Ok(Command::MonthPrev),
138 "mnext" | "month-next" => return Ok(Command::MonthNext), 200 "mnext" | "month-next" => return Ok(Command::MonthNext),
139 "q" | "quit" => return Ok(Command::Quit), 201 "q" | "quit" => return Ok(Command::Quit),
202 "w" | "write" => return Ok(Command::Write),
140 "" => return Ok(Command::Blank), 203 "" => return Ok(Command::Blank),
141 s => return Err(CommandLineError::InvalidCommand(s.into())), 204 s => return Err(CommandLineError::InvalidCommand(s.into())),
142 } 205 }
diff --git a/src/habit/bit.rs b/src/habit/bit.rs
index 3386182..8fa14c2 100644
--- a/src/habit/bit.rs
+++ b/src/habit/bit.rs
@@ -100,11 +100,22 @@ impl Habit for Bit {
100 fn goal(&self) -> u32 { 100 fn goal(&self) -> u32 {
101 return 1; 101 return 1;
102 } 102 }
103 fn modify(&mut self, date: NaiveDate, _: TrackEvent) { 103 fn modify(&mut self, date: NaiveDate, event: TrackEvent) {
104 if let Some(val) = self.stats.get_mut(&date) { 104 if let Some(val) = self.stats.get_mut(&date) {
105 *val = (val.0 ^ true).into(); 105 match event {
106 TrackEvent::Increment => *val = (val.0 ^ true).into(),
107 TrackEvent::Decrement => {
108 if val.0 {
109 *val = false.into();
110 } else {
111 self.stats.remove(&date);
112 }
113 }
114 }
106 } else { 115 } else {
107 self.insert_entry(date, CustomBool(true)); 116 if event == TrackEvent::Increment {
117 self.insert_entry(date, CustomBool(true));
118 }
108 } 119 }
109 } 120 }
110 fn set_view_month_offset(&mut self, offset: u32) { 121 fn set_view_month_offset(&mut self, offset: u32) {
diff --git a/src/habit/count.rs b/src/habit/count.rs
index 1bdf920..d351758 100644
--- a/src/habit/count.rs
+++ b/src/habit/count.rs
@@ -84,12 +84,15 @@ impl Habit for Count {
84 if *val > 0 { 84 if *val > 0 {
85 *val -= 1 85 *val -= 1
86 } else { 86 } else {
87 *val = 0 87 self.stats.remove(&date);
88 }; 88 };
89 } 89 }
90 } 90 }
91 } else { 91 } else {
92 self.insert_entry(date, 1); 92 match event {
93 TrackEvent::Increment => self.insert_entry(date, 1),
94 _ => {}
95 };
93 } 96 }
94 } 97 }
95 fn set_view_month_offset(&mut self, offset: u32) { 98 fn set_view_month_offset(&mut self, offset: u32) {
diff --git a/src/habit/prelude.rs b/src/habit/prelude.rs
index 19b00a7..8335d6c 100644
--- a/src/habit/prelude.rs
+++ b/src/habit/prelude.rs
@@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize};
2use std::default; 2use std::default;
3use std::fmt; 3use std::fmt;
4 4
5#[derive(Debug, PartialEq)]
5pub enum TrackEvent { 6pub enum TrackEvent {
6 Increment, 7 Increment,
7 Decrement, 8 Decrement,
diff --git a/src/main.rs b/src/main.rs
index fb16aaa..dec3156 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -12,7 +12,13 @@ use crate::command::{open_command_window, Command};
12use crate::utils::{load_configuration_file, AppConfig}; 12use crate::utils::{load_configuration_file, AppConfig};
13 13
14use clap::{App as ClapApp, Arg}; 14use clap::{App as ClapApp, Arg};
15
16#[cfg(target_os = "linux")]
17use cursive::termion;
18
19#[cfg(target_os = "windows")]
15use cursive::crossterm; 20use cursive::crossterm;
21
16use cursive::views::{LinearLayout, NamedView}; 22use cursive::views::{LinearLayout, NamedView};
17use lazy_static::lazy_static; 23use lazy_static::lazy_static;
18 24
@@ -33,6 +39,14 @@ fn main() {
33 .value_name("CMD") 39 .value_name("CMD")
34 .help("run a dijo command"), 40 .help("run a dijo command"),
35 ) 41 )
42 .arg(
43 Arg::with_name("list")
44 .short("l")
45 .long("list")
46 .takes_value(false)
47 .help("list dijo habits")
48 .conflicts_with("command"),
49 )
36 .get_matches(); 50 .get_matches();
37 if let Some(c) = matches.value_of("command") { 51 if let Some(c) = matches.value_of("command") {
38 let command = Command::from_string(c); 52 let command = Command::from_string(c);
@@ -49,8 +63,17 @@ fn main() {
49 "Commands other than `track-up` and `track-down` are currently not supported!" 63 "Commands other than `track-up` and `track-down` are currently not supported!"
50 ), 64 ),
51 } 65 }
66 } else if matches.is_present("list") {
67 for h in App::load_state().list_habits() {
68 println!("{}", h);
69 }
52 } else { 70 } else {
71 #[cfg(target_os = "windows")]
53 let mut s = crossterm().unwrap(); 72 let mut s = crossterm().unwrap();
73
74 #[cfg(target_os = "linux")]
75 let mut s = termion().unwrap();
76
54 let app = App::load_state(); 77 let app = App::load_state();
55 let layout = NamedView::new( 78 let layout = NamedView::new(
56 "Frame", 79 "Frame",
diff --git a/src/views.rs b/src/views.rs
index 24c8a4d..da077ac 100644
--- a/src/views.rs
+++ b/src/views.rs
@@ -43,7 +43,7 @@ where
43 let strikethrough = Style::from(Effect::Strikethrough); 43 let strikethrough = Style::from(Effect::Strikethrough);
44 44
45 let goal_status = 45 let goal_status =
46 self.view_month_offset() == 0 && self.reached_goal(Local::now().naive_utc().date()); 46 self.view_month_offset() == 0 && self.reached_goal(Local::now().naive_local().date());
47 47
48 printer.with_style( 48 printer.with_style(
49 Style::merge(&[ 49 Style::merge(&[
@@ -77,12 +77,20 @@ where
77 .collect::<Vec<_>>(); 77 .collect::<Vec<_>>();
78 for (week, line_nr) in days.chunks(7).zip(2..) { 78 for (week, line_nr) in days.chunks(7).zip(2..) {
79 let weekly_goal = self.goal() * week.len() as u32; 79 let weekly_goal = self.goal() * week.len() as u32;
80 let is_this_week = week.contains(&Local::now().naive_utc().date()); 80 let is_this_week = week.contains(&Local::now().naive_local().date());
81 let remaining = week.iter().map(|&i| self.remaining(i)).sum::<u32>(); 81 let remaining = week.iter().map(|&i| self.remaining(i)).sum::<u32>();
82 let completions = weekly_goal - remaining; 82 let completions = weekly_goal - remaining;
83 let full = CONFIGURATION.view_width - 8; 83 let full = CONFIGURATION.view_width - 8;
84 let bars_to_fill = if weekly_goal > 0 {(completions * full as u32) / weekly_goal} else {0}; 84 let bars_to_fill = if weekly_goal > 0 {
85 let percentage = if weekly_goal > 0 {(completions as f64 * 100.) / weekly_goal as f64} else {0.0}; 85 (completions * full as u32) / weekly_goal
86 } else {
87 0
88 };
89 let percentage = if weekly_goal > 0 {
90 (completions as f64 * 100.) / weekly_goal as f64
91 } else {
92 0.0
93 };
86 printer.with_style(future_style, |p| { 94 printer.with_style(future_style, |p| {
87 p.print((4, line_nr), &"─".repeat(full)); 95 p.print((4, line_nr), &"─".repeat(full));
88 }); 96 });
@@ -141,7 +149,7 @@ where
141 } 149 }
142 150
143 fn on_event(&mut self, e: Event) -> EventResult { 151 fn on_event(&mut self, e: Event) -> EventResult {
144 let now = Local::now().naive_utc().date(); 152 let now = Local::now().naive_local().date();
145 if self.is_auto() { 153 if self.is_auto() {
146 return EventResult::Ignored; 154 return EventResult::Ignored;
147 } 155 }