todo-txt 0.9 → 0.10

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: 04e209879cbf8434759e4343c03bdbb2b48a7e3f
4
- data.tar.gz: bad9b8396a6d32ef8eb8f647394de850d5c0a658
3
+ metadata.gz: 0e6194a6c093149543776903fec449488ff35e12
4
+ data.tar.gz: 1ff563eca42c38f95a55eda4732f4c43005a2079
5
5
  SHA512:
6
- metadata.gz: 7d5599684ab23946adde905819a231f75d38029780c49e0771acd0258a6622fd88d5edd1c0e5ff2d6415b1e359047b7b6fbef8cd684c701ce94fdafa972d2b31
7
- data.tar.gz: 9b7c601dad7b1a07541fea804b06a9c12490406501a7173b4e2e0dfaf26d5da320a7605960f4e8b9d5ba5f66d10dac3892c50bee9e2676d95fbb8df295f7c014
6
+ metadata.gz: 0d614c4914dfc5e7e84addd6185f79c52270a4196003be1bef3f88452063e4d549062855d3229b92413dfe1f9b0f896ff6fe12cc7b64786767300aa9ff430a56
7
+ data.tar.gz: 0b8307a2946e632a5a68fb1b03b1df5d2fcbf56317087ebac52e7d3317b742ba8e9a5f7e31de9ff8723683e940a50d2d11bd39e58a4b806265a05180f6fa123e
data/.rubocop.yml CHANGED
@@ -1,2 +1,5 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'lib/todo-txt.rb'
1
4
  StringLiterals:
2
5
  Enabled: false
data/.travis.yml CHANGED
@@ -1,6 +1,5 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.0
4
3
  - 2.1
5
4
  - 2.2
6
5
  - 2.3.0
data/Gemfile CHANGED
@@ -4,7 +4,7 @@ group :test do
4
4
  gem 'rspec', '~> 3.4'
5
5
  gem 'rake'
6
6
  gem 'timecop'
7
- gem 'simplecov', :require => false
7
+ gem 'simplecov', require: false
8
8
  end
9
9
 
10
10
  group :development do
data/Rakefile CHANGED
@@ -2,7 +2,7 @@ require 'bundler/setup'
2
2
  require 'bundler/gem_tasks'
3
3
  require 'rspec/core/rake_task'
4
4
 
5
- task :default => [:test]
5
+ task default: :test
6
6
 
7
7
  desc "Run all tests"
8
8
  RSpec::Core::RakeTask.new(:test)
