todo-jsonl 0.1.8 → 0.1.14

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 -31
  3. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ce5f15989aea79c3d9cea13d312faec53a31fbfc92593bdabaea225d9e9fb70
4
- data.tar.gz: ffb39beeaa6f6a9b4e1e1c053c98e8e473d687d4cdde406e577b1339fece18a4
3
+ metadata.gz: c84036e07afa19297ed5bf2e0967d7e715dba649ba5a8bd84bacfe2936ea1d1f
4
+ data.tar.gz: '0291fca44038ce3674086f8078348c1a44d40a2755dbc36479eeabf223abe2db'
5
5
  SHA512:
6
- metadata.gz: 71ac2951cad1bc62ccaf9aead3aece8b33c1a7139f5f21eee214b1994a94aacf67f61a1b3619db26f1cb539a30371f9a33bd9838e0de15b474a525a98ff3b733
7
- data.tar.gz: 016e953216f6360811ba62ada07389ff8788258d03449db38dca10c730bdb90b5ceb30ec31768a387ac5f1db8077155e135fef1fc7ea50c137c5d33d8702fcaa
6
+ metadata.gz: 398dbcea18e7730823ffc9dc65192187b5f0eb223aff1402c6d3089b3a479cdbeebf01f6969970fa4dfc046aad26921367ec67bd032c09fedadc680f01e4aca0
7
+ data.tar.gz: 3987df17cc1cbeb03c8cad2a3dc15f3ff867ff1213b80352a0745d48b0e7b506e32f6b820e25dd2019f7870efb196b207fa1cbede3367faa426ebbbe5d03a715
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)
@@ -207,7 +231,7 @@ def list(tasks = nil, patterns = nil)
207
231
  task_indent = [tasks.keys.max.to_s.size, 4].max
208
232
  patterns = patterns.nil? || patterns.empty? ? [QUERIES[':active']] : patterns
209
233
  tasks.each do |num, task|
210
- normalized_task = "state=#{task[:state]} #{task[:title]}"
234
+ normalized_task = "state=#{task[:state]} due=#{task[:due]} #{task[:title]}"
211
235
  match = true
212
236
  patterns.each do |pattern|
213
237
  match = false unless /#{QUERIES[pattern] || pattern}/ix.match(normalized_task)
@@ -221,7 +245,9 @@ def list(tasks = nil, patterns = nil)
221
245
  state = task[:state] || 'default'
222
246
  color = COLORS[state]
223
247
  display_state = colorize(STATES[state], color)
224
- 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
225
251
  priority_flag = task[:priority] ? colorize(PRIORITY_FLAG, :red) : ' '
226
252
  due_date = ''
227
253
  if task[:due] && state != 'done'
@@ -229,7 +255,7 @@ def list(tasks = nil, patterns = nil)
229
255
  if date_diff < 0
230
256
  due_date = colorize("(#{date_diff.abs}d overdue)", :red)
231
257
  elsif date_diff == 0 || date_diff == 1
232
- due_date = colorize("(#{DUE_DATE_DAYS[date_diff]})", :yellow)
258
+ due_date = colorize("(#{DUE_DATE_DAYS_SIMPLE[date_diff]})", :yellow)
233
259
  else
234
260
  due_date = colorize("(#{DUE_DATE_DAYS[date_diff] || task[:due]})", :magenta) if date_diff > 1
235
261
  end
@@ -282,6 +308,19 @@ def colorize(text, color)
282
308
  "\e[#{COLOR_CODES[color]}m#{text}\e[0m"
283
309
  end
284
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
+
285
324
  def read(arguments)
286
325
  begin
287
326
  action = arguments.first
@@ -291,13 +330,13 @@ def read(arguments)
291
330
  raise action + ' command requires at least one parameter' if args.nil? || args.empty?
292
331
  add(args.join(' '))
293
332
  when 'start'
294
- 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'])
295
334
  when 'done'
296
- 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'])
297
336
  when 'block'
298
- 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'])
299
338
  when 'reset'
300
- 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'])
301
340
  when 'prio'
302
341
  raise action + ' command requires exactly one parameter' if args.length != 1
303
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.8
4
+ version: 0.1.14
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: