doing 1.0.81 → 1.0.85

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 860b1b1c3f8f1ffd33fc344cabb37a7a060cdcb80d54f65a8f3598450b4c22cf
4
- data.tar.gz: 83767946e503d9bd2515aa99a7e11de87c2910f48a1b2c6e5a745c9e7dada0be
3
+ metadata.gz: f11196376a7860f14164c80a107ce4151b18399f785cd607ba4f3a27205828fb
4
+ data.tar.gz: 1033e528416e1e3660db44ae55973009aa9b766f2566c0ff6e4bb942fdce4c05
5
5
  SHA512:
6
- metadata.gz: d78baf84b0bef668b7c877e908b6f1ee4ea05ce3914735f8d2e069abed215dfe5c9fc1fc3a9eb0dc5e4f5e251420d2454fb14a1b7465a5df8cf688e4b16b24ef
7
- data.tar.gz: b38cf4b873baeb35ecab15ef10b3c1b8be4a7d2e28f9b34511d30f50def0b47ef127829bc5755bb0b2ba424183959dd009a222958df03ee8e54680e2ce267ab6
6
+ metadata.gz: db7dc195eedf329ec7aad7e29830cf0d42c57178570a4675ce607e29761f733d2371b390f8c8ea73425a86240a28a91f7e05842ad9b49dd89257affbca12e5e6
7
+ data.tar.gz: 2b992ffea5a41c6f81ab7b5b1fb9b244197cca7eb1d7bbbae1eb6f9911db6ee16293b19e32c8c0b31cd03a3e181c4df9f8e1de893cc37c479fa2bdfd4accf6c4
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.79<!--END VER-->.
30
+ The current version of `doing` is <!--VER-->1.0.84<!--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: Old
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
  ![](http://ckyp.us/XKpj+)
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 file.
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
@@ -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], default_value: wwid.current_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
- section = wwid.guess_section(options[:s]) || options[:s].cap_first
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
- section = wwid.guess_section(options[:s]) || options[:s].cap_first
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], default_value: wwid.current_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
- section = wwid.guess_section(options[:s]) || options[:s].cap_first
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 format for export (doing|taskpaper|csv|html|json|template|timeline)'
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[:e] || (args.empty? && $stdin.stat.size.zero?)
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], default_value: wwid.current_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
- section = wwid.guess_section(options[:s]) || options[:s].cap_first
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[:e]
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], default_value: wwid.current_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
- section = wwid.guess_section(options[:s]) || options[:s].cap_first
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], default_value: wwid.current_section
611
+ c.flag %i[s section]
594
612
 
595
613
  c.action do |_global_options, options, args|
596
- section = wwid.guess_section(options[:s]) || options[:s].cap_first
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[:back]
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], default_value: wwid.current_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
@@ -1285,7 +1317,7 @@ command :last do |c|
1285
1317
 
1286
1318
  end
1287
1319
 
1288
- if options[:e]
1320
+ if options[:editor]
1289
1321
  wwid.edit_last(section: options[:s], options: { search: options[:search], tag: tags, tag_bool: options[:bool] })
1290
1322
  else
