doing 1.0.80 → 1.0.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +10 -4
- data/bin/doing +154 -29
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +222 -101
- data/lib/doing.rb +3 -2
- 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: c11e6c75768b8dc2db3a36c5cdd572ad03eb884e118848ebbe413df2988a53df
|
4
|
+
data.tar.gz: a3f45b396e4c6b06bab65d24007f0437e72687c6811f559ece8f6c5ca2e7bbba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7be6844f9f6caa072014afeded310cbaddf2a15380fde5498aea0f980dd5e8448ca44f5c5d9692a2075818f632bbbd19cce93e0db338c621a487abed8259f555
|
7
|
+
data.tar.gz: d0af857fee4113e2055af031059901b56e2ff94e849f9d3a0d167d8edde0519e4d6a6ebdeef55050ee313ed628ba6fa2fbd17e73cfc66143e57e7a2c42f93c8f
|
data/README.md
CHANGED
@@ -27,7 +27,7 @@ If there's something I want to look at later but doesn't need to be added to a t
|
|
27
27
|
|
28
28
|
## Installation
|
29
29
|
|
30
|
-
The current version of `doing` is <!--VER-->1.0.
|
30
|
+
The current version of `doing` is <!--VER-->1.0.83<!--END VER-->.
|
31
31
|
|
32
32
|
$ [sudo] gem install doing
|
33
33
|
|
@@ -245,7 +245,7 @@ You can create your own "views" in the `~/.doingrc` file and view them with `doi
|
|
245
245
|
|
246
246
|
views:
|
247
247
|
old:
|
248
|
-
section:
|
248
|
+
section: Archive
|
249
249
|
count: 5
|
250
250
|
wrap_width: 0
|
251
251
|
date_format: '%F %_I:%M%P'
|
@@ -253,6 +253,10 @@ You can create your own "views" in the `~/.doingrc` file and view them with `doi
|
|
253
253
|
order: asc
|
254
254
|
tags: done finished cancelled
|
255
255
|
tags_bool: ANY
|
256
|
+
only_timed: false
|
257
|
+
tag_sort: time
|
258
|
+
tag_order: asc
|
259
|
+
totals: true
|
256
260
|
|
257
261
|
You can add additional custom views. Just nest them under the `views` key (indented two spaces from the edge). Multiple views would look like this:
|
258
262
|
|
@@ -276,7 +280,9 @@ You can add new sections with `doing add_section section_name`. You can also cre
|
|
276
280
|
|
277
281
|
The `tags` and `tags_bool` keys allow you to specify tags that the view is filtered by. You can list multiple tags separated by spaces, and then use `tags_bool` to specify `ALL`, `ANY`, or `NONE` to determine how it handles the multiple tags.
|
278
282
|
|
279
|
-
The `order` key defines the sort order of the output. This is applied _after_ the tasks are retrieved and cut off at the maximum number specified in `count`.
|
283
|
+
The `order` key defines the sort order of the output (asc or desc). This is applied _after_ the tasks are retrieved and cut off at the maximum number specified in `count`.
|
284
|
+
|
285
|
+
You can include tag timers and totals in the output with `totals: true`. Control tag output using `tag_sort` (name or title) and `tag_order` (asc or desc). You can also output only timed entries using `only_timed: true`. All of these options can be overridden using flags on the `doing view` command.
|
280
286
|
|
281
287
|
Regarding colors, you can use them to create very nice displays if you're outputting to a color terminal. Example:
|
282
288
|
|
@@ -291,7 +297,7 @@ Outputs:
|
|
291
297
|
|
292
298
|