data/lib/todo-txt.rb CHANGED
@@ -1,6 +1 @@
1
- require 'logger'
2
- require 'todo-txt/logger'
3
- require 'todo-txt/options'
4
- require 'todo-txt/syntax'
5
- require 'todo-txt/list'
6
- require 'todo-txt/task'
1
+ require_relative 'todo'
data/lib/todo.rb ADDED
@@ -0,0 +1,38 @@
1
+ require 'logger'
2
+ require 'todo/logger'
3
+ require 'todo/options'
4
+ require 'todo/syntax'
5
+ require 'todo/list'
6
+ require 'todo/task'
7
+ require 'todo/file'
8
+
9
+ # Allows for easy management of task lists and tasks in the todo.txt format.
10
+ module Todo
11
+ # Provides global options for customizing todo list behaviour.
12
+ class << self
13
+ attr_accessor :options_instance
14
+ end
15
+
16
+ # Global access to custom options.
17
+ #
18
+ # Example:
19
+ #
20
+ # if Todo.options.require_completed_on
21
+ # # Do something
22
+ # end
23
+ def self.options
24
+ self.options_instance ||= Options.new
25
+ end
26
+
27
+ # Customize global list behaviour.
28
+ #
29
+ # Example:
30
+ #
31
+ # Todo.customize do |opts|
32
+ # opts.require_completed_on = false
33
+ # end
34
+ def self.customize
35
+ self.options_instance ||= Options.new
36
+ yield(options_instance)
37
+ end
38
+ end
data/lib/todo/file.rb ADDED
@@ -0,0 +1,83 @@
1
+ module Todo
2
+ # A high level wrapper around the Ruby File interface which supports reading
3
+ # from and writing to an IO handle with Todo::Task objects.
4
+ class File
5
+ # Open a list file handle and pass it to the given block. The file is
6
+ # automatically closed when the block returns.
7
+ #
8
+ # The file is opened in read-only mode by default.
9
+ #
10
+ # Todo::File.open("~/Dropbox/todo/todo.txt") do |file|
11
+ # file.each_task do |task|
12
+ # puts task.done?
13
+ # end
14
+ # end
15
+ #
16
+ # @param [String, Pathname] path
17
+ # @param [String] mode
18
+ def self.open(path, mode = 'r')
19
+ ios = new(path, mode)
20
+
21
+ if block_given?
22
+ yield ios
23
+ return ios.close
24
+ end
25
+
26
+ ios
27
+ end
28
+
29
+ # @param [String, Pathname] path
30
+ def self.read(path)
31
+ list = []
32
+
33
+ open(path) do |file|
34
+ file.each_task do |task|
35
+ list << task
36
+ end
37
+ end
38
+
39
+ list
40
+ end
41
+
42
+ # @param [String, Pathname] path
43
+ # @param [Array, Todo::List] list
44
+ def self.write(path, list)
45
+ open(path, 'w') do |file|
46
+ list.each do |task|
47
+ file.puts(task)
48
+ end
49
+ end
50
+ end
51
+
52
+ # @param [String, Pathname] path
53
+ # @param [String] mode
54
+ def initialize(path, mode = 'r')
55
+ @ios = ::File.open(path, mode)
56
+ end
57
+
58
+ # Executes the block for every task in the list.
59
+ def each
60
+ return enum_for(:each) unless block_given?
61
+
62
+ @ios.each_line do |line|
63
+ yield Task.new(line)
64
+ end
65
+ end
66
+
67
+ alias each_task each
68
+
69
+ # Writes the given tasks to the underlying IO handle.
70
+ #
71
+ # @overload puts(task, ...)
72
+ # @param [Todo::Task] task
73
+ # @param [Todo::Task] ...
74
+ def puts(*tasks)
75
+ @ios.puts(tasks.map(&:to_s))
76
+ end
77
+
78
+ # Closes the IO handle and flushes any pending writes.
79
+ def close
80
+ @ios.close
81
+ end
82
+ end
83
+ end
data/lib/todo/list.rb ADDED
@@ -0,0 +1,127 @@
1
+ module Todo
2
+ # Initializes a Todo List object with a path to the corresponding todo.txt
3
+ # file. For example, if your todo.txt file is located at:
4
+ #
5
+ # /home/sam/Dropbox/todo/todo.txt
6
+ #
7
+ # You would initialize the list object like:
8
+ #
9
+ # list = Todo::List.new("/home/sam/Dropbox/todo/todo.txt")
10
+ #
11
+ # Alternately, you can initialize the object with an array of strings or
12
+ # tasks. If the array is of strings, the strings will be converted into
13
+ # tasks. You can supply a mixed list of string and tasks if you wish.
14
+ #
15
+ # Example:
16
+ #
17
+ # tasks = []
18
+ # tasks << "A task line"
19
+ # tasks << Todo::Task.new("A task object")
20
+ #
21
+ # list = Todo::List.new(tasks)
22
+ class List < Array
23
+ def initialize(list)
24
+ case list
25
+ when Array
26
+ tasks = list.map do |item|
27
+ case item
28
+ when String then Task.new(item)
29
+ when Task then item
30
+ else
31
+ raise "Cannot add #{item.class} to list."
32
+ end
33
+ end
34
+
35
+ concat(tasks)
36
+
37
+ when String
38
+ @path = list
39
+ concat(File.read(list))
40
+ end
41
+ end
42
+
43
+ # The path to the todo.txt file that you supplied when you created the
44
+ # Todo::List object.
45
+ attr_reader :path
46
+
47
+ # Filters the list by priority and returns a new list.
48
+ #
49
+ # Example:
50
+ #
51
+ # list = Todo::List.new("/path/to/list")
52
+ # list.by_priority("A")
53
+ # # => Will be a new list with only priority 'A' tasks
54
+ #
55
+ # @param priority [String]
56
+ # @return [Todo::List]
57
+ def by_priority(priority)
58
+ List.new(select { |task| task.priority == priority })
59
+ end
60
+
61
+ # Filters the list by context and returns a new list.
62
+ #
63
+ # Example:
64
+ #
65
+ # list = Todo::List.new("/path/to/list")
66
+ # list.by_context("@admin")
67
+ # # => <Todo::List> filtered by '@admin'
68
+ #
69
+ # @param context [String]
70
+ # @return [Todo::List]
71
+ def by_context(context)
72
+ List.new(select { |task| task.contexts.include? context })
73
+ end
74
+
75
+ # Filters the list by project and returns a new list.
76
+ #
77
+ # Example:
78
+ #
79
+ # list = Todo::List.new("/path/to/list")
80
+ # list.by_project("+blog")
81
+ # # => <Todo::List> filtered by '+blog'
82
+ #
83
+ # @param project [String]
84
+ # @return [Todo::List]
85
+ def by_project(project)
86
+ List.new(select { |task| task.projects.include? project })
87
+ end
88
+
89
+ # Filters the list by completed tasks and returns a new list.
90
+ #
91
+ # Example:
92
+ #
93
+ # list = Todo::List.new("/path/to/list")
94
+ # list.by_done
95
+ # # => <Todo::List> filtered by tasks that are done
96
+ #
97
+ # @return [Todo::List]
98
+ def by_done
99
+ List.new(select(&:done?))
100
+ end
101
+
102
+ # Filters the list by incomplete tasks and returns a new list.
103
+ #
104
+ # Example:
105
+ #
106
+ # list = Todo::List.new("/path/to/list")
107
+ # list.by_not_done
108
+ # # => <Todo::List> filtered by tasks that are not done
109
+ #
110
+ # @return [Todo::List]
111
+ def by_not_done
112
+ List.new(select { |task| task.done? == false })
113
+ end
114
+
115
+ # Saves the list to the original file location.
116
+ #
117
+ # Warning: This is a destructive operation and will overwrite any existing
118
+ # content in the file. It does not attempt to diff and append changes.
119
+ #
120
+ # If no `path` is specified in the constructor then an error is raised.
121
+ def save!
122
+ raise "No path specified." unless path
123
+
124
+ File.write(path, self)
125
+ end
126
+ end
127
+ end
File without changes
@@ -1,17 +1,4 @@
1
1
  module Todo
