diff options
-rw-r--r-- | .github/workflows/main.yml | 119 | ||||
-rw-r--r-- | Cargo.lock | 80 | ||||
-rw-r--r-- | Cargo.toml | 4 | ||||
-rw-r--r-- | readme.md | 7 | ||||
-rw-r--r-- | src/app/impl_self.rs | 41 | ||||
-rw-r--r-- | src/app/impl_view.rs | 19 | ||||
-rw-r--r-- | src/command.rs | 87 | ||||
-rw-r--r-- | src/habit/bit.rs | 17 | ||||
-rw-r--r-- | src/habit/count.rs | 7 | ||||
-rw-r--r-- | src/habit/prelude.rs | 1 | ||||
-rw-r--r-- | src/main.rs | 23 | ||||
-rw-r--r-- | src/views.rs | 18 |
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 @@ | |||
1 | name: Rust | ||
2 | |||
3 | on: | ||
4 | push: | ||
5 | tags: | ||
6 | - '*' | ||
7 | |||
8 | jobs: | ||
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 | ||
@@ -92,12 +92,6 @@ dependencies = [ | |||
92 | ] | 92 | ] |
93 | 93 | ||
94 | [[package]] | 94 | [[package]] |
95 | name = "cc" | ||
96 | version = "1.0.58" | ||
97 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
98 | checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518" | ||
99 | |||
100 | [[package]] | ||
101 | name = "cfg-if" | 95 | name = "cfg-if" |
102 | version = "0.1.10" | 96 | version = "0.1.10" |
103 | source = "registry+https://github.com/rust-lang/crates.io-index" | 97 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -167,12 +161,12 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" | |||
167 | 161 | ||
168 | [[package]] | 162 | [[package]] |
169 | name = "crossbeam-channel" | 163 | name = "crossbeam-channel" |
170 | version = "0.4.2" | 164 | version = "0.4.3" |
171 | source = "registry+https://github.com/rust-lang/crates.io-index" | 165 | source = "registry+https://github.com/rust-lang/crates.io-index" |
172 | checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" | 166 | checksum = "09ee0cc8804d5393478d743b035099520087a5186f3b93fa58cec08fa62407b6" |
173 | dependencies = [ | 167 | dependencies = [ |
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]] |
305 | name = "dijo" | 297 | name = "dijo" |
306 | version = "0.1.3" | 298 | version = "0.2.0" |
307 | dependencies = [ | 299 | dependencies = [ |
308 | "chrono", | 300 | "chrono", |
309 | "clap", | 301 | "clap", |
@@ -583,18 +575,6 @@ dependencies = [ | |||
583 | ] | 575 | ] |
584 | 576 | ||
585 | [[package]] | 577 | [[package]] |
586 | name = "maplit" | ||
587 | version = "1.0.2" | ||
588 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
589 | checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" | ||
590 | |||
591 | [[package]] | ||
592 | name = "maybe-uninit" | ||
593 | version = "2.0.0" | ||
594 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
595 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" | ||
596 | |||
597 | [[package]] | ||
598 | name = "mio" | 578 | name = "mio" |
599 | version = "0.6.22" | 579 | version = "0.6.22" |
600 | source = "registry+https://github.com/rust-lang/crates.io-index" | 580 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -662,17 +642,6 @@ dependencies = [ | |||
662 | ] | 642 | ] |
663 | 643 | ||
664 | [[package]] | 644 | [[package]] |
665 | name = "ncurses" | ||
666 | version = "5.99.0" | ||
667 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
668 | checksum = "15699bee2f37e9f8828c7b35b2bc70d13846db453f2d507713b758fabe536b82" | ||
669 | dependencies = [ | ||
670 | "cc", | ||
671 | "libc", | ||
672 | "pkg-config", | ||
673 | ] | ||
674 | |||
675 | [[package]] | ||
676 | name = "net2" | 645 | name = "net2" |
677 | version = "0.2.34" | 646 | version = "0.2.34" |
678 | source = "registry+https://github.com/rust-lang/crates.io-index" | 647 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -774,6 +743,12 @@ dependencies = [ | |||
774 | ] | 743 | ] |
775 | 744 | ||
776 | [[package]] | 745 | [[package]] |
746 | name = "numtoa" | ||
747 | version = "0.1.0" | ||
748 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
749 | checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" | ||
750 | |||
751 | [[package]] | ||
777 | name = "owning_ref" | 752 | name = "owning_ref" |
778 | version = "0.4.1" | 753 | version = "0.4.1" |
779 | source = "registry+https://github.com/rust-lang/crates.io-index" | 754 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -807,12 +782,6 @@ dependencies = [ | |||
807 | ] | 782 | ] |
808 | 783 | ||
809 | [[package]] | 784 | [[package]] |
810 | name = "pkg-config" | ||
811 | version = "0.3.18" | ||
812 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
813 | checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" | ||
814 | |||
815 | [[package]] | ||
816 | name = "proc-macro-hack" | 785 | name = "proc-macro-hack" |
817 | version = "0.5.16" | 786 | version = "0.5.16" |
818 | source = "registry+https://github.com/rust-lang/crates.io-index" | 787 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -820,9 +789,9 @@ checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" | |||
820 | 789 | ||
821 | [[package]] | 790 | [[package]] |
822 | name = "proc-macro2" | 791 | name = "proc-macro2" |
823 | version = "1.0.18" | 792 | version = "1.0.19" |
824 | source = "registry+https://github.com/rust-lang/crates.io-index" | 793 | source = "registry+https://github.com/rust-lang/crates.io-index" |
825 | checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" | 794 | checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" |
826 | dependencies = [ | 795 | dependencies = [ |
827 | "unicode-xid", | 796 | "unicode-xid", |
828 | ] | 797 | ] |
@@ -843,6 +812,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
843 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" | 812 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" |
844 | 813 | ||
845 | [[package]] | 814 | [[package]] |
815 | name = "redox_termios" | ||
816 | version = "0.1.1" | ||
817 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
818 | checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" | ||
819 | dependencies = [ | ||
820 | "redox_syscall", | ||
821 | ] | ||
822 | |||
823 | [[package]] | ||
846 | name = "redox_users" | 824 | name = "redox_users" |
847 | version = "0.3.4" | 825 | version = "0.3.4" |
848 | source = "registry+https://github.com/rust-lang/crates.io-index" | 826 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -982,9 +960,9 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" | |||
982 | 960 | ||
983 | [[package]] | 961 | [[package]] |
984 | name = "syn" | 962 | name = "syn" |
985 | version = "1.0.34" | 963 | version = "1.0.35" |
986 | source = "registry+https://github.com/rust-lang/crates.io-index" | 964 | source = "registry+https://github.com/rust-lang/crates.io-index" |
987 | checksum = "936cae2873c940d92e697597c5eee105fb570cd5689c695806f672883653349b" | 965 | checksum = "fb7f4c519df8c117855e19dd8cc851e89eb746fe7a73f0157e0d95fdec5369b0" |
988 | dependencies = [ | 966 | dependencies = [ |
989 | "proc-macro2", | 967 | "proc-macro2", |
990 | "quote", | 968 | "quote", |
@@ -992,13 +970,15 @@ dependencies = [ | |||
992 | ] | 970 | ] |
993 | 971 | ||
994 | [[package]] | 972 | [[package]] |
995 | name = "term_size" | 973 | name = "termion" |
996 | version = "0.3.2" | 974 | version = "1.5.5" |
997 | source = "registry+https://github.com/rust-lang/crates.io-index" | 975 | source = "registry+https://github.com/rust-lang/crates.io-index" |
998 | checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" | 976 | checksum = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905" |
999 | dependencies = [ | 977 | dependencies = [ |
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]] |
@@ -1,6 +1,6 @@ | |||
1 | [package] | 1 | [package] |
2 | name = "dijo" | 2 | name = "dijo" |
3 | version = "0.1.3" | 3 | version = "0.2.0" |
4 | authors = ["Akshay <[email protected]>"] | 4 | authors = ["Akshay <[email protected]>"] |
5 | edition = "2018" | 5 | edition = "2018" |
6 | description = "Scriptable, curses-based, digital habit tracker" | 6 | description = "Scriptable, curses-based, digital habit tracker" |
@@ -23,7 +23,7 @@ notify = "4.0" | |||
23 | [dependencies.cursive] | 23 | [dependencies.cursive] |
24 | version = "0.15" | 24 | version = "0.15" |
25 | default-features = false | 25 | default-features = false |
26 | features = ["crossterm-backend"] | 26 | features = ["termion-backend", "crossterm-backend"] |
27 | 27 | ||
28 | [dependencies.chrono] | 28 | [dependencies.chrono] |
29 | version = "0.4" | 29 | version = "0.4" |
@@ -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 | ||
26 | Get `dijo` by running the following at the nearest prompt: | 26 | Get `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 @@ | |||
1 | use std::fmt; | 1 | use std::fmt; |
2 | 2 | ||
3 | use cursive::event::{Event, EventResult, Key}; | ||
3 | use cursive::theme::{BaseColor, Color, ColorStyle}; | 4 | use cursive::theme::{BaseColor, Color, ColorStyle}; |
4 | use cursive::view::Resizable; | 5 | use cursive::view::Resizable; |
5 | use cursive::views::{EditView, LinearLayout, TextView}; | 6 | use cursive::views::{EditView, LinearLayout, OnEventView, TextView}; |
6 | use cursive::Cursive; | 7 | use cursive::Cursive; |
7 | 8 | ||
8 | use crate::{app::App, CONFIGURATION}; | 9 | use crate::{app::App, CONFIGURATION}; |
9 | 10 | ||
11 | static 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 | |||
24 | fn 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 | |||
29 | fn 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 | |||
10 | pub fn open_command_window(s: &mut Cursive) { | 35 | pub 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)] |
67 | pub enum CommandLineError { | 123 | pub 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 | ||
73 | impl std::error::Error for CommandLineError {} | 129 | impl 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}; | |||
2 | use std::default; | 2 | use std::default; |
3 | use std::fmt; | 3 | use std::fmt; |
4 | 4 | ||
5 | #[derive(Debug, PartialEq)] | ||
5 | pub enum TrackEvent { | 6 | pub 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}; | |||
12 | use crate::utils::{load_configuration_file, AppConfig}; | 12 | use crate::utils::{load_configuration_file, AppConfig}; |
13 | 13 | ||
14 | use clap::{App as ClapApp, Arg}; | 14 | use clap::{App as ClapApp, Arg}; |
15 | |||
16 | #[cfg(target_os = "linux")] | ||
17 | use cursive::termion; | ||
18 | |||
19 | #[cfg(target_os = "windows")] | ||
15 | use cursive::crossterm; | 20 | use cursive::crossterm; |
21 | |||
16 | use cursive::views::{LinearLayout, NamedView}; | 22 | use cursive::views::{LinearLayout, NamedView}; |
17 | use lazy_static::lazy_static; | 23 | use 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 | } |