todo-jsonl 0.1.11 → 0.1.17

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