2
- class << self
3
- attr_accessor :options_instance
4
- end
5
-
6
- def self.options
7
- self.options_instance ||= Options.new
8
- end
9
-
10
- def self.customize
11
- self.options_instance ||= Options.new
12
- yield(options_instance)
13
- end
14
-
15
2
  # Options for default preferences and library settings that can be customized
16
3
  # by clients of the gem.
17
4
  class Options
@@ -1,5 +1,5 @@
1
1
  module Todo
2
- # Parsing and extraction of the core syntax fragments in todo.txt lines.
2
+ # Supports parsing and extracting core syntax fragments from todo.txt lines.
3
3
  module Syntax
4
4
  # The regex used to match contexts.
5
5
  CONTEXTS_PATTERN = /(?:\s+|^)@[^\s]+/
@@ -19,6 +19,9 @@ module Todo
19
19
  # The regex used to match due date.
20
20
  DUE_ON_PATTERN = /(?:due:)(\d{4}-\d{2}-\d{2})(?:\s+|$)/i
21
21
 
22
+ # The regex used to match generic tags.
23
+ TAGS_PATTERN = /([a-z]+):([a-z0-9_-]+)/i
24
+
22
25
  # Extracts the readable text content of a task line, stripping out all the
23
26
  # discrete pieces of metadata (priority, dates, completion flag, projects,
24
27
  # contexts, etc).
@@ -76,8 +79,8 @@ module Todo
76
79
 
77
80
  # Checks whether the given todo item has a completion flag set.
78
81
  #
79
- # This provides support for ad-hoc handwritten lists where the completed flag
80
- # is set but there is no completed date.
82
+ # This provides support for ad-hoc handwritten lists where the completed
83
+ # flag is set but there is no completed date.
81
84
  #
82
85
  # @param line [String] the todo item to be processed
83
86
  # @return [Boolean]
@@ -85,18 +88,13 @@ module Todo
85
88
  line[0] == COMPLETED_FLAG && line[1] == SINGLE_SPACE
86
89
  end
87
90
 
88
- # Extracts the completion date for the given todo item.
89
- # Returns nil if a valid date is not found.
91
+ # Extracts the collection of tag annotations for the given todo item.
92
+ # Returns an empty hash if no tags are found.
90
93
  #
91
94
  # @param line [String] the todo item to be processed
92
- # @return [Date] the due date of the line
93
- def extract_due_on_date(line)
94
- date = DUE_ON_PATTERN.match(line)
95
- begin
96
- Date.parse(date[1]) if date
97
- rescue ArgumentError
98
- return nil # The given date is not valid
99
- end
95
+ # @return [Hash<Symbol,String>] the collection of tag annotations
96
+ def extract_tags(line)
97
+ line.scan(TAGS_PATTERN).map { |tag, val| [tag.downcase.to_sym, val] }.to_h
100
98
  end
101
99
 
102
100
  # Extract the list of `@context` tags out of the task line.
@@ -12,36 +12,37 @@ module Todo
12
12
  include Todo::Logger
13
13
  include Todo::Syntax
14
14
 
15
- def initialize(task)
16
- @orig = task
17
- @priority = extract_priority(orig)
18
- @created_on = extract_created_on(orig)
19
- @due_on = extract_due_on_date(orig)
20
- @contexts ||= extract_contexts(orig)
21
- @projects ||= extract_projects(orig)
15
+ def initialize(line)
16
+ @raw = line
17
+ @priority = extract_priority(raw)
18
+ @created_on = extract_created_on(raw)
19
+ @tags = extract_tags(raw)
20
+ @contexts ||= extract_contexts(raw)
21
+ @projects ||= extract_projects(raw)
22
22
 
23
23
  if Todo.options.require_completed_on
24
- @completed_on = extract_completed_date(orig)
24
+ @completed_on = extract_completed_date(raw)
25
25
  @is_completed = !@completed_on.nil?
26
26
  else
27
- @completed_on = extract_completed_date(orig)
28
- @is_completed = check_completed_flag(orig)
27
+ @completed_on = extract_completed_date(raw)
28
+ @is_completed = check_completed_flag(raw)
29
29
  end
30
30
  end
31
31
 
32
- # Returns the original content of the task.
32
+ # Returns the raw content of the original task line.
33
33
  #
34
34
  # Example:
35
35
  #
36
- # task = Todo::Task.new "(A) @context +project Hello!"
37
- # task.orig #=> "(A) @context +project Hello!"
38
- attr_reader :orig
36
+ # task = Todo::Task.new("(A) @context +project Hello!")
37
+ # task.raw
38
+ # # => "(A) @context +project Hello!"
39
+ attr_reader :raw
39
40
 
40
41
  # Returns the task's creation date, if any.
41
42
  #
42
43
  # Example:
43
44
  #
44
- # task = Todo::Task.new "(A) 2012-03-04 Task."
45
+ # task = Todo::Task.new("(A) 2012-03-04 Task.")
45
46
  # task.created_on
46
47
  # #=> <Date: 2012-03-04 (4911981/2,0,2299161)>
47
48
  #
@@ -54,53 +55,57 @@ module Todo
54
55
  #
55
56
  # Example:
56
57
  #
57
- # task = Todo::Task.new "x 2012-03-04 Task."
58
+ # task = Todo::Task.new("x 2012-03-04 Task.")
58
59
  # task.completed_on
