todo-jsonl 0.1.16 → 0.1.21

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