1291
1323
  puts wwid.last(times: true, section: options[:s],
@@ -1344,13 +1376,14 @@ command :colors do |c|
1344
1376
  end
1345
1377
 
1346
1378
  desc 'Display a user-created view'
1379
+ long_desc 'Command line options override associated view settings'
1347
1380
  arg_name 'VIEW_NAME'
1348
1381
  command :view do |c|
1349
- c.desc 'Section (override view settings)'
1382
+ c.desc 'Section'
1350
1383
  c.arg_name 'NAME'
1351
1384
  c.flag %i[s section]
1352
1385
 
1353
- c.desc 'Count to display (override view settings)'
1386
+ c.desc 'Count to display'
1354
1387
  c.arg_name 'COUNT'
1355
1388
  c.flag %i[c count], must_match: /^\d+$/, type: Integer
1356
1389
 
@@ -1368,12 +1401,14 @@ command :view do |c|
1368
1401
  c.switch [:color], default_value: true, negatable: true
1369
1402
 
1370
1403
  c.desc 'Sort tags by (name|time)'
1371
- default = 'time'
1372
- default = wwid.config['tag_sort'] if wwid.config.key?('tag_sort')
1373
1404
  c.arg_name 'KEY'
1374
- c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
1405
+ c.flag [:tag_sort], must_match: /^(?:name|time)$/i
1375
1406
 
1376
- c.desc 'Only show items with recorded time intervals'
1407
+ c.desc 'Tag sort direction (asc|desc)'
1408
+ c.arg_name 'DIRECTION'
1409
+ c.flag [:tag_order], must_match: /^(?:a(?:sc)?|d(?:esc)?)$/i
1410
+
1411
+ c.desc 'Only show items with recorded time intervals (override view settings)'
1377
1412
  c.switch [:only_timed], default_value: false, negatable: false
1378
1413
 
1379
1414
  c.action do |_global_options, options, args|
@@ -1383,7 +1418,11 @@ command :view do |c|
1383
1418
  wwid.guess_view(args[0])
1384
1419
  end
1385
1420
 
1386
- section = wwid.guess_section(options[:s]) || options[:s].cap_first if options[:s]
1421
+ if options[:section]
1422
+ section = wwid.guess_section(options[:section]) || options[:section].cap_first
1423
+ else
1424
+ section = wwid.config['current_section']
1425
+ end
1387
1426
 
1388
1427
  view = wwid.get_view(title)
1389
1428
  if view
@@ -1422,10 +1461,31 @@ command :view do |c|
1422
1461
  end
1423
1462
  order = view.key?('order') ? view['order'] : 'asc'
1424
1463
 
1425
- options[:t] = true if options[:totals]
1464
+ totals = if options[:totals]
1465
+ true
1466
+ else
1467
+ view.key?('totals') ? view['totals'] : false
1468
+ end
1469
+
1470
+ options[:t] = true if totals
1426
1471
  options[:output]&.downcase!
1427
- options[:sort_tags] = options[:tag_sort] =~ /^n/i
1428
1472
 
1473
+ options[:sort_tags] = if options[:tag_sort]
1474
+ options[:tag_sort] =~ /^n/i ? true : false
1475
+ elsif view.key?('tag_sort')
1476
+ view['tag_sort'] =~ /^n/i ? true : false
1477
+ else
1478
+ false
1479
+ end
1480
+
1481
+ tag_order = if options[:tag_order]
1482
+ options[:tag_order] =~ /^d/i ? 'desc' : 'asc'
1483
+ elsif view.key?('tag_order')
1484
+ view['tag_order'] =~ /^d/i ? 'desc' : 'asc'
1485
+ else
1486
+ 'asc'
1487
+ end
1488
+ warn "TAG ORDER: #{options[:tag_order]}"
1429
1489
  opts = {
1430
1490
  count: count,
1431
1491
  format: format,
@@ -1436,10 +1496,11 @@ command :view do |c|
1436
1496
  section: section,
1437
1497
  sort_tags: options[:sort_tags],
1438
1498
  tag_filter: tag_filter,
1499
+ tag_order: tag_order,
1439
1500
  tags_color: tags_color,
1440
1501
  template: template,
1441
1502
  times: options[:t],
1442
- totals: options[:totals]
1503
+ totals: totals
1443
1504
  }
1444
1505
 
1445
1506
  puts wwid.list_section(opts)
@@ -1489,6 +1550,11 @@ command :archive do |c|
1489
1550
  c.arg_name 'QUERY'
1490
1551
  c.flag [:search]
1491
1552
 
1553
+ c.desc 'Archive entries older than date
1554
+ (Flexible date format, e.g. 1/27/2021, 2020-07-19, or Monday 3pm)'
1555
+ c.arg_name 'DATE_STRING'
1556
+ c.flag [:before]
1557
+
1492
1558
  c.action do |_global_options, options, args|
1493
1559
  if args.empty?
1494
1560
  section = wwid.current_section
@@ -1518,6 +1584,7 @@ command :archive do |c|
1518
1584
  'AND'
1519
1585
  end
1520
1586
  opts = {
1587
+ before: options[:before],
1521
1588
  bool: options[:bool],
1522
1589
  destination: options[:to],
1523
1590
  keep: options[:keep],
@@ -1528,17 +1595,64 @@ command :archive do |c|
1528
1595
  end
1529
1596
  end
1530
1597
 
1598
+ desc 'Move entries to archive file'
1599
+ command :rotate do |c|
1600
+ c.desc 'How many items to keep in each section (most recent)'
1601
+ c.arg_name 'X'
1602
+ c.flag %i[k keep], must_match: /^\d+$/, type: Integer
1603
+
1604
+ c.desc 'Section to rotate'
1605
+ c.arg_name 'SECTION_NAME'
1606
+ c.flag %i[s section], default_value: 'All'
1607
+
1608
+ c.desc 'Tag filter, combine multiple tags with a comma. Added for compatibility with other commands.'
1609
+ c.arg_name 'TAG'
1610
+ c.flag [:tag]
1611
+
1612
+ c.desc 'Tag boolean (AND|OR|NOT)'
1613
+ c.arg_name 'BOOLEAN'
1614
+ c.flag [:bool], must_match: /(?:and|all|any|or|not|none)/i, default_value: 'AND'
1615
+
1616
+ c.desc 'Search filter'
1617
+ c.arg_name 'QUERY'
1618
+ c.flag [:search]
1619
+
1620
+ c.desc 'Rotate entries older than date
1621
+ (Flexible date format, e.g. 1/27/2021, 2020-07-19, or Monday 3pm)'
1622
+ c.arg_name 'DATE_STRING'
1623
+ c.flag [:before]
1624
+
1625
+ c.action do |_global_options, options, args|
1626
+ if options[:section] && options[:section] !~ /^all$/i
1627
+ options[:section] = wwid.guess_section(options[:section])
1628
+ end
1629
+
1630
+ options[:bool] = case options[:bool]
1631
+ when /(and|all)/i
1632
+ 'AND'
1633
+ when /(any|or)/i
1634
+ 'OR'
1635
+ when /(not|none)/i
1636
+ 'NOT'
1637
+ else
1638
+ 'AND'
1639
+ end
1640
+
1641
+ wwid.rotate(options)
1642
+ end
1643
+ end
1644
+
1531
1645
  desc 'Open the "doing" file in an editor'
1532
1646
  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
1647
  command :open do |c|
1534
1648
  if `uname` =~ /Darwin/
1535
1649
  c.desc 'Open with app name'
1536
1650
  c.arg_name 'APP_NAME'
1537
- c.flag [:a]
1651
+ c.flag %i[a app]
1538
1652
 
1539
1653
  c.desc 'Open with app bundle id'
1540
1654
  c.arg_name 'BUNDLE_ID'
1541
- c.flag [:b]
1655
+ c.flag %i[b bundle_id]
1542
1656
  end
1543
1657
  c.desc "Open with $EDITOR (#{ENV['EDITOR']})"
1544
1658
  c.switch %i[e editor], negatable: false, default_value: false
@@ -1549,11 +1663,11 @@ command :open do |c|
1549
1663
  k.instance_of?(String) || v.nil? || v == false
1550
1664
  end
1551
1665
  if `uname` =~ /Darwin/
1552
- if options[:a]
1666
+ if options[:app]
1553
1667
  system %(open -a "#{options[:a]}" "#{File.expand_path(wwid.doing_file)}")
1554
- elsif options[:b]
1668
+ elsif options[:bundle_id]
1555
1669
  system %(open -b "#{options[:b]}" "#{File.expand_path(wwid.doing_file)}")
1556
- elsif options[:e]
1670
+ elsif options[:editor]
1557
1671
  exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
1558
1672
 
1559
1673
  system %($EDITOR "#{File.expand_path(wwid.doing_file)}")
@@ -1637,12 +1751,15 @@ command :import do |c|
1637
1751
 
1638
1752
  c.desc 'Target section'
1639
1753
  c.arg_name 'NAME'
1640
- c.flag %i[s section], default_value: wwid.current_section
1754
+ c.flag %i[s section]
1641
1755
 
1642
1756
  c.desc 'Tag all imported entries'
1643
1757
  c.arg_name 'TAGS'
1644
1758
  c.flag :tag
1645
1759
 
1760
+ c.desc 'Autotag entries'
1761
+ c.switch :autotag, negatable: true, default_value: true
1762
+
1646
1763
  c.desc 'Prefix entries with'
1647
1764
  c.arg_name 'PREFIX'
1648
1765
  c.flag :prefix
@@ -1652,11 +1769,22 @@ command :import do |c|
1652
1769
 
1653
1770
  c.action do |_global_options, options, args|
1654
1771
 
1655
- section = wwid.guess_section(options[:s]) || options[:s].cap_first
1772
+ if options[:section]
1773
+ section = wwid.guess_section(options[:section]) || options[:section].cap_first
1774
+ else
1775
+ section = wwid.config['current_section']
1776
+ end
1656
1777
 
1657
1778
  if options[:type] =~ /^tim/i
1658
1779
  args.each do |path|
1659
- wwid.import_timing(path, { section: section, tag: options[:tag], prefix: options[:prefix], no_overlap: !options[:overlap] })
1780
+ options = {
1781
+ autotag: options[:autotag],
1782
+ no_overlap: !options[:overlap],
1783
+ prefix: options[:prefix],
1784
+ section: section,
1785
+ tag: options[:tag]
1786
+ }
1787
+ wwid.import_timing(path, options)
1660
1788
  wwid.write(wwid.doing_file)
1661
1789
  end
1662
1790
  else
data/lib/doing/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Doing
2
- VERSION = '1.0.81'
2
+ VERSION = '1.0.85'
3
3
  end
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 =~ /all/i
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 @auto_tag
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!
@@ -1098,7 +1100,6 @@ class WWID
1098
1100
  items = @content[section]['items'].dup.sort_by { |item| item['date'] }.reverse
1099
1101
  idx = 0
1100
1102
  done_date = Time.now
1101
- next_start = Time.now
1102
1103
  count = (opt[:count]).zero? ? items.length : opt[:count]
1103
1104
  items.map! do |item|
1104
1105
  break if idx == count
@@ -1117,8 +1118,8 @@ class WWID
1117
1118
  end
1118
1119
  else
1119
1120
  if opt[:sequential]
1120
- done_date = next_start - 1
1121
- next_start = item['date']
1121
+ next_entry = next_item(item)
1122
+ done_date = next_entry['date'] - 60 if next_entry
1122
1123
  elsif opt[:took]
1123
1124
  if item['date'] + opt[:took] > Time.now
1124
1125
  item['date'] = Time.now - opt[:took]
@@ -1207,6 +1208,24 @@ class WWID
1207
1208
  return new_item
1208
1209
  end
1209
1210
 
1211
+ ##
1212
+ ## @brief Get next item in the index
1213
+ ##
1214
+ ## @param old_item
1215
+ ##
1216
+ def next_item(old_item)
1217
+ section = old_item['section']
1218
+
1219
+ section_items = @content[section]['items'].sort_by { |entry| entry['date'] }
1220
+ idx = section_items.index(old_item)
1221
+
1222
+ if section_items.size > idx
1223
+ section_items[idx + 1]
1224
+ else
1225
+ nil
1226
+ end
1227
+ end
1228
+
1210
1229
  ##
1211
1230
  ## @brief Delete an item from the index
1212
1231
  ##
@@ -1476,7 +1495,7 @@ class WWID
1476
1495
  ##
1477
1496
  ## @param file (String) The filepath to write to
1478
1497
  ##
1479
- def write(file = nil)
1498
+ def write(file = nil, backup: true)
1480
1499
  output = @other_content_top ? "#{@other_content_top.join("\n")}\n" : ''
1481
1500
 
1482
1501
  @content.each do |title, section|
@@ -1488,7 +1507,7 @@ class WWID
1488
1507
  $stdout.puts output
1489
1508
  else
1490
1509
  file = File.expand_path(file)
1491
- if File.exist?(file)
1510
+ if File.exist?(file) && backup
1492
1511
  # Create a backup copy for the undo command
1493
1512
  FileUtils.cp(file, "#{file}~")
1494
1513
  end
@@ -1520,6 +1539,88 @@ class WWID
1520
1539
  end
1521
1540
  end
1522
1541
 
1542
+ ##
1543
+ ## @brief Rename doing file with date and start fresh one
1544
+ ##
1545
+ def rotate(opt = {})
1546
+ count = opt[:keep] || 0
1547
+ tags = []
1548
+ tags.concat(opt[:tag].split(/ *, */).map { |t| t.sub(/^@/, '').strip }) if opt[:tag]
1549
+ bool = opt[:bool] || :and
1550
+ sect = opt[:section] !~ /^all$/i ? guess_section(opt[:section]) : 'all'
1551
+
1552
+ if sect =~ /^all$/i
1553
+ all_sections = sections.dup
1554
+ else
1555
+ all_sections = [sect]
1556
+ end
1557
+
1558
+ counter = 0
1559
+ new_content = {}
1560
+
1561
+
1562
+ all_sections.each do |section|
1563
+ items = @content[section]['items'].dup
1564
+ new_content[section] = {}
1565
+ new_content[section]['original'] = @content[section]['original']
1566
+ new_content[section]['items'] = []
1567
+
1568
+ moved_items = []
1569
+ if !tags.empty? || opt[:search] || opt[:before]
1570
+ if opt[:before]
1571
+ time_string = opt[:before]
1572
+ time_string += ' 12am' if time_string !~ /(\d+:\d+|\d+[ap])/
1573
+ cutoff = chronify(time_string)
1574
+ end
1575
+
1576
+ items.delete_if do |item|
1577
+ if ((!tags.empty? && item.has_tags?(tags, bool)) || (opt[:search] && item.matches_search?(opt[:search].to_s)) || (opt[:before] && item['date'] < cutoff))
1578
+ moved_items.push(item)
1579
+ counter += 1
1580
+ true
1581
+ else
1582
+ false
1583
+ end
1584
+ end
1585
+ @content[section]['items'] = items
1586
+ new_content[section]['items'] = moved_items
1587
+ @results.push("Rotated #{moved_items.length} items from #{section}")
1588
+ else
1589
+ new_content[section]['items'] = []
1590
+ moved_items = []
1591
+
1592
+ count = items.length if items.length < count
1593
+
1594
+ if items.count > count
1595
+ moved_items.concat(items[count..-1])
1596
+ else
1597
+ moved_items.concat(items)
1598
+ end
1599
+
1600
+ @content[section]['items'] = if count.zero?
1601
+ []
1602
+ else
1603
+ items[0..count - 1]
1604
+ end
1605
+ new_content[section]['items'] = moved_items
1606
+
1607
+ @results.push("Rotated #{items.length - count} items from #{section}")
1608
+ end
1609
+ end
1610
+
1611
+ write(@doing_file)
1612
+
1613
+ file = @doing_file.sub(/(\.\w+)$/, "_#{Time.now.strftime('%Y-%m-%d')}\\1")
1614
+ if File.exist?(file)
1615
+ init_doing_file(file)
1616
+ @content.deep_merge(new_content)
1617
+ else
1618
+ @content = new_content
1619
+ end
1620
+
1621
+ write(file, backup: false)
1622
+ end
1623
+
1523
1624
  ##
1524
1625
  ## @brief Generate a menu of sections and allow user selection
1525
1626
  ##
@@ -1569,20 +1670,21 @@ class WWID
1569
1670
  def list_section(opt = {})
1570
1671
  opt[:count] ||= 0
1571
1672
  count = opt[:count] - 1
1572
- opt[:section] ||= nil
1573
- opt[:format] ||= @default_date_format
1574
- opt[:template] ||= @default_template
1575
1673
  opt[:age] ||= 'newest'
1674
+ opt[:date_filter] ||= []
1675
+ opt[:format] ||= @default_date_format
1676
+ opt[:only_timed] ||= false
1576
1677
  opt[:order] ||= 'desc'
1577
- opt[:today] ||= false
1678
+ opt[:search] ||= false
1679
+ opt[:section] ||= nil
1680
+ opt[:sort_tags] ||= false
1578
1681
  opt[:tag_filter] ||= false
1682
+ opt[:tag_order] ||= 'asc'
1579
1683
  opt[:tags_color] ||= false
1684
+ opt[:template] ||= @default_template
1580
1685
  opt[:times] ||= false
1686
+ opt[:today] ||= false
1581
1687
  opt[:totals] ||= false
1582
- opt[:sort_tags] ||= false
1583
- opt[:search] ||= false
1584
- opt[:only_timed] ||= false
1585
- opt[:date_filter] ||= []
1586
1688
 
1587
1689
  # opt[:highlight] ||= true
1588
1690
  section = ''
@@ -1635,7 +1737,7 @@ class WWID
1635
1737
 
1636
1738
  if opt[:only_timed]
1637
1739
  items.delete_if do |item|
1638
- get_interval(item) == false
1740
+ get_interval(item, record: false) == false
1639
1741
  end
1640
1742
  end
1641
1743
 
@@ -1670,7 +1772,7 @@ class WWID
1670
1772
  arr = i['note'].map { |line| line.strip }.delete_if { |e| e =~ /^\s*$/ }
1671
1773
  note = arr.join("\n") unless arr.nil?
1672
1774
  end
1673
- interval = get_interval(i, false) if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1775
+ interval = get_interval(i, formatted: false) if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1674
1776
  interval ||= 0
1675
1777
  output.push(CSV.generate_line([i['date'], i['title'], note, interval, i['section']]))
1676
1778
  end
@@ -1689,7 +1791,7 @@ class WWID
1689
1791
  end
1690
1792
  if i['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1691
1793
  end_date = Time.parse(Regexp.last_match(1))
1692
- interval = get_interval(i, false)
1794
+ interval = get_interval(i, formatted: false)
1693
1795
  end
1694
1796
  end_date ||= ''
1695
1797
  interval ||= 0
@@ -1738,7 +1840,7 @@ class WWID
1738
1840
  out = {
1739
1841
  'section' => section,
1740
1842
  'items' => items_out,
1741
- 'timers' => tag_times('json', opt[:sort_tags])
1843
+ 'timers' => tag_times(format: 'json', sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order])
1742
1844
  }.to_json
1743
1845
  elsif opt[:output] == 'timeline'
1744
1846
  template = <<~EOTEMPLATE
@@ -1819,7 +1921,7 @@ class WWID
1819
1921
  css_template
1820
1922
  end
1821
1923
 
1822
- totals = opt[:totals] ? tag_times('html', opt[:sort_tags]) : ''
1924
+ totals = opt[:totals] ? tag_times(format: 'html', sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order]) : ''
1823
1925
  engine = Haml::Engine.new(template)
1824
1926
  out = engine.render(Object.new,
1825
1927
  { :@items => items_out, :@page_title => page_title, :@style => style, :@totals => totals })
@@ -1860,7 +1962,7 @@ class WWID
1860
1962
 
1861
1963
  output.sub!(/%date/, item['date'].strftime(opt[:format]))
1862
1964
 
1863
- interval = get_interval(item) if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1965
+ interval = get_interval(item, record: true) if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
1864
1966
  interval ||= ''
1865
1967
  output.sub!(/%interval/, interval)
1866
1968
 
@@ -1910,7 +2012,8 @@ class WWID
1910
2012
 
1911
2013
  out += "#{output}\n"
1912
2014
  end
1913
- out += tag_times('text', opt[:sort_tags]) if opt[:totals]
2015
+
2016
+ out += tag_times(format: 'text', sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order]) if opt[:totals]
1914
2017
  end
