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