59
- # #=> <Date: 2012-03-04 (4911981/2,0,2299161)>
60
+ # # => <Date: 2012-03-04 (4911981/2,0,2299161)>
60
61
  #
61
62
  # Dates _must_ be in the YYYY-MM-DD format as specified in the todo.txt
62
63
  # format. Dates in any other format will be classed as malformed and this
63
64
  # attribute will be nil.
64
65
  attr_reader :completed_on
65
66
 
66
- # Returns the task's due date, if any.
67
+ # Returns tag annotations embedded in the list item.
67
68
  #
68
69
  # Example:
69
70
  #
70
- # task = Todo::Task.new "(A) This is a task. due:2012-03-04"
71
- # task.due_on
72
- # #=> <Date: 2012-03-04 (4911981/2,0,2299161)>
71
+ # task = Todo::Task.new("Some task. due:2016-06-16 hello:world")
72
+ # task.tags
73
+ # # => { :due => '2016-06-16', :hello => 'world' }
73
74
  #
74
- # Dates _must_ be in the YYYY-MM-DD format as specified in the todo.txt
75
- # format. Dates in any other format will be classed as malformed and this
76
- # attribute will be nil.
77
- attr_reader :due_on
75
+ # task = Todo::Task.new("Some task.")
76
+ # task.tags.empty?
77
+ # # => true
78
+ attr_reader :tags
78
79
 
79
80
  # Returns the priority, if any.
80
81
  #
81
82
  # Example:
82
83
  #
83
- # task = Todo::Task.new "(A) Some task."
84
- # task.priority #=> "A"
84
+ # task = Todo::Task.new("(A) Some task.")
85
+ # task.priority
86
+ # # => "A"
85
87
  #
86
88
  # task = Todo::Task.new "Some task."
87
- # task.priority #=> nil
89
+ # task.priority
90
+ # # => nil
88
91
  attr_reader :priority
89
92
 
90
93
  # Returns an array of all the @context annotations.
91
94
  #
92
95
  # Example:
93
96
  #
94
- # task = Todo:Task.new "(A) @context Testing!"
95
- # task.context #=> ["@context"]
97
+ # task = Todo:Task.new("(A) @context Testing!")
98
+ # task.context
99
+ # # => ["@context"]
96
100
  attr_reader :contexts
97
101
 
98
102
  # Returns an array of all the +project annotations.
99
103
  #
100
104
  # Example:
101
105
  #
102
- # task = Todo:Task.new "(A) +test Testing!"
103
- # task.projects #=> ["+test"]
106
+ # task = Todo:Task.new("(A) +test Testing!")
107
+ # task.projects
108
+ # # => ["+test"]
104
109
  attr_reader :projects
105
110
 
106
111
  # Gets just the text content of the todo, without the priority, contexts
@@ -108,29 +113,44 @@ module Todo
108
113
  #
109
114
  # Example:
110
115
  #
111
- # task = Todo::Task.new "(A) @test Testing!"
112
- # task.text #=> "Testing!"
116
+ # task = Todo::Task.new("(A) @test Testing!")
117
+ # task.text
118
+ # # => "Testing!"
113
119
  def text
114
- @text ||= extract_item_text(orig)
120
+ @text ||= extract_item_text(raw)
115
121
  end
116
122
 
117
- # Returns the task's creation date, if any.
123
+ # Deprecated. See: #created_on
124
+ def date
125
+ logger.warn("`Task#date` is deprecated, use `Task#created_on` instead.")
126
+
127
+ @created_on
128
+ end
129
+
130
+ # Deprecated. See: #raw
131
+ def orig
132
+ logger.warn("`Task#orig` is deprecated, use `Task#raw` instead.")
133
+
134
+ raw
135
+ end
136
+
137
+ # Returns the task's due date, if any.
118
138
  #
119
139
  # Example:
120
140
  #
121
- # task = Todo::Task.new "(A) 2012-03-04 Task."
122
- # task.date
123
- # #=> <Date: 2012-03-04 (4911981/2,0,2299161)>
141
+ # task = Todo::Task.new("(A) This is a task. due:2012-03-04")
142
+ # task.due_on
143
+ # # => <Date: 2012-03-04 (4911981/2,0,2299161)>
124
144
  #
125
145
  # Dates _must_ be in the YYYY-MM-DD format as specified in the todo.txt
126
146
  # format. Dates in any other format will be classed as malformed and this
127
- # method will return nil.
128
- #
129
- # Deprecated
130
- def date
131
- logger.warn("Task#date is deprecated, use created_on instead.")
132
-
133
- @created_on
147
+ # attribute will be nil.
148
+ def due_on
149
+ begin
150
+ Date.parse(tags[:due]) if tags[:due] =~ /(\d{4}-\d{2}-\d{2})/
151
+ rescue ArgumentError
152
+ return nil
153
+ end
134
154
  end
135
155
 
136
156
  # Returns whether a task's due date is in the past.
@@ -139,22 +159,22 @@ module Todo
139
159
  #
140
160
  # task = Todo::Task.new("This task is overdue! due:#{Date.today - 1}")
141
161
  # task.overdue?
142
- # #=> true
162
+ # # => true
143
163
  def overdue?
144
164
  !due_on.nil? && due_on < Date.today
145
165
  end
146
166
 
147
- # Returns if the task is done.
167
+ # Returns true if the task is completed.
148
168
  #
149
169
  # Example:
