doing 1.0.82 → 1.0.86
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 +13 -4
- data/bin/doing +173 -40
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +177 -46
- 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: 2af84d93e54c8366719e285303dc11d966f9b83361ae4ac20ca172536465f4ee
|
|
4
|
+
data.tar.gz: 7fa72f0ec1ba1a18e45d80c1604910cabb00c8dad485b96d05bd8c3e33a5b4ae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 158707f75c6375fb5d684457d0b17c874fb2dc0f9b2e5318a24f055665ed246e69149211fb45487acac2174b2ca9db6ebd62b071af0c283377d5e2fa78f663c2
|
|
7
|
+
data.tar.gz: 80bf8ff4da513c0d911afdf5bbeb2589f6a08bf46eb746c5eb610f5ddac1bdc7d7469f1f3eb399e86c4a671358df704c98596841c2f8964d3f81ebe985de1435
|
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.85<!--END VER-->.
|
|
31
31
|
|
|
32
32
|
$ [sudo] gem install doing
|
|
33
33
|
|
|
@@ -92,6 +92,7 @@ A basic configuration looks like this:
|
|
|
92
92
|
date_format: '%_I:%M%P'
|
|
93
93
|
template: '%date > %title%odnote'
|
|
94
94
|
wrap_width: 50
|
|
95
|
+
count: 10
|
|
95
96
|
autotag:
|
|
96
97
|
whitelist:
|
|
97
98
|
- coding
|
|
@@ -239,13 +240,15 @@ and output my recent entries like this:
|
|
|
239
240
|
|
|
240
241
|
$
|
|
241
242
|
|
|
243
|
+
The recent template can include a `count` key to specify the number of entries shown when run without an argument. Default is 10.
|
|
244
|
+
|
|
242
245
|
### Custom views
|
|
243
246
|
|
|
244
247
|
You can create your own "views" in the `~/.doingrc` file and view them with `doing view view_name`. Just add a section like this:
|
|
245
248
|
|
|
246
249
|
views:
|
|
247
250
|
old:
|
|
248
|
-
section:
|
|
251
|
+
section: Archive
|
|
249
252
|
count: 5
|
|
250
253
|
wrap_width: 0
|
|
251
254
|
date_format: '%F %_I:%M%P'
|
|
@@ -253,6 +256,10 @@ You can create your own "views" in the `~/.doingrc` file and view them with `doi
|
|
|
253
256
|
order: asc
|
|
254
257
|
tags: done finished cancelled
|
|
255
258
|
tags_bool: ANY
|
|
259
|
+
only_timed: false
|
|
260
|
+
tag_sort: time
|
|
261
|
+
tag_order: asc
|
|
262
|
+
totals: true
|
|
256
263
|
|
|
257
264
|
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
265
|
|
|
@@ -276,7 +283,9 @@ You can add new sections with `doing add_section section_name`. You can also cre
|
|
|
276
283
|
|
|
277
284
|
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
285
|
|
|
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`.
|
|
286
|
+
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`.
|
|
287
|
+
|
|
288
|
+
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
289
|
|
|
281
290
|
Regarding colors, you can use them to create very nice displays if you're outputting to a color terminal. Example:
|
|
282
291
|
|
|
@@ -291,7 +300,7 @@ Outputs:
|
|
|
291
300
|
|
|
292
301
|

|
|
293
302
|
|
|
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
|
|
303
|
+
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
304
|
|
|
296
305
|
### Colors
|
|
297
306
|
|
data/bin/doing
CHANGED
|
@@ -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]
|
|
@@ -308,7 +318,7 @@ command :select do |c|
|
|
|
308
318
|
c.arg_name 'FILE'
|
|
309
319
|
c.flag %i[save_to]
|
|
310
320
|
|
|
311
|
-
c.desc 'Output
|
|
321
|
+
c.desc 'Output entries to format (doing|taskpaper|csv|html|json|template|timeline)'
|
|
312
322
|
c.arg_name 'FORMAT'
|
|
313
323
|
c.flag %i[o output], must_match: /^(?:doing|taskpaper|html|csv|json|template|timeline)$/i
|
|
314
324
|
|
|
@@ -323,10 +333,6 @@ command :later do |c|
|
|
|
323
333
|
c.desc "Edit entry with #{ENV['EDITOR']}"
|
|
324
334
|
c.switch %i[e editor], negatable: false, default_value: false
|
|
325
335
|
|
|
326
|
-
c.desc 'Edit entry with specified app'
|
|
327
|
-
c.arg_name 'APP'
|
|
328
|
-
c.flag %i[a app]
|
|
329
|
-
|
|
330
336
|
c.desc 'Backdate start time to date string [4pm|20m|2h|yesterday noon]'
|
|
331
337
|
c.arg_name 'DATE_STRING'
|
|
332
338
|
c.flag %i[b back]
|
|
@@ -343,7 +349,7 @@ command :later do |c|
|
|
|
343
349
|
date = Time.now
|
|
344
350
|
end
|
|
345
351
|
|
|
346
|
-
if options[:
|
|
352
|
+
if options[:editor] || (args.empty? && $stdin.stat.size.zero?)
|
|
347
353
|
exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
|
|
348
354
|
|
|
349
355
|
input = args.empty? ? '' : args.join(' ')
|
|
@@ -399,7 +405,7 @@ command %i[done did] do |c|
|
|
|
399
405
|
|
|
400
406
|
c.desc 'Section'
|
|
401
407
|
c.arg_name 'NAME'
|
|
402
|
-
c.flag %i[s section]
|
|
408
|
+
c.flag %i[s section]
|
|
403
409
|
|
|
404
410
|
c.desc "Edit entry with #{ENV['EDITOR']}"
|
|
405
411
|
c.switch %i[e editor], negatable: false, default_value: false
|
|
@@ -440,9 +446,13 @@ command %i[done did] do |c|
|
|
|
440
446
|
donedate = options[:date] ? "(#{finish_date.strftime('%F %R')})" : ''
|
|
441
447
|
end
|
|
442
448
|
|
|
443
|
-
|
|
449
|
+
if options[:section]
|
|
450
|
+
section = wwid.guess_section(options[:section]) || options[:section].cap_first
|
|
451
|
+
else
|
|
452
|
+
section = wwid.config['current_section']
|
|
453
|
+
end
|
|
444
454
|
|
|
445
|
-
if options[:
|
|
455
|
+
if options[:editor]
|
|
446
456
|
exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
|
|
447
457
|
|
|
448
458
|
input = ''
|
|
@@ -497,7 +507,7 @@ command :cancel do |c|
|
|
|
497
507
|
|
|
498
508
|
c.desc 'Section'
|
|
499
509
|
c.arg_name 'NAME'
|
|
500
|
-
c.flag %i[s section]
|
|
510
|
+
c.flag %i[s section]
|
|
501
511
|
|
|
502
512
|
c.desc 'Cancel the last X entries containing TAG. Separate multiple tags with comma (--tag=tag1,tag2)'
|
|
503
513
|
c.arg_name 'TAG'
|
|
@@ -511,7 +521,11 @@ command :cancel do |c|
|
|
|
511
521
|
c.switch %i[u unfinished], negatable: false, default_value: false
|
|
512
522
|
|
|
513
523
|
c.action do |_global_options, options, args|
|
|
514
|
-
|
|
524
|
+
if options[:section]
|
|
525
|
+
section = wwid.guess_section(options[:section]) || options[:section].cap_first
|
|
526
|
+
else
|
|
527
|
+
section = wwid.config['current_section']
|
|
528
|
+
end
|
|
515
529
|
|
|
516
530
|
if options[:tag].nil?
|
|
517
531
|
tags = []
|
|
@@ -564,6 +578,10 @@ command :finish do |c|
|
|
|
564
578
|
c.arg_name 'INTERVAL'
|
|
565
579
|
c.flag %i[t took]
|
|
566
580
|
|
|
581
|
+
c.desc %(Set finish date to specific date/time (natural langauge parsed, e.g. --at=1:30pm). If used, ignores --back.)
|
|
582
|
+
c.arg_name 'DATE_STRING'
|
|
583
|
+
c.flag [:at]
|
|
584
|
+
|
|
567
585
|
c.desc 'Finish the last X entries containing TAG.
|
|
568
586
|
Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool'
|
|
569
587
|
c.arg_name 'TAG'
|
|
@@ -590,17 +608,31 @@ command :finish do |c|
|
|
|
590
608
|
|
|
591
609
|
c.desc 'Section'
|
|
592
610
|
c.arg_name 'NAME'
|
|
593
|
-
c.flag %i[s section]
|
|
611
|
+
c.flag %i[s section]
|
|
594
612
|
|
|
595
613
|
c.action do |_global_options, options, args|
|
|
596
|
-
|
|
614
|
+
if options[:section]
|
|
615
|
+
section = wwid.guess_section(options[:section]) || options[:section].cap_first
|
|
616
|
+
else
|
|
617
|
+
section = wwid.config['current_section']
|
|
618
|
+
end
|
|
597
619
|
|
|
598
620
|
unless options[:auto]
|
|
621
|
+
if options[:took]
|
|
622
|
+
took = wwid.chronify_qty(options[:took])
|
|
623
|
+
exit_now! 'Unable to parse date string for --took' if took.nil?
|
|
624
|
+
end
|
|
625
|
+
|
|
599
626
|
exit_now! '--back and --took cannot be used together' if options[:back] && options[:took]
|
|
600
627
|
|
|
601
628
|
exit_now! '--search and --tag cannot be used together' if options[:search] && options[:tag]
|
|
602
629
|
|
|
603
|
-
if options[:
|
|
630
|
+
if options[:at]
|
|
631
|
+
finish_date = wwid.chronify(options[:at])
|
|
632
|
+
exit_now! 'Unable to parse date string for --at' if finish_date.nil?
|
|
633
|
+
|
|
634
|
+
date = options[:took] ? finish_date - took : finish_date
|
|
635
|
+
elsif options[:back]
|
|
604
636
|
date = wwid.chronify(options[:back])
|
|
605
637
|
|
|
606
638
|
exit_now! 'Unable to parse date string' if date.nil?
|
|
@@ -826,7 +858,7 @@ desc 'Mark last entry as highlighted'
|
|
|
826
858
|
command [:mark, :flag] do |c|
|
|
827
859
|
c.desc 'Section'
|
|
828
860
|
c.arg_name 'NAME'
|
|
829
|
-
c.flag %i[s section]
|
|
861
|
+
c.flag %i[s section]
|
|
830
862
|
|
|
831
863
|
c.desc 'Remove mark'
|
|
832
864
|
c.switch %i[r remove], negatable: false, default_value: false
|
|
@@ -1065,7 +1097,12 @@ command :recent do |c|
|
|
|
1065
1097
|
section = wwid.guess_section(options[:s]) || options[:s].cap_first
|
|
1066
1098
|
|
|
1067
1099
|
unless global_options[:version]
|
|
1068
|
-
|
|
1100
|
+
if wwid.config['templates']['recent'].key?('count')
|
|
1101
|
+
config_count = wwid.config['templates']['recent']['count'].to_i
|
|
1102
|
+
else
|
|
1103
|
+
config_count = 10
|
|
1104
|
+
end
|
|
1105
|
+
count = args.empty? ? config_count : args[0].to_i
|
|
1069
1106
|
options[:t] = true if options[:totals]
|
|
1070
1107
|
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
|
1071
1108
|
tags_color = wwid.config.key?('tags_color') ? wwid.config['tags_color'] : nil
|
|
@@ -1285,7 +1322,7 @@ command :last do |c|
|
|
|
1285
1322
|
|
|
1286
1323
|
end
|
|
1287
1324
|
|
|
1288
|
-
if options[:
|
|
1325
|
+
if options[:editor]
|
|
1289
1326
|
wwid.edit_last(section: options[:s], options: { search: options[:search], tag: tags, tag_bool: options[:bool] })
|
|
1290
1327
|
else
|
|
1291
1328
|
puts wwid.last(times: true, section: options[:s],
|
|
@@ -1344,13 +1381,14 @@ command :colors do |c|
|
|
|
1344
1381
|
end
|
|
1345
1382
|
|
|
1346
1383
|
desc 'Display a user-created view'
|
|
1384
|
+
long_desc 'Command line options override associated view settings'
|
|
1347
1385
|
arg_name 'VIEW_NAME'
|
|
1348
1386
|
command :view do |c|
|
|
1349
|
-
c.desc 'Section
|
|
1387
|
+
c.desc 'Section'
|
|
1350
1388
|
c.arg_name 'NAME'
|
|
1351
1389
|
c.flag %i[s section]
|
|
1352
1390
|
|
|
1353
|
-
c.desc 'Count to display
|
|
1391
|
+
c.desc 'Count to display'
|
|
1354
1392
|
c.arg_name 'COUNT'
|
|
1355
1393
|
c.flag %i[c count], must_match: /^\d+$/, type: Integer
|
|
1356
1394
|
|
|
@@ -1368,12 +1406,14 @@ command :view do |c|
|
|
|
1368
1406
|
c.switch [:color], default_value: true, negatable: true
|
|
1369
1407
|
|
|
1370
1408
|
c.desc 'Sort tags by (name|time)'
|
|
1371
|
-
default = 'time'
|
|
1372
|
-
default = wwid.config['tag_sort'] if wwid.config.key?('tag_sort')
|
|
1373
1409
|
c.arg_name 'KEY'
|
|
1374
|
-
c.flag [:tag_sort], must_match: /^(?:name|time)$/i
|
|
1410
|
+
c.flag [:tag_sort], must_match: /^(?:name|time)$/i
|
|
1375
1411
|
|
|
1376
|
-
c.desc '
|
|
1412
|
+
c.desc 'Tag sort direction (asc|desc)'
|
|
1413
|
+
c.arg_name 'DIRECTION'
|
|
1414
|
+
c.flag [:tag_order], must_match: /^(?:a(?:sc)?|d(?:esc)?)$/i
|
|
1415
|
+
|
|
1416
|
+
c.desc 'Only show items with recorded time intervals (override view settings)'
|
|
1377
1417
|
c.switch [:only_timed], default_value: false, negatable: false
|
|
1378
1418
|
|
|
1379
1419
|
c.action do |_global_options, options, args|
|
|
@@ -1383,7 +1423,11 @@ command :view do |c|
|
|
|
1383
1423
|
wwid.guess_view(args[0])
|
|
1384
1424
|
end
|
|
1385
1425
|
|
|
1386
|
-
|
|
1426
|
+
if options[:section]
|
|
1427
|
+
section = wwid.guess_section(options[:section]) || options[:section].cap_first
|
|
1428
|
+
else
|
|
1429
|
+
section = wwid.config['current_section']
|
|
1430
|
+
end
|
|
1387
1431
|
|
|
1388
1432
|
view = wwid.get_view(title)
|
|
1389
1433
|
if view
|
|
@@ -1422,10 +1466,31 @@ command :view do |c|
|
|
|
1422
1466
|
end
|
|
1423
1467
|
order = view.key?('order') ? view['order'] : 'asc'
|
|
1424
1468
|
|
|
1425
|
-
|
|
1469
|
+
totals = if options[:totals]
|
|
1470
|
+
true
|
|
1471
|
+
else
|
|
1472
|
+
view.key?('totals') ? view['totals'] : false
|
|
1473
|
+
end
|
|
1474
|
+
|
|
1475
|
+
options[:t] = true if totals
|
|
1426
1476
|
options[:output]&.downcase!
|
|
1427
|
-
options[:sort_tags] = options[:tag_sort] =~ /^n/i
|
|
1428
1477
|
|
|
1478
|
+
options[:sort_tags] = if options[:tag_sort]
|
|
1479
|
+
options[:tag_sort] =~ /^n/i ? true : false
|
|
1480
|
+
elsif view.key?('tag_sort')
|
|
1481
|
+
view['tag_sort'] =~ /^n/i ? true : false
|
|
1482
|
+
else
|
|
1483
|
+
false
|
|
1484
|
+
end
|
|
1485
|
+
|
|
1486
|
+
tag_order = if options[:tag_order]
|
|
1487
|
+
options[:tag_order] =~ /^d/i ? 'desc' : 'asc'
|
|
1488
|
+
elsif view.key?('tag_order')
|
|
1489
|
+
view['tag_order'] =~ /^d/i ? 'desc' : 'asc'
|
|
1490
|
+
else
|
|
1491
|
+
'asc'
|
|
1492
|
+
end
|
|
1493
|
+
warn "TAG ORDER: #{options[:tag_order]}"
|
|
1429
1494
|
opts = {
|
|
1430
1495
|
count: count,
|
|
1431
1496
|
format: format,
|
|
@@ -1436,10 +1501,11 @@ command :view do |c|
|
|
|
1436
1501
|
section: section,
|
|
1437
1502
|
sort_tags: options[:sort_tags],
|
|
1438
1503
|
tag_filter: tag_filter,
|
|
1504
|
+
tag_order: tag_order,
|
|
1439
1505
|
tags_color: tags_color,
|
|
1440
1506
|
template: template,
|
|
1441
1507
|
times: options[:t],
|
|
1442
|
-
totals:
|
|
1508
|
+
totals: totals
|
|
1443
1509
|
}
|
|
1444
1510
|
|
|
1445
1511
|
puts wwid.list_section(opts)
|
|
@@ -1489,6 +1555,11 @@ command :archive do |c|
|
|
|
1489
1555
|
c.arg_name 'QUERY'
|
|
1490
1556
|
c.flag [:search]
|
|
1491
1557
|
|
|
1558
|
+
c.desc 'Archive entries older than date
|
|
1559
|
+
(Flexible date format, e.g. 1/27/2021, 2020-07-19, or Monday 3pm)'
|
|
1560
|
+
c.arg_name 'DATE_STRING'
|
|
1561
|
+
c.flag [:before]
|
|
1562
|
+
|
|
1492
1563
|
c.action do |_global_options, options, args|
|
|
1493
1564
|
if args.empty?
|
|
1494
1565
|
section = wwid.current_section
|
|
@@ -1518,6 +1589,7 @@ command :archive do |c|
|
|
|
1518
1589
|
'AND'
|
|
1519
1590
|
end
|
|
1520
1591
|
opts = {
|
|
1592
|
+
before: options[:before],
|
|
1521
1593
|
bool: options[:bool],
|
|
1522
1594
|
destination: options[:to],
|
|
1523
1595
|
keep: options[:keep],
|
|
@@ -1528,17 +1600,64 @@ command :archive do |c|
|
|
|
1528
1600
|
end
|
|
1529
1601
|
end
|
|
1530
1602
|
|
|
1603
|
+
desc 'Move entries to archive file'
|
|
1604
|
+
command :rotate do |c|
|
|
1605
|
+
c.desc 'How many items to keep in each section (most recent)'
|
|
1606
|
+
c.arg_name 'X'
|
|
1607
|
+
c.flag %i[k keep], must_match: /^\d+$/, type: Integer
|
|
1608
|
+
|
|
1609
|
+
c.desc 'Section to rotate'
|
|
1610
|
+
c.arg_name 'SECTION_NAME'
|
|
1611
|
+
c.flag %i[s section], default_value: 'All'
|
|
1612
|
+
|
|
1613
|
+
c.desc 'Tag filter, combine multiple tags with a comma. Added for compatibility with other commands.'
|
|
1614
|
+
c.arg_name 'TAG'
|
|
1615
|
+
c.flag [:tag]
|
|
1616
|
+
|
|
1617
|
+
c.desc 'Tag boolean (AND|OR|NOT)'
|
|
1618
|
+
c.arg_name 'BOOLEAN'
|
|
1619
|
+
c.flag [:bool], must_match: /(?:and|all|any|or|not|none)/i, default_value: 'AND'
|
|
1620
|
+
|
|
1621
|
+
c.desc 'Search filter'
|
|
1622
|
+
c.arg_name 'QUERY'
|
|
1623
|
+
c.flag [:search]
|
|
1624
|
+
|
|
1625
|
+
c.desc 'Rotate entries older than date
|
|
1626
|
+
(Flexible date format, e.g. 1/27/2021, 2020-07-19, or Monday 3pm)'
|
|
1627
|
+
c.arg_name 'DATE_STRING'
|
|
1628
|
+
c.flag [:before]
|
|
1629
|
+
|
|
1630
|
+
c.action do |_global_options, options, args|
|
|
1631
|
+
if options[:section] && options[:section] !~ /^all$/i
|
|
1632
|
+
options[:section] = wwid.guess_section(options[:section])
|
|
1633
|
+
end
|
|
1634
|
+
|
|
1635
|
+
options[:bool] = case options[:bool]
|
|
1636
|
+
when /(and|all)/i
|
|
1637
|
+
'AND'
|
|
1638
|
+
when /(any|or)/i
|
|
1639
|
+
'OR'
|
|
1640
|
+
when /(not|none)/i
|
|
1641
|
+
'NOT'
|
|
1642
|
+
else
|
|
1643
|
+
'AND'
|
|
1644
|
+
end
|
|
1645
|
+
|
|
1646
|
+
wwid.rotate(options)
|
|
1647
|
+
end
|
|
1648
|
+
end
|
|
1649
|
+
|
|
1531
1650
|
desc 'Open the "doing" file in an editor'
|
|
1532
1651
|
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'})"
|
|
1533
1652
|
command :open do |c|
|
|
1534
1653
|
if `uname` =~ /Darwin/
|
|
1535
1654
|
c.desc 'Open with app name'
|
|
1536
1655
|
c.arg_name 'APP_NAME'
|
|
1537
|
-
c.flag [
|
|
1656
|
+
c.flag %i[a app]
|
|
1538
1657
|
|
|
1539
1658
|
c.desc 'Open with app bundle id'
|
|
1540
1659
|
c.arg_name 'BUNDLE_ID'
|
|
1541
|
-
c.flag [
|
|
1660
|
+
c.flag %i[b bundle_id]
|
|
1542
1661
|
end
|
|
1543
1662
|
c.desc "Open with $EDITOR (#{ENV['EDITOR']})"
|
|
1544
1663
|
c.switch %i[e editor], negatable: false, default_value: false
|
|
@@ -1549,11 +1668,11 @@ command :open do |c|
|
|
|
1549
1668
|
k.instance_of?(String) || v.nil? || v == false
|
|
1550
1669
|
end
|
|
1551
1670
|
if `uname` =~ /Darwin/
|
|
1552
|
-
if options[:
|
|
1671
|
+
if options[:app]
|
|
1553
1672
|
system %(open -a "#{options[:a]}" "#{File.expand_path(wwid.doing_file)}")
|
|
1554
|
-
elsif options[:
|
|
1673
|
+
elsif options[:bundle_id]
|
|
1555
1674
|
system %(open -b "#{options[:b]}" "#{File.expand_path(wwid.doing_file)}")
|
|
1556
|
-
elsif options[:
|
|
1675
|
+
elsif options[:editor]
|
|
1557
1676
|
exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
|
|
1558
1677
|
|
|
1559
1678
|
system %($EDITOR "#{File.expand_path(wwid.doing_file)}")
|
|
@@ -1637,12 +1756,15 @@ command :import do |c|
|
|
|
1637
1756
|
|
|
1638
1757
|
c.desc 'Target section'
|
|
1639
1758
|
c.arg_name 'NAME'
|
|
1640
|
-
c.flag %i[s section]
|
|
1759
|
+
c.flag %i[s section]
|
|
1641
1760
|
|
|
1642
1761
|
c.desc 'Tag all imported entries'
|
|
1643
1762
|
c.arg_name 'TAGS'
|
|
1644
1763
|
c.flag :tag
|
|
1645
1764
|
|
|
1765
|
+
c.desc 'Autotag entries'
|
|
1766
|
+
c.switch :autotag, negatable: true, default_value: true
|
|
1767
|
+
|
|
1646
1768
|
c.desc 'Prefix entries with'
|
|
1647
1769
|
c.arg_name 'PREFIX'
|
|
1648
1770
|
c.flag :prefix
|
|
@@ -1652,11 +1774,22 @@ command :import do |c|
|
|
|
1652
1774
|
|
|
1653
1775
|
c.action do |_global_options, options, args|
|
|
1654
1776
|
|
|
1655
|
-
|
|
1777
|
+
if options[:section]
|
|
1778
|
+
section = wwid.guess_section(options[:section]) || options[:section].cap_first
|
|
1779
|
+
else
|
|
1780
|
+
section = wwid.config['current_section']
|
|
1781
|
+
end
|
|
1656
1782
|
|
|
1657
1783
|
if options[:type] =~ /^tim/i
|
|
1658
1784
|
args.each do |path|
|
|
1659
|
-
|
|
1785
|
+
options = {
|
|
1786
|
+
autotag: options[:autotag],
|
|
1787
|
+
no_overlap: !options[:overlap],
|
|
1788
|
+
prefix: options[:prefix],
|
|
1789
|
+
section: section,
|
|
1790
|
+
tag: options[:tag]
|
|
1791
|
+
}
|
|
1792
|
+
wwid.import_timing(path, options)
|
|
1660
1793
|
wwid.write(wwid.doing_file)
|
|
1661
1794
|
end
|
|
1662
1795
|
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)
|
|
@@ -123,7 +124,8 @@ class WWID
|
|
|
123
124
|
@config['templates']['recent'] ||= {
|
|
124
125
|
'date_format' => '%_I:%M%P',
|
|
125
126
|
'template' => '%shortdate: %title (%section)',
|
|
126
|
-
'wrap_width' => 88
|
|
127
|
+
'wrap_width' => 88,
|
|
128
|
+
'count' => 10
|
|
127
129
|
}
|
|
128
130
|
@config['views'] ||= {
|
|
129
131
|
'done' => {
|
|
@@ -424,8 +426,8 @@ class WWID
|
|
|
424
426
|
## @param guessed (Boolean) already guessed and failed
|
|
425
427
|
##
|
|
426
428
|
def guess_section(frag, guessed: false)
|
|
427
|
-
return 'All' if frag =~
|
|
428
|
-
|
|
429
|
+
return 'All' if frag =~ /^all$/i
|
|
430
|
+
frag ||= @current_section
|
|
429
431
|
sections.each { |section| return section.cap_first if frag.downcase == section.downcase }
|
|
430
432
|
section = false
|
|
431
433
|
re = frag.split('').join('.*?')
|
|
@@ -583,17 +585,17 @@ class WWID
|
|
|
583
585
|
end
|
|
584
586
|
|
|
585
587
|
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
|
|
588
|
+
item_a['date'] == item_b['date'] ? get_interval(item_a, formatted: false, record: false) == get_interval(item_b, formatted: false, record: false) : false
|
|
587
589
|
end
|
|
588
590
|
|
|
589
591
|
def overlapping_time?(item_a, item_b)
|
|
590
592
|
return true if same_time?(item_a, item_b)
|
|
591
593
|
|
|
592
594
|
start_a = item_a['date']
|
|
593
|
-
interval = get_interval(item_a, false)
|
|
595
|
+
interval = get_interval(item_a, formatted: false, record: false)
|
|
594
596
|
end_a = interval ? start_a + interval.to_i : start_a
|
|
595
597
|
start_b = item_b['date']
|
|
596
|
-
interval = get_interval(item_b, false)
|
|
598
|
+
interval = get_interval(item_b, formatted: false, record: false)
|
|
597
599
|
end_b = interval ? start_b + interval.to_i : start_b
|
|
598
600
|
(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
601
|
end
|
|
@@ -626,6 +628,7 @@ class WWID
|
|
|
626
628
|
def import_timing(path, opt = {})
|
|
627
629
|
section = opt[:section] || @current_section
|
|
628
630
|
opt[:no_overlap] ||= false
|
|
631
|
+
opt[:autotag] ||= @auto_tag
|
|
629
632
|
|
|
630
633
|
add_section(section) unless @content.has_key?(section)
|
|
631
634
|
|
|
@@ -656,7 +659,7 @@ class WWID
|
|
|
656
659
|
title += " @#{tag}"
|
|
657
660
|
end
|
|
658
661
|
end
|
|
659
|
-
title = autotag(title) if
|
|
662
|
+
title = autotag(title) if opt[:autotag]
|
|
660
663
|
title += " @done(#{end_time.strftime('%Y-%m-%d %H:%M')})"
|
|
661
664
|
title.gsub!(/ +/, ' ')
|
|
662
665
|
title.strip!
|
|
@@ -1098,7 +1101,6 @@ class WWID
|
|
|
1098
1101
|
items = @content[section]['items'].dup.sort_by { |item| item['date'] }.reverse
|
|
1099
1102
|
idx = 0
|
|
1100
1103
|
done_date = Time.now
|
|
1101
|
-
next_start = Time.now
|
|
1102
1104
|
count = (opt[:count]).zero? ? items.length : opt[:count]
|
|
1103
1105
|
items.map! do |item|
|
|
1104
1106
|
break if idx == count
|
|
@@ -1117,8 +1119,8 @@ class WWID
|
|
|
1117
1119
|
end
|
|
1118
1120
|
else
|
|
1119
1121
|
if opt[:sequential]
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
+
next_entry = next_item(item)
|
|
1123
|
+
done_date = next_entry['date'] - 60 if next_entry
|
|
1122
1124
|
elsif opt[:took]
|
|
1123
1125
|
if item['date'] + opt[:took] > Time.now
|
|
1124
1126
|
item['date'] = Time.now - opt[:took]
|
|
@@ -1207,6 +1209,24 @@ class WWID
|
|
|
1207
1209
|
return new_item
|
|
1208
1210
|
end
|
|
1209
1211
|
|
|
1212
|
+
##
|
|
1213
|
+
## @brief Get next item in the index
|
|
1214
|
+
##
|
|
1215
|
+
## @param old_item
|
|
1216
|
+
##
|
|
1217
|
+
def next_item(old_item)
|
|
1218
|
+
section = old_item['section']
|
|
1219
|
+
|
|
1220
|
+
section_items = @content[section]['items'].sort_by { |entry| entry['date'] }
|
|
1221
|
+
idx = section_items.index(old_item)
|
|
1222
|
+
|
|
1223
|
+
if section_items.size > idx
|
|
1224
|
+
section_items[idx + 1]
|
|
1225
|
+
else
|
|
1226
|
+
nil
|
|
1227
|
+
end
|
|
1228
|
+
end
|
|
1229
|
+
|
|
1210
1230
|
##
|
|
1211
1231
|
## @brief Delete an item from the index
|
|
1212
1232
|
##
|
|
@@ -1476,7 +1496,7 @@ class WWID
|
|
|
1476
1496
|
##
|
|
1477
1497
|
## @param file (String) The filepath to write to
|
|
1478
1498
|
##
|
|
1479
|
-
def write(file = nil)
|
|
1499
|
+
def write(file = nil, backup: true)
|
|
1480
1500
|
output = @other_content_top ? "#{@other_content_top.join("\n")}\n" : ''
|
|
1481
1501
|
|
|
1482
1502
|
@content.each do |title, section|
|
|
@@ -1488,7 +1508,7 @@ class WWID
|
|
|
1488
1508
|
$stdout.puts output
|
|
1489
1509
|
else
|
|
1490
1510
|
file = File.expand_path(file)
|
|
1491
|
-
if File.exist?(file)
|
|
1511
|
+
if File.exist?(file) && backup
|
|
1492
1512
|
# Create a backup copy for the undo command
|
|
1493
1513
|
FileUtils.cp(file, "#{file}~")
|
|
1494
1514
|
end
|
|
@@ -1520,6 +1540,88 @@ class WWID
|
|
|
1520
1540
|
end
|
|
1521
1541
|
end
|
|
1522
1542
|
|
|
1543
|
+
##
|
|
1544
|
+
## @brief Rename doing file with date and start fresh one
|
|
1545
|
+
##
|
|
1546
|
+
def rotate(opt = {})
|
|
1547
|
+
count = opt[:keep] || 0
|
|
1548
|
+
tags = []
|
|
1549
|
+
tags.concat(opt[:tag].split(/ *, */).map { |t| t.sub(/^@/, '').strip }) if opt[:tag]
|
|
1550
|
+
bool = opt[:bool] || :and
|
|
1551
|
+
sect = opt[:section] !~ /^all$/i ? guess_section(opt[:section]) : 'all'
|
|
1552
|
+
|
|
1553
|
+
if sect =~ /^all$/i
|
|
1554
|
+
all_sections = sections.dup
|
|
1555
|
+
else
|
|
1556
|
+
all_sections = [sect]
|
|
1557
|
+
end
|
|
1558
|
+
|
|
1559
|
+
counter = 0
|
|
1560
|
+
new_content = {}
|
|
1561
|
+
|
|
1562
|
+
|
|
1563
|
+
all_sections.each do |section|
|
|
1564
|
+
items = @content[section]['items'].dup
|
|
1565
|
+
new_content[section] = {}
|
|
1566
|
+
new_content[section]['original'] = @content[section]['original']
|
|
1567
|
+
new_content[section]['items'] = []
|
|
1568
|
+
|
|
1569
|
+
moved_items = []
|
|
1570
|
+
if !tags.empty? || opt[:search] || opt[:before]
|
|
1571
|
+
if opt[:before]
|
|
1572
|
+
time_string = opt[:before]
|
|
1573
|
+
time_string += ' 12am' if time_string !~ /(\d+:\d+|\d+[ap])/
|
|
1574
|
+
cutoff = chronify(time_string)
|
|
1575
|
+
end
|
|
1576
|
+
|
|
1577
|
+
items.delete_if do |item|
|
|
1578
|
+
if ((!tags.empty? && item.has_tags?(tags, bool)) || (opt[:search] && item.matches_search?(opt[:search].to_s)) || (opt[:before] && item['date'] < cutoff))
|
|
1579
|
+
moved_items.push(item)
|
|
1580
|
+
counter += 1
|
|
1581
|
+
true
|
|
1582
|
+
else
|
|
1583
|
+
false
|
|
1584
|
+
end
|
|
1585
|
+
end
|
|
1586
|
+
@content[section]['items'] = items
|
|
1587
|
+
new_content[section]['items'] = moved_items
|
|
1588
|
+
@results.push("Rotated #{moved_items.length} items from #{section}")
|
|
1589
|
+
else
|
|
1590
|
+
new_content[section]['items'] = []
|
|
1591
|
+
moved_items = []
|
|
1592
|
+
|
|
1593
|
+
count = items.length if items.length < count
|
|
1594
|
+
|
|
1595
|
+
if items.count > count
|
|
1596
|
+
moved_items.concat(items[count..-1])
|
|
1597
|
+
else
|
|
1598
|
+
moved_items.concat(items)
|
|
1599
|
+
end
|
|
1600
|
+
|
|
1601
|
+
@content[section]['items'] = if count.zero?
|
|
1602
|
+
[]
|
|
1603
|
+
else
|
|
1604
|
+
items[0..count - 1]
|
|
1605
|
+
end
|
|
1606
|
+
new_content[section]['items'] = moved_items
|
|
1607
|
+
|
|
1608
|
+
@results.push("Rotated #{items.length - count} items from #{section}")
|
|
1609
|
+
end
|
|
1610
|
+
end
|
|
1611
|
+
|
|
1612
|
+
write(@doing_file)
|
|
1613
|
+
|
|
1614
|
+
file = @doing_file.sub(/(\.\w+)$/, "_#{Time.now.strftime('%Y-%m-%d')}\\1")
|
|
1615
|
+
if File.exist?(file)
|
|
1616
|
+
init_doing_file(file)
|
|
1617
|
+
@content.deep_merge(new_content)
|
|
1618
|
+
else
|
|
1619
|
+
@content = new_content
|
|
1620
|
+
end
|
|
1621
|
+
|
|
1622
|
+
write(file, backup: false)
|
|
1623
|
+
end
|
|
1624
|
+
|
|
1523
1625
|
##
|
|
1524
1626
|
## @brief Generate a menu of sections and allow user selection
|
|
1525
1627
|
##
|
|
@@ -1569,20 +1671,21 @@ class WWID
|
|
|
1569
1671
|
def list_section(opt = {})
|
|
1570
1672
|
opt[:count] ||= 0
|
|
1571
1673
|
count = opt[:count] - 1
|
|
1572
|
-
opt[:section] ||= nil
|
|
1573
|
-
opt[:format] ||= @default_date_format
|
|
1574
|
-
opt[:template] ||= @default_template
|
|
1575
1674
|
opt[:age] ||= 'newest'
|
|
1675
|
+
opt[:date_filter] ||= []
|
|
1676
|
+
opt[:format] ||= @default_date_format
|
|
1677
|
+
opt[:only_timed] ||= false
|
|
1576
1678
|
opt[:order] ||= 'desc'
|
|
1577
|
-
opt[:
|
|
1679
|
+
opt[:search] ||= false
|
|
1680
|
+
opt[:section] ||= nil
|
|
1681
|
+
opt[:sort_tags] ||= false
|
|
1578
1682
|
opt[:tag_filter] ||= false
|
|
1683
|
+
opt[:tag_order] ||= 'asc'
|
|
1579
1684
|
opt[:tags_color] ||= false
|
|
1685
|
+
opt[:template] ||= @default_template
|
|
1580
1686
|
opt[:times] ||= false
|
|
1687
|
+
opt[:today] ||= false
|
|
1581
1688
|
opt[:totals] ||= false
|
|
1582
|
-
opt[:sort_tags] ||= false
|
|
1583
|
-
opt[:search] ||= false
|
|
1584
|
-
opt[:only_timed] ||= false
|
|
1585
|
-
opt[:date_filter] ||= []
|
|
1586
1689
|
|
|
1587
1690
|
# opt[:highlight] ||= true
|
|
1588
1691
|
section = ''
|
|
@@ -1635,7 +1738,7 @@ class WWID
|
|
|
1635
1738
|
|
|
1636
1739
|
if opt[:only_timed]
|
|
1637
1740
|
items.delete_if do |item|
|
|
1638
|
-
get_interval(item) == false
|
|
1741
|
+
get_interval(item, record: false) == false
|
|
1639
1742
|
end
|
|
1640
1743
|
end
|
|
1641
1744
|
|
|
@@ -1670,7 +1773,7 @@ class WWID
|
|
|
1670
1773
|
arr = i['note'].map { |line| line.strip }.delete_if { |e| e =~ /^\s*$/ }
|
|
1671
1774
|
note = arr.join("\n") unless arr.nil?
|
|
1672
1775
|
end
|
|
1673
|
-
interval = get_interval(i, false) if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
|
1776
|
+
interval = get_interval(i, formatted: false) if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
|
1674
1777
|
interval ||= 0
|
|
1675
1778
|
output.push(CSV.generate_line([i['date'], i['title'], note, interval, i['section']]))
|
|
1676
1779
|
end
|
|
@@ -1689,7 +1792,7 @@ class WWID
|
|
|
1689
1792
|
end
|
|
1690
1793
|
if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
|
1691
1794
|
end_date = Time.parse(Regexp.last_match(1))
|
|
1692
|
-
interval = get_interval(i, false)
|
|
1795
|
+
interval = get_interval(i, formatted: false)
|
|
1693
1796
|
end
|
|
1694
1797
|
end_date ||= ''
|
|
1695
1798
|
interval ||= 0
|
|
@@ -1738,7 +1841,7 @@ class WWID
|
|
|
1738
1841
|
out = {
|
|
1739
1842
|
'section' => section,
|
|
1740
1843
|
'items' => items_out,
|
|
1741
|
-
'timers' => tag_times('json', opt[:sort_tags])
|
|
1844
|
+
'timers' => tag_times(format: 'json', sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order])
|
|
1742
1845
|
}.to_json
|
|
1743
1846
|
elsif opt[:output] == 'timeline'
|
|
1744
1847
|
template = <<~EOTEMPLATE
|
|
@@ -1819,7 +1922,7 @@ class WWID
|
|
|
1819
1922
|
css_template
|
|
1820
1923
|
end
|
|
1821
1924
|
|
|
1822
|
-
totals = opt[:totals] ? tag_times('html', opt[:sort_tags]) : ''
|
|
1925
|
+
totals = opt[:totals] ? tag_times(format: 'html', sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order]) : ''
|
|
1823
1926
|
engine = Haml::Engine.new(template)
|
|
1824
1927
|
out = engine.render(Object.new,
|
|
1825
1928
|
{ :@items => items_out, :@page_title => page_title, :@style => style, :@totals => totals })
|
|
@@ -1860,7 +1963,7 @@ class WWID
|
|
|
1860
1963
|
|
|
1861
1964
|
output.sub!(/%date/, item['date'].strftime(opt[:format]))
|
|
1862
1965
|
|
|
1863
|
-
interval = get_interval(item) if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
|
1966
|
+
interval = get_interval(item, record: true) if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
|
1864
1967
|
interval ||= ''
|
|
1865
1968
|
output.sub!(/%interval/, interval)
|
|
1866
1969
|
|
|
@@ -1910,7 +2013,8 @@ class WWID
|
|
|
1910
2013
|
|
|
1911
2014
|
out += "#{output}\n"
|
|
1912
2015
|
end
|
|
1913
|
-
|
|
2016
|
+
|
|
2017
|
+
out += tag_times(format: 'text', sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order]) if opt[:totals]
|
|
1914
2018
|
end
|
|
1915
2019
|
out
|
|
1916
2020
|
end
|
|
@@ -1922,7 +2026,7 @@ class WWID
|
|
|
1922
2026
|
## @param section (String) The source section
|
|
1923
2027
|
## @param options (Hash) Options
|
|
1924
2028
|
##
|
|
1925
|
-
def archive(section =
|
|
2029
|
+
def archive(section = @current_section, options = {})
|
|
1926
2030
|
count = options[:keep] || 0
|
|
1927
2031
|
destination = options[:destination] || 'Archive'
|
|
1928
2032
|
tags = options[:tags] || []
|
|
@@ -1937,7 +2041,7 @@ class WWID
|
|
|
1937
2041
|
destination = guess_section(destination)
|
|
1938
2042
|
|
|
1939
2043
|
if sections.include?(destination) && (sections.include?(section) || archive_all)
|
|
1940
|
-
do_archive(section, destination, { count: count, tags: tags, bool: bool, search: options[:search], label: options[:label] })
|
|
2044
|
+
do_archive(section, destination, { count: count, tags: tags, bool: bool, search: options[:search], label: options[:label], before: options[:before] })
|
|
1941
2045
|
write(doing_file)
|
|
1942
2046
|
else
|
|
1943
2047
|
exit_now! 'Either source or destination does not exist'
|
|
@@ -1970,9 +2074,15 @@ class WWID
|
|
|
1970
2074
|
items = @content[section]['items'].dup
|
|
1971
2075
|
|
|
1972
2076
|
moved_items = []
|
|
1973
|
-
if !tags.empty? || opt[:search]
|
|
2077
|
+
if !tags.empty? || opt[:search] || opt[:before]
|
|
2078
|
+
if opt[:before]
|
|
2079
|
+
time_string = opt[:before]
|
|
2080
|
+
time_string += ' 12am' if time_string !~ /(\d+:\d+|\d+[ap])/
|
|
2081
|
+
cutoff = chronify(time_string)
|
|
2082
|
+
end
|
|
2083
|
+
|
|
1974
2084
|
items.delete_if do |item|
|
|
1975
|
-
if (!tags.empty? && item.has_tags?(tags, bool) || (opt[:search] && item.matches_search?(opt[:search].to_s)))
|
|
2085
|
+
if ((!tags.empty? && item.has_tags?(tags, bool)) || (opt[:search] && item.matches_search?(opt[:search].to_s)) || (opt[:before] && item['date'] < cutoff))
|
|
1976
2086
|
moved_items.push(item)
|
|
1977
2087
|
counter += 1
|
|
1978
2088
|
true
|
|
@@ -1981,7 +2091,7 @@ class WWID
|
|
|
1981
2091
|
end
|
|
1982
2092
|
end
|
|
1983
2093
|
moved_items.each do |item|
|
|
1984
|
-
if label && section !=
|
|
2094
|
+
if label && section != @current_section
|
|
1985
2095
|
item['title'] =
|
|
1986
2096
|
item['title'].sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{section})")
|
|
1987
2097
|
end
|
|
@@ -1994,7 +2104,7 @@ class WWID
|
|
|
1994
2104
|
count = items.length if items.length < count
|
|
1995
2105
|
|
|
1996
2106
|
items.map! do |item|
|
|
1997
|
-
if label && section !=
|
|
2107
|
+
if label && section != @current_section
|
|
1998
2108
|
item['title'] =
|
|
1999
2109
|
item['title'].sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{section})")
|
|
2000
2110
|
end
|
|
@@ -2173,11 +2283,15 @@ class WWID
|
|
|
2173
2283
|
end
|
|
2174
2284
|
|
|
2175
2285
|
##
|
|
2176
|
-
## @brief Get total elapsed time for all tags in
|
|
2286
|
+
## @brief Get total elapsed time for all tags in
|
|
2287
|
+
## selection
|
|
2177
2288
|
##
|
|
2178
|
-
## @param format
|
|
2289
|
+
## @param format (String) return format (html,
|
|
2290
|
+
## json, or text)
|
|
2291
|
+
## @param sort_by_name (Boolean) Sort by name if true, otherwise by time
|
|
2292
|
+
## @param sort_order (String) The sort order (asc or desc)
|
|
2179
2293
|
##
|
|
2180
|
-
def tag_times(format
|
|
2294
|
+
def tag_times(format: 'text', sort_by_name: false, sort_order: 'asc')
|
|
2181
2295
|
return '' if @timers.empty?
|
|
2182
2296
|
|
|
2183
2297
|
max = @timers.keys.sort_by { |k| k.length }.reverse[0].length + 1
|
|
@@ -2186,11 +2300,13 @@ class WWID
|
|
|
2186
2300
|
|
|
2187
2301
|
tags_data = @timers.delete_if { |_k, v| v == 0 }
|
|
2188
2302
|
sorted_tags_data = if sort_by_name
|
|
2189
|
-
tags_data.sort_by { |k, _v| k }
|
|
2303
|
+
tags_data.sort_by { |k, _v| k }
|
|
2190
2304
|
else
|
|
2191
2305
|
tags_data.sort_by { |_k, v| v }
|
|
2192
2306
|
end
|
|
2193
2307
|
|
|
2308
|
+
sorted_tags_data.reverse! if sort_order =~ /^asc/i
|
|
2309
|
+
|
|
2194
2310
|
if format == 'html'
|
|
2195
2311
|
output = <<EOS
|
|
2196
2312
|
<table>
|
|
@@ -2324,19 +2440,20 @@ EOS
|
|
|
2324
2440
|
## @param item (Hash) The entry
|
|
2325
2441
|
## @param formatted (Bool) Return human readable time (default seconds)
|
|
2326
2442
|
##
|
|
2327
|
-
def get_interval(item, formatted
|
|
2443
|
+
def get_interval(item, formatted: true, record: true)
|
|
2328
2444
|
done = nil
|
|
2329
2445
|
start = nil
|
|
2330
2446
|
|
|
2331
2447
|
if @interval_cache.keys.include? item['title']
|
|
2332
2448
|
seconds = @interval_cache[item['title']]
|
|
2449
|
+
record_tag_times(item, seconds) if record
|
|
2333
2450
|
return seconds > 0 ? '%02d:%02d:%02d' % fmt_time(seconds) : false
|
|
2334
2451
|
end
|
|
2335
2452
|
|
|
2336
2453
|
if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
|
|
2337
2454
|
done = Time.parse(Regexp.last_match(1))
|
|
2338
2455
|
else
|
|
2339
|
-
return
|
|
2456
|
+
return false
|
|
2340
2457
|
end
|
|
2341
2458
|
|
|
2342
2459
|
start = if item['title'] =~ /@start\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
|
|
@@ -2347,20 +2464,34 @@ EOS
|
|
|
2347
2464
|
|
|
2348
2465
|
seconds = (done - start).to_i
|
|
2349
2466
|
|
|
2467
|
+
if record
|
|
2468
|
+
record_tag_times(item, seconds)
|
|
2469
|
+
end
|
|
2470
|
+
|
|
2471
|
+
@interval_cache[item['title']] = seconds
|
|
2472
|
+
|
|
2473
|
+
return seconds > 0 ? seconds : false unless formatted
|
|
2474
|
+
|
|
2475
|
+
seconds > 0 ? '%02d:%02d:%02d' % fmt_time(seconds) : false
|
|
2476
|
+
end
|
|
2477
|
+
|
|
2478
|
+
##
|
|
2479
|
+
## @brief Record times for item tags
|
|
2480
|
+
##
|
|
2481
|
+
## @param item The item
|
|
2482
|
+
##
|
|
2483
|
+
def record_tag_times(item, seconds)
|
|
2484
|
+
return if @recorded_items.include?(item)
|
|
2485
|
+
|
|
2350
2486
|
item['title'].scan(/(?mi)@(\S+?)(\(.*\))?(?=\s|$)/).each do |m|
|
|
2351
2487
|
k = m[0] == 'done' ? 'All' : m[0].downcase
|
|
2352
|
-
if @timers.
|
|
2488
|
+
if @timers.key?(k)
|
|
2353
2489
|
@timers[k] += seconds
|
|
2354
2490
|
else
|
|
2355
2491
|
@timers[k] = seconds
|
|
2356
2492
|
end
|
|
2493
|
+
@recorded_items.push(item)
|
|
2357
2494
|
end
|
|
2358
|
-
|
|
2359
|
-
@interval_cache[item['title']] = seconds
|
|
2360
|
-
|
|
2361
|
-
return seconds unless formatted
|
|
2362
|
-
|
|
2363
|
-
seconds > 0 ? '%02d:%02d:%02d' % fmt_time(seconds) : false
|
|
2364
2495
|
end
|
|
2365
2496
|
|
|
2366
2497
|
##
|
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.86
|
|
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
|