todo-jsonl 0.1.9 → 0.1.15
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 +69 -36
- 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: eac1c59e2d905a14881aae2903048c63b0b3ff796a1d8511cff286e0d64052fa
|
4
|
+
data.tar.gz: b7105e1b10437375614ea5f696533613100a81eaa57cc11590a1248b2f1ad082
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef12c1dd2fd26e691a68b8e7934e431cc3cca2eb6542c03671481ecfdbcc29515b133b9db45d103e7cf554da9b12be20139463254192fb55a8fb381fc97345fc
|
7
|
+
data.tar.gz: bcabeafc8735514c3b4f4f19e2c70a970588b7bff168f1f1e928a1b06a79726527cb9df4d3a7250648d256de9749290b70a712bb19f01479481531884536570a
|
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,19 +62,28 @@ 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
|
77
|
-
|
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
|
78
70
|
DUE_DATE_DAYS_SIMPLE = ['today', 'tomorrow']
|
79
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
|
+
|
80
87
|
PRIORITY_FLAG = '*'
|
81
88
|
|
82
89
|
TODO_FILE = "#{ENV['HOME']}/todo.jsonl"
|
@@ -87,12 +94,12 @@ def usage
|
|
87
94
|
|
88
95
|
Commands:
|
89
96
|
* add <text> add new task
|
90
|
-
* start <tasknumber>
|
91
|
-
* done <tasknumber>
|
92
|
-
* block <tasknumber>
|
93
|
-
* 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
|
94
101
|
* prio <tasknumber> toggle high priority flag
|
95
|
-
* due <tasknumber>
|
102
|
+
* due <tasknumber> [date] set/unset due date (in YYYY-MM-DD format)
|
96
103
|
|
97
104
|
* append <tasknumber> <text> append text to task title
|
98
105
|
* rename <tasknumber> <text> rename task
|
@@ -108,8 +115,12 @@ def usage
|
|
108
115
|
With list command the following pre-defined regex patterns can be also used:
|
109
116
|
#{QUERIES.keys.join(', ')}
|
110
117
|
|
118
|
+
Due dates can be also added via tags in task title: "due:YYYY-MM-DD"
|
119
|
+
|
111
120
|
Legend:
|
112
121
|
#{STATES.select { |k, v| k != 'default' }.map { |k, v| "#{k} #{v}" }.join(', ') }, priority #{PRIORITY_FLAG}
|
122
|
+
|
123
|
+
Todo file: #{TODO_FILE}
|
113
124
|
USAGE
|
114
125
|
end
|
115
126
|
|
@@ -139,12 +150,22 @@ def write_tasks(tasks)
|
|
139
150
|
end
|
140
151
|
end
|
141
152
|
|
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])
|
158
|
+
end
|
159
|
+
raise 'title must not be empty' if task[:title].empty?
|
160
|
+
end
|
161
|
+
|
142
162
|
def add(text)
|
143
163
|
task = {
|
144
164
|
state: 'new',
|
145
165
|
title: text,
|
146
166
|
modified: Time.now.strftime(DATE_FORMAT)
|
147
167
|
}
|
168
|
+
postprocess_tags(task)
|
148
169
|
File.open(TODO_FILE, 'a:UTF-8') do |file|
|
149
170
|
file.write(JSON.generate(task) + "\n")
|
150
171
|
end
|
@@ -155,6 +176,7 @@ def append(item, text = '')
|
|
155
176
|
tasks = load_tasks(item)
|
156
177
|
tasks[item][:title] = [tasks[item][:title], text].join(' ')
|
157
178
|
tasks[item][:modified] = Time.now.strftime(DATE_FORMAT)
|
179
|
+
postprocess_tags(tasks[item])
|
158
180
|
write_tasks(tasks)
|
159
181
|
list(tasks)
|
160
182
|
end
|
@@ -163,6 +185,7 @@ def rename(item, text)
|
|
163
185
|
tasks = load_tasks(item)
|
164
186
|
tasks[item][:title] = text
|
165
187
|
tasks[item][:modified] = Time.now.strftime(DATE_FORMAT)
|
188
|
+
postprocess_tags(tasks[item])
|
166
189
|
write_tasks(tasks)
|
167
190
|
list(tasks)
|
168
191
|
end
|
@@ -174,10 +197,14 @@ def delete(item)
|
|
174
197
|
list
|
175
198
|
end
|
176
199
|
|
177
|
-
def change_state(item, state)
|
200
|
+
def change_state(item, state, note = nil)
|
178
201
|
tasks = load_tasks(item)
|
179
202
|
tasks[item][:state] = state
|
180
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
|
181
208
|
write_tasks(tasks)
|
182
209
|
list(tasks)
|
183
210
|
end
|
@@ -190,14 +217,9 @@ def set_priority(item)
|
|
190
217
|
list(tasks)
|
191
218
|
end
|
192
219
|
|
193
|
-
def due_date(item, date = '')
|
220
|
+
def due_date(item, date = '', task = nil)
|
194
221
|
tasks = load_tasks(item)
|
195
|
-
|
196
|
-
if day_index
|
197
|
-
tasks[item][:due] = (TODAY.to_date + day_index).strftime(DATE_FORMAT)
|
198
|
-
else
|
199
|
-
tasks[item][:due] = date.nil? || date.empty? ? nil : Date.parse(date).strftime(DATE_FORMAT)
|
200
|
-
end
|
222
|
+
tasks[item][:due] = convert_due_date(date)
|
201
223
|
tasks[item][:modified] = Time.now.strftime(DATE_FORMAT)
|
202
224
|
write_tasks(tasks)
|
203
225
|
list(tasks)
|
@@ -209,7 +231,7 @@ def list(tasks = nil, patterns = nil)
|
|
209
231
|
task_indent = [tasks.keys.max.to_s.size, 4].max
|
210
232
|
patterns = patterns.nil? || patterns.empty? ? [QUERIES[':active']] : patterns
|
211
233
|
tasks.each do |num, task|
|
212
|
-
normalized_task = "state=#{task[:state]} #{task[:title]}"
|
234
|
+
normalized_task = "state=#{task[:state]} due=#{task[:due]} #{task[:title]}"
|
213
235
|
match = true
|
214
236
|
patterns.each do |pattern|
|
215
237
|
match = false unless /#{QUERIES[pattern] || pattern}/ix.match(normalized_task)
|
@@ -223,7 +245,9 @@ def list(tasks = nil, patterns = nil)
|
|
223
245
|
state = task[:state] || 'default'
|
224
246
|
color = COLORS[state]
|
225
247
|
display_state = colorize(STATES[state], color)
|
226
|
-
title = task[:title].gsub(
|
248
|
+
title = task[:title].gsub(CONTEXT_TAG_PATTERN) do |tag|
|
249
|
+
(tag.start_with?(' ') ? ' ' : '') + colorize(tag.strip, :cyan)
|
250
|
+
end
|
227
251
|
priority_flag = task[:priority] ? colorize(PRIORITY_FLAG, :red) : ' '
|
228
252
|
due_date = ''
|
229
253
|
if task[:due] && state != 'done'
|
@@ -284,6 +308,19 @@ def colorize(text, color)
|
|
284
308
|
"\e[#{COLOR_CODES[color]}m#{text}\e[0m"
|
285
309
|
end
|
286
310
|
|
311
|
+
def convert_due_date(date = '')
|
312
|
+
due = nil
|
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)
|
320
|
+
end
|
321
|
+
return due
|
322
|
+
end
|
323
|
+
|
287
324
|
def read(arguments)
|
288
325
|
begin
|
289
326
|
action = arguments.first
|
@@ -293,17 +330,13 @@ def read(arguments)
|
|
293
330
|
raise action + ' command requires at least one parameter' if args.nil? || args.empty?
|
294
331
|
add(args.join(' '))
|
295
332
|
when 'start'
|
296
|
-
|
297
|
-
args.length == 1 ? change_state(args.first.to_i, 'started') : list(nil, [':started'])
|
333
|
+
args.length > 0 ? change_state(args.first.to_i, 'started', args[1..-1].join(' ')) : list(nil, [':started'])
|
298
334
|
when 'done'
|
299
|
-
|
300
|
-
args.length == 1 ? change_state(args.first.to_i, 'done') : list(nil, [':done'])
|
335
|
+
args.length > 0 ? change_state(args.first.to_i, 'done', args[1..-1].join(' ')) : list(nil, [':done'])
|
301
336
|
when 'block'
|
302
|
-
|
303
|
-
args.length == 1 ? change_state(args.first.to_i, 'blocked') : list(nil, [':blocked'])
|
337
|
+
args.length > 0 ? change_state(args.first.to_i, 'blocked', args[1..-1].join(' ')) : list(nil, [':blocked'])
|
304
338
|
when 'reset'
|
305
|
-
|
306
|
-
args.length == 1 ? change_state(args.first.to_i, 'new') : list(nil, [':new'])
|
339
|
+
args.length > 0 ? change_state(args.first.to_i, 'new', args[1..-1].join(' ')) : list(nil, [':new'])
|
307
340
|
when 'prio'
|
308
341
|
raise action + ' command requires exactly one parameter' if args.length != 1
|
309
342
|
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.15
|
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:
|