150
170
  #
151
- # task = Todo::Task.new "x 2012-12-08 Task."
171
+ # task = Todo::Task.new("x 2012-12-08 Task.")
152
172
  # task.done?
153
- # #=> true
173
+ # # => true
154
174
  #
155
- # task = Todo::Task.new "Task."
175
+ # task = Todo::Task.new("Task.")
156
176
  # task.done?
157
- # #=> false
177
+ # # => false
158
178
  def done?
159
179
  @is_completed
160
180
  end
@@ -163,17 +183,16 @@ module Todo
163
183
  #
164
184
  # Example:
165
185
  #
166
- # task = Todo::Task.new "2012-12-08 Task."
186
+ # task = Todo::Task.new("2012-12-08 Task.")
187
+ #
167
188
  # task.done?
168
- # #=> false
189
+ # # => false
169
190
  #
191
+ # # Complete the task
170
192
  # task.do!
193
+ #
171
194
  # task.done?
172
- # #=> true
173
- # task.created_on
174
- # #=> <Date: 2012-12-08 (4911981/2,0,2299161)>
175
- # task.completed_on
176
- # #=> # the current date
195
+ # # => true
177
196
  def do!
178
197
  @completed_on = Date.today
179
198
  @is_completed = true
@@ -184,21 +203,19 @@ module Todo
184
203
  #
185
204
  # Example:
186
205
  #
187
- # task = Todo::Task.new "x 2012-12-08 2012-03-04 Task."
206
+ # task = Todo::Task.new("x 2012-12-08 2012-03-04 Task.")
188
207
  # task.done?
189
- # #=> true
208
+ # # => true
190
209
  #
210
+ # # Undo the completed task
191
211
  # task.undo!
212
+ #
192
213
  # task.done?
193
- # #=> false
194
- # task.created_on
195
- # #=> <Date: 2012-03-04 (4911981/2,0,2299161)>
196
- # task.completed_on
197
- # #=> nil
214
+ # # => false
198
215
  def undo!
199
216
  @completed_on = nil
200
217
  @is_completed = false
201
- @priority = extract_priority(orig)
218
+ @priority = extract_priority(raw)
202
219
  end
203
220
 
204
221
  # Increases the priority until A. If it's nil, it sets it to A.
@@ -225,17 +242,20 @@ module Todo
225
242
  #
226
243
  # Example:
227
244
  #
228
- # task = Todo::Task.new "x 2012-12-08 Task."
245
+ # task = Todo::Task.new("x 2012-12-08 Task.")
229
246
  # task.done?
230
- # #=> true
247
+ # # => true
231
248
  #
249
+ # # Toggle between complete and incomplete
232
250
  # task.toggle!
251
+ #
233
252
  # task.done?
234
- # #=> false
253
+ # # => false
235
254
  #
236
255
  # task.toggle!
256
+ #
237
257
  # task.done?
238
- # #=> true
258
+ # # => true
239
259
  def toggle!
240
260
  done? ? undo! : do!
241
261
  end
@@ -244,25 +264,27 @@ module Todo
244
264
  #
245
265
  # Example:
246
266
  #
247
- # task = Todo::Task.new "(A) 2012-12-08 Task"
267
+ # task = Todo::Task.new("(A) 2012-12-08 Task")
248
268
  # task.to_s
249
- # #=> "(A) 2012-12-08 Task"
269
+ # # => "(A) 2012-12-08 Task"
250
270
  def to_s
251
- priority_string = priority ? "(#{priority}) " : ""
252
- done_string = done? ? "x #{completed_on} " : ""
253
- created_on_string = created_on ? "#{created_on} " : ""
254
- contexts_string = contexts.empty? ? "" : " #{contexts.join ' '}"
255
- projects_string = projects.empty? ? "" : " #{projects.join ' '}"
256
- due_on_string = due_on.nil? ? "" : " due:#{due_on}"
257
- "#{done_string}#{priority_string}#{created_on_string}#{text}#{contexts_string}#{projects_string}#{due_on_string}"
271
+ [
272
+ done? && "x #{completed_on}",
273
+ priority && "(#{priority})",
274
+ created_on.to_s,
275
+ text,
276
+ contexts.join(' '),
277
+ projects.join(' '),
278
+ tags.map { |tag,val| "#{tag}:#{val}" }.join(' ')
279
+ ].grep(String).join(' ').strip
258
280
  end
259
281
 
260
282
  # Compares the priorities of two tasks.
261
283
  #
262
284
  # Example:
263
285
  #
264
- # task1 = Todo::Task.new "(A) Priority A."
265
- # task2 = Todo::Task.new "(B) Priority B."
286
+ # task1 = Todo::Task.new("(A) Priority A.")
287
+ # task2 = Todo::Task.new("(B) Priority B.")
266
288
  #
267
289
  # task1 > task2