|
293
299
|
|
294
|
-
You can also specify a default output format for a view. Most of the optional output formats override the template specification (`html`, `csv`, `json`). If the `view` command is used with the `-o` flag, it will override what's specified in the
|
300
|
+
You can also specify a default output format for a view. Most of the optional output formats override the template specification (`html`, `csv`, `json`). If the `view` command is used with the `-o` flag, it will override what's specified for the view in the config.
|
295
301
|
|
296
302
|
### Colors
|
297
303
|
|
data/bin/doing
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
1
|
+
#!/usr/bin/env ruby -W1
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
$LOAD_PATH.unshift File.join(__dir__, '..', 'lib')
|
@@ -55,7 +55,7 @@ arg_name 'ENTRY'
|
|
55
55
|
command %i[now next] do |c|
|
56
56
|
c.desc 'Section'
|
57
57
|
c.arg_name 'NAME'
|
58
|
-
c.flag %i[s section]
|
58
|
+
c.flag %i[s section]
|
59
59
|
|
60
60
|
c.desc "Edit entry with #{ENV['EDITOR']}"
|
61
61
|
c.switch %i[e editor], negatable: false, default_value: false
|
@@ -84,7 +84,11 @@ command %i[now next] do |c|
|
|
84
84
|
date = Time.now
|
85
85
|
end
|
86
86
|
|
87
|
-
|
87
|
+
if options[:section]
|
88
|
+
section = wwid.guess_section(options[:section]) || options[:section].cap_first
|
89
|
+
else
|
90
|
+
options[:section] = wwid.config['current_section']
|
91
|
+
end
|
88
92
|
|
89
93
|
if options[:e] || (args.empty? && $stdin.stat.size.zero?)
|
90
94
|
exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
|
@@ -135,7 +139,9 @@ command :note do |c|
|
|
135
139
|
c.switch %i[r remove], negatable: false, default_value: false
|
136
140
|
|
137
141
|
c.action do |_global_options, options, args|
|
138
|
-
|
142
|
+
if options[:section]
|
143
|
+
section = wwid.guess_section(options[:section]) || options[:section].cap_first
|
144
|
+
end
|
139
145
|
|
140
146
|
if options[:e] || (args.empty? && $stdin.stat.size.zero? && !options[:r])
|
141
147
|
exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
|
@@ -176,7 +182,7 @@ arg_name 'ENTRY'
|
|
176
182
|
command :meanwhile do |c|
|
177
183
|
c.desc 'Section'
|
178
184
|
c.arg_name 'NAME'
|
179
|
-
c.flag %i[s section]
|
185
|
+
c.flag %i[s section]
|
180
186
|
|
181
187
|
c.desc "Edit entry with #{ENV['EDITOR']}"
|
182
188
|
c.switch %i[e editor], negatable: false, default_value: false
|
@@ -201,7 +207,11 @@ command :meanwhile do |c|
|
|
201
207
|
date = Time.now
|
202
208
|
end
|
203
209
|
|
204
|
-
|
210
|
+
if options[:section]
|
211
|
+
section = wwid.guess_section(options[:section]) || options[:section].cap_first
|
212
|
+
else
|
213
|
+
section = wwid.config['current_section']
|
214
|
+
end
|
205
215
|
input = ''
|
206
216
|
|
207
217
|
if options[:e]
|
@@ -301,11 +311,14 @@ command :select do |c|
|
|
301
311
|
c.desc 'Add flag to selected item(s)'
|
302
312
|
c.switch %i[flag], negatable: false, default_value: false
|
303
313
|
|
314
|
+
c.desc 'Perform action without confirmation'
|
315
|
+
c.switch %i[force], negatable: false, default_value: false
|
316
|
+
|
304
317
|
c.desc 'Save selected entries to file using --output format'
|
305
318
|
c.arg_name 'FILE'
|
306
319
|
c.flag %i[save_to]
|
307
320
|
|
308
|
-
c.desc 'Output
|
321
|
+
c.desc 'Output entries to format (doing|taskpaper|csv|html|json|template|timeline)'
|
309
322
|
c.arg_name 'FORMAT'
|
310
323
|
c.flag %i[o output], must_match: /^(?:doing|taskpaper|html|csv|json|template|timeline)$/i
|
311
324
|
|
@@ -396,7 +409,7 @@ command %i[done did] do |c|
|
|
396
409
|
|
397
410
|
c.desc 'Section'
|
398
411
|
c.arg_name 'NAME'
|
399
|
-
c.flag %i[s section]
|
412
|
+
c.flag %i[s section]
|
400
413
|
|
401
414
|
c.desc "Edit entry with #{ENV['EDITOR']}"
|
402
415
|
c.switch %i[e editor], negatable: false, default_value: false
|
@@ -437,7 +450,11 @@ command %i[done did] do |c|
|
|
437
450
|
donedate = options[:date] ? "(#{finish_date.strftime('%F %R')})" : ''
|
438
451
|
end
|
439
452
|
|
440
|
-
|
453
|
+
if options[:section]
|
454
|
+
section = wwid.guess_section(options[:section]) || options[:section].cap_first
|
455
|
+
else
|
456
|
+
section = wwid.config['current_section']
|
457
|
+
end
|
441
458
|
|
442
459
|
if options[:e]
|
443
460
|
exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
|
@@ -494,7 +511,7 @@ command :cancel do |c|
|
|
494
511
|
|
495
512
|
c.desc 'Section'
|
496
513
|
c.arg_name 'NAME'
|
497
|
-
c.flag %i[s section]
|
514
|
+
c.flag %i[s section]
|
498
515
|
|
499
516
|
c.desc 'Cancel the last X entries containing TAG. Separate multiple tags with comma (--tag=tag1,tag2)'
|
500
517
|
c.arg_name 'TAG'
|
@@ -508,7 +525,11 @@ command :cancel do |c|
|
|
508
525
|
c.switch %i[u unfinished], negatable: false, default_value: false
|
509
526
|
|
510
527
|
c.action do |_global_options, options, args|
|
511
|
-
|
528
|
+
if options[:section]
|
529
|
+
section = wwid.guess_section(options[:section]) || options[:section].cap_first
|
530
|
+
else
|
531
|
+
section = wwid.config['current_section']
|
532
|
+
end
|
512
533
|
|
513
534
|
if options[:tag].nil?
|
514
535
|
tags = []
|
@@ -561,6 +582,10 @@ command :finish do |c|
|
|
561
582
|
c.arg_name 'INTERVAL'
|
562
583
|
c.flag %i[t took]
|
563
584
|
|
585
|
+
c.desc %(Set finish date to specific date/time (natural langauge parsed, e.g. --at=1:30pm). If used, ignores --back.)
|
586
|
+
c.arg_name 'DATE_STRING'
|
587
|
+
c.flag [:at]
|
588
|
+
|
564
589
|
c.desc 'Finish the last X entries containing TAG.
|
565
590
|
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool'
|
566
591
|
c.arg_name 'TAG'
|
@@ -587,17 +612,31 @@ command :finish do |c|
|
|
587
612
|
|
588
613
|
c.desc 'Section'
|
589
614
|
c.arg_name 'NAME'
|
590
|
-
c.flag %i[s section]
|
615
|
+
c.flag %i[s section]
|
591
616
|
|
592
617
|
c.action do |_global_options, options, args|
|
593
|
-
|
618
|
+
if options[:section]
|
619
|
+
section = wwid.guess_section(options[:section]) || options[:section].cap_first
|
620
|
+
else
|
621
|
+
section = wwid.config['current_section']
|
622
|
+
end
|
594
623
|
|
595
624
|
unless options[:auto]
|
625
|
+
if options[:took]
|
626
|
+
took = wwid.chronify_qty(options[:took])
|
627
|
+
exit_now! 'Unable to parse date string for --took' if took.nil?
|
628
|
+
end
|
629
|
+
|
596
630
|
exit_now! '--back and --took cannot be used together' if options[:back] && options[:took]
|
597
631
|
|
598
632
|
exit_now! '--search and --tag cannot be used together' if options[:search] && options[:tag]
|
599
633
|
|
600
|
-
if options[:
|
634
|
+
if options[:at]
|
635
|
+
finish_date = wwid.chronify(options[:at])
|
636
|
+
exit_now! 'Unable to parse date string for --at' if finish_date.nil?
|
637
|
+
|
638
|
+
date = options[:took] ? finish_date - took : finish_date
|
639
|
+
elsif options[:back]
|
601
640
|
date = wwid.chronify(options[:back])
|
602
641
|
|
603
642
|
exit_now! 'Unable to parse date string' if date.nil?
|
@@ -823,7 +862,7 @@ desc 'Mark last entry as highlighted'
|
|
823
862
|
command [:mark, :flag] do |c|
|
824
863
|
c.desc 'Section'
|
825
864
|
c.arg_name 'NAME'
|
826
|
-
c.flag %i[s section]
|
865
|
+
c.flag %i[s section]
|
827
866
|
|
828
867
|
c.desc 'Remove mark'
|
829
868
|
c.switch %i[r remove], negatable: false, default_value: false
|
@@ -1306,7 +1345,8 @@ desc 'Select a section to display from a menu'
|
|
1306
1345
|
command :choose do |c|
|
1307
1346
|
c.action do |_global_options, _options, _args|
|
1308
1347
|
section = wwid.choose_section
|
1309
|
-
|
1348
|
+
|
1349
|
+
puts wwid.list_section({ section: section.cap_first, count: 0 }) if section
|
1310
1350
|
end
|
1311
1351
|
end
|
1312
1352
|
|
@@ -1340,13 +1380,14 @@ command :colors do |c|
|
|
1340
1380
|
end
|
1341
1381
|
|
1342
1382
|
desc 'Display a user-created view'
|
1383
|
+
long_desc 'Command line options override associated view settings'
|
1343
1384
|
arg_name 'VIEW_NAME'
|
1344
1385
|
command :view do |c|
|
1345
|
-
c.desc 'Section
|
1386
|
+
c.desc 'Section'
|
1346
1387
|
c.arg_name 'NAME'
|
1347
1388
|
c.flag %i[s section]
|
1348
1389
|
|
1349
|
-
c.desc 'Count to display
|
1390
|
+
c.desc 'Count to display'
|
1350
1391
|
c.arg_name 'COUNT'
|
1351
1392
|
c.flag %i[c count], must_match: /^\d+$/, type: Integer
|
1352
1393
|
|
@@ -1364,12 +1405,14 @@ command :view do |c|
|
|
1364
1405
|
c.switch [:color], default_value: true, negatable: true
|
1365
1406
|
|
1366
1407
|
c.desc 'Sort tags by (name|time)'
|
1367
|
-
default = 'time'
|
1368
|
-
default = wwid.config['tag_sort'] if wwid.config.key?('tag_sort')
|
1369
1408
|
c.arg_name 'KEY'
|
1370
|
-
c.flag [:tag_sort], must_match: /^(?:name|time)$/i
|
1409
|
+
c.flag [:tag_sort], must_match: /^(?:name|time)$/i
|
1371
1410
|
|
1372
|
-
c.desc '
|
1411
|
+
c.desc 'Tag sort direction (asc|desc)'
|
1412
|
+
c.arg_name 'DIRECTION'
|
1413
|
+
c.flag [:tag_order], must_match: /^(?:a(?:sc)?|d(?:esc)?)$/i
|
1414
|
+
|
1415
|
+
c.desc 'Only show items with recorded time intervals (override view settings)'
|
1373
1416
|
c.switch [:only_timed], default_value: false, negatable: false
|
1374
1417
|
|
1375
1418
|
c.action do |_global_options, options, args|
|
@@ -1379,7 +1422,11 @@ command :view do |c|
|
|
1379
1422
|
wwid.guess_view(args[0])
|
1380
1423
|
end
|
1381
1424
|
|
1382
|
-
|
1425
|
+
if options[:section]
|
1426
|
+
section = wwid.guess_section(options[:section]) || options[:section].cap_first
|
1427
|
+
else
|
1428
|
+
section = wwid.config['current_section']
|
1429
|
+
end
|
1383
1430
|
|
1384
1431
|
view = wwid.get_view(title)
|
1385
1432
|
if view
|
@@ -1418,10 +1465,31 @@ command :view do |c|
|
|
1418
1465
|
end
|
1419
1466
|
order = view.key?('order') ? view['order'] : 'asc'
|
1420
1467
|
|
1421
|
-
|
1468
|
+
totals = if options[:totals]
|
1469
|
+
true
|
1470
|
+
else
|
1471
|
+
view.key?('totals') ? view['totals'] : false
|
1472
|
+
end
|
1473
|
+
|
1474
|
+
options[:t] = true if totals
|
1422
1475
|
options[:output]&.downcase!
|
1423
|
-
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
1424
1476
|
|
1477
|
+
options[:sort_tags] = if options[:tag_sort]
|
1478
|
+
options[:tag_sort] =~ /^n/i ? true : false
|
1479
|
+
elsif view.key?('tag_sort')
|
1480
|
+
view['tag_sort'] =~ /^n/i ? true : false
|
1481
|
+
else
|
1482
|
+
false
|
1483
|
+
end
|
1484
|
+
|
1485
|
+
tag_order = if options[:tag_order]
|
1486
|
+
options[:tag_order] =~ /^d/i ? 'desc' : 'asc'
|
1487
|
+
elsif view.key?('tag_order')
|
1488
|
+
view['tag_order'] =~ /^d/i ? 'desc' : 'asc'
|
1489
|
+
else
|
1490
|
+
'asc'
|
1491
|
+
end
|
1492
|
+
warn "TAG ORDER: #{options[:tag_order]}"
|
1425
1493
|
opts = {
|
1426
1494
|
count: count,
|
1427
1495
|
format: format,
|
@@ -1432,10 +1500,11 @@ command :view do |c|
|
|
1432
1500
|
section: section,
|
1433
1501
|
sort_tags: options[:sort_tags],
|
1434
1502
|
tag_filter: tag_filter,
|
1503
|
+
tag_order: tag_order,
|
1435
1504
|
tags_color: tags_color,
|
1436
1505
|
template: template,
|
1437
1506
|
times: options[:t],
|
1438
|
-
totals:
|
1507
|
+
totals: totals
|
1439
1508
|
}
|
1440
1509
|
|
1441
1510
|
puts wwid.list_section(opts)
|
@@ -1524,6 +1593,48 @@ command :archive do |c|
|
|
1524
1593
|
end
|
1525
1594
|
end
|
1526
1595
|
|
1596
|
+
desc 'Move entries to archive file'
|
1597
|
+
command :rotate do |c|
|
1598
|
+
c.desc 'How many items to keep in each section (most recent)'
|
1599
|
+
c.arg_name 'X'
|
1600
|
+
c.flag %i[k keep], must_match: /^\d+$/, type: Integer
|
1601
|
+
|
1602
|
+
c.desc 'Section to rotate'
|
1603
|
+
c.arg_name 'SECTION_NAME'
|
1604
|
+
c.flag %i[s section], default_value: 'All'
|
1605
|
+
|
1606
|
+
c.desc 'Tag filter, combine multiple tags with a comma. Added for compatibility with other commands.'
|
1607
|
+
c.arg_name 'TAG'
|
1608
|
+
c.flag [:tag]
|
1609
|
+
|
1610
|
+
c.desc 'Tag boolean (AND|OR|NOT)'
|
1611
|
+
c.arg_name 'BOOLEAN'
|
1612
|
+
c.flag [:bool], must_match: /(?:and|all|any|or|not|none)/i, default_value: 'AND'
|
1613
|
+
|
1614
|
+
c.desc 'Search filter'
|
1615
|
+
c.arg_name 'QUERY'
|
1616
|
+
c.flag [:search]
|
1617
|
+
|
1618
|
+
c.action do |_global_options, options, args|
|
1619
|
+
if options[:section] && options[:section] !~ /^all$/i
|
1620
|
+
options[:section] = wwid.guess_section(options[:section])
|
1621
|
+
end
|
1622
|
+
|
1623
|
+
options[:bool] = case options[:bool]
|
1624
|
+
when /(and|all)/i
|
1625
|
+
'AND'
|
1626
|
+
when /(any|or)/i
|
1627
|
+
'OR'
|
1628
|
+
when /(not|none)/i
|
1629
|
+
'NOT'
|
1630
|
+
else
|
1631
|
+
'AND'
|
1632
|
+
end
|
1633
|
+
|
1634
|
+
wwid.rotate(options)
|
1635
|
+
end
|
1636
|
+
end
|
1637
|
+
|
1527
1638
|
desc 'Open the "doing" file in an editor'
|
1528
1639
|
long_desc "`doing open` defaults to using the editor_app setting in #{wwid.config_file} (#{wwid.config.key?('editor_app') ? wwid.config['editor_app'] : 'not set'})"
|
1529
1640
|
command :open do |c|
|
@@ -1633,12 +1744,15 @@ command :import do |c|
|
|
1633
1744
|
|
1634
1745
|
c.desc 'Target section'
|
1635
1746
|
c.arg_name 'NAME'
|
1636
|
-
c.flag %i[s section]
|
1747
|
+
c.flag %i[s section]
|
1637
1748
|
|
1638
1749
|
c.desc 'Tag all imported entries'
|
1639
1750
|
c.arg_name 'TAGS'
|
1640
1751
|
c.flag :tag
|
1641
1752
|
|
1753
|
+
c.desc 'Autotag entries'
|
1754
|
+
c.switch :autotag, negatable: true, default_value: true
|
1755
|
+
|
1642
1756
|
c.desc 'Prefix entries with'
|
1643
1757
|
c.arg_name 'PREFIX'
|
1644
1758
|
c.flag :prefix
|
@@ -1648,11 +1762,22 @@ command :import do |c|
|
|
1648
1762
|
|
1649
1763
|
c.action do |_global_options, options, args|
|
1650
1764
|
|
1651
|
-
|
1765
|
+
if options[:section]
|
1766
|
+
section = wwid.guess_section(options[:section]) || options[:section].cap_first
|
1767
|
+
else
|
1768
|
+
section = wwid.config['current_section']
|
1769
|
+
end
|
1652
1770
|
|
1653
1771
|
if options[:type] =~ /^tim/i
|
1654
1772
|
args.each do |path|
|
1655
|
-
|
1773
|
+
options = {
|
1774
|
+
autotag: options[:autotag],
|
1775
|
+
no_overlap: !options[:overlap],
|
1776
|
+
prefix: options[:prefix],
|
1777
|
+
section: section,
|
1778
|
+
tag: options[:tag]
|
1779
|
+
}
|
1780
|
+
wwid.import_timing(path, options)
|
1656
1781
|
wwid.write(wwid.doing_file)
|
1657
1782
|
end
|
1658
1783
|
else
|
data/lib/doing/version.rb
CHANGED
data/lib/doing/wwid.rb
CHANGED
@@ -84,6 +84,7 @@ class WWID
|
|
84
84
|
##
|
85
85
|
def configure(opt = {})
|
86
86
|
@timers = {}
|
87
|
+
@recorded_items = []
|
87
88
|
opt[:ignore_local] ||= false
|
88
89
|
|
89
90
|
@config_file ||= File.join(@user_home, @default_config_file)
|
@@ -424,8 +425,8 @@ class WWID
|
|
424
425
|
## @param guessed (Boolean) already guessed and failed
|
425
426
|
##
|
426
427
|
def guess_section(frag, guessed: false)
|
427
|
-
return 'All' if frag =~
|
428
|
-
|
428
|
+
return 'All' if frag =~ /^all$/i
|
429
|
+
frag ||= @current_section
|
429
430
|
sections.each { |section| return section.cap_first if frag.downcase == section.downcase }
|
430
431
|
section = false
|
431
432
|
re = frag.split('').join('.*?')
|
@@ -583,17 +584,17 @@ class WWID
|
|
583
584
|
end
|
584
585
|
|
585
586
|
def same_time?(item_a, item_b)
|
586
|
-
item_a['date'] == item_b['date'] ? get_interval(item_a, false) == get_interval(item_b, false) : false
|
587
|
+
item_a['date'] == item_b['date'] ? get_interval(item_a, formatted: false, record: false) == get_interval(item_b, formatted: false, record: false) : false
|
587
588
|
end
|
588
589
|
|
589
590
|
def overlapping_time?(item_a, item_b)
|
590
591
|
return true if same_time?(item_a, item_b)
|
591
592
|
|
592
593
|
start_a = item_a['date']
|
593
|
-
interval = get_interval(item_a, false)
|
594
|
+
interval = get_interval(item_a, formatted: false, record: false)
|
594
595
|
end_a = interval ? start_a + interval.to_i : start_a
|
595
596
|
start_b = item_b['date']
|
596
|
-
interval = get_interval(item_b, false)
|
597
|
+
interval = get_interval(item_b, formatted: false, record: false)
|
597
598
|
end_b = interval ? start_b + interval.to_i : start_b
|
598
599
|
(start_a >= start_b && start_a <= end_b) || (end_a >= start_b && end_a <= end_b) || (start_a < start_b && end_a > end_b)
|
599
600
|
end
|
@@ -626,6 +627,7 @@ class WWID
|
|
626
627
|
def import_timing(path, opt = {})
|
627
628
|
section = opt[:section] || @current_section
|
628
629
|
opt[:no_overlap] ||= false
|
630
|
+
opt[:autotag] ||= @auto_tag
|
629
631
|
|
630
632
|
add_section(section) unless @content.has_key?(section)
|
631
633
|
|
@@ -656,7 +658,7 @@ class WWID
|
|
656
658
|
title += " @#{tag}"
|
657
659
|
end
|
658
660
|
end
|
659
|
-
title = autotag(title) if
|
661
|
+
title = autotag(title) if opt[:autotag]
|
660
662
|
title += " @done(#{end_time.strftime('%Y-%m-%d %H:%M')})"
|
661
663
|
title.gsub!(/ +/, ' ')
|
662
664
|
title.strip!
|
@@ -757,7 +759,7 @@ class WWID
|
|
757
759
|
all_items.concat(@content[section]['items'].dup) if @content.key?(section)
|
758
760
|
end
|
759
761
|
|
760
|
-
if opt[:tag]
|
762
|
+
if opt[:tag]&.length
|
761
763
|
all_items.select! { |item| item.has_tags?(opt[:tag], opt[:tag_bool]) }
|
762
764
|
elsif opt[:search]&.length
|
763
765
|
all_items.select! { |item| item.matches_search?(opt[:search]) }
|
@@ -771,9 +773,14 @@ class WWID
|
|
771
773
|
##
|
772
774
|
## @return (String) The selected option
|
773
775
|
##
|
774
|
-
def choose_from(options, prompt)
|
776
|
+
def choose_from(options, prompt: 'Make a selection: ', multiple: false, fzf_args: [])
|
775
777
|
fzf = File.join(File.dirname(__FILE__), '../helpers/fuzzyfilefinder')
|
776
|
-
|
778
|
+
fzf_args << '-1'
|
779
|
+
fzf_args << %(--prompt "#{prompt}")
|
780
|
+
fzf_args << '--multi' if multiple
|
781
|
+
header = "esc: cancel,#{multiple ? ' tab: multi-select, ctrl-a: select all,' : ''} return: confirm"
|
782
|
+
fzf_args << %(--header "#{header}")
|
783
|
+
res = `echo #{Shellwords.escape(options.join("\n"))}|#{fzf} #{fzf_args.join(' ')}`
|
777
784
|
return false if res.strip.size.zero?
|
778
785
|
|
779
786
|
res
|
@@ -807,7 +814,7 @@ class WWID
|
|
807
814
|
') ',
|
808
815
|
item['date'],
|
809
816
|
' | ',
|
810
|
-
item['title']
|
817
|
+
item['title']
|
811
818
|
]
|
812
819
|
if opt[:section] =~ /^all/i
|
813
820
|
out.concat([
|
@@ -818,8 +825,15 @@ class WWID
|
|
818
825
|
end
|
819
826
|
out.join('')
|
820
827
|
end
|
821
|
-
|
822
|
-
|
828
|
+
fzf_args = [
|
829
|
+
%(--header="Arrows: navigate, tab: mark for selection, ctrl-a: select all, enter: commit"),
|
830
|
+
%(--prompt="Select entries to act on > "),
|
831
|
+
'-1',
|
832
|
+
'-m',
|
833
|
+
'--bind ctrl-a:select-all',
|
834
|
+
%(-q "#{opt[:query]}")
|
835
|
+
]
|
836
|
+
res = `echo #{Shellwords.escape(options.join("\n"))}|#{fzf} #{fzf_args.join(' ')}`
|
823
837
|
selected = []
|
824
838
|
res.split(/\n/).each do |item|
|
825
839
|
idx = item.match(/^(\d+)\)/)[1].to_i
|
@@ -841,62 +855,71 @@ class WWID
|
|
841
855
|
end
|
842
856
|
|
843
857
|
unless has_action
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
opt[:
|
858
|
+
choice = choose_from([
|
859
|
+
'add tag',
|
860
|
+
'remove tag',
|
861
|
+
'cancel',
|
862
|
+
'delete',
|
863
|
+
'finish',
|
864
|
+
'flag',
|
865
|
+
'archive',
|
866
|
+
'move',
|
867
|
+
'edit',
|
868
|
+
'output formatted'
|
869
|
+
],
|
870
|
+
prompt: 'What do you want to do with the selected items? > ',
|
871
|
+
multiple: true,
|
872
|
+
fzf_args: ['--height=60%', '--tac', '--no-sort'])
|
873
|
+
return unless choice
|
874
|
+
|
875
|
+
to_do = choice.strip.split(/\n/)
|
876
|
+
to_do.each do |action|
|
877
|
+
case action
|
878
|
+
when /(add|remove) tag/
|
879
|
+
type = action =~ /^add/ ? 'add' : 'remove'
|
880
|
+
if opt[:tag]
|
881
|
+
warn "'add tag' and 'remove tag' can not be used together"
|
882
|
+
Process.exit 1
|
883
|
+
end
|
884
|
+
print "#{colors['yellow']}Tag to #{type}: #{colors['reset']}"
|
885
|
+
tag = STDIN.gets
|
886
|
+
return if tag =~ /^ *$/
|
887
|
+
opt[:tag] = tag.strip.sub(/^@/, '')
|
888
|
+
opt[:remove] = true if type == 'remove'
|
889
|
+
when /output formatted/
|
890
|
+
output_format = choose_from(%w[doing taskpaper json timeline html csv].sort, prompt: 'Which output format? > ', fzf_args: ['--height=60%', '--tac', '--no-sort'])
|
891
|
+
return if tag =~ /^ *$/
|
892
|
+
opt[:output] = output_format.strip
|
893
|
+
res = opt[:force] ? false : yn('Save to file?', default_response: 'n')
|
894
|
+
if res
|
895
|
+
print "#{colors['yellow']}File path/name: #{colors['reset']}"
|
896
|
+
filename = STDIN.gets.strip
|
897
|
+
return if filename.empty?
|
898
|
+
opt[:save_to] = filename
|
899
|
+
end
|
900
|
+
when /archive/
|
901
|
+
opt[:archive] = true
|
902
|
+
when /delete/
|
903
|
+
opt[:delete] = true
|
904
|
+
when /edit/
|
905
|
+
opt[:editor] = true
|
906
|
+
when /finish/
|
907
|
+
opt[:finish] = true
|
908
|
+
when /cancel/
|
909
|
+
opt[:cancel] = true
|
910
|
+
when /move/
|
911
|
+
section = choose_section.strip
|
912
|
+
opt[:move] = section.strip unless section =~ /^ *$/
|
913
|
+
when /flag/
|
914
|
+
opt[:flag] = true
|
875
915
|
end
|
876
|
-
when /archive/
|
877
|
-
opt[:archive] = true
|
878
|
-
when /delete/
|
879
|
-
opt[:delete] = true
|
880
|
-
when /edit/
|
881
|
-
opt[:editor] = true
|
882
|
-
when /finish/
|
883
|
-
opt[:finish] = true
|
884
|
-
when /cancel/
|
885
|
-
opt[:cancel] = true
|
886
|
-
when /move/
|
887
|
-
section = choose_section.strip
|
888
|
-
return if section =~ /^ *$/
|
889
|
-
opt[:move] = section.strip
|
890
|
-
when /flag/
|
891
|
-
opt[:flag] = true
|
892
916
|
end
|
893
917
|
end
|
894
918
|
|
895
|
-
|
896
919
|
if opt[:delete]
|
897
|
-
res = yn("Delete #{selected.size} items?", default_response: 'y')
|
920
|
+
res = opt[:force] ? true : yn("Delete #{selected.size} items?", default_response: 'y')
|
898
921
|
if res
|
899
|
-
selected.each {|item| delete_item(item) }
|
922
|
+
selected.each { |item| delete_item(item) }
|
900
923
|
write(@doing_file)
|
901
924
|
end
|
902
925
|
return
|
@@ -990,13 +1013,16 @@ class WWID
|
|
990
1013
|
item
|
991
1014
|
end
|
992
1015
|
|
993
|
-
@content = {'Export' => {'original' => 'Export:', 'items' => selected}}
|
994
|
-
options = {section: 'Export'}
|
1016
|
+
@content = { 'Export' => { 'original' => 'Export:', 'items' => selected } }
|
1017
|
+
options = { section: 'Export' }
|
995
1018
|
|
996
|
-
|
997
|
-
|
998
|
-
else
|
1019
|
+
case opt[:output]
|
1020
|
+
when /doing/
|
999
1021
|
options[:template] = '- %date | %title%note'
|
1022
|
+
when /taskpaper/
|
1023
|
+
options[:template] = '- %title @date(%date)%note'
|
1024
|
+
else
|
1025
|
+
options[:output] = opt[:output]
|
1000
1026
|
end
|
1001
1027
|
|
1002
1028
|
output = list_section(options)
|
@@ -1078,7 +1104,6 @@ class WWID
|
|
1078
1104
|
count = (opt[:count]).zero? ? items.length : opt[:count]
|
1079
1105
|
items.map! do |item|
|
1080
1106
|
break if idx == count
|
1081
|
-
|
1082
1107
|
finished = opt[:unfinished] && item.has_tags?('done', :and)
|
1083
1108
|
tag_match = opt[:tag].nil? || opt[:tag].empty? ? true : item.has_tags?(opt[:tag], opt[:tag_bool])
|
1084
1109
|
search_match = opt[:search].nil? || opt[:search].empty? ? true : item.matches_search?(opt[:search])
|
@@ -1497,13 +1522,85 @@ class WWID
|
|
1497
1522
|
end
|
1498
1523
|
end
|
1499
1524
|
|
1525
|
+
##
|
1526
|
+
## @brief Rename doing file with date and start fresh one
|
1527
|
+
##
|
1528
|
+
def rotate(opt = {})
|
1529
|
+
count = opt[:keep] || 0
|
1530
|
+
tags = []
|
1531
|
+
tags.concat(opt[:tag].split(/ *, */).map { |t| t.sub(/^@/, '').strip }) if opt[:tag]
|
1532
|
+
bool = opt[:bool] || :and
|
1533
|
+
sect = opt[:section] !~ /^all$/i ? guess_section(opt[:section]) : 'all'
|
1534
|
+
|
1535
|
+
if sect =~ /^all$/i
|
1536
|
+
all_sections = sections.dup
|
1537
|
+
else
|
1538
|
+
all_sections = [sect]
|
1539
|
+
end
|
1540
|
+
|
1541
|
+
counter = 0
|
1542
|
+
new_content = {}
|
1543
|
+
|
1544
|
+
|
1545
|
+
all_sections.each do |section|
|
1546
|
+
items = @content[section]['items'].dup
|
1547
|
+
new_content[section] = {}
|
1548
|
+
new_content[section]['original'] = @content[section]['original']
|
1549
|
+
new_content[section]['items'] = []
|
1550
|
+
|
1551
|
+
moved_items = []
|
1552
|
+
if !tags.empty? || opt[:search]
|
1553
|
+
items.delete_if do |item|
|
1554
|
+
if ((!tags.empty? && item.has_tags?(tags, bool)) || (opt[:search] && item.matches_search?(opt[:search].to_s)))
|
1555
|
+
moved_items.push(item)
|
1556
|
+
counter += 1
|
1557
|
+
true
|
1558
|
+
else
|
1559
|
+
false
|
1560
|
+
end
|
1561
|
+
end
|
1562
|
+
@content[section]['items'] = items
|
1563
|
+
new_content[section]['items'] = moved_items
|
1564
|
+
@results.push("Rotated #{moved_items.length} items from #{section}")
|
1565
|
+
else
|
1566
|
+
new_content[section]['items'] = []
|
1567
|
+
moved_items = []
|
1568
|
+
|
1569
|
+
count = items.length if items.length < count
|
1570
|
+
|
1571
|
+
if items.count > count
|
1572
|
+
moved_items.concat(items[count..-1])
|
1573
|
+
else
|
1574
|
+
moved_items.concat(items)
|
1575
|
+
end
|
1576
|
+
|
1577
|
+
@content[section]['items'] = if count.zero?
|
1578
|
+
[]
|
1579
|
+
else
|
1580
|
+
items[0..count - 1]
|
1581
|
+
end
|
1582
|
+
new_content[section]['items'] = moved_items
|
1583
|
+
|
1584
|
+
@results.push("Rotated #{items.length - count} items from #{section}")
|
1585
|
+
end
|
1586
|
+
end
|
1587
|
+
|
1588
|
+
write(@doing_file)
|
1589
|
+
|
1590
|
+
file = @doing_file.sub(/(\.\w+)$/, "_#{Time.now.strftime('%Y-%m-%d%H:%M')}\\1")
|
1591
|
+
@content = new_content
|
1592
|
+
|
1593
|
+
write(file)
|
1594
|
+
end
|
1595
|
+
|
1500
1596
|
##
|
1501
1597
|
## @brief Generate a menu of sections and allow user selection
|
1502
1598
|
##
|
1503
1599
|
## @return (String) The selected section name
|
1504
1600
|
##
|
1505
1601
|
def choose_section
|
1506
|
-
choose_from(sections, 'Choose a section > ')
|
1602
|
+
choice = choose_from(sections.sort, prompt: 'Choose a section > ', fzf_args: ['--height=60%'])
|
1603
|
+
choice ? choice.strip : choice
|
1507
1604
|
end
|
1508
1605
|
|
1509
1606
|
##
|
@@ -1521,7 +1618,8 @@ class WWID
|
|
1521
1618
|
## @return (String) The selected view name
|
1522
1619
|
##
|
1523
1620
|
def choose_view
|
1524
|
-
choose_from(views, 'Choose a view > ')
|
1621
|
+
choice = choose_from(views.sort, prompt: 'Choose a view > ', fzf_args: ['--height=60%'])
|
1622
|
+
choice ? choice.strip : choice
|
1525
1623
|
end
|
1526
1624
|
|
1527
1625
|
##
|
@@ -1544,20 +1642,21 @@ class WWID
|
|
1544
1642
|
def list_section(opt = {})
|
1545
1643
|
opt[:count] ||= 0
|
1546
1644
|
count = opt[:count] - 1
|
1547
|
-
opt[:section] ||= nil
|
1548
|
-
opt[:format] ||= @default_date_format
|
1549
|
-
opt[:template] ||= @default_template
|
1550
1645
|
opt[:age] ||= 'newest'
|
1646
|
+
opt[:date_filter] ||= []
|
1647
|
+
opt[:format] ||= @default_date_format
|
1648
|
+
opt[:only_timed] ||= false
|
1551
1649
|
opt[:order] ||= 'desc'
|
1552
|
-
opt[:
|
1650
|
+
opt[:search] ||= false
|
1651
|
+
opt[:section] ||= nil
|
1652
|
+
opt[:sort_tags] ||= false
|
1553
1653
|
opt[:tag_filter] ||= false
|
1654
|
+
opt[:tag_order] ||= 'asc'
|
1554
1655
|
opt[:tags_color] ||= false
|
1656
|
+
opt[:template] ||= @default_template
|
1555
1657
|
opt[:times] ||= false
|
1658
|
+
opt[:today] ||= false
|
1556
1659
|
opt[:totals] ||= false
|
1557
|
-
opt[:sort_tags] ||= false
|
1558
|
-
opt[:search] ||= false
|
1559
|
-
opt[:only_timed] ||= false
|
1560
|
-
opt[:date_filter] ||= []
|
1561
1660
|
|
1562
1661
|
# opt[:highlight] ||= true
|
1563
1662
|
section = ''
|
@@ -1610,7 +1709,7 @@ class WWID
|
|
1610
1709
|
|
1611
1710
|
if opt[:only_timed]
|
1612
1711
|
items.delete_if do |item|
|
1613
|
-
get_interval(item) == false
|
1712
|
+
get_interval(item, record: false) == false
|
1614
1713
|
end
|
1615
1714
|
end
|
1616
1715
|
|
@@ -1645,7 +1744,7 @@ class WWID
|
|
1645
1744
|
arr = i['note'].map { |line| line.strip }.delete_if { |e| e =~ /^\s*$/ }
|
1646
1745
|
note = arr.join("\n") unless arr.nil?
|
1647
1746
|
end
|
1648
|
-
interval = get_interval(i, false) if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
1747
|
+
interval = get_interval(i, formatted: false) if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
1649
1748
|
interval ||= 0
|
1650
1749
|
output.push(CSV.generate_line([i['date'], i['title'], note, interval, i['section']]))
|
1651
1750
|
end
|
@@ -1664,7 +1763,7 @@ class WWID
|
|
1664
1763
|
end
|
1665
1764
|
if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
1666
1765
|
end_date = Time.parse(Regexp.last_match(1))
|
1667
|
-
interval = get_interval(i, false)
|
1766
|
+
interval = get_interval(i, formatted: false)
|
1668
1767
|
end
|
1669
1768
|
end_date ||= ''
|
1670
1769
|
interval ||= 0
|
@@ -1713,7 +1812,7 @@ class WWID
|
|
1713
1812
|
out = {
|
1714
1813
|
'section' => section,
|
1715
1814
|
'items' => items_out,
|
1716
|
-
'timers' => tag_times('json', opt[:sort_tags])
|
1815
|
+
'timers' => tag_times(format: 'json', sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order])
|
1717
1816
|
}.to_json
|
1718
1817
|
elsif opt[:output] == 'timeline'
|
1719
1818
|
template = <<~EOTEMPLATE
|
@@ -1794,7 +1893,7 @@ class WWID
|
|
1794
1893
|
css_template
|
1795
1894
|
end
|
1796
1895
|
|
1797
|
-
totals = opt[:totals] ? tag_times('html', opt[:sort_tags]) : ''
|
1896
|
+
totals = opt[:totals] ? tag_times(format: 'html', sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order]) : ''
|
1798
1897
|
engine = Haml::Engine.new(template)
|
1799
1898
|
out = engine.render(Object.new,
|
1800
1899
|
{ :@items => items_out, :@page_title => page_title, :@style => style, :@totals => totals })
|
@@ -1835,7 +1934,7 @@ class WWID
|
|
1835
1934
|
|
1836
1935
|
output.sub!(/%date/, item['date'].strftime(opt[:format]))
|
1837
1936
|
|
1838
|
-
interval = get_interval(item) if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
1937
|
+
interval = get_interval(item, record: true) if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
1839
1938
|
interval ||= ''
|
1840
1939
|
output.sub!(/%interval/, interval)
|
1841
1940
|
|
@@ -1885,7 +1984,8 @@ class WWID
|
|
1885
1984
|
|
1886
1985
|
out += "#{output}\n"
|
1887
1986
|
end
|
1888
|
-
|
1987
|
+
|
1988
|
+
out += tag_times(format: 'text', sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order]) if opt[:totals]
|
1889
1989
|
end
|
1890
1990
|
out
|
1891
1991
|
end
|
@@ -1897,7 +1997,7 @@ class WWID
|
|
1897
1997
|
## @param section (String) The source section
|
1898
1998
|
## @param options (Hash) Options
|
1899
1999
|
##
|
1900
|
-
def archive(section =
|
2000
|
+
def archive(section = @current_section, options = {})
|
1901
2001
|
count = options[:keep] || 0
|
1902
2002
|
destination = options[:destination] || 'Archive'
|
1903
2003
|
tags = options[:tags] || []
|
@@ -1956,7 +2056,7 @@ class WWID
|
|
1956
2056
|
end
|
1957
2057
|
end
|
1958
2058
|
moved_items.each do |item|
|
1959
|
-
if label && section !=
|
2059
|
+
if label && section != @current_section
|
1960
2060
|
item['title'] =
|
1961
2061
|
item['title'].sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{section})")
|
1962
2062
|
end
|
@@ -1969,7 +2069,7 @@ class WWID
|
|
1969
2069
|
count = items.length if items.length < count
|
1970
2070
|
|
1971
2071
|
items.map! do |item|
|
1972
|
-
if label && section !=
|
2072
|
+
if label && section != @current_section
|
1973
2073
|
item['title'] =
|
1974
2074
|
item['title'].sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{section})")
|
1975
2075
|
end
|
@@ -2148,11 +2248,15 @@ class WWID
|
|
2148
2248
|
end
|
2149
2249
|
|
2150
2250
|
##
|
2151
|
-
## @brief Get total elapsed time for all tags in
|
2251
|
+
## @brief Get total elapsed time for all tags in
|
2252
|
+
## selection
|
2152
2253
|
##
|
2153
|
-
## @param format
|
2254
|
+
## @param format (String) return format (html,
|
2255
|
+
## json, or text)
|
2256
|
+
## @param sort_by_name (Boolean) Sort by name if true, otherwise by time
|
2257
|
+
## @param sort_order (String) The sort order (asc or desc)
|
2154
2258
|
##
|
2155
|
-
def tag_times(format
|
2259
|
+
def tag_times(format: 'text', sort_by_name: false, sort_order: 'asc')
|
2156
2260
|
return '' if @timers.empty?
|
2157
2261
|
|
2158
2262
|
max = @timers.keys.sort_by { |k| k.length }.reverse[0].length + 1
|
@@ -2161,11 +2265,13 @@ class WWID
|
|
2161
2265
|
|
2162
2266
|
tags_data = @timers.delete_if { |_k, v| v == 0 }
|
2163
2267
|
sorted_tags_data = if sort_by_name
|
2164
|
-
tags_data.sort_by { |k, _v| k }
|
2268
|
+
tags_data.sort_by { |k, _v| k }
|
2165
2269
|
else
|
2166
2270
|
tags_data.sort_by { |_k, v| v }
|
2167
2271
|
end
|
2168
2272
|
|
2273
|
+
sorted_tags_data.reverse! if sort_order =~ /^asc/i
|
2274
|
+
|
2169
2275
|
if format == 'html'
|
2170
2276
|
output = <<EOS
|
2171
2277
|
<table>
|
@@ -2299,19 +2405,20 @@ EOS
|
|
2299
2405
|
## @param item (Hash) The entry
|
2300
2406
|
## @param formatted (Bool) Return human readable time (default seconds)
|
2301
2407
|
##
|
2302
|
-
def get_interval(item, formatted
|
2408
|
+
def get_interval(item, formatted: true, record: true)
|
2303
2409
|
done = nil
|
2304
2410
|
start = nil
|
2305
2411
|
|
2306
2412
|
if @interval_cache.keys.include? item['title']
|
2307
2413
|
seconds = @interval_cache[item['title']]
|
2414
|
+
record_tag_times(item, seconds) if record
|
2308
2415
|
return seconds > 0 ? '%02d:%02d:%02d' % fmt_time(seconds) : false
|
2309
2416
|
end
|
2310
2417
|
|
2311
2418
|
if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
|
2312
2419
|
done = Time.parse(Regexp.last_match(1))
|
2313
2420
|
else
|
2314
|
-
return
|
2421
|
+
return false
|
2315
2422
|
end
|
2316
2423
|
|
2317
2424
|
start = if item['title'] =~ /@start\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
|
@@ -2322,20 +2429,34 @@ EOS
|
|
2322
2429
|
|
2323
2430
|
seconds = (done - start).to_i
|
2324
2431
|
|
2432
|
+
if record
|
2433
|
+
record_tag_times(item, seconds)
|
2434
|
+
end
|
2435
|
+
|
2436
|
+
@interval_cache[item['title']] = seconds
|
2437
|
+
|
2438
|
+
return seconds > 0 ? seconds : false unless formatted
|
2439
|
+
|
2440
|
+
seconds > 0 ? '%02d:%02d:%02d' % fmt_time(seconds) : false
|
2441
|
+
end
|
2442
|
+
|
2443
|
+
##
|
2444
|
+
## @brief Record times for item tags
|
2445
|
+
##
|
2446
|
+
## @param item The item
|
2447
|
+
##
|
2448
|
+
def record_tag_times(item, seconds)
|
2449
|
+
return if @recorded_items.include?(item)
|
2450
|
+
|
2325
2451
|
item['title'].scan(/(?mi)@(\S+?)(\(.*\))?(?=\s|$)/).each do |m|
|
2326
2452
|
k = m[0] == 'done' ? 'All' : m[0].downcase
|
2327
|
-
if @timers.
|
2453
|
+
if @timers.key?(k)
|
2328
2454
|
@timers[k] += seconds
|
2329
2455
|
else
|
2330
2456
|
@timers[k] = seconds
|
2331
2457
|
end
|
2458
|
+
@recorded_items.push(item)
|
2332
2459
|
end
|
2333
|
-
|
2334
|
-
@interval_cache[item['title']] = seconds
|
2335
|
-
|
2336
|
-
return seconds unless formatted
|
2337
|
-
|
2338
|
-
seconds > 0 ? '%02d:%02d:%02d' % fmt_time(seconds) : false
|
2339
2460
|
end
|
2340
2461
|
|
2341
2462
|
##
|
data/lib/doing.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: doing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.84
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brett Terpstra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-10-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|