1915
2018
  out
1916
2019
  end
@@ -1922,7 +2025,7 @@ class WWID
1922
2025
  ## @param section (String) The source section
1923
2026
  ## @param options (Hash) Options
1924
2027
  ##
1925
- def archive(section = 'Currently', options = {})
2028
+ def archive(section = @current_section, options = {})
1926
2029
  count = options[:keep] || 0
1927
2030
  destination = options[:destination] || 'Archive'
1928
2031
  tags = options[:tags] || []
@@ -1937,7 +2040,7 @@ class WWID
1937
2040
  destination = guess_section(destination)
1938
2041
 
1939
2042
  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] })
2043
+ do_archive(section, destination, { count: count, tags: tags, bool: bool, search: options[:search], label: options[:label], before: options[:before] })
1941
2044
  write(doing_file)
1942
2045
  else
1943
2046
  exit_now! 'Either source or destination does not exist'
@@ -1970,9 +2073,15 @@ class WWID
1970
2073
  items = @content[section]['items'].dup
1971
2074
 
1972
2075
  moved_items = []
1973
- if !tags.empty? || opt[:search]
2076
+ if !tags.empty? || opt[:search] || opt[:before]
2077
+ if opt[:before]
2078
+ time_string = opt[:before]
2079
+ time_string += ' 12am' if time_string !~ /(\d+:\d+|\d+[ap])/
2080
+ cutoff = chronify(time_string)
2081
+ end
2082
+
1974
2083
  items.delete_if do |item|
