doing 1.0.44 → 1.0.49
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/README.md +5 -4
- data/bin/doing +77 -79
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +655 -682
- metadata +2 -2
data/lib/doing/version.rb
CHANGED
data/lib/doing/wwid.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
|
3
3
|
require 'deep_merge'
|
4
|
+
require 'open3'
|
4
5
|
|
5
6
|
##
|
6
7
|
## @brief String helpers
|
7
8
|
##
|
8
9
|
class String
|
9
10
|
def cap_first
|
10
|
-
|
11
|
+
sub(/^\w/) do |m|
|
11
12
|
m.upcase
|
12
13
|
end
|
13
14
|
end
|
@@ -17,21 +18,21 @@ class String
|
|
17
18
|
##
|
18
19
|
## @param opt (Hash) Additional Options
|
19
20
|
##
|
20
|
-
def link_urls(opt={})
|
21
|
+
def link_urls(opt = {})
|
21
22
|
opt[:format] ||= :html
|
22
23
|
if opt[:format] == :html
|
23
|
-
|
24
|
+
gsub(%r{(?mi)((http|https)://)?([\w\-_]+(\.[\w\-_]+)+)([\w\-.,@?^=%&:/~+#]*[\w\-@^=%&/~+#])?}) do |_match|
|
24
25
|
m = Regexp.last_match
|
25
|
-
proto = m[1].nil? ?
|
26
|
-
%
|
27
|
-
|
26
|
+
proto = m[1].nil? ? 'http://' : ''
|
27
|
+
%(<a href="#{proto}#{m[0]}" title="Link to #{m[0]}">[#{m[3]}]</a>)
|
28
|
+
end.gsub(/<(\w+:.*?)>/) do |match|
|
28
29
|
m = Regexp.last_match
|
29
|
-
|
30
|
-
%Q{<a href="#{m[1]}" title="Link to #{m[1]}">[link]</a>}
|
31
|
-
else
|
30
|
+
if m[1] =~ /<a href/
|
32
31
|
match
|
32
|
+
else
|
33
|
+
%(<a href="#{m[1]}" title="Link to #{m[1]}">[link]</a>)
|
33
34
|
end
|
34
|
-
|
35
|
+
end
|
35
36
|
else
|
36
37
|
self
|
37
38
|
end
|
@@ -42,7 +43,8 @@ end
|
|
42
43
|
## @brief Main "What Was I Doing" methods
|
43
44
|
##
|
44
45
|
class WWID
|
45
|
-
attr_accessor :content, :sections, :current_section, :doing_file, :config, :user_home, :default_config_file,
|
46
|
+
attr_accessor :content, :sections, :current_section, :doing_file, :config, :user_home, :default_config_file,
|
47
|
+
:config_file, :results, :auto_tag
|
46
48
|
|
47
49
|
##
|
48
50
|
## @brief Initializes the object.
|
@@ -62,16 +64,13 @@ class WWID
|
|
62
64
|
## @return (String) A file path
|
63
65
|
##
|
64
66
|
def find_local_config
|
65
|
-
|
66
67
|
config = {}
|
67
68
|
dir = Dir.pwd
|
68
69
|
|
69
70
|
local_config_files = []
|
70
71
|
|
71
|
-
while
|
72
|
-
if File.
|
73
|
-
local_config_files.push(File.join(dir, @default_config_file))
|
74
|
-
end
|
72
|
+
while dir != '/' && (dir =~ %r{[A-Z]:/}).nil?
|
73
|
+
local_config_files.push(File.join(dir, @default_config_file)) if File.exist? File.join(dir, @default_config_file)
|
75
74
|
|
76
75
|
dir = File.dirname(dir)
|
77
76
|
end
|
@@ -82,32 +81,30 @@ class WWID
|
|
82
81
|
##
|
83
82
|
## @brief Reads a configuration.
|
84
83
|
##
|
85
|
-
def read_config(opt={})
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
end
|
92
|
-
end
|
84
|
+
def read_config(opt = {})
|
85
|
+
@config_file ||= if Dir.respond_to?('home')
|
86
|
+
File.join(Dir.home, @default_config_file)
|
87
|
+
else
|
88
|
+
File.join(File.expand_path('~'), @default_config_file)
|
89
|
+
end
|
93
90
|
|
94
|
-
if opt[:ignore_local]
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
91
|
+
additional_configs = if opt[:ignore_local]
|
92
|
+
[]
|
93
|
+
else
|
94
|
+
find_local_config
|
95
|
+
end
|
99
96
|
|
100
97
|
begin
|
101
98
|
@local_config = {}
|
102
99
|
|
103
|
-
@config = YAML.load_file(@config_file) || {} if File.
|
104
|
-
additional_configs.each
|
100
|
+
@config = YAML.load_file(@config_file) || {} if File.exist?(@config_file)
|
101
|
+
additional_configs.each do |cfg|
|
105
102
|
new_config = YAML.load_file(cfg) || {} if cfg
|
106
103
|
@local_config = @local_config.deep_merge(new_config)
|
107
|
-
|
104
|
+
end
|
108
105
|
|
109
106
|
# @config.deep_merge(@local_config)
|
110
|
-
rescue
|
107
|
+
rescue StandardError
|
111
108
|
@config = {}
|
112
109
|
@local_config = {}
|
113
110
|
# raise "error reading config"
|
@@ -119,24 +116,20 @@ class WWID
|
|
119
116
|
##
|
120
117
|
## @param opt (Hash) Additional Options
|
121
118
|
##
|
122
|
-
def configure(opt={})
|
119
|
+
def configure(opt = {})
|
123
120
|
@timers = {}
|
124
121
|
opt[:ignore_local] ||= false
|
125
122
|
|
126
|
-
|
127
|
-
@config_file = File.join(@user_home, @default_config_file)
|
128
|
-
end
|
129
|
-
|
130
|
-
read_config({:ignore_local => opt[:ignore_local]})
|
123
|
+
@config_file ||= File.join(@user_home, @default_config_file)
|
131
124
|
|
132
|
-
|
125
|
+
read_config({ ignore_local: opt[:ignore_local] })
|
133
126
|
|
134
127
|
@config = {} if @config.nil?
|
135
128
|
|
136
129
|
@config['autotag'] ||= {}
|
137
130
|
@config['autotag']['whitelist'] ||= []
|
138
131
|
@config['autotag']['synonyms'] ||= {}
|
139
|
-
@config['doing_file'] ||=
|
132
|
+
@config['doing_file'] ||= '~/what_was_i_doing.md'
|
140
133
|
@config['current_section'] ||= 'Currently'
|
141
134
|
@config['editor_app'] ||= nil
|
142
135
|
|
@@ -177,12 +170,12 @@ class WWID
|
|
177
170
|
'tags_bool' => 'OR'
|
178
171
|
},
|
179
172
|
'color' => {
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
173
|
+
'date_format' => '%F %_I:%M%P',
|
174
|
+
'template' => '%boldblack%date %boldgreen| %boldwhite%title%default%note',
|
175
|
+
'wrap_width' => 0,
|
176
|
+
'section' => 'Currently',
|
177
|
+
'count' => 10,
|
178
|
+
'order' => 'asc'
|
186
179
|
}
|
187
180
|
}
|
188
181
|
@config['marker_tag'] ||= 'flagged'
|
@@ -202,17 +195,13 @@ class WWID
|
|
202
195
|
# end
|
203
196
|
# end
|
204
197
|
|
205
|
-
unless File.
|
206
|
-
File.open(@config_file, 'w') { |yf| YAML::dump(@config, yf) }
|
207
|
-
end
|
198
|
+
File.open(@config_file, 'w') { |yf| YAML.dump(@config, yf) } unless File.exist?(@config_file)
|
208
199
|
|
209
200
|
@config = @local_config.deep_merge(@config)
|
210
201
|
|
211
202
|
@current_section = @config['current_section']
|
212
203
|
@default_template = @config['templates']['default']['template']
|
213
|
-
@default_date_format = @config['templates']['default']['date_format']
|
214
|
-
|
215
|
-
|
204
|
+
@default_date_format = @config['templates']['default']['date_format']
|
216
205
|
end
|
217
206
|
|
218
207
|
##
|
@@ -220,16 +209,16 @@ class WWID
|
|
220
209
|
##
|
221
210
|
## @param path (String) Override path to a doing file, optional
|
222
211
|
##
|
223
|
-
def init_doing_file(path=nil)
|
212
|
+
def init_doing_file(path = nil)
|
224
213
|
@doing_file = File.expand_path(@config['doing_file'])
|
225
214
|
|
226
215
|
input = path
|
227
216
|
|
228
217
|
if input.nil?
|
229
|
-
create(@doing_file) unless File.
|
218
|
+
create(@doing_file) unless File.exist?(@doing_file)
|
230
219
|
input = IO.read(@doing_file)
|
231
220
|
input = input.force_encoding('utf-8') if input.respond_to? :force_encoding
|
232
|
-
elsif File.
|
221
|
+
elsif File.exist?(File.expand_path(input)) && File.file?(File.expand_path(input)) && File.stat(File.expand_path(input)).size.positive?
|
233
222
|
@doing_file = File.expand_path(input)
|
234
223
|
input = IO.read(File.expand_path(input))
|
235
224
|
input = input.force_encoding('utf-8') if input.respond_to? :force_encoding
|
@@ -243,40 +232,36 @@ class WWID
|
|
243
232
|
@other_content_top = []
|
244
233
|
@other_content_bottom = []
|
245
234
|
|
246
|
-
section =
|
235
|
+
section = 'Uncategorized'
|
247
236
|
lines = input.split(/[\n\r]/)
|
248
237
|
current = 0
|
249
238
|
|
250
|
-
lines.each
|
239
|
+
lines.each do |line|
|
251
240
|
next if line =~ /^\s*$/
|
241
|
+
|
252
242
|
if line =~ /^(\S[\S ]+):\s*(@\S+\s*)*$/
|
253
|
-
section =
|
243
|
+
section = Regexp.last_match(1)
|
254
244
|
@content[section] = {}
|
255
245
|
@content[section]['original'] = line
|
256
246
|
@content[section]['items'] = []
|
257
247
|
current = 0
|
258
248
|
elsif line =~ /^\s*- (\d{4}-\d\d-\d\d \d\d:\d\d) \| (.*)/
|
259
|
-
date = Time.parse(
|
260
|
-
title =
|
261
|
-
@content[section]['items'].push({'title' => title, 'date' => date, 'section' => section})
|
249
|
+
date = Time.parse(Regexp.last_match(1))
|
250
|
+
title = Regexp.last_match(2)
|
251
|
+
@content[section]['items'].push({ 'title' => title, 'date' => date, 'section' => section })
|
262
252
|
current += 1
|
263
|
-
|
253
|
+
elsif current.zero?
|
264
254
|
# if content[section]['items'].length - 1 == current
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
@content[section]['items'][current - 1]['note'] = []
|
273
|
-
end
|
274
|
-
@content[section]['items'][current - 1]['note'].push(line.gsub(/ *$/,''))
|
275
|
-
end
|
276
|
-
end
|
255
|
+
@other_content_top.push(line)
|
256
|
+
elsif line =~ /^\S/
|
257
|
+
@other_content_bottom.push(line)
|
258
|
+
else
|
259
|
+
@content[section]['items'][current - 1]['note'] = [] unless @content[section]['items'][current - 1].key? 'note'
|
260
|
+
|
261
|
+
@content[section]['items'][current - 1]['note'].push(line.gsub(/ *$/, ''))
|
277
262
|
# end
|
278
263
|
end
|
279
|
-
|
264
|
+
end
|
280
265
|
end
|
281
266
|
|
282
267
|
##
|
@@ -300,14 +285,12 @@ class WWID
|
|
300
285
|
##
|
301
286
|
## @brief Create a new doing file
|
302
287
|
##
|
303
|
-
def create(filename=nil)
|
304
|
-
if filename.nil?
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
f.puts @current_section + ":"
|
310
|
-
end
|
288
|
+
def create(filename = nil)
|
289
|
+
filename = @doing_file if filename.nil?
|
290
|
+
return if File.exist?(filename) && File.stat(filename).size.positive?
|
291
|
+
|
292
|
+
File.open(filename, 'w+') do |f|
|
293
|
+
f.puts "#{@current_section}:"
|
311
294
|
end
|
312
295
|
end
|
313
296
|
|
@@ -316,22 +299,26 @@ class WWID
|
|
316
299
|
##
|
317
300
|
## @param input (String) Text input for editor
|
318
301
|
##
|
319
|
-
def fork_editor(input=
|
320
|
-
tmpfile = Tempfile.new(['doing','.md'])
|
302
|
+
def fork_editor(input = '')
|
303
|
+
tmpfile = Tempfile.new(['doing', '.md'])
|
321
304
|
|
322
|
-
File.open(tmpfile.path,'w+') do |f|
|
305
|
+
File.open(tmpfile.path, 'w+') do |f|
|
323
306
|
f.puts input
|
324
307
|
f.puts "\n# The first line is the entry title, any lines after that are added as a note"
|
325
308
|
end
|
326
309
|
|
327
310
|
pid = Process.fork { system("$EDITOR #{tmpfile.path}") }
|
328
311
|
|
329
|
-
trap(
|
330
|
-
|
312
|
+
trap('INT') do
|
313
|
+
begin
|
314
|
+
Process.kill(9, pid)
|
315
|
+
rescue StandardError
|
316
|
+
Errno::ESRCH
|
317
|
+
end
|
331
318
|
tmpfile.unlink
|
332
319
|
tmpfile.close!
|
333
320
|
exit 0
|
334
|
-
|
321
|
+
end
|
335
322
|
|
336
323
|
Process.wait(pid)
|
337
324
|
|
@@ -339,7 +326,7 @@ class WWID
|
|
339
326
|
if $?.exitstatus == 0
|
340
327
|
input = IO.read(tmpfile.path)
|
341
328
|
else
|
342
|
-
raise
|
329
|
+
raise 'Cancelled'
|
343
330
|
end
|
344
331
|
ensure
|
345
332
|
tmpfile.close
|
@@ -357,15 +344,13 @@ class WWID
|
|
357
344
|
# @param input (String) The string to parse
|
358
345
|
#
|
359
346
|
def format_input(input)
|
360
|
-
raise
|
347
|
+
raise 'No content in entry' if input.nil? || input.strip.empty?
|
348
|
+
|
361
349
|
input_lines = input.split(/[\n\r]+/)
|
362
350
|
title = input_lines[0].strip
|
363
351
|
note = input_lines.length > 1 ? input_lines[1..-1] : []
|
364
|
-
note.map!
|
365
|
-
|
366
|
-
}.delete_if { |line|
|
367
|
-
line =~ /^\s*$/ || line =~ /^#/
|
368
|
-
}
|
352
|
+
note.map!(&:strip)
|
353
|
+
note.delete_if { |line| line =~ /^\s*$/ || line =~ /^#/ }
|
369
354
|
|
370
355
|
[title, note]
|
371
356
|
end
|
@@ -383,21 +368,22 @@ class WWID
|
|
383
368
|
#
|
384
369
|
def chronify(input)
|
385
370
|
now = Time.now
|
386
|
-
raise "Invalid time expression #{input.inspect}" if input.to_s.strip ==
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
371
|
+
raise "Invalid time expression #{input.inspect}" if input.to_s.strip == ''
|
372
|
+
|
373
|
+
secs_ago = if input.match(/^(\d+)$/)
|
374
|
+
# plain number, assume minutes
|
375
|
+
Regexp.last_match(1).to_i * 60
|
376
|
+
elsif (m = input.match(/^(?:(?<day>\d+)d)?(?:(?<hour>\d+)h)?(?:(?<min>\d+)m)?$/i))
|
377
|
+
# day/hour/minute format e.g. 1d2h30m
|
378
|
+
[[m['day'], 24 * 3600],
|
379
|
+
[m['hour'], 3600],
|
380
|
+
[m['min'], 60]].map { |qty, secs| qty ? (qty.to_i * secs) : 0 }.reduce(0, :+)
|
381
|
+
end
|
396
382
|
|
397
383
|
if secs_ago
|
398
384
|
now - secs_ago
|
399
385
|
else
|
400
|
-
Chronic.parse(input, {:
|
386
|
+
Chronic.parse(input, { context: :past, ambiguous_time_range: 8 })
|
401
387
|
end
|
402
388
|
end
|
403
389
|
|
@@ -412,23 +398,24 @@ class WWID
|
|
412
398
|
#
|
413
399
|
def chronify_qty(qty)
|
414
400
|
minutes = 0
|
415
|
-
|
416
|
-
|
417
|
-
minutes +=
|
418
|
-
|
419
|
-
|
420
|
-
|
401
|
+
case qty.strip
|
402
|
+
when /^(\d+):(\d\d)$/
|
403
|
+
minutes += Regexp.last_match(1).to_i * 60
|
404
|
+
minutes += Regexp.last_match(2).to_i
|
405
|
+
when /^(\d+(?:\.\d+)?)([hmd])?$/
|
406
|
+
amt = Regexp.last_match(1)
|
407
|
+
type = Regexp.last_match(2).nil? ? 'm' : Regexp.last_match(2)
|
421
408
|
|
422
409
|
minutes = case type.downcase
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
410
|
+
when 'm'
|
411
|
+
amt.to_i
|
412
|
+
when 'h'
|
413
|
+
(amt.to_f * 60).round
|
414
|
+
when 'd'
|
415
|
+
(amt.to_f * 60 * 24).round
|
416
|
+
else
|
417
|
+
minutes
|
418
|
+
end
|
432
419
|
end
|
433
420
|
minutes * 60
|
434
421
|
end
|
@@ -448,8 +435,8 @@ class WWID
|
|
448
435
|
## @param title (String) The new section title
|
449
436
|
##
|
450
437
|
def add_section(title)
|
451
|
-
@content[title.cap_first] = {'original' => "#{title}:", 'items' => []}
|
452
|
-
@results.push(%
|
438
|
+
@content[title.cap_first] = { 'original' => "#{title}:", 'items' => [] }
|
439
|
+
@results.push(%(Added section "#{title.cap_first}"))
|
453
440
|
end
|
454
441
|
|
455
442
|
##
|
@@ -458,32 +445,32 @@ class WWID
|
|
458
445
|
## @param frag (String) The user-provided string
|
459
446
|
## @param guessed (Boolean) already guessed and failed
|
460
447
|
##
|
461
|
-
def guess_section(frag,guessed
|
462
|
-
return
|
463
|
-
|
448
|
+
def guess_section(frag, guessed: false)
|
449
|
+
return 'All' if frag =~ /all/i
|
450
|
+
|
451
|
+
sections.each { |section| return section.cap_first if frag.downcase == section.downcase }
|
464
452
|
section = false
|
465
|
-
re = frag.split('').join(
|
466
|
-
sections.each
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
453
|
+
re = frag.split('').join('.*?')
|
454
|
+
sections.each do |sect|
|
455
|
+
next unless sect =~ /#{re}/i
|
456
|
+
|
457
|
+
warn "Assuming you meant #{sect}"
|
458
|
+
section = sect
|
459
|
+
break
|
460
|
+
end
|
473
461
|
unless section || guessed
|
474
|
-
alt = guess_view(frag,true)
|
475
|
-
if alt
|
476
|
-
raise "Did you mean `doing view #{alt}`?"
|
477
|
-
else
|
478
|
-
res = yn("Section #{frag} not found, create it",false)
|
462
|
+
alt = guess_view(frag, true)
|
463
|
+
raise "Did you mean `doing view #{alt}`?" if alt
|
479
464
|
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
465
|
+
res = yn("Section #{frag} not found, create it", default_response: false)
|
466
|
+
|
467
|
+
if res
|
468
|
+
add_section(frag.cap_first)
|
469
|
+
write(@doing_file)
|
470
|
+
return frag.cap_first
|
486
471
|
end
|
472
|
+
|
473
|
+
raise "Unknown section: #{frag}"
|
487
474
|
end
|
488
475
|
section ? section.cap_first : guessed
|
489
476
|
end
|
@@ -496,37 +483,31 @@ class WWID
|
|
496
483
|
##
|
497
484
|
## @return (Bool) yes or no
|
498
485
|
##
|
499
|
-
def yn(question, default_response
|
500
|
-
|
501
|
-
|
502
|
-
else
|
503
|
-
default = 'n'
|
504
|
-
end
|
486
|
+
def yn(question, default_response: false)
|
487
|
+
default = default_response ? 'y' : 'n'
|
488
|
+
|
505
489
|
# if this isn't an interactive shell, answer default
|
506
|
-
unless $stdout.isatty
|
507
|
-
|
508
|
-
return true
|
509
|
-
else
|
510
|
-
return false
|
511
|
-
end
|
512
|
-
end
|
490
|
+
return default.downcase == 'y' unless $stdout.isatty
|
491
|
+
|
513
492
|
# clear the buffer
|
514
|
-
if ARGV
|
493
|
+
if ARGV&.length
|
515
494
|
ARGV.length.times do
|
516
495
|
ARGV.shift
|
517
496
|
end
|
518
497
|
end
|
519
498
|
system 'stty cbreak'
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
499
|
+
|
500
|
+
cw = colors['white']
|
501
|
+
cbw = colors['boldwhite']
|
502
|
+
cbg = colors['boldgreen']
|
503
|
+
cd = colors['default']
|
504
|
+
|
505
|
+
options = if default
|
506
|
+
default =~ /y/i ? "#{cw}[#{cbg}Y#{cw}/#{cbw}n#{cw}]#{cd}" : "#{cw}[#{cbw}y#{cw}/#{cbg}N#{cw}]#{cd}"
|
507
|
+
else
|
508
|
+
"#{cw}[#{cbw}y#{cw}/#{cbw}n#{cw}]#{cd}"
|
509
|
+
end
|
510
|
+
$stdout.syswrite "#{cbw}#{question.sub(/\?$/, '')} #{options}#{cbw}?#{cd} "
|
530
511
|
res = $stdin.sysread 1
|
531
512
|
puts
|
532
513
|
system 'stty cooked'
|
@@ -534,9 +515,9 @@ class WWID
|
|
534
515
|
res.chomp!
|
535
516
|
res.downcase!
|
536
517
|
|
537
|
-
res = default.downcase if res ==
|
518
|
+
res = default.downcase if res == ''
|
538
519
|
|
539
|
-
|
520
|
+
res =~ /y/i
|
540
521
|
end
|
541
522
|
|
542
523
|
##
|
@@ -545,19 +526,19 @@ class WWID
|
|
545
526
|
## @param frag (String) The user-provided string
|
546
527
|
## @param guessed (Boolean) already guessed
|
547
528
|
##
|
548
|
-
def guess_view(frag,guessed=false)
|
549
|
-
views.each {|view| return view if frag.downcase == view.downcase}
|
529
|
+
def guess_view(frag, guessed = false)
|
530
|
+
views.each { |view| return view if frag.downcase == view.downcase }
|
550
531
|
view = false
|
551
|
-
re = frag.split('').join(
|
552
|
-
views.each
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
532
|
+
re = frag.split('').join('.*?')
|
533
|
+
views.each do |v|
|
534
|
+
next unless v =~ /#{re}/i
|
535
|
+
|
536
|
+
warn "Assuming you meant #{v}"
|
537
|
+
view = v
|
538
|
+
break
|
539
|
+
end
|
559
540
|
unless view || guessed
|
560
|
-
alt = guess_section(frag,true)
|
541
|
+
alt = guess_section(frag, guessed: true)
|
561
542
|
if alt
|
562
543
|
raise "Did you mean `doing show #{alt}`?"
|
563
544
|
else
|
@@ -574,7 +555,7 @@ class WWID
|
|
574
555
|
## @param section (String) The section to add to
|
575
556
|
## @param opt (Hash) Additional Options {:date, :note, :back, :timed}
|
576
557
|
##
|
577
|
-
def add_item(title,section=nil,opt={})
|
558
|
+
def add_item(title, section = nil, opt = {})
|
578
559
|
section ||= @current_section
|
579
560
|
add_section(section) unless @content.has_key?(section)
|
580
561
|
opt[:date] ||= Time.now
|
@@ -582,7 +563,7 @@ class WWID
|
|
582
563
|
opt[:back] ||= Time.now
|
583
564
|
opt[:timed] ||= false
|
584
565
|
|
585
|
-
opt[:note] = [opt[:note]] if opt[:note].
|
566
|
+
opt[:note] = [opt[:note]] if opt[:note].instance_of?(String)
|
586
567
|
|
587
568
|
title = [title.strip.cap_first]
|
588
569
|
title = title.join(' ')
|
@@ -590,39 +571,37 @@ class WWID
|
|
590
571
|
if @auto_tag
|
591
572
|
title = autotag(title)
|
592
573
|
unless @config['default_tags'].empty?
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
574
|
+
default_tags = @config['default_tags'].map do |t|
|
575
|
+
next if t.nil?
|
576
|
+
|
577
|
+
dt = t.sub(/^ *@/, '').chomp
|
578
|
+
if title =~ /@#{dt}/
|
579
|
+
''
|
580
|
+
else
|
581
|
+
" @#{dt}"
|
601
582
|
end
|
602
|
-
|
583
|
+
end
|
584
|
+
default_tags.delete_if { |t| t == '' }
|
585
|
+
title += default_tags.join(' ')
|
603
586
|
end
|
604
587
|
end
|
605
|
-
title.gsub!(/ +/,' ')
|
606
|
-
entry = {'title' => title.strip, 'date' => opt[:back]}
|
607
|
-
unless opt[:note].join('').strip == ''
|
608
|
-
entry['note'] = opt[:note].map {|n| n.chomp}
|
609
|
-
end
|
588
|
+
title.gsub!(/ +/, ' ')
|
589
|
+
entry = { 'title' => title.strip, 'date' => opt[:back] }
|
590
|
+
entry['note'] = opt[:note].map(&:chomp) unless opt[:note].join('').strip == ''
|
610
591
|
items = @content[section]['items']
|
611
592
|
if opt[:timed]
|
612
593
|
items.reverse!
|
613
|
-
items.each_with_index
|
614
|
-
if i['title'] =~ / @done/
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
end
|
620
|
-
}
|
594
|
+
items.each_with_index do |i, x|
|
595
|
+
next if i['title'] =~ / @done/
|
596
|
+
|
597
|
+
items[x]['title'] = "#{i['title']} @done(#{opt[:back].strftime('%F %R')})"
|
598
|
+
break
|
599
|
+
end
|
621
600
|
items.reverse!
|
622
601
|
end
|
623
602
|
items.push(entry)
|
624
603
|
@content[section]['items'] = items
|
625
|
-
@results.push(%
|
604
|
+
@results.push(%(Added "#{entry['title']}" to #{section}))
|
626
605
|
end
|
627
606
|
|
628
607
|
##
|
@@ -631,27 +610,23 @@ class WWID
|
|
631
610
|
## @param section (String) The section to retrieve from, default
|
632
611
|
## All
|
633
612
|
##
|
634
|
-
def last_note(section=
|
613
|
+
def last_note(section = 'All')
|
635
614
|
section = guess_section(section)
|
636
615
|
if section =~ /^all$/i
|
637
|
-
combined = {'items' => []}
|
638
|
-
@content.each
|
616
|
+
combined = { 'items' => [] }
|
617
|
+
@content.each do |_k, v|
|
639
618
|
combined['items'] += v['items']
|
640
|
-
}
|
641
|
-
section = combined['items'].dup.sort_by{|item| item['date'] }.reverse[0]['section']
|
642
|
-
end
|
643
|
-
|
644
|
-
if @content.has_key?(section)
|
645
|
-
last_item = @content[section]['items'].dup.sort_by{|item| item['date'] }.reverse[0]
|
646
|
-
$stderr.puts "Editing note for #{last_item['title']}"
|
647
|
-
note = ''
|
648
|
-
unless last_item['note'].nil?
|
649
|
-
note = last_item['note'].map{|line| line.strip }.join("\n")
|
650
619
|
end
|
651
|
-
|
652
|
-
else
|
653
|
-
raise "Section #{section} not found"
|
620
|
+
section = combined['items'].dup.sort_by { |item| item['date'] }.reverse[0]['section']
|
654
621
|
end
|
622
|
+
|
623
|
+
raise "Section #{section} not found" unless @content.key?(section)
|
624
|
+
|
625
|
+
last_item = @content[section]['items'].dup.sort_by { |item| item['date'] }.reverse[0]
|
626
|
+
warn "Editing note for #{last_item['title']}"
|
627
|
+
note = ''
|
628
|
+
note = last_item['note'].map(&:strip).join("\n") unless last_item['note'].nil?
|
629
|
+
"#{last_item['title']}\n# EDIT BELOW THIS LINE ------------\n#{note}"
|
655
630
|
end
|
656
631
|
|
657
632
|
##
|
@@ -665,12 +640,13 @@ class WWID
|
|
665
640
|
|
666
641
|
last = last_entry(opt)
|
667
642
|
if last.nil?
|
668
|
-
@results.push(%
|
643
|
+
@results.push(%(No previous entry found))
|
669
644
|
return
|
670
645
|
end
|
671
646
|
# Remove @done tag
|
672
|
-
title = last['title'].sub
|
673
|
-
|
647
|
+
title = last['title'].sub(/\s*@done(\(.*?\))?/, '').chomp
|
648
|
+
@auto_tag = false
|
649
|
+
add_item(title, last['section'], { note: opt[:note], back: opt[:date], timed: true })
|
674
650
|
write(@doing_file)
|
675
651
|
end
|
676
652
|
|
@@ -679,20 +655,20 @@ class WWID
|
|
679
655
|
##
|
680
656
|
## @param opt (Hash) Additional Options
|
681
657
|
##
|
682
|
-
def last_entry(opt={})
|
658
|
+
def last_entry(opt = {})
|
683
659
|
opt[:section] ||= @current_section
|
684
660
|
|
685
661
|
sec_arr = []
|
686
662
|
|
687
663
|
if opt[:section].nil?
|
688
664
|
sec_arr = [@current_section]
|
689
|
-
elsif opt[:section].
|
665
|
+
elsif opt[:section].instance_of?(String)
|
690
666
|
if opt[:section] =~ /^all$/i
|
691
|
-
combined = {'items' => []}
|
692
|
-
@content.each
|
667
|
+
combined = { 'items' => [] }
|
668
|
+
@content.each do |_k, v|
|
693
669
|
combined['items'] += v['items']
|
694
|
-
|
695
|
-
items = combined['items'].dup.sort_by{|item| item['date'] }.reverse
|
670
|
+
end
|
671
|
+
items = combined['items'].dup.sort_by { |item| item['date'] }.reverse
|
696
672
|
sec_arr.push(items[0]['section'])
|
697
673
|
|
698
674
|
sec_arr = sections
|
@@ -701,13 +677,12 @@ class WWID
|
|
701
677
|
end
|
702
678
|
end
|
703
679
|
|
704
|
-
|
705
680
|
all_items = []
|
706
681
|
sec_arr.each do |section|
|
707
|
-
all_items.concat(@content[section]['items'].dup) if @content.
|
682
|
+
all_items.concat(@content[section]['items'].dup) if @content.key?(section)
|
708
683
|
end
|
709
684
|
|
710
|
-
all_items.
|
685
|
+
all_items.max_by { |item| item['date'] }
|
711
686
|
end
|
712
687
|
|
713
688
|
##
|
@@ -715,33 +690,32 @@ class WWID
|
|
715
690
|
##
|
716
691
|
## @param opt (Hash) Additional Options
|
717
692
|
##
|
718
|
-
def tag_last(opt={})
|
693
|
+
def tag_last(opt = {})
|
719
694
|
opt[:section] ||= @current_section
|
720
695
|
opt[:count] ||= 1
|
721
696
|
opt[:archive] ||= false
|
722
|
-
opt[:tags] ||= [
|
697
|
+
opt[:tags] ||= ['done']
|
723
698
|
opt[:sequential] ||= false
|
724
699
|
opt[:date] ||= false
|
725
700
|
opt[:remove] ||= false
|
726
701
|
opt[:autotag] ||= false
|
727
702
|
opt[:back] ||= false
|
728
703
|
|
729
|
-
|
730
704
|
sec_arr = []
|
731
705
|
|
732
706
|
if opt[:section].nil?
|
733
707
|
sec_arr = [@current_section]
|
734
|
-
elsif opt[:section].
|
708
|
+
elsif opt[:section].instance_of?(String)
|
735
709
|
if opt[:section] =~ /^all$/i
|
736
710
|
if opt[:count] == 1
|
737
|
-
combined = {'items' => []}
|
738
|
-
@content.each
|
711
|
+
combined = { 'items' => [] }
|
712
|
+
@content.each do |_k, v|
|
739
713
|
combined['items'] += v['items']
|
740
|
-
|
741
|
-
items = combined['items'].dup.sort_by{|item| item['date'] }.reverse
|
714
|
+
end
|
715
|
+
items = combined['items'].dup.sort_by { |item| item['date'] }.reverse
|
742
716
|
sec_arr.push(items[0]['section'])
|
743
717
|
elsif opt[:count] > 1
|
744
|
-
raise
|
718
|
+
raise 'A count greater than one requires a section to be specified'
|
745
719
|
else
|
746
720
|
sec_arr = sections
|
747
721
|
end
|
@@ -750,21 +724,27 @@ class WWID
|
|
750
724
|
end
|
751
725
|
end
|
752
726
|
|
727
|
+
sec_arr.each do |section|
|
728
|
+
if @content.key?(section)
|
753
729
|
|
754
|
-
|
755
|
-
sec_arr.each {|section|
|
756
|
-
if @content.has_key?(section)
|
757
|
-
|
758
|
-
items = @content[section]['items'].dup.sort_by{|item| item['date'] }.reverse
|
730
|
+
items = @content[section]['items'].dup.sort_by { |item| item['date'] }.reverse
|
759
731
|
|
760
732
|
index = 0
|
761
733
|
done_date = Time.now
|
762
734
|
next_start = Time.now
|
763
|
-
count = opt[:count]
|
764
|
-
items.map!
|
735
|
+
count = (opt[:count]).zero? ? items.length : opt[:count]
|
736
|
+
items.map! do |item|
|
765
737
|
break if index == count
|
766
738
|
|
767
|
-
|
739
|
+
if opt[:autotag]
|
740
|
+
new_title = autotag(item['title']) if @auto_tag
|
741
|
+
if new_title == item['title']
|
742
|
+
@results.push(%(Autotag: No changes))
|
743
|
+
else
|
744
|
+
@results.push("Tags updated: #{new_title}")
|
745
|
+
item['title'] = new_title
|
746
|
+
end
|
747
|
+
else
|
768
748
|
if opt[:sequential]
|
769
749
|
done_date = next_start - 1
|
770
750
|
next_start = item['date']
|
@@ -775,62 +755,50 @@ class WWID
|
|
775
755
|
end
|
776
756
|
|
777
757
|
title = item['title']
|
778
|
-
opt[:tags].each
|
758
|
+
opt[:tags].each do |tag|
|
779
759
|
tag.strip!
|
780
|
-
if opt[:remove]
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
title += " @#{tag}"
|
792
|
-
end
|
793
|
-
@results.push(%Q{Added @#{tag}: "#{title}" in #{section}})
|
794
|
-
end
|
760
|
+
if opt[:remove] && title =~ /@#{tag}\b/
|
761
|
+
title.gsub!(/(^| )@#{tag}(\([^)]*\))?/, '')
|
762
|
+
@results.push(%(Removed @#{tag}: "#{title}" in #{section}))
|
763
|
+
elsif title !~ /@#{tag}/
|
764
|
+
title.chomp!
|
765
|
+
title += if opt[:date]
|
766
|
+
" @#{tag}(#{done_date.strftime('%F %R')})"
|
767
|
+
else
|
768
|
+
" @#{tag}"
|
769
|
+
end
|
770
|
+
@results.push(%(Added @#{tag}: "#{title}" in #{section}))
|
795
771
|
end
|
796
|
-
}
|
797
|
-
item['title'] = title
|
798
|
-
else
|
799
|
-
new_title = autotag(item['title']) if @auto_tag
|
800
|
-
unless new_title == item['title']
|
801
|
-
@results.push("Tags updated: #{new_title}")
|
802
|
-
item['title'] = new_title
|
803
|
-
else
|
804
|
-
@results.push(%Q{Autotag: No changes})
|
805
772
|
end
|
773
|
+
item['title'] = title
|
806
774
|
end
|
807
775
|
|
808
776
|
index += 1
|
809
777
|
|
810
778
|
item
|
811
|
-
|
779
|
+
end
|
812
780
|
|
813
781
|
@content[section]['items'] = items
|
814
782
|
|
815
|
-
if opt[:archive] && section !=
|
783
|
+
if opt[:archive] && section != 'Archive' && (opt[:count]).positive?
|
816
784
|
# concat [count] items from [section] and archive section
|
817
|
-
archived = @content[section]['items'][0..opt[:count]-1].map
|
818
|
-
i['title'].sub(/(?:@from\(.*?\))?(.*)$/,"\\1 @from(#{i['section']})")
|
819
|
-
|
785
|
+
archived = @content[section]['items'][0..opt[:count] - 1].map do |i|
|
786
|
+
i['title'].sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{i['section']})")
|
787
|
+
end.concat(@content['Archive']['items'])
|
820
788
|
# chop [count] items off of [section] items
|
821
789
|
@content[opt[:section]]['items'] = @content[opt[:section]]['items'][opt[:count]..-1]
|
822
790
|
# overwrite archive section with concatenated array
|
823
791
|
@content['Archive']['items'] = archived
|
824
792
|
# log it
|
825
|
-
result = opt[:count] == 1 ?
|
793
|
+
result = opt[:count] == 1 ? '1 entry' : "#{opt[:count]} entries"
|
826
794
|
@results.push("Archived #{result} from #{section}")
|
827
|
-
elsif opt[:archive] && opt[:count]
|
828
|
-
@results.push(
|
795
|
+
elsif opt[:archive] && (opt[:count]).zero?
|
796
|
+
@results.push('Archiving is skipped when operating on all entries') if (opt[:count]).zero?
|
829
797
|
end
|
830
798
|
else
|
831
799
|
raise "Section not found: #{section}"
|
832
800
|
end
|
833
|
-
|
801
|
+
end
|
834
802
|
|
835
803
|
write(@doing_file)
|
836
804
|
end
|
@@ -842,49 +810,47 @@ class WWID
|
|
842
810
|
## @param note (String) The note to add
|
843
811
|
## @param replace (Bool) Should replace existing note
|
844
812
|
##
|
845
|
-
def note_last(section, note, replace
|
813
|
+
def note_last(section, note, replace: false)
|
846
814
|
section = guess_section(section)
|
847
815
|
|
848
816
|
if section =~ /^all$/i
|
849
|
-
combined = {'items' => []}
|
850
|
-
@content.each
|
817
|
+
combined = { 'items' => [] }
|
818
|
+
@content.each do |_k, v|
|
851
819
|
combined['items'] += v['items']
|
852
|
-
|
853
|
-
section = combined['items'].dup.sort_by{|item| item['date'] }.reverse[0]['section']
|
820
|
+
end
|
821
|
+
section = combined['items'].dup.sort_by { |item| item['date'] }.reverse[0]['section']
|
854
822
|
end
|
855
823
|
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
@results.push(%Q{Entry "#{title}" has no note})
|
873
|
-
end
|
874
|
-
elsif current_note.class == Array
|
875
|
-
items[0]['note'] = current_note.concat(note)
|
876
|
-
@results.push(%Q{Added note to "#{title}"}) if note.length > 0
|
824
|
+
raise "Section #{section} not found" unless @content.key?(section)
|
825
|
+
|
826
|
+
# sort_section(opt[:section])
|
827
|
+
items = @content[section]['items'].dup.sort_by { |item| item['date'] }.reverse
|
828
|
+
|
829
|
+
current_note = items[0]['note']
|
830
|
+
current_note = [] if current_note.nil?
|
831
|
+
title = items[0]['title']
|
832
|
+
if replace
|
833
|
+
items[0]['note'] = note
|
834
|
+
if note.empty? && !current_note.empty?
|
835
|
+
@results.push(%(Removed note from "#{title}"))
|
836
|
+
elsif !current_note.empty? && !note.empty?
|
837
|
+
@results.push(%(Replaced note from "#{title}"))
|
838
|
+
elsif !note.empty?
|
839
|
+
@results.push(%(Added note to "#{title}"))
|
877
840
|
else
|
878
|
-
|
879
|
-
@results.push(%Q{Added note to "#{title}"}) if note.length > 0
|
841
|
+
@results.push(%(Entry "#{title}" has no note))
|
880
842
|
end
|
881
|
-
|
882
|
-
|
843
|
+
elsif current_note.instance_of?(Array)
|
844
|
+
items[0]['note'] = current_note.concat(note)
|
845
|
+
@results.push(%(Added note to "#{title}")) unless note.empty?
|
883
846
|
else
|
884
|
-
|
847
|
+
items[0]['note'] = note
|
848
|
+
@results.push(%(Added note to "#{title}")) unless note.empty?
|
885
849
|
end
|
886
|
-
end
|
887
850
|
|
851
|
+
@content[section]['items'] = items
|
852
|
+
|
853
|
+
end
|
888
854
|
|
889
855
|
#
|
890
856
|
# @brief Accepts one tag and the raw text of a new item if the passed tag
|
@@ -896,7 +862,7 @@ class WWID
|
|
896
862
|
# @param tag (String) Tag to replace
|
897
863
|
# @param opt (Hash) Additional Options
|
898
864
|
#
|
899
|
-
def stop_start(tag,opt={})
|
865
|
+
def stop_start(tag, opt = {})
|
900
866
|
opt[:section] ||= @current_section
|
901
867
|
opt[:archive] ||= false
|
902
868
|
opt[:back] ||= Time.now
|
@@ -905,36 +871,36 @@ class WWID
|
|
905
871
|
|
906
872
|
opt[:section] = guess_section(opt[:section])
|
907
873
|
|
908
|
-
tag.sub!(/^@/,'')
|
874
|
+
tag.sub!(/^@/, '')
|
909
875
|
|
910
876
|
found_items = 0
|
911
|
-
@content[opt[:section]]['items'].each_with_index
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
877
|
+
@content[opt[:section]]['items'].each_with_index do |item, i|
|
878
|
+
next unless item['title'] =~ /@#{tag}/
|
879
|
+
|
880
|
+
title = item['title'].gsub(/(^| )@(#{tag}|done)(\([^)]*\))?/, '')
|
881
|
+
title += " @done(#{opt[:back].strftime('%F %R')})"
|
882
|
+
|
883
|
+
@content[opt[:section]]['items'][i]['title'] = title
|
884
|
+
found_items += 1
|
885
|
+
|
886
|
+
if opt[:archive] && opt[:section] != 'Archive'
|
887
|
+
@results.push(%(Completed and archived "#{@content[opt[:section]]['items'][i]['title']}"))
|
888
|
+
archive_item = @content[opt[:section]]['items'][i]
|
889
|
+
archive_item['title'] = i['title'].sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{i['section']})")
|
890
|
+
@content['Archive']['items'].push(archive_item)
|
891
|
+
@content[opt[:section]]['items'].delete_at(i)
|
892
|
+
else
|
893
|
+
@results.push(%(Completed "#{@content[opt[:section]]['items'][i]['title']}"))
|
928
894
|
end
|
929
|
-
|
895
|
+
end
|
930
896
|
|
931
897
|
@results.push("No active @#{tag} tasks found.") if found_items == 0
|
932
898
|
|
933
899
|
if opt[:new_item]
|
934
900
|
title, note = format_input(opt[:new_item])
|
935
|
-
note.push(opt[:note].gsub(/ *$/,'')) if opt[:note]
|
901
|
+
note.push(opt[:note].gsub(/ *$/, '')) if opt[:note]
|
936
902
|
title += " @#{tag}"
|
937
|
-
add_item(title.cap_first, opt[:section], {:
|
903
|
+
add_item(title.cap_first, opt[:section], { note: note.join(' ').rstrip, back: opt[:back] })
|
938
904
|
end
|
939
905
|
|
940
906
|
write(@doing_file)
|
@@ -945,28 +911,34 @@ class WWID
|
|
945
911
|
##
|
946
912
|
## @param file (String) The filepath to write to
|
947
913
|
##
|
948
|
-
def write(file=nil)
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
output
|
914
|
+
def write(file = nil)
|
915
|
+
output = @other_content_top ? "#{@other_content_top.join("\n")}\n" : ''
|
916
|
+
|
917
|
+
@content.each do |title, section|
|
918
|
+
output += "#{section['original']}\n"
|
919
|
+
output += list_section({ section: title, template: "\t- %date | %title%note", highlight: false })
|
953
920
|
end
|
954
|
-
@content.each {|title, section|
|
955
|
-
output += section['original'] + "\n"
|
956
|
-
output += list_section({:section => title, :template => "\t- %date | %title%note", :highlight => false})
|
957
|
-
}
|
958
921
|
output += @other_content_bottom.join("\n") unless @other_content_bottom.nil?
|
959
922
|
if file.nil?
|
960
923
|
$stdout.puts output
|
961
924
|
else
|
962
|
-
|
925
|
+
file = File.expand_path(file)
|
926
|
+
if File.exist?(file)
|
963
927
|
# Create a backup copy for the undo command
|
964
|
-
FileUtils.cp(file,file
|
928
|
+
FileUtils.cp(file, "#{file}~")
|
965
929
|
|
966
|
-
File.open(
|
930
|
+
File.open(file, 'w+') do |f|
|
967
931
|
f.puts output
|
968
932
|
end
|
969
933
|
end
|
934
|
+
|
935
|
+
if @config.key?('run_after')
|
936
|
+
stdout, stderr, status = Open3.capture3(@config['run_after'])
|
937
|
+
if status.exitstatus.positive?
|
938
|
+
warn "Error running #{@config['run_after']}"
|
939
|
+
warn stderr
|
940
|
+
end
|
941
|
+
end
|
970
942
|
end
|
971
943
|
end
|
972
944
|
|
@@ -976,27 +948,27 @@ class WWID
|
|
976
948
|
## @param file (String) The filepath to restore
|
977
949
|
##
|
978
950
|
def restore_backup(file)
|
979
|
-
if File.
|
980
|
-
puts file+
|
981
|
-
FileUtils.cp(file+
|
951
|
+
if File.exist?(file + '~')
|
952
|
+
puts file + '~'
|
953
|
+
FileUtils.cp(file + '~', file)
|
982
954
|
@results.push("Restored #{file}")
|
983
955
|
end
|
984
956
|
end
|
985
957
|
|
986
|
-
|
987
958
|
##
|
988
959
|
## @brief Generate a menu of sections and allow user selection
|
989
960
|
##
|
990
961
|
## @return (String) The selected section name
|
991
962
|
##
|
992
963
|
def choose_section
|
993
|
-
sections.each_with_index
|
994
|
-
puts
|
995
|
-
|
964
|
+
sections.each_with_index do |section, i|
|
965
|
+
puts format('% 3d: %s', i + 1, section)
|
966
|
+
end
|
996
967
|
print "#{colors['green']}> #{colors['default']}"
|
997
968
|
num = STDIN.gets
|
998
969
|
return false if num =~ /^[a-z ]*$/i
|
999
|
-
|
970
|
+
|
971
|
+
sections[num.to_i - 1]
|
1000
972
|
end
|
1001
973
|
|
1002
974
|
##
|
@@ -1014,13 +986,14 @@ class WWID
|
|
1014
986
|
## @return (String) The selected view name
|
1015
987
|
##
|
1016
988
|
def choose_view
|
1017
|
-
views.each_with_index
|
1018
|
-
puts
|
1019
|
-
|
1020
|
-
print
|
989
|
+
views.each_with_index do |view, i|
|
990
|
+
puts format('% 3d: %s', i + 1, view)
|
991
|
+
end
|
992
|
+
print '> '
|
1021
993
|
num = STDIN.gets
|
1022
994
|
return false if num =~ /^[a-z ]*$/i
|
1023
|
-
|
995
|
+
|
996
|
+
views[num.to_i - 1]
|
1024
997
|
end
|
1025
998
|
|
1026
999
|
##
|
@@ -1029,9 +1002,8 @@ class WWID
|
|
1029
1002
|
## @param title (String) The title of the view to retrieve
|
1030
1003
|
##
|
1031
1004
|
def get_view(title)
|
1032
|
-
if @config['views'].has_key?(title)
|
1033
|
-
|
1034
|
-
end
|
1005
|
+
return @config['views'][title] if @config['views'].has_key?(title)
|
1006
|
+
|
1035
1007
|
false
|
1036
1008
|
end
|
1037
1009
|
|
@@ -1041,14 +1013,14 @@ class WWID
|
|
1041
1013
|
##
|
1042
1014
|
## @param opt (Hash) Additional Options
|
1043
1015
|
##
|
1044
|
-
def list_section(opt={})
|
1016
|
+
def list_section(opt = {})
|
1045
1017
|
opt[:count] ||= 0
|
1046
1018
|
count = opt[:count] - 1
|
1047
1019
|
opt[:section] ||= nil
|
1048
1020
|
opt[:format] ||= @default_date_format
|
1049
1021
|
opt[:template] ||= @default_template
|
1050
|
-
opt[:age] ||=
|
1051
|
-
opt[:order] ||=
|
1022
|
+
opt[:age] ||= 'newest'
|
1023
|
+
opt[:order] ||= 'desc'
|
1052
1024
|
opt[:today] ||= false
|
1053
1025
|
opt[:tag_filter] ||= false
|
1054
1026
|
opt[:tags_color] ||= false
|
@@ -1060,17 +1032,23 @@ class WWID
|
|
1060
1032
|
opt[:date_filter] ||= []
|
1061
1033
|
|
1062
1034
|
# opt[:highlight] ||= true
|
1063
|
-
section =
|
1035
|
+
section = ''
|
1064
1036
|
if opt[:section].nil?
|
1065
1037
|
section = choose_section
|
1066
1038
|
opt[:section] = @content[section]
|
1067
|
-
elsif opt[:section].
|
1039
|
+
elsif opt[:section].instance_of?(String)
|
1068
1040
|
if opt[:section] =~ /^all$/i
|
1069
|
-
combined = {'items' => []}
|
1070
|
-
@content.each
|
1041
|
+
combined = { 'items' => [] }
|
1042
|
+
@content.each do |_k, v|
|
1071
1043
|
combined['items'] += v['items']
|
1072
|
-
|
1073
|
-
section = opt[:tag_filter] && opt[:tag_filter]['bool'] != 'NONE'
|
1044
|
+
end
|
1045
|
+
section = if opt[:tag_filter] && opt[:tag_filter]['bool'] != 'NONE'
|
1046
|
+
opt[:tag_filter]['tags'].map do |tag|
|
1047
|
+
"@#{tag}"
|
1048
|
+
end.join(' + ')
|
1049
|
+
else
|
1050
|
+
'doing'
|
1051
|
+
end
|
1074
1052
|
opt[:section] = combined
|
1075
1053
|
else
|
1076
1054
|
section = guess_section(opt[:section])
|
@@ -1079,209 +1057,201 @@ class WWID
|
|
1079
1057
|
end
|
1080
1058
|
|
1081
1059
|
if opt[:section].class != Hash
|
1082
|
-
|
1060
|
+
warn 'Invalid section object'
|
1083
1061
|
return
|
1084
1062
|
end
|
1085
1063
|
|
1086
|
-
items = opt[:section]['items'].sort_by{|item| item['date'] }
|
1064
|
+
items = opt[:section]['items'].sort_by { |item| item['date'] }
|
1087
1065
|
|
1088
1066
|
if opt[:date_filter].length == 2
|
1089
1067
|
start_date = opt[:date_filter][0]
|
1090
1068
|
end_date = opt[:date_filter][1]
|
1091
|
-
items.keep_if
|
1069
|
+
items.keep_if do |item|
|
1092
1070
|
if end_date
|
1093
1071
|
item['date'] >= start_date && item['date'] <= end_date
|
1094
1072
|
else
|
1095
1073
|
item['date'].strftime('%F') == start_date.strftime('%F')
|
1096
1074
|
end
|
1097
|
-
|
1075
|
+
end
|
1098
1076
|
end
|
1099
1077
|
|
1100
1078
|
if opt[:tag_filter] && !opt[:tag_filter]['tags'].empty?
|
1101
|
-
items.delete_if
|
1079
|
+
items.delete_if do |item|
|
1102
1080
|
if opt[:tag_filter]['bool'] =~ /(AND|ALL)/
|
1103
1081
|
score = 0
|
1104
|
-
opt[:tag_filter]['tags'].each
|
1082
|
+
opt[:tag_filter]['tags'].each do |tag|
|
1105
1083
|
score += 1 if item['title'] =~ /@#{tag}/
|
1106
|
-
|
1084
|
+
end
|
1107
1085
|
score < opt[:tag_filter]['tags'].length
|
1108
1086
|
elsif opt[:tag_filter]['bool'] =~ /NONE/
|
1109
1087
|
del = false
|
1110
|
-
opt[:tag_filter]['tags'].each
|
1088
|
+
opt[:tag_filter]['tags'].each do |tag|
|
1111
1089
|
del = true if item['title'] =~ /@#{tag}/
|
1112
|
-
|
1090
|
+
end
|
1113
1091
|
del
|
1114
1092
|
elsif opt[:tag_filter]['bool'] =~ /(OR|ANY)/
|
1115
1093
|
del = true
|
1116
|
-
opt[:tag_filter]['tags'].each
|
1094
|
+
opt[:tag_filter]['tags'].each do |tag|
|
1117
1095
|
del = false if item['title'] =~ /@#{tag}/
|
1118
|
-
|
1096
|
+
end
|
1119
1097
|
del
|
1120
1098
|
end
|
1121
|
-
|
1099
|
+
end
|
1122
1100
|
end
|
1123
1101
|
|
1124
1102
|
if opt[:search]
|
1125
|
-
items.keep_if
|
1126
|
-
text = item['note'] ? item['title'] + item['note'].join(
|
1127
|
-
if opt[:search].strip =~
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1103
|
+
items.keep_if do |item|
|
1104
|
+
text = item['note'] ? item['title'] + item['note'].join(' ') : item['title']
|
1105
|
+
pattern = if opt[:search].strip =~ %r{^/.*?/$}
|
1106
|
+
opt[:search].sub(%r{/(.*?)/}, '\1')
|
1107
|
+
else
|
1108
|
+
opt[:search].split('').join('.{0,3}')
|
1109
|
+
end
|
1132
1110
|
text =~ /#{pattern}/i
|
1133
|
-
|
1111
|
+
end
|
1134
1112
|
end
|
1135
1113
|
|
1136
1114
|
if opt[:only_timed]
|
1137
|
-
items.delete_if
|
1115
|
+
items.delete_if do |item|
|
1138
1116
|
get_interval(item) == false
|
1139
|
-
|
1117
|
+
end
|
1140
1118
|
end
|
1141
1119
|
|
1142
1120
|
if opt[:today]
|
1143
|
-
items.delete_if
|
1121
|
+
items.delete_if do |item|
|
1144
1122
|
item['date'] < Date.today.to_time
|
1145
|
-
|
1123
|
+
end.reverse!
|
1146
1124
|
section = Time.now.strftime('%A, %B %d')
|
1147
1125
|
elsif opt[:yesterday]
|
1148
|
-
items.delete_if
|
1149
|
-
|
1150
|
-
|
1126
|
+
items.delete_if do |item|
|
1127
|
+
item['date'] <= Date.today.prev_day.to_time or
|
1128
|
+
item['date'] >= Date.today.to_time
|
1129
|
+
end.reverse!
|
1130
|
+
elsif opt[:age] =~ /oldest/i
|
1131
|
+
items = items[0..count]
|
1151
1132
|
else
|
1152
|
-
|
1153
|
-
items = items[0..count]
|
1154
|
-
else
|
1155
|
-
items = items.reverse[0..count]
|
1156
|
-
end
|
1133
|
+
items = items.reverse[0..count]
|
1157
1134
|
end
|
1158
1135
|
|
1159
|
-
if opt[:order] =~ /^a/i
|
1160
|
-
items.reverse!
|
1161
|
-
end
|
1136
|
+
items.reverse! if opt[:order] =~ /^a/i
|
1162
1137
|
|
1163
|
-
out =
|
1138
|
+
out = ''
|
1164
1139
|
|
1165
|
-
if opt[:output]
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
note = ""
|
1140
|
+
raise 'Unknown output format' if opt[:output] && !(opt[:output] =~ /(template|html|csv|json|timeline)/)
|
1141
|
+
|
1142
|
+
if opt[:output] == 'csv'
|
1143
|
+
output = [CSV.generate_line(%w[date title note timer section])]
|
1144
|
+
items.each do |i|
|
1145
|
+
note = ''
|
1172
1146
|
if i['note']
|
1173
|
-
arr = i['note'].map{|line| line.strip}.delete_if{|e| e =~ /^\s*$/}
|
1147
|
+
arr = i['note'].map { |line| line.strip }.delete_if { |e| e =~ /^\s*$/ }
|
1174
1148
|
note = arr.join("\n") unless arr.nil?
|
1175
1149
|
end
|
1176
|
-
if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
1177
|
-
interval = get_interval(i, false)
|
1178
|
-
end
|
1150
|
+
interval = get_interval(i, false) if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
1179
1151
|
interval ||= 0
|
1180
|
-
output.push(CSV.generate_line([i['date'],i['title'],note,interval,i['section']]))
|
1181
|
-
|
1182
|
-
out = output.join(
|
1183
|
-
elsif opt[:output] ==
|
1152
|
+
output.push(CSV.generate_line([i['date'], i['title'], note, interval, i['section']]))
|
1153
|
+
end
|
1154
|
+
out = output.join('')
|
1155
|
+
elsif opt[:output] == 'json' || opt[:output] == 'timeline'
|
1184
1156
|
items_out = []
|
1185
1157
|
max = items[-1]['date'].strftime('%F')
|
1186
1158
|
min = items[0]['date'].strftime('%F')
|
1187
|
-
items.each_with_index
|
1159
|
+
items.each_with_index do |i, index|
|
1188
1160
|
if String.method_defined? :force_encoding
|
1189
1161
|
title = i['title'].force_encoding('utf-8')
|
1190
|
-
note = i['note'].map {|line| line.force_encoding('utf-8').strip } if i['note']
|
1162
|
+
note = i['note'].map { |line| line.force_encoding('utf-8').strip } if i['note']
|
1191
1163
|
else
|
1192
1164
|
title = i['title']
|
1193
1165
|
note = i['note'].map { |line| line.strip } if i['note']
|
1194
1166
|
end
|
1195
1167
|
if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
1196
|
-
end_date = Time.parse(
|
1197
|
-
interval = get_interval(i,false)
|
1168
|
+
end_date = Time.parse(Regexp.last_match(1))
|
1169
|
+
interval = get_interval(i, false)
|
1198
1170
|
end
|
1199
|
-
end_date ||=
|
1171
|
+
end_date ||= ''
|
1200
1172
|
interval ||= 0
|
1201
|
-
note ||=
|
1173
|
+
note ||= ''
|
1202
1174
|
|
1203
1175
|
tags = []
|
1204
|
-
skip_tags = [
|
1205
|
-
i['title'].scan(/@([
|
1176
|
+
skip_tags = %w[meanwhile done cancelled flagged]
|
1177
|
+
i['title'].scan(/@([^(\s]+)(?:\((.*?)\))?/).each do |tag|
|
1206
1178
|
tags.push(tag[0]) unless skip_tags.include?(tag[0])
|
1207
|
-
|
1208
|
-
if opt[:output] ==
|
1179
|
+
end
|
1180
|
+
if opt[:output] == 'json'
|
1209
1181
|
|
1210
1182
|
items_out << {
|
1211
|
-
:
|
1212
|
-
:
|
1213
|
-
:
|
1214
|
-
:
|
1215
|
-
:
|
1216
|
-
:
|
1183
|
+
date: i['date'],
|
1184
|
+
end_date: end_date,
|
1185
|
+
title: title.strip, #+ " #{note}"
|
1186
|
+
note: note.instance_of?(Array) ? note.map(&:strip).join("\n") : note,
|
1187
|
+
time: '%02d:%02d:%02d' % fmt_time(interval),
|
1188
|
+
tags: tags
|
1217
1189
|
}
|
1218
1190
|
|
1219
|
-
elsif opt[:output] ==
|
1191
|
+
elsif opt[:output] == 'timeline'
|
1220
1192
|
new_item = {
|
1221
1193
|
'id' => index + 1,
|
1222
1194
|
'content' => title.strip, #+ " #{note}"
|
1223
|
-
'title' => title.strip + " (#{
|
1195
|
+
'title' => title.strip + " (#{'%02d:%02d:%02d' % fmt_time(interval)})",
|
1224
1196
|
'start' => i['date'].strftime('%F'),
|
1225
1197
|
'type' => 'point'
|
1226
1198
|
}
|
1227
1199
|
|
1228
1200
|
if interval && interval > 0
|
1229
1201
|
new_item['end'] = end_date.strftime('%F')
|
1230
|
-
if interval > 3600 * 3
|
1231
|
-
new_item['type'] = 'range'
|
1232
|
-
end
|
1202
|
+
new_item['type'] = 'range' if interval > 3600 * 3
|
1233
1203
|
end
|
1234
1204
|
items_out.push(new_item)
|
1235
1205
|
end
|
1236
|
-
|
1237
|
-
if opt[:output] ==
|
1206
|
+
end
|
1207
|
+
if opt[:output] == 'json'
|
1238
1208
|
out = {
|
1239
1209
|
'section' => section,
|
1240
1210
|
'items' => items_out,
|
1241
|
-
'timers' => tag_times(
|
1211
|
+
'timers' => tag_times('json', opt[:sort_tags])
|
1242
1212
|
}.to_json
|
1243
|
-
elsif opt[:output] ==
|
1244
|
-
|
1245
|
-
<!doctype html>
|
1246
|
-
<html>
|
1247
|
-
<head>
|
1248
|
-
|
1249
|
-
|
1250
|
-
</head>
|
1251
|
-
<body>
|
1252
|
-
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1259
|
-
|
1260
|
-
|
1261
|
-
|
1262
|
-
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1268
|
-
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
</body>
|
1277
|
-
</html>
|
1278
|
-
EOTEMPLATE
|
1213
|
+
elsif opt[:output] == 'timeline'
|
1214
|
+
template = <<~EOTEMPLATE
|
1215
|
+
<!doctype html>
|
1216
|
+
<html>
|
1217
|
+
<head>
|
1218
|
+
<link href="http://visjs.org/dist/vis.css" rel="stylesheet" type="text/css" />
|
1219
|
+
<script src="http://visjs.org/dist/vis.js"></script>
|
1220
|
+
</head>
|
1221
|
+
<body>
|
1222
|
+
<div id="mytimeline"></div>
|
1223
|
+
#{' '}
|
1224
|
+
<script type="text/javascript">
|
1225
|
+
// DOM element where the Timeline will be attached
|
1226
|
+
var container = document.getElementById('mytimeline');
|
1227
|
+
#{' '}
|
1228
|
+
// Create a DataSet with data (enables two way data binding)
|
1229
|
+
var data = new vis.DataSet(#{items_out.to_json});
|
1230
|
+
#{' '}
|
1231
|
+
// Configuration for the Timeline
|
1232
|
+
var options = {
|
1233
|
+
width: '100%',
|
1234
|
+
height: '800px',
|
1235
|
+
margin: {
|
1236
|
+
item: 20
|
1237
|
+
},
|
1238
|
+
stack: true,
|
1239
|
+
min: '#{min}',
|
1240
|
+
max: '#{max}'
|
1241
|
+
};
|
1242
|
+
#{' '}
|
1243
|
+
// Create a Timeline
|
1244
|
+
var timeline = new vis.Timeline(container, data, options);
|
1245
|
+
</script>
|
1246
|
+
</body>
|
1247
|
+
</html>
|
1248
|
+
EOTEMPLATE
|
1279
1249
|
return template
|
1280
1250
|
end
|
1281
|
-
elsif opt[:output] ==
|
1251
|
+
elsif opt[:output] == 'html'
|
1282
1252
|
page_title = section
|
1283
1253
|
items_out = []
|
1284
|
-
items.each
|
1254
|
+
items.each do |i|
|
1285
1255
|
# if i.has_key?('note')
|
1286
1256
|
# note = '<span class="note">' + i['note'].map{|n| n.strip }.join('<br>') + '</span>'
|
1287
1257
|
# else
|
@@ -1289,83 +1259,81 @@ EOTEMPLATE
|
|
1289
1259
|
# end
|
1290
1260
|
if String.method_defined? :force_encoding
|
1291
1261
|
title = i['title'].force_encoding('utf-8').link_urls
|
1292
|
-
note = i['note'].map {|line| line.force_encoding('utf-8').strip.link_urls } if i['note']
|
1262
|
+
note = i['note'].map { |line| line.force_encoding('utf-8').strip.link_urls } if i['note']
|
1293
1263
|
else
|
1294
1264
|
title = i['title'].link_urls
|
1295
1265
|
note = i['note'].map { |line| line.strip.link_urls } if i['note']
|
1296
1266
|
end
|
1297
1267
|
|
1298
|
-
if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
1299
|
-
interval = get_interval(i)
|
1300
|
-
end
|
1268
|
+
interval = get_interval(i) if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
1301
1269
|
interval ||= false
|
1302
1270
|
|
1303
1271
|
items_out << {
|
1304
|
-
:
|
1305
|
-
:
|
1306
|
-
:
|
1307
|
-
:
|
1308
|
-
:
|
1272
|
+
date: i['date'].strftime('%a %-I:%M%p'),
|
1273
|
+
title: title.gsub(/(@[^ (]+(\(.*?\))?)/im, '<span class="tag">\1</span>').strip, #+ " #{note}"
|
1274
|
+
note: note,
|
1275
|
+
time: interval,
|
1276
|
+
section: i['section']
|
1309
1277
|
}
|
1310
|
-
}
|
1311
|
-
|
1312
|
-
if @config['html_template']['haml'] && File.exists?(File.expand_path(@config['html_template']['haml']))
|
1313
|
-
template = IO.read(File.expand_path(@config['html_template']['haml']))
|
1314
|
-
else
|
1315
|
-
template = haml_template
|
1316
1278
|
end
|
1317
1279
|
|
1318
|
-
if @config['html_template']['
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
1280
|
+
template = if @config['html_template']['haml'] && File.exist?(File.expand_path(@config['html_template']['haml']))
|
1281
|
+
IO.read(File.expand_path(@config['html_template']['haml']))
|
1282
|
+
else
|
1283
|
+
haml_template
|
1284
|
+
end
|
1285
|
+
|
1286
|
+
style = if @config['html_template']['css'] && File.exist?(File.expand_path(@config['html_template']['css']))
|
1287
|
+
IO.read(File.expand_path(@config['html_template']['css']))
|
1288
|
+
else
|
1289
|
+
css_template
|
1290
|
+
end
|
1323
1291
|
|
1324
|
-
totals = opt[:totals] ? tag_times(
|
1292
|
+
totals = opt[:totals] ? tag_times('html', opt[:sort_tags]) : ''
|
1325
1293
|
engine = Haml::Engine.new(template)
|
1326
|
-
puts engine.render(Object.new,
|
1294
|
+
puts engine.render(Object.new,
|
1295
|
+
{ :@items => items_out, :@page_title => page_title, :@style => style, :@totals => totals })
|
1327
1296
|
else
|
1328
|
-
items.each
|
1329
|
-
|
1297
|
+
items.each do |item|
|
1330
1298
|
if opt[:highlight] && item['title'] =~ /@#{@config['marker_tag']}\b/i
|
1331
1299
|
flag = colors[@config['marker_color']]
|
1332
1300
|
reset = colors['default']
|
1333
1301
|
else
|
1334
|
-
flag =
|
1335
|
-
reset =
|
1302
|
+
flag = ''
|
1303
|
+
reset = ''
|
1336
1304
|
end
|
1337
1305
|
|
1338
1306
|
if (item.has_key?('note') && !item['note'].empty?) && @config[:include_notes]
|
1339
|
-
note_lines = item['note'].delete_if
|
1307
|
+
note_lines = item['note'].delete_if do |line|
|
1308
|
+
line =~ /^\s*$/
|
1309
|
+
end.map { |line| "\t\t" + line.sub(/^\t*/, '').sub(/^-/, '—') + ' ' }
|
1340
1310
|
if opt[:wrap_width] && opt[:wrap_width] > 0
|
1341
1311
|
width = opt[:wrap_width]
|
1342
|
-
note_lines.map!
|
1312
|
+
note_lines.map! do |line|
|
1343
1313
|
line.strip.gsub(/(.{1,#{width}})(\s+|\Z)/, "\t\\1\n")
|
1344
|
-
|
1314
|
+
end
|
1345
1315
|
end
|
1346
1316
|
note = "\n#{note_lines.join("\n").chomp}"
|
1347
1317
|
else
|
1348
|
-
note =
|
1318
|
+
note = ''
|
1349
1319
|
end
|
1350
1320
|
output = opt[:template].dup
|
1351
1321
|
|
1352
1322
|
output.gsub!(/%[a-z]+/) do |m|
|
1353
|
-
if colors.has_key?(m.sub(/^%/,''))
|
1354
|
-
colors[m.sub(/^%/,'')]
|
1323
|
+
if colors.has_key?(m.sub(/^%/, ''))
|
1324
|
+
colors[m.sub(/^%/, '')]
|
1355
1325
|
else
|
1356
1326
|
m
|
1357
1327
|
end
|
1358
1328
|
end
|
1359
1329
|
|
1360
|
-
output.sub!(/%date/,item['date'].strftime(opt[:format]))
|
1330
|
+
output.sub!(/%date/, item['date'].strftime(opt[:format]))
|
1361
1331
|
|
1362
|
-
if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
1363
|
-
|
1364
|
-
|
1365
|
-
interval ||= ""
|
1366
|
-
output.sub!(/%interval/,interval)
|
1332
|
+
interval = get_interval(item) if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
1333
|
+
interval ||= ''
|
1334
|
+
output.sub!(/%interval/, interval)
|
1367
1335
|
|
1368
|
-
output.sub!(/%shortdate/)
|
1336
|
+
output.sub!(/%shortdate/) do
|
1369
1337
|
if item['date'] > Date.today.to_time
|
1370
1338
|
item['date'].strftime('%_I:%M%P')
|
1371
1339
|
elsif item['date'] > (Date.today - 7).to_time
|
@@ -1375,45 +1343,45 @@ EOTEMPLATE
|
|
1375
1343
|
else
|
1376
1344
|
item['date'].strftime('%b %d %Y, %-I:%M%P')
|
1377
1345
|
end
|
1378
|
-
|
1346
|
+
end
|
1379
1347
|
|
1380
|
-
output.sub!(/%title/)
|
1348
|
+
output.sub!(/%title/) do |_m|
|
1381
1349
|
if opt[:wrap_width] && opt[:wrap_width] > 0
|
1382
|
-
flag+item['title'].gsub(/(.{1,#{opt[:wrap_width]}})(\s+|\Z)/, "\\1\n\t ").chomp+reset
|
1350
|
+
flag + item['title'].gsub(/(.{1,#{opt[:wrap_width]}})(\s+|\Z)/, "\\1\n\t ").chomp + reset
|
1383
1351
|
else
|
1384
|
-
flag+item['title'].chomp+reset
|
1352
|
+
flag + item['title'].chomp + reset
|
1385
1353
|
end
|
1386
|
-
|
1354
|
+
end
|
1387
1355
|
|
1388
|
-
output.sub!(/%section/,item['section']) if item['section']
|
1356
|
+
output.sub!(/%section/, item['section']) if item['section']
|
1389
1357
|
|
1390
1358
|
if opt[:tags_color]
|
1391
1359
|
escapes = output.scan(/(\e\[[\d;]+m)[^\e]+@/)
|
1392
|
-
if escapes.length > 0
|
1393
|
-
|
1394
|
-
|
1395
|
-
|
1396
|
-
|
1397
|
-
output.gsub!(/\s(@[^
|
1360
|
+
last_color = if escapes.length > 0
|
1361
|
+
escapes[-1][0]
|
1362
|
+
else
|
1363
|
+
colors['default']
|
1364
|
+
end
|
1365
|
+
output.gsub!(/\s(@[^ (]+)/, " #{colors[opt[:tags_color]]}\\1#{last_color}")
|
1398
1366
|
end
|
1399
|
-
output.sub!(/%note/,note)
|
1400
|
-
output.sub!(/%odnote/,note.gsub(/^\t*/,
|
1401
|
-
output.sub!(/%chompnote/,note.gsub(/\n+/,' ').gsub(/(^\s*|\s*$)/,'').gsub(/\s+/,' '))
|
1402
|
-
output.gsub!(/%hr(_under)?/) do |
|
1403
|
-
o =
|
1367
|
+
output.sub!(/%note/, note)
|
1368
|
+
output.sub!(/%odnote/, note.gsub(/^\t*/, ''))
|
1369
|
+
output.sub!(/%chompnote/, note.gsub(/\n+/, ' ').gsub(/(^\s*|\s*$)/, '').gsub(/\s+/, ' '))
|
1370
|
+
output.gsub!(/%hr(_under)?/) do |_m|
|
1371
|
+
o = ''
|
1404
1372
|
`tput cols`.to_i.times do
|
1405
|
-
o +=
|
1373
|
+
o += Regexp.last_match(1).nil? ? '-' : '_'
|
1406
1374
|
end
|
1407
1375
|
o
|
1408
1376
|
end
|
1409
|
-
output.gsub!(/%n/,"\n")
|
1410
|
-
output.gsub!(/%t/,"\t")
|
1377
|
+
output.gsub!(/%n/, "\n")
|
1378
|
+
output.gsub!(/%t/, "\t")
|
1411
1379
|
|
1412
1380
|
out += output + "\n"
|
1413
|
-
|
1414
|
-
out += tag_times(
|
1381
|
+
end
|
1382
|
+
out += tag_times('text', opt[:sort_tags]) if opt[:totals]
|
1415
1383
|
end
|
1416
|
-
|
1384
|
+
out
|
1417
1385
|
end
|
1418
1386
|
|
1419
1387
|
##
|
@@ -1426,15 +1394,12 @@ EOTEMPLATE
|
|
1426
1394
|
## @param tags (Array) Tags to archive
|
1427
1395
|
## @param bool (String) Tag boolean combinator
|
1428
1396
|
##
|
1429
|
-
def archive(section=
|
1430
|
-
|
1397
|
+
def archive(section = 'Currently', count = 5, destination = nil, tags = nil, bool = nil, _export = nil)
|
1431
1398
|
section = choose_section if section.nil? || section =~ /choose/i
|
1432
1399
|
archive_all = section =~ /all/i # && !(tags.nil? || tags.empty?)
|
1433
1400
|
section = guess_section(section) unless archive_all
|
1434
1401
|
|
1435
|
-
if destination =~ /archive/i && !sections.include?(
|
1436
|
-
add_section("Archive")
|
1437
|
-
end
|
1402
|
+
add_section('Archive') if destination =~ /archive/i && !sections.include?('Archive')
|
1438
1403
|
|
1439
1404
|
destination = guess_section(destination)
|
1440
1405
|
|
@@ -1442,16 +1407,16 @@ EOTEMPLATE
|
|
1442
1407
|
if archive_all
|
1443
1408
|
to_archive = sections.dup
|
1444
1409
|
to_archive.delete(destination)
|
1445
|
-
to_archive.each
|
1446
|
-
do_archive(source, destination, { :
|
1447
|
-
|
1410
|
+
to_archive.each do |source, _v|
|
1411
|
+
do_archive(source, destination, { count: count, tags: tags, bool: bool, label: true })
|
1412
|
+
end
|
1448
1413
|
else
|
1449
|
-
do_archive(section, destination, { :
|
1414
|
+
do_archive(section, destination, { count: count, tags: tags, bool: bool, label: true })
|
1450
1415
|
end
|
1451
1416
|
|
1452
1417
|
write(doing_file)
|
1453
1418
|
else
|
1454
|
-
raise
|
1419
|
+
raise 'Either source or destination does not exist'
|
1455
1420
|
end
|
1456
1421
|
end
|
1457
1422
|
|
@@ -1462,63 +1427,66 @@ EOTEMPLATE
|
|
1462
1427
|
## @param destination (String) The destination section
|
1463
1428
|
## @param opt (Hash) Additional Options
|
1464
1429
|
##
|
1465
|
-
def do_archive(section, destination, opt={})
|
1430
|
+
def do_archive(section, destination, opt = {})
|
1466
1431
|
count = opt[:count] || 5
|
1467
1432
|
tags = opt[:tags] || []
|
1468
|
-
bool = opt[:bool] ||
|
1433
|
+
bool = opt[:bool] || 'AND'
|
1469
1434
|
label = opt[:label] || false
|
1470
1435
|
|
1471
1436
|
items = @content[section]['items']
|
1472
1437
|
moved_items = []
|
1473
1438
|
|
1474
1439
|
if tags && !tags.empty?
|
1475
|
-
items.delete_if
|
1440
|
+
items.delete_if do |item|
|
1476
1441
|
if bool =~ /(AND|ALL)/
|
1477
1442
|
score = 0
|
1478
|
-
tags.each
|
1443
|
+
tags.each do |tag|
|
1479
1444
|
score += 1 if item['title'] =~ /@#{tag}/i
|
1480
|
-
|
1445
|
+
end
|
1481
1446
|
res = score < tags.length
|
1482
1447
|
moved_items.push(item) if res
|
1483
1448
|
res
|
1484
1449
|
elsif bool =~ /NONE/
|
1485
1450
|
del = false
|
1486
|
-
tags.each
|
1451
|
+
tags.each do |tag|
|
1487
1452
|
del = true if item['title'] =~ /@#{tag}/i
|
1488
|
-
|
1453
|
+
end
|
1489
1454
|
moved_items.push(item) if del
|
1490
1455
|
del
|
1491
1456
|
elsif bool =~ /(OR|ANY)/
|
1492
1457
|
del = true
|
1493
|
-
tags.each
|
1458
|
+
tags.each do |tag|
|
1494
1459
|
del = false if item['title'] =~ /@#{tag}/i
|
1495
|
-
|
1460
|
+
end
|
1496
1461
|
moved_items.push(item) if del
|
1497
1462
|
del
|
1498
1463
|
end
|
1499
|
-
|
1500
|
-
moved_items.each
|
1501
|
-
if label
|
1502
|
-
item['title'] =
|
1464
|
+
end
|
1465
|
+
moved_items.each do |item|
|
1466
|
+
if label && !(section == 'Currently')
|
1467
|
+
item['title'] =
|
1468
|
+
item['title'].sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{section})")
|
1503
1469
|
end
|
1504
|
-
|
1470
|
+
end
|
1505
1471
|
@content[section]['items'] = moved_items
|
1506
1472
|
@content[destination]['items'] += items
|
1507
1473
|
@results.push("Archived #{items.length} items from #{section} to #{destination}")
|
1508
1474
|
else
|
1509
1475
|
|
1510
1476
|
return if items.length < count
|
1511
|
-
if count == 0
|
1512
|
-
@content[section]['items'] = []
|
1513
|
-
else
|
1514
|
-
@content[section]['items'] = items[0..count-1]
|
1515
|
-
end
|
1516
1477
|
|
1517
|
-
items
|
1518
|
-
|
1519
|
-
|
1478
|
+
@content[section]['items'] = if count == 0
|
1479
|
+
[]
|
1480
|
+
else
|
1481
|
+
items[0..count - 1]
|
1482
|
+
end
|
1483
|
+
|
1484
|
+
items.each do |item|
|
1485
|
+
if label && !(section == 'Currently')
|
1486
|
+
item['title'] =
|
1487
|
+
item['title'].sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{section})")
|
1520
1488
|
end
|
1521
|
-
|
1489
|
+
end
|
1522
1490
|
|
1523
1491
|
@content[destination]['items'] += items[count..-1]
|
1524
1492
|
@results.push("Archived #{items.length - count} items from #{section} to #{destination}")
|
@@ -1574,7 +1542,6 @@ EOTEMPLATE
|
|
1574
1542
|
color
|
1575
1543
|
end
|
1576
1544
|
|
1577
|
-
|
1578
1545
|
##
|
1579
1546
|
## @brief Show all entries from the current day
|
1580
1547
|
##
|
@@ -1582,12 +1549,13 @@ EOTEMPLATE
|
|
1582
1549
|
## @param output (String) output format
|
1583
1550
|
## @param opt (Hash) Options
|
1584
1551
|
##
|
1585
|
-
def today(times=true,output=nil,opt={})
|
1552
|
+
def today(times = true, output = nil, opt = {})
|
1586
1553
|
opt[:totals] ||= false
|
1587
1554
|
opt[:sort_tags] ||= false
|
1588
1555
|
|
1589
1556
|
cfg = @config['templates']['today']
|
1590
|
-
list_section({:
|
1557
|
+
list_section({ section: opt[:section], wrap_width: cfg['wrap_width'], count: 0,
|
1558
|
+
format: cfg['date_format'], template: cfg['template'], order: 'asc', today: true, times: times, output: output, totals: opt[:totals], sort_tags: opt[:sort_tags] })
|
1591
1559
|
end
|
1592
1560
|
|
1593
1561
|
##
|
@@ -1599,16 +1567,15 @@ EOTEMPLATE
|
|
1599
1567
|
## @param output (String) Output format
|
1600
1568
|
## @param opt (Hash) Additional Options
|
1601
1569
|
##
|
1602
|
-
def list_date(dates,section,times=nil,output=nil,opt={})
|
1570
|
+
def list_date(dates, section, times = nil, output = nil, opt = {})
|
1603
1571
|
opt[:totals] ||= false
|
1604
1572
|
opt[:sort_tags] ||= false
|
1605
1573
|
section = guess_section(section)
|
1606
1574
|
# :date_filter expects an array with start and end date
|
1607
|
-
if dates.
|
1608
|
-
dates = [dates, dates]
|
1609
|
-
end
|
1575
|
+
dates = [dates, dates] if dates.instance_of?(String)
|
1610
1576
|
|
1611
|
-
list_section({:
|
1577
|
+
list_section({ section: section, count: 0, order: 'asc', date_filter: dates, times: times,
|
1578
|
+
output: output, totals: opt[:totals], sort_tags: opt[:sort_tags] })
|
1612
1579
|
end
|
1613
1580
|
|
1614
1581
|
##
|
@@ -1619,11 +1586,12 @@ EOTEMPLATE
|
|
1619
1586
|
## @param output (String) Output format
|
1620
1587
|
## @param opt (Hash) Additional Options
|
1621
1588
|
##
|
1622
|
-
def yesterday(section,times=nil,output=nil,opt={})
|
1589
|
+
def yesterday(section, times = nil, output = nil, opt = {})
|
1623
1590
|
opt[:totals] ||= false
|
1624
1591
|
opt[:sort_tags] ||= false
|
1625
1592
|
section = guess_section(section)
|
1626
|
-
list_section({:
|
1593
|
+
list_section({ section: section, count: 0, order: 'asc', yesterday: true, times: times,
|
1594
|
+
output: output, totals: opt[:totals], sort_tags: opt[:sort_tags] })
|
1627
1595
|
end
|
1628
1596
|
|
1629
1597
|
##
|
@@ -1633,7 +1601,7 @@ EOTEMPLATE
|
|
1633
1601
|
## @param section (String) The section to show from, default Currently
|
1634
1602
|
## @param opt (Hash) Additional Options
|
1635
1603
|
##
|
1636
|
-
def recent(count=10,section=nil,opt={})
|
1604
|
+
def recent(count = 10, section = nil, opt = {})
|
1637
1605
|
times = opt[:t] || true
|
1638
1606
|
opt[:totals] ||= false
|
1639
1607
|
opt[:sort_tags] ||= false
|
@@ -1641,7 +1609,8 @@ EOTEMPLATE
|
|
1641
1609
|
cfg = @config['templates']['recent']
|
1642
1610
|
section ||= @current_section
|
1643
1611
|
section = guess_section(section)
|
1644
|
-
list_section({:
|
1612
|
+
list_section({ section: section, wrap_width: cfg['wrap_width'], count: count,
|
1613
|
+
format: cfg['date_format'], template: cfg['template'], order: 'asc', times: times, totals: opt[:totals], sort_tags: opt[:sort_tags] })
|
1645
1614
|
end
|
1646
1615
|
|
1647
1616
|
##
|
@@ -1650,11 +1619,12 @@ EOTEMPLATE
|
|
1650
1619
|
## @param times (Bool) Show times
|
1651
1620
|
## @param section (String) Section to pull from, default Currently
|
1652
1621
|
##
|
1653
|
-
def last(times=true,section=nil)
|
1622
|
+
def last(times = true, section = nil)
|
1654
1623
|
section ||= @current_section
|
1655
1624
|
section = guess_section(section)
|
1656
1625
|
cfg = @config['templates']['last']
|
1657
|
-
list_section({:
|
1626
|
+
list_section({ section: section, wrap_width: cfg['wrap_width'], count: 1, format: cfg['date_format'],
|
1627
|
+
template: cfg['template'], times: times })
|
1658
1628
|
end
|
1659
1629
|
|
1660
1630
|
##
|
@@ -1662,22 +1632,22 @@ EOTEMPLATE
|
|
1662
1632
|
##
|
1663
1633
|
## @param format (String) return format (html, json, or text)
|
1664
1634
|
##
|
1665
|
-
def tag_times(format=
|
1666
|
-
return
|
1635
|
+
def tag_times(format = 'text', sort_by_name = false)
|
1636
|
+
return '' if @timers.empty?
|
1667
1637
|
|
1668
|
-
max = @timers.keys.sort_by {|k| k.length }.reverse[0].length + 1
|
1638
|
+
max = @timers.keys.sort_by { |k| k.length }.reverse[0].length + 1
|
1669
1639
|
|
1670
|
-
total = @timers.delete(
|
1640
|
+
total = @timers.delete('All')
|
1671
1641
|
|
1672
|
-
tags_data = @timers.delete_if { |
|
1673
|
-
if sort_by_name
|
1674
|
-
|
1675
|
-
|
1676
|
-
|
1677
|
-
|
1642
|
+
tags_data = @timers.delete_if { |_k, v| v == 0 }
|
1643
|
+
sorted_tags_data = if sort_by_name
|
1644
|
+
tags_data.sort_by { |k, _v| k }.reverse
|
1645
|
+
else
|
1646
|
+
tags_data.sort_by { |_k, v| v }
|
1647
|
+
end
|
1678
1648
|
|
1679
|
-
if format ==
|
1680
|
-
output
|
1649
|
+
if format == 'html'
|
1650
|
+
output = <<EOS
|
1681
1651
|
<table>
|
1682
1652
|
<caption id="tagtotals">Tag Totals</caption>
|
1683
1653
|
<colgroup>
|
@@ -1692,10 +1662,12 @@ EOTEMPLATE
|
|
1692
1662
|
</thead>
|
1693
1663
|
<tbody>
|
1694
1664
|
EOS
|
1695
|
-
sorted_tags_data.reverse.each
|
1696
|
-
|
1697
|
-
|
1698
|
-
|
1665
|
+
sorted_tags_data.reverse.each do |k, v|
|
1666
|
+
if v > 0
|
1667
|
+
output += "<tr><td style='text-align:left;'>#{k}</td><td style='text-align:left;'>#{'%02d:%02d:%02d' % fmt_time(v)}</td></tr>\n"
|
1668
|
+
end
|
1669
|
+
end
|
1670
|
+
tail = <<EOS
|
1699
1671
|
<tr>
|
1700
1672
|
<td style="text-align:left;" colspan="2"></td>
|
1701
1673
|
</tr>
|
@@ -1703,34 +1675,34 @@ EOS
|
|
1703
1675
|
<tfoot>
|
1704
1676
|
<tr>
|
1705
1677
|
<td style="text-align:left;"><strong>Total</strong></td>
|
1706
|
-
<td style="text-align:left;">#{
|
1678
|
+
<td style="text-align:left;">#{'%02d:%02d:%02d' % fmt_time(total)}</td>
|
1707
1679
|
</tr>
|
1708
1680
|
</tfoot>
|
1709
1681
|
</table>
|
1710
1682
|
EOS
|
1711
1683
|
output + tail
|
1712
|
-
elsif format ==
|
1684
|
+
elsif format == 'json'
|
1713
1685
|
output = []
|
1714
|
-
sorted_tags_data.reverse.each
|
1686
|
+
sorted_tags_data.reverse.each do |k, v|
|
1715
1687
|
output << {
|
1716
1688
|
'tag' => k,
|
1717
1689
|
'seconds' => v,
|
1718
|
-
'formatted' =>
|
1690
|
+
'formatted' => '%02d:%02d:%02d' % fmt_time(v)
|
1719
1691
|
}
|
1720
|
-
|
1692
|
+
end
|
1721
1693
|
output
|
1722
1694
|
else
|
1723
1695
|
output = []
|
1724
|
-
sorted_tags_data.reverse.each
|
1725
|
-
spacer =
|
1696
|
+
sorted_tags_data.reverse.each do |k, v|
|
1697
|
+
spacer = ''
|
1726
1698
|
(max - k.length).times do
|
1727
|
-
spacer +=
|
1699
|
+
spacer += ' '
|
1728
1700
|
end
|
1729
|
-
output.push("#{k}:#{spacer}#{
|
1730
|
-
|
1701
|
+
output.push("#{k}:#{spacer}#{'%02d:%02d:%02d' % fmt_time(v)}")
|
1702
|
+
end
|
1731
1703
|
|
1732
|
-
output = output.empty? ?
|
1733
|
-
output += "\n\nTotal tracked: #{
|
1704
|
+
output = output.empty? ? '' : "\n--- Tag Totals ---\n" + output.join("\n")
|
1705
|
+
output += "\n\nTotal tracked: #{'%02d:%02d:%02d' % fmt_time(total)}\n"
|
1734
1706
|
output
|
1735
1707
|
end
|
1736
1708
|
end
|
@@ -1744,53 +1716,54 @@ EOS
|
|
1744
1716
|
def autotag(text)
|
1745
1717
|
return unless text
|
1746
1718
|
return text unless @auto_tag
|
1719
|
+
|
1747
1720
|
current_tags = text.scan(/@\w+/)
|
1748
1721
|
whitelisted = []
|
1749
|
-
@config['autotag']['whitelist'].each
|
1722
|
+
@config['autotag']['whitelist'].each do |tag|
|
1723
|
+
next if text =~ /@#{tag}\b/i
|
1724
|
+
|
1750
1725
|
text.sub!(/(?<!@)(#{tag.strip})\b/i) do |m|
|
1751
1726
|
m.downcase! if tag =~ /[a-z]/
|
1752
1727
|
whitelisted.push("@#{m}")
|
1753
1728
|
"@#{m}"
|
1754
|
-
end
|
1755
|
-
|
1729
|
+
end
|
1730
|
+
end
|
1756
1731
|
tail_tags = []
|
1757
|
-
@config['autotag']['synonyms'].each
|
1758
|
-
v.each
|
1759
|
-
|
1760
|
-
|
1761
|
-
|
1762
|
-
|
1763
|
-
|
1764
|
-
}
|
1765
|
-
}
|
1732
|
+
@config['autotag']['synonyms'].each do |tag, v|
|
1733
|
+
v.each do |word|
|
1734
|
+
next unless text =~ /\b#{word}\b/i
|
1735
|
+
|
1736
|
+
tail_tags.push(tag) unless current_tags.include?("@#{tag}") || whitelisted.include?("@#{tag}")
|
1737
|
+
end
|
1738
|
+
end
|
1766
1739
|
if @config['autotag'].key? 'transform'
|
1767
|
-
@config['autotag']['transform'].each
|
1768
|
-
|
1769
|
-
|
1770
|
-
|
1771
|
-
|
1772
|
-
|
1773
|
-
|
1774
|
-
|
1775
|
-
|
1776
|
-
|
1777
|
-
|
1778
|
-
|
1779
|
-
|
1780
|
-
|
1781
|
-
|
1782
|
-
|
1740
|
+
@config['autotag']['transform'].each do |tag|
|
1741
|
+
next unless tag =~ /\S+:\S+/
|
1742
|
+
|
1743
|
+
rx, r = tag.split(/:/)
|
1744
|
+
r.gsub!(/\$/, '\\')
|
1745
|
+
rx.sub!(/^@/, '')
|
1746
|
+
regex = Regexp.new('@' + rx + '\b')
|
1747
|
+
|
1748
|
+
matches = text.scan(regex)
|
1749
|
+
next unless matches
|
1750
|
+
|
1751
|
+
matches.each do |m|
|
1752
|
+
new_tag = r
|
1753
|
+
if m.is_a?(Array)
|
1754
|
+
index = 1
|
1755
|
+
m.each do |v|
|
1756
|
+
new_tag = new_tag.gsub('\\' + index.to_s, v)
|
1757
|
+
index += 1
|
1783
1758
|
end
|
1784
|
-
|
1785
|
-
|
1759
|
+
end
|
1760
|
+
tail_tags.push(new_tag)
|
1786
1761
|
end
|
1787
|
-
|
1788
|
-
end
|
1789
|
-
if whitelisted.length > 0
|
1790
|
-
@results.push("Whitelisted tags: #{whitelisted.join(', ')}")
|
1762
|
+
end
|
1791
1763
|
end
|
1764
|
+
@results.push("Whitelisted tags: #{whitelisted.join(', ')}") if whitelisted.length > 0
|
1792
1765
|
if tail_tags.length > 0
|
1793
|
-
tags = tail_tags.uniq.map {|t| '@'+t }.join(' ')
|
1766
|
+
tags = tail_tags.uniq.map { |t| '@' + t }.join(' ')
|
1794
1767
|
@results.push("Synonym tags: #{tags}")
|
1795
1768
|
text + ' ' + tags
|
1796
1769
|
else
|
@@ -1806,43 +1779,43 @@ EOS
|
|
1806
1779
|
## @param item (Hash) The entry
|
1807
1780
|
## @param formatted (Bool) Return human readable time (default seconds)
|
1808
1781
|
##
|
1809
|
-
def get_interval(item, formatted=true)
|
1782
|
+
def get_interval(item, formatted = true)
|
1810
1783
|
done = nil
|
1811
1784
|
start = nil
|
1812
1785
|
|
1813
1786
|
if @interval_cache.keys.include? item['title']
|
1814
1787
|
seconds = @interval_cache[item['title']]
|
1815
|
-
return seconds > 0 ?
|
1788
|
+
return seconds > 0 ? '%02d:%02d:%02d' % fmt_time(seconds) : false
|
1816
1789
|
end
|
1817
1790
|
|
1818
1791
|
if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
|
1819
|
-
done = Time.parse(
|
1792
|
+
done = Time.parse(Regexp.last_match(1))
|
1820
1793
|
else
|
1821
1794
|
return nil
|
1822
1795
|
end
|
1823
1796
|
|
1824
|
-
if item['title'] =~ /@start\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
|
1825
|
-
|
1826
|
-
|
1827
|
-
|
1828
|
-
|
1797
|
+
start = if item['title'] =~ /@start\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
|
1798
|
+
Time.parse(Regexp.last_match(1))
|
1799
|
+
else
|
1800
|
+
item['date']
|
1801
|
+
end
|
1829
1802
|
|
1830
1803
|
seconds = (done - start).to_i
|
1831
1804
|
|
1832
|
-
item['title'].scan(/(?mi)@(\S+?)(\(.*\))?(?=\s|$)/).each
|
1833
|
-
k = m[0] ==
|
1805
|
+
item['title'].scan(/(?mi)@(\S+?)(\(.*\))?(?=\s|$)/).each do |m|
|
1806
|
+
k = m[0] == 'done' ? 'All' : m[0].downcase
|
1834
1807
|
if @timers.has_key?(k)
|
1835
1808
|
@timers[k] += seconds
|
1836
1809
|
else
|
1837
1810
|
@timers[k] = seconds
|
1838
1811
|
end
|
1839
|
-
|
1812
|
+
end
|
1840
1813
|
|
1841
1814
|
@interval_cache[item['title']] = seconds
|
1842
1815
|
|
1843
1816
|
return seconds unless formatted
|
1844
1817
|
|
1845
|
-
seconds > 0 ?
|
1818
|
+
seconds > 0 ? '%02d:%02d:%02d' % fmt_time(seconds) : false
|
1846
1819
|
end
|
1847
1820
|
|
1848
1821
|
##
|
@@ -1851,19 +1824,19 @@ EOS
|
|
1851
1824
|
## @param seconds The seconds
|
1852
1825
|
##
|
1853
1826
|
def fmt_time(seconds)
|
1854
|
-
if seconds.nil?
|
1855
|
-
|
1856
|
-
end
|
1827
|
+
return [0, 0, 0] if seconds.nil?
|
1828
|
+
|
1857
1829
|
if seconds =~ /(\d+):(\d+):(\d+)/
|
1858
|
-
h
|
1830
|
+
h = Regexp.last_match(1)
|
1831
|
+
m = Regexp.last_match(2)
|
1832
|
+
s = Regexp.last_match(3)
|
1859
1833
|
seconds = (h.to_i * 60 * 60) + (m.to_i * 60) + s.to_i
|
1860
1834
|
end
|
1861
|
-
minutes =
|
1835
|
+
minutes = (seconds / 60).to_i
|
1862
1836
|
hours = (minutes / 60).to_i
|
1863
1837
|
days = (hours / 24).to_i
|
1864
1838
|
hours = (hours % 24).to_i
|
1865
1839
|
minutes = (minutes % 60).to_i
|
1866
1840
|
[days, hours, minutes]
|
1867
1841
|
end
|
1868
|
-
|
1869
1842
|
end
|