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.
- checksums.yaml +4 -4
- data/bin/todo.rb +322 -311
- 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: 3c21f9ba1d360f6a7d30edf43d125eb6e99ff98eb8b71d2d36c9bb0ae14283e6
|
4
|
+
data.tar.gz: f2e1bea4d40ff4a612f8ec5976f778e4a339fac49c627c9e85996b928838cdda
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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> [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
|
-
|
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
|
+
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
|
-
|
140
|
-
|
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
|
146
|
-
|
147
|
-
|
148
|
-
|
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
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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
|
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")
|
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
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
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
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
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
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
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
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
tasks
|
206
|
-
tasks
|
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
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
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
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
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
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
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
|
-
|
282
|
+
write_tasks(tasks)
|
283
|
+
list(tasks)
|
244
284
|
end
|
245
|
-
|
246
|
-
|
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
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
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
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
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
|
-
|
331
|
+
puts "#{num.to_s.rjust(task_indent, ' ')}:#{priority_flag}#{display_state} #{title}#{due_date}"
|
267
332
|
end
|
268
|
-
puts
|
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
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
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
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
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
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
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
|
312
|
-
|
313
|
-
|
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
|
316
|
-
|
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
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
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
|
-
|
386
|
+
due = date.nil? || date.empty? ? nil : Date.strptime(date, DATE_FORMAT).strftime(DATE_FORMAT)
|
376
387
|
end
|
377
|
-
|
378
|
-
puts "#{colorize('ERROR:', :red)} #{error}"
|
388
|
+
return due
|
379
389
|
end
|
390
|
+
|
380
391
|
end
|
381
392
|
|
382
|
-
|
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.
|
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-
|
11
|
+
date: 2021-03-03 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|