minder 0.1 → 0.2.beta1

Sign up to get free protection for your applications and to get access to all the features.
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