1975
- if (!tags.empty? && item.has_tags?(tags, bool) || (opt[:search] && item.matches_search?(opt[:search].to_s)))
2084
+ if ((!tags.empty? && item.has_tags?(tags, bool)) || (opt[:search] && item.matches_search?(opt[:search].to_s)) || (opt[:before] && item['date'] < cutoff))
1976
2085
  moved_items.push(item)
1977
2086
  counter += 1
1978
2087
  true
@@ -1981,7 +2090,7 @@ class WWID
1981
2090
  end
1982
2091
  end
1983
2092
  moved_items.each do |item|
1984
- if label && section != 'Currently'
2093
+ if label && section != @current_section
1985
2094
  item['title'] =
1986
2095
  item['title'].sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{section})")
1987
2096
  end
@@ -1994,7 +2103,7 @@ class WWID
1994
2103
  count = items.length if items.length < count
1995
2104
 
1996
2105
  items.map! do |item|
1997
- if label && section != 'Currently'
2106
+ if label && section != @current_section
1998
2107
  item['title'] =
1999
2108
  item['title'].sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{section})")
2000
2109
  end
@@ -2173,11 +2282,15 @@ class WWID
2173
2282
  end
2174
2283
 
2175
2284
  ##
2176
- ## @brief Get total elapsed time for all tags in selection
2285
+ ## @brief Get total elapsed time for all tags in
2286
+ ## selection
2177
2287
  ##
