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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/bin/todo.rb +70 -32
  3. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7aab10deef9e956db5b18eaed5938749e225e3fdb99764c119fabe7bfa16969f
4
- data.tar.gz: 682cf59b330c12e2276e350dba4112e34a56edbf0274276eabb12be81c3b406d
3
+ metadata.gz: bb612ac4efe61a2da25f5438e4e158612ca1d1837484a5338d8768e527fd9e71
4
+ data.tar.gz: '079821ed0936b0a250133b18304a1110d59b3fa5f5e008c228ae4c9d01a98799'
5
5
  SHA512:
6
- metadata.gz: 9f4e3193f34df98c4cc50b86722246724cb31dd3d54d754435db5d80a0ac0300a026d5e09c479061a1f8fd65f40773dff5d175184078c573c1d102344a5dd7f7
7
- data.tar.gz: 824d0796aa292733ef672d2998377975b3b55a43021224213fa9b78af52fb3dc96842db1d7c00dfe727d6ea593d2bfb694e4072234932d5aff1a53919d48a615
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
- DUE_DATE_DAYS = ['today', 'tomorrow']
79
- (2..6).each do |day|
80
- DUE_DATE_DAYS.push((TODAY.to_date + day).strftime('%A').downcase)
81
- end
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> mark task as started
94
- * done <tasknumber> mark task as completed
95
- * block <tasknumber> mark task as blocked
96
- * reset <tasknumber> reset task to new state
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> <date> set due date (in YYYY-MM-DD format)
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] = date.nil? || date.empty? ? nil : Date.parse(date).strftime(DATE_FORMAT)
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(/@\w+/) { |tag| colorize(tag, :cyan) }
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("(#{DUE_DATE_DAYS[date_diff]})", :yellow)
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(4, ' ')}:#{priority_flag}#{display_state} #{title}#{due_date}"
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 == 1 ? change_state(args.first.to_i, 'started') : list(nil, [':started'])
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 == 1 ? change_state(args.first.to_i, 'done') : list(nil, [':done'])
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 == 1 ? change_state(args.first.to_i, 'blocked') : list(nil, [':blocked'])
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 == 1 ? change_state(args.first.to_i, 'new') : list(nil, [':new'])
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.7
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: 2020-10-05 00:00:00.000000000 Z
11
+ date: 2021-02-25 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: