doing 1.0.45 → 1.0.50
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 +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
|