minder 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +7 -4
  3. data/README.md +1 -1
  4. data/bin/minder +2 -2
  5. data/db/migrate/003_create_periods.rb +16 -0
  6. data/lib/minder/application.rb +24 -24
  7. data/lib/minder/{filter_frame.rb → cli/filter_frame.rb} +2 -2
  8. data/lib/minder/{frame.rb → cli/frame.rb} +2 -0
  9. data/lib/minder/{help_frame.rb → cli/help_frame.rb} +3 -2
  10. data/lib/minder/{message_frame.rb → cli/message_frame.rb} +67 -30
  11. data/lib/minder/{pomodoro_frame.rb → cli/pomodoro_frame.rb} +9 -3
  12. data/lib/minder/{quick_add_frame.rb → cli/quick_add_frame.rb} +2 -2
  13. data/lib/minder/{scene.rb → cli/scene.rb} +7 -6
  14. data/lib/minder/{search_frame.rb → cli/search_frame.rb} +2 -2
  15. data/lib/minder/cli/task_editor.rb +68 -0
  16. data/lib/minder/database/database.rb +98 -0
  17. data/lib/minder/{database_migrator.rb → database/database_migrator.rb} +1 -1
  18. data/lib/minder/database/period_mapper.rb +12 -0
  19. data/lib/minder/database/periods.rb +18 -0
  20. data/lib/minder/{tasks.rb → database/tasks.rb} +4 -0
  21. data/lib/minder/pomodoro/break_period.rb +15 -0
  22. data/lib/minder/{idle_period.rb → pomodoro/idle_period.rb} +10 -2
  23. data/lib/minder/pomodoro/period.rb +45 -0
  24. data/lib/minder/pomodoro/pomodoro_runner.rb +83 -0
  25. data/lib/minder/pomodoro/work_period.rb +15 -0
  26. data/lib/minder/{task_recorder.rb → tasks/task_manager.rb} +10 -4
  27. data/lib/minder/version.rb +1 -1
  28. data/lib/minder.rb +8 -0
  29. data/spec/minder/application_spec.rb +1 -1
  30. data/spec/minder/{pomodoro_break_spec.rb → break_period_spec.rb} +3 -3
  31. data/spec/minder/{pomodoro_spec.rb → pomodoro_period_spec.rb} +3 -3
  32. metadata +30 -27
  33. data/lib/minder/break_period.rb +0 -13
  34. data/lib/minder/database.rb +0 -47
  35. data/lib/minder/period.rb +0 -35
  36. data/lib/minder/pomodoro_period.rb +0 -13
  37. data/lib/minder/pomodoro_runner.rb +0 -69
  38. data/lib/minder/timer.rb +0 -32
  39. /data/lib/minder/{commands → database/commands}/delete_task.rb +0 -0
  40. /data/lib/minder/{task_mapper.rb → database/task_mapper.rb} +0 -0
  41. /data/lib/minder/{task.rb → tasks/task.rb} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8fc827a792b9ba35db1c5574bf3531ff5a1c97a6
4
- data.tar.gz: 30cf93f9ef8cf0fd8bc60009cd564e1dbd58980a
3
+ metadata.gz: a8777b449e7047ba218141d8e227aee5e6bb2311
4
+ data.tar.gz: a019f497edd579dcfc62c26da1ac6d53bd49612b
5
5
  SHA512:
6
- metadata.gz: 20765de38667425c89ac7bfd72f06b2ea491861dd59dcb28a984ebdb365f9160a689cc04f34fa1f2316e4259b8fba3ce30edc0e0c6d94fa7d78cb810857ca3c1
7
- data.tar.gz: cd9ff1818ea9fa56a48f44601337d21fd76fb277d01b512b2597ee206de108b37d266584e2affa53a722db023c64a90f8ee7dc45bcd699ec2885c365f48de048
6
+ metadata.gz: 106ec92ccd594c3f53d9bc631df7ddb0d3dfcfa4d8dd34bfce525d28d15c9569bb29eb9fb867f23c1c2648d0dac7c1bad76d28d58bc1b18bec9e332214020a8f
7
+ data.tar.gz: 0144effc35f9fa967662fed2a9a86a9e5125cbf43a78aa45a01ec7f784a91fbf260c81d6a2caf5630dbd872aa94546af31b413168569106b863f0b1a82c2834f
data/Gemfile.lock CHANGED
@@ -1,9 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- minder (0.2.2)
4
+ minder (0.3.0)
5
5
  activesupport (~> 4.2, >= 4.2.1)
6
6
  curses (~> 1.0, >= 1.0.1)
7
+ emoji
7
8
  rom (~> 0.7, >= 0.7)
8
9
  rom-sql (~> 0.5, >= 0.5)
9
10
  sqlite3 (~> 1.3, >= 1.3.10)
@@ -35,10 +36,12 @@ GEM
35
36
  descendants_tracker (0.0.4)
36
37
  thread_safe (~> 0.3, >= 0.3.1)
37
38
  diff-lcs (1.2.5)
39
+ emoji (1.0.4)
40
+ json
38
41
  equalizer (0.0.11)
39
42
  i18n (0.7.0)
40
43
  ice_nine (0.11.1)
41
- json (1.8.2)
44
+ json (1.8.3)
42
45
  method_source (0.8.2)
43
46
  minitest (5.7.0)
44
47
  pry (0.10.1)
@@ -71,12 +74,12 @@ GEM
71
74
  diff-lcs (>= 1.2.0, < 2.0)
72
75
  rspec-support (~> 3.2.0)
73
76
  rspec-support (3.2.2)
74
- sequel (4.22.0)
77
+ sequel (4.23.0)
75
78
  slop (3.6.0)
76
79
  sqlite3 (1.3.10)
