minder 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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