todo-jsonl 0.1.7 → 0.1.12
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 +70 -32
- 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: bb612ac4efe61a2da25f5438e4e158612ca1d1837484a5338d8768e527fd9e71
|
4
|
+
data.tar.gz: '079821ed0936b0a250133b18304a1110d59b3fa5f5e008c228ae4c9d01a98799'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6a531a29f85db3d3af7baa47f4b26485639db66eebbe028d3327375ff0ebf5d68958a6713a82bb6ae10c3e5beb305d374273ccc82529e4eccc4c0494bfec95f
|
7
|
+
data.tar.gz: 250acb20cb6edf8b5cfac5fc3b568f5cfc2f72245e0884618c7d8143c5a63e81bed6dfa699aa833d4fc96bff9ac62475e2f65647c3e3f9f342725b623d0101b5
|
data/bin/todo.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# todo.rb - todo list manager inspired by todo.txt using the jsonl format.
|
4
4
|
#
|
5
|
-
# Copyright (c) 2020 Gabor Bata
|
5
|
+
# Copyright (c) 2020-2021 Gabor Bata
|
6
6
|
#
|
7
7
|
# Permission is hereby granted, free of charge, to any person
|
8
8
|
# obtaining a copy of this software and associated documentation files
|
@@ -27,8 +27,6 @@
|
|
27
27
|
require 'json'
|
28
28
|
require 'date'
|
29
29
|
|
30
|
-
DATE_FORMAT = '%Y-%m-%d'
|
31
|
-
|
32
30
|
COLOR_CODES = {
|
33
31
|
black: 30,
|
34
32
|
red: 31,
|
@@ -64,21 +62,27 @@ COLORS = {
|
|
64
62
|
'default' => :magenta
|
65
63
|
}
|
66
64
|
|
67
|
-
QUERIES = {
|
68
|
-
':active' => 'state=(new|started|blocked)',
|
69
|
-
':done' => 'state=done',
|
70
|
-
':blocked' => 'state=blocked',
|
71
|
-
':started' => 'state=started',
|
72
|
-
':new' => 'state=new',
|
73
|
-
':all' => 'state=\w+'
|
74
|
-
}
|
75
|
-
|
76
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']
|
77
71
|
|
78
|
-
|
79
|
-
(
|
80
|
-
|
81
|
-
|
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
|
+
}
|
82
86
|
|
83
87
|
PRIORITY_FLAG = '*'
|
84
88
|
|
@@ -90,12 +94,12 @@ def usage
|
|
90
94
|
|
91
95
|
Commands:
|
92
96
|
* add <text> add new task
|
93
|
-
* start <tasknumber>
|
94
|
-
* done <tasknumber>
|
95
|
-
* block <tasknumber>
|
96
|
-
* reset <tasknumber>
|
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
|
97
101
|
* prio <tasknumber> toggle high priority flag
|
98
|
-
* due <tasknumber>
|
102
|
+
* due <tasknumber> [date] set/unset due date (in YYYY-MM-DD format)
|
99
103
|
|
100
104
|
* append <tasknumber> <text> append text to task title
|
101
105
|
* rename <tasknumber> <text> rename task
|
@@ -111,8 +115,12 @@ def usage
|
|
111
115
|
With list command the following pre-defined regex patterns can be also used:
|
112
116
|
#{QUERIES.keys.join(', ')}
|
113
117
|
|
118
|
+
Due dates can be also added via tags in task title: "due:YYYY-MM-DD"
|
119
|
+
|
114
120
|
Legend:
|
115
121
|
#{STATES.select { |k, v| k != 'default' }.map { |k, v| "#{k} #{v}" }.join(', ') }, priority #{PRIORITY_FLAG}
|
122
|
+
|
123
|
+
Todo file: #{TODO_FILE}
|
116
124
|
USAGE
|
117
125
|
end
|
118
126
|
|
@@ -142,12 +150,22 @@ def write_tasks(tasks)
|
|
142
150
|
end
|
143
151
|
end
|
144
152
|
|
153
|
+
def postprocess_tags(task)
|
154
|
+
title = task[:title]
|
155
|
+
match_data = title.match(DUE_DATE_TAG_PATTERN)
|
156
|
+
if match_data
|
157
|
+
task[:title] = title.gsub(DUE_DATE_TAG_PATTERN, '')
|
158
|
+
task[:due] = convert_due_date(match_data[2])
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
145
162
|
def add(text)
|
146
163
|
task = {
|
147
164
|
state: 'new',
|
148
165
|
title: text,
|
149
166
|
modified: Time.now.strftime(DATE_FORMAT)
|
150
167
|
}
|
168
|
+
postprocess_tags(task)
|
151
169
|
File.open(TODO_FILE, 'a:UTF-8') do |file|
|
152
170
|
file.write(JSON.generate(task) + "\n")
|
153
171
|
end
|
@@ -158,6 +176,7 @@ def append(item, text = '')
|
|
158
176
|
tasks = load_tasks(item)
|
159
177
|
tasks[item][:title] = [tasks[item][:title], text].join(' ')
|
160
178
|
tasks[item][:modified] = Time.now.strftime(DATE_FORMAT)
|
179
|
+
postprocess_tags(tasks[item])
|
161
180
|
write_tasks(tasks)
|
162
181
|
list(tasks)
|
163
182
|
end
|
@@ -166,6 +185,7 @@ def rename(item, text)
|
|
166
185
|
tasks = load_tasks(item)
|
167
186
|
tasks[item][:title] = text
|
168
187
|
tasks[item][:modified] = Time.now.strftime(DATE_FORMAT)
|
188
|
+
postprocess_tags(tasks[item])
|
169
189
|
write_tasks(tasks)
|
170
190
|
list(tasks)
|
171
191
|
end
|
@@ -177,10 +197,14 @@ def delete(item)
|
|
177
197
|
list
|
178
198
|
end
|
179
199
|
|
180
|
-
def change_state(item, state)
|
200
|
+
def change_state(item, state, note = nil)
|
181
201
|
tasks = load_tasks(item)
|
182
202
|
tasks[item][:state] = state
|
183
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)
|
207
|
+
end
|
184
208
|
write_tasks(tasks)
|
185
209
|
list(tasks)
|
186
210
|
end
|
@@ -193,9 +217,9 @@ def set_priority(item)
|
|
193
217
|
list(tasks)
|
194
218
|
end
|
195
219
|
|
196
|
-
def due_date(item, date = '')
|
220
|
+
def due_date(item, date = '', task = nil)
|
197
221
|
tasks = load_tasks(item)
|
198
|
-
tasks[item][:due] =
|
222
|
+
tasks[item][:due] = convert_due_date(date)
|
199
223
|
tasks[item][:modified] = Time.now.strftime(DATE_FORMAT)
|
200
224
|
write_tasks(tasks)
|
201
225
|
list(tasks)
|
@@ -204,9 +228,10 @@ end
|
|
204
228
|
def list(tasks = nil, patterns = nil)
|
205
229
|
items = {}
|
206
230
|
tasks = tasks || load_tasks
|
231
|
+
task_indent = [tasks.keys.max.to_s.size, 4].max
|
207
232
|
patterns = patterns.nil? || patterns.empty? ? [QUERIES[':active']] : patterns
|
208
233
|
tasks.each do |num, task|
|
209
|
-
normalized_task = "state=#{task[:state]} #{task[:title]}"
|
234
|
+
normalized_task = "state=#{task[:state]} due=#{task[:due]} #{task[:title]}"
|
210
235
|
match = true
|
211
236
|
patterns.each do |pattern|
|
212
237
|
match = false unless /#{QUERIES[pattern] || pattern}/ix.match(normalized_task)
|
@@ -220,7 +245,9 @@ def list(tasks = nil, patterns = nil)
|
|
220
245
|
state = task[:state] || 'default'
|
221
246
|
color = COLORS[state]
|
222
247
|
display_state = colorize(STATES[state], color)
|
223
|
-
title = task[:title].gsub(
|
248
|
+
title = task[:title].gsub(CONTEXT_TAG_PATTERN) do |tag|
|
249
|
+
(tag.start_with?(' ') ? ' ' : '') + colorize(tag.strip, :cyan)
|
250
|
+
end
|
224
251
|
priority_flag = task[:priority] ? colorize(PRIORITY_FLAG, :red) : ' '
|
225
252
|
due_date = ''
|
226
253
|
if task[:due] && state != 'done'
|
@@ -228,13 +255,13 @@ def list(tasks = nil, patterns = nil)
|
|
228
255
|
if date_diff < 0
|
229
256
|
due_date = colorize("(#{date_diff.abs}d overdue)", :red)
|
230
257
|
elsif date_diff == 0 || date_diff == 1
|
231
|
-
due_date = colorize("(#{
|
258
|
+
due_date = colorize("(#{DUE_DATE_DAYS_SIMPLE[date_diff]})", :yellow)
|
232
259
|
else
|
233
260
|
due_date = colorize("(#{DUE_DATE_DAYS[date_diff] || task[:due]})", :magenta) if date_diff > 1
|
234
261
|
end
|
235
262
|
due_date = ' ' + due_date
|
236
263
|
end
|
237
|
-
puts "#{num.to_s.rjust(
|
264
|
+
puts "#{num.to_s.rjust(task_indent, ' ')}:#{priority_flag}#{display_state} #{title}#{due_date}"
|
238
265
|
end
|
239
266
|
puts 'No todos found' if items.empty?
|
240
267
|
end
|
@@ -281,6 +308,17 @@ def colorize(text, color)
|
|
281
308
|
"\e[#{COLOR_CODES[color]}m#{text}\e[0m"
|
282
309
|
end
|
283
310
|
|
311
|
+
def convert_due_date(date = '')
|
312
|
+
due = nil
|
313
|
+
day_index = DUE_DATE_DAYS.index(date.to_s.downcase) || DUE_DATE_DAYS_SIMPLE.index(date.to_s.downcase)
|
314
|
+
if day_index
|
315
|
+
due = (TODAY.to_date + day_index).strftime(DATE_FORMAT)
|
316
|
+
else
|
317
|
+
due = date.nil? || date.empty? ? nil : Date.parse(date).strftime(DATE_FORMAT)
|
318
|
+
end
|
319
|
+
return due
|
320
|
+
end
|
321
|
+
|
284
322
|
def read(arguments)
|
285
323
|
begin
|
286
324
|
action = arguments.first
|
@@ -290,13 +328,13 @@ def read(arguments)
|
|
290
328
|
raise action + ' command requires at least one parameter' if args.nil? || args.empty?
|
291
329
|
add(args.join(' '))
|
292
330
|
when 'start'
|
293
|
-
args.length
|
331
|
+
args.length > 0 ? change_state(args.first.to_i, 'started', args[1..-1].join(' ')) : list(nil, [':started'])
|
294
332
|
when 'done'
|
295
|
-
args.length
|
333
|
+
args.length > 0 ? change_state(args.first.to_i, 'done', args[1..-1].join(' ')) : list(nil, [':done'])
|
296
334
|
when 'block'
|
297
|
-
args.length
|
335
|
+
args.length > 0 ? change_state(args.first.to_i, 'blocked', args[1..-1].join(' ')) : list(nil, [':blocked'])
|
298
336
|
when 'reset'
|
299
|
-
args.length
|
337
|
+
args.length > 0 ? change_state(args.first.to_i, 'new', args[1..-1].join(' ')) : list(nil, [':new'])
|
300
338
|
when 'prio'
|
301
339
|
raise action + ' command requires exactly one parameter' if args.length != 1
|
302
340
|
set_priority(args.first.to_i)
|
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.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gabor Bata
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-02-25 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|