77
80
  thread_safe (0.3.5)
78
81
  timecop (0.7.3)
79
- transproc (0.2.2)
82
+ transproc (0.2.3)
80
83
  tzinfo (1.2.2)
81
84
  thread_safe (~> 0.1)
82
85
  virtus (1.0.5)
data/README.md CHANGED
@@ -61,7 +61,7 @@ pressing Tab. The commands for each frame only work when the frame is focused.
61
61
  Vim keystrokes:
62
62
  - `/` to open a dialog to search within the list of tasks.
63
63
  - `d` to mark a task as done.
64
- - `e` to edit the whole tasks list in your `$EDITOR`.
64
+ - ~~`e` to edit the whole tasks list in your `$EDITOR`~~ (this is broken atm).
65
65
  - `f` to open a dialog to filter the list of tasks.
66
66
  - `gg` to go to top of task list and `G` to go to bottom of list.
67
67
  - `j` to do down the list and `k` to go up.
data/bin/minder CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'minder'
4
- require 'minder/database'
4
+ require 'minder/database/database'
5
5
  database = Database.new
6
6
  DB = database.rom.repositories[:default]
7
7
 
8
8
  require 'minder/application'
9
- require 'minder/database_migrator'
9
+ require 'minder/database/database_migrator'
10
10
 
11
11
  migrator = Minder::DatabaseMigrator.new(database: database)
12
12
  migrator.run
@@ -0,0 +1,16 @@
1
+ Sequel.migration do
2
+ up do
3
+ create_table(:periods) do
4
+ primary_key :id
5
+ String :type, null: false
6
+ Datetime :started_at
7
+ Datetime :ended_at
8
+ Datetime :created_at
9
+ Datetime :updated_at
10
+ end
11
+ end
12
+
13
+ down do
14
+ drop_table(:periods)
15
+ end
16
+ end
@@ -1,10 +1,9 @@
1
1
  require 'minder/config'
2
- require 'minder/pomodoro_runner'
3
- require 'minder/task_recorder'
4
- require 'minder/scene'
5
- require 'active_support'
2
+ require 'minder/pomodoro/pomodoro_runner'
3
+ require 'minder/tasks/task_manager'
4
+ require 'minder/cli/scene'
5
+
6
6
  require 'curses'
7
- require 'fileutils'
8
7
 
9
8
  module Minder
10
9
  class Application
@@ -39,7 +38,7 @@ module Minder
39
38
  self.scene = Scene.new
40
39
  scene.setup
41
40
 
42
- options = { pomodoro_runner: pomodoro_runner, task_manager: task_recorder }
41
+ options = { pomodoro_runner: pomodoro_runner, task_manager: task_manager }
43
42
 
44
43
  self.pomodoro_frame = PomodoroFrame.new(options)
45
44
  self.help_frame = HelpFrame.new(options)
@@ -95,11 +94,12 @@ module Minder
95
94
  @runner ||= PomodoroRunner.new(
96
95
  work_duration: config.work_duration,
97
96
  short_break_duration: config.short_break_duration,
98
- long_break_duration: config.long_break_duration)
97
+ long_break_duration: config.long_break_duration,
98
+ database: database)
99
99
  end
100
100
 
101
- def task_recorder
102
- @task_recorder ||= TaskRecorder.new(database: database)
101
+ def task_manager
102
+ @task_manager ||= TaskManager.new(database: database)
103
103
  end
104
104
 
105
105
  def handle_event(event, data = {})
@@ -114,27 +114,27 @@ module Minder
114
114
  pomodoro_runner.continue
115
115
  when :editor
116
116
  `$EDITOR ~/.minder/doing.txt`
117
- task_recorder.reload
117
+ task_manager.reload
118
118
  when :add_task
119
- task_recorder.add_task(data[:task])
119
+ task_manager.add_task(data[:task])
120
120
  when :switch_focus
121
121
  scene.switch_focus
122
122
  when :select_next_task
123
- task_recorder.select_next_task
123
+ task_manager.select_next_task
124
124
  when :select_previous_task
125
- task_recorder.select_previous_task
125
+ task_manager.select_previous_task
126
126
  when :delete_task
127
- task_recorder.delete_task
127
+ task_manager.delete_task
128
128
  when :complete_task
129
- task_recorder.complete_task
129
+ task_manager.complete_task
130
130
  when :start_task
131
- task_recorder.start_task
131
+ task_manager.start_task
132
132
  when :unstart_task
133
- task_recorder.unstart_task
133
+ task_manager.unstart_task
134
134
  when :select_last_task
135
- task_recorder.select_last_task
135
+ task_manager.select_last_task
136
136
  when :select_first_task
137
- task_recorder.select_first_task
137
+ task_manager.select_first_task
138
138
  when :help
139
139
  message_frame.hide
140
140
  help_frame.unhide
@@ -150,7 +150,7 @@ module Minder
150
150
  filter_frame.hide if data[:text] == ''
151
151
  scene.focus_frame(message_frame)
152
152
  when :update_filter
153
- task_recorder.filter(data[:text])
153
+ task_manager.filter(data[:text])
154
154
  when :search
155
155
  search_frame.unhide
156
156
  search_frame.begin_search
@@ -158,12 +158,12 @@ module Minder
158
158
  when :submit_search
159
159
  search_frame.hide
160
160
  scene.focus_frame(message_frame)
161
- task_recorder.search(data[:text])
162
- task_recorder.select_search_result
161
+ task_manager.search(data[:text])
162
+ task_manager.select_search_result
163
163
  when :next_search
164
- task_recorder.next_search
164
+ task_manager.next_search
165
165
  when :previous_search
166
- task_recorder.previous_search
166
+ task_manager.previous_search
167
167
  when :escape_search
168
168
  search_frame.hide
