dooby 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -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 *SPECIAL_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 list (what_to_show=[])
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
- list << " (#{id.red}) #{task.colorize}"
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
- when *SPECIAL_TAGS then
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
- list << tag.blue unless list.include? tag.blue
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 << " (#{id.red}) #{task.colorize}"
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(*SPECIAL_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
@@ -0,0 +1,13 @@
1
+ module Dooby
2
+ module StatusGenerator
3
+ private
4
+ def statuses(*args)
5
+ args.each do |s|
6
+ method_name = "#{s.to_s}?".to_sym
7
+ define_method method_name do
8
+ @todo.include? ":#{s.to_s}"
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,49 +1,33 @@
1
1
  module Dooby
2
2
  class Task
3
- include Formateable
3
+ extend StatusGenerator
4
4
 
5
- attr_accessor :todo, :priority, :status
5
+ attr_accessor :todo, :priority
6
+ statuses *AVAILABLE_STATUSES
6
7
 
7
- def initialize
8
- @todo ||= nil
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
- colorized_todo.gsub(/(#\w+)|(@\w+)|(%\w+)/).each do |todo|
36
- if todo.include? "@"
37
- todo.blue
38
- elsif todo.include? "%"
39
- todo.white
40
- else
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
@@ -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