todo-jsonl 0.1.15 → 0.1.20
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.
- checksums.yaml +4 -4
- data/bin/todo.rb +321 -308
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d39cdc12b9d904368b86e79438a242689700825c668b0ca69aedba68025c9c9f
|
4
|
+
data.tar.gz: f219916b78035ba1daab7211c4318dc7063123a10f9fdcbc6aae91e62265b2b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4bb6565bd2a1d4890f3c06fa9b88b7a711d48f1f02fad0b8fcac306c8348a1b65635b9e48245d12abfee95c1eb465ab1790593e3b746c0dc5508d7e97524df23
|
7
|
+
data.tar.gz: d1d9bdcebc0c579ca4e15e1aeb6723885766c5b8790accf284d7619f507e3f4d613ef2cca98c8b7d6e25e43e4bdb55523a28a5616ba1ed95ca1a763ed50bb459
|
data/bin/todo.rb
CHANGED
@@ -27,352 +27,365 @@
|
|
27
27
|
require 'json'
|
28
28
|
require 'date'
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
'new' => '[ ]',
|
43
|
-
'done' => '[x]',
|
44
|
-
'started' => '[>]',
|
45
|
-
'blocked' => '[!]',
|
46
|
-
'default' => '[?]'
|
47
|
-
}
|
48
|
-
|
49
|
-
ORDER = {
|
50
|
-
'new' => 3,
|
51
|
-
'done' => 4,
|
52
|
-
'started' => 2,
|
53
|
-
'blocked' => 1,
|
54
|
-
'default' => 100
|
55
|
-
}
|
56
|
-
|
57
|
-
COLORS = {
|
58
|
-
'new' => :white,
|
59
|
-
'done' => :blue,
|
60
|
-
'started' => :green,
|
61
|
-
'blocked' => :yellow,
|
62
|
-
'default' => :magenta
|
63
|
-
}
|
64
|
-
|
65
|
-
TODAY = DateTime.now
|
66
|
-
NEXT_7_DAYS = (0..6).map do |day| (TODAY.to_date + day) end
|
67
|
-
DATE_FORMAT = '%Y-%m-%d'
|
68
|
-
DUE_DATE_DAYS = NEXT_7_DAYS.map do |day| day.strftime('%A').downcase end
|
69
|
-
DUE_DATES_FOR_QUERIES = NEXT_7_DAYS.map do |day| day.strftime(DATE_FORMAT) end
|
70
|
-
DUE_DATE_DAYS_SIMPLE = ['today', 'tomorrow']
|
71
|
-
|
72
|
-
DUE_DATE_TAG_PATTERN = /(^| )due:([a-zA-Z0-9-]+)/
|
73
|
-
CONTEXT_TAG_PATTERN = /(^| )[@+][\w-]+/
|
74
|
-
|
75
|
-
QUERIES = {
|
76
|
-
':active' => 'state=(new|started|blocked)',
|
77
|
-
':done' => 'state=done',
|
78
|
-
':blocked' => 'state=blocked',
|
79
|
-
':started' => 'state=started',
|
80
|
-
':new' => 'state=new',
|
81
|
-
':all' => 'state=\w+',
|
82
|
-
':today' => "due=#{DUE_DATES_FOR_QUERIES[0]}",
|
83
|
-
':tomorrow' => "due=#{DUE_DATES_FOR_QUERIES[1]}",
|
84
|
-
':next7days' => "due=(#{DUE_DATES_FOR_QUERIES.join('|')})"
|
85
|
-
}
|
86
|
-
|
87
|
-
PRIORITY_FLAG = '*'
|
88
|
-
|
89
|
-
TODO_FILE = "#{ENV['HOME']}/todo.jsonl"
|
90
|
-
|
91
|
-
def usage
|
92
|
-
<<~USAGE
|
93
|
-
Usage: todo <command> <arguments>
|
94
|
-
|
95
|
-
Commands:
|
96
|
-
* add <text> add new task
|
97
|
-
* start <tasknumber> [text] mark task as started, with optional note
|
98
|
-
* done <tasknumber> [text] mark task as completed, with optional note
|
99
|
-
* block <tasknumber> [text] mark task as blocked, with optional note
|
100
|
-
* reset <tasknumber> [text] reset task to new state, with optional note
|
101
|
-
* prio <tasknumber> toggle high priority flag
|
102
|
-
* due <tasknumber> [date] set/unset due date (in YYYY-MM-DD format)
|
103
|
-
|
104
|
-
* append <tasknumber> <text> append text to task title
|
105
|
-
* rename <tasknumber> <text> rename task
|
106
|
-
* del <tasknumber> delete task
|
107
|
-
* note <tasknumber> <text> add note to task
|
108
|
-
* delnote <tasknumber> delete all notes from task
|
109
|
-
|
110
|
-
* list <regex> [regex...] list tasks (only active tasks by default)
|
111
|
-
* show <tasknumber> show all task details
|
112
|
-
* repl enter read-eval-print loop mode
|
113
|
-
* help this help screen
|
114
|
-
|
115
|
-
With list command the following pre-defined regex patterns can be also used:
|
116
|
-
#{QUERIES.keys.join(', ')}
|
117
|
-
|
118
|
-
Due dates can be also added via tags in task title: "due:YYYY-MM-DD"
|
119
|
-
|
120
|
-
Legend:
|
121
|
-
#{STATES.select { |k, v| k != 'default' }.map { |k, v| "#{k} #{v}" }.join(', ') }, priority #{PRIORITY_FLAG}
|
122
|
-
|
123
|
-
Todo file: #{TODO_FILE}
|
124
|
-
USAGE
|
125
|
-
end
|
30
|
+
class Todo
|
31
|
+
|
32
|
+
COLOR_CODES = {
|
33
|
+
black: 30,
|
34
|
+
red: 31,
|
35
|
+
green: 32,
|
36
|
+
yellow: 33,
|
37
|
+
blue: 34,
|
38
|
+
magenta: 35,
|
39
|
+
cyan: 36,
|
40
|
+
white: 37
|
41
|
+
}
|
126
42
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
43
|
+
STATES = {
|
44
|
+
'new' => '[ ]',
|
45
|
+
'done' => '[x]',
|
46
|
+
'started' => '[>]',
|
47
|
+
'blocked' => '[!]',
|
48
|
+
'default' => '[?]'
|
49
|
+
}
|
50
|
+
|
51
|
+
ORDER = {
|
52
|
+
'new' => 3,
|
53
|
+
'done' => 4,
|
54
|
+
'started' => 2,
|
55
|
+
'blocked' => 1,
|
56
|
+
'default' => 100
|
57
|
+
}
|
58
|
+
|
59
|
+
COLORS = {
|
60
|
+
'new' => :white,
|
61
|
+
'done' => :blue,
|
62
|
+
'started' => :green,
|
63
|
+
'blocked' => :yellow,
|
64
|
+
'default' => :magenta
|
65
|
+
}
|
66
|
+
|
67
|
+
DATE_FORMAT = '%Y-%m-%d'
|
68
|
+
DUE_DATE_DAYS_SIMPLE = ['today', 'tomorrow']
|
69
|
+
DUE_DATE_TAG_PATTERN = /(^| )due:([a-zA-Z0-9-]+)/
|
70
|
+
CONTEXT_TAG_PATTERN = /(^| )[@+][\w-]+/
|
71
|
+
PRIORITY_FLAG = '*'
|
72
|
+
TODO_FILE = "#{ENV['HOME']}/todo.jsonl"
|
73
|
+
|
74
|
+
def execute(arguments)
|
75
|
+
begin
|
76
|
+
setup
|
77
|
+
action = arguments.first
|
78
|
+
args = arguments[1..-1] || []
|
79
|
+
case action
|
80
|
+
when 'add'
|
81
|
+
raise action + ' command requires at least one parameter' if args.nil? || args.empty?
|
82
|
+
add(args.join(' '))
|
83
|
+
when 'start'
|
84
|
+
args.length > 0 ? change_state(args.first.to_i, 'started', (args[1..-1] || []).join(' ')) : list(nil, [':started'])
|
85
|
+
when 'done'
|
86
|
+
args.length > 0 ? change_state(args.first.to_i, 'done', (args[1..-1] || []).join(' ')) : list(nil, [':done'])
|
87
|
+
when 'block'
|
88
|
+
args.length > 0 ? change_state(args.first.to_i, 'blocked', (args[1..-1] || []).join(' ')) : list(nil, [':blocked'])
|
89
|
+
when 'reset'
|
90
|
+
args.length > 0 ? change_state(args.first.to_i, 'new', (args[1..-1] || []).join(' ')) : list(nil, [':new'])
|
91
|
+
when 'prio'
|
92
|
+
raise action + ' command requires at least one parameter' if args.length < 1
|
93
|
+
set_priority(args.first.to_i, (args[1..-1] || []).join(' '))
|
94
|
+
when 'due'
|
95
|
+
raise action + ' command requires at least one parameter' if args.length < 1
|
96
|
+
due_date(args.first.to_i, (args[1..-1] || []).join(' '))
|
97
|
+
when 'append'
|
98
|
+
raise action + ' command requires at least two parameters' if args.length < 2
|
99
|
+
append(args.first.to_i, args[1..-1].join(' '))
|
100
|
+
when 'rename'
|
101
|
+
raise action + ' command requires at least two parameters' if args.length < 2
|
102
|
+
rename(args.first.to_i, args[1..-1].join(' '))
|
103
|
+
when 'del'
|
104
|
+
raise action + ' command requires exactly one parameter' if args.length != 1
|
105
|
+
delete(args.first.to_i)
|
106
|
+
when 'note'
|
107
|
+
raise action + ' command requires at least two parameters' if args.length < 2
|
108
|
+
add_note(args.first.to_i, args[1..-1].join(' '))
|
109
|
+
when 'delnote'
|
110
|
+
raise action + ' command requires exactly one parameter' if args.length != 1
|
111
|
+
delete_note(args.first.to_i)
|
112
|
+
when 'list'
|
113
|
+
list(nil, args)
|
114
|
+
when 'show'
|
115
|
+
raise action + ' command requires exactly one parameter' if args.length != 1
|
116
|
+
show(args.first.to_i)
|
117
|
+
when 'help'
|
118
|
+
puts usage
|
119
|
+
when 'repl'
|
120
|
+
start_repl
|
121
|
+
else
|
122
|
+
list(nil, arguments)
|
136
123
|
end
|
124
|
+
rescue => error
|
125
|
+
puts "#{colorize('ERROR:', :red)} #{error}"
|
137
126
|
end
|
138
127
|
end
|
139
|
-
|
140
|
-
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def usage
|
132
|
+
<<~USAGE
|
133
|
+
Usage: todo <command> <arguments>
|
134
|
+
|
135
|
+
Commands:
|
136
|
+
* add <text> add new task
|
137
|
+
* start <tasknumber> [text] mark task as started, with optional note
|
138
|
+
* done <tasknumber> [text] mark task as completed, with optional note
|
139
|
+
* block <tasknumber> [text] mark task as blocked, with optional note
|
140
|
+
* reset <tasknumber> [text] reset task to new state, with optional note
|
141
|
+
* prio <tasknumber> [text] toggle high priority flag, with optional note
|
142
|
+
* due <tasknumber> [date] set/unset due date (in YYYY-MM-DD format)
|
143
|
+
|
144
|
+
* append <tasknumber> <text> append text to task title
|
145
|
+
* rename <tasknumber> <text> rename task
|
146
|
+
* del <tasknumber> delete task
|
147
|
+
* note <tasknumber> <text> add note to task
|
148
|
+
* delnote <tasknumber> delete all notes from task
|
149
|
+
|
150
|
+
* list <regex> [regex...] list tasks (only active tasks by default)
|
151
|
+
* show <tasknumber> show all task details
|
152
|
+
* repl enter read-eval-print loop mode
|
153
|
+
* help this help screen
|
154
|
+
|
155
|
+
With list command the following pre-defined regex patterns can be also used:
|
156
|
+
#{@queries.keys.join(', ')}
|
157
|
+
|
158
|
+
Due dates can be also added via tags in task title: "due:YYYY-MM-DD"
|
159
|
+
|
160
|
+
Legend:
|
161
|
+
#{STATES.select { |k, v| k != 'default' }.map { |k, v| "#{k} #{v}" }.join(', ') }, priority #{PRIORITY_FLAG}
|
162
|
+
|
163
|
+
Todo file: #{TODO_FILE}
|
164
|
+
USAGE
|
141
165
|
end
|
142
|
-
tasks
|
143
|
-
end
|
144
166
|
|
145
|
-
def
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
end
|
167
|
+
def setup
|
168
|
+
@today = Date.today
|
169
|
+
next_7_days = (0..6).map do |day| @today + day end
|
170
|
+
@due_date_days = next_7_days.map do |day| day.strftime('%A').downcase end
|
171
|
+
due_dates_for_queries = next_7_days.map do |day| day.strftime(DATE_FORMAT) end
|
172
|
+
|
173
|
+
@queries = {
|
174
|
+
':active' => 'state=(new|started|blocked)',
|
175
|
+
':done' => 'state=done',
|
176
|
+
':blocked' => 'state=blocked',
|
177
|
+
':started' => 'state=started',
|
178
|
+
':new' => 'state=new',
|
179
|
+
':all' => 'state=\w+',
|
180
|
+
':today' => "due=#{due_dates_for_queries[0]}",
|
181
|
+
':tomorrow' => "due=#{due_dates_for_queries[1]}",
|
182
|
+
':next7days' => "due=(#{due_dates_for_queries.join('|')})"
|
183
|
+
}
|
150
184
|
end
|
151
|
-
end
|
152
185
|
|
153
|
-
def
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
186
|
+
def load_tasks(item_to_check = nil)
|
187
|
+
count = 0
|
188
|
+
tasks = {}
|
189
|
+
if File.exist?(TODO_FILE)
|
190
|
+
File.open(TODO_FILE, 'r:UTF-8') do |file|
|
191
|
+
file.each_line do |line|
|
192
|
+
next if line.strip == ''
|
193
|
+
count += 1
|
194
|
+
tasks[count] = JSON.parse(line.chomp, :symbolize_names => true)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
if item_to_check && !tasks.has_key?(item_to_check)
|
199
|
+
raise "#{item_to_check}: No such todo"
|
200
|
+
end
|
201
|
+
tasks
|
158
202
|
end
|
159
|
-
raise 'title must not be empty' if task[:title].empty?
|
160
|
-
end
|
161
203
|
|
162
|
-
def
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
postprocess_tags(task)
|
169
|
-
File.open(TODO_FILE, 'a:UTF-8') do |file|
|
170
|
-
file.write(JSON.generate(task) + "\n")
|
204
|
+
def write_tasks(tasks)
|
205
|
+
File.open(TODO_FILE, 'w:UTF-8') do |file|
|
206
|
+
tasks.keys.sort.each do |key|
|
207
|
+
file.write(JSON.generate(tasks[key]) + "\n")
|
208
|
+
end
|
209
|
+
end
|
171
210
|
end
|
172
|
-
list
|
173
|
-
end
|
174
211
|
|
175
|
-
def
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
end
|
212
|
+
def postprocess_tags(task)
|
213
|
+
match_data = task[:title].match(DUE_DATE_TAG_PATTERN)
|
214
|
+
if match_data
|
215
|
+
task[:title] = task[:title].gsub(DUE_DATE_TAG_PATTERN, '')
|
216
|
+
task[:due] = convert_due_date(match_data[2])
|
217
|
+
end
|
218
|
+
raise 'title must not be empty' if task[:title].empty?
|
219
|
+
end
|
183
220
|
|
184
|
-
def
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
221
|
+
def add(text)
|
222
|
+
task = {
|
223
|
+
state: 'new',
|
224
|
+
title: text,
|
225
|
+
modified: @today.strftime(DATE_FORMAT)
|
226
|
+
}
|
227
|
+
postprocess_tags(task)
|
228
|
+
File.open(TODO_FILE, 'a:UTF-8') do |file|
|
229
|
+
file.write(JSON.generate(task) + "\n")
|
230
|
+
end
|
231
|
+
list
|
232
|
+
end
|
192
233
|
|
193
|
-
def
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
234
|
+
def append(item, text = '')
|
235
|
+
tasks = load_tasks(item)
|
236
|
+
tasks[item][:title] = [tasks[item][:title], text].join(' ')
|
237
|
+
tasks[item][:modified] = @today.strftime(DATE_FORMAT)
|
238
|
+
postprocess_tags(tasks[item])
|
239
|
+
write_tasks(tasks)
|
240
|
+
list(tasks)
|
241
|
+
end
|
199
242
|
|
200
|
-
def
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
tasks
|
206
|
-
tasks
|
243
|
+
def rename(item, text)
|
244
|
+
tasks = load_tasks(item)
|
245
|
+
tasks[item][:title] = text
|
246
|
+
tasks[item][:modified] = @today.strftime(DATE_FORMAT)
|
247
|
+
postprocess_tags(tasks[item])
|
248
|
+
write_tasks(tasks)
|
249
|
+
list(tasks)
|
207
250
|
end
|
208
|
-
write_tasks(tasks)
|
209
|
-
list(tasks)
|
210
|
-
end
|
211
251
|
|
212
|
-
def
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
end
|
252
|
+
def delete(item)
|
253
|
+
tasks = load_tasks(item)
|
254
|
+
tasks.delete(item)
|
255
|
+
write_tasks(tasks)
|
256
|
+
list
|
257
|
+
end
|
219
258
|
|
220
|
-
def
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
259
|
+
def change_state(item, state, note = nil)
|
260
|
+
tasks = load_tasks(item)
|
261
|
+
tasks[item][:state] = state
|
262
|
+
tasks[item][:modified] = @today.strftime(DATE_FORMAT)
|
263
|
+
if !note.nil? && !note.empty?
|
264
|
+
tasks[item][:note] ||= []
|
265
|
+
tasks[item][:note].push(note)
|
266
|
+
end
|
267
|
+
write_tasks(tasks)
|
268
|
+
list(tasks)
|
269
|
+
end
|
227
270
|
|
228
|
-
def
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
patterns.each do |pattern|
|
237
|
-
match = false unless /#{QUERIES[pattern] || pattern}/ix.match(normalized_task)
|
271
|
+
def set_priority(item, note = nil)
|
272
|
+
tasks = load_tasks(item)
|
273
|
+
tasks[item][:priority] = !tasks[item][:priority]
|
274
|
+
tasks[item].delete(:priority) if !tasks[item][:priority]
|
275
|
+
tasks[item][:modified] = @today.strftime(DATE_FORMAT)
|
276
|
+
if !note.nil? && !note.empty?
|
277
|
+
tasks[item][:note] ||= []
|
278
|
+
tasks[item][:note].push(note)
|
238
279
|
end
|
239
|
-
|
280
|
+
write_tasks(tasks)
|
281
|
+
list(tasks)
|
240
282
|
end
|
241
|
-
|
242
|
-
|
283
|
+
|
284
|
+
def due_date(item, date = '')
|
285
|
+
tasks = load_tasks(item)
|
286
|
+
tasks[item][:due] = convert_due_date(date)
|
287
|
+
tasks[item].delete(:due) if tasks[item][:due].nil?
|
288
|
+
tasks[item][:modified] = @today.strftime(DATE_FORMAT)
|
289
|
+
write_tasks(tasks)
|
290
|
+
list(tasks)
|
243
291
|
end
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
292
|
+
|
293
|
+
def list(tasks = nil, patterns = nil)
|
294
|
+
items = {}
|
295
|
+
tasks = tasks || load_tasks
|
296
|
+
task_indent = [tasks.keys.max.to_s.size, 4].max
|
297
|
+
patterns = patterns.nil? || patterns.empty? ? [@queries[':active']] : patterns
|
298
|
+
tasks.each do |num, task|
|
299
|
+
normalized_task = "state=#{task[:state]} due=#{task[:due]} #{task[:title]}"
|
300
|
+
match = true
|
301
|
+
patterns.each do |pattern|
|
302
|
+
match = false unless /#{@queries[pattern] || pattern}/ix.match(normalized_task)
|
303
|
+
end
|
304
|
+
items[num] = task if match
|
250
305
|
end
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
306
|
+
items = items.sort_by do |num, task|
|
307
|
+
[task[:priority] && task[:state] != 'done' ? 0 : 1, ORDER[task[:state] || 'default'], task[:due] || 'n/a', num]
|
308
|
+
end
|
309
|
+
items.each do |num, task|
|
310
|
+
state = task[:state] || 'default'
|
311
|
+
color = COLORS[state]
|
312
|
+
display_state = colorize(STATES[state], color)
|
313
|
+
title = task[:title].gsub(CONTEXT_TAG_PATTERN) do |tag|
|
314
|
+
(tag.start_with?(' ') ? ' ' : '') + colorize(tag.strip, :cyan)
|
315
|
+
end
|
316
|
+
priority_flag = task[:priority] ? colorize(PRIORITY_FLAG, :red) : ' '
|
317
|
+
due_date = ''
|
318
|
+
if task[:due] && state != 'done'
|
319
|
+
date_diff = (Date.strptime(task[:due], DATE_FORMAT) - @today).to_i
|
320
|
+
if date_diff < 0
|
321
|
+
due_date = colorize("(#{date_diff.abs}d overdue)", :red)
|
322
|
+
elsif date_diff == 0 || date_diff == 1
|
323
|
+
due_date = colorize("(#{DUE_DATE_DAYS_SIMPLE[date_diff]})", :yellow)
|
324
|
+
else
|
325
|
+
due_date = colorize("(#{@due_date_days[date_diff] || task[:due]})", :magenta) if date_diff > 1
|
326
|
+
end
|
327
|
+
due_date = ' ' + due_date
|
261
328
|
end
|
262
|
-
|
329
|
+
puts "#{num.to_s.rjust(task_indent, ' ')}:#{priority_flag}#{display_state} #{title}#{due_date}"
|
263
330
|
end
|
264
|
-
puts
|
331
|
+
puts 'No todos found' if items.empty?
|
265
332
|
end
|
266
|
-
puts 'No todos found' if items.empty?
|
267
|
-
end
|
268
|
-
|
269
|
-
def add_note(item, text)
|
270
|
-
tasks = load_tasks(item)
|
271
|
-
tasks[item][:note] ||= []
|
272
|
-
tasks[item][:note].push(text)
|
273
|
-
tasks[item][:modified] = Time.now.strftime(DATE_FORMAT)
|
274
|
-
write_tasks(tasks)
|
275
|
-
show(item)
|
276
|
-
end
|
277
333
|
|
278
|
-
def
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
334
|
+
def add_note(item, text)
|
335
|
+
tasks = load_tasks(item)
|
336
|
+
tasks[item][:note] ||= []
|
337
|
+
tasks[item][:note].push(text)
|
338
|
+
tasks[item][:modified] = @today.strftime(DATE_FORMAT)
|
339
|
+
write_tasks(tasks)
|
340
|
+
show(item)
|
341
|
+
end
|
285
342
|
|
286
|
-
def
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
343
|
+
def delete_note(item)
|
344
|
+
tasks = load_tasks(item)
|
345
|
+
tasks[item].delete(:note)
|
346
|
+
tasks[item][:modified] = @today.strftime(DATE_FORMAT)
|
347
|
+
write_tasks(tasks)
|
348
|
+
show(item)
|
291
349
|
end
|
292
|
-
end
|
293
350
|
|
294
|
-
def
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
else
|
300
|
-
read(command == 'repl' ? [] : command.split(/\s+/))
|
351
|
+
def show(item)
|
352
|
+
tasks = load_tasks(item)
|
353
|
+
tasks[item].each do |key, value|
|
354
|
+
val = value.kind_of?(Array) ? "\n" + value.join("\n") : value
|
355
|
+
puts "#{colorize(key.to_s.rjust(10, ' ') + ':', :cyan)} #{val}"
|
301
356
|
end
|
302
|
-
print "\ntodo> "
|
303
|
-
command = STDIN.gets.chomp
|
304
357
|
end
|
305
|
-
end
|
306
358
|
|
307
|
-
def
|
308
|
-
|
309
|
-
|
359
|
+
def start_repl
|
360
|
+
command = ''
|
361
|
+
while !['exit', 'quit'].include?(command)
|
362
|
+
if ['clear', 'cls'].include?(command)
|
363
|
+
print "\e[H\e[2J"
|
364
|
+
else
|
365
|
+
execute(command == 'repl' ? [] : command.split(/\s+/))
|
366
|
+
end
|
367
|
+
print "\ntodo> "
|
368
|
+
command = STDIN.gets.chomp
|
369
|
+
end
|
370
|
+
end
|
310
371
|
|
311
|
-
def
|
312
|
-
|
313
|
-
day_index = DUE_DATE_DAYS.index(date.to_s.downcase) ||
|
314
|
-
DUE_DATE_DAYS_SIMPLE.index(date.to_s.downcase) ||
|
315
|
-
DUE_DATE_DAYS.map do |day| day[0..2] end.index(date.to_s.downcase)
|
316
|
-
if day_index
|
317
|
-
due = (TODAY.to_date + day_index).strftime(DATE_FORMAT)
|
318
|
-
else
|
319
|
-
due = date.nil? || date.empty? ? nil : Date.parse(date).strftime(DATE_FORMAT)
|
372
|
+
def colorize(text, color)
|
373
|
+
"\e[#{COLOR_CODES[color]}m#{text}\e[0m"
|
320
374
|
end
|
321
|
-
return due
|
322
|
-
end
|
323
375
|
|
324
|
-
def
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
add(args.join(' '))
|
332
|
-
when 'start'
|
333
|
-
args.length > 0 ? change_state(args.first.to_i, 'started', args[1..-1].join(' ')) : list(nil, [':started'])
|
334
|
-
when 'done'
|
335
|
-
args.length > 0 ? change_state(args.first.to_i, 'done', args[1..-1].join(' ')) : list(nil, [':done'])
|
336
|
-
when 'block'
|
337
|
-
args.length > 0 ? change_state(args.first.to_i, 'blocked', args[1..-1].join(' ')) : list(nil, [':blocked'])
|
338
|
-
when 'reset'
|
339
|
-
args.length > 0 ? change_state(args.first.to_i, 'new', args[1..-1].join(' ')) : list(nil, [':new'])
|
340
|
-
when 'prio'
|
341
|
-
raise action + ' command requires exactly one parameter' if args.length != 1
|
342
|
-
set_priority(args.first.to_i)
|
343
|
-
when 'due'
|
344
|
-
raise action + ' command requires at least one parameter' if args.length < 1
|
345
|
-
due_date(args.first.to_i, (args[1..-1] || []).join(' '))
|
346
|
-
when 'append'
|
347
|
-
raise action + ' command requires at least two parameters' if args.length < 2
|
348
|
-
append(args.first.to_i, args[1..-1].join(' '))
|
349
|
-
when 'rename'
|
350
|
-
raise action + ' command requires at least two parameters' if args.length < 2
|
351
|
-
rename(args.first.to_i, args[1..-1].join(' '))
|
352
|
-
when 'del'
|
353
|
-
raise action + ' command requires exactly one parameter' if args.length != 1
|
354
|
-
delete(args.first.to_i)
|
355
|
-
when 'note'
|
356
|
-
raise action + ' command requires at least two parameters' if args.length < 2
|
357
|
-
add_note(args.first.to_i, args[1..-1].join(' '))
|
358
|
-
when 'delnote'
|
359
|
-
raise action + ' command requires exactly one parameter' if args.length != 1
|
360
|
-
delete_note(args.first.to_i)
|
361
|
-
when 'list'
|
362
|
-
list(nil, args)
|
363
|
-
when 'show'
|
364
|
-
raise action + ' command requires exactly one parameter' if args.length != 1
|
365
|
-
show(args.first.to_i)
|
366
|
-
when 'help'
|
367
|
-
puts usage
|
368
|
-
when 'repl'
|
369
|
-
start_repl
|
376
|
+
def convert_due_date(date = '')
|
377
|
+
due = nil
|
378
|
+
day_index = @due_date_days.index(date.to_s.downcase) ||
|
379
|
+
DUE_DATE_DAYS_SIMPLE.index(date.to_s.downcase) ||
|
380
|
+
@due_date_days.map do |day| day[0..2] end.index(date.to_s.downcase)
|
381
|
+
if day_index
|
382
|
+
due = (@today + day_index).strftime(DATE_FORMAT)
|
370
383
|
else
|
371
|
-
|
384
|
+
due = date.nil? || date.empty? ? nil : Date.strptime(date, DATE_FORMAT).strftime(DATE_FORMAT)
|
372
385
|
end
|
373
|
-
|
374
|
-
puts "#{colorize('ERROR:', :red)} #{error}"
|
386
|
+
return due
|
375
387
|
end
|
388
|
+
|
376
389
|
end
|
377
390
|
|
378
|
-
|
391
|
+
Todo.new.execute(ARGV)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: todo-jsonl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.20
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gabor Bata
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-02
|
11
|
+
date: 2021-03-02 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|