268
290
  # # => true
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ describe Todo::File do
4
+ let(:path) { File.dirname(__FILE__) + '/../data/todo.txt' }
5
+
6
+ context 'reading input' do
7
+ it 'should raise a system error when file not found' do
8
+ expect { Todo::File.open('not_found') }.to raise_error Errno::ENOENT
9
+ end
10
+
11
+ it 'should yield each task from block passed to open' do
12
+ Todo::File.open(path) do |file|
13
+ file.each do |task|
14
+ expect(task).to be_a Todo::Task
15
+ end
16
+ end
17
+ end
18
+
19
+ it 'should return a file object when no block is passed to open' do
20
+ file = Todo::File.open(path)
21
+ expect(file).to be_a Todo::File
22
+ end
23
+
24
+ it 'should return an enumerator when no block is passed to each' do
25
+ enumerator = Todo::File.open(path).each
26
+ expect(enumerator).to be_a Enumerator
27
+ expect(enumerator.next).to be_a Todo::Task
28
+ end
29
+
30
+ it 'should read a file into an array of tasks' do
31
+ list = Todo::File.read(path)
32
+ expect(list).to be_a Array
33
+ expect(list.first).to be_a Todo::Task
34
+ end
35
+ end
36
+
37
+ context 'writing' do
38
+ let(:path) { File.dirname(__FILE__) + '/../data/test.txt' }
39
+
40
+ it 'should write to a list file when initialized in writeable mode' do
41
+ file = Todo::File.new(path, 'w')
42
+ file.puts(Todo::Task.new('Task 1'))
43
+ file.puts(Todo::Task.new('Task 2'))
44
+ file.close
45
+
46
+ target = File.open(path)
47
+ expect(target.gets.rstrip).to eq("Task 1")
48
+ expect(target.gets.rstrip).to eq("Task 2")
49
+ end
50
+
51
+ it 'should write to a list file when opened in writeable mode' do
52
+ Todo::File.open(path, 'w') do |file|
53
+ file.puts(Todo::Task.new('Task 1'))
54
+ file.puts(Todo::Task.new('Task 2'))
55
+ end
56
+
57
+ target = File.open(path)
58
+ expect(target.gets.rstrip).to eq("Task 1")
59
+ expect(target.gets.rstrip).to eq("Task 2")
60
+ end
61
+
62
+ it 'should write each task in the list to a line in the file' do
63
+ list = Todo::List.new([Todo::Task.new('Task 1'), Todo::Task.new('Task 2')])
64
+ Todo::File.write(path, list)
65
+
66
+ target = File.open(path)
67
+ expect(target.gets.rstrip).to eq("Task 1")
68
+ expect(target.gets.rstrip).to eq("Task 2")
69
+ end
70
+
71
+ after do
72
+ File.delete(path)
73
+ end
74
+ end
75
+ end
@@ -7,17 +7,17 @@ describe Todo::List do
7
7
 
8
8
  context 'create with an array' do
9
9
  it 'successfully creates an object' do
10
- array = Array.new
11
- array.push "(A) A string task!"
12
- array.push Todo::Task.new("(A) An actual task!")
13
- expect(Todo::List.new array).not_to eq(nil)
10
+ items = []
11
+ items << "(A) A string task!"
12
+ items << Todo::Task.new("(A) An actual task!")
13
+ expect(Todo::List.new(items).length).to eq(2)
14
14
  end
15
15
 
16
16
  it 'does not have a path' do
17
- array = Array.new
18
- array.push "(A) A string task!"
19
- array.push Todo::Task.new("(A) An actual task!")
20
- expect(Todo::List.new(array).path).to eq(nil)
17
+ items = []
18
+ items << "(A) A string task!"
19
+ items << Todo::Task.new("(A) An actual task!")
20
+ expect(Todo::List.new(items).path).to eq(nil)
21
21
  end
22
22
  end
23
23
 
@@ -38,12 +38,9 @@ describe Todo::List do
38
38
  end
39
39
 
40
40
  it 'accepts a mix of tasks and strings' do
41
- l = Todo::List.new([
42
- "A task!",
43
- Todo::Task.new("Another task!"),
44
- ])
41
+ mixed = Todo::List.new(["A task!", Todo::Task.new("Another task!")])
45
42
 
46
- expect(l.length).to eq(2)
43
+ expect(mixed.length).to eq(2)
47
44
  end
48
45
 
49
46
  it 'has the correct path' do
@@ -97,9 +94,7 @@ describe Todo::List do
97
94
  end
98
95
 
99
96
  it 'should be able to filter by project, context and priority' do
100
- filtered = list.by_project('+project').
101
- by_context('@context').
102
- by_priority('C')
97
+ filtered = list.by_project('+project').by_context('@context').by_priority('C')
103
98
 
104
99
  # Make sure some data was actually checked
105
100
  expect(filtered.length).to be > 0
@@ -115,17 +115,25 @@ describe Todo::Syntax do
115
115
  end
116
116
  end
117
117
 
118
- describe '#extract_due_on_date' do
118
+ describe '#extract_tags' do
119
119
  specify 'empty task' do
120
- expect(extract_due_on_date('')).to be nil
120
+ expect(extract_tags('')).to eq({})
121
121
  end
122
122
 
123
- specify 'task without due date' do
124
- expect(extract_due_on_date('something to do')).to be nil
123
+ specify 'task without tag' do
124
+ expect(extract_tags('something to do')).to be {}
125
125
  end
126
126
 
127
127
  specify 'task with due date' do
128
- expect(extract_due_on_date('something to do due:2016-03-30')).to eq(Date.new(2016, 03, 30))
128
+ expect(extract_tags('something to do due:2016-03-30')).to eq(:due => '2016-03-30')
129
+ end
130
+
131
+ specify 'task with case-insensitive due date' do
132
+ expect(extract_tags('something to do DUE:2016-03-30')).to eq(:due => '2016-03-30')
133
+ end
134
+
135
+ specify 'task with multiple tags' do
136
+ expect(extract_tags('something to do hello:world and foo:bar')).to eq(:hello => 'world', :foo => 'bar')
129
137
  end