169
169
  scene.focus_frame(message_frame)
@@ -1,4 +1,4 @@
1
- require 'minder/frame'
1
+ require 'minder/cli/frame'
2
2
 
3
3
  module Minder
4
4
  class FilterFrame < Frame
@@ -38,7 +38,7 @@ TEXT
38
38
  notify_observers(:submit_filter, { text: filter_string })
39
39
  end
40
40
 
41
- @filter_string.chop! if key == 127
41
+ @filter_string.chop! if key == Curses::Key::BACKSPACE
42
42
 
43
43
  refresh
44
44
  changed
@@ -1,5 +1,6 @@
1
1
  require 'observer'
2
2
  require 'erb'
3
+
3
4
  module Minder
4
5
  class Frame
5
6
  include Observable
@@ -35,6 +36,7 @@ module Minder
35
36
  self.left = left
36
37
 
37
38
  self.window = build_window
39
+ window.keypad(true)
38
40
  self.lines = []
39
41
  end
40
42
 
@@ -1,4 +1,4 @@
1
- require 'minder/frame'
1
+ require 'minder/cli/frame'
2
2
 
3
3
  module Minder
4
4
  class HelpFrame < Frame
@@ -10,10 +10,11 @@ Commands: any key to dismiss
10
10
  (u) un-start task
11
11
  (d) mark task as done
12
12
  (x) delete task
13
- (e) open tasks in text editor
13
+ (e) edit task
14
14
  (G) go to bottom of list
15
15
  (gg) go to top of list
16
16
  (/) Search among tasks
17
+ (m) minimize messages frame
17
18
  (n) Next search result
18
19
  (N) Previous search result
19
20
  (?) to view this text
@@ -1,13 +1,16 @@
1
- require 'minder/frame'
1
+ require 'minder/cli/frame'
2
+ require 'minder/cli/task_editor'
2
3
 
3
4
  module Minder
4
5
  class MessageFrame < Frame
5
- attr_reader :current_line
6
+ attr_reader :current_line,
7
+ :task_editor
6
8
 
7
9
  def initialize(*)
8
10
  super
9
11
  self.height = desired_height
10
12
  @minimized = false
13
+ @editing = false
11
14
  end
12
15
 
13
16
  def minimize
@@ -23,6 +26,10 @@ module Minder
23
26
  @minimized
24
27
  end
25
28
 
29
+ def editing?
30
+ @editing
31
+ end
32
+
26
33
  def template
27
34
  if minimized?
28
35
  minimized_message
@@ -101,6 +108,9 @@ TEXT
101
108
  def set_cursor_position
102
109
  if minimized?
103
110
  window.setpos(1, 20)
111
+ elsif editing?
112
+ window.setpos(3 + task_manager.selected_task_index - scroll_offset,
113
+ task_editor.cursor_position + 6)
104
114
  else
105
115
  window.setpos(3 + task_manager.selected_task_index - scroll_offset, 3)
106
116
  end
@@ -119,37 +129,64 @@ TEXT
119
129
  end
120
130
  end
121
131
 
132
+ def handle_keypress(key)
133
+ if editing?
134
+ task_editor.handle_keypress(key)
135
+ else
136
+ super
137
+ end
138
+ end
139
+
140
+ def handle_task_editor_event(event, data = {})
141
+ if event == :stop_editing
142
+ @editing = false
143
+ @task_editor = nil
144
+ elsif event == :update_task
145
+ task_manager.update_task(task_manager.selected_task, data)
146
+ @editing = false
147
+ @task_editor = nil
148
+ end
149
+
150
+ changed
151
+ notify_observers(event)
152
+ end
153
+
122
154
  def handle_char_keypress(key)
123
- event = case key
124
- when 'j' then :select_next_task
125
- when 'k' then :select_previous_task
126
- when 'd' then :complete_task
127
- when 'x' then :delete_task
128
- when 's' then :start_task
129
- when 'u' then :unstart_task
130
- when 'G' then :select_last_task
131
- when 'e' then :editor
132
- when '?' then :help
133
- when '/' then :search
134
- when 'm'
135
- minimize
136
- :redraw
137
- when 'n' then :next_search
138
- when 'N' then :previous_search
139
- when 'f' then :open_filter
140
- when 'g'
141
- @keypress_memory ||= []
142
- @keypress_memory << 'g'
143
- if @keypress_memory == ['g', 'g']
144
- @keypress_memory = []
145
- :select_first_task
146
- end
147
- when ' '
148
- if minimized?
149
- unminimize
155
+ event =
156
+ case key
157
+ when 'j' then :select_next_task
158
+ when 'k' then :select_previous_task
159
+ when 'd' then :complete_task
160
+ when 'x' then :delete_task
161
+ when 's' then :start_task
162
+ when 'u' then :unstart_task
163
+ when 'G' then :select_last_task
164
+ when 'e'
165
+ @editing = true
166
+ @task_editor = TaskEditor.new(task_manager.selected_task, self)
167
+ @task_editor.add_observer(self, :handle_task_editor_event)
168
+ :edit_task
169
+ when '?' then :help
170
+ when '/' then :search
171
+ when 'm'
172
+ minimize
150
173
  :redraw
174
+ when 'n' then :next_search
175
+ when 'N' then :previous_search
176
+ when 'f' then :open_filter
177
+ when 'g'
178
+ @keypress_memory ||= []
179
+ @keypress_memory << 'g'
180
+ if @keypress_memory == ['g', 'g']
181
+ @keypress_memory = []
182
+ :select_first_task
183
+ end
184
+ when ' '
185
+ if minimized?
186
+ unminimize
187
+ :redraw
188
+ end
151
189
  end
152
- end
153
190
 
154
191
  changed
155
192
  notify_observers(event)
