tlist 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|