2178
- ## @param format (String) return format (html, json, or text)
2288
+ ## @param format (String) return format (html,
2289
+ ## json, or text)
2290
+ ## @param sort_by_name (Boolean) Sort by name if true, otherwise by time
2291
+ ## @param sort_order (String) The sort order (asc or desc)
2179
2292
  ##
2180
- def tag_times(format = 'text', sort_by_name = false)
2293
+ def tag_times(format: 'text', sort_by_name: false, sort_order: 'asc')
2181
2294
  return '' if @timers.empty?
2182
2295
 
2183
2296
  max = @timers.keys.sort_by { |k| k.length }.reverse[0].length + 1
@@ -2186,11 +2299,13 @@ class WWID
2186
2299
 
2187
2300
  tags_data = @timers.delete_if { |_k, v| v == 0 }
2188
2301
  sorted_tags_data = if sort_by_name
2189
- tags_data.sort_by { |k, _v| k }.reverse
2302
+ tags_data.sort_by { |k, _v| k }
2190
2303
  else
2191
2304
  tags_data.sort_by { |_k, v| v }
2192
2305
  end
2193
2306
 
2307
+ sorted_tags_data.reverse! if sort_order =~ /^asc/i
2308
+
2194
2309
  if format == 'html'
2195
2310
  output = <<EOS