@@ -1,10 +1,10 @@
1
- require 'minder/frame'
1
+ require 'minder/cli/frame'
2
2
 
3
3
  module Minder
4
4
  class PomodoroFrame < Frame
5
5
  def template
6
6
  text = <<-TEXT
7
- <%= period.title %>
7
+ <%= period.title %> #{pomodoros}
8
8
  TEXT
9
9
 
10
10
  if period.message
@@ -25,7 +25,7 @@ TEXT
25
25
  end
26
26
 
27
27
  def period
28
- pomodoro_runner.current_action
28
+ pomodoro_runner.current_period
29
29
  end
30
30
 
31
31
  def handle_char_keypress(key)
@@ -49,5 +49,11 @@ TEXT
49
49
  def set_cursor_position
50
50
  window.setpos(1, lines[0].strip.length + 2)
51
51
  end
52
+
53
+ def pomodoros
54
+ pomodoro_runner.pomodoros_today.map do |pomodoro|
55
+ "\xF0\x9F\x8D\x85 "
56
+ end.join
57
+ end
52
58
  end
53
59
  end
@@ -1,4 +1,4 @@
1
- require 'minder/frame'
1
+ require 'minder/cli/frame'
2
2
 
3
3
  module Minder
4
4
  class QuickAddFrame < Frame
@@ -31,7 +31,7 @@ TEXT
31
31
 
32
32
  def handle_non_char_keypress(key)
33
33
  case key
34
- when 127
34
+ when Curses::Key::BACKSPACE
35
35
  self.input.chop!
36
36
  refresh
37
37
  when 10
@@ -1,10 +1,10 @@
1
1
  require 'ostruct'
2
- require 'minder/help_frame'
3
- require 'minder/search_frame'
4
- require 'minder/filter_frame'
5
- require 'minder/pomodoro_frame'
6
- require 'minder/message_frame'
7
- require 'minder/quick_add_frame'
2
+ require 'minder/cli/help_frame'
3
+ require 'minder/cli/search_frame'
4
+ require 'minder/cli/filter_frame'
5
+ require 'minder/cli/pomodoro_frame'
6
+ require 'minder/cli/message_frame'
7
+ require 'minder/cli/quick_add_frame'
8
8
 
9
9
  module Minder
10
10
  class Scene
@@ -15,6 +15,7 @@ module Minder
15
15
  end
16
16
 
17
17
  def setup
18
+ Curses.ESCDELAY = 0
18
19
  Curses.noecho
19
20
  Curses.init_screen
20
21
  Curses.timeout = 0
@@ -1,4 +1,4 @@
1
- require 'minder/frame'
1
+ require 'minder/cli/frame'
2
2
 
3
3
  module Minder
4
4
  class SearchFrame < Frame
@@ -35,7 +35,7 @@ TEXT
35
35
  when 27
36
36
  changed
37
37
  notify_observers(:escape_search)
38
- when 127
38
+ when Curses::Key::BACKSPACE
39
39
  @search_string.chop!
40
40
  refresh
41
41
  when 10
@@ -0,0 +1,68 @@
1
+ require 'observer'
2
+
3
+ module Minder
4
+ class TaskEditor
5
+ include Observable
6
+
7
+ attr_reader :cursor_position
8
+
9
+ def initialize(task, parent)
10
+ @original_text = task.description.dup
11
+ @cursor_position = 0
12
+ @task = task
13
+ @parent = parent
14
+ end
15
+
16
+ def text
17
+ @task.description
18
+ end
19
+
20
+ def handle_keypress(key)
21
+ return unless key
22
+
23
+ if key.is_a?(Fixnum)
24
+ handle_non_char_keypress(key)
25
+ else
26
+ handle_char_keypress(key)
27
+ end
28
+ end
29
+
30
+ def handle_non_char_keypress(key)
31
+ return unless key
32
+
33
+ data = {}
34
+
35
+ event =
36
+ case key
37
+ when Curses::Key::LEFT
38
+ @cursor_position -= 1 unless cursor_position == 0
39
+ :redraw
40
+ when Curses::Key::RIGHT
41
+ @cursor_position += 1 unless cursor_position > text.length - 1
42
+ :redraw
43
+ when Curses::Key::BACKSPACE
44
+ return if @cursor_position == 0
45
+ @task.description.slice!(@cursor_position - 1)
46
+ @cursor_position -= 1 unless cursor_position == 0
47
+ :redraw
48
+ when 27, 9
49
+ @task.description = @original_text
50
+ :stop_editing
51
+ when 10
52
+ data = { description: @task.description }
53
+ :update_task
54
+ end
55
+
56
+ changed
57
+ notify_observers(event, data)
58
+ end
59
+
60
+ def handle_char_keypress(key)
61
+ @task.description.insert(@cursor_position, key)
62
+ @cursor_position += 1
63
+
64
+ changed
65
+ notify_observers(:redraw)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,98 @@
1
+ require "sqlite3"
2
+ require 'rom'
3
+ require 'rom-sql'
4
+ require 'logger'
5
+
6
+ class Database
7
+ attr_reader :rom
8
+
9
+ def initialize
10
+ FileUtils.mkdir_p(File.dirname(Minder::DATABASE_LOCATION))
11
+ ROM.setup(:sql, "sqlite://#{Minder::DATABASE_LOCATION}")
12
+
13
+ require 'minder/tasks/task'
14
+ require 'minder/database/task_mapper'
15
+ require 'minder/database/tasks'
16
+ require 'minder/database/period_mapper'
17
+ require 'minder/database/periods'
18
+
19
+ ROM.commands(:tasks) do
20
+ define(:delete)
21
+ define(:update)
22
+ end
23
+
24
+ ROM.commands(:periods) do
25
+ define(:update)
26
+ end
27
+
28
+ @rom = ROM.finalize.env
29
+ rom.repositories[:default].use_logger(Logger.new(Minder::LOG_LOCATION))
30
+ end
31
+
32
+ def tasks
33
+ rom.relation(:tasks).active.as(:entity).to_a
34
+ end
35
+
36
+ def tasks_filtered_by(text)
37
+ rom.relation(:tasks).active.as(:entity).filtered_by(text).to_a
38
+ end
39
+
40
+ def add_task(description)
41
+ rom.relations.tasks.insert(description: description)
42
+ end
43
+
44
+ def delete_task(task)
45
+ rom.command(:tasks).delete.by_id(task.id).call
46
+ end
47
+
48
+ def complete_task(task)
49
+ update_task(task, completed_at: Time.now)
50
+ end
51
+
52
+ def start_task(task)
53
+ update_task(task, started_at: Time.now)
54
+ end
55
+
56
+ def unstart_task(task)
57
+ update_task(task, started_at: nil)
58
+ end
59
+
60
+ def update_task(task, options = {})
61
+ rom.command(:tasks).update.by_id(task.id).call(options)
62
+ end
63
+
64
+ def complete_period(task)
65
+ update_period(task, ended_at: Time.now)
66
+ end
67
+
68
+ def add_period(period)
69
+ rom.relations.periods.insert(started_at: period.started_at,
70
+ type: period.type)
71
+ end
72
+
73
+ def last_period
74
+ require 'minder/pomodoro/work_period'
75
+ require 'minder/pomodoro/break_period'
76
+ require 'minder/pomodoro/idle_period'
77
+ data = rom.relations.periods.last
78
+ if data[:type] == 'work'
79
+ Minder::WorkPeriod.new(data)
80
+ elsif data[:type] == 'break'
81
+ Minder::BreakPeriod.new(data)
82
+ else
83
+ Minder::IdlePeriod.new(data)
84
+ end
85
+ end
86
+
87
+ def update_period(period, options = {})
88
+ rom.command(:periods).update.by_id(period.id).call(options)
89
+ end
90
+
91
+ def periods
92
+ rom.relation(:tasks).active.as(:entity).to_a
93
+ end
94
+
95
+ def pomodoros_today
96
+ rom.relation(:periods).pomodoros_today.as(:entity).to_a
97
+ end
98
+ end
@@ -9,7 +9,7 @@ module Minder
9
9
  def run
10
10
  ROM::SQL::Migration::Migrator.new(
11
11
  ROM.env.repositories[:default].connection,
12
- path: File.expand_path(File.dirname(__FILE__) + '/../../db/migrate')
12
+ path: File.expand_path(File.dirname(__FILE__) + '/../../../db/migrate')
13
13
  ).run
14
14
  end
15
15
  end
@@ -0,0 +1,12 @@
1
+ class PeriodMapper < ROM::Mapper
2
+ relation :periods
3
+ register_as :entity
4
+
5
+ # TODO: figure out STI
6
+ model Minder::Period
7
+
8
+ attribute :id
9
+ attribute :type
10
+ attribute :started_at
11
+ attribute :ended_at
12
+ end
@@ -0,0 +1,18 @@
1
+ class Periods < ROM::Relation[:sql]
2
+ def by_id(id)
3
+ where(id: id)
4
+ end
5
+
6
+ def completed_pomodoros
7
+ where(type: 'work')
8
+ .where('started_at IS NOT NULL')
9
+ .where('ended_at IS NOT NULL')
10
+ end
11
+
12
+ def pomodoros_today
13
+ completed_pomodoros.
14
+ where('ended_at BETWEEN ? AND ?',
15
+ Time.now.strftime('%Y-%m-%d 00:00:00'),
16
+ Time.now.strftime('%Y-%m-%d 23:59:59'))
17
+ end
18
+ end
@@ -3,6 +3,10 @@ class Tasks < ROM::Relation[:sql]
3
3
  where("description LIKE '%#{text}%'")
4
4
  end
5
5
 
6
+ def active
7
+ where(completed_at: nil)
8
+ end
9
+
6
10
  def by_id(id)
7
11
  where(id: id)
8
12
  end
@@ -0,0 +1,15 @@
1
+ require 'minder/pomodoro/period'
2
+
3
+ module Minder
4
+ class BreakPeriod < Period
5
+ attribute :duration_in_seconds, Integer, default: DEFAULT_BREAK_PERIOD * 60
6
+
7
+ def title
8
+ "Break period"
9
+ end
10
+
11
+ def type
12
+ 'break'
13
+ end
14
+ end
15
+ end
@@ -1,4 +1,4 @@
1
- require 'minder/period'
1
+ require 'minder/pomodoro/period'
2
2
 
3
3
  module Minder
4
4
  class IdlePeriod < Period
@@ -11,7 +11,11 @@ module Minder
11
11
  end
12
12
 
13
13
  def start!
14
- # noop
14
+ self.started_at = Time.now
15
+ end
16
+
17
+ def complete!
18
+ self.ended_at = Time.now
15
19
  end
16
20
 
17
21
  def elapsed?
@@ -21,5 +25,9 @@ module Minder
21
25
  def completed?
22
26
  true
23
27
  end
28
+
29
+ def type
30
+ 'idle'
31
+ end
24
32
  end
25
33
  end
