xtdo 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY ADDED
@@ -0,0 +1,7 @@
1
+ 0.2.0 - 28 November 2010
2
+ * Added ZSH completion
3
+ * Added XTDO_PATH environment variable for customizing storage location
4
+ * Added recurring tasks to list all
5
+
6
+ 0.1.0 - 27 November 2010
7
+ * Initial release
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2010 Xavier Shay
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,58 @@
1
+ = xtdo
2
+
3
+ An ultra fast command line todo list manager, with some strong opinions about workflow.
4
+
5
+ == Usage
6
+
7
+ Every command is mapped to a single character. If you want to be fast, you need to get the key strokes down.
8
+
9
+ l = list
10
+ a = add (or all when used with list)
11
+ d = done
12
+ b = bump
13
+ r = recur
14
+ c = completion (to help enable shell tab completion)
15
+
16
+ Here is how it fits together:
17
+
18
+ gem install xtdo
19
+
20
+ alias t=xtdo # Recommended!
21
+
22
+ t a some task # Add to list
23
+ t l a # All tasks
24
+ t b 0 some task # Move task to TODAY
25
+ t a 0 another task # New task on today list, shortcut letter
26
+ t l # Today's tasks
27
+ t d another task # It is done!
28
+ t b 1w some task # Actually we will to it next week
29
+ t r a 1d,thu bin night # Bin night every Thursday
30
+ t r a 1m,4 pay rent # Pay rent on the 4th of the month
31
+ t r d pay rent # Bah who wants to pay rent
32
+
33
+ t l c # List all tasks in a format suitable for tab completion
34
+ t r c # List all recurring tasks for completion
35
+
36
+ There is a command line completion script for ZSH in bin/xtdo_completion.zsh that you should use, since it auto completes task names for you. I don't know the best way to install this yet.
37
+
38
+ By default tasks are stored in ~/.xtdo. Customize this by setting the XTDO_PATH environment variable. I store mine in Dropbox.
39
+
40
+ == Why?
41
+
42
+ I used to use a small subset of Things.app, and I really dug the workflow. I spend my life on the command-line though. Existing command line apps didn't quite match my workflow, or were too slow.
43
+
44
+ Xtdo is fast in the sense that you type very little to make it do things, but also in that it is extremely quick to respond to commands - you will never notice any delay.
45
+
46
+ == Compatibility
47
+
48
+ Requires ruby 1.9.2. Will not run on any variant of 1.8.
49
+
50
+ == Developing
51
+
52
+ Refactors accepted, as long as they don't turn everything into a First Class Object. I see no reason why this code should require more than one file. I will likely not accept any features that I will not use personally.
53
+
54
+ There are no runtime external dependencies. Let's keep it that way.
55
+
56
+ == Status
57
+
58
+ Almost, but not quite, useable. This file is the only documentation you'll get currently.
@@ -0,0 +1,10 @@
1
+ desc 'Default: run specs.'
2
+ task :default => :spec
3
+
4
+ # RSpec provided helper doesn't like me, for now just run it myself
5
+ desc "Run specs"
6
+ task :spec do
7
+ commands = []
8
+ commands << "bundle exec rspec spec/*_spec.rb"
9
+ exec commands.join(" && ")
10
+ end
data/TODO ADDED
@@ -0,0 +1,4 @@
1
+ - gemspec
2
+ - start using it
3
+ - help on the command line
4
+ - better UI feedback
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
4
+
5
+ require 'xtdo'
6
+ file = ENV['XTDO_PATH'] || "~/.xtdo"
7
+ result = Xtdo.run file, ARGV.join(" ")
8
+ puts result if result && result.is_a?(String) && result.length > 0
@@ -0,0 +1,26 @@
1
+ autoload -U compinit
2
+ compinit
3
+
4
+ _xtdo() {
5
+ # TODO: Complete a bare 'xtdo' with commands + description
6
+
7
+ if [[ $words[2] == b ]]; then
8
+ if (( CURRENT == 4 )); then
9
+ compadd `bin/xtdo l c`
10
+ fi
11
+ fi
12
+
13
+ if [[ $words[2] == d ]]; then
14
+ if (( CURRENT == 3 )); then
15
+ compadd `bin/xtdo l c`
16
+ fi
17
+ fi
18
+
19
+ if [[ $words[2] == r && $words[3] == d ]]; then
20
+ if (( CURRENT == 4 )); then
21
+ compadd `bin/xtdo r c`
22
+ fi
23
+ fi
24
+ }
25
+
26
+ compdef _xtdo xtdo
@@ -0,0 +1,203 @@
1
+ require 'date'
2
+ require 'yaml'
3
+
4
+ class Xtdo
5
+ def self.run(store, operation)
6
+ operation = operation.split(/\s+/)
7
+ verb = operation.shift
8
+
9
+ # tasks = {
10
+ # 'T1' => {:scheduled => Date.today}
11
+ # }
12
+ store = File.expand_path(store)
13
+ tasks = if File.exists?(store)
14
+ YAML.load(File.open(store))
15
+ else
16
+ {}
17
+ end
18
+ tasks[:tasks] ||= {}
19
+ tasks[:recurring] ||= {}
20
+
21
+ manager = Xtdo.new(tasks)
22
+
23
+ ret = case verb
24
+ when 'a' then # A is for add!
25
+ manager.add operation.join(' ')
26
+ when 'b' then # B is for bump!
27
+ manager.bump operation.join(' ')
28
+ when 'd' then # D is for done!
29
+ manager.done operation.join(' ')
30
+ when 'l' then # L is for list!
31
+ case operation[0]
32
+ when 'a' then
33
+ manager.list [:today, :next, :scheduled]
34
+ when 'c' then
35
+ manager.list [:today, :next, :scheduled], :format => :completion
36
+ else
37
+ manager.list [:today]
38
+ end
39
+ when 'r' then # R is for recur!
40
+ manager.recur operation.join(' ')
41
+ end
42
+
43
+ manager.save(store)
44
+ ret
45
+ end
46
+
47
+ attr_reader :tasks, :recurring
48
+
49
+ def initialize(tasks)
50
+ @tasks = tasks[:tasks]
51
+ @recurring = tasks[:recurring]
52
+ end
53
+
54
+ def parse_relative_time(number, period)
55
+ adj = {
56
+ 'd' => 1,
57
+ 'w' => 7,
58
+ 'm' => 30,
59
+ 'y' => 365
60
+ }[period] || 1
61
+ Date.today + adj * number
62
+ end
63
+
64
+ def add(task)
65
+ number, period, task = /^(?:(\d+)([dwmy])? )?(.*)/.match(task).captures
66
+ @tasks[make_key task] = {:name => task}
67
+ @tasks[make_key task][:scheduled] = parse_relative_time(number.to_i, period) if number
68
+ end
69
+
70
+ def bump(task)
71
+ number, period, task = /^(?:(\d+)([dwmy])? )?(.*)/.match(task).captures
72
+ if !number
73
+ "Invalid time"
74
+ elsif tasks[make_key task]
75
+ tasks[make_key task][:scheduled] = parse_relative_time(number.to_i, period)
76
+ else
77
+ "No such task"
78
+ end
79
+ end
80
+
81
+ def done(task)
82
+ if tasks.delete(make_key task)
83
+ "Task done"
84
+ else
85
+ "No such task"
86
+ end
87
+ end
88
+
89
+ def list(groups, opts = {})
90
+ # Check for recurring
91
+ recurring.each do |name, task|
92
+ if task[:next] <= Date.today
93
+ tasks[make_key name] = {:scheduled => Date.today, :name => task[:name] }
94
+ number, period, start, _ = self.class.extract_recur_tokens(task[:period] + ' ' + name)
95
+
96
+ task[:next] = self.class.calculate_starting_day(Date.today, number, period, start)
97
+ end
98
+ end
99
+
100
+ if opts[:format] == :completion
101
+ tasks.keys.join "\n"
102
+ else
103
+ # Print groups
104
+ task_selector = {
105
+ :today => lambda {|x| x && x <= Date.today },
106
+ :next => lambda {|x| !x },
107
+ :scheduled => lambda {|x| x && x > Date.today }
108
+ }
109
+
110
+ groups.map do |group|
111
+ t = tasks.select {|name, opts| task_selector[group][opts[:scheduled]] }
112
+ next if t.empty?
113
+ "===== #{group.to_s.upcase}\n" + t.map { |name, attrs|
114
+ attrs[:name]
115
+ }.join("\n")
116
+ end.join("\n")
117
+ end
118
+ end
119
+
120
+ def recur(task)
121
+ tokens = task.split(/\s+/)
122
+ verb = tokens.shift
123
+ task = tokens.join(' ')
124
+ case verb
125
+ when 'a' then
126
+ number, period, start, name = self.class.extract_recur_tokens(task)
127
+
128
+ period_string = "#{number}#{period}"
129
+ period_string += ",#{start}" if start
130
+ recurring[make_key name] = {
131
+ :name => name,
132
+ :next => Date.today + 1,
133
+ :period => period_string
134
+ }
135
+ when 'd' then
136
+ if recurring.delete make_key(task)
137
+ "Recurring task removed"
138
+ else
139
+ "No such recurring task"
140
+ end
141
+ when 'l' then
142
+ "===== RECURRING\n" + recurring.map do |name, task|
143
+ "%-6s%s" % [task[:period], task[:name]]
144
+ end.join("\n")
145
+ when 'c' then
146
+ recurring.keys.join "\n"
147
+ end
148
+ end
149
+
150
+ def make_key(name)
151
+ name.gsub /[^a-zA-Z0-9,.]/, '-'
152
+ end
153
+
154
+ def self.calculate_starting_day(date, number, period, start = nil)
155
+ days = %w(sun mon tue wed thu fri sat)
156
+ number = (number || 1).to_i
157
+
158
+ adjust = case period
159
+ when 'd' then 1
160
+ when 'w' then
161
+ if days.index(start)
162
+ (days.index(start) - date.wday) % 7 + (number - 1) * 7
163
+ end
164
+ when 'm' then
165
+ start = start.to_i
166
+ if start > 0
167
+ year = date.year
168
+ if start.to_i <= date.day
169
+ month = date.month + 1
170
+ if month > 12
171
+ month = 1
172
+ year += 1
173
+ end
174
+ else
175
+ month = date.month
176
+ end
177
+ Date.new(year, month, start.to_i) - date
178
+ end
179
+ end
180
+ if adjust
181
+ if adjust == 0
182
+ date + 7
183
+ else
184
+ date + adjust
185
+ end
186
+ end
187
+ end
188
+
189
+ def self.extract_recur_tokens(task)
190
+ /^(\d+)([dwmy])?(?:,(#{days.join('|')}|\d{1,2}))? (.*)/.match(task).captures
191
+ end
192
+ def self.days
193
+ days = %w(sun mon tue wed thu fri sat)
194
+ end
195
+
196
+ def save(file)
197
+ File.open(file, 'w') {|f| f.write({
198
+ :tasks => tasks,
199
+ :recurring => recurring,
200
+ :version => 1
201
+ }.to_yaml) }
202
+ end
203
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ feature 'add' do
4
+ scenario 'to next' do
5
+ t('a T1')
6
+ t('l a').should have_task('T1', :in => :next)
7
+ end
8
+
9
+ scenario 'to today' do
10
+ t('a 0 T1')
11
+ t('l a').should have_task('T1', :in => :today)
12
+ end
13
+
14
+ scenario 'to scheduled' do
15
+ t('a 1w T1')
16
+ t('l a').should have_task('T1', :in => :scheduled, :for => Date.today + 7)
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ feature 'bumping tasks' do
4
+ scenario 'moving around' do
5
+ t('a T1')
6
+ t('l').should_not have_task('T1', :in => :today)
7
+ t('b 0 T1')
8
+ t('l').should have_task('T1', :in => :today)
9
+ t('b 1 T1')
10
+ t('l a').should have_task('T1', :in => :scheduled, :for => Date.today + 1)
11
+ t('b 1w T1')
12
+ t('l a').should have_task('T1', :in => :scheduled, :for => Date.today + 7)
13
+ end
14
+
15
+ scenario 'invalid input' do
16
+ t('b 0 T1').should == 'No such task'
17
+ t('b T1').should == 'Invalid time'
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ feature 'completion' do
4
+ scenario 'normal' do
5
+ t('a 0 T1') # Today
6
+ t('a T2') # Next
7
+ t('a 1 T3') # Scheduled
8
+
9
+ t('l c').should have_completion_task('T1')
10
+ t('l c').should have_completion_task('T2')
11
+ t('l c').should have_completion_task('T3')
12
+ end
13
+
14
+ scenario 'recurring' do
15
+ t('r a 1d T1')
16
+ t('r c').should have_completion_task('T1')
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ feature 'done' do
4
+ scenario 'marking off tasks' do
5
+ t('a T1')
6
+ t('l a').should have_task('T1')
7
+ t('d T1').should == "Task done"
8
+ t('l').should_not have_task('T1')
9
+ end
10
+
11
+ scenario 'invalid input' do
12
+ t('d T1').should == "No such task"
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ feature 'list' do
4
+ scenario 'options' do
5
+ t('a 0 T1') # Today
6
+ t('a T2') # Next
7
+ t('a 1 T3') # Scheduled
8
+
9
+ # Today
10
+ t('l').should have_task('T1')
11
+ t('l').should_not have_task('T2')
12
+ t('l').should_not have_task('T3')
13
+
14
+ # All
15
+ t('l a').should have_task('T1')
16
+ t('l a').should have_task('T2')
17
+ t('l a').should have_task('T3')
18
+ end
19
+ end
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+
3
+ feature 'recurring' do
4
+ describe 'extract_r_tokens' do
5
+ it { Xtdo.extract_recur_tokens('1d T1').should == ['1', 'd', nil, 'T1'] }
6
+ it { Xtdo.extract_recur_tokens('1w,thu T1').should == ['1', 'w', 'thu', 'T1'] }
7
+ it { Xtdo.extract_recur_tokens('1m,2 T1').should == ['1', 'm', '2', 'T1'] }
8
+ it { Xtdo.extract_recur_tokens('1m,20 T1').should == ['1', 'm', '20', 'T1'] }
9
+ end
10
+
11
+ describe 'starting day' do
12
+ let(:today) { Date.new(2010,11,22) } # Monday
13
+
14
+ it { Xtdo.calculate_starting_day(today, 1, 'd').should == today + 1 }
15
+ it { Xtdo.calculate_starting_day(today, 1, 'w', 'mon').should == today + 7}
16
+ it { Xtdo.calculate_starting_day(today, 1, 'w', 'tue').should == today + 1}
17
+ it { Xtdo.calculate_starting_day(today, 1, 'w', 'sun').should == today + 6}
18
+ it { Xtdo.calculate_starting_day(today, 1, 'w', '0').should == nil }
19
+ it { Xtdo.calculate_starting_day(today, 1, 'm', '1').should == Date.new(2010,12,1)}
20
+ it { Xtdo.calculate_starting_day(today, 1, 'm', '22').should == Date.new(2010,12,22)}
21
+ it { Xtdo.calculate_starting_day(today, 1, 'm', '23').should == Date.new(2010,11,23)}
22
+ it { Xtdo.calculate_starting_day(today, 1, 'm', '0').should == nil }
23
+ end
24
+
25
+ let(:today) { Date.new(2010,11,17) } # Monday
26
+
27
+ scenario 'list' do
28
+ t('r a 1d T1')
29
+ t('r l').should have_task('1d T1')
30
+ end
31
+
32
+ scenario 'daily' do
33
+
34
+ time_travel today
35
+ t('r a 1d T1')
36
+
37
+ time_travel today + 1
38
+ t('l').should have_task('T1')
39
+ t('b 1 T1')
40
+ t('l').should_not have_task('T1')
41
+ t('d T1')
42
+ t('l').should_not have_task('T1')
43
+
44
+ time_travel today + 2
45
+ t('l').should have_task('T1')
46
+ t('d T1')
47
+
48
+ time_travel today + 4
49
+ t('l').should have_task('T1')
50
+ end
51
+
52
+ scenario 'remove' do
53
+ time_travel today
54
+ t('r a 1d T1')
55
+ t('r d T1')
56
+ time_travel today + 1
57
+ t('l a').should_not have_task('T1')
58
+ end
59
+
60
+ scenario 'weekly' do
61
+ time_travel today
62
+ t('r a 1w,thu T1')
63
+
64
+ time_travel Date.new(2010,11,18) # Thursday
65
+ t('l').should have_task('T1')
66
+ t('b 1 T1')
67
+ t('l').should_not have_task('T1')
68
+ t('d T1')
69
+ t('l').should_not have_task('T1') # Do not recreate the task
70
+
71
+ time_travel Date.new(2010,11,25) # Next Thursday
72
+ t('l').should have_task('T1')
73
+ t('d T1')
74
+
75
+ time_travel Date.new(2010,12,3) # Friday after
76
+ t('l').should have_task('T1')
77
+ end
78
+
79
+ scenario 'monthly' do
80
+ time_travel today
81
+ t('r a 1m,3 T1')
82
+
83
+ time_travel Date.new(2010,12,3)
84
+ t('l').should have_task('T1')
85
+ t('b 1 T1')
86
+ t('l').should_not have_task('T1')
87
+ t('d T1')
88
+ t('l').should_not have_task('T1')
89
+
90
+ time_travel Date.new(2011,1,4)
91
+ t('l').should have_task('T1')
92
+ end
93
+ end
@@ -0,0 +1,112 @@
1
+ require 'rspec'
2
+ require 'tempfile'
3
+ require 'date'
4
+ require 'timecop'
5
+
6
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
7
+ require 'xtdo'
8
+
9
+ RSpec.configure do |config|
10
+ config.before :each do
11
+ @file = File.join(Dir.tmpdir, 'xtdo_test.yml')
12
+ end
13
+
14
+ config.after :each do
15
+ File.unlink @file if File.exists? @file
16
+ Timecop.return
17
+ end
18
+
19
+ def t(operation)
20
+ Xtdo.run @file, operation
21
+ end
22
+
23
+ def time_travel(time)
24
+ Timecop.travel(time)
25
+ end
26
+ end
27
+
28
+ RSpec::Matchers.define(:have_completion_task) do |task|
29
+ match do |result|
30
+ parse(result).include? task
31
+ end
32
+
33
+ failure_message_for_should do |result|
34
+ buffer = "Expected to find #{task}. Instead found:\n"
35
+ parse(result).each do |found|
36
+ buffer += " #{found}"
37
+ end
38
+ buffer
39
+ end
40
+
41
+ def parse(data)
42
+ data.to_s.lines.map(&:chomp)
43
+ end
44
+ end
45
+
46
+ RSpec::Matchers.define(:have_task) do |task, opts = {}|
47
+ match do |result|
48
+ parsed = parse(result)
49
+ tasks = opts[:in] ? parsed[opts[:in]] : parsed.values.flatten
50
+ tasks && tasks.include?(task)
51
+ end
52
+
53
+ failure_message_for_should do |result|
54
+ in_string = " in #{opts[:in]}" if opts[:in]
55
+ buffer = "Expected to find #{task}#{in_string}. Instead found:\n"
56
+ parse(result).each do |group, tasks|
57
+ buffer += " #{group}\n"
58
+ buffer += tasks.map {|x| " #{x}" }.join("\n")
59
+ buffer += "\n"
60
+ end
61
+ buffer
62
+ end
63
+
64
+ failure_message_for_should_not do |result|
65
+ in_string = " in #{opts[:in]}" if opts[:in]
66
+ buffer = "Expected not to find #{task}#{in_string}:\n"
67
+ parse(result).each do |group, tasks|
68
+ buffer += " #{group}\n"
69
+ buffer += tasks.map {|x| " #{x}" }.join("\n")
70
+ buffer += "\n"
71
+ end
72
+ buffer
73
+ end
74
+
75
+ def parse(data)
76
+ @parsed ||= begin
77
+ group = nil
78
+ data.to_s.lines.inject({}) do |a, line|
79
+ if line =~ /^=+ (.+)/
80
+ group = line[/^=+ (.+)/, 1].downcase.to_sym
81
+ else
82
+ a[group] ||= []
83
+ a[group] << line.chomp
84
+ end
85
+ a
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ module Steak
92
+ module AcceptanceExampleGroup
93
+ def self.included(base)
94
+ base.instance_eval do
95
+ alias scenario example
96
+ alias background before
97
+ end
98
+ end
99
+ end
100
+
101
+ module DSL
102
+ def feature(*args, &block)
103
+ args << {} unless args.last.is_a?(Hash)
104
+ args.last.update :type => :acceptance, :steak => true, :caller => caller
105
+ describe(*args, &block)
106
+ end
107
+ end
108
+ end
109
+
110
+ extend Steak::DSL
111
+
112
+ RSpec.configuration.include Steak::AcceptanceExampleGroup, :type => :acceptance
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xtdo
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ version: "0.2"
9
+ platform: ruby
10
+ authors:
11
+ - Xavier Shay
12
+ autorequire:
13
+ bindir: bin
14
+ cert_chain: []
15
+
16
+ date: 2010-11-28 00:00:00 +11:00
17
+ default_executable:
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
20
+ name: rspec
21
+ prerelease: false
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 2
29
+ - 1
30
+ - 0
31
+ version: 2.1.0
32
+ type: :development
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: timecop
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ segments:
43
+ - 0
44
+ version: "0"
45
+ type: :development
46
+ version_requirements: *id002
47
+ description:
48
+ email:
49
+ - hello@xaviershay.com
50
+ executables:
51
+ - xtdo
52
+ extensions: []
53
+
54
+ extra_rdoc_files: []
55
+
56
+ files:
57
+ - spec/add_spec.rb
58
+ - spec/bump_spec.rb
59
+ - spec/completion_spec.rb
60
+ - spec/done_spec.rb
61
+ - spec/list_spec.rb
62
+ - spec/recurring_spec.rb
63
+ - spec/spec_helper.rb
64
+ - lib/xtdo.rb
65
+ - bin/xtdo
66
+ - bin/xtdo_completion.zsh
67
+ - README.rdoc
68
+ - HISTORY
69
+ - LICENSE
70
+ - Rakefile
71
+ - TODO
72
+ has_rdoc: false
73
+ homepage: http://github.com/xaviershay/xtdo
74
+ licenses: []
75
+
76
+ post_install_message:
77
+ rdoc_options: []
78
+
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ segments:
95
+ - 0
96
+ version: "0"
97
+ requirements: []
98
+
99
+ rubyforge_project:
100
+ rubygems_version: 1.3.7
101
+ signing_key:
102
+ specification_version: 3
103
+ summary: Minimal and fast command line todo manager
104
+ test_files: []
105
+