doing 1.0.45 → 1.0.50
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -5
- data/bin/doing +78 -126
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +649 -685
- metadata +8 -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
647
|
title = last['title'].sub(/\s*@done(\(.*?\))?/, '').chomp
|
673
|
-
|
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,35 +911,32 @@ 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)
|
963
|
-
if File.
|
926
|
+
if File.exist?(file)
|
964
927
|
# Create a backup copy for the undo command
|
965
|
-
FileUtils.cp(file,file
|
928
|
+
FileUtils.cp(file, "#{file}~")
|
966
929
|
|
967
|
-
File.open(file,'w+') do |f|
|
930
|
+
File.open(file, 'w+') do |f|
|
968
931
|
f.puts output
|
969
932
|
end
|
970
933
|
end
|
971
934
|
|
972
935
|
if @config.key?('run_after')
|
973
|
-
|
974
|
-
if
|
975
|
-
|
976
|
-
|
936
|
+
stdout, stderr, status = Open3.capture3(@config['run_after'])
|
937
|
+
if status.exitstatus.positive?
|
938
|
+
warn "Error running #{@config['run_after']}"
|
939
|
+
warn stderr
|
977
940
|
end
|
978
941
|
end
|
979
942
|
end
|
@@ -985,27 +948,27 @@ class WWID
|
|
985
948
|
## @param file (String) The filepath to restore
|
986
949
|
##
|
987
950
|
def restore_backup(file)
|
988
|
-
if File.
|
989
|
-
puts file+
|
990
|
-
FileUtils.cp(file+
|
951
|
+
if File.exist?(file + '~')
|
952
|
+
puts file + '~'
|
953
|
+
FileUtils.cp(file + '~', file)
|
991
954
|
@results.push("Restored #{file}")
|
992
955
|
end
|
993
956
|
end
|
994
957
|
|
995
|
-
|
996
958
|
##
|
997
959
|
## @brief Generate a menu of sections and allow user selection
|
998
960
|
##
|
999
961
|
## @return (String) The selected section name
|
1000
962
|
##
|
1001
963
|
def choose_section
|
1002
|
-
sections.each_with_index
|
1003
|
-
puts
|
1004
|
-
|
964
|
+
sections.each_with_index do |section, i|
|
965
|
+
puts format('% 3d: %s', i + 1, section)
|
966
|
+
end
|
1005
967
|
print "#{colors['green']}> #{colors['default']}"
|
1006
968
|
num = STDIN.gets
|
1007
969
|
return false if num =~ /^[a-z ]*$/i
|
1008
|
-
|
970
|
+
|
971
|
+
sections[num.to_i - 1]
|
1009
972
|
end
|
1010
973
|
|
1011
974
|
##
|
@@ -1023,13 +986,14 @@ class WWID
|
|
1023
986
|
## @return (String) The selected view name
|
1024
987
|
##
|
1025
988
|
def choose_view
|
1026
|
-
views.each_with_index
|
1027
|
-
puts
|
1028
|
-
|
1029
|
-
print
|
989
|
+
views.each_with_index do |view, i|
|
990
|
+
puts format('% 3d: %s', i + 1, view)
|
991
|
+
end
|
992
|
+
print '> '
|
1030
993
|
num = STDIN.gets
|
1031
994
|
return false if num =~ /^[a-z ]*$/i
|
1032
|
-
|
995
|
+
|
996
|
+
views[num.to_i - 1]
|
1033
997
|
end
|
1034
998
|
|
1035
999
|
##
|
@@ -1038,9 +1002,8 @@ class WWID
|
|
1038
1002
|
## @param title (String) The title of the view to retrieve
|
1039
1003
|
##
|
1040
1004
|
def get_view(title)
|
1041
|
-
if @config['views'].has_key?(title)
|
1042
|
-
|
1043
|
-
end
|
1005
|
+
return @config['views'][title] if @config['views'].has_key?(title)
|
1006
|
+
|
1044
1007
|
false
|
1045
1008
|
end
|
1046
1009
|
|
@@ -1050,14 +1013,14 @@ class WWID
|
|
1050
1013
|
##
|
1051
1014
|
## @param opt (Hash) Additional Options
|
1052
1015
|
##
|
1053
|
-
def list_section(opt={})
|
1016
|
+
def list_section(opt = {})
|
1054
1017
|
opt[:count] ||= 0
|
1055
1018
|
count = opt[:count] - 1
|
1056
1019
|
opt[:section] ||= nil
|
1057
1020
|
opt[:format] ||= @default_date_format
|
1058
1021
|
opt[:template] ||= @default_template
|
1059
|
-
opt[:age] ||=
|
1060
|
-
opt[:order] ||=
|
1022
|
+
opt[:age] ||= 'newest'
|
1023
|
+
opt[:order] ||= 'desc'
|
1061
1024
|
opt[:today] ||= false
|
1062
1025
|
opt[:tag_filter] ||= false
|
1063
1026
|
opt[:tags_color] ||= false
|
@@ -1069,17 +1032,23 @@ class WWID
|
|
1069
1032
|
opt[:date_filter] ||= []
|
1070
1033
|
|
1071
1034
|
# opt[:highlight] ||= true
|
1072
|
-
section =
|
1035
|
+
section = ''
|
1073
1036
|
if opt[:section].nil?
|
1074
1037
|
section = choose_section
|
1075
1038
|
opt[:section] = @content[section]
|
1076
|
-
elsif opt[:section].
|
1039
|
+
elsif opt[:section].instance_of?(String)
|
1077
1040
|
if opt[:section] =~ /^all$/i
|
1078
|
-
combined = {'items' => []}
|
1079
|
-
@content.each
|
1041
|
+
combined = { 'items' => [] }
|
1042
|
+
@content.each do |_k, v|
|
1080
1043
|
combined['items'] += v['items']
|
1081
|
-
|
1082
|
-
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
|
1083
1052
|
opt[:section] = combined
|
1084
1053
|
else
|
1085
1054
|
section = guess_section(opt[:section])
|
@@ -1088,209 +1057,201 @@ class WWID
|
|
1088
1057
|
end
|
1089
1058
|
|
1090
1059
|
if opt[:section].class != Hash
|
1091
|
-
|
1060
|
+
warn 'Invalid section object'
|
1092
1061
|
return
|
1093
1062
|
end
|
1094
1063
|
|
1095
|
-
items = opt[:section]['items'].sort_by{|item| item['date'] }
|
1064
|
+
items = opt[:section]['items'].sort_by { |item| item['date'] }
|
1096
1065
|
|
1097
1066
|
if opt[:date_filter].length == 2
|
1098
1067
|
start_date = opt[:date_filter][0]
|
1099
1068
|
end_date = opt[:date_filter][1]
|
1100
|
-
items.keep_if
|
1069
|
+
items.keep_if do |item|
|
1101
1070
|
if end_date
|
1102
1071
|
item['date'] >= start_date && item['date'] <= end_date
|
1103
1072
|
else
|
1104
1073
|
item['date'].strftime('%F') == start_date.strftime('%F')
|
1105
1074
|
end
|
1106
|
-
|
1075
|
+
end
|
1107
1076
|
end
|
1108
1077
|
|
1109
1078
|
if opt[:tag_filter] && !opt[:tag_filter]['tags'].empty?
|
1110
|
-
items.delete_if
|
1079
|
+
items.delete_if do |item|
|
1111
1080
|
if opt[:tag_filter]['bool'] =~ /(AND|ALL)/
|
1112
1081
|
score = 0
|
1113
|
-
opt[:tag_filter]['tags'].each
|
1082
|
+
opt[:tag_filter]['tags'].each do |tag|
|
1114
1083
|
score += 1 if item['title'] =~ /@#{tag}/
|
1115
|
-
|
1084
|
+
end
|
1116
1085
|
score < opt[:tag_filter]['tags'].length
|
1117
1086
|
elsif opt[:tag_filter]['bool'] =~ /NONE/
|
1118
1087
|
del = false
|
1119
|
-
opt[:tag_filter]['tags'].each
|
1088
|
+
opt[:tag_filter]['tags'].each do |tag|
|
1120
1089
|
del = true if item['title'] =~ /@#{tag}/
|
1121
|
-
|
1090
|
+
end
|
1122
1091
|
del
|
1123
1092
|
elsif opt[:tag_filter]['bool'] =~ /(OR|ANY)/
|
1124
1093
|
del = true
|
1125
|
-
opt[:tag_filter]['tags'].each
|
1094
|
+
opt[:tag_filter]['tags'].each do |tag|
|
1126
1095
|
del = false if item['title'] =~ /@#{tag}/
|
1127
|
-
|
1096
|
+
end
|
1128
1097
|
del
|
1129
1098
|
end
|
1130
|
-
|
1099
|
+
end
|
1131
1100
|
end
|
1132
1101
|
|
1133
1102
|
if opt[:search]
|
1134
|
-
items.keep_if
|
1135
|
-
text = item['note'] ? item['title'] + item['note'].join(
|
1136
|
-
if opt[:search].strip =~
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
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
|
1141
1110
|
text =~ /#{pattern}/i
|
1142
|
-
|
1111
|
+
end
|
1143
1112
|
end
|
1144
1113
|
|
1145
1114
|
if opt[:only_timed]
|
1146
|
-
items.delete_if
|
1115
|
+
items.delete_if do |item|
|
1147
1116
|
get_interval(item) == false
|
1148
|
-
|
1117
|
+
end
|
1149
1118
|
end
|
1150
1119
|
|
1151
1120
|
if opt[:today]
|
1152
|
-
items.delete_if
|
1121
|
+
items.delete_if do |item|
|
1153
1122
|
item['date'] < Date.today.to_time
|
1154
|
-
|
1123
|
+
end.reverse!
|
1155
1124
|
section = Time.now.strftime('%A, %B %d')
|
1156
1125
|
elsif opt[:yesterday]
|
1157
|
-
items.delete_if
|
1158
|
-
|
1159
|
-
|
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]
|
1160
1132
|
else
|
1161
|
-
|
1162
|
-
items = items[0..count]
|
1163
|
-
else
|
1164
|
-
items = items.reverse[0..count]
|
1165
|
-
end
|
1133
|
+
items = items.reverse[0..count]
|
1166
1134
|
end
|
1167
1135
|
|
1168
|
-
if opt[:order] =~ /^a/i
|
1169
|
-
items.reverse!
|
1170
|
-
end
|
1136
|
+
items.reverse! if opt[:order] =~ /^a/i
|
1171
1137
|
|
1172
|
-
out =
|
1138
|
+
out = ''
|
1173
1139
|
|
1174
|
-
if opt[:output]
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
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 = ''
|
1181
1146
|
if i['note']
|
1182
|
-
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*$/ }
|
1183
1148
|
note = arr.join("\n") unless arr.nil?
|
1184
1149
|
end
|
1185
|
-
if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
1186
|
-
interval = get_interval(i, false)
|
1187
|
-
end
|
1150
|
+
interval = get_interval(i, false) if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
1188
1151
|
interval ||= 0
|
1189
|
-
output.push(CSV.generate_line([i['date'],i['title'],note,interval,i['section']]))
|
1190
|
-
|
1191
|
-
out = output.join(
|
1192
|
-
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'
|
1193
1156
|
items_out = []
|
1194
1157
|
max = items[-1]['date'].strftime('%F')
|
1195
1158
|
min = items[0]['date'].strftime('%F')
|
1196
|
-
items.each_with_index
|
1159
|
+
items.each_with_index do |i, index|
|
1197
1160
|
if String.method_defined? :force_encoding
|
1198
1161
|
title = i['title'].force_encoding('utf-8')
|
1199
|
-
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']
|
1200
1163
|
else
|
1201
1164
|
title = i['title']
|
1202
1165
|
note = i['note'].map { |line| line.strip } if i['note']
|
1203
1166
|
end
|
1204
1167
|
if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
1205
|
-
end_date = Time.parse(
|
1206
|
-
interval = get_interval(i,false)
|
1168
|
+
end_date = Time.parse(Regexp.last_match(1))
|
1169
|
+
interval = get_interval(i, false)
|
1207
1170
|
end
|
1208
|
-
end_date ||=
|
1171
|
+
end_date ||= ''
|
1209
1172
|
interval ||= 0
|
1210
|
-
note ||=
|
1173
|
+
note ||= ''
|
1211
1174
|
|
1212
1175
|
tags = []
|
1213
|
-
skip_tags = [
|
1214
|
-
i['title'].scan(/@([
|
1176
|
+
skip_tags = %w[meanwhile done cancelled flagged]
|
1177
|
+
i['title'].scan(/@([^(\s]+)(?:\((.*?)\))?/).each do |tag|
|
1215
1178
|
tags.push(tag[0]) unless skip_tags.include?(tag[0])
|
1216
|
-
|
1217
|
-
if opt[:output] ==
|
1179
|
+
end
|
1180
|
+
if opt[:output] == 'json'
|
1218
1181
|
|
1219
1182
|
items_out << {
|
1220
|
-
:
|
1221
|
-
:
|
1222
|
-
:
|
1223
|
-
:
|
1224
|
-
:
|
1225
|
-
:
|
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
|
1226
1189
|
}
|
1227
1190
|
|
1228
|
-
elsif opt[:output] ==
|
1191
|
+
elsif opt[:output] == 'timeline'
|
1229
1192
|
new_item = {
|
1230
1193
|
'id' => index + 1,
|
1231
1194
|
'content' => title.strip, #+ " #{note}"
|
1232
|
-
'title' => title.strip + " (#{
|
1195
|
+
'title' => title.strip + " (#{'%02d:%02d:%02d' % fmt_time(interval)})",
|
1233
1196
|
'start' => i['date'].strftime('%F'),
|
1234
1197
|
'type' => 'point'
|
1235
1198
|
}
|
1236
1199
|
|
1237
1200
|
if interval && interval > 0
|
1238
1201
|
new_item['end'] = end_date.strftime('%F')
|
1239
|
-
if interval > 3600 * 3
|
1240
|
-
new_item['type'] = 'range'
|
1241
|
-
end
|
1202
|
+
new_item['type'] = 'range' if interval > 3600 * 3
|
1242
1203
|
end
|
1243
1204
|
items_out.push(new_item)
|
1244
1205
|
end
|
1245
|
-
|
1246
|
-
if opt[:output] ==
|
1206
|
+
end
|
1207
|
+
if opt[:output] == 'json'
|
1247
1208
|
out = {
|
1248
1209
|
'section' => section,
|
1249
1210
|
'items' => items_out,
|
1250
|
-
'timers' => tag_times(
|
1211
|
+
'timers' => tag_times('json', opt[:sort_tags])
|
1251
1212
|
}.to_json
|
1252
|
-
elsif opt[:output] ==
|
1253
|
-
|
1254
|
-
<!doctype html>
|
1255
|
-
<html>
|
1256
|
-
<head>
|
1257
|
-
|
1258
|
-
|
1259
|
-
</head>
|
1260
|
-
<body>
|
1261
|
-
|
1262
|
-
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1268
|
-
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
1284
|
-
|
1285
|
-
</body>
|
1286
|
-
</html>
|
1287
|
-
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
|
1288
1249
|
return template
|
1289
1250
|
end
|
1290
|
-
elsif opt[:output] ==
|
1251
|
+
elsif opt[:output] == 'html'
|
1291
1252
|
page_title = section
|
1292
1253
|
items_out = []
|
1293
|
-
items.each
|
1254
|
+
items.each do |i|
|
1294
1255
|
# if i.has_key?('note')
|
1295
1256
|
# note = '<span class="note">' + i['note'].map{|n| n.strip }.join('<br>') + '</span>'
|
1296
1257
|
# else
|
@@ -1298,83 +1259,81 @@ EOTEMPLATE
|
|
1298
1259
|
# end
|
1299
1260
|
if String.method_defined? :force_encoding
|
1300
1261
|
title = i['title'].force_encoding('utf-8').link_urls
|
1301
|
-
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']
|
1302
1263
|
else
|
1303
1264
|
title = i['title'].link_urls
|
1304
1265
|
note = i['note'].map { |line| line.strip.link_urls } if i['note']
|
1305
1266
|
end
|
1306
1267
|
|
1307
|
-
if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
1308
|
-
interval = get_interval(i)
|
1309
|
-
end
|
1268
|
+
interval = get_interval(i) if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
1310
1269
|
interval ||= false
|
1311
1270
|
|
1312
1271
|
items_out << {
|
1313
|
-
:
|
1314
|
-
:
|
1315
|
-
:
|
1316
|
-
:
|
1317
|
-
:
|
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']
|
1318
1277
|
}
|
1319
|
-
}
|
1320
|
-
|
1321
|
-
if @config['html_template']['haml'] && File.exists?(File.expand_path(@config['html_template']['haml']))
|
1322
|
-
template = IO.read(File.expand_path(@config['html_template']['haml']))
|
1323
|
-
else
|
1324
|
-
template = haml_template
|
1325
1278
|
end
|
1326
1279
|
|
1327
|
-
if @config['html_template']['
|
1328
|
-
|
1329
|
-
|
1330
|
-
|
1331
|
-
|
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
|
1332
1291
|
|
1333
|
-
totals = opt[:totals] ? tag_times(
|
1292
|
+
totals = opt[:totals] ? tag_times('html', opt[:sort_tags]) : ''
|
1334
1293
|
engine = Haml::Engine.new(template)
|
1335
|
-
puts engine.render(Object.new,
|
1294
|
+
puts engine.render(Object.new,
|
1295
|
+
{ :@items => items_out, :@page_title => page_title, :@style => style, :@totals => totals })
|
1336
1296
|
else
|
1337
|
-
items.each
|
1338
|
-
|
1297
|
+
items.each do |item|
|
1339
1298
|
if opt[:highlight] && item['title'] =~ /@#{@config['marker_tag']}\b/i
|
1340
1299
|
flag = colors[@config['marker_color']]
|
1341
1300
|
reset = colors['default']
|
1342
1301
|
else
|
1343
|
-
flag =
|
1344
|
-
reset =
|
1302
|
+
flag = ''
|
1303
|
+
reset = ''
|
1345
1304
|
end
|
1346
1305
|
|
1347
1306
|
if (item.has_key?('note') && !item['note'].empty?) && @config[:include_notes]
|
1348
|
-
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(/^-/, '—') + ' ' }
|
1349
1310
|
if opt[:wrap_width] && opt[:wrap_width] > 0
|
1350
1311
|
width = opt[:wrap_width]
|
1351
|
-
note_lines.map!
|
1312
|
+
note_lines.map! do |line|
|
1352
1313
|
line.strip.gsub(/(.{1,#{width}})(\s+|\Z)/, "\t\\1\n")
|
1353
|
-
|
1314
|
+
end
|
1354
1315
|
end
|
1355
1316
|
note = "\n#{note_lines.join("\n").chomp}"
|
1356
1317
|
else
|
1357
|
-
note =
|
1318
|
+
note = ''
|
1358
1319
|
end
|
1359
1320
|
output = opt[:template].dup
|
1360
1321
|
|
1361
1322
|
output.gsub!(/%[a-z]+/) do |m|
|
1362
|
-
if colors.has_key?(m.sub(/^%/,''))
|
1363
|
-
colors[m.sub(/^%/,'')]
|
1323
|
+
if colors.has_key?(m.sub(/^%/, ''))
|
1324
|
+
colors[m.sub(/^%/, '')]
|
1364
1325
|
else
|
1365
1326
|
m
|
1366
1327
|
end
|
1367
1328
|
end
|
1368
1329
|
|
1369
|
-
output.sub!(/%date/,item['date'].strftime(opt[:format]))
|
1330
|
+
output.sub!(/%date/, item['date'].strftime(opt[:format]))
|
1370
1331
|
|
1371
|
-
if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
1372
|
-
|
1373
|
-
|
1374
|
-
interval ||= ""
|
1375
|
-
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)
|
1376
1335
|
|
1377
|
-
output.sub!(/%shortdate/)
|
1336
|
+
output.sub!(/%shortdate/) do
|
1378
1337
|
if item['date'] > Date.today.to_time
|
1379
1338
|
item['date'].strftime('%_I:%M%P')
|
1380
1339
|
elsif item['date'] > (Date.today - 7).to_time
|
@@ -1384,45 +1343,45 @@ EOTEMPLATE
|
|
1384
1343
|
else
|
1385
1344
|
item['date'].strftime('%b %d %Y, %-I:%M%P')
|
1386
1345
|
end
|
1387
|
-
|
1346
|
+
end
|
1388
1347
|
|
1389
|
-
output.sub!(/%title/)
|
1348
|
+
output.sub!(/%title/) do |_m|
|
1390
1349
|
if opt[:wrap_width] && opt[:wrap_width] > 0
|
1391
|
-
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
|
1392
1351
|
else
|
1393
|
-
flag+item['title'].chomp+reset
|
1352
|
+
flag + item['title'].chomp + reset
|
1394
1353
|
end
|
1395
|
-
|
1354
|
+
end
|
1396
1355
|
|
1397
|
-
output.sub!(/%section/,item['section']) if item['section']
|
1356
|
+
output.sub!(/%section/, item['section']) if item['section']
|
1398
1357
|
|
1399
1358
|
if opt[:tags_color]
|
1400
1359
|
escapes = output.scan(/(\e\[[\d;]+m)[^\e]+@/)
|
1401
|
-
if escapes.length > 0
|
1402
|
-
|
1403
|
-
|
1404
|
-
|
1405
|
-
|
1406
|
-
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}")
|
1407
1366
|
end
|
1408
|
-
output.sub!(/%note/,note)
|
1409
|
-
output.sub!(/%odnote/,note.gsub(/^\t*/,
|
1410
|
-
output.sub!(/%chompnote/,note.gsub(/\n+/,' ').gsub(/(^\s*|\s*$)/,'').gsub(/\s+/,' '))
|
1411
|
-
output.gsub!(/%hr(_under)?/) do |
|
1412
|
-
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 = ''
|
1413
1372
|
`tput cols`.to_i.times do
|
1414
|
-
o +=
|
1373
|
+
o += Regexp.last_match(1).nil? ? '-' : '_'
|
1415
1374
|
end
|
1416
1375
|
o
|
1417
1376
|
end
|
1418
|
-
output.gsub!(/%n/,"\n")
|
1419
|
-
output.gsub!(/%t/,"\t")
|
1377
|
+
output.gsub!(/%n/, "\n")
|
1378
|
+
output.gsub!(/%t/, "\t")
|
1420
1379
|
|
1421
1380
|
out += output + "\n"
|
1422
|
-
|
1423
|
-
out += tag_times(
|
1381
|
+
end
|
1382
|
+
out += tag_times('text', opt[:sort_tags]) if opt[:totals]
|
1424
1383
|
end
|
1425
|
-
|
1384
|
+
out
|
1426
1385
|
end
|
1427
1386
|
|
1428
1387
|
##
|
@@ -1435,15 +1394,12 @@ EOTEMPLATE
|
|
1435
1394
|
## @param tags (Array) Tags to archive
|
1436
1395
|
## @param bool (String) Tag boolean combinator
|
1437
1396
|
##
|
1438
|
-
def archive(section=
|
1439
|
-
|
1397
|
+
def archive(section = 'Currently', count = 5, destination = nil, tags = nil, bool = nil, _export = nil)
|
1440
1398
|
section = choose_section if section.nil? || section =~ /choose/i
|
1441
1399
|
archive_all = section =~ /all/i # && !(tags.nil? || tags.empty?)
|
1442
1400
|
section = guess_section(section) unless archive_all
|
1443
1401
|
|
1444
|
-
if destination =~ /archive/i && !sections.include?(
|
1445
|
-
add_section("Archive")
|
1446
|
-
end
|
1402
|
+
add_section('Archive') if destination =~ /archive/i && !sections.include?('Archive')
|
1447
1403
|
|
1448
1404
|
destination = guess_section(destination)
|
1449
1405
|
|
@@ -1451,16 +1407,16 @@ EOTEMPLATE
|
|
1451
1407
|
if archive_all
|
1452
1408
|
to_archive = sections.dup
|
1453
1409
|
to_archive.delete(destination)
|
1454
|
-
to_archive.each
|
1455
|
-
do_archive(source, destination, { :
|
1456
|
-
|
1410
|
+
to_archive.each do |source, _v|
|
1411
|
+
do_archive(source, destination, { count: count, tags: tags, bool: bool, label: true })
|
1412
|
+
end
|
1457
1413
|
else
|
1458
|
-
do_archive(section, destination, { :
|
1414
|
+
do_archive(section, destination, { count: count, tags: tags, bool: bool, label: true })
|
1459
1415
|
end
|
1460
1416
|
|
1461
1417
|
write(doing_file)
|
1462
1418
|
else
|
1463
|
-
raise
|
1419
|
+
raise 'Either source or destination does not exist'
|
1464
1420
|
end
|
1465
1421
|
end
|
1466
1422
|
|
@@ -1471,63 +1427,66 @@ EOTEMPLATE
|
|
1471
1427
|
## @param destination (String) The destination section
|
1472
1428
|
## @param opt (Hash) Additional Options
|
1473
1429
|
##
|
1474
|
-
def do_archive(section, destination, opt={})
|
1430
|
+
def do_archive(section, destination, opt = {})
|
1475
1431
|
count = opt[:count] || 5
|
1476
1432
|
tags = opt[:tags] || []
|
1477
|
-
bool = opt[:bool] ||
|
1433
|
+
bool = opt[:bool] || 'AND'
|
1478
1434
|
label = opt[:label] || false
|
1479
1435
|
|
1480
1436
|
items = @content[section]['items']
|
1481
1437
|
moved_items = []
|
1482
1438
|
|
1483
1439
|
if tags && !tags.empty?
|
1484
|
-
items.delete_if
|
1440
|
+
items.delete_if do |item|
|
1485
1441
|
if bool =~ /(AND|ALL)/
|
1486
1442
|
score = 0
|
1487
|
-
tags.each
|
1443
|
+
tags.each do |tag|
|
1488
1444
|
score += 1 if item['title'] =~ /@#{tag}/i
|
1489
|
-
|
1445
|
+
end
|
1490
1446
|
res = score < tags.length
|
1491
1447
|
moved_items.push(item) if res
|
1492
1448
|
res
|
1493
1449
|
elsif bool =~ /NONE/
|
1494
1450
|
del = false
|
1495
|
-
tags.each
|
1451
|
+
tags.each do |tag|
|
1496
1452
|
del = true if item['title'] =~ /@#{tag}/i
|
1497
|
-
|
1453
|
+
end
|
1498
1454
|
moved_items.push(item) if del
|
1499
1455
|
del
|
1500
1456
|
elsif bool =~ /(OR|ANY)/
|
1501
1457
|
del = true
|
1502
|
-
tags.each
|
1458
|
+
tags.each do |tag|
|
1503
1459
|
del = false if item['title'] =~ /@#{tag}/i
|
1504
|
-
|
1460
|
+
end
|
1505
1461
|
moved_items.push(item) if del
|
1506
1462
|
del
|
1507
1463
|
end
|
1508
|
-
|
1509
|
-
moved_items.each
|
1510
|
-
if label
|
1511
|
-
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})")
|
1512
1469
|
end
|
1513
|
-
|
1470
|
+
end
|
1514
1471
|
@content[section]['items'] = moved_items
|
1515
1472
|
@content[destination]['items'] += items
|
1516
1473
|
@results.push("Archived #{items.length} items from #{section} to #{destination}")
|
1517
1474
|
else
|
1518
1475
|
|
1519
1476
|
return if items.length < count
|
1520
|
-
if count == 0
|
1521
|
-
@content[section]['items'] = []
|
1522
|
-
else
|
1523
|
-
@content[section]['items'] = items[0..count-1]
|
1524
|
-
end
|
1525
1477
|
|
1526
|
-
items
|
1527
|
-
|
1528
|
-
|
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})")
|
1529
1488
|
end
|
1530
|
-
|
1489
|
+
end
|
1531
1490
|
|
1532
1491
|
@content[destination]['items'] += items[count..-1]
|
1533
1492
|
@results.push("Archived #{items.length - count} items from #{section} to #{destination}")
|
@@ -1583,7 +1542,6 @@ EOTEMPLATE
|
|
1583
1542
|
color
|
1584
1543
|
end
|
1585
1544
|
|
1586
|
-
|
1587
1545
|
##
|
1588
1546
|
## @brief Show all entries from the current day
|
1589
1547
|
##
|
@@ -1591,12 +1549,13 @@ EOTEMPLATE
|
|
1591
1549
|
## @param output (String) output format
|
1592
1550
|
## @param opt (Hash) Options
|
1593
1551
|
##
|
1594
|
-
def today(times=true,output=nil,opt={})
|
1552
|
+
def today(times = true, output = nil, opt = {})
|
1595
1553
|
opt[:totals] ||= false
|
1596
1554
|
opt[:sort_tags] ||= false
|
1597
1555
|
|
1598
1556
|
cfg = @config['templates']['today']
|
1599
|
-
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] })
|
1600
1559
|
end
|
1601
1560
|
|
1602
1561
|
##
|
@@ -1608,16 +1567,15 @@ EOTEMPLATE
|
|
1608
1567
|
## @param output (String) Output format
|
1609
1568
|
## @param opt (Hash) Additional Options
|
1610
1569
|
##
|
1611
|
-
def list_date(dates,section,times=nil,output=nil,opt={})
|
1570
|
+
def list_date(dates, section, times = nil, output = nil, opt = {})
|
1612
1571
|
opt[:totals] ||= false
|
1613
1572
|
opt[:sort_tags] ||= false
|
1614
1573
|
section = guess_section(section)
|
1615
1574
|
# :date_filter expects an array with start and end date
|
1616
|
-
if dates.
|
1617
|
-
dates = [dates, dates]
|
1618
|
-
end
|
1575
|
+
dates = [dates, dates] if dates.instance_of?(String)
|
1619
1576
|
|
1620
|
-
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] })
|
1621
1579
|
end
|
1622
1580
|
|
1623
1581
|
##
|
@@ -1628,11 +1586,12 @@ EOTEMPLATE
|
|
1628
1586
|
## @param output (String) Output format
|
1629
1587
|
## @param opt (Hash) Additional Options
|
1630
1588
|
##
|
1631
|
-
def yesterday(section,times=nil,output=nil,opt={})
|
1589
|
+
def yesterday(section, times = nil, output = nil, opt = {})
|
1632
1590
|
opt[:totals] ||= false
|
1633
1591
|
opt[:sort_tags] ||= false
|
1634
1592
|
section = guess_section(section)
|
1635
|
-
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] })
|
1636
1595
|
end
|
1637
1596
|
|
1638
1597
|
##
|
@@ -1642,7 +1601,7 @@ EOTEMPLATE
|
|
1642
1601
|
## @param section (String) The section to show from, default Currently
|
1643
1602
|
## @param opt (Hash) Additional Options
|
1644
1603
|
##
|
1645
|
-
def recent(count=10,section=nil,opt={})
|
1604
|
+
def recent(count = 10, section = nil, opt = {})
|
1646
1605
|
times = opt[:t] || true
|
1647
1606
|
opt[:totals] ||= false
|
1648
1607
|
opt[:sort_tags] ||= false
|
@@ -1650,7 +1609,8 @@ EOTEMPLATE
|
|
1650
1609
|
cfg = @config['templates']['recent']
|
1651
1610
|
section ||= @current_section
|
1652
1611
|
section = guess_section(section)
|
1653
|
-
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] })
|
1654
1614
|
end
|
1655
1615
|
|
1656
1616
|
##
|
@@ -1659,11 +1619,12 @@ EOTEMPLATE
|
|
1659
1619
|
## @param times (Bool) Show times
|
1660
1620
|
## @param section (String) Section to pull from, default Currently
|
1661
1621
|
##
|
1662
|
-
def last(times=true,section=nil)
|
1622
|
+
def last(times = true, section = nil)
|
1663
1623
|
section ||= @current_section
|
1664
1624
|
section = guess_section(section)
|
1665
1625
|
cfg = @config['templates']['last']
|
1666
|
-
list_section({:
|
1626
|
+
list_section({ section: section, wrap_width: cfg['wrap_width'], count: 1, format: cfg['date_format'],
|
1627
|
+
template: cfg['template'], times: times })
|
1667
1628
|
end
|
1668
1629
|
|
1669
1630
|
##
|
@@ -1671,22 +1632,22 @@ EOTEMPLATE
|
|
1671
1632
|
##
|
1672
1633
|
## @param format (String) return format (html, json, or text)
|
1673
1634
|
##
|
1674
|
-
def tag_times(format=
|
1675
|
-
return
|
1635
|
+
def tag_times(format = 'text', sort_by_name = false)
|
1636
|
+
return '' if @timers.empty?
|
1676
1637
|
|
1677
|
-
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
|
1678
1639
|
|
1679
|
-
total = @timers.delete(
|
1640
|
+
total = @timers.delete('All')
|
1680
1641
|
|
1681
|
-
tags_data = @timers.delete_if { |
|
1682
|
-
if sort_by_name
|
1683
|
-
|
1684
|
-
|
1685
|
-
|
1686
|
-
|
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
|
1687
1648
|
|
1688
|
-
if format ==
|
1689
|
-
output
|
1649
|
+
if format == 'html'
|
1650
|
+
output = <<EOS
|
1690
1651
|
<table>
|
1691
1652
|
<caption id="tagtotals">Tag Totals</caption>
|
1692
1653
|
<colgroup>
|
@@ -1701,10 +1662,12 @@ EOTEMPLATE
|
|
1701
1662
|
</thead>
|
1702
1663
|
<tbody>
|
1703
1664
|
EOS
|
1704
|
-
sorted_tags_data.reverse.each
|
1705
|
-
|
1706
|
-
|
1707
|
-
|
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
|
1708
1671
|
<tr>
|
1709
1672
|
<td style="text-align:left;" colspan="2"></td>
|
1710
1673
|
</tr>
|
@@ -1712,34 +1675,34 @@ EOS
|
|
1712
1675
|
<tfoot>
|
1713
1676
|
<tr>
|
1714
1677
|
<td style="text-align:left;"><strong>Total</strong></td>
|
1715
|
-
<td style="text-align:left;">#{
|
1678
|
+
<td style="text-align:left;">#{'%02d:%02d:%02d' % fmt_time(total)}</td>
|
1716
1679
|
</tr>
|
1717
1680
|
</tfoot>
|
1718
1681
|
</table>
|
1719
1682
|
EOS
|
1720
1683
|
output + tail
|
1721
|
-
elsif format ==
|
1684
|
+
elsif format == 'json'
|
1722
1685
|
output = []
|
1723
|
-
sorted_tags_data.reverse.each
|
1686
|
+
sorted_tags_data.reverse.each do |k, v|
|
1724
1687
|
output << {
|
1725
1688
|
'tag' => k,
|
1726
1689
|
'seconds' => v,
|
1727
|
-
'formatted' =>
|
1690
|
+
'formatted' => '%02d:%02d:%02d' % fmt_time(v)
|
1728
1691
|
}
|
1729
|
-
|
1692
|
+
end
|
1730
1693
|
output
|
1731
1694
|
else
|
1732
1695
|
output = []
|
1733
|
-
sorted_tags_data.reverse.each
|
1734
|
-
spacer =
|
1696
|
+
sorted_tags_data.reverse.each do |k, v|
|
1697
|
+
spacer = ''
|
1735
1698
|
(max - k.length).times do
|
1736
|
-
spacer +=
|
1699
|
+
spacer += ' '
|
1737
1700
|
end
|
1738
|
-
output.push("#{k}:#{spacer}#{
|
1739
|
-
|
1701
|
+
output.push("#{k}:#{spacer}#{'%02d:%02d:%02d' % fmt_time(v)}")
|
1702
|
+
end
|
1740
1703
|
|
1741
|
-
output = output.empty? ?
|
1742
|
-
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"
|
1743
1706
|
output
|
1744
1707
|
end
|
1745
1708
|
end
|
@@ -1753,53 +1716,54 @@ EOS
|
|
1753
1716
|
def autotag(text)
|
1754
1717
|
return unless text
|
1755
1718
|
return text unless @auto_tag
|
1719
|
+
|
1756
1720
|
current_tags = text.scan(/@\w+/)
|
1757
1721
|
whitelisted = []
|
1758
|
-
@config['autotag']['whitelist'].each
|
1722
|
+
@config['autotag']['whitelist'].each do |tag|
|
1723
|
+
next if text =~ /@#{tag}\b/i
|
1724
|
+
|
1759
1725
|
text.sub!(/(?<!@)(#{tag.strip})\b/i) do |m|
|
1760
1726
|
m.downcase! if tag =~ /[a-z]/
|
1761
1727
|
whitelisted.push("@#{m}")
|
1762
1728
|
"@#{m}"
|
1763
|
-
end
|
1764
|
-
|
1729
|
+
end
|
1730
|
+
end
|
1765
1731
|
tail_tags = []
|
1766
|
-
@config['autotag']['synonyms'].each
|
1767
|
-
v.each
|
1768
|
-
|
1769
|
-
|
1770
|
-
|
1771
|
-
|
1772
|
-
|
1773
|
-
}
|
1774
|
-
}
|
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
|
1775
1739
|
if @config['autotag'].key? 'transform'
|
1776
|
-
@config['autotag']['transform'].each
|
1777
|
-
|
1778
|
-
|
1779
|
-
|
1780
|
-
|
1781
|
-
|
1782
|
-
|
1783
|
-
|
1784
|
-
|
1785
|
-
|
1786
|
-
|
1787
|
-
|
1788
|
-
|
1789
|
-
|
1790
|
-
|
1791
|
-
|
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
|
1792
1758
|
end
|
1793
|
-
|
1794
|
-
|
1759
|
+
end
|
1760
|
+
tail_tags.push(new_tag)
|
1795
1761
|
end
|
1796
|
-
|
1797
|
-
end
|
1798
|
-
if whitelisted.length > 0
|
1799
|
-
@results.push("Whitelisted tags: #{whitelisted.join(', ')}")
|
1762
|
+
end
|
1800
1763
|
end
|
1764
|
+
@results.push("Whitelisted tags: #{whitelisted.join(', ')}") if whitelisted.length > 0
|
1801
1765
|
if tail_tags.length > 0
|
1802
|
-
tags = tail_tags.uniq.map {|t| '@'+t }.join(' ')
|
1766
|
+
tags = tail_tags.uniq.map { |t| '@' + t }.join(' ')
|
1803
1767
|
@results.push("Synonym tags: #{tags}")
|
1804
1768
|
text + ' ' + tags
|
1805
1769
|
else
|
@@ -1815,43 +1779,43 @@ EOS
|
|
1815
1779
|
## @param item (Hash) The entry
|
1816
1780
|
## @param formatted (Bool) Return human readable time (default seconds)
|
1817
1781
|
##
|
1818
|
-
def get_interval(item, formatted=true)
|
1782
|
+
def get_interval(item, formatted = true)
|
1819
1783
|
done = nil
|
1820
1784
|
start = nil
|
1821
1785
|
|
1822
1786
|
if @interval_cache.keys.include? item['title']
|
1823
1787
|
seconds = @interval_cache[item['title']]
|
1824
|
-
return seconds > 0 ?
|
1788
|
+
return seconds > 0 ? '%02d:%02d:%02d' % fmt_time(seconds) : false
|
1825
1789
|
end
|
1826
1790
|
|
1827
1791
|
if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
|
1828
|
-
done = Time.parse(
|
1792
|
+
done = Time.parse(Regexp.last_match(1))
|
1829
1793
|
else
|
1830
1794
|
return nil
|
1831
1795
|
end
|
1832
1796
|
|
1833
|
-
if item['title'] =~ /@start\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
|
1834
|
-
|
1835
|
-
|
1836
|
-
|
1837
|
-
|
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
|
1838
1802
|
|
1839
1803
|
seconds = (done - start).to_i
|
1840
1804
|
|
1841
|
-
item['title'].scan(/(?mi)@(\S+?)(\(.*\))?(?=\s|$)/).each
|
1842
|
-
k = m[0] ==
|
1805
|
+
item['title'].scan(/(?mi)@(\S+?)(\(.*\))?(?=\s|$)/).each do |m|
|
1806
|
+
k = m[0] == 'done' ? 'All' : m[0].downcase
|
1843
1807
|
if @timers.has_key?(k)
|
1844
1808
|
@timers[k] += seconds
|
1845
1809
|
else
|
1846
1810
|
@timers[k] = seconds
|
1847
1811
|
end
|
1848
|
-
|
1812
|
+
end
|
1849
1813
|
|
1850
1814
|
@interval_cache[item['title']] = seconds
|
1851
1815
|
|
1852
1816
|
return seconds unless formatted
|
1853
1817
|
|
1854
|
-
seconds > 0 ?
|
1818
|
+
seconds > 0 ? '%02d:%02d:%02d' % fmt_time(seconds) : false
|
1855
1819
|
end
|
1856
1820
|
|
1857
1821
|
##
|
@@ -1860,19 +1824,19 @@ EOS
|
|
1860
1824
|
## @param seconds The seconds
|
1861
1825
|
##
|
1862
1826
|
def fmt_time(seconds)
|
1863
|
-
if seconds.nil?
|
1864
|
-
|
1865
|
-
end
|
1827
|
+
return [0, 0, 0] if seconds.nil?
|
1828
|
+
|
1866
1829
|
if seconds =~ /(\d+):(\d+):(\d+)/
|
1867
|
-
h
|
1830
|
+
h = Regexp.last_match(1)
|
1831
|
+
m = Regexp.last_match(2)
|
1832
|
+
s = Regexp.last_match(3)
|
1868
1833
|
seconds = (h.to_i * 60 * 60) + (m.to_i * 60) + s.to_i
|
1869
1834
|
end
|
1870
|
-
minutes =
|
1835
|
+
minutes = (seconds / 60).to_i
|
1871
1836
|
hours = (minutes / 60).to_i
|
1872
1837
|
days = (hours / 24).to_i
|
1873
1838
|
hours = (hours % 24).to_i
|
1874
1839
|
minutes = (minutes % 60).to_i
|
1875
1840
|
[days, hours, minutes]
|
1876
1841
|
end
|
1877
|
-
|
1878
1842
|
end
|