@@ -0,0 +1,45 @@
1
+ require 'virtus'
2
+
3
+ module Minder
4
+ class Period
5
+ include Virtus.model
6
+
7
+ attribute :id, Integer
8
+ attribute :type, String
9
+ attribute :started_at, DateTime
10
+ attribute :ended_at, DateTime
11
+ attribute :duration_in_seconds, Integer, default: 0
12
+ attribute :completed, Boolean
13
+
14
+ def duration_in_minutes=(minutes)
15
+ self.duration_in_seconds = minutes.to_i * 60
16
+ end
17
+
18
+ def start!
19
+ Minder.play_sound('start.wav')
20
+ self.started_at = Time.now
21
+ end
22
+
23
+ def complete!
24
+ Minder.play_sound('done.wav')
25
+ self.ended_at = Time.now
26
+ self.completed = true
27
+ end
28
+
29
+ def elapsed?
30
+ elapsed_time >= duration_in_seconds
31
+ end
32
+
33
+ def message
34
+ "#{Minder.formatted_time(elapsed_time)} " \
35
+ "(out of #{Minder.formatted_time(duration_in_seconds)})"
36
+ end
37
+
38
+ def elapsed_time
39
+ return 0 unless started_at
40
+ return ended_at.to_i - started_at.to_i if ended_at
41
+
42
+ (Time.now.to_time.to_i - started_at.to_time.to_i)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,83 @@
1
+ require 'observer'
2
+
3
+ require 'minder/pomodoro/work_period'
4
+ require 'minder/pomodoro/break_period'
5
+ require 'minder/pomodoro/idle_period'
6
+
7
+ module Minder
8
+ class PomodoroRunner
9
+ include Observable
10
+
11
+ attr_accessor :work_duration,
12
+ :short_break_duration,
13
+ :long_break_duration,
14
+ :database
15
+
16
+ attr_reader :period_count,
17
+ :current_period
18
+
19
+ def initialize(**options)
20
+ self.work_duration = options.fetch(:work_duration)
21
+ self.short_break_duration = options.fetch(:short_break_duration)
22
+ self.long_break_duration = options.fetch(:long_break_duration)
23
+ self.database = options.fetch(:database)
24
+ @period_count = 0
25
+ current_period = IdlePeriod.new
26
+ current_period.start!
27
+ database.add_period(current_period)
28
+ @current_period = database.last_period
29
+ end
30
+
31
+ def tick
32
+ return if !current_period.elapsed? || current_period.completed?
33
+
34
+ old_period = current_period
35
+ @current_period = IdlePeriod.new
36
+
37
+ changed
38
+ if old_period.is_a?(WorkPeriod)
39
+ notify_observers(:completed_work)
40
+ elsif old_period.is_a?(BreakPeriod)
41
+ notify_observers(:completed_break)
42
+ end
43
+ end
44
+
45
+ def continue
46
+ return unless current_period.elapsed?
47
+
48
+ current_period.complete!
49
+ @pomodoros = nil
50
+ database.complete_period(current_period)
51
+
52
+ advance_period
53
+ current_period.start!
54
+ database.add_period(current_period)
55
+ @current_period = database.last_period
56
+ end
57
+
58
+ def advance_period
59
+ @period_count += 1
60
+ changed
61
+
62
+ if period_count.odd?
63
+ notify_observers(:started_work)
64
+ @current_period = WorkPeriod.new(duration_in_minutes: work_duration)
65
+ else
66
+ notify_observers(:started_break)
67
+ @current_period = BreakPeriod.new(duration_in_minutes: break_duration)
68
+ end
69
+ end
70
+
71
+ def break_duration
72
+ if period_count % 8 == 0
73
+ long_break_duration
74
+ else
75
+ short_break_duration
76
+ end
77
+ end
78
+
79
+ def pomodoros_today
80
+ @pomodoros_today ||= database.pomodoros_today
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,15 @@
1
+ require 'minder/pomodoro/period'
2
+
3
+ module Minder
4
+ class WorkPeriod < Period
5
+ attribute :duration_in_seconds, Integer, default: DEFAULT_WORK_PERIOD * 60
6
+
7
+ def title
8
+ "Work period"
9
+ end
10
+
11
+ def type
12
+ 'work'
13
+ end
14
+ end
15
+ end
@@ -1,8 +1,7 @@
1
- require 'minder/task'
2
- require 'fileutils'
1
+ require 'minder/tasks/task'
3
2
 
4
3
  module Minder
5
- class TaskRecorder
4
+ class TaskManager
6
5
  attr_accessor :lines
7
6
 
8
7
  attr_reader :search_results,
@@ -40,6 +39,10 @@ module Minder
40
39
  !tasks.empty?
41
40
  end
42
41
 
42
+ def update_task(task, options = {})
43
+ database.update_task(task, options)
44
+ end
45
+
43
46
  def add_task(task)
44
47
  File.open(DOING_FILE, 'a') do |file|
45
48
  file.write("#{task}\n")
@@ -88,7 +91,10 @@ module Minder
88
91
 
89
92
  def complete_task
90
93
  task = selected_task
91
- delete_task
94
+ database.complete_task(task)
95
+ write_file_with_backup
96
+ reload
97
+ select_previous_task
92
98
  add_to_done_file("Finished: #{task.description}")
93
99
  end
94
100
 
@@ -1,3 +1,3 @@
1
1
  module Minder
2
- VERSION = '0.2.3'
2
+ VERSION = '0.3.0'
3
3
  end
data/lib/minder.rb CHANGED
@@ -7,6 +7,14 @@ module Minder
7
7
  DONE_FILE = File.join(ENV["HOME"], '.minder', 'done.txt')
8
8
  MIGRATIONS_PATH = File.expand_path(File.dirname(__FILE__) + '/../')
9
9
  DATABASE_LOCATION = "#{ENV['HOME']}/.minder/database.sqlite3"
10
+ LOG_LOCATION = "#{ENV['HOME']}/.minder/info.log"
11
+
12
+ require 'minder/application'
13
+ require 'minder/config'
14
+ require 'minder/version'
15
+
16
+ require 'active_support'
17
+ require 'fileutils'
10
18
 
11
19
  def self.formatted_time(seconds)
