todotxt 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE.txt +20 -0
- data/README.md +65 -0
- data/Rakefile +1 -0
- data/bin/todotxt +13 -0
- data/conf/todo.txt +1 -0
- data/conf/todotxt.cfg +1 -0
- data/fixtures/complex_todo.txt +3 -0
- data/fixtures/simple_todo.txt +4 -0
- data/lib/todotxt/cli.rb +289 -0
- data/lib/todotxt/clihelpers.rb +54 -0
- data/lib/todotxt/regex.rb +6 -0
- data/lib/todotxt/todo.rb +90 -0
- data/lib/todotxt/todolist.rb +83 -0
- data/lib/todotxt/version.rb +3 -0
- data/lib/todotxt.rb +13 -0
- data/spec/todo_spec.rb +129 -0
- data/spec/todolist_spec.rb +99 -0
- data/todotxt.gemspec +26 -0
- metadata +112 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Johan Sahlén, http://jsahlen.se/
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# Todotxt.rb
|
2
|
+
|
3
|
+
*todo.txt with a ruby flair*
|
4
|
+
|
5
|
+
|
6
|
+
## About
|
7
|
+
|
8
|
+
Todotxt is a ruby CLI interface to work with a
|
9
|
+
[todo.txt](http://www.todotxt.com) file
|
10
|
+
|
11
|
+
|
12
|
+
## Install
|
13
|
+
|
14
|
+
Clone from [jsahlen/todotxt](http://github.com/jsahlen/todotxt) and do
|
15
|
+
|
16
|
+
rake install
|
17
|
+
|
18
|
+
I might release it as a proper gem in the future…
|
19
|
+
|
20
|
+
Todotxt relies on a configuration file (`.todotxt.cfg`) in your home directory,
|
21
|
+
which points to the location of your todo.txt. You can run
|
22
|
+
|
23
|
+
todotxt generate_cfg
|
24
|
+
|
25
|
+
to generate this file, which will then point to `~/todo.txt`.
|
26
|
+
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
The gem will install a command, `todotxt` which is used to interact with your
|
31
|
+
todo.txt.
|
32
|
+
|
33
|
+
Tasks:
|
34
|
+
todotxt add | a TEXT # Add a new Todo item
|
35
|
+
todotxt append | app ITEM# STRING # Append STRING to ITEM#
|
36
|
+
todotxt del | rm ITEM#[, ITEM#, ITEM#, ...] # Remove ITEM#
|
37
|
+
todotxt do ITEM#[, ITEM#, ITEM#, ...] # Mark ITEM# as done
|
38
|
+
todotxt dp | depri ITEM#[, ITEM#, ITEM#, ...] # Remove priority for ITEM#
|
39
|
+
todotxt generate_cfg # Create a sample todo.txt
|
40
|
+
todotxt generate_config # Create a .todotxt.cfg file in your home folder, containing the path to todo.txt
|
41
|
+
todotxt help [TASK] # Describe available tasks or one specific task
|
42
|
+
todotxt list | ls [SEARCH] # List all todos, or todos matching SEARCH
|
43
|
+
todotxt listproj | lsproj # List all projects
|
44
|
+
todotxt lscon | lsc # List all contexts
|
45
|
+
todotxt lsdone | lsd # List all done items
|
46
|
+
todotxt prepend | prep ITEM# STRING # Prepend STRING to ITEM#
|
47
|
+
todotxt pri | p ITEM# PRIORITY # Set priority of ITEM# to PRIORITY
|
48
|
+
todotxt replace ITEM# TEXT # Completely replace ITEM# text with TEXT
|
49
|
+
todotxt undo | u ITEM#[, ITEM#, ITEM#, ...] # Mark ITEM# item as not done
|
50
|
+
todotxt version # Show todotxt version
|
51
|
+
|
52
|
+
Calling simply `todotxt` will automatically run the `ls` command.
|
53
|
+
|
54
|
+
|
55
|
+
## Dependencies
|
56
|
+
|
57
|
+
* [Thor](http://github.com/wycats/thor)
|
58
|
+
* [Rainbow](http://github.com/sickill/rainbow)
|
59
|
+
* [ParseConfig](http://www.5dollarwhitebox.org/drupal/?q=node/21)
|
60
|
+
|
61
|
+
|
62
|
+
## Bugs
|
63
|
+
|
64
|
+
Please report any bugs using the
|
65
|
+
[GitHub Issue Tracker](http://github.com/jsahlen/todotxt/issues).
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/bin/todotxt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
libdir = File.join(File.dirname(File.dirname(__FILE__)), "lib")
|
4
|
+
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
|
5
|
+
|
6
|
+
args = ARGV.clone
|
7
|
+
|
8
|
+
if args.empty?
|
9
|
+
args.push "ls"
|
10
|
+
end
|
11
|
+
|
12
|
+
require "todotxt"
|
13
|
+
Todotxt::CLI.start(args)
|
data/conf/todo.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Learn todo.txt +myproject @home
|
data/conf/todotxt.cfg
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
todo_txt_path = ~/todo.txt
|
data/lib/todotxt/cli.rb
ADDED
@@ -0,0 +1,289 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "rainbow"
|
3
|
+
require "parseconfig"
|
4
|
+
|
5
|
+
module Todotxt
|
6
|
+
CFG_PATH = File.expand_path("~/.todotxt.cfg")
|
7
|
+
|
8
|
+
class CLI < Thor
|
9
|
+
include Thor::Actions
|
10
|
+
include Todotxt::CLIHelpers
|
11
|
+
|
12
|
+
def self.source_root
|
13
|
+
File.join File.dirname(__FILE__), "..", "..", "conf"
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(*args)
|
17
|
+
super
|
18
|
+
|
19
|
+
unless ["help", "generate_config"].include? ARGV[0]
|
20
|
+
parse_config
|
21
|
+
|
22
|
+
@list = TodoList.new @txt_path
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Listing
|
28
|
+
#
|
29
|
+
|
30
|
+
desc "list | ls [SEARCH]", "List all todos, or todos matching SEARCH"
|
31
|
+
method_option :done, :type => :boolean, :aliases => "-d", :desc => "Include todo items that have been marked as done"
|
32
|
+
method_option :simple, :type => :boolean, :desc => "Simple output (for scripts, etc)"
|
33
|
+
def list search=""
|
34
|
+
with_done = false
|
35
|
+
|
36
|
+
with_done = true if options[:done]
|
37
|
+
|
38
|
+
@list.filter(search, :with_done => with_done)
|
39
|
+
|
40
|
+
render_list :simple => !!options[:simple]
|
41
|
+
end
|
42
|
+
map "ls" => :list
|
43
|
+
|
44
|
+
desc "lsdone | lsd", "List all done items"
|
45
|
+
def lsdone search=""
|
46
|
+
@list.filter(search, :only_done => true)
|
47
|
+
|
48
|
+
render_list
|
49
|
+
end
|
50
|
+
map "lsd" => :lsdone
|
51
|
+
|
52
|
+
desc "listproj | lsproj", "List all projects"
|
53
|
+
def listproj
|
54
|
+
@list.projects.each { |p| say p }
|
55
|
+
end
|
56
|
+
map "lsproj" => :listproj
|
57
|
+
|
58
|
+
desc "lscon | lsc", "List all contexts"
|
59
|
+
def lscon
|
60
|
+
@list.contexts.each { |c| say c }
|
61
|
+
end
|
62
|
+
map "lsc" => :lscon
|
63
|
+
|
64
|
+
#
|
65
|
+
# Todo management
|
66
|
+
#
|
67
|
+
|
68
|
+
desc "add | a TEXT", "Add a new Todo item"
|
69
|
+
def add(str, *str2)
|
70
|
+
string = "#{str} #{str2.join(' ')}"
|
71
|
+
todo = @list.add string
|
72
|
+
|
73
|
+
puts format_todo(todo)
|
74
|
+
|
75
|
+
@list.save
|
76
|
+
end
|
77
|
+
map "a" => :add
|
78
|
+
|
79
|
+
desc "do ITEM#[, ITEM#, ITEM#, ...]", "Mark ITEM# as done"
|
80
|
+
def do line1, *lines
|
81
|
+
lines.unshift(line1).each do |line|
|
82
|
+
todo = @list.find_by_line line
|
83
|
+
if todo
|
84
|
+
todo.do
|
85
|
+
puts format_todo(todo)
|
86
|
+
|
87
|
+
@list.save
|
88
|
+
else
|
89
|
+
error "No todo found at line #{line}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
desc "undo | u ITEM#[, ITEM#, ITEM#, ...]", "Mark ITEM# item as not done"
|
95
|
+
def undo line1, *lines
|
96
|
+
lines.unshift(line1).each do |line|
|
97
|
+
todo = @list.find_by_line line
|
98
|
+
if todo
|
99
|
+
todo.undo
|
100
|
+
puts format_todo(todo)
|
101
|
+
|
102
|
+
@list.save
|
103
|
+
else
|
104
|
+
error "No todo found at line #{line}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
map "u" => :undo
|
109
|
+
|
110
|
+
desc "pri | p ITEM# PRIORITY", "Set priority of ITEM# to PRIORITY"
|
111
|
+
def pri line, priority
|
112
|
+
todo = @list.find_by_line line
|
113
|
+
if todo
|
114
|
+
todo.prioritize priority
|
115
|
+
puts format_todo(todo)
|
116
|
+
|
117
|
+
@list.save
|
118
|
+
else
|
119
|
+
error "No todo found at line #{line}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
map "p" => :pri
|
123
|
+
|
124
|
+
desc "dp | depri ITEM#[, ITEM#, ITEM#, ...]", "Remove priority for ITEM#"
|
125
|
+
def dp line1, *lines
|
126
|
+
lines.unshift(line1).each do |line|
|
127
|
+
todo = @list.find_by_line line
|
128
|
+
if todo
|
129
|
+
todo.prioritize
|
130
|
+
puts format_todo(todo)
|
131
|
+
|
132
|
+
@list.save
|
133
|
+
else
|
134
|
+
error "No todo found at line #{line}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
map "depri" => :dp
|
139
|
+
|
140
|
+
desc "append | app ITEM# STRING", "Append STRING to ITEM#"
|
141
|
+
def append line, str, *str2
|
142
|
+
string = "#{str} #{str2.join(' ')}"
|
143
|
+
todo = @list.find_by_line line
|
144
|
+
if todo
|
145
|
+
todo.append string
|
146
|
+
puts format_todo(todo)
|
147
|
+
|
148
|
+
@list.save
|
149
|
+
else
|
150
|
+
error "No todo found at line #{line}"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
map "app" => :append
|
154
|
+
|
155
|
+
desc "prepend | prep ITEM# STRING", "Prepend STRING to ITEM#"
|
156
|
+
def prepend line, str, *str2
|
157
|
+
string = "#{str} #{str2.join(' ')}"
|
158
|
+
todo = @list.find_by_line line
|
159
|
+
if todo
|
160
|
+
todo.prepend string
|
161
|
+
puts format_todo(todo)
|
162
|
+
|
163
|
+
@list.save
|
164
|
+
else
|
165
|
+
error "No todo found at line #{line}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
map "prep" => :prepend
|
169
|
+
|
170
|
+
desc "replace ITEM# TEXT", "Completely replace ITEM# text with TEXT"
|
171
|
+
def replace line, str, *str2
|
172
|
+
string = "#{str} #{str2.join(' ')}"
|
173
|
+
todo = @list.find_by_line line
|
174
|
+
if todo
|
175
|
+
todo.replace string
|
176
|
+
puts format_todo(todo)
|
177
|
+
|
178
|
+
@list.save
|
179
|
+
else
|
180
|
+
error "No todo found at line #{line}"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
desc "del | rm ITEM#[, ITEM#, ITEM#, ...]", "Remove ITEM#"
|
185
|
+
method_option :force, :type => :boolean, :aliases => "-f", :desc => "Don't confirm removal"
|
186
|
+
def del line1, *lines
|
187
|
+
lines.unshift(line1).each do |line|
|
188
|
+
todo = @list.find_by_line line
|
189
|
+
if todo
|
190
|
+
say format_todo(todo)
|
191
|
+
if options[:force] || yes?("Remove this item? [y/N]")
|
192
|
+
@list.remove line
|
193
|
+
notice "Removed from list"
|
194
|
+
|
195
|
+
@list.save
|
196
|
+
end
|
197
|
+
else
|
198
|
+
error "No todo found at line #{line}"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
map "rm" => :del
|
203
|
+
|
204
|
+
#
|
205
|
+
# File generation
|
206
|
+
#
|
207
|
+
|
208
|
+
desc "generate_config", "Create a .todotxt.cfg file in your home folder, containing the path to todo.txt"
|
209
|
+
def generate_config
|
210
|
+
copy_file "todotxt.cfg", CFG_PATH
|
211
|
+
puts ""
|
212
|
+
|
213
|
+
parse_config
|
214
|
+
end
|
215
|
+
|
216
|
+
desc "generate_cfg", "Create a sample todo.txt"
|
217
|
+
def generate_txt
|
218
|
+
copy_file "todo.txt", @txt_path
|
219
|
+
puts ""
|
220
|
+
end
|
221
|
+
|
222
|
+
#
|
223
|
+
# Extras
|
224
|
+
#
|
225
|
+
|
226
|
+
desc "version", "Show todotxt version"
|
227
|
+
def version
|
228
|
+
say "todotxt #{VERSION}"
|
229
|
+
end
|
230
|
+
|
231
|
+
private
|
232
|
+
|
233
|
+
def render_list opts={}
|
234
|
+
numsize = @list.count + 1
|
235
|
+
numsize = numsize.to_s.length + 0
|
236
|
+
|
237
|
+
@list.each do |t|
|
238
|
+
if opts[:simple]
|
239
|
+
say "#{t.line} #{t.to_s}"
|
240
|
+
else
|
241
|
+
say format_todo(t, numsize)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
unless opts[:simple]
|
246
|
+
puts "TODO: #{@list.count} items".color(:black).bright
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def parse_config
|
251
|
+
unless File.exist? CFG_PATH
|
252
|
+
puts "You need a .todotxt.cfg file in your home folder to continue (used to determine the path of your todo.txt.) Answer yes to have it generated for you (pointing to ~/todo.txt), or no to create it yourself.\n\n"
|
253
|
+
confirm_generate = yes? "Create ~/.todotxt.cfg? [y/N]"
|
254
|
+
|
255
|
+
if confirm_generate
|
256
|
+
generate_config
|
257
|
+
else
|
258
|
+
puts ""
|
259
|
+
exit
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
cfg = ParseConfig.new(CFG_PATH)
|
264
|
+
|
265
|
+
txt = cfg.get_value "todo_txt_path"
|
266
|
+
|
267
|
+
if txt
|
268
|
+
@txt_path = File.expand_path(txt)
|
269
|
+
|
270
|
+
unless File.exist? @txt_path
|
271
|
+
puts "#{txt} doesn't exist yet. Would you like to generate a sample file?"
|
272
|
+
confirm_generate = yes? "Create #{txt}? [y/N]"
|
273
|
+
|
274
|
+
if confirm_generate
|
275
|
+
generate_txt
|
276
|
+
else
|
277
|
+
puts ""
|
278
|
+
exit
|
279
|
+
end
|
280
|
+
end
|
281
|
+
else
|
282
|
+
error "Couldn't find todo_txt_path setting in ~/.todotxt.cfg."
|
283
|
+
puts "Please run the following to create a new configuration file:"
|
284
|
+
puts " todotxt generate_config"
|
285
|
+
exit
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Todotxt
|
2
|
+
module CLIHelpers
|
3
|
+
|
4
|
+
def format_todo(todo, number_padding=nil)
|
5
|
+
line = todo.line.to_s
|
6
|
+
|
7
|
+
if number_padding
|
8
|
+
line = line.rjust number_padding
|
9
|
+
end
|
10
|
+
|
11
|
+
text = todo.to_s
|
12
|
+
|
13
|
+
unless todo.done
|
14
|
+
text.gsub! PRIORITY_REGEX do |p|
|
15
|
+
case p[1]
|
16
|
+
when "A"
|
17
|
+
color = :red
|
18
|
+
when "B"
|
19
|
+
color = :yellow
|
20
|
+
when "C"
|
21
|
+
color = :green
|
22
|
+
else
|
23
|
+
color = :white
|
24
|
+
end
|
25
|
+
|
26
|
+
p.to_s.color(color)
|
27
|
+
end
|
28
|
+
|
29
|
+
text.gsub! PROJECT_REGEX, '\1'.color(:green)
|
30
|
+
text.gsub! CONTEXT_REGEX, '\1'.color(:blue)
|
31
|
+
else
|
32
|
+
text = text.color(:black).bright
|
33
|
+
end
|
34
|
+
|
35
|
+
ret = ""
|
36
|
+
|
37
|
+
ret << "#{line}. ".color(:black).bright
|
38
|
+
ret << "#{text}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def warn message=""
|
42
|
+
puts "WARN: #{message}".color(:yellow)
|
43
|
+
end
|
44
|
+
|
45
|
+
def notice message=""
|
46
|
+
puts "=> #{message}".color(:green)
|
47
|
+
end
|
48
|
+
|
49
|
+
def error message=""
|
50
|
+
puts "ERROR: #{message}".color(:red)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
data/lib/todotxt/todo.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require "todotxt/regex"
|
2
|
+
|
3
|
+
module Todotxt
|
4
|
+
class Todo
|
5
|
+
|
6
|
+
attr_accessor :text
|
7
|
+
attr_accessor :line
|
8
|
+
attr_accessor :priority
|
9
|
+
attr_accessor :projects
|
10
|
+
attr_accessor :contexts
|
11
|
+
attr_accessor :done
|
12
|
+
|
13
|
+
def initialize text, line=nil
|
14
|
+
@line = line
|
15
|
+
|
16
|
+
create_from_text text
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_from_text text
|
20
|
+
@text = text
|
21
|
+
@priority = text.scan(PRIORITY_REGEX).flatten.first || nil
|
22
|
+
@projects = text.scan(PROJECT_REGEX).flatten.uniq || []
|
23
|
+
@contexts = text.scan(CONTEXT_REGEX).flatten.uniq || []
|
24
|
+
@done = !text.scan(DONE_REGEX).empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
def do
|
28
|
+
unless done
|
29
|
+
@text = "x #{text}".strip
|
30
|
+
@done = true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def undo
|
35
|
+
if done
|
36
|
+
@text = text.sub(DONE_REGEX, "").strip
|
37
|
+
@done = false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def prioritize new_priority=nil, opts={}
|
42
|
+
if new_priority && !new_priority.match(/^[A-Z]$/i)
|
43
|
+
return
|
44
|
+
end
|
45
|
+
|
46
|
+
if new_priority
|
47
|
+
new_priority = new_priority.upcase
|
48
|
+
end
|
49
|
+
|
50
|
+
priority_string = new_priority ? "(#{new_priority}) " : ""
|
51
|
+
|
52
|
+
if priority && !opts[:force]
|
53
|
+
@text.gsub! PRIORITY_REGEX, priority_string
|
54
|
+
else
|
55
|
+
@text = "#{priority_string}#{text}".strip
|
56
|
+
end
|
57
|
+
|
58
|
+
@priority = new_priority
|
59
|
+
end
|
60
|
+
|
61
|
+
def append appended_text=""
|
62
|
+
@text << " " << appended_text
|
63
|
+
end
|
64
|
+
|
65
|
+
def prepend prepended_text=""
|
66
|
+
@text = "#{prepended_text} #{text.gsub(PRIORITY_REGEX, '')}"
|
67
|
+
prioritize priority, :force => true
|
68
|
+
end
|
69
|
+
|
70
|
+
def replace text
|
71
|
+
create_from_text text
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_s
|
75
|
+
text.clone
|
76
|
+
end
|
77
|
+
|
78
|
+
def <=> b
|
79
|
+
if priority.nil? && b.priority.nil?
|
80
|
+
return line <=> b.line
|
81
|
+
end
|
82
|
+
|
83
|
+
return 1 if priority.nil?
|
84
|
+
return -1 if b.priority.nil?
|
85
|
+
|
86
|
+
return priority <=> b.priority
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require "todotxt/todo"
|
2
|
+
|
3
|
+
module Todotxt
|
4
|
+
class TodoList
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
attr_accessor :todos
|
8
|
+
|
9
|
+
def initialize file
|
10
|
+
@todos = []
|
11
|
+
@file = file
|
12
|
+
|
13
|
+
File.open(file).read.each_line do |l|
|
14
|
+
add l.strip unless l.empty?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def add str
|
19
|
+
todo = Todo.new str, (@todos.count + 1)
|
20
|
+
@todos.push todo
|
21
|
+
@todos.sort!
|
22
|
+
|
23
|
+
return todo
|
24
|
+
end
|
25
|
+
|
26
|
+
def remove line
|
27
|
+
@todos.reject! { |t| t.line.to_s == line.to_s }
|
28
|
+
end
|
29
|
+
|
30
|
+
def projects
|
31
|
+
map { |t| t.projects }.flatten.uniq.sort
|
32
|
+
end
|
33
|
+
|
34
|
+
def contexts
|
35
|
+
map { |t| t.contexts }.flatten.uniq.sort
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_by_line line
|
39
|
+
@todos.find { |t| t.line.to_s == line.to_s }
|
40
|
+
end
|
41
|
+
|
42
|
+
def save
|
43
|
+
File.open(@file, "w") { |f| f.write to_txt }
|
44
|
+
end
|
45
|
+
|
46
|
+
def each &block
|
47
|
+
@todos.each &block
|
48
|
+
end
|
49
|
+
|
50
|
+
def filter search="", opts={}
|
51
|
+
@todos.select! do |t|
|
52
|
+
select = false
|
53
|
+
|
54
|
+
text = t.to_s.downcase
|
55
|
+
|
56
|
+
if opts[:only_done]
|
57
|
+
select = true if t.done
|
58
|
+
else
|
59
|
+
if opts[:with_done]
|
60
|
+
select = true
|
61
|
+
else
|
62
|
+
select = true unless t.done
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
select = false unless text.include?(search.downcase)
|
67
|
+
|
68
|
+
select
|
69
|
+
end
|
70
|
+
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_txt
|
75
|
+
@todos.sort { |a,b| a.line <=> b.line }.map { |t| t.to_s.strip }.join("\n")
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_a
|
79
|
+
map { |t| ["#{t.line}. ", t.to_s] }
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
data/lib/todotxt.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Setup our load paths
|
2
|
+
libdir = File.dirname(__FILE__)
|
3
|
+
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
|
4
|
+
|
5
|
+
module Todotxt
|
6
|
+
autoload :Todo, "todotxt/todo"
|
7
|
+
autoload :TodoList, "todotxt/todolist"
|
8
|
+
autoload :CLI, "todotxt/cli"
|
9
|
+
autoload :CLIHelpers, "todotxt/clihelpers"
|
10
|
+
end
|
11
|
+
|
12
|
+
require "todotxt/regex"
|
13
|
+
require "todotxt/version"
|
data/spec/todo_spec.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
require "todotxt/todo"
|
2
|
+
|
3
|
+
describe Todotxt::Todo do
|
4
|
+
|
5
|
+
it "should create a todo item string" do
|
6
|
+
todo = Todotxt::Todo.new "an item"
|
7
|
+
todo.to_s.should eql("an item")
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should parse metadata when creating a simple item" do
|
11
|
+
todo = Todotxt::Todo.new "x an item +project1 +project2 @context1 @context2"
|
12
|
+
|
13
|
+
todo.to_s.should eql "x an item +project1 +project2 @context1 @context2"
|
14
|
+
todo.priority.should eql nil
|
15
|
+
todo.projects.should eql ["+project1", "+project2"]
|
16
|
+
todo.contexts.should eql ["@context1", "@context2"]
|
17
|
+
todo.done.should eql true
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should parse metadata when creating an item with priority" do
|
21
|
+
todo = Todotxt::Todo.new "(A) x an item +project1 +project2 @context1 @context2"
|
22
|
+
|
23
|
+
todo.to_s.should eql "(A) x an item +project1 +project2 @context1 @context2"
|
24
|
+
todo.priority.should eql "A"
|
25
|
+
todo.projects.should eql ["+project1", "+project2"]
|
26
|
+
todo.contexts.should eql ["@context1", "@context2"]
|
27
|
+
todo.done.should eql true
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should store line number when creating an item" do
|
31
|
+
todo = Todotxt::Todo.new "an item", "2"
|
32
|
+
|
33
|
+
todo.line.should eql "2"
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should set an item as done" do
|
37
|
+
todo = Todotxt::Todo.new "an item"
|
38
|
+
|
39
|
+
todo.do
|
40
|
+
|
41
|
+
todo.to_s.should eql "x an item"
|
42
|
+
todo.done.should eql true
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should set an item as not done" do
|
46
|
+
todo = Todotxt::Todo.new "x an item"
|
47
|
+
|
48
|
+
todo.undo
|
49
|
+
|
50
|
+
todo.to_s.should eql "an item"
|
51
|
+
todo.done.should eql false
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should add priority to an item" do
|
55
|
+
todo = Todotxt::Todo.new "an item"
|
56
|
+
|
57
|
+
todo.prioritize "a"
|
58
|
+
|
59
|
+
todo.to_s.should eql "(A) an item"
|
60
|
+
todo.priority.should eql "A"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should change priority of an item" do
|
64
|
+
todo = Todotxt::Todo.new "(A) an item"
|
65
|
+
|
66
|
+
todo.prioritize "z"
|
67
|
+
|
68
|
+
todo.to_s.should eql "(Z) an item"
|
69
|
+
todo.priority.should eql "Z"
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should remove priority from an item" do
|
73
|
+
todo = Todotxt::Todo.new "(A) an item"
|
74
|
+
|
75
|
+
todo.prioritize
|
76
|
+
|
77
|
+
todo.to_s.should eql "an item"
|
78
|
+
todo.priority.should eql nil
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should append text to an item" do
|
82
|
+
todo = Todotxt::Todo.new "an item"
|
83
|
+
|
84
|
+
todo.append "more text"
|
85
|
+
|
86
|
+
todo.to_s.should eql "an item more text"
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should prepend text to an item" do
|
90
|
+
todo = Todotxt::Todo.new "an item"
|
91
|
+
|
92
|
+
todo.prepend "more text"
|
93
|
+
|
94
|
+
todo.to_s.should eql "more text an item"
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should preserve priority when prepending text to an item" do
|
98
|
+
todo = Todotxt::Todo.new "(A) an item"
|
99
|
+
|
100
|
+
todo.prepend "more text"
|
101
|
+
|
102
|
+
todo.to_s.should eql "(A) more text an item"
|
103
|
+
todo.priority.should eql "A"
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should replace an item with new text" do
|
107
|
+
todo = Todotxt::Todo.new "an item"
|
108
|
+
|
109
|
+
todo.replace "(A) a replacement item"
|
110
|
+
|
111
|
+
todo.to_s.should eql "(A) a replacement item"
|
112
|
+
todo.priority.should eql "A"
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should sort based on line number" do
|
116
|
+
todo1 = Todotxt::Todo.new "an item 1", 1
|
117
|
+
todo2 = Todotxt::Todo.new "an item 2", 2
|
118
|
+
|
119
|
+
(todo1 <=> todo2).should eql -1
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should value items with priority higher when sorting" do
|
123
|
+
todo1 = Todotxt::Todo.new "an item 1", 1
|
124
|
+
todo2 = Todotxt::Todo.new "(A) an item 2", 2
|
125
|
+
|
126
|
+
(todo1 <=> todo2).should eql 1
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require "todotxt/todolist"
|
2
|
+
|
3
|
+
describe Todotxt::TodoList do
|
4
|
+
|
5
|
+
describe "with simple list" do
|
6
|
+
before :each do
|
7
|
+
@list = Todotxt::TodoList.new File.join(File.dirname(__FILE__), "..", "fixtures", "simple_todo.txt")
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should parse a file on creation" do
|
11
|
+
@list.todos[0].to_s.should eql "First item"
|
12
|
+
@list.todos[1].to_s.should eql "Second item"
|
13
|
+
@list.todos[2].to_s.should eql "Third item"
|
14
|
+
@list.todos[3].to_s.should eql "x First done item"
|
15
|
+
|
16
|
+
@list.todos[0].line.should eql 1
|
17
|
+
@list.todos[1].line.should eql 2
|
18
|
+
@list.todos[2].line.should eql 3
|
19
|
+
@list.todos[3].line.should eql 4
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should add a new item" do
|
23
|
+
@list.add "Fourth item"
|
24
|
+
|
25
|
+
@list.todos[4].to_s.should eql "Fourth item"
|
26
|
+
@list.todos[4].line.should eql 5
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should remove an item" do
|
30
|
+
@list.remove 1
|
31
|
+
|
32
|
+
@list.todos[0].to_s.should eql "Second item"
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should find item by line" do
|
36
|
+
todo = @list.find_by_line 3
|
37
|
+
|
38
|
+
todo.to_s.should eql "Third item"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should filter list when searching" do
|
42
|
+
@list.filter "First"
|
43
|
+
|
44
|
+
@list.todos.count.should eql 1
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should filter list when searching case-sensitive" do
|
48
|
+
@list.filter "first"
|
49
|
+
|
50
|
+
@list.todos.count.should eql 1
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should include done items in search when told to do so" do
|
54
|
+
@list.filter "first", :with_done => true
|
55
|
+
|
56
|
+
@list.todos.count.should eql 2
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should only include done items in search when told to do so" do
|
60
|
+
@list.filter "first", :only_done => true
|
61
|
+
|
62
|
+
@list.todos.count.should eql 1
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should render plain text" do
|
66
|
+
comparison_string = <<EOF
|
67
|
+
First item
|
68
|
+
Second item
|
69
|
+
Third item
|
70
|
+
x First done item
|
71
|
+
EOF
|
72
|
+
@list.to_txt.should eql comparison_string.strip
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "with complex list" do
|
77
|
+
before :each do
|
78
|
+
@list = Todotxt::TodoList.new File.join(File.dirname(__FILE__), "..", "fixtures", "complex_todo.txt")
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should sort itself automatically on parse" do
|
82
|
+
@list.todos[0].to_s.should eql "(A) an item"
|
83
|
+
@list.todos[0].line.should eql 3
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should re-sort itself after adding a new item" do
|
87
|
+
@list.add "(B) A new item"
|
88
|
+
|
89
|
+
@list.todos[1].to_s.should eql "(B) A new item"
|
90
|
+
@list.todos[1].line.should eql 4
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should list all projects and contexts in the list" do
|
94
|
+
@list.projects.should eql ["+project1", "+project2"]
|
95
|
+
@list.contexts.should eql ["@context1", "@context2"]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
data/todotxt.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "todotxt/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "todotxt"
|
7
|
+
s.version = Todotxt::VERSION
|
8
|
+
s.authors = ["Johan Sahlén"]
|
9
|
+
s.email = ["johan@monospace.se"]
|
10
|
+
s.homepage = "http://github.com/jsahlen/todotxt"
|
11
|
+
s.summary = %q{todo.txt with a Ruby flair}
|
12
|
+
s.description = %q{Interact with your todo.txt using a Ruby-base command-line tool}
|
13
|
+
|
14
|
+
s.rubyforge_project = "todotxt"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency "thor", ">= 0.14.6"
|
22
|
+
s.add_dependency "rainbow", ">= 1.1.1"
|
23
|
+
s.add_dependency "parseconfig", ">= 0.5.2"
|
24
|
+
|
25
|
+
s.add_development_dependency "rspec", ">= 2.6.0"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: todotxt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Johan Sahlén
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-08-09 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: thor
|
16
|
+
requirement: &2164860840 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.14.6
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2164860840
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rainbow
|
27
|
+
requirement: &2164860340 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.1.1
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2164860340
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: parseconfig
|
38
|
+
requirement: &2164859880 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.5.2
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2164859880
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: &2164859420 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.6.0
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *2164859420
|
58
|
+
description: Interact with your todo.txt using a Ruby-base command-line tool
|
59
|
+
email:
|
60
|
+
- johan@monospace.se
|
61
|
+
executables:
|
62
|
+
- todotxt
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- .gitignore
|
67
|
+
- Gemfile
|
68
|
+
- MIT-LICENSE.txt
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- bin/todotxt
|
72
|
+
- conf/todo.txt
|
73
|
+
- conf/todotxt.cfg
|
74
|
+
- fixtures/complex_todo.txt
|
75
|
+
- fixtures/simple_todo.txt
|
76
|
+
- lib/todotxt.rb
|
77
|
+
- lib/todotxt/cli.rb
|
78
|
+
- lib/todotxt/clihelpers.rb
|
79
|
+
- lib/todotxt/regex.rb
|
80
|
+
- lib/todotxt/todo.rb
|
81
|
+
- lib/todotxt/todolist.rb
|
82
|
+
- lib/todotxt/version.rb
|
83
|
+
- spec/todo_spec.rb
|
84
|
+
- spec/todolist_spec.rb
|
85
|
+
- todotxt.gemspec
|
86
|
+
homepage: http://github.com/jsahlen/todotxt
|
87
|
+
licenses: []
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project: todotxt
|
106
|
+
rubygems_version: 1.8.6
|
107
|
+
signing_key:
|
108
|
+
specification_version: 3
|
109
|
+
summary: todo.txt with a Ruby flair
|
110
|
+
test_files:
|
111
|
+
- spec/todo_spec.rb
|
112
|
+
- spec/todolist_spec.rb
|