doing 1.0.45 → 1.0.46
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/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
|