12
20
  minutes = (seconds / 60).to_i
@@ -49,7 +49,7 @@ describe Minder::Application do
49
49
  expect(application).to have_received(:system).with('stty -raw echo')
50
50
  expect(STDIN).to have_received(:getc).with('stty -raw echo')
51
51
  expect(pomodoro_runner).to have_received(:next_action)
52
- expect(interval).to receive(:)
52
+ expect(interval).to receive(:blah)
53
53
  end
54
54
  end
55
55
  end
@@ -1,11 +1,11 @@
1
- require 'minder/pomodoro_break'
1
+ require 'minder/break_period'
2
2
 
3
- describe Minder::PomodoroBreak do
3
+ describe Minder::BreakPeriod do
4
4
  describe '#run' do
5
5
  let(:timer) { instance_spy(Minder::Timer) }
6
6
 
7
7
  it 'runs the pomodoro' do
8
- pomodoro = described_class.new(minutes: 5)
8
+ pomodoro = described_class.new(duration_in_minutes: 5)
9
9
 
10
10
  allow(Minder::Timer).to receive(:new)
11
11
  .with(seconds: 300)
@@ -1,11 +1,11 @@
1
- require 'minder/pomodoro'
1
+ require 'minder/work_period'
2
2
 
3
- describe Minder::Pomodoro do
3
+ describe Minder::WorkPeriod do
4
4
  describe '#run' do
5
5
  let(:timer) { instance_spy(Minder::Timer) }
6
6
 
7
7
  it 'runs the pomodoro' do
8
- pomodoro = described_class.new(minutes: 5)
8
+ pomodoro = described_class.new(duration_in_minutes: 5)
9
9
 
10
10
  allow(Minder::Timer).to receive(:new)
11
11
  .with(seconds: 300)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joseph Method
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-01 00:00:00.000000000 Z
11
+ date: 2015-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: curses
@@ -258,37 +258,40 @@ files:
258
258
  - bin/minder
259
259
  - db/migrate/001_create_tasks.rb
260
260
  - db/migrate/002_import_tasks.rb
261
+ - db/migrate/003_create_periods.rb
261
262
  - lib/minder.rb
262
263
  - lib/minder/application.rb
263
- - lib/minder/break_period.rb
264
- - lib/minder/commands/delete_task.rb
264
+ - lib/minder/cli/filter_frame.rb
265
+ - lib/minder/cli/frame.rb
266
+ - lib/minder/cli/help_frame.rb
267
+ - lib/minder/cli/message_frame.rb
268
+ - lib/minder/cli/pomodoro_frame.rb
269
+ - lib/minder/cli/quick_add_frame.rb
270
+ - lib/minder/cli/scene.rb
271
+ - lib/minder/cli/search_frame.rb
272
+ - lib/minder/cli/task_editor.rb
265
273
  - lib/minder/config.rb
266
- - lib/minder/database.rb
267
- - lib/minder/database_migrator.rb
268
- - lib/minder/filter_frame.rb
269
- - lib/minder/frame.rb
270
- - lib/minder/help_frame.rb
271
- - lib/minder/idle_period.rb
272
- - lib/minder/message_frame.rb
273
- - lib/minder/period.rb
274
- - lib/minder/pomodoro_frame.rb
275
- - lib/minder/pomodoro_period.rb
276
- - lib/minder/pomodoro_runner.rb
277
- - lib/minder/quick_add_frame.rb
278
- - lib/minder/scene.rb
279
- - lib/minder/search_frame.rb
280
- - lib/minder/task.rb
281
- - lib/minder/task_mapper.rb
282
- - lib/minder/task_recorder.rb
283
- - lib/minder/tasks.rb
284
- - lib/minder/timer.rb
274
+ - lib/minder/database/commands/delete_task.rb
275
+ - lib/minder/database/database.rb
276
+ - lib/minder/database/database_migrator.rb
277
+ - lib/minder/database/period_mapper.rb
278
+ - lib/minder/database/periods.rb
279
+ - lib/minder/database/task_mapper.rb
280
+ - lib/minder/database/tasks.rb
281
+ - lib/minder/pomodoro/break_period.rb
282
+ - lib/minder/pomodoro/idle_period.rb
283
+ - lib/minder/pomodoro/period.rb
284
+ - lib/minder/pomodoro/pomodoro_runner.rb
285
+ - lib/minder/pomodoro/work_period.rb
286
+ - lib/minder/tasks/task.rb
287
+ - lib/minder/tasks/task_manager.rb
285
288
  - lib/minder/version.rb
286
289
  - minder.gemspec
287
290
  - spec/minder/application_spec.rb
291
+ - spec/minder/break_period_spec.rb
288
292
  - spec/minder/config_spec.rb
289
- - spec/minder/pomodoro_break_spec.rb
293
+ - spec/minder/pomodoro_period_spec.rb
290
294
  - spec/minder/pomodoro_runner_spec.rb
291
- - spec/minder/pomodoro_spec.rb
292
295
  - spec/minder/timer_spec.rb
293
296
  - spec/spec_helper.rb
294
297
  homepage: http://github.com/tristil/minder
@@ -318,9 +321,9 @@ summary: Combines a Pomodoro Technique runner with GTD-style task backlogs and D
318
321
  One-style prompts."
319
322
  test_files:
320
323
  - spec/minder/application_spec.rb
324
+ - spec/minder/break_period_spec.rb
321
325
  - spec/minder/config_spec.rb
322
- - spec/minder/pomodoro_break_spec.rb
326
+ - spec/minder/pomodoro_period_spec.rb
323
327
  - spec/minder/pomodoro_runner_spec.rb
324
- - spec/minder/pomodoro_spec.rb
325
328
  - spec/minder/timer_spec.rb
326
329
  - spec/spec_helper.rb
