minder 0.1 → 0.2.beta1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d1bd7f4b433d3f09c0b5c5c932d45f65b7e00dd6
4
- data.tar.gz: e8c92eac92153eea880e7762c040fef0f43ffc27
3
+ metadata.gz: a57aa002981f78574ea362295234ca6e673c87bb
4
+ data.tar.gz: fee6f114376157724bd5371bb88dda9bf32fe1b2
5
5
  SHA512:
6
- metadata.gz: 44bbcf6c44358e936ad5d0896992a7ddb9d1815ab18bf4b5c077b346ab7651e19baba8ae5d1fbdb3c9391c97e8bc4f9d6fef108477fd29715c237fcc0d9860f3
7
- data.tar.gz: efd8144a3790c3afe5d2848d8d392ec1ceb59bfedc6584e2b0b734e3f2215a8f775e988a2636ef2663d537f60a8490e000fae70786ff4a1bd19e4223ae160939
6
+ metadata.gz: 409d1644bf65106d27ad1e8893f951bdf4062b85bab27958bfcec605384cc2071da58f0186c7352b2b5949499ce6d1c964d9ff20958445d8515eaaec9b7afb12
7
+ data.tar.gz: 66cbb70b7a152744008236769fa0e47c3996334162329b80285928235333324184d453fa5aed2d3f297f0932c936a21dce4224df6c785e55b98264386eddb2c2
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.gem
data/Gemfile.lock CHANGED
@@ -2,12 +2,22 @@ PATH
2
2
  remote: .
3
3
  specs:
4
4
  minder (0.1)
5
- curses
6
- virtus
5
+ activesupport
6
+ curses (~> 1.0.0, >= 1.0.1)
7
+ rom (~> 0.7.0, >= 0.7)
8
+ rom-sql (~> 0.5.0, >= 0.5)
9
+ sqlite3
10
+ virtus (~> 1.0.0, >= 1.0.5)
7
11
 
8
12
  GEM
9
13
  remote: https://rubygems.org/
10
14
  specs:
15
+ activesupport (4.2.1)
16
+ i18n (~> 0.7)
17
+ json (~> 1.7, >= 1.7.7)
18
+ minitest (~> 5.1)
19
+ thread_safe (~> 0.3, >= 0.3.4)
20
+ tzinfo (~> 1.1)
11
21
  axiom-types (0.1.1)
12
22
  descendants_tracker (~> 0.0.4)
13
23
  ice_nine (~> 0.11.0)
@@ -26,8 +36,11 @@ GEM
26
36
  thread_safe (~> 0.3, >= 0.3.1)
27
37
  diff-lcs (1.2.5)
28
38
  equalizer (0.0.11)
39
+ i18n (0.7.0)
29
40
  ice_nine (0.11.1)
41
+ json (1.8.2)
30
42
  method_source (0.8.2)
43
+ minitest (5.7.0)
31
44
  pry (0.10.1)
32
45
  coderay (~> 1.1.0)
33
46
  method_source (~> 0.8.1)
@@ -38,23 +51,34 @@ GEM
38
51
  pry-stack_explorer (0.4.9.2)
39
52
  binding_of_caller (>= 0.7)
40
53
  pry (>= 0.9.11)
41
- rake (10.4.2)
54
+ rom (0.7.1)
55
+ equalizer (~> 0.0, >= 0.0.9)
56
+ transproc (~> 0.2, >= 0.2.2)
57
+ rom-sql (0.5.1)
58
+ equalizer (~> 0.0, >= 0.0.9)
59
+ rom (~> 0.7, >= 0.7.0)
60
+ sequel (~> 4.18)
42
61
  rspec (3.2.0)
43
62
  rspec-core (~> 3.2.0)
44
63
  rspec-expectations (~> 3.2.0)
45
64
  rspec-mocks (~> 3.2.0)
46
- rspec-core (3.2.2)
65
+ rspec-core (3.2.3)
47
66
  rspec-support (~> 3.2.0)
48
- rspec-expectations (3.2.0)
67
+ rspec-expectations (3.2.1)
49
68
  diff-lcs (>= 1.2.0, < 2.0)
50
69
  rspec-support (~> 3.2.0)
51
70
  rspec-mocks (3.2.1)
52
71
  diff-lcs (>= 1.2.0, < 2.0)
53
72
  rspec-support (~> 3.2.0)
54
73
  rspec-support (3.2.2)
74
+ sequel (4.22.0)
55
75
  slop (3.6.0)
76
+ sqlite3 (1.3.10)
56
77
  thread_safe (0.3.5)
57
78
  timecop (0.7.3)
79
+ transproc (0.2.2)
80
+ tzinfo (1.2.2)
81
+ thread_safe (~> 0.1)
58
82
  virtus (1.0.5)
59
83
  axiom-types (~> 0.1)
60
84
  coercible (~> 1.0)
@@ -65,11 +89,10 @@ PLATFORMS
65
89
  ruby
66
90
 
67
91
  DEPENDENCIES
68
- bundler
92
+ bundler (~> 1)
69
93
  minder!
70
- pry
71
- pry-byebug
72
- pry-stack_explorer
73
- rake
74
- rspec
75
- timecop
94
+ pry (~> 0.10, >= 0.10)
95
+ pry-byebug (~> 3.1, >= 3.1.0)
96
+ pry-stack_explorer (~> 0.4.9)
97
+ rspec (~> 3.2, >= 3.2)
98
+ timecop (~> 0.7, >= 0.7.3)
data/README.md CHANGED
@@ -11,21 +11,25 @@ At present it has these features:
11
11
  soothing sounds of a "pomodoro" kitchen timer at the beginning and end of a
12
12
  break.
13
13
  - A todo log that allows starting, unstarting and completing a task.
14
- - Stores a simple text log of task and pomodoro activity.
14
+ - Stores a simple text log of task and pomodoro activity (in `~/.minder/done.txt`).
15
15
 
16
16
  Plans for the future include:
17
17
 
18
- - Filtering by GTD-style labels (like @context, +project, and #tag)
18
+ - Filtering by [Getting Things Done](http://gettingthingsdone.com/)-style labels (like @context, +project, and #tag)
19
19
  - Integration with a website blacklist tool like
20
20
  [SelfControl](https://github.com/SelfControlApp/selfcontrol/).
21
21
  - Keeping track of the number of pomodoros performed during a day.
22
22
  - Desktop notifications for Linux and MacOS
23
- - Prompts to summarize plans for the day and day's end progress.
23
+ - Prompts to summarize plans for the day and day's end progress, inspired by [Day One](http://dayoneapp.com/).
24
24
 
25
25
  Audio files are from https://github.com/niftylettuce/pomodoro-timer
26
26
 
27
27
  ## Usage
28
28
 
29
+ Minder is designed to be run in a separate pane in a Tmux session (or in a split
30
+ inside a program like iTerm2). The tasks list will scroll if there is not enough
31
+ room to display all tasks.
32
+
29
33
  There are three sections. You can switch between sections by pressing Tab. The
30
34
  commands for each section only work when the section is focused.
31
35
 
@@ -37,7 +41,9 @@ commands for each section only work when the section is focused.
37
41
  - Press 'x' to delete a task.
38
42
  - Press 's' to start a task.
39
43
  - Press 'u' to un-start a task.
40
- - The Quick Add Task section. Enter text here to add a task to the tasks list
44
+ - Press 'j' to do down the list and 'k' to go up.
45
+ - Press 'gg' to go to top of task list and 'G' to go to bottom of list.
46
+ - The Quick Add Task section. Enter text here to add a task to the tasks list.
41
47
  at any time.
42
48
 
43
49
  ## License
data/bin/minder CHANGED
@@ -1,7 +1,15 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'minder'
4
+ require 'minder/database'
5
+ database = Database.new
6
+ DB = database.rom.repositories[:default]
7
+
4
8
  require 'minder/application'
9
+ require 'minder/database_migrator'
10
+
11
+ migrator = Minder::DatabaseMigrator.new(database: database)
12
+ migrator.run
5
13
 
6
- application = Minder::Application.new
14
+ application = Minder::Application.new(database: database)
7
15
  application.run
@@ -0,0 +1,15 @@
1
+ Sequel.migration do
2
+ up do
3
+ create_table(:tasks) do
4
+ primary_key :id
5
+ String :description, null: false
6
+ Boolean :selected, default: false
7
+ Datetime :started_at
8
+ Datetime :completed_at
9
+ end
10
+ end
11
+
12
+ down do
13
+ drop_table(:tasks)
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ Sequel.migration do
2
+ up do
3
+ next unless File.exists?(Minder::DOING_FILE)
4
+
5
+ tasks = ROM.env.repositories[:default].connection.from(:tasks)
6
+ tasks.delete
7
+
8
+ File.readlines(Minder::DOING_FILE).each do |line|
9
+ tasks.insert(description: line.strip)
10
+ end
11
+ end
12
+
13
+ down do
14
+ end
15
+ end
@@ -2,7 +2,7 @@ require 'minder/config'
2
2
  require 'minder/pomodoro_runner'
3
3
  require 'minder/task_recorder'
4
4
  require 'minder/scene'
5
-
5
+ require 'active_support'
6
6
  require 'curses'
7
7
  require 'fileutils'
8
8
 
@@ -11,10 +11,17 @@ module Minder
11
11
  attr_accessor :config,
12
12
  :scene,
13
13
  :pomodoro_frame,
14
+ :help_frame,
15
+ :search_frame,
16
+ :filter_frame,
14
17
  :message_frame,
15
18
  :quick_add_frame
16
19
 
17
- def initialize(config: Minder::Config.new(CONFIG_LOCATION))
20
+ attr_reader :database
21
+
22
+ def initialize(config: Minder::Config.new(CONFIG_LOCATION),
23
+ database: Database.new)
24
+ @database = database
18
25
  self.config = config
19
26
  config.load
20
27
  FileUtils.mkdir_p(File.join(ENV['HOME'], '.minder'))
@@ -35,12 +42,21 @@ module Minder
35
42
  options = { pomodoro_runner: pomodoro_runner, task_manager: task_recorder }
36
43
 
37
44
  self.pomodoro_frame = PomodoroFrame.new(options)
45
+ self.help_frame = HelpFrame.new(options)
46
+ help_frame.hide
47
+ self.filter_frame = FilterFrame.new(options)
48
+ filter_frame.hide
49
+ self.search_frame = SearchFrame.new(options)
50
+ search_frame.hide
38
51
  self.message_frame = MessageFrame.new(options)
39
52
  self.quick_add_frame = QuickAddFrame.new(options)
40
53
  quick_add_frame.focus
41
54
 
42
55
  scene.frames << pomodoro_frame
43
56
  scene.frames << message_frame
57
+ scene.frames << help_frame
58
+ scene.frames << filter_frame
59
+ scene.frames << search_frame
44
60
  scene.frames << quick_add_frame
45
61
 
46
62
  scene.frames.each do |frame|
@@ -83,7 +99,7 @@ module Minder
83
99
  end
84
100
 
85
101
  def task_recorder
86
- @task_recorder ||= TaskRecorder.new
102
+ @task_recorder ||= TaskRecorder.new(database: database)
87
103
  end
88
104
 
89
105
  def handle_event(event, data = {})
@@ -91,9 +107,9 @@ module Minder
91
107
 
92
108
  case event
93
109
  when :started_work
94
- message_frame.hide
110
+ message_frame.minimize
95
111
  when :completed_work
96
- message_frame.unhide
112
+ message_frame.unminimize
97
113
  when :continue
98
114
  pomodoro_runner.continue
99
115
  when :editor
@@ -119,6 +135,38 @@ module Minder
119
135
  task_recorder.select_last_task
120
136
  when :select_first_task
121
137
  task_recorder.select_first_task
138
+ when :help
139
+ message_frame.hide
140
+ help_frame.unhide
141
+ scene.focus_frame(help_frame)
142
+ when :hide_help
143
+ help_frame.hide
144
+ message_frame.unhide
145
+ scene.focus_frame(message_frame)
146
+ when :open_filter
147
+ filter_frame.unhide
148
+ scene.focus_frame(filter_frame)
149
+ when :submit_filter
150
+ filter_frame.hide if data[:text] == ''
151
+ scene.focus_frame(message_frame)
152
+ when :update_filter
153
+ task_recorder.filter(data[:text])
154
+ when :search
155
+ search_frame.unhide
156
+ search_frame.begin_search
157
+ scene.focus_frame(search_frame)
158
+ when :submit_search
159
+ search_frame.hide
160
+ scene.focus_frame(message_frame)
161
+ task_recorder.search(data[:text])
162
+ task_recorder.select_search_result
163
+ when :next_search
164
+ task_recorder.next_search
165
+ when :previous_search
166
+ task_recorder.previous_search
167
+ when :escape_search
168
+ search_frame.hide
169
+ scene.focus_frame(message_frame)
122
170
  end
123
171
 
124
172
  scene.redraw
@@ -0,0 +1,9 @@
1
+ module Minder
2
+ module Commands
3
+ class CreateUser < ROM::Commands::Delete[:sql]
4
+ register_as :delete
5
+ relation :tasks
6
+ end
7
+ end
8
+ end
9
+
@@ -0,0 +1,45 @@
1
+ require "sqlite3"
2
+ require 'rom'
3
+ require 'rom-sql'
4
+
5
+ class Database
6
+ attr_reader :rom
7
+
8
+ def initialize
9
+ ROM.setup(:sql, Minder::DATABASE_LOCATION)
10
+ require 'minder/task'
11
+ require 'minder/task_mapper'
12
+ require 'minder/tasks'
13
+
14
+ ROM.commands(:tasks) do
15
+ define(:delete)
16
+ define(:update)
17
+ end
18
+
19
+ @rom = ROM.finalize.env
20
+ end
21
+
22
+ def tasks
23
+ rom.relation(:tasks).as(:entity).to_a
24
+ end
25
+
26
+ def tasks_filtered_by(text)
27
+ rom.relation(:tasks).as(:entity).filtered_by(text).to_a
28
+ end
29
+
30
+ def add_task(description)
31
+ rom.relations.tasks.insert(description: description)
32
+ end
33
+
34
+ def delete_task(task)
35
+ rom.command(:tasks).delete.by_id(task.id).call
36
+ end
37
+
38
+ def start_task(task)
39
+ rom.command(:tasks).update.by_id(task.id).call(started_at: Time.now)
40
+ end
41
+
42
+ def unstart_task(task)
43
+ rom.command(:tasks).update.by_id(task.id).call(started_at: nil)
44
+ end
45
+ end
@@ -0,0 +1,13 @@
1
+ module Minder
2
+ class DatabaseMigrator
3
+ attr_reader :database
4
+
5
+ def initialize(database:)
6
+ @database = database
7
+ end
8
+
9
+ def run
10
+ database.rom.repositories[:default].run_migrations
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,49 @@
1
+ require 'minder/frame'
2
+
3
+ module Minder
4
+ class FilterFrame < Frame
5
+ attr_reader :filter_string
6
+
7
+ def initialize(*)
8
+ super
9
+ @filter_string = ''
10
+ end
11
+
12
+ def desired_height
13
+ 3
14
+ end
15
+
16
+ def template
17
+ <<-TEXT
18
+ Filter: #{filter_string}
19
+ TEXT
20
+ end
21
+
22
+ def set_cursor_position
23
+ window.setpos(1, filter_string.length + 9)
24
+ end
25
+
26
+ def handle_char_keypress(key)
27
+ return unless key
28
+
29
+ @filter_string += key
30
+ refresh
31
+ changed
32
+ notify_observers(:update_filter, { text: filter_string })
33
+ end
34
+
35
+ def handle_non_char_keypress(key)
36
+ if key == 10
37
+ changed
38
+ notify_observers(:submit_filter, { text: filter_string })
39
+ end
40
+
41
+ @filter_string.chop! if key == 127
42
+
43
+ refresh
44
+ changed
45
+ notify_observers(:update_filter, { text: filter_string })
46
+ end
47
+ end
48
+ end
49
+
@@ -0,0 +1,33 @@
1
+ require 'minder/frame'
2
+
3
+ module Minder
4
+ class HelpFrame < Frame
5
+ def template
6
+ <<-TEXT
7
+ Commands: any key to dismiss
8
+
9
+ (s) start task
10
+ (u) un-start task
11
+ (d) mark task as done
12
+ (x) delete task
13
+ (e) open tasks in text editor
14
+ (G) go to bottom of list
15
+ (gg) go to top of list
16
+ (/) Search among tasks
17
+ (n) Next search result
18
+ (N) Previous search result
19
+ (?) to view this text
20
+ TEXT
21
+ end
22
+
23
+ def desired_height
24
+ template.split("\n").length + 2
25
+ end
26
+
27
+ def handle_keypress(key)
28
+ return unless key
29
+ changed
30
+ notify_observers(:hide_help)
31
+ end
32
+ end
33
+ end
@@ -7,23 +7,36 @@ module Minder
7
7
  def initialize(*)
8
8
  super
9
9
  self.height = desired_height
10
+ @minimized = false
11
+ end
12
+
13
+ def minimize
14
+ @minimized = true
15
+ end
16
+
17
+ def unminimize
18
+ @minimized = false
19
+ end
20
+
21
+ def minimized?
22
+ @minimized
10
23
  end
11
24
 
12
25
  def template
13
- if task_manager.tasks?
26
+ if minimized?
27
+ minimized_message
28
+ elsif task_manager.tasks?
14
29
  doing_message
15
30
  else
16
31
  prompt_message
17
32
  end
18
33
  end
19
34
 
20
- #def build_window
21
- #Curses::Pad.new(min_height, width)
22
- #end
23
-
24
- #def window_refresh
25
- #window.refresh(scroll_offset, 0, 3, 0, height + 2, width)
26
- #end
35
+ def minimized_message
36
+ <<-TEXT
37
+ Space to see tasks
38
+ TEXT
39
+ end
27
40
 
28
41
  def prompt_message
29
42
  <<-TEXT
@@ -35,7 +48,7 @@ TEXT
35
48
 
36
49
  def header_text
37
50
  <<-TEXT
38
- Tasks (s) start (x) to delete (d) to mark as done
51
+ Tasks ? to see commands
39
52
 
40
53
  TEXT
41
54
  end
@@ -61,6 +74,8 @@ TEXT
61
74
  end
62
75
 
63
76
  def desired_height
77
+ return 3 if minimized?
78
+
64
79
  header_text_lines.length + tasks_text_lines.length + 3
65
80
  end
66
81
 
@@ -72,22 +87,17 @@ TEXT
72
87
  tasks_text_lines[scroll_offset..(allocated_tasks_height + scroll_offset - 4)].join("\n")
73
88
  end
74
89
 
75
- def blank_line
76
- " " * (width - 2) + "\n"
77
- end
78
-
79
90
  def doing_message
80
91
  header_text +
81
- offset_tasks_text #+
82
- #blank_lines
83
- end
84
-
85
- def blank_lines
86
- blank_line * scroll_offset
92
+ offset_tasks_text
87
93
  end
88
94
 
89
95
  def set_cursor_position
90
- window.setpos(3 + task_manager.selected_task_index - scroll_offset, 3)
96
+ if minimized?
97
+ window.setpos(1, 20)
98
+ else
99
+ window.setpos(3 + task_manager.selected_task_index - scroll_offset, 3)
100
+ end
91
101
  end
92
102
 
93
103
  def scroll_offset
@@ -108,6 +118,15 @@ TEXT
108
118
  when 's' then :start_task
109
119
  when 'u' then :unstart_task
110
120
  when 'G' then :select_last_task
121
+ when 'e' then :editor
122
+ when '?' then :help
123
+ when '/' then :search
124
+ when 'm'
125
+ minimize
126
+ :redraw
127
+ when 'n' then :next_search
128
+ when 'N' then :previous_search
129
+ when 'f' then :open_filter
111
130
  when 'g'
112
131
  @keypress_memory ||= []
113
132
  @keypress_memory << 'g'
@@ -115,6 +134,11 @@ TEXT
115
134
  @keypress_memory = []
116
135
  :select_first_task
117
136
  end
137
+ when ' '
138
+ if minimized?
139
+ unminimize
140
+ :redraw
141
+ end
118
142
  end
119
143
 
120
144
  changed
@@ -31,7 +31,6 @@ TEXT
31
31
  def handle_char_keypress(key)
32
32
  event = case key
33
33
  when ' ' then :continue
34
- when 'e' then :editor
35
34
  end
36
35
 
37
36
  changed
@@ -39,8 +39,6 @@ TEXT
39
39
  notify_observers(:add_task, { task: input })
40
40
  self.input = ''
41
41
  refresh
42
- else
43
- #Minder.pry_open(binding)
44
42
  end
45
43
  end
46
44
  end
data/lib/minder/scene.rb CHANGED
@@ -1,4 +1,7 @@
1
1
  require 'ostruct'
2
+ require 'minder/help_frame'
3
+ require 'minder/search_frame'
4
+ require 'minder/filter_frame'
2
5
  require 'minder/pomodoro_frame'
3
6
  require 'minder/message_frame'
4
7
  require 'minder/quick_add_frame'
@@ -34,37 +37,66 @@ module Minder
34
37
  end
35
38
  end
36
39
 
40
+ def focus_frame(frame)
41
+ focused_frame.unfocus
42
+ frame.focus
43
+ end
44
+
37
45
  def refresh
38
46
  frames.map(&:refresh)
39
47
  end
40
48
 
41
49
  def resize_frames
42
50
  frames.map(&:resize)
51
+ first_frame = frames.first
52
+
53
+ first_frame.move(0, 0)
54
+ next_height = first_frame.height
43
55
 
44
- available_height = Curses.lines - frames.last.height
56
+ other_frames = (frames.reject(&:hidden?) - [main_frame]).compact
57
+ if main_frame
45
58
 
46
- frames.first.move(0, 0)
47
- next_height = frames.first.height
48
- second_frame = frames[1]
59
+ other_height = other_frames.reduce(0) do |num, frame|
60
+ num += frame.height
61
+ num
62
+ end
63
+
64
+ available_height = Curses.lines - other_height
49
65
 
50
- unless second_frame.hidden?
51
- proposed_height = available_height - frames.first.height
52
- if proposed_height < second_frame.desired_height
53
- second_frame.height = proposed_height
66
+ if available_height > main_frame.desired_height
67
+ main_frame.height = main_frame.desired_height
54
68
  else
55
- second_frame.height = second_frame.desired_height
69
+ main_frame.height = available_height
56
70
  end
57
- second_frame.move(next_height, 0)
58
- next_height += second_frame.height
71
+ main_frame.move(next_height, 0)
72
+
73
+ next_height += main_frame.height
59
74
  end
60
75
 
61
- if next_height <= available_height
62
- frames.last.move(next_height, 0)
76
+ (other_frames - [first_frame]).each do |frame|
77
+ frame.move(next_height, 0)
78
+ next_height += frame.height
79
+ end
80
+ end
81
+
82
+ def main_frame
83
+ return if message_frame.hidden? && help_frame.hidden?
84
+
85
+ if message_frame.hidden?
86
+ help_frame
63
87
  else
64
- frames.last.move(available_height, 0)
88
+ message_frame
65
89
  end
66
90
  end
67
91
 
92
+ def message_frame
93
+ @message_frame ||= frames.find { |frame| frame.is_a?(MessageFrame) }
94
+ end
95
+
96
+ def help_frame
97
+ @help_frame ||= frames.find { |frame| frame.is_a?(HelpFrame) }
98
+ end
99
+
68
100
  def redraw
69
101
  refresh
70
102
  resize_frames
@@ -0,0 +1,53 @@
1
+ require 'minder/frame'
2
+
3
+ module Minder
4
+ class SearchFrame < Frame
5
+ attr_reader :search_string
6
+
7
+ def initialize(*)
8
+ super
9
+ @search_string = ''
10
+ end
11
+
12
+ def desired_height
13
+ 3
14
+ end
15
+
16
+ def template
17
+ <<-TEXT
18
+ /#{search_string}
19
+ TEXT
20
+ end
21
+
22
+ def set_cursor_position
23
+ window.setpos(1, search_string.length + 2)
24
+ end
25
+
26
+ def handle_char_keypress(key)
27
+ return unless key
28
+
29
+ @search_string += key
30
+ refresh
31
+ end
32
+
33
+ def handle_non_char_keypress(key)
34
+ case key
35
+ when 27
36
+ changed
37
+ notify_observers(:escape_search)
38
+ when 127
39
+ @search_string.chop!
40
+ refresh
41
+ when 10
42
+ changed
43
+ notify_observers(:submit_search, { text: search_string })
44
+ refresh
45
+ end
46
+ end
47
+
48
+ def begin_search
49
+ @search_string = ''
50
+ end
51
+ end
52
+ end
53
+
data/lib/minder/task.rb CHANGED
@@ -4,21 +4,14 @@ module Minder
4
4
  class Task
5
5
  include Virtus.model
6
6
 
7
+ attribute :id, Integer
7
8
  attribute :description, String
8
- attribute :selected, Boolean, default: false
9
- attribute :started, Boolean, default: false
10
-
11
- def start
12
- self.started = true
13
- end
14
-
15
- def unstart
16
- self.description.gsub!(/\* /, '')
17
- self.started = false
18
- end
9
+ attribute :selected, Boolean
10
+ attribute :started_at, DateTime
11
+ attribute :completed_at, DateTime
19
12
 
20
13
  def started?
21
- super || description =~ /\* /
14
+ !!started_at
22
15
  end
23
16
 
24
17
  def to_s
@@ -0,0 +1,11 @@
1
+ class TaskMapper < ROM::Mapper
2
+ relation :tasks
3
+ register_as :entity
4
+
5
+ model Minder::Task
6
+
7
+ attribute :id
8
+ attribute :description
9
+ attribute :selected
10
+ attribute :started
11
+ end
@@ -3,16 +3,36 @@ require 'fileutils'
3
3
 
4
4
  module Minder
5
5
  class TaskRecorder
6
- attr_accessor :tasks,
7
- :lines
6
+ attr_accessor :lines
8
7
 
9
- def initialize
8
+ attr_reader :search_results,
9
+ :database,
10
+ :tasks
11
+
12
+ def initialize(database:)
13
+ @database = database
10
14
  @selected_task_index = 0
15
+ @selected_search_result = 0
16
+ @search_results = []
17
+ @filter = ''
11
18
  reload
12
19
  end
13
20
 
21
+ def filter(text)
22
+ @tasks = nil
23
+ @filter = text
24
+ end
25
+
14
26
  def tasks
15
- @tasks ||= lines.map { |task| Task.new(description: task) }
27
+ @tasks ||= fetch_filtered_tasks
28
+ end
29
+
30
+ def unfiltered_tasks
31
+ database.tasks
32
+ end
33
+
34
+ def fetch_filtered_tasks
35
+ database.tasks_filtered_by(@filter)
16
36
  end
17
37
 
18
38
  def tasks?
@@ -23,6 +43,7 @@ module Minder
23
43
  File.open(DOING_FILE, 'a') do |file|
24
44
  file.write("#{task}\n")
25
45
  end
46
+ database.add_task(task)
26
47
  reload
27
48
  end
28
49
 
@@ -51,7 +72,7 @@ module Minder
51
72
  end
52
73
 
53
74
  def delete_task
54
- lines.delete_at(selected_task_index)
75
+ database.delete_task(selected_task)
55
76
  @tasks = nil
56
77
  write_file_with_backup
57
78
  reload
@@ -83,7 +104,7 @@ module Minder
83
104
 
84
105
  def write_file(path)
85
106
  File.open(path, 'w') do |file|
86
- tasks.each do |task|
107
+ unfiltered_tasks.each do |task|
87
108
  line = task.to_s
88
109
  line = "* #{line}" if task.started?
89
110
  file.write("#{line}\n")
@@ -92,14 +113,14 @@ module Minder
92
113
  end
93
114
 
94
115
  def start_task
95
- selected_task.start
116
+ database.start_task(selected_task)
96
117
  write_file_with_backup
97
118
  add_to_done_file("Started: #{selected_task.description}")
98
119
  reload
99
120
  end
100
121
 
101
122
  def unstart_task
102
- selected_task.unstart
123
+ database.unstart_task(selected_task)
103
124
  write_file_with_backup
104
125
  add_to_done_file("Un-started: #{selected_task.description}")
105
126
  reload
@@ -116,5 +137,39 @@ module Minder
116
137
  def select_first_task
117
138
  @selected_task_index = 0
118
139
  end
140
+
141
+ def search(text)
142
+ @search_results = tasks.select do |task|
143
+ task.description.downcase.include?(text.downcase)
144
+ end
145
+ @selected_search_result = 0
146
+ end
147
+
148
+ def select_search_result(search_index = 0)
149
+ return if search_results.empty?
150
+
151
+ @selected_task_index = tasks.find_index do |task|
152
+ task.description == search_results[search_index].description
153
+ end
154
+ end
155
+
156
+ def next_search
157
+ @selected_search_result += 1
158
+
159
+ if @selected_search_result > search_results.length - 1
160
+ @selected_search_result = 0
161
+ end
162
+ select_search_result(@selected_search_result)
163
+ end
164
+
165
+ def previous_search
166
+ @selected_search_result -= 1
167
+
168
+ if @selected_search_result < 0
169
+ @selected_search_result = search_results.length - 1
170
+ end
171
+
172
+ select_search_result(@selected_search_result)
173
+ end
119
174
  end
120
175
  end
@@ -0,0 +1,9 @@
1
+ class Tasks < ROM::Relation[:sql]
2
+ def filtered_by(text)
3
+ where("description LIKE '%#{text}%'")
4
+ end
5
+
6
+ def by_id(id)
7
+ where(id: id)
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
1
  module Minder
2
- VERSION = 0.1
2
+ VERSION = '0.2.beta1'
3
3
  end
data/lib/minder.rb CHANGED
@@ -5,6 +5,9 @@ module Minder
5
5
  ASSETS_LOCATION = File.expand_path(File.dirname(__FILE__) + '/../assets')
6
6
  DOING_FILE = File.join(ENV["HOME"], '.minder', 'doing.txt')
7
7
  DONE_FILE = File.join(ENV["HOME"], '.minder', 'done.txt')
8
+ MIGRATIONS_PATH = File.expand_path(File.dirname(__FILE__) + '/../')
9
+ DATABASE_LOCATION = "sqlite://#{ENV['HOME']}/.minder/database.sqlite3"
10
+
8
11
  def self.formatted_time(seconds)
9
12
  minutes = (seconds / 60).to_i
10
13
  seconds = (seconds % 60).round
data/minder.gemspec CHANGED
@@ -18,8 +18,12 @@ Gem::Specification.new do |spec|
18
18
 
19
19
  spec.add_runtime_dependency 'curses', '~> 1.0', '>= 1.0.1'
20
20
  spec.add_runtime_dependency 'virtus', '~> 1.0', '>= 1.0.5'
21
+ spec.add_runtime_dependency 'rom', '~> 0.7', '>= 0.7'
22
+ spec.add_runtime_dependency 'rom-sql', '~> 0.5', '>= 0.5'
23
+ spec.add_runtime_dependency 'sqlite3', '~> 1.3', '>= 1.3.10'
24
+ spec.add_runtime_dependency 'activesupport', '~> 4.2', '>= 4.2.1'
21
25
 
22
- spec.add_development_dependency "bundler", '~> 0'
26
+ spec.add_development_dependency "bundler", '~> 1'
23
27
  spec.add_development_dependency "rspec", '~> 3.2', '>= 3.2'
24
28
  spec.add_development_dependency "timecop", '~> 0.7', '>= 0.7.3'
25
29
  spec.add_development_dependency "pry", '~> 0.10', '>= 0.10'
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.1'
4
+ version: 0.2.beta1
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-05-12 00:00:00.000000000 Z
11
+ date: 2015-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: curses
@@ -50,20 +50,100 @@ dependencies:
50
50
  - - ">="
51
51
  - !ruby/object:Gem::Version
52
52
  version: 1.0.5
53
+ - !ruby/object:Gem::Dependency
54
+ name: rom
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '0.7'
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0.7'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '0.7'
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0.7'
73
+ - !ruby/object:Gem::Dependency
74
+ name: rom-sql
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '0.5'
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0.5'
83
+ type: :runtime
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.5'
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0.5'
93
+ - !ruby/object:Gem::Dependency
94
+ name: sqlite3
95
+ requirement: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - "~>"
98
+ - !ruby/object:Gem::Version
99
+ version: '1.3'
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 1.3.10
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '1.3'
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 1.3.10
113
+ - !ruby/object:Gem::Dependency
114
+ name: activesupport
115
+ requirement: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - "~>"
118
+ - !ruby/object:Gem::Version
119
+ version: '4.2'
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: 4.2.1
123
+ type: :runtime
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '4.2'
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: 4.2.1
53
133
  - !ruby/object:Gem::Dependency
54
134
  name: bundler
55
135
  requirement: !ruby/object:Gem::Requirement
56
136
  requirements:
57
137
  - - "~>"
58
138
  - !ruby/object:Gem::Version
59
- version: '0'
139
+ version: '1'
60
140
  type: :development
61
141
  prerelease: false
62
142
  version_requirements: !ruby/object:Gem::Requirement
63
143
  requirements:
64
144
  - - "~>"
65
145
  - !ruby/object:Gem::Version
66
- version: '0'
146
+ version: '1'
67
147
  - !ruby/object:Gem::Dependency
68
148
  name: rspec
69
149
  requirement: !ruby/object:Gem::Requirement
@@ -166,6 +246,7 @@ executables:
166
246
  extensions: []
167
247
  extra_rdoc_files: []
168
248
  files:
249
+ - ".gitignore"
169
250
  - ".rspec"
170
251
  - ".ruby-version"
171
252
  - Gemfile
@@ -175,11 +256,18 @@ files:
175
256
  - assets/done.wav
176
257
  - assets/start.wav
177
258
  - bin/minder
259
+ - db/migrate/001_create_tasks.rb
260
+ - db/migrate/002_import_tasks.rb
178
261
  - lib/minder.rb
179
262
  - lib/minder/application.rb
180
263
  - lib/minder/break_period.rb
264
+ - lib/minder/commands/delete_task.rb
181
265
  - lib/minder/config.rb
266
+ - lib/minder/database.rb
267
+ - lib/minder/database_migrator.rb
268
+ - lib/minder/filter_frame.rb
182
269
  - lib/minder/frame.rb
270
+ - lib/minder/help_frame.rb
183
271
  - lib/minder/idle_period.rb
184
272
  - lib/minder/message_frame.rb
185
273
  - lib/minder/period.rb
@@ -188,8 +276,11 @@ files:
188
276
  - lib/minder/pomodoro_runner.rb
189
277
  - lib/minder/quick_add_frame.rb
190
278
  - lib/minder/scene.rb
279
+ - lib/minder/search_frame.rb
191
280
  - lib/minder/task.rb
281
+ - lib/minder/task_mapper.rb
192
282
  - lib/minder/task_recorder.rb
283
+ - lib/minder/tasks.rb
193
284
  - lib/minder/timer.rb
194
285
  - lib/minder/version.rb
195
286
  - minder.gemspec
@@ -215,9 +306,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
215
306
  version: '0'
216
307
  required_rubygems_version: !ruby/object:Gem::Requirement
217
308
  requirements:
218
- - - ">="
309
+ - - ">"
219
310
  - !ruby/object:Gem::Version
220
- version: '0'
311
+ version: 1.3.1
221
312
  requirements: []
222
313
  rubyforge_project:
223
314
  rubygems_version: 2.4.5