130
138
  end
131
139
 
@@ -11,7 +11,7 @@ describe Todo::Task do
11
11
  task = Todo::Task.new '(A) 2012-12-08 My task @test +test2'
12
12
  Timecop.freeze(2013, 12, 8) do
13
13
  task.do!
14
- expect(task.orig).to eq('(A) 2012-12-08 My task @test +test2')
14
+ expect(task.raw).to eq('(A) 2012-12-08 My task @test +test2')
15
15
  end
16
16
  end
17
17
 
@@ -32,7 +32,7 @@ describe Todo::Task do
32
32
 
33
33
  it 'should retain the original task creation string' do
34
34
  task = Todo::Task.new '(A) This is an awesome task, yo. +winning'
35
- expect(task.orig).to eq('(A) This is an awesome task, yo. +winning')
35
+ expect(task.raw).to eq('(A) This is an awesome task, yo. +winning')
36
36
  end
37
37
 
38
38
  it 'should be able to get just the text, no contexts etc.' do
@@ -411,11 +411,22 @@ describe Todo::Task do
411
411
  logger = double(Logger)
412
412
  Todo::Logger.logger = logger
413
413
 
414
- task = Todo::Task.new 'x 2012-09-11 (B) 2012-03-04 This is a sweet task. @context due:2012-02-31 +project'
415
- error_message = 'Task#date is deprecated, use created_on instead.'
414
+ task = Todo::Task.new('x 2012-09-11 (B) 2012-03-04 This is a sweet task. @context due:2012-02-31 +project')
415
+ error_message = '`Task#date` is deprecated, use `Task#created_on` instead.'
416
416
 
417
417
  expect(logger).to receive(:warn).with(error_message)
418
418
  task.date
419
419
  end
420
+
421
+ it 'should call @logger.warn if #orig called as deprecated method' do
422
+ logger = double(Logger)
423
+ Todo::Logger.logger = logger
424
+
425
+ task = Todo::Task.new('x 2012-09-11 (B) 2012-03-04 This is a sweet task. @context due:2012-02-31 +project')
426
+ error_message = '`Task#orig` is deprecated, use `Task#raw` instead.'
427
+
428
+ expect(logger).to receive(:warn).with(error_message)
429
+ task.orig
430
+ end
420
431
  end
421
432
  end