@@ -1,13 +0,0 @@
1
- require 'minder/period'
2
-
3
- module Minder
4
- class BreakPeriod < Period
5
- def initialize(minutes: DEFAULT_WORK_PERIOD)
6
- super
7
- end
8
-
9
- def title
10
- "Break period"
11
- end
12
- end
13
- end
@@ -1,47 +0,0 @@
1
- require 'fileutils'
2
- require "sqlite3"
3
- require 'rom'
4
- require 'rom-sql'
5
-
6
- class Database
7
- attr_reader :rom
8
-
9
- def initialize
10
- FileUtils.mkdir_p(File.dirname(Minder::DATABASE_LOCATION))
11
- ROM.setup(:sql, "sqlite://#{Minder::DATABASE_LOCATION}")
12
- require 'minder/task'
13
- require 'minder/task_mapper'
14
- require 'minder/tasks'
15
-
16
- ROM.commands(:tasks) do
17
- define(:delete)
18
- define(:update)
19
- end
20
-
21
- @rom = ROM.finalize.env
22
- end
23
-
24
- def tasks
25
- rom.relation(:tasks).as(:entity).to_a
26
- end
27
-
28
- def tasks_filtered_by(text)
29
- rom.relation(:tasks).as(:entity).filtered_by(text).to_a
30
- end
31
-
32
- def add_task(description)
33
- rom.relations.tasks.insert(description: description)
34
- end
35
-
36
- def delete_task(task)
37
- rom.command(:tasks).delete.by_id(task.id).call
38
- end
39
-
40
- def start_task(task)
41
- rom.command(:tasks).update.by_id(task.id).call(started_at: Time.now)
42
- end
43
-
44
- def unstart_task(task)
45
- rom.command(:tasks).update.by_id(task.id).call(started_at: nil)
46
- end
47
- end
data/lib/minder/period.rb DELETED
@@ -1,35 +0,0 @@
1
- require 'minder/timer'
2
-
3
- module Minder
4
- class Period
5
- attr_accessor :minutes,
6
- :timer
7
-
8
- def initialize(minutes: nil)
9
- self.minutes = minutes
10
- self.timer = Minder::Timer.new(seconds: minutes.to_i * 60)
11
- end
12
-
13
- def start!
14
- Minder.play_sound('start.wav')
15
- timer.start!
16
- end
17
-
18
- def complete!
19
- Minder.play_sound('done.wav')
20
- @status = :completed
21
- end
22
-
23
- def completed?
24
- @status == :completed
25
- end
26
-
27
- def elapsed?
28
- timer.completed?
29
- end
30
-
31
- def message
32
- timer.to_s
33
- end
34
- end
35
- end
@@ -1,13 +0,0 @@
1
- require 'minder/period'
2
-
3
- module Minder
4
- class PomodoroPeriod < Period
5
- def initialize(minutes: DEFAULT_WORK_PERIOD)
6
- super
7
- end
8
-
9
- def title
10
- "Work period"
11
- end
12
- end
13
- end
@@ -1,69 +0,0 @@
1
- require 'observer'
2
-
3
- require 'minder/pomodoro_period'
4
- require 'minder/break_period'
5
- require 'minder/idle_period'
6
-
7
- module Minder
8
- class PomodoroRunner
9
- include Observable
10
-
11
- attr_accessor :work_duration,
12
- :short_break_duration,
13
- :long_break_duration
14
-
15
- attr_reader :action_count,
16
- :current_action
17
-
18
- def initialize(**options)
19
- self.work_duration = options.fetch(:work_duration)
20
- self.short_break_duration = options.fetch(:short_break_duration)
21
- self.long_break_duration = options.fetch(:long_break_duration)
22
- @action_count = 0
23
- @current_action = IdlePeriod.new
24
- end
25
-
26
- def tick
27
- return if !current_action.elapsed? || current_action.completed?
28
-
29
- old_action = current_action
30
- current_action.complete!
31
- @current_action = IdlePeriod.new
32
-
33
- changed
34
- if old_action.is_a?(PomodoroPeriod)
35
- notify_observers(:completed_work)
36
- elsif old_action.is_a?(BreakPeriod)
37
- notify_observers(:completed_break)
38
- end
39
- end
40
-
41
- def continue
42
- return unless current_action.elapsed?
43
-
44
- advance_action
45
- current_action.start!
46
- end
47
-
48
- def advance_action
49
- @action_count += 1
50
- changed
51
-
52
- if action_count.odd?
53
- notify_observers(:started_work)
54
- @current_action = PomodoroPeriod.new(minutes: work_duration)
55
- else
56
- notify_observers(:started_break)
57
- @current_action = BreakPeriod.new(minutes: break_duration)
58
- end
59
- end
60
-
61
- def break_duration
62
- if action_count % 8 == 0
63
- long_break_duration
64
- else
65
- short_break_duration
66
- end
67
- end
68
- end
69
- end
data/lib/minder/timer.rb DELETED
@@ -1,32 +0,0 @@
1
- require 'minder'
2
-
3
- module Minder
4
- class Timer
5
- attr_accessor :seconds,
6
- :start_time
7
-
8
- def initialize(seconds: DEFAULT_WORK_PERIOD)
9
- self.seconds = seconds
10
- end
11
-
12
- def start!
13
- self.start_time = Time.now
14
- end
15
-
16
- def completed?
17
- elapsed_time.to_i >= seconds
18
- end
19
-
20
- def elapsed_time
21
- return 0 unless start_time
22
-
23
- (Time.now - start_time)
24
- end
25
-
26
- def to_s
27
- "#{Minder.formatted_time(elapsed_time)} " \
28
- "(out of #{Minder.formatted_time(seconds)})"
29
- end
30
- end
31
- end
32
-
File without changes