tlist 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +20 -0
- data/LICENSE.txt +17 -0
- data/README.md +226 -0
- data/Rakefile +1 -0
- data/TODO.md +11 -0
- data/bin/tlist +297 -0
- data/test/add.t +50 -0
- data/test/delete.t +73 -0
- data/test/files/empty.txt +0 -0
- data/test/files/empty_labels.txt +7 -0
- data/test/files/many_labels.txt +17 -0
- data/test/files/one_label_and_unsorted.txt +7 -0
- data/test/files/only_one_label.txt +4 -0
- data/test/files/only_unsorted.txt +5 -0
- data/test/print.t +86 -0
- data/tlist.gemspec +20 -0
- data/tlist_completion.bash +23 -0
- metadata +99 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.gem
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
tlist (0.0.1)
|
5
|
+
colorize
|
6
|
+
methodchain
|
7
|
+
trollop
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: http://rubygems.org/
|
11
|
+
specs:
|
12
|
+
colorize (0.5.8)
|
13
|
+
methodchain (0.4.2)
|
14
|
+
trollop (1.16.2)
|
15
|
+
|
16
|
+
PLATFORMS
|
17
|
+
ruby
|
18
|
+
|
19
|
+
DEPENDENCIES
|
20
|
+
tlist!
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Copyright (c) 2011, Caleb Spare
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that
|
5
|
+
the following conditions are met:
|
6
|
+
|
7
|
+
Redistributions of source code must retain the above copyright notice, this list of conditions and the
|
8
|
+
following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of
|
9
|
+
conditions and the following disclaimer in the documentation and/or other materials provided with the
|
10
|
+
distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
11
|
+
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
12
|
+
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
13
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
14
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
15
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
16
|
+
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
17
|
+
DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,226 @@
|
|
1
|
+
tlist
|
2
|
+
=====
|
3
|
+
|
4
|
+
tlist is an extremely lightweight TODO list manager for your commandline. It was written to be the bare
|
5
|
+
minimum that would meet my particular needs. It takes its inspiration heavily from
|
6
|
+
[t](http://stevelosh.com/projects/t/), a wonderful tool in the same vein written by the amazing Steve Losh
|
7
|
+
(incidentally, owner of the prettiest website on the interwebs). tlist also borrows a workflow from
|
8
|
+
[Things](http://culturedcode.com/things/) (as described to me; I've never actually used it).
|
9
|
+
|
10
|
+
The are three primary features that separate tlist from t:
|
11
|
+
|
12
|
+
1. tlist is optimized for the single-person use case. The order of the list can be significant (it can
|
13
|
+
represent a priority).
|
14
|
+
2. tlist has a built-in notion of labels for each task.
|
15
|
+
3. tlist can be used with a two-step workflow. Each new task is put into the "Unsorted" category, and then
|
16
|
+
later the unsorted tasks may all be sorted at once.
|
17
|
+
|
18
|
+
Point #3 is why tlist is very efficient in my workflow. When a new task occurs to me, I can add it to my queue
|
19
|
+
of new tasks with almost no overhead (this avoids interruping my current train of thought). Later, I can sort
|
20
|
+
through my new tasks and organize them all at once.
|
21
|
+
|
22
|
+
Installation
|
23
|
+
------------
|
24
|
+
|
25
|
+
The easiest way to install tlist is to install the gem
|
26
|
+
|
27
|
+
gem install tlist
|
28
|
+
|
29
|
+
This will install all the dependencies for you. It requires Ruby (I've tested with 1.8.7 and 1.9.2).
|
30
|
+
|
31
|
+
You can also install the latest version from Github by cloning the
|
32
|
+
[repository](https://github.com/cespare/tlist) and then adding `tlist` to your path. You'll need to make sure
|
33
|
+
the following dependencies (gems) are installed:
|
34
|
+
|
35
|
+
* trollop
|
36
|
+
* colorize
|
37
|
+
* methodchain
|
38
|
+
|
39
|
+
After you've got tlist, you should set up a few things in your `.bashrc`/`.bash_profile`. First, specify your
|
40
|
+
chosen todo-list file as follows:
|
41
|
+
|
42
|
+
export TLIST_FILE=path/to/tlist_file.txt
|
43
|
+
|
44
|
+
(Pro-tip: put this in your Dropbox folder as a quick way to share your todo list between multiple computers.)
|
45
|
+
Next, you can get tab completion by sourcing the included file. Download `tlist_completion.bash` and put it
|
46
|
+
somewhere on your machine. Then
|
47
|
+
|
48
|
+
source path/to/tlist_completion.bash
|
49
|
+
|
50
|
+
(This is optional.) Finally, if you want to get really crazy, you might alias `tlist` to something shorter. I
|
51
|
+
do this as follows:
|
52
|
+
|
53
|
+
alias t=tlist
|
54
|
+
complete -F _tlist t
|
55
|
+
|
56
|
+
Now you should be ready to go. Make sure it works by checking that you see the following output at your
|
57
|
+
command line (be sure to source your `.bashrc` or open a new terminal first):
|
58
|
+
|
59
|
+
$ tlist
|
60
|
+
Unsorted
|
61
|
+
(No tasks)
|
62
|
+
|
63
|
+
Usage
|
64
|
+
-----
|
65
|
+
|
66
|
+
You can see the full list of options by typing
|
67
|
+
|
68
|
+
$ tlist --help
|
69
|
+
|
70
|
+
However, there are only a few that you'll typically need. I'll guide you through a typical workflow using
|
71
|
+
those commands.
|
72
|
+
|
73
|
+
Let's start by adding a few tasks.
|
74
|
+
|
75
|
+
$ tlist -a "Go to the bank"
|
76
|
+
Added task 'Go to the bank' to label 'Unsorted'.
|
77
|
+
$ tlist -a "Set up dentist appointment"
|
78
|
+
Added task 'Set up dentist appointment' to label 'Unsorted'.
|
79
|
+
$ tlist -a "Buy milk"
|
80
|
+
Added task 'Buy milk' to label 'Unsorted'.
|
81
|
+
$ tlist -a "Buy coffee"
|
82
|
+
Added task 'Buy coffee' to label 'Unsorted'.
|
83
|
+
|
84
|
+
(Note: tlist will join all arguments together, so you can type `$ tlist -a Go to the bank` with no quotes.
|
85
|
+
However, if you've got any `'` characters in the task it will be interpreted as the start of a single-quoted
|
86
|
+
string by bash, so it's good to get in the habit of quoting the whole task.) You can inspect your tasks by
|
87
|
+
simply using `tlist`:
|
88
|
+
|
89
|
+
$ tlist
|
90
|
+
Unsorted
|
91
|
+
* Go to the bank
|
92
|
+
* Set up dentist appointment
|
93
|
+
* Buy milk
|
94
|
+
* Buy coffee
|
95
|
+
|
96
|
+
Now, adding new tasks is extremely quick, but they go to this "Unsorted" label. Now let's suppose you have a
|
97
|
+
few spare minutes, so you decide to organize all your tasks.
|
98
|
+
|
99
|
+
$ tlist -s
|
100
|
+
Sorting 4 unsorted tasks.
|
101
|
+
|
102
|
+
Unsorted task:
|
103
|
+
> Go to the bank
|
104
|
+
No existing labels.
|
105
|
+
Other options:
|
106
|
+
n Create a new label
|
107
|
+
s Skip this task
|
108
|
+
q Abort
|
109
|
+
Select an option: n
|
110
|
+
Enter new label name: Errands
|
111
|
+
Added task 'Go to the bank' to new label 'Errands'.
|
112
|
+
Removed task 'Go to the bank' from label 'Unsorted'.
|
113
|
+
|
114
|
+
Unsorted task:
|
115
|
+
> Set up dentist appointment
|
116
|
+
...
|
117
|
+
|
118
|
+
I'm ommitting extra output here, but you can see how this interactive sorting step lets you quickly sort the
|
119
|
+
tasks you made earlier. Let's suppose that your current task list looks like this:
|
120
|
+
|
121
|
+
$ tlist
|
122
|
+
Errands
|
123
|
+
1. Go to the bank
|
124
|
+
2. Set up dentist appointment
|
125
|
+
|
126
|
+
Groceries
|
127
|
+
1. Buy milk
|
128
|
+
2. Buy coffee
|
129
|
+
|
130
|
+
Unsorted
|
131
|
+
(No tasks)
|
132
|
+
|
133
|
+
We can easily remove tasks:
|
134
|
+
|
135
|
+
$ tlist -d
|
136
|
+
Errands
|
137
|
+
1. Go to the bank
|
138
|
+
2. Set up dentist appointment
|
139
|
+
|
140
|
+
Groceries
|
141
|
+
3. Buy milk
|
142
|
+
4. Buy coffee
|
143
|
+
|
144
|
+
Select a task to delete (1-4 or q to abort): 3
|
145
|
+
Removed task 'Buy milk' from label 'Groceries'.
|
146
|
+
|
147
|
+
It's also easy to restrict many of these operations to a specific label:
|
148
|
+
|
149
|
+
$ tlist -l Groceries -a Buy yogurt
|
150
|
+
Added task 'Buy yogurt' to label 'Groceries'.
|
151
|
+
$ tlist -l Groceries -a Buy fruit
|
152
|
+
Added task 'Buy fruit' to label 'Groceries'.
|
153
|
+
$ tlist -l Groceries
|
154
|
+
Groceries
|
155
|
+
1. Buy coffee
|
156
|
+
2. Buy yogurt
|
157
|
+
3. Buy fruit
|
158
|
+
|
159
|
+
$ tlist -l Groceries -d
|
160
|
+
Groceries
|
161
|
+
1. Buy coffee
|
162
|
+
2. Buy yogurt
|
163
|
+
3. Buy fruit
|
164
|
+
|
165
|
+
Select a task to delete (1-3 or q to abort): 2
|
166
|
+
Removed task 'Buy yogurt' from label 'Groceries'.
|
167
|
+
|
168
|
+
Advanced usage
|
169
|
+
--------------
|
170
|
+
|
171
|
+
In addition to these basic operations, there is a concept of a "working" label from which you are actively
|
172
|
+
completing items. You can get and set your working label as follows:
|
173
|
+
|
174
|
+
$ tlist -g
|
175
|
+
No working label set. Use $ tlist --current to set a working label.
|
176
|
+
$ tlist -c Errands
|
177
|
+
Set current working label to 'Errands'.
|
178
|
+
$ tlist -g
|
179
|
+
Errands
|
180
|
+
|
181
|
+
Once you've set a working label, you can quickly pop items from the list and delete them as you finish with
|
182
|
+
the `--next` and `--finish` commands:
|
183
|
+
|
184
|
+
$ tlist -n
|
185
|
+
Go to the bank
|
186
|
+
$ tlist -f
|
187
|
+
Removed task 'Go to the bank' from label 'Errands'.
|
188
|
+
$ tlist -n
|
189
|
+
Set up dentist appointment
|
190
|
+
$ tlist -f
|
191
|
+
Removed task 'Set up dentist appointment' from label 'Errands'.
|
192
|
+
Removed empty label 'Errands'.
|
193
|
+
|
194
|
+
One last command that is very useful is `--edit`. Just type
|
195
|
+
|
196
|
+
$ tlist -e
|
197
|
+
|
198
|
+
and the tlist file will be opened in your `$EDITOR`. Be careful not to change the formatting. The blank lines
|
199
|
+
are significant, as are the colons (signify labels) and `>` (signifies current label). It's often quickest to
|
200
|
+
do complex reorganization of your tasks in your editor, rather than the one-by-one operations that tlist
|
201
|
+
natively supports.
|
202
|
+
|
203
|
+
Tests
|
204
|
+
-----
|
205
|
+
|
206
|
+
The tests for tlist are in the `tests/` directory, and they're written using
|
207
|
+
[cram](https://bitheap.org/cram/). Install cram and then run all the tests with
|
208
|
+
|
209
|
+
$ cram test/*.t
|
210
|
+
|
211
|
+
Contributions
|
212
|
+
-------------
|
213
|
+
|
214
|
+
If you'd like to hack on tlist, go ahead and [fork the project on github](https://github.com/cespare/tlist).
|
215
|
+
Open a pull request if you'd like me to incorporate your changes, but keep in mind that I'd like to keep tlist
|
216
|
+
pretty lightweight so I may not accept large new features.
|
217
|
+
|
218
|
+
Author
|
219
|
+
------
|
220
|
+
|
221
|
+
tlist was written by Caleb Spare (cespare on Github).
|
222
|
+
|
223
|
+
License
|
224
|
+
-------
|
225
|
+
|
226
|
+
[BSD 2-clause license](http://opensource.org/licenses/bsd-license.php); see LICENSE.txt.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/TODO.md
ADDED
data/bin/tlist
ADDED
@@ -0,0 +1,297 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "trollop"
|
5
|
+
require "colorize"
|
6
|
+
require "methodchain"
|
7
|
+
|
8
|
+
UNSORTED_NAME = "Unsorted"
|
9
|
+
|
10
|
+
def print_numbered(item, number, total_items)
|
11
|
+
number_width = total_items.to_s.size + 1
|
12
|
+
puts "#{"#{number}.".ljust(number_width)} #{item}"
|
13
|
+
end
|
14
|
+
|
15
|
+
class Tlist
|
16
|
+
attr_reader :label_array, :label_hash
|
17
|
+
attr_accessor :current
|
18
|
+
def initialize(filename)
|
19
|
+
@filename = filename
|
20
|
+
# I'd like to have ordered hashes instead of keeping the list + the hash, but I want 1.8 compatibility.
|
21
|
+
@label_array = [] # Array of label names
|
22
|
+
@label_hash = {} # Label name => Array of tasks
|
23
|
+
@current = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_label(name)
|
27
|
+
raise "Invalid name '#{name}'" if name.start_with? "> "
|
28
|
+
@label_array << name
|
29
|
+
@label_hash[name] = []
|
30
|
+
end
|
31
|
+
|
32
|
+
def delete_label(name)
|
33
|
+
raise "No such label '#{name}'" unless @label_hash.include? name
|
34
|
+
@label_array.delete name
|
35
|
+
@label_hash.delete name
|
36
|
+
end
|
37
|
+
|
38
|
+
def resolve_label(label_part)
|
39
|
+
return UNSORTED_NAME unless label_part
|
40
|
+
selected_labels = @label_array.select { |l| l.downcase.index label_part.downcase }
|
41
|
+
if selected_labels.size > 1
|
42
|
+
raise "Error: '#{label}' matches multiple labels: #{selected_labels.join(', ')}."
|
43
|
+
end
|
44
|
+
selected_labels[0]
|
45
|
+
end
|
46
|
+
|
47
|
+
def set_current(label_part)
|
48
|
+
label = resolve_label label_part
|
49
|
+
raise "Error: no label matches '#{label_part}'." unless label
|
50
|
+
@current = label
|
51
|
+
puts "Set current working label to '#{label}'."
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_task(label, task)
|
55
|
+
raise "Invalid task name." if task.strip.empty?
|
56
|
+
selected_label = resolve_label(label)
|
57
|
+
add_label(label) unless selected_label
|
58
|
+
label = selected_label || label
|
59
|
+
if @label_hash[label].include? task
|
60
|
+
raise "Error: '#{label}' already includes a task '#{task}'."
|
61
|
+
end
|
62
|
+
@label_hash[label] << task
|
63
|
+
puts "Added task '#{task}' to #{"new " unless selected_label}label '#{label}'."
|
64
|
+
end
|
65
|
+
|
66
|
+
def delete_task(label, task)
|
67
|
+
selected_label = resolve_label(label)
|
68
|
+
raise "Error: No such label '#{label}'." unless selected_label
|
69
|
+
unless @label_hash[selected_label].include? task
|
70
|
+
raise "Error: No such task '#{task}' in label '#{selected_label}'."
|
71
|
+
end
|
72
|
+
@label_hash[selected_label].delete task
|
73
|
+
puts "Removed task '#{task}' from label '#{selected_label}'."
|
74
|
+
if @label_hash[selected_label].empty? && selected_label != UNSORTED_NAME
|
75
|
+
delete_label selected_label
|
76
|
+
puts "Removed empty label '#{selected_label}'."
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def next_task
|
81
|
+
raise "No working label is set." unless @current
|
82
|
+
tasks = @label_hash[@current]
|
83
|
+
raise "No tasks left in working label '#{@current}'." if tasks.empty?
|
84
|
+
tasks[0]
|
85
|
+
end
|
86
|
+
|
87
|
+
def print_all_tasks_numbered(label)
|
88
|
+
labels = (label ? [resolve_label(label)] : @label_array).reject { |label| @label_hash[label].empty? }
|
89
|
+
return {} if labels.empty?
|
90
|
+
result = {}
|
91
|
+
task_count = @label_hash.values_at(*labels).map(&:size).reduce(&:+)
|
92
|
+
number = 0
|
93
|
+
labels.each do |label|
|
94
|
+
print_label label
|
95
|
+
@label_hash[label].each do |task|
|
96
|
+
print_numbered task, number += 1, task_count
|
97
|
+
result[number] = [label, task]
|
98
|
+
end
|
99
|
+
puts
|
100
|
+
end
|
101
|
+
result
|
102
|
+
end
|
103
|
+
|
104
|
+
def print(label)
|
105
|
+
labels = label ? [resolve_label(label)] : @label_array
|
106
|
+
labels.each do |label|
|
107
|
+
print_label label
|
108
|
+
tasks = @label_hash[label]
|
109
|
+
if tasks.empty?
|
110
|
+
puts "(No tasks)"
|
111
|
+
elsif label == UNSORTED_NAME
|
112
|
+
tasks.each { |task| puts "* #{task}" }
|
113
|
+
else
|
114
|
+
tasks.each_with_index { |task, i| print_numbered task, i + 1, tasks.size }
|
115
|
+
end
|
116
|
+
puts
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def print_label(name)
|
121
|
+
colored = name == UNSORTED_NAME ? name.blue.underline : name.green.underline
|
122
|
+
puts @current == name ? "#{colored} (Current)" : colored
|
123
|
+
end
|
124
|
+
|
125
|
+
def write_file!
|
126
|
+
File.open(@filename, "w") do |file|
|
127
|
+
@label_array.each do |label|
|
128
|
+
file.puts "#{"> " if @current == label}#{label}:"
|
129
|
+
@label_hash[label].each { |task| file.puts task }
|
130
|
+
file.puts
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.from_file(filename)
|
136
|
+
raise "Bad file: #{filename}" unless File.file? filename
|
137
|
+
tlist = Tlist.new(filename)
|
138
|
+
lines = File.open(filename).readlines.map(&:strip)
|
139
|
+
expect_label = true
|
140
|
+
lines.each_with_index do |line, i|
|
141
|
+
if line.empty?
|
142
|
+
expect_label = true
|
143
|
+
next
|
144
|
+
end
|
145
|
+
|
146
|
+
if expect_label
|
147
|
+
current, label = line.match(/^(> )?(.*):$/).to_a[1..2]
|
148
|
+
raise "Error parsing #{filename} at line #{i + 1} (expecting label)." unless label
|
149
|
+
if tlist.label_hash.include? label
|
150
|
+
raise "Error parsing #{filename} at line #{i + 1} (duplicate label)."
|
151
|
+
end
|
152
|
+
if current
|
153
|
+
if tlist.current
|
154
|
+
raise "Error parsing #{filename} at line #{i + 1} (more than one label is marked as current)."
|
155
|
+
end
|
156
|
+
tlist.current = label
|
157
|
+
end
|
158
|
+
tlist.add_label label
|
159
|
+
expect_label = false
|
160
|
+
else
|
161
|
+
tlist.label_hash[tlist.label_array.last] << line
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Keep the unsorted task list at the end.
|
166
|
+
if tlist.label_hash.include? UNSORTED_NAME
|
167
|
+
tlist.label_array.delete UNSORTED_NAME
|
168
|
+
tlist.label_array << UNSORTED_NAME
|
169
|
+
else
|
170
|
+
tlist.add_label UNSORTED_NAME
|
171
|
+
end
|
172
|
+
tlist
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
MODES = [:print, :add, :sort, :delete, :edit, :next, :finish, :current, :get_current, :unsorted, :labels]
|
177
|
+
|
178
|
+
def get_mode(options)
|
179
|
+
modes = options.keys.select { |option| MODES.include?(option) && options[option] }
|
180
|
+
if modes.size > 1
|
181
|
+
puts "Error: only one of the following may be specified:"
|
182
|
+
MODES.each { |mode| puts " --#{mode}" }
|
183
|
+
exit 1
|
184
|
+
end
|
185
|
+
modes[0] || :print
|
186
|
+
end
|
187
|
+
|
188
|
+
todo_file = ENV["TLIST_FILE"]
|
189
|
+
abort "Need to set TLIST_FILE environment variable" unless todo_file
|
190
|
+
editor = ENV["EDITOR"] || "vim"
|
191
|
+
|
192
|
+
options = Trollop::options do
|
193
|
+
opt :print, "Print tasks (can omit this flag)."
|
194
|
+
opt :add, "Add a task"
|
195
|
+
opt :sort, "Sort unsorted tasks"
|
196
|
+
opt :delete, "Delete a task"
|
197
|
+
opt :label, "Limit to a particular label (used in combination with another option)",
|
198
|
+
:default => nil, :type => String
|
199
|
+
opt :edit, "Edit the task list file in your $EDITOR (use carefully)"
|
200
|
+
opt :current, "Set the current working label", :default => nil, :type => String
|
201
|
+
opt :get_current, "Get the current working label"
|
202
|
+
opt :next, "Print the next task in the current label"
|
203
|
+
opt :finish, "Delete the next task in the current label"
|
204
|
+
opt :unsorted, "Print unsorted tasks (shorthand for --print #{UNSORTED_NAME})"
|
205
|
+
opt :labels, "Print all labels"
|
206
|
+
|
207
|
+
# TODO: Decide whether to implement something like this
|
208
|
+
#opt :finish, "Move a task to the finished list", :default => false
|
209
|
+
#opt :print_finished, "Print finished tasks.", :default => false
|
210
|
+
end
|
211
|
+
|
212
|
+
begin
|
213
|
+
mode = get_mode(options)
|
214
|
+
exec "#{editor} #{todo_file}" if mode == :edit
|
215
|
+
|
216
|
+
tlist = Tlist.from_file(todo_file)
|
217
|
+
label = options[:label]
|
218
|
+
argument = ARGV.join(" ")
|
219
|
+
|
220
|
+
case mode
|
221
|
+
when :print
|
222
|
+
tlist.print label
|
223
|
+
when :unsorted
|
224
|
+
tlist.print UNSORTED_NAME
|
225
|
+
when :add
|
226
|
+
label = tlist.add_task(label, argument)
|
227
|
+
tlist.write_file!
|
228
|
+
when :sort
|
229
|
+
unsorted_tasks = tlist.label_hash[UNSORTED_NAME].clone
|
230
|
+
puts "Sorting #{unsorted_tasks.size} unsorted tasks."
|
231
|
+
unsorted_tasks.each do |task|
|
232
|
+
puts
|
233
|
+
labels = tlist.label_array - [UNSORTED_NAME]
|
234
|
+
puts "Unsorted task:"
|
235
|
+
puts "> #{task}".yellow
|
236
|
+
if labels.size >= 1
|
237
|
+
puts "Existing labels:"
|
238
|
+
labels.each_with_index { |label, i| print_numbered label, i + 1, labels.size }
|
239
|
+
else
|
240
|
+
puts "No existing labels."
|
241
|
+
end
|
242
|
+
puts "Other options:"
|
243
|
+
["n Create a new label", "s Skip this task", "q Abort"].each { |option| puts option }
|
244
|
+
print "Select an option: "
|
245
|
+
choice = STDIN.gets.strip
|
246
|
+
case choice
|
247
|
+
when *(1..labels.size).map(&:to_s)
|
248
|
+
tlist.add_task labels[choice.to_i - 1], task
|
249
|
+
tlist.delete_task UNSORTED_NAME, task
|
250
|
+
tlist.write_file!
|
251
|
+
when "n", "new"
|
252
|
+
print "Enter new label name: "
|
253
|
+
tlist.add_task STDIN.gets.strip, task
|
254
|
+
tlist.delete_task UNSORTED_NAME, task
|
255
|
+
tlist.write_file!
|
256
|
+
when "s", "skip"
|
257
|
+
when "q", "quit", "a"
|
258
|
+
exit 0
|
259
|
+
else
|
260
|
+
raise "Error: '#{choice}' unrecognized option. Aborting."
|
261
|
+
end
|
262
|
+
end
|
263
|
+
when :delete
|
264
|
+
choices = tlist.print_all_tasks_numbered label
|
265
|
+
if choices.empty?
|
266
|
+
puts "No tasks."
|
267
|
+
else
|
268
|
+
range = choices.size == 1 ? "1" : "1-#{choices.size}"
|
269
|
+
print "Select a task to delete (#{range} or q to abort): "
|
270
|
+
choice = STDIN.gets.strip
|
271
|
+
case choice
|
272
|
+
when *choices.keys.map(&:to_s)
|
273
|
+
tlist.delete_task *choices[choice.to_i]
|
274
|
+
tlist.write_file!
|
275
|
+
when "q", "quit", "a"
|
276
|
+
exit 0
|
277
|
+
else
|
278
|
+
raise "Error: '#{choice}' unrecognized option. Aborting."
|
279
|
+
end
|
280
|
+
end
|
281
|
+
when :next
|
282
|
+
puts tlist.next_task
|
283
|
+
when :finish
|
284
|
+
task = tlist.next_task
|
285
|
+
tlist.delete_task tlist.current, task
|
286
|
+
tlist.write_file!
|
287
|
+
when :current
|
288
|
+
tlist.set_current options[:current]
|
289
|
+
tlist.write_file!
|
290
|
+
when :get_current
|
291
|
+
puts tlist.current.else { "No working label set. Use $ tlist --current to set a working label." }
|
292
|
+
when :labels
|
293
|
+
puts tlist.label_array.join(" ")
|
294
|
+
end
|
295
|
+
rescue RuntimeError => e
|
296
|
+
abort e.message
|
297
|
+
end
|
data/test/add.t
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Test adding new tasks
|
2
|
+
|
3
|
+
# Setup
|
4
|
+
$ touch tlist_file
|
5
|
+
$ export TLIST_FILE=tlist_file
|
6
|
+
$ PATH=$TESTDIR/../bin:$PATH
|
7
|
+
|
8
|
+
# Tests
|
9
|
+
$ tlist
|
10
|
+
Unsorted
|
11
|
+
(No tasks)
|
12
|
+
|
13
|
+
$ tlist -a task 1
|
14
|
+
Added task 'task 1' to label 'Unsorted'.
|
15
|
+
$ tlist
|
16
|
+
Unsorted
|
17
|
+
* task 1
|
18
|
+
|
19
|
+
$ tlist --label "Label1" --add task 2
|
20
|
+
Added task 'task 2' to new label 'Label1'.
|
21
|
+
$ tlist
|
22
|
+
Label1
|
23
|
+
1. task 2
|
24
|
+
|
25
|
+
Unsorted
|
26
|
+
* task 1
|
27
|
+
|
28
|
+
$ tlist -l "Label1" -a task 3
|
29
|
+
Added task 'task 3' to label 'Label1'.
|
30
|
+
$ tlist
|
31
|
+
Label1
|
32
|
+
1. task 2
|
33
|
+
2. task 3
|
34
|
+
|
35
|
+
Unsorted
|
36
|
+
* task 1
|
37
|
+
|
38
|
+
$ tlist -l "Label2" -a task 4
|
39
|
+
Added task 'task 4' to new label 'Label2'.
|
40
|
+
$ tlist
|
41
|
+
Label1
|
42
|
+
1. task 2
|
43
|
+
2. task 3
|
44
|
+
|
45
|
+
Label2
|
46
|
+
1. task 4
|
47
|
+
|
48
|
+
Unsorted
|
49
|
+
* task 1
|
50
|
+
|
data/test/delete.t
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# Deleting tasks.
|
2
|
+
|
3
|
+
# Setup
|
4
|
+
$ cp $TESTDIR/files/many_labels.txt tlist_file
|
5
|
+
$ export TLIST_FILE=tlist_file
|
6
|
+
$ PATH=$TESTDIR/../bin:$PATH
|
7
|
+
|
8
|
+
# Tests
|
9
|
+
# The long line below is confusing. There is no newline after ": " because '4' is echoed in. When the user
|
10
|
+
# inputs '4' from the prompt, a newline will separate the prompt from the response.
|
11
|
+
$ echo "4" | tlist --delete
|
12
|
+
Foo
|
13
|
+
1. foo task 1
|
14
|
+
2. foo task 2
|
15
|
+
|
16
|
+
Bar
|
17
|
+
3. bar task 1
|
18
|
+
|
19
|
+
Baz
|
20
|
+
4. baz task 1
|
21
|
+
5. baz task 2
|
22
|
+
6. baz task 3
|
23
|
+
|
24
|
+
Unsorted
|
25
|
+
7. unsorted 1
|
26
|
+
8. unsorted 2
|
27
|
+
9. unsorted 3
|
28
|
+
10. unsorted 4
|
29
|
+
|
30
|
+
Select a task to delete (1-10 or q to abort): Removed task 'baz task 1' from label 'Baz'.
|
31
|
+
$ tlist
|
32
|
+
Foo
|
33
|
+
1. foo task 1
|
34
|
+
2. foo task 2
|
35
|
+
|
36
|
+
Bar
|
37
|
+
1. bar task 1
|
38
|
+
|
39
|
+
Baz
|
40
|
+
1. baz task 2
|
41
|
+
2. baz task 3
|
42
|
+
|
43
|
+
Unsorted
|
44
|
+
* unsorted 1
|
45
|
+
* unsorted 2
|
46
|
+
* unsorted 3
|
47
|
+
* unsorted 4
|
48
|
+
|
49
|
+
$ echo "3" | tlist -d -l unsor
|
50
|
+
Unsorted
|
51
|
+
1. unsorted 1
|
52
|
+
2. unsorted 2
|
53
|
+
3. unsorted 3
|
54
|
+
4. unsorted 4
|
55
|
+
|
56
|
+
Select a task to delete (1-4 or q to abort): Removed task 'unsorted 3' from label 'Unsorted'.
|
57
|
+
$ tlist
|
58
|
+
Foo
|
59
|
+
1. foo task 1
|
60
|
+
2. foo task 2
|
61
|
+
|
62
|
+
Bar
|
63
|
+
1. bar task 1
|
64
|
+
|
65
|
+
Baz
|
66
|
+
1. baz task 2
|
67
|
+
2. baz task 3
|
68
|
+
|
69
|
+
Unsorted
|
70
|
+
* unsorted 1
|
71
|
+
* unsorted 2
|
72
|
+
* unsorted 4
|
73
|
+
|
File without changes
|
data/test/print.t
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# Test printing various pre-made tlist files.
|
2
|
+
|
3
|
+
# Setup
|
4
|
+
$ PATH=$TESTDIR/../bin:$PATH
|
5
|
+
|
6
|
+
# Tests
|
7
|
+
$ TLIST_FILE=$TESTDIR/files/empty.txt tlist
|
8
|
+
Unsorted
|
9
|
+
(No tasks)
|
10
|
+
|
11
|
+
$ TLIST_FILE=$TESTDIR/files/empty.txt tlist --print
|
12
|
+
Unsorted
|
13
|
+
(No tasks)
|
14
|
+
|
15
|
+
$ TLIST_FILE=$TESTDIR/files/empty.txt tlist -p
|
16
|
+
Unsorted
|
17
|
+
(No tasks)
|
18
|
+
|
19
|
+
$ TLIST_FILE=$TESTDIR/files/empty_labels.txt tlist
|
20
|
+
Foo
|
21
|
+
(No tasks)
|
22
|
+
|
23
|
+
Bar
|
24
|
+
(No tasks)
|
25
|
+
|
26
|
+
Baz
|
27
|
+
(No tasks)
|
28
|
+
|
29
|
+
Unsorted
|
30
|
+
(No tasks)
|
31
|
+
|
32
|
+
$ TLIST_FILE=$TESTDIR/files/only_unsorted.txt tlist
|
33
|
+
Unsorted
|
34
|
+
* unsorted 1
|
35
|
+
* unsorted 2
|
36
|
+
* unsorted 3
|
37
|
+
* unsorted 4
|
38
|
+
|
39
|
+
$ TLIST_FILE=$TESTDIR/files/only_one_label.txt tlist
|
40
|
+
Foo
|
41
|
+
1. foo task 1
|
42
|
+
2. foo task 2
|
43
|
+
3. foo task 3
|
44
|
+
|
45
|
+
Unsorted
|
46
|
+
(No tasks)
|
47
|
+
|
48
|
+
$ TLIST_FILE=$TESTDIR/files/one_label_and_unsorted.txt tlist
|
49
|
+
Foo
|
50
|
+
1. foo task 1
|
51
|
+
2. foo task 2
|
52
|
+
|
53
|
+
Unsorted
|
54
|
+
* unsorted 1
|
55
|
+
* unsorted 2
|
56
|
+
|
57
|
+
$ TLIST_FILE=$TESTDIR/files/many_labels.txt tlist
|
58
|
+
Foo
|
59
|
+
1. foo task 1
|
60
|
+
2. foo task 2
|
61
|
+
|
62
|
+
Bar
|
63
|
+
1. bar task 1
|
64
|
+
|
65
|
+
Baz
|
66
|
+
1. baz task 1
|
67
|
+
2. baz task 2
|
68
|
+
3. baz task 3
|
69
|
+
|
70
|
+
Unsorted
|
71
|
+
* unsorted 1
|
72
|
+
* unsorted 2
|
73
|
+
* unsorted 3
|
74
|
+
* unsorted 4
|
75
|
+
|
76
|
+
$ TLIST_FILE=$TESTDIR/files/many_labels.txt tlist --unsorted
|
77
|
+
Unsorted
|
78
|
+
* unsorted 1
|
79
|
+
* unsorted 2
|
80
|
+
* unsorted 3
|
81
|
+
* unsorted 4
|
82
|
+
|
83
|
+
$ TLIST_FILE=$TESTDIR/files/only_one_label.txt tlist -u
|
84
|
+
Unsorted
|
85
|
+
(No tasks)
|
86
|
+
|
data/tlist.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "tlist"
|
5
|
+
s.version = "0.0.1"
|
6
|
+
s.authors = ["Caleb Spare"]
|
7
|
+
s.email = ["cespare@gmail.com"]
|
8
|
+
s.homepage = ""
|
9
|
+
s.summary = "A simple todo-list"
|
10
|
+
s.description = "tlist is a small todo-list manager with labels for tasks and a highly efficient workflow."
|
11
|
+
|
12
|
+
s.rubyforge_project = "tlist"
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
16
|
+
|
17
|
+
s.add_dependency "trollop"
|
18
|
+
s.add_dependency "colorize"
|
19
|
+
s.add_dependency "methodchain"
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
_tlist() {
|
4
|
+
local current_word
|
5
|
+
local options='--print --add --sort --delete --label --edit --current --get-current --next --finish
|
6
|
+
--unsorted --labels -p -a -s -d -l -e -c -g -n -f -u -b'
|
7
|
+
COMPREPLY=()
|
8
|
+
current_word=${COMP_WORDS[COMP_CWORD]}
|
9
|
+
if [[ ${#COMP_WORDS[@]} -gt 1 && $COMP_CWORD -gt 0 ]]; then
|
10
|
+
local previous_word=${COMP_WORDS[COMP_CWORD - 1]}
|
11
|
+
for test_label in "--label" "-l" "--current" "-c"; do
|
12
|
+
if [[ "$test_label" = "$previous_word" ]]; then
|
13
|
+
local labels=$(tlist --labels)
|
14
|
+
COMPREPLY=($(compgen -W "$labels" -- $current_word))
|
15
|
+
return 0
|
16
|
+
fi
|
17
|
+
done
|
18
|
+
fi
|
19
|
+
COMPREPLY=($(compgen -W "$options" -- $current_word))
|
20
|
+
return 0
|
21
|
+
}
|
22
|
+
|
23
|
+
complete -F _tlist tlist
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tlist
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Caleb Spare
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-09-06 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: trollop
|
16
|
+
requirement: &70256313193860 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70256313193860
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: colorize
|
27
|
+
requirement: &70256313193420 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70256313193420
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: methodchain
|
38
|
+
requirement: &70256313193000 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70256313193000
|
47
|
+
description: tlist is a small todo-list manager with labels for tasks and a highly
|
48
|
+
efficient workflow.
|
49
|
+
email:
|
50
|
+
- cespare@gmail.com
|
51
|
+
executables:
|
52
|
+
- tlist
|
53
|
+
extensions: []
|
54
|
+
extra_rdoc_files: []
|
55
|
+
files:
|
56
|
+
- .gitignore
|
57
|
+
- Gemfile
|
58
|
+
- Gemfile.lock
|
59
|
+
- LICENSE.txt
|
60
|
+
- README.md
|
61
|
+
- Rakefile
|
62
|
+
- TODO.md
|
63
|
+
- bin/tlist
|
64
|
+
- test/add.t
|
65
|
+
- test/delete.t
|
66
|
+
- test/files/empty.txt
|
67
|
+
- test/files/empty_labels.txt
|
68
|
+
- test/files/many_labels.txt
|
69
|
+
- test/files/one_label_and_unsorted.txt
|
70
|
+
- test/files/only_one_label.txt
|
71
|
+
- test/files/only_unsorted.txt
|
72
|
+
- test/print.t
|
73
|
+
- tlist.gemspec
|
74
|
+
- tlist_completion.bash
|
75
|
+
homepage: ''
|
76
|
+
licenses: []
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
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
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project: tlist
|
95
|
+
rubygems_version: 1.8.7
|
96
|
+
signing_key:
|
97
|
+
specification_version: 3
|
98
|
+
summary: A simple todo-list
|
99
|
+
test_files: []
|