data/todo-txt.gemspec CHANGED
@@ -1,11 +1,11 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'todo-txt'
3
- s.version = '0.9'
4
- s.authors = ["Sam Rose"]
5
- s.email = %q{samwho@lbak.co.uk}
6
- s.summary = %q{A client library for parsing todo.txt files.}
7
- s.homepage = %q{http://github.com/samwho/todo-txt-gem}
8
- s.description = %q{Allows for simple parsing of todo.txt files, as per Gina Trapani's todo.txt project.}
3
+ s.version = '0.10'
4
+ s.authors = ['Sam Rose']
5
+ s.email = 'samwho@lbak.co.uk'
6
+ s.summary = 'A client library for parsing todo.txt files.'
7
+ s.homepage = 'http://github.com/samwho/todo-txt-gem'
8
+ s.description = 'Allows for simple parsing of todo.txt files, as per Gina Trapani\'s todo.txt project.'
9
9
  s.required_ruby_version = '>= 2.0'
10
10
  s.license = 'GPL-2'
11
11
  s.files = `git ls-files`.split(/\n/)
data/todo.txt CHANGED
@@ -1,13 +1,19 @@
1
1
  x 2016-04-21 modify list#save! to work with windows line separators +bug
2
2
  determine correct/expected behaviour for missing file paths +feature +api
3
3
  dirty flag on lists that have modifications after loading +feature
4
- extract file handling into separate IO-like module +refactor +api
4
+ x 2016-04-27 extract file handling into separate IO-like module +refactor +api
5
5
  implement enumerable API on list rather than inherit from array +api
6
6
  list operations should be algebraically closed +api
7
7
  include rubocop in travis builds +build
8
8
  use << operation to move tasks between lists +api +feature
9
9
  create github issue with roadmap for 1.0 release +feature +api
10
10
  consider rewriting file write tests with mocking and stringio +refactor +build
11
- remove deprecated date method from task +api
11
+ remove deprecated date and orig methods from task +api
12
12
  pass in options as an instance rather than make it always global +refactor +api
13
13
  save a backup of an existing todo file when the IO object is read in +feature
14
+ x 2016-06-16 extract `due:` formatting into a more general syntax for adding tags +feature +refactor +api
15
+ x rename lib path to match standard gem conventions +refactor
16
+ x 2016-04-23 tidy up code examples in doc comments to match ruby styleguide conventions +refactor +docs
17
+ consider renaming or aliasing `#done?` to `#complete?` +api +refactor
18
+ add complementary method returning opposite of `#done?` to provide symmetry +api
19
+ restructure test specs to reflect different core concerns and areas of functionality +refactor
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: todo-txt
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.9'
4
+ version: '0.10'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Rose
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-20 00:00:00.000000000 Z
11
+ date: 2016-07-02 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Allows for simple parsing of todo.txt files, as per Gina Trapani's todo.txt
14
14
  project.
@@ -26,14 +26,17 @@ files:
26
26
  - README.md
27
27
  - Rakefile
28
28
  - lib/todo-txt.rb
29
- - lib/todo-txt/list.rb
30
- - lib/todo-txt/logger.rb
31
- - lib/todo-txt/options.rb
32
- - lib/todo-txt/syntax.rb
33
- - lib/todo-txt/task.rb
29
+ - lib/todo.rb
30
+ - lib/todo/file.rb
31
+ - lib/todo/list.rb
32
+ - lib/todo/logger.rb
33
+ - lib/todo/options.rb
34
+ - lib/todo/syntax.rb
35
+ - lib/todo/task.rb
34
36
  - spec/data/tasks.txt
35
37
  - spec/data/todo.txt
36
38
  - spec/spec_helper.rb
39
+ - spec/todo-txt/file_spec.rb
37
40
  - spec/todo-txt/list_spec.rb
38
41
  - spec/todo-txt/options/require_completed_on_spec.rb
39
42
  - spec/todo-txt/options_spec.rb
@@ -61,9 +64,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
61
64
  version: '0'
62
65
  requirements: []
63
66
  rubyforge_project:
64
- rubygems_version: 2.2.2
67
+ rubygems_version: 2.4.5
65
68
  signing_key:
66
69
  specification_version: 4
67
70
  summary: A client library for parsing todo.txt files.
68
71
  test_files: []
69
- has_rdoc:
data/lib/todo-txt/list.rb DELETED
@@ -1,122 +0,0 @@
1
- module Todo
2
- # Initializes a Todo List object with a path to the corresponding todo.txt
3
- # file. For example, if your todo.txt file is located at:
4
- #
5
- # /home/sam/Dropbox/todo/todo.txt
6
- #
7
- # You would initialize this object like:
8
- #
9
- # list = Todo::List.new "/home/sam/Dropbox/todo/todo-txt"
10
- #
11
- # Alternately, you can initialize this object with an array of strings or
12
- # tasks. If the array is of strings, the strings will be converted into
13
- # tasks. You can supply a mixed list of string and tasks if you wish.
14
- #
15
- # Example:
16
- #
17
- # array = Array.new
18
- # array.push "(A) A string task!"
19
- # array.push Todo::Task.new("(A) An actual task!")
20
- #
21
- # list = Todo::List.new array
22
- class List < Array
23
- def initialize(list)
24
- if list.is_a? Array
25
- # No file path was given.
26
- @path = nil
27
-
28
- # If path is an array, loop over it, adding to self.
29
- list.each do |task|
30
- # If it's a string, make a new task out of it.
31
- if task.is_a? String
32
- push Task.new task
33
- # If it's a task, just add it.
34
- elsif task.is_a? Todo::Task
35
- push task
36
- end
37
- end
38
- elsif list.is_a? String
39
- @path = list
40
-
41
- # Read in lines from file, create Todo::Tasks out of them and push them
42
- # onto self.
43
- File.open(list) do |file|
44
- file.each_line { |line| push Task.new(line) }
45
- end
46
- end
47
- end
48
-
49
- # The path to the todo.txt file that you supplied when you created the
50
- # Todo::List object.
51
- attr_reader :path
52
-
53
- # Filters the list by priority and returns a new list.
54
- #
55
- # Example:
56
- #
57
- # list = Todo::List.new "/path/to/list"
58
- # list.by_priority "A" #=> Will be a new list with only priority A tasks
59
- def by_priority(priority)
60
- List.new(select { |task| task.priority == priority })
61
- end
62
-
63
- # Filters the list by context and returns a new list.
64
- #
65
- # Example:
66
- #
67
- # list = Todo::List.new "/path/to/list"
68
- # list.by_context "@context" #=> Will be a new list with only tasks
69
- # containing "@context"
70
- def by_context(context)
71
- List.new(select { |task| task.contexts.include? context })
72
- end
73
-
74
- # Filters the list by project and returns a new list.
75
- #
76
- # Example:
77
- #
78
- # list = Todo::List.new "/path/to/list"
79
- # list.by_project "+project" #=> Will be a new list with only tasks
80
- # containing "+project"
81
- def by_project(project)
82
- List.new(select { |task| task.projects.include? project })
83
- end
84
-
85
- # Filters the list by completed tasks and returns a new list.
86
- #
87
- # Example:
88
- #
89
- # list = Todo::List.new "/path/to/list"
90
- # list.by_done #=> Will be a new list with only tasks marked with
91
- # an [x]
92
- def by_done
93
- List.new(select(&:done?))
94
- end
95
-
96
- # Filters the list by incomplete tasks and returns a new list.
97
- #
98
- # Example:
99
- #
100
- # list = Todo::List.new "/path/to/list"
101
- # list.by_not_done #=> Will be a new list with only incomplete tasks
102
- def by_not_done
103
- List.new(select { |task| task.done? == false })
104
- end
105
-
106
- # Saves the list to the original file location.
107
- #
108
- # Warning: This is a destructive operation and will overwrite what is
109
- # currently there.
110
- #
111
- # If no `path` is specified in the constructor then an error is raised.
112
- def save!
113
- raise "No path specified." unless path
114
-
115
- File.open(path, 'w') do |outfile|
116
- each do |task|
117
- outfile.puts(task.to_s)
118
- end
119
- end
120
- end
121
- end
122
- end