2196
2311
  <table>
@@ -2324,19 +2439,20 @@ EOS
2324
2439
  ## @param item (Hash) The entry
2325
2440
  ## @param formatted (Bool) Return human readable time (default seconds)
2326
2441
  ##
2327
- def get_interval(item, formatted = true)
2442
+ def get_interval(item, formatted: true, record: true)
2328
2443
  done = nil
2329
2444
  start = nil
2330
2445
 
2331
2446
  if @interval_cache.keys.include? item['title']
2332
2447
  seconds = @interval_cache[item['title']]
2448
+ record_tag_times(item, seconds) if record
2333
2449
  return seconds > 0 ? '%02d:%02d:%02d' % fmt_time(seconds) : false
2334
2450
  end
2335
2451
 
2336
2452
  if item['title'] =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
2337
2453
  done = Time.parse(Regexp.last_match(1))
2338
2454
  else
2339
- return nil
2455
+ return false
2340
2456
  end
2341
2457
 
2342
2458
  start = if item['title'] =~ /@start\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
@@ -2347,20 +2463,34 @@ EOS
2347
2463
 
2348
2464
  seconds = (done - start).to_i
2349
2465
 
2466
+ if record
2467
+ record_tag_times(item, seconds)
2468
+ end
2469
+
2470
+ @interval_cache[item['title']] = seconds
2471
+
2472
+ return seconds > 0 ? seconds : false unless formatted
2473
+
2474
+ seconds > 0 ? '%02d:%02d:%02d' % fmt_time(seconds) : false
2475
+ end
2476
+
2477
+ ##
2478
+ ## @brief Record times for item tags
2479
+ ##
2480
+ ## @param item The item
2481
+ ##
2482
+ def record_tag_times(item, seconds)
2483
+ return if @recorded_items.include?(item)
2484
+
2350
2485
  item['title'].scan(/(?mi)@(\S+?)(\(.*\))?(?=\s|$)/).each do |m|
2351
2486
  k = m[0] == 'done' ? 'All' : m[0].downcase
2352
- if @timers.has_key?(k)
2487
+ if @timers.key?(k)
2353
2488
  @timers[k] += seconds
2354
2489
  else
2355
2490
  @timers[k] = seconds
2356
2491
  end
2492
+ @recorded_items.push(item)
2357
2493
  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
2494
  end
2365
2495
 
2366
2496
  ##
data/lib/doing.rb CHANGED
@@ -10,4 +10,4 @@ require 'haml'
10
10
  require 'json'
11
11
  require 'doing/helpers'
12
12
  require 'doing/wwid'
13
- require 'doing/markdown_document_listener'
13
+ # require 'doing/markdown_document_listener'
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.81
4
+ version: 1.0.85
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-09-26 00:00:00.000000000 Z
11
+ date: 2021-10-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake