dooby 0.2.0 → 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.
- data/.rspec +2 -0
- data/CHANGELOG.md +38 -0
- data/Gemfile +13 -3
- data/Gemfile.lock +24 -0
- data/README.md +109 -99
- data/Rakefile +36 -37
- data/VERSION +1 -1
- data/bin/dooby +36 -20
- data/dooby.gemspec +62 -31
- data/lib/dooby.rb +12 -13
- data/lib/dooby/cli_helper.rb +5 -2
- data/lib/dooby/config.rb +29 -0
- data/lib/dooby/core_ext.rb +15 -1
- data/lib/dooby/dates_helper.rb +23 -0
- data/lib/dooby/list.rb +60 -18
- data/lib/dooby/status_generator.rb +13 -0
- data/lib/dooby/task.rb +14 -30
- data/spec/dooby_module_spec.rb +53 -0
- data/spec/list_spec.rb +167 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/task_spec.rb +51 -0
- metadata +154 -33
- data/.gitignore +0 -21
- data/lib/dooby/formatter.rb +0 -7
data/lib/dooby/config.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Dooby
|
2
|
+
DOOBY_DIR = '.dooby'
|
3
|
+
CURRENT_TODO_LIST_FILE = "#{DOOBY_DIR}/list.yml"
|
4
|
+
|
5
|
+
## STATUSES
|
6
|
+
AVAILABLE_STATUSES = [:hold, :doing, :done]
|
7
|
+
|
8
|
+
## SPECIAL CHARS
|
9
|
+
SPECIAL_CHAR_COLORS = Hash['@', :blue, '#', :yellow, '%', :white, ':', :magenta]
|
10
|
+
SPECIAL_CHARS = SPECIAL_CHAR_COLORS.keys
|
11
|
+
|
12
|
+
## TAGS
|
13
|
+
TODAY_TAG = '#today'
|
14
|
+
TOMORROW_TAG = "#tomorrow"
|
15
|
+
URGENT_TAG = '#urgent'
|
16
|
+
|
17
|
+
SPLITTABLE_TAGS = [TODAY_TAG, URGENT_TAG]
|
18
|
+
|
19
|
+
CURRENT_ITEM_TAG = ':doing'
|
20
|
+
|
21
|
+
## DATES
|
22
|
+
DATE_FORMAT = '%b/%d/%Y'
|
23
|
+
|
24
|
+
## TEMPLATES
|
25
|
+
TASK_ROW_TEMPLATE = lambda do |task|
|
26
|
+
" (#{task.id.red}) #{task.colorize}"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/lib/dooby/core_ext.rb
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
class Object
|
2
|
+
def blank?
|
3
|
+
respond_to?(:empty?) ? empty? : !self
|
4
|
+
end
|
5
|
+
|
6
|
+
def present?
|
7
|
+
!blank?
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
1
11
|
class Array
|
2
12
|
def only_tags!(*wanted_tags)
|
3
13
|
replace(only_tags(wanted_tags))
|
@@ -5,7 +15,7 @@ class Array
|
|
5
15
|
|
6
16
|
def only_tags(*wanted_tags)
|
7
17
|
wanted_tags = '#' if wanted_tags.empty?
|
8
|
-
tags = self.grep(/[#{wanted_tags}]
|
18
|
+
tags = self.grep(/[#{wanted_tags}]./)
|
9
19
|
tags.flatten
|
10
20
|
end
|
11
21
|
|
@@ -35,6 +45,10 @@ class String
|
|
35
45
|
def only_tags(*wanted_tags)
|
36
46
|
split(' ').only_tags(*wanted_tags)
|
37
47
|
end
|
48
|
+
|
49
|
+
def first_char
|
50
|
+
self[0, 1]
|
51
|
+
end
|
38
52
|
end
|
39
53
|
|
40
54
|
class NilClass
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Dooby
|
2
|
+
module DatesHelper
|
3
|
+
def tomorrow
|
4
|
+
TOMORROW_TAG[1..-1]
|
5
|
+
end
|
6
|
+
|
7
|
+
def tomorrows_date
|
8
|
+
Chronic.parse(tomorrow).strftime(DATE_FORMAT)
|
9
|
+
end
|
10
|
+
|
11
|
+
def todays_date
|
12
|
+
Time.now.strftime(DATE_FORMAT)
|
13
|
+
end
|
14
|
+
|
15
|
+
def todays_date_tag
|
16
|
+
"{#{todays_date}}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def today_tag
|
20
|
+
"#today"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/dooby/list.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
module Dooby
|
2
2
|
|
3
3
|
class List
|
4
|
-
|
4
|
+
|
5
5
|
attr_reader :location
|
6
|
+
|
7
|
+
include DatesHelper
|
6
8
|
|
7
9
|
def initialize(location)
|
8
10
|
@location = location
|
@@ -17,8 +19,10 @@ module Dooby
|
|
17
19
|
|
18
20
|
def add(task = Task.new)
|
19
21
|
yield task if block_given?
|
22
|
+
task = handle_tomorrow_tag task
|
20
23
|
@tasks[task.id] = task
|
21
24
|
save!
|
25
|
+
task
|
22
26
|
end
|
23
27
|
|
24
28
|
def delete!(task_id)
|
@@ -30,24 +34,21 @@ module Dooby
|
|
30
34
|
end
|
31
35
|
|
32
36
|
def bulk_delete!(terms)
|
33
|
-
only_tags = terms.only_tags *
|
37
|
+
only_tags = terms.only_tags *SPECIAL_CHARS
|
34
38
|
matches = []
|
35
39
|
@tasks.each do |id, task|
|
36
40
|
delete! id if only_tags.all? { |tag| task.todo.include? tag }
|
37
|
-
end
|
41
|
+
end unless only_tags.empty?
|
38
42
|
end
|
39
43
|
|
40
44
|
def edit!(task_id)
|
41
45
|
old_task = @tasks[task_id]
|
42
|
-
t = Task.new
|
43
46
|
if old_task
|
47
|
+
t = Task.new
|
44
48
|
yield t
|
49
|
+
delete! task_id
|
50
|
+
add t
|
45
51
|
end
|
46
|
-
|
47
|
-
t.status = old_task.status
|
48
|
-
|
49
|
-
delete! task_id
|
50
|
-
add t
|
51
52
|
end
|
52
53
|
|
53
54
|
def tasks?
|
@@ -58,27 +59,52 @@ module Dooby
|
|
58
59
|
@tasks.dup
|
59
60
|
end
|
60
61
|
|
61
|
-
def
|
62
|
+
def find (what_to_show=[])
|
62
63
|
list = []
|
63
|
-
|
64
|
+
|
64
65
|
if @tasks.empty?
|
65
66
|
list = nil
|
66
67
|
else
|
67
68
|
case what_to_show
|
68
69
|
when [] then
|
70
|
+
to_delete = []
|
71
|
+
to_add = []
|
69
72
|
@tasks.each do |id, task|
|
70
|
-
|
73
|
+
|
74
|
+
#if item has {today's date} replace it with #today
|
75
|
+
if task.todo.include? todays_date_tag
|
76
|
+
task_with_today_tag = task.dup
|
77
|
+
task_with_today_tag.todo.gsub!(todays_date_tag, TODAY_TAG)
|
78
|
+
to_add << task_with_today_tag
|
79
|
+
to_delete << id
|
80
|
+
else
|
81
|
+
list << TASK_ROW_TEMPLATE.call(task)
|
82
|
+
end
|
71
83
|
end
|
72
|
-
|
84
|
+
|
85
|
+
to_delete.each { |task_id| delete!(task_id) }
|
86
|
+
to_add.each do |task|
|
87
|
+
add task
|
88
|
+
list << TASK_ROW_TEMPLATE.call(task)
|
89
|
+
end
|
90
|
+
|
91
|
+
when *SPECIAL_CHARS then
|
73
92
|
@tasks.each do |id, task|
|
74
93
|
task.todo.gsub(/(#{what_to_show}\w+)/).each do |tag|
|
75
|
-
|
94
|
+
color = SPECIAL_CHAR_COLORS[tag.first_char]
|
95
|
+
colorized = tag.send color
|
96
|
+
list << colorized unless list.include? colorized
|
76
97
|
end
|
77
98
|
end
|
78
|
-
else
|
99
|
+
else #user has enter search terms
|
100
|
+
if what_to_show.any? { |s| s.include? tomorrow }
|
101
|
+
what_to_show << tomorrows_date
|
102
|
+
what_to_show.delete(tomorrow)
|
103
|
+
what_to_show.delete(TOMORROW_TAG)
|
104
|
+
end
|
79
105
|
@tasks.each do |id, task|
|
80
106
|
if what_to_show.all? { |term| task.todo.include? term }
|
81
|
-
list <<
|
107
|
+
list << TASK_ROW_TEMPLATE.call(task)
|
82
108
|
end
|
83
109
|
end
|
84
110
|
|
@@ -92,12 +118,28 @@ module Dooby
|
|
92
118
|
def all_tags
|
93
119
|
tags = []
|
94
120
|
@tasks.each do |id, task|
|
95
|
-
tags << task.todo.only_tags(*
|
121
|
+
tags << task.todo.only_tags(*SPECIAL_CHARS)
|
96
122
|
end
|
97
123
|
tags.flatten
|
98
124
|
end
|
99
125
|
|
126
|
+
def current_item
|
127
|
+
current = tasks.select do |id, task|
|
128
|
+
task.todo.include? CURRENT_ITEM_TAG
|
129
|
+
end
|
130
|
+
current.flatten.last.todo.gsub(CURRENT_ITEM_TAG,'').strip unless current.blank?
|
131
|
+
end
|
132
|
+
|
100
133
|
private
|
134
|
+
def handle_tomorrow_tag(task)
|
135
|
+
if task.todo.include? TOMORROW_TAG
|
136
|
+
task.todo.gsub!(/\{[\w\/]+\}/, '')
|
137
|
+
task.todo.gsub!(TOMORROW_TAG, "{#{tomorrows_date}}")
|
138
|
+
task.todo.gsub!(/#{TOMORROW_TAG}/, '')
|
139
|
+
end
|
140
|
+
task
|
141
|
+
end
|
142
|
+
|
101
143
|
def save!
|
102
144
|
File.open( @location, 'w' ) do |f|
|
103
145
|
f << @tasks.to_yaml
|
@@ -117,7 +159,7 @@ module Dooby
|
|
117
159
|
puts "Todo list file is invalid or do not exist."
|
118
160
|
end
|
119
161
|
end
|
120
|
-
|
162
|
+
|
121
163
|
end
|
122
164
|
|
123
165
|
end
|
data/lib/dooby/task.rb
CHANGED
@@ -1,49 +1,33 @@
|
|
1
1
|
module Dooby
|
2
2
|
class Task
|
3
|
-
|
3
|
+
extend StatusGenerator
|
4
4
|
|
5
|
-
attr_accessor :todo, :priority
|
5
|
+
attr_accessor :todo, :priority
|
6
|
+
statuses *AVAILABLE_STATUSES
|
6
7
|
|
7
|
-
def initialize
|
8
|
-
@todo
|
9
|
-
@status ||= DEFAULT_STATUS
|
8
|
+
def initialize (todo = nil)
|
9
|
+
@todo = todo
|
10
10
|
end
|
11
11
|
|
12
12
|
def id
|
13
13
|
@todo ? Digest::SHA1.hexdigest(@todo)[0,6] : nil
|
14
14
|
end
|
15
15
|
|
16
|
-
def valid?
|
16
|
+
def valid?
|
17
17
|
@todo.nil? || @todo == '' ? false : true
|
18
18
|
end
|
19
|
-
|
20
|
-
def doing!
|
21
|
-
@status = :doing
|
22
|
-
end
|
23
|
-
|
24
|
-
def done!
|
25
|
-
@status = :done
|
26
|
-
end
|
27
|
-
|
28
|
-
def hold!
|
29
|
-
@status = :hold
|
30
|
-
end
|
31
|
-
|
19
|
+
|
32
20
|
def colorize
|
33
21
|
colorized_todo = @todo.dup
|
34
22
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
todo.yellow
|
42
|
-
end
|
23
|
+
string_pattern = SPECIAL_CHARS.collect { |c| "(#{c}\\w+)" }
|
24
|
+
pattern = Regexp.new(string_pattern.join("|"))
|
25
|
+
|
26
|
+
colorized_todo.gsub(pattern).each do |todo|
|
27
|
+
color = SPECIAL_CHAR_COLORS[todo.first_char]
|
28
|
+
todo.send color
|
43
29
|
end
|
44
30
|
|
45
|
-
end
|
46
|
-
|
31
|
+
end
|
47
32
|
end
|
48
|
-
|
49
33
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Dooby
|
4
|
+
describe Dooby do
|
5
|
+
describe '#init' do
|
6
|
+
it "should create a .dooby dir if it doesn't exists" do
|
7
|
+
#stubs
|
8
|
+
File.stub(:exist?).and_return false
|
9
|
+
FileUtils.stub!(:mkdir).and_return true
|
10
|
+
File.stub(:new).and_return true
|
11
|
+
true.stub(:close).and_return true
|
12
|
+
|
13
|
+
#expectations
|
14
|
+
File.should_receive(:exist?).with(CURRENT_TODO_LIST_FILE)
|
15
|
+
FileUtils.should_receive(:mkdir).with(DOOBY_DIR).and_return true
|
16
|
+
File.should_receive(:new).with(CURRENT_TODO_LIST_FILE, "w+").and_return true
|
17
|
+
|
18
|
+
Dooby.init
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should return false if .dooby/list.yml exists" do
|
22
|
+
File.stub(:exist?).and_return true
|
23
|
+
Dooby.init.should == false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#trash!' do
|
28
|
+
it "should delete the .dooby directory" do
|
29
|
+
FileUtils.stub(:remove_dir).and_return true
|
30
|
+
|
31
|
+
FileUtils.should_receive(:remove_dir).with(DOOBY_DIR, force = true)
|
32
|
+
|
33
|
+
Dooby.trash!
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#current_list' do
|
38
|
+
it "should return an instance of List class" do
|
39
|
+
list = mock('list')
|
40
|
+
List.stub(:new).and_return list
|
41
|
+
List.should_receive(:new).with(CURRENT_TODO_LIST_FILE).and_return list
|
42
|
+
|
43
|
+
Dooby.current_list.should == list
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#cli_helper' do
|
48
|
+
it "should return the CLIHelper class" do
|
49
|
+
Dooby.cli_helper.should == CLIHelper
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/spec/list_spec.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Dooby
|
4
|
+
describe List do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
@location = 'location'
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#new' do
|
11
|
+
|
12
|
+
it "should load the list of items if list.yml exists" do
|
13
|
+
File.stub(:exist?).and_return true
|
14
|
+
YAML.stub(:load_file).and_return fake_tasks
|
15
|
+
|
16
|
+
File.should_receive(:exist?).with(CURRENT_TODO_LIST_FILE).and_return true
|
17
|
+
YAML.should_receive(:load_file).with(@location).and_return fake_tasks
|
18
|
+
|
19
|
+
List.new @location
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#flush' do
|
24
|
+
it 'should empty the list of items and clear the list.yml file' do
|
25
|
+
File.stub(:exist?).and_return true
|
26
|
+
YAML.stub(:load_file).and_return fake_tasks
|
27
|
+
File.stub(:open).and_return true
|
28
|
+
|
29
|
+
File.should_receive(:open).with(@location, 'w').and_return true
|
30
|
+
|
31
|
+
list = List.new @location
|
32
|
+
list.flush!
|
33
|
+
list.tasks.should be_empty
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'CRUD' do
|
38
|
+
before(:all) do
|
39
|
+
File.stub(:exist?).and_return true
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#add' do
|
43
|
+
it "should create an item" do
|
44
|
+
YAML.stub(:load_file).and_return {}
|
45
|
+
|
46
|
+
list = List.new @location
|
47
|
+
list.stub(:save!).and_return true
|
48
|
+
task = Task.new fake_todo_text
|
49
|
+
|
50
|
+
list.should_receive(:save!).and_return true
|
51
|
+
list.add task
|
52
|
+
|
53
|
+
list.tasks.should_not be_empty
|
54
|
+
list.tasks[task.id].todo.should == fake_todo_text
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#delete!' do
|
59
|
+
it "should delete an item" do
|
60
|
+
YAML.stub(:load_file).and_return fake_tasks
|
61
|
+
|
62
|
+
list = List.new @location
|
63
|
+
list.should_receive(:save!).and_return true
|
64
|
+
|
65
|
+
task_to_delete = list.tasks.first
|
66
|
+
|
67
|
+
list.delete! task_to_delete.first
|
68
|
+
|
69
|
+
list.tasks.should_not include(task_to_delete.first)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '#bulk_delete!' do
|
74
|
+
before(:each) do
|
75
|
+
YAML.stub(:load_file).and_return fake_tasks
|
76
|
+
@list = List.new @location
|
77
|
+
@list.stub(:save!).and_return true
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should delete all the items containing a keyword" do
|
81
|
+
@list.should_receive(:save!).and_return true
|
82
|
+
@list.bulk_delete! ['#context']
|
83
|
+
@list.tasks.should be_empty
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should delete all the items containing a set of keywords" do
|
87
|
+
@list.should_receive(:save!).and_return true
|
88
|
+
@list.bulk_delete! %w[#context @person %project]
|
89
|
+
@list.tasks.should be_empty
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should not delete anything if the argument does not contain any tags' do
|
93
|
+
old_tasks = @list.tasks
|
94
|
+
@list.bulk_delete! %w[# @ %]
|
95
|
+
@list.tasks.should == old_tasks
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should not delete anything if the argument is empty' do
|
99
|
+
old_tasks = @list.tasks
|
100
|
+
@list.bulk_delete! []
|
101
|
+
@list.tasks.should == old_tasks
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
describe '#edit!' do
|
107
|
+
it 'should delete the edited task and add a new one' do
|
108
|
+
YAML.stub(:load_file).and_return fake_tasks
|
109
|
+
list = List.new @location
|
110
|
+
list.stub(:save!).and_return true
|
111
|
+
|
112
|
+
list.should_receive(:save!).twice
|
113
|
+
|
114
|
+
task_to_edit = list.tasks.first
|
115
|
+
task_id = task_to_edit.first
|
116
|
+
|
117
|
+
new_todo = 'this is the new text'
|
118
|
+
|
119
|
+
new_task = list.edit! task_id do |task|
|
120
|
+
task.todo = new_todo
|
121
|
+
end
|
122
|
+
|
123
|
+
list.tasks.should_not include task_id
|
124
|
+
list.tasks.should include new_task.id
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe '#tasks?' do
|
129
|
+
it 'should return true if there are tasks' do
|
130
|
+
YAML.stub(:load_file).and_return fake_tasks
|
131
|
+
list = List.new @location
|
132
|
+
|
133
|
+
list.tasks?.should be_true
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should return false if there aren't tasks" do
|
137
|
+
YAML.stub(:load_file).and_return {}
|
138
|
+
list = List.new @location
|
139
|
+
|
140
|
+
list.tasks?.should be_false
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe '#find' do
|
145
|
+
describe 'with empty array as argument' do
|
146
|
+
it 'should return a list of all the items'
|
147
|
+
end
|
148
|
+
|
149
|
+
describe 'with special tags as argument' do
|
150
|
+
it 'should return only the specified tags'
|
151
|
+
end
|
152
|
+
|
153
|
+
describe 'with an array of terms as argument' do
|
154
|
+
it 'should return only the items containing all the specified tags'
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe '#all_tags' do
|
159
|
+
it 'should return only the tags of all the items'
|
160
|
+
end
|
161
|
+
|
162
|
+
describe '#current_item' do
|
163
|
+
it 'should return the item tagged with :doing'
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|