todorb 0.1.0
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/LICENSE +20 -0
- data/README.markdown +20 -0
- data/VERSION +1 -0
- data/bin/todorb +5 -0
- data/lib/common/colorconstants.rb +79 -0
- data/lib/common/sed.rb +75 -0
- data/lib/todorb.rb +698 -0
- metadata +79 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Rahul Kumar
|
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.markdown
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# todo.rb
|
2
|
+
|
3
|
+
## Command-line todo manager
|
4
|
+
|
5
|
+
This is a port of my [todoapp written in shell](http://github.com/rkumar/todoapp). That todo app is similar to the famous one by Gina Trapani, except that it added various features including sub-tasks, and notes.
|
6
|
+
|
7
|
+
I may not make this application as feature-rich as the [shell version](http://github.com/rkumar/todoapp), however I needed to do this so I stop writing command-line programs using shell (portability issues across BSD and GNU versions of all commands). The shell version used `sed` extensively.
|
8
|
+
|
9
|
+
The fun things about this app, is that I am having to write ruby code that will give me sed's functionality (subset of course). Some of the stuff is present in sed.rb and is heavily being rewritten and improved as I go along.
|
10
|
+
|
11
|
+
The TODO file output is TODO2.txt and is a plain text file. A TAB separates the task number from the Task. I use task numbers, since I may refer to tasks elsewhere. Gina's app never saved a task Id but kept showing them.
|
12
|
+
|
13
|
+
The shell version, todoapp, allowed for any levels of sub-tasks. I have not yet added that, I may.
|
14
|
+
|
15
|
+
I also may fork this and make a YAML file, so that the format is standard, especially when having sub-tasks.
|
16
|
+
After this, I will port over my bug tracker, [bugzy.txt](http://github.com/rkumar/bugzy.txt), which uses a TAB delimited file. It's a cool app to use for bug tracking - you should try it out. I will possibly use sqlite instead of screwing around with a delimited file.
|
17
|
+
|
18
|
+
## Copyright
|
19
|
+
|
20
|
+
Copyright (c) 2010 Rahul Kumar. See LICENSE for details.
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/bin/todorb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Color constants so we can print onto console using print or puts
|
4
|
+
# @example
|
5
|
+
# string = "hello ruby"
|
6
|
+
# puts " #{RED}#{BOLD}#{UNDERLINE}#{string}#{CLEAR}"
|
7
|
+
#
|
8
|
+
# http://wiki.bash-hackers.org/scripting/terminalcodes
|
9
|
+
# ripped off highline gem
|
10
|
+
module ColorConstants
|
11
|
+
|
12
|
+
# Embed in a String to clear all previous ANSI sequences. This *MUST* be
|
13
|
+
# done before the program exits!
|
14
|
+
#
|
15
|
+
CLEAR = "\e[0m"
|
16
|
+
# An alias for CLEAR.
|
17
|
+
RESET = CLEAR
|
18
|
+
NORMAL = CLEAR
|
19
|
+
# Erase the current line of terminal output.
|
20
|
+
ERASE_LINE = "\e[K"
|
21
|
+
# Erase the character under the cursor.
|
22
|
+
ERASE_CHAR = "\e[P"
|
23
|
+
# The start of an ANSI bold sequence.
|
24
|
+
BOLD = "\e[1m"
|
25
|
+
# The start of an ANSI dark sequence. (Terminal support uncommon.)
|
26
|
+
DARK = "\e[2m"
|
27
|
+
DIM = DARK
|
28
|
+
# The start of an ANSI underline sequence.
|
29
|
+
UNDERLINE = "\e[4m"
|
30
|
+
# An alias for UNDERLINE.
|
31
|
+
UNDERSCORE = UNDERLINE
|
32
|
+
# The start of an ANSI blink sequence. (Terminal support uncommon.)
|
33
|
+
BLINK = "\e[5m"
|
34
|
+
# The start of an ANSI reverse sequence.
|
35
|
+
REVERSE = "\e[7m"
|
36
|
+
# The start of an ANSI concealed sequence. (Terminal support uncommon.)
|
37
|
+
CONCEALED = "\e[8m"
|
38
|
+
|
39
|
+
# added from http://understudy.net/custom.html
|
40
|
+
BOLD_OFF = "\e[22m"
|
41
|
+
UNDERILNE_OFF = "\e[24m"
|
42
|
+
BLINK_OFF = "\e[25m"
|
43
|
+
REVERSE_OFF = "\e[27m"
|
44
|
+
|
45
|
+
# Set the terminal's foreground ANSI color to black.
|
46
|
+
BLACK = "\e[30m"
|
47
|
+
# Set the terminal's foreground ANSI color to red.
|
48
|
+
RED = "\e[31m"
|
49
|
+
# Set the terminal's foreground ANSI color to green.
|
50
|
+
GREEN = "\e[32m"
|
51
|
+
# Set the terminal's foreground ANSI color to yellow.
|
52
|
+
YELLOW = "\e[33m"
|
53
|
+
# Set the terminal's foreground ANSI color to blue.
|
54
|
+
BLUE = "\e[34m"
|
55
|
+
# Set the terminal's foreground ANSI color to magenta.
|
56
|
+
MAGENTA = "\e[35m"
|
57
|
+
# Set the terminal's foreground ANSI color to cyan.
|
58
|
+
CYAN = "\e[36m"
|
59
|
+
# Set the terminal's foreground ANSI color to white.
|
60
|
+
WHITE = "\e[37m"
|
61
|
+
|
62
|
+
# Set the terminal's background ANSI color to black.
|
63
|
+
ON_BLACK = "\e[40m"
|
64
|
+
# Set the terminal's background ANSI color to red.
|
65
|
+
ON_RED = "\e[41m"
|
66
|
+
# Set the terminal's background ANSI color to green.
|
67
|
+
ON_GREEN = "\e[42m"
|
68
|
+
# Set the terminal's background ANSI color to yellow.
|
69
|
+
ON_YELLOW = "\e[43m"
|
70
|
+
# Set the terminal's background ANSI color to blue.
|
71
|
+
ON_BLUE = "\e[44m"
|
72
|
+
# Set the terminal's background ANSI color to magenta.
|
73
|
+
ON_MAGENTA = "\e[45m"
|
74
|
+
# Set the terminal's background ANSI color to cyan.
|
75
|
+
ON_CYAN = "\e[46m"
|
76
|
+
# Set the terminal's background ANSI color to white.
|
77
|
+
ON_WHITE = "\e[47m"
|
78
|
+
|
79
|
+
end
|
data/lib/common/sed.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
#*************************************************************
|
3
|
+
# Sed like operations - just change row and delete row
|
4
|
+
# While converting some shell scripts in which i have used
|
5
|
+
# sed extensively, I need this kind of functionality
|
6
|
+
#
|
7
|
+
#*************************************************************
|
8
|
+
##
|
9
|
+
# changes one or more rows based on pattern and replacement
|
10
|
+
# Also if replacement is not given, expects a block and yields
|
11
|
+
# matching lines to it
|
12
|
+
|
13
|
+
module Sed
|
14
|
+
def change_row filename, pattern, replacement = nil
|
15
|
+
d = _read filename
|
16
|
+
d.each { |row|
|
17
|
+
if row =~ pattern
|
18
|
+
if replacement
|
19
|
+
row.gsub!( pattern, replacement)
|
20
|
+
else
|
21
|
+
yield row
|
22
|
+
end
|
23
|
+
end
|
24
|
+
}
|
25
|
+
_write filename, d
|
26
|
+
end
|
27
|
+
def change_file filename
|
28
|
+
d = _read filename
|
29
|
+
d.each { |row|
|
30
|
+
yield row
|
31
|
+
}
|
32
|
+
_write filename, d
|
33
|
+
end
|
34
|
+
##
|
35
|
+
# deletes on more rows based on a pattern
|
36
|
+
# Also takes a block and yields each row to it
|
37
|
+
def delete_row filename, pattern = nil
|
38
|
+
d = _read filename
|
39
|
+
if pattern
|
40
|
+
d.delete_if { |row| row =~ pattern }
|
41
|
+
else
|
42
|
+
d.delete_if { |row| yield row }
|
43
|
+
end
|
44
|
+
_write filename, d
|
45
|
+
end
|
46
|
+
##
|
47
|
+
# read the given filename into an array
|
48
|
+
def _read filename
|
49
|
+
d = []
|
50
|
+
File.open(filename).each { |line|
|
51
|
+
d << line
|
52
|
+
}
|
53
|
+
return d
|
54
|
+
end
|
55
|
+
##
|
56
|
+
# write the given array to the filename
|
57
|
+
def _write filename, array
|
58
|
+
File.open(filename, "w") do |file|
|
59
|
+
array.each { |row| file.puts row }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end # module
|
63
|
+
|
64
|
+
if __FILE__ == $0
|
65
|
+
include Sed
|
66
|
+
filename = "tmp.txt"
|
67
|
+
change_row filename, /_10_/ do |row|
|
68
|
+
row.sub!(/_10_/,"10")
|
69
|
+
end
|
70
|
+
change_row filename, /\+13\+/, "13"
|
71
|
+
delete_row(filename,/XXX/)
|
72
|
+
delete_row(filename) do |line|
|
73
|
+
line =~ /junk/
|
74
|
+
end
|
75
|
+
end
|
data/lib/todorb.rb
ADDED
@@ -0,0 +1,698 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
=begin
|
3
|
+
* Name: todorb.rb
|
4
|
+
* Description: a command line todo list manager
|
5
|
+
* Author: rkumar
|
6
|
+
* Date: 2010-06-10 20:10
|
7
|
+
* License: GPL
|
8
|
+
|
9
|
+
=end
|
10
|
+
require 'rubygems'
|
11
|
+
#require 'csv'
|
12
|
+
require 'common/colorconstants'
|
13
|
+
include ColorConstants
|
14
|
+
require 'common/sed'
|
15
|
+
include Sed
|
16
|
+
|
17
|
+
PRI_A = YELLOW + BOLD
|
18
|
+
PRI_B = WHITE + BOLD
|
19
|
+
PRI_C = GREEN + BOLD
|
20
|
+
PRI_D = CYAN + BOLD
|
21
|
+
VERSION = "1.0"
|
22
|
+
DATE = "2010-06-10"
|
23
|
+
APPNAME = $0
|
24
|
+
AUTHOR = "rkumar"
|
25
|
+
|
26
|
+
class Todo
|
27
|
+
def initialize options, argv
|
28
|
+
|
29
|
+
@options = options
|
30
|
+
@argv = argv
|
31
|
+
@file = options[:file]
|
32
|
+
## data is a 2 dim array: rows and fields. It contains each row of the file
|
33
|
+
# as an array of strings. The item number is space padded.
|
34
|
+
@data = []
|
35
|
+
init_vars
|
36
|
+
end
|
37
|
+
def init_vars
|
38
|
+
@todo_default_action = "list"
|
39
|
+
@todo_file_path = @options[:file] || "TODO2.txt"
|
40
|
+
@archive_path = "archive.txt" # should take path of todo and put there TODO:
|
41
|
+
@todo_delim = "\t"
|
42
|
+
@appname = File.basename( Dir.getwd ) #+ ".#{$0}"
|
43
|
+
t = Time.now
|
44
|
+
@now = t.strftime("%Y-%m-%d %H:%M:%S")
|
45
|
+
@today = t.strftime("%Y-%m-%d")
|
46
|
+
@verbose = @options[:verbose]
|
47
|
+
#@actions = %w[ list add pri priority depri tag del delete status redo note archive help]
|
48
|
+
@actions = {}
|
49
|
+
@actions["list"] = "List all tasks.\n\t --hide-numbering --renumber"
|
50
|
+
@actions["add"] = "Add a task. \n\t #{$0} add <TEXT>\n\t --component C --project P --priority X add <TEXT>"
|
51
|
+
@actions["pri"] = "Add priority to task. \n\t #{$0} pri <ITEM> [A-Z]"
|
52
|
+
@actions["priority"] = "Same as pri"
|
53
|
+
@actions["depri"] = "Remove priority of task. \n\t #{$0} depri <ITEM>"
|
54
|
+
@actions["delete"] = "Delete a task. \n\t #{$0} delete <ITEM>"
|
55
|
+
@actions["del"] = "Same as delete"
|
56
|
+
@actions["status"] = "Change the status of a task. \n\t #{$0} status <STAT> <ITEM>\n\t<STAT> are closed started pending unstarted hold next"
|
57
|
+
@actions["redo"] = "Renumbers the todo file starting 1"
|
58
|
+
@actions["note"] = "Add a note to an item. \n\t #{$0} note <ITEM> <TEXT>"
|
59
|
+
@actions["archive"] = "archive closed tasks to archive.txt"
|
60
|
+
@actions["help"] = "Display help"
|
61
|
+
|
62
|
+
|
63
|
+
# TODO config
|
64
|
+
# we need to read up from config file and update
|
65
|
+
end
|
66
|
+
# menu MENU
|
67
|
+
def run
|
68
|
+
@action = @argv[0] || @todo_default_action
|
69
|
+
@action = @action.downcase
|
70
|
+
@action.sub!('priority', 'pri')
|
71
|
+
@action.sub!(/^del$/, 'delete')
|
72
|
+
|
73
|
+
|
74
|
+
@argv.shift
|
75
|
+
if @actions.include? @action
|
76
|
+
send(@action, @argv)
|
77
|
+
else
|
78
|
+
help @argv
|
79
|
+
end
|
80
|
+
end
|
81
|
+
def help args
|
82
|
+
#puts "Actions are #{@actions.join(", ")} "
|
83
|
+
@actions.each_pair { |name, val| puts "#{name}\t#{val}" }
|
84
|
+
end
|
85
|
+
def add args
|
86
|
+
if args.empty?
|
87
|
+
print "Enter todo: "
|
88
|
+
STDOUT.flush
|
89
|
+
text = gets.chomp
|
90
|
+
if text.empty?
|
91
|
+
exit 1
|
92
|
+
end
|
93
|
+
Kernel.print("You gave me '#{text}'")
|
94
|
+
else
|
95
|
+
text = args.join " "
|
96
|
+
Kernel.print("I got '#{text}'")
|
97
|
+
end
|
98
|
+
# convert actual newline to C-a. slash n's are escapes so echo -e does not muck up.
|
99
|
+
text.tr! "\n", ''
|
100
|
+
Kernel.print("Got '#{text}'\n")
|
101
|
+
item = _get_serial_number
|
102
|
+
paditem = _paditem(item)
|
103
|
+
print "item no is:#{paditem}:\n"
|
104
|
+
priority = @options[:priority] ? " (#{@options[:priority]})" : ""
|
105
|
+
project = @options[:project] ? " +#{@options[:project]}" : ""
|
106
|
+
component = @options[:component] ? " @#{@options[:component]}" : ""
|
107
|
+
newtext="#{paditem}#{@todo_delim}[ ]#{priority}#{project}#{component} #{text} (#{@today})"
|
108
|
+
puts "Adding"
|
109
|
+
puts newtext
|
110
|
+
File.open(@todo_file_path, "a") { | file| file.puts newtext }
|
111
|
+
|
112
|
+
end
|
113
|
+
##
|
114
|
+
# reads serial_number file, returns serialno for this app
|
115
|
+
# and increments the serial number and writes back.
|
116
|
+
def _get_serial_number
|
117
|
+
require 'fileutils'
|
118
|
+
appname = @appname
|
119
|
+
filename = "/Users/rahul/serial_numbers"
|
120
|
+
h = {}
|
121
|
+
File.open(filename).each { |line|
|
122
|
+
#sn = $1 if line.match regex
|
123
|
+
x = line.split ":"
|
124
|
+
h[x[0]] = x[1].chomp
|
125
|
+
}
|
126
|
+
sn = h[appname] || 1
|
127
|
+
# update the sn in file
|
128
|
+
nsn = sn.to_i + 1
|
129
|
+
# this will create if not exists in addition to storing if it does
|
130
|
+
h[appname] = nsn
|
131
|
+
# write back to file
|
132
|
+
File.open(filename, "w") do |f|
|
133
|
+
h.each_pair {|k,v| f.print "#{k}:#{v}\n"}
|
134
|
+
end
|
135
|
+
return sn
|
136
|
+
end
|
137
|
+
##
|
138
|
+
# After doing a redo of the numbering, we need to reset the numbers for that app
|
139
|
+
def _set_serial_number number
|
140
|
+
appname = @appname
|
141
|
+
pattern = Regexp.new "^#{appname}:.*$"
|
142
|
+
filename = "/Users/rahul/serial_numbers"
|
143
|
+
_backup filename
|
144
|
+
change_row filename, pattern, "#{appname}:#{number}"
|
145
|
+
end
|
146
|
+
def _backup filename=@todo_file_path
|
147
|
+
require 'fileutils'
|
148
|
+
FileUtils.cp filename, "#{filename}.org"
|
149
|
+
end
|
150
|
+
##
|
151
|
+
# for historical reasons, I pad item to 3 spaces in text file.
|
152
|
+
# It used to help me in printing straight off without any formatting in unix shell
|
153
|
+
def _paditem item
|
154
|
+
return sprintf("%3s", item)
|
155
|
+
end
|
156
|
+
def populate
|
157
|
+
@ctr = 0
|
158
|
+
@total = 0
|
159
|
+
#CSV.foreach(@file,:col_sep => "\t") do |row| # 1.9 2009-10-05 11:12
|
160
|
+
File.open(@file).each do |line|
|
161
|
+
row = line.chomp.split "\t"
|
162
|
+
@total += 1
|
163
|
+
if @options[:show_all]
|
164
|
+
@data << row
|
165
|
+
@ctr += 1
|
166
|
+
else
|
167
|
+
unless row[1] =~ /^\[x\]/
|
168
|
+
@data << row
|
169
|
+
@ctr += 1
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
##
|
175
|
+
# filters output based on project and or component and or priority
|
176
|
+
def filter
|
177
|
+
project = @options[:project]
|
178
|
+
component = @options[:component]
|
179
|
+
priority = @options[:priority]
|
180
|
+
if project
|
181
|
+
r = Regexp.new "\\+#{project}"
|
182
|
+
@data = @data.select { |row| row[1] =~ r }
|
183
|
+
end
|
184
|
+
if component
|
185
|
+
r = Regexp.new "@#{component}"
|
186
|
+
@data = @data.select { |row| row[1] =~ r }
|
187
|
+
end
|
188
|
+
if priority
|
189
|
+
r = Regexp.new "\\(#{priority}\\)"
|
190
|
+
@data = @data.select { |row| row[1] =~ r }
|
191
|
+
end
|
192
|
+
end
|
193
|
+
def list args
|
194
|
+
populate
|
195
|
+
grep if @options[:grep]
|
196
|
+
filter if @options[:filter]
|
197
|
+
sort if @options[:sort]
|
198
|
+
renumber if @options[:renumber]
|
199
|
+
colorize # << currently this is where I print !! Since i colorize the whole line
|
200
|
+
puts
|
201
|
+
puts " #{@data.length} of #{@total} rows displayed from #{@todo_file_path} "
|
202
|
+
|
203
|
+
end
|
204
|
+
def print_todo
|
205
|
+
@ctr = 0
|
206
|
+
@data.each { |row|
|
207
|
+
unless row[1] =~ /^\[x\]/
|
208
|
+
puts " #{row[0]} | #{row[1]} " #unless row[1] =~ /^\[x\]/
|
209
|
+
@ctr += 1
|
210
|
+
end
|
211
|
+
}
|
212
|
+
end
|
213
|
+
def each
|
214
|
+
@data.each { |row|
|
215
|
+
yield row
|
216
|
+
}
|
217
|
+
end
|
218
|
+
def active_tasks
|
219
|
+
@ctr = 0
|
220
|
+
@data.each { |row|
|
221
|
+
unless row[1] =~ /^\[x\]/
|
222
|
+
yield row
|
223
|
+
@ctr += 1
|
224
|
+
end
|
225
|
+
}
|
226
|
+
end
|
227
|
+
##
|
228
|
+
# colorize each line, if required.
|
229
|
+
# However, we should put the colors in some Map, so it can be changed at configuration level.
|
230
|
+
#
|
231
|
+
def colorize
|
232
|
+
colorme = @options[:colorize]
|
233
|
+
@data.each do |r|
|
234
|
+
if @options[:hide_numbering]
|
235
|
+
string = "#{r[1]} "
|
236
|
+
else
|
237
|
+
string = " #{r[0]} #{r[1]} "
|
238
|
+
end
|
239
|
+
if colorme
|
240
|
+
m=string.match(/\(([A-Z])\)/)
|
241
|
+
if m
|
242
|
+
case m[1]
|
243
|
+
when "A", "B", "C", "D"
|
244
|
+
pri = self.class.const_get("PRI_#{m[1]}")
|
245
|
+
#string = "#{YELLOW}#{BOLD}#{string}#{CLEAR}"
|
246
|
+
string = "#{pri}#{string}#{CLEAR}"
|
247
|
+
else
|
248
|
+
string = "#{NORMAL}#{GREEN}#{string}#{CLEAR}"
|
249
|
+
#string = "#{BLUE}\e[6m#{string}#{CLEAR}"
|
250
|
+
#string = "#{BLUE}#{string}#{CLEAR}"
|
251
|
+
end
|
252
|
+
else
|
253
|
+
string = "#{NORMAL}#{string}#{CLEAR}"
|
254
|
+
end
|
255
|
+
end # colorme
|
256
|
+
## since we've added notes, we convert C-a to newline with spaces
|
257
|
+
# so it prints in next line with some neat indentation.
|
258
|
+
string.gsub!('', "\n ")
|
259
|
+
#string.tr! '', "\n"
|
260
|
+
puts string
|
261
|
+
end
|
262
|
+
end
|
263
|
+
def sort
|
264
|
+
@data.sort! { |a,b| b[1] <=> a[1] }
|
265
|
+
end
|
266
|
+
def grep
|
267
|
+
r = Regexp.new @options[:grep]
|
268
|
+
#@data = @data.grep r
|
269
|
+
@data = @data.find_all {|i| i[1] =~ r }
|
270
|
+
end
|
271
|
+
##
|
272
|
+
# Adds or changes priority for a task
|
273
|
+
#
|
274
|
+
# @param [Array] priority, single char A-Z, item or items
|
275
|
+
# @return
|
276
|
+
# @ example:
|
277
|
+
# pri A 5 6 7
|
278
|
+
# pri 5 6 7 A
|
279
|
+
# pri A 5 6 7 B 1 2 3
|
280
|
+
# pri 5 6 7 A 1 2 3 B
|
281
|
+
|
282
|
+
def pri args
|
283
|
+
populate
|
284
|
+
changeon = nil
|
285
|
+
items = []
|
286
|
+
prior = nil
|
287
|
+
item = nil
|
288
|
+
## if the first arg is priority then following items all have that priority
|
289
|
+
## if the first arg is item/s then wait for priority and use that
|
290
|
+
if args[0] =~ /^[A-Z]$/
|
291
|
+
changeon = :ITEM
|
292
|
+
elsif args[0] =~ /^[0-9]+$/
|
293
|
+
changeon = :PRI
|
294
|
+
else
|
295
|
+
puts "ERROR! "
|
296
|
+
exit 1
|
297
|
+
end
|
298
|
+
puts "args 0 is #{args[0]} "
|
299
|
+
args.each do |arg|
|
300
|
+
if arg =~ /^[A-Z]$/
|
301
|
+
prior = arg #$1
|
302
|
+
if changeon == :PRI
|
303
|
+
puts " changing previous items #{items} to #{prior} "
|
304
|
+
items.each { |i| _pri(i, prior) }
|
305
|
+
items = []
|
306
|
+
end
|
307
|
+
elsif arg =~ /^[0-9]+$/
|
308
|
+
item = arg #$1
|
309
|
+
if changeon == :ITEM
|
310
|
+
puts " changing #{item} to #{prior} "
|
311
|
+
_pri(item, prior)
|
312
|
+
else
|
313
|
+
items << item
|
314
|
+
end
|
315
|
+
else
|
316
|
+
puts "ERROR in arg :#{arg}:"
|
317
|
+
end
|
318
|
+
end
|
319
|
+
save_array
|
320
|
+
end
|
321
|
+
##
|
322
|
+
# Reove the priority of a task
|
323
|
+
#
|
324
|
+
# @param [Array] items to deprioritize
|
325
|
+
# @return
|
326
|
+
public
|
327
|
+
def depri(args)
|
328
|
+
populate
|
329
|
+
puts "depri got #{args} "
|
330
|
+
each do |row|
|
331
|
+
item = row[0].sub(/^[ -]*/,'')
|
332
|
+
if args.include? item
|
333
|
+
if row[1] =~ /\] (\([A-Z]\) )/
|
334
|
+
puts row[1]
|
335
|
+
row[1].sub!(/\([A-Z]\) /,"")
|
336
|
+
puts "#{RED}#{row[1]}#{CLEAR}"
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
##
|
342
|
+
# saves the task array to disk
|
343
|
+
def save_array
|
344
|
+
File.open(@todo_file_path, "w") do |file|
|
345
|
+
@data.each { |row| file.puts "#{row[0]}\t#{row[1]}" }
|
346
|
+
end
|
347
|
+
end
|
348
|
+
##
|
349
|
+
# change priority of given item to priority in array
|
350
|
+
private
|
351
|
+
def _pri item, pri
|
352
|
+
paditem = _paditem(item)
|
353
|
+
@data.each { |row|
|
354
|
+
if row[0] == paditem
|
355
|
+
puts " #{row[0]} : #{row[1]} "
|
356
|
+
if row[1] =~ /\] (\([A-Z]\) )/
|
357
|
+
row[1].sub!(/\([A-Z]\) /,"")
|
358
|
+
end
|
359
|
+
row[1].sub!(/\] /,"] (#{pri}) ")
|
360
|
+
puts " #{RED}#{row[0]} : #{row[1]} #{CLEAR}"
|
361
|
+
return true
|
362
|
+
end
|
363
|
+
}
|
364
|
+
puts " #{RED} no such item #{item} #{CLEAR} "
|
365
|
+
return false
|
366
|
+
|
367
|
+
end
|
368
|
+
##
|
369
|
+
# Appends a tag to task
|
370
|
+
#
|
371
|
+
# @param [Array] items and tag, or tag and items
|
372
|
+
# @return
|
373
|
+
public
|
374
|
+
def tag(args)
|
375
|
+
puts "tags args #{args} "
|
376
|
+
items_first = items_first? args
|
377
|
+
items = []
|
378
|
+
tag = nil
|
379
|
+
args.each do |arg|
|
380
|
+
if arg =~ /^[a-zA-Z]/
|
381
|
+
tag = arg
|
382
|
+
elsif arg =~ /^[0-9]+$/
|
383
|
+
items << arg
|
384
|
+
end
|
385
|
+
end
|
386
|
+
#items.each { |i| change_row }
|
387
|
+
change_file @todo_file_path do |line|
|
388
|
+
item = line.match(/^ *([0-9]+)/)
|
389
|
+
if items.include? item[1]
|
390
|
+
puts "#{line}" if @verbose
|
391
|
+
line.sub!(/$/, " @#{tag}")
|
392
|
+
puts "#{RED}#{line}#{CLEAR} " if @verbose
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
##
|
397
|
+
# deletes one or more items
|
398
|
+
#
|
399
|
+
# @param [Array, #include?] items to delete
|
400
|
+
# @return
|
401
|
+
public
|
402
|
+
def delete(args)
|
403
|
+
puts "delete with #{args} "
|
404
|
+
delete_row @todo_file_path do |line|
|
405
|
+
#puts "line #{line} "
|
406
|
+
item = line.match(/^ *([0-9]+)/)
|
407
|
+
#puts "item #{item} "
|
408
|
+
if args.include? item[1]
|
409
|
+
if @options[:force]
|
410
|
+
true
|
411
|
+
else
|
412
|
+
puts line
|
413
|
+
print "Do you wish to delete (Y/N): "
|
414
|
+
STDOUT.flush
|
415
|
+
ans = STDIN.gets.chomp
|
416
|
+
ans =~ /[Yy]/
|
417
|
+
end
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
##
|
422
|
+
# Change status of given items
|
423
|
+
#
|
424
|
+
# @param [Array, #include?] items to delete
|
425
|
+
# @return [true, false] success or fail
|
426
|
+
public
|
427
|
+
def status(args)
|
428
|
+
stat, items = _separate args
|
429
|
+
status, newstatus = _resolve_status stat
|
430
|
+
if status.nil?
|
431
|
+
print_red "Status #{stat} is invalid!"
|
432
|
+
exit 1
|
433
|
+
end
|
434
|
+
change_items(items, /(\[.\])/, "[#{newstatus}]")
|
435
|
+
#change_items items do |item, line|
|
436
|
+
#puts line if @verbose
|
437
|
+
#line.sub!(/(\[.\])/, "[#{newstatus}]")
|
438
|
+
#puts "#{RED}#{line}#{CLEAR}" if @verbose
|
439
|
+
#end
|
440
|
+
end
|
441
|
+
##
|
442
|
+
# separates args into tag or subcommand and items
|
443
|
+
# This allows user to pass e.g. a priority first and then item list
|
444
|
+
# or item list first and then priority.
|
445
|
+
# This can only be used if the tag or pri or status is non-numeric and the item is numeric.
|
446
|
+
def _separate args
|
447
|
+
tag = nil
|
448
|
+
items = []
|
449
|
+
args.each do |arg|
|
450
|
+
if arg =~ /^[a-zA-Z]/
|
451
|
+
tag = arg
|
452
|
+
elsif arg =~ /^[0-9]+$/
|
453
|
+
items << arg
|
454
|
+
end
|
455
|
+
end
|
456
|
+
return tag, items
|
457
|
+
end
|
458
|
+
##
|
459
|
+
# Renumber while displaying
|
460
|
+
#
|
461
|
+
# @return [true, false] success or fail
|
462
|
+
private
|
463
|
+
def renumber
|
464
|
+
@data.each_with_index { |row, i|
|
465
|
+
paditem = _paditem(i+1)
|
466
|
+
row[0] = paditem
|
467
|
+
}
|
468
|
+
end
|
469
|
+
##
|
470
|
+
# For given items, add a note
|
471
|
+
#
|
472
|
+
# @param [Array, #include?] items to add note to, note
|
473
|
+
# @return [true, false] success or fail
|
474
|
+
public
|
475
|
+
def note(args)
|
476
|
+
_backup
|
477
|
+
text = args.pop
|
478
|
+
change_items args do |item, line|
|
479
|
+
m = line.match(/^ */)
|
480
|
+
indent = m[0]
|
481
|
+
puts line if @verbose
|
482
|
+
# we place the text before the date, adding a C-a and indent
|
483
|
+
# At printing the C-a is replaced with a newline and some spaces
|
484
|
+
ret = line.sub!(/ (\([0-9]{4})/," #{indent}* #{text} "+'\1')
|
485
|
+
print_red line if @verbose
|
486
|
+
end
|
487
|
+
end
|
488
|
+
##
|
489
|
+
# Archive all items
|
490
|
+
#
|
491
|
+
# @param none (ignored)
|
492
|
+
# @return [true, false] success or fail
|
493
|
+
public
|
494
|
+
def archive(args=nil)
|
495
|
+
filename = @archive_path
|
496
|
+
file = File.open(filename, "a")
|
497
|
+
ctr = 0
|
498
|
+
delete_row @todo_file_path do |line|
|
499
|
+
if line =~ /\[x\]/
|
500
|
+
file.puts line
|
501
|
+
ctr += 1
|
502
|
+
puts line if @verbose
|
503
|
+
true
|
504
|
+
end
|
505
|
+
end
|
506
|
+
file.close
|
507
|
+
puts "Archived #{ctr} tasks."
|
508
|
+
end
|
509
|
+
##
|
510
|
+
# For given items, ...
|
511
|
+
#
|
512
|
+
# @param [Array, #include?] items to delete
|
513
|
+
# @return [true, false] success or fail
|
514
|
+
public
|
515
|
+
def CHANGEME(args)
|
516
|
+
end
|
517
|
+
##
|
518
|
+
# yields lines from file that match the given item
|
519
|
+
# We do not need to now parse and match the item in each method
|
520
|
+
def change_items args, pattern=nil, replacement=nil
|
521
|
+
change_file @todo_file_path do |line|
|
522
|
+
item = line.match(/^ *([0-9]+)/)
|
523
|
+
if args.include? item[1]
|
524
|
+
if pattern
|
525
|
+
puts line if @verbose
|
526
|
+
line.sub!(pattern, replacement)
|
527
|
+
print_red line if @verbose
|
528
|
+
else
|
529
|
+
yield item[1], line
|
530
|
+
end
|
531
|
+
end
|
532
|
+
end
|
533
|
+
end
|
534
|
+
##
|
535
|
+
# Redoes the numbering in the file.
|
536
|
+
# Useful if the numbers have gone high and you want to start over.
|
537
|
+
def redo args
|
538
|
+
ctr = 1
|
539
|
+
require 'fileutils'
|
540
|
+
FileUtils.cp @todo_file_path, "#{@todo_file_path}.org"
|
541
|
+
puts "Saved #{@todo_file_path} as #{@todo_file_path}.org"
|
542
|
+
change_file @todo_file_path do |line|
|
543
|
+
paditem = _paditem ctr
|
544
|
+
line.sub!(/^ *[0-9]+/, paditem)
|
545
|
+
ctr += 1
|
546
|
+
end
|
547
|
+
_set_serial_number ctr
|
548
|
+
puts "Redone numbering"
|
549
|
+
end
|
550
|
+
##
|
551
|
+
# does this command start with items or something else
|
552
|
+
private
|
553
|
+
def items_first?(args)
|
554
|
+
return true if args[0] =~ /^[0-9]+$/
|
555
|
+
return false
|
556
|
+
end
|
557
|
+
def print_red line
|
558
|
+
puts "#{RED}#{line}#{CLEAR}"
|
559
|
+
end
|
560
|
+
private
|
561
|
+
def _resolve_status stat
|
562
|
+
status = nil
|
563
|
+
#puts " got #{stat} "
|
564
|
+
case stat
|
565
|
+
when "@","sta","star","start","started"
|
566
|
+
status="start"
|
567
|
+
newstatus = "@"
|
568
|
+
when "P","pen","pend","pending"
|
569
|
+
status="pend"
|
570
|
+
newstatus = "P"
|
571
|
+
when "x","clo","clos","close","closed"
|
572
|
+
status="close"
|
573
|
+
newstatus = "x"
|
574
|
+
when "1","next"
|
575
|
+
status="next"
|
576
|
+
newstatus = "1"
|
577
|
+
when "H","hold"
|
578
|
+
status="hold"
|
579
|
+
newstatus = "H"
|
580
|
+
when "u","uns","unst","unstart","unstarted"
|
581
|
+
status="unstarted"
|
582
|
+
newstatus = " "
|
583
|
+
end
|
584
|
+
#puts " after #{status} "
|
585
|
+
#newstatus=$( echo $status | sed 's/^start/@/;s/^pend/P/;s/^close/x/;s/hold/H/;s/next/1/;s/^unstarted/ /' )
|
586
|
+
return status, newstatus
|
587
|
+
end
|
588
|
+
|
589
|
+
def self.main args
|
590
|
+
begin
|
591
|
+
# http://www.ruby-doc.org/stdlib/libdoc/optparse/rdoc/classes/OptionParser.html
|
592
|
+
require 'optparse'
|
593
|
+
options = {}
|
594
|
+
options[:verbose] = false
|
595
|
+
options[:colorize] = true
|
596
|
+
|
597
|
+
OptionParser.new do |opts|
|
598
|
+
opts.banner = "Usage: #{$0} [options] action"
|
599
|
+
|
600
|
+
opts.separator ""
|
601
|
+
opts.separator "Specific options:"
|
602
|
+
|
603
|
+
|
604
|
+
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
|
605
|
+
options[:verbose] = v
|
606
|
+
end
|
607
|
+
opts.on("-f", "--file FILENAME", "CSV filename") do |v|
|
608
|
+
options[:file] = v
|
609
|
+
end
|
610
|
+
opts.on("-P", "--project PROJECTNAME", "name of project for add or list") { |v|
|
611
|
+
options[:project] = v
|
612
|
+
options[:filter] = true
|
613
|
+
}
|
614
|
+
opts.on("-p", "--priority A-Z", "priority code for add or list") { |v|
|
615
|
+
options[:priority] = v
|
616
|
+
options[:filter] = true
|
617
|
+
}
|
618
|
+
opts.on("-C", "--component COMPONENT", "component name for add or list") { |v|
|
619
|
+
options[:component] = v
|
620
|
+
options[:filter] = true
|
621
|
+
}
|
622
|
+
opts.on("--force", "force delete or add without prompting") do |v|
|
623
|
+
options[:force] = v
|
624
|
+
end
|
625
|
+
opts.separator ""
|
626
|
+
opts.separator "List options:"
|
627
|
+
|
628
|
+
opts.on("--[no-]color", "--[no-]colors", "colorize listing") do |v|
|
629
|
+
options[:colorize] = v
|
630
|
+
options[:color_scheme] = 1
|
631
|
+
end
|
632
|
+
opts.on("-s", "--sort", "sort list on priority") do |v|
|
633
|
+
options[:sort] = v
|
634
|
+
end
|
635
|
+
opts.on("-g", "--grep REGEXP", "filter list on pattern") do |v|
|
636
|
+
options[:grep] = v
|
637
|
+
end
|
638
|
+
opts.on("--renumber", "renumber while listing") do |v|
|
639
|
+
options[:renumber] = v
|
640
|
+
end
|
641
|
+
opts.on("--hide-numbering", "hide-numbering while listing ") do |v|
|
642
|
+
options[:hide_numbering] = v
|
643
|
+
end
|
644
|
+
opts.on("--show-all", "show all tasks (incl closed)") do |v|
|
645
|
+
options[:show_all] = v
|
646
|
+
end
|
647
|
+
|
648
|
+
opts.separator ""
|
649
|
+
opts.separator "Common options:"
|
650
|
+
|
651
|
+
opts.on_tail("-d DIR", "--dir DIR", "Use TODO file in this directory") do |v|
|
652
|
+
require 'FileUtils'
|
653
|
+
dir = File.expand_path v
|
654
|
+
if File.directory? dir
|
655
|
+
options[:dir] = dir
|
656
|
+
FileUtils.cd dir
|
657
|
+
else
|
658
|
+
puts "#{RED}#{v}: no such directory #{CLEAR}"
|
659
|
+
exit 1
|
660
|
+
end
|
661
|
+
end
|
662
|
+
# No argument, shows at tail. This will print an options summary.
|
663
|
+
# Try it and see!
|
664
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
665
|
+
puts opts
|
666
|
+
exit
|
667
|
+
end
|
668
|
+
|
669
|
+
opts.on_tail("--show-actions", "show actions ") do |v|
|
670
|
+
todo = Todo.new(options, ARGV)
|
671
|
+
todo.help nil
|
672
|
+
exit 0
|
673
|
+
end
|
674
|
+
|
675
|
+
opts.on_tail("--version", "Show version") do
|
676
|
+
puts "#{APPNAME} version #{VERSION}, #{DATE}"
|
677
|
+
puts "by #{AUTHOR}. This software is under the GPL License."
|
678
|
+
exit 0
|
679
|
+
end
|
680
|
+
end.parse!(args)
|
681
|
+
|
682
|
+
#options[:file] ||= "rbcurse/TODO2.txt"
|
683
|
+
options[:file] ||= "TODO2.txt"
|
684
|
+
if options[:verbose]
|
685
|
+
p options
|
686
|
+
print "ARGV: "
|
687
|
+
p args #ARGV
|
688
|
+
end
|
689
|
+
#raise "-f FILENAME is mandatory" unless options[:file]
|
690
|
+
|
691
|
+
todo = Todo.new(options, args)
|
692
|
+
todo.run
|
693
|
+
ensure
|
694
|
+
end
|
695
|
+
end # main
|
696
|
+
end # class Todo
|
697
|
+
|
698
|
+
Todo.main(ARGV) if __FILE__ == $0
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: todorb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Rahul Kumar
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-06-13 00:00:00 +05:30
|
18
|
+
default_executable: todorb
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: thoughtbot-shoulda
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :development
|
31
|
+
version_requirements: *id001
|
32
|
+
description: "command-line program that manages a todo list text file "
|
33
|
+
email: sentinel1879@gmail.com
|
34
|
+
executables:
|
35
|
+
- todorb
|
36
|
+
extensions: []
|
37
|
+
|
38
|
+
extra_rdoc_files:
|
39
|
+
- LICENSE
|
40
|
+
- README.markdown
|
41
|
+
files:
|
42
|
+
- README.markdown
|
43
|
+
- VERSION
|
44
|
+
- lib/common/colorconstants.rb
|
45
|
+
- lib/common/sed.rb
|
46
|
+
- lib/todorb.rb
|
47
|
+
- LICENSE
|
48
|
+
has_rdoc: true
|
49
|
+
homepage: http://github.com/rkumar/todorb
|
50
|
+
licenses: []
|
51
|
+
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options:
|
54
|
+
- --charset=UTF-8
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
segments:
|
69
|
+
- 0
|
70
|
+
version: "0"
|
71
|
+
requirements: []
|
72
|
+
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 1.3.6
|
75
|
+
signing_key:
|
76
|
+
specification_version: 3
|
77
|
+
summary: command-line todo list manager
|
78
|
+
test_files: []
|
79
|
+
|