doing 1.0.83 → 1.0.87

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: fb85f27f305f246d4ab182268c9e5188da67f22fde46d3ee4ae716996c78b2c3
4
- data.tar.gz: 7b302748d9837511adc77679fd619c6daf85b2f7e15f541d530c8cc211e0aac3
3
+ metadata.gz: 2540726a17efa50ef0cddc29353d8c14a4d921a89fb302711225053faacfbc4d
4
+ data.tar.gz: e4f59cf1e2feda50df653bbbd981e1ab4ad75d807eaf47a8afbad9d98032d27b
5
5
  SHA512:
6
- metadata.gz: dbf5e04626a97e5287c2417cf5afffd6516860a1fa5f3ed64e93fe15bdc62b85f045d0a54adc08c774b4d711add29f02799cf3d4a2c22853e1273a691d0a9f00
7
- data.tar.gz: 87bf6eac2e770b161c8ae4fd2fee4adea5ed261b828f2142997858393a627e0d36788981fe52a4c66486f442da2de349ce1ac157732d824b0ef633e4dcbdc703
6
+ metadata.gz: dabcbefd24a3d099d71eb14c03f71250067e509b385d573405219d3cfaea8a66f6ec526a32547401f249f0fdd82774415ed0da1c95e233aada2f54135de49558
7
+ data.tar.gz: 5b5a5d7800110de9aeaf636b6b6b4947c1923e55ce4f455d2316483c37fcfb30461b818df78c24097ef66f574ed57bdeebf50f77e4f806161212c4d36c86eabd
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.82<!--END VER-->.
30
+ The current version of `doing` is <!--VER-->1.0.86<!--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,6 +240,8 @@ 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:
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
@@ -864,10 +896,22 @@ command :show do |c|
864
896
  c.arg_name 'MAX'
865
897
  c.flag %i[c count], default_value: 0
866
898
 
867
- c.desc 'Age (oldest/newest)'
899
+ c.desc 'Age (oldest|newest)'
868
900
  c.arg_name 'AGE'
869
901
  c.flag %i[a age], default_value: 'newest'
870
902
 
903
+ c.desc 'View entries older than date'
904
+ c.arg_name 'DATE_STRING'
905
+ c.flag [:before]
906
+
907
+ c.desc 'View entries newer than date'
908
+ c.arg_name 'DATE_STRING'
909
+ c.flag [:after]
910
+
911
+ c.desc 'Search filter, surround with slashes for regex (/query/)'
912
+ c.arg_name 'QUERY'
913
+ c.flag [:search]
914
+
871
915
  c.desc 'Sort order (asc/desc)'
872
916
  c.arg_name 'ORDER'
873
917
  c.flag %i[s sort], must_match: /^[ad].*/i, default_value: 'ASC'
@@ -892,6 +936,10 @@ command :show do |c|
892
936
  c.arg_name 'KEY'
893
937
  c.flag [:tag_sort], must_match: /^(?:name|time)/i, default_value: default
894
938
 
939
+ c.desc 'Tag sort direction (asc|desc)'
940
+ c.arg_name 'DIRECTION'
941
+ c.flag [:tag_order], must_match: /^(?:a(?:sc)?|d(?:esc)?)$/i
942
+
895
943
  c.desc 'Only show items with recorded time intervals'
896
944
  c.switch [:only_timed], default_value: false, negatable: false
897
945
 
@@ -947,8 +995,8 @@ command :show do |c|
947
995
  }
948
996
  end
949
997
 
950
- if options[:f]
951
- date_string = options[:f]
998
+ if options[:from]
999
+ date_string = options[:from]
952
1000
  if date_string =~ / (to|through|thru|(un)?til|-+) /
953
1001
  dates = date_string.split(/ (to|through|thru|(un)?til|-+) /)
954
1002
  start = wwid.chronify(dates[0])
@@ -961,22 +1009,31 @@ command :show do |c|
961
1009
  dates = [start, finish]
962
1010
  end
963
1011
 
964
- options[:t] = true if options[:totals]
1012
+ options[:times] = true if options[:totals]
965
1013
 
966
1014
  tags_color = wwid.config.key?('tags_color') ? wwid.config['tags_color'] : nil
967
1015
 
968
1016
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
1017
+ tag_order = if options[:tag_order]
1018
+ options[:tag_order] =~ /^d/i ? 'desc' : 'asc'
1019
+ else
1020
+ 'asc'
1021
+ end
969
1022
  opts = {
970
- age: options[:a],
1023
+ after: options[:after],
1024
+ age: options[:age],
1025
+ before: options[:before],
971
1026
  count: options[:c].to_i,
972
1027
  date_filter: dates,
973
1028
  highlight: true,
974
1029
  only_timed: options[:only_timed],
975
1030
  order: options[:s],
976
1031
  output: options[:output],
1032
+ search: options[:search],
977
1033
  section: section,
978
1034
  sort_tags: options[:sort_tags],
979
1035
  tag_filter: tag_filter,
1036
+ tag_order: tag_order,
980
1037
  tags_color: tags_color,
981
1038
  times: options[:t],
982
1039
  totals: options[:totals]
@@ -998,6 +1055,14 @@ command [:grep, :search] do |c|
998
1055
  c.arg_name 'NAME'
999
1056
  c.flag %i[s section], default_value: 'All'
1000
1057
 
1058
+ c.desc 'Constrain search to entries older than date'
1059
+ c.arg_name 'DATE_STRING'
1060
+ c.flag [:before]
1061
+
1062
+ c.desc 'Constrain search to entries newer than date'
1063
+ c.arg_name 'DATE_STRING'
1064
+ c.flag [:after]
1065
+
1001
1066
  c.desc 'Output to export format (csv|html|json|template|timeline)'
1002
1067
  c.arg_name 'FORMAT'
1003
1068
  c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
@@ -1022,10 +1087,12 @@ command [:grep, :search] do |c|
1022
1087
 
1023
1088
  section = wwid.guess_section(options[:s]) if options[:s]
1024
1089
 
1025
- options[:t] = true if options[:totals]
1090
+ options[:times] = true if options[:totals]
1026
1091
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
1027
1092
 
1028
1093
  opts = {
1094
+ after: options[:after],
1095
+ before: options[:before],
1029
1096
  highlight: true,
1030
1097
  only_timed: options[:only_timed],
1031
1098
  output: options[:output],
@@ -1033,7 +1100,7 @@ command [:grep, :search] do |c|
1033
1100
  section: section,
1034
1101
  sort_tags: options[:sort_tags],
1035
1102
  tags_color: tags_color,
1036
- times: options[:t],
1103
+ times: options[:times],
1037
1104
  totals: options[:totals]
1038
1105
  }
1039
1106
 
@@ -1065,7 +1132,12 @@ command :recent do |c|
1065
1132
  section = wwid.guess_section(options[:s]) || options[:s].cap_first
1066
1133
 
1067
1134
  unless global_options[:version]
1068
- count = args.empty? ? 10 : args[0].to_i
1135
+ if wwid.config['templates']['recent'].key?('count')
1136
+ config_count = wwid.config['templates']['recent']['count'].to_i
1137
+ else
1138
+ config_count = 10
1139
+ end
1140
+ count = args.empty? ? config_count : args[0].to_i
1069
1141
  options[:t] = true if options[:totals]
1070
1142
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
1071
1143
  tags_color = wwid.config.key?('tags_color') ? wwid.config['tags_color'] : nil
@@ -1105,12 +1177,25 @@ command :today do |c|
1105
1177
  c.arg_name 'FORMAT'
1106
1178
  c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
1107
1179
 
1180
+ c.desc 'View entries before specified time (e.g. 8am, 12:30pm, 15:00)'
1181
+ c.arg_name 'TIME_STRING'
1182
+ c.flag [:before]
1183
+
1184
+ c.desc 'View entries after specified time (e.g. 8am, 12:30pm, 15:00)'
1185
+ c.arg_name 'TIME_STRING'
1186
+ c.flag [:after]
1187
+
1108
1188
  c.action do |_global_options, options, _args|
1109
1189
  options[:t] = true if options[:totals]
1110
1190
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
1111
-
1112
- puts wwid.today(options[:t], options[:output],
1113
- { totals: options[:totals], section: options[:s], sort_tags: options[:sort_tags] }).chomp
1191
+ opt = {
1192
+ after: options[:after],
1193
+ before: options[:before],
1194
+ section: options[:section],
1195
+ sort_tags: options[:sort_tags],
1196
+ totals: options[:totals]
1197
+ }
1198
+ puts wwid.today(options[:times], options[:output], opt).chomp
1114
1199
  end
1115
1200
  end
1116
1201
 
@@ -1285,7 +1370,7 @@ command :last do |c|
1285
1370
 
1286
1371
  end
1287
1372
 
1288
- if options[:e]
1373
+ if options[:editor]
1289
1374
  wwid.edit_last(section: options[:s], options: { search: options[:search], tag: tags, tag_bool: options[:bool] })
1290
1375
  else
1291
1376
  puts wwid.last(times: true, section: options[:s],
@@ -1344,7 +1429,7 @@ command :colors do |c|
1344
1429
  end
1345
1430
 
1346
1431
  desc 'Display a user-created view'
1347
- long_desc 'Command line options override associated view settings'
1432
+ long_desc 'Command line options override view configuration'
1348
1433
  arg_name 'VIEW_NAME'
1349
1434
  command :view do |c|
1350
1435
  c.desc 'Section'
@@ -1368,6 +1453,18 @@ command :view do |c|
1368
1453
  c.desc 'Include colors in output'
1369
1454
  c.switch [:color], default_value: true, negatable: true
1370
1455
 
1456
+ c.desc 'Tag filter, combine multiple tags with a comma.'
1457
+ c.arg_name 'TAG'
1458
+ c.flag [:tag]
1459
+
1460
+ c.desc 'Tag boolean (AND,OR,NOT)'
1461
+ c.arg_name 'BOOLEAN'
1462
+ c.flag %i[b bool], must_match: /(?:and|all|any|or|not|none)/i, default_value: 'OR'
1463
+
1464
+ c.desc 'Search filter, surround with slashes for regex (/query/)'
1465
+ c.arg_name 'QUERY'
1466
+ c.flag [:search]
1467
+
1371
1468
  c.desc 'Sort tags by (name|time)'
1372
1469
  c.arg_name 'KEY'
1373
1470
  c.flag [:tag_sort], must_match: /^(?:name|time)$/i
@@ -1376,17 +1473,31 @@ command :view do |c|
1376
1473
  c.arg_name 'DIRECTION'
1377
1474
  c.flag [:tag_order], must_match: /^(?:a(?:sc)?|d(?:esc)?)$/i
1378
1475
 
1476
+ c.desc 'View entries older than date'
1477
+ c.arg_name 'DATE_STRING'
1478
+ c.flag [:before]
1479
+
1480
+ c.desc 'View entries newer than date'
1481
+ c.arg_name 'DATE_STRING'
1482
+ c.flag [:after]
1483
+
1379
1484
  c.desc 'Only show items with recorded time intervals (override view settings)'
1380
1485
  c.switch [:only_timed], default_value: false, negatable: false
1381
1486
 
1382
1487
  c.action do |_global_options, options, args|
1488
+ exit_now! '--tag and --search cannot be used together' if options[:tag] && options[:search]
1489
+
1383
1490
  title = if args.empty?
1384
1491
  wwid.choose_view
1385
1492
  else
1386
1493
  wwid.guess_view(args[0])
1387
1494
  end
1388
1495
 
1389
- section = wwid.guess_section(options[:s]) || options[:s].cap_first if options[:s]
1496
+ if options[:section]
1497
+ section = wwid.guess_section(options[:section]) || options[:section].cap_first
1498
+ else
1499
+ section = wwid.config['current_section']
1500
+ end
1390
1501
 
1391
1502
  view = wwid.get_view(title)
1392
1503
  if view
@@ -1400,7 +1511,11 @@ command :view do |c|
1400
1511
  format = view.key?('date_format') ? view['date_format'] : nil
1401
1512
  tags_color = view.key?('tags_color') ? view['tags_color'] : nil
1402
1513
  tag_filter = false
1403
- if view.key?('tags') && !(view['tags'].nil? || view['tags'].empty?)
1514
+ if options[:tag]
1515
+ tag_filter = { 'tags' => [], 'bool' => 'OR' }
1516
+ tag_filter['tags'] = options[:tag].gsub(/[, ]+/, ' ').split(' ').map(&:strip)
1517
+ tag_filter['bool'] = options[:bool].normalize_bool
1518
+ elsif view.key?('tags') && !(view['tags'].nil? || view['tags'].empty?)
1404
1519
  tag_filter = { 'tags' => [], 'bool' => 'OR' }
1405
1520
  tag_filter['tags'] = if view['tags'].instance_of?(Array)
1406
1521
  view['tags'].map(&:strip)
@@ -1449,14 +1564,17 @@ command :view do |c|
1449
1564
  else
1450
1565
  'asc'
1451
1566
  end
1452
- warn "TAG ORDER: #{options[:tag_order]}"
1567
+
1453
1568
  opts = {
1569
+ after: options[:after],
1570
+ before: options[:before],
1454
1571
  count: count,
1455
1572
  format: format,
1456
1573
  highlight: options[:color],
1457
1574
  only_timed: only_timed,
1458
1575
  order: order,
1459
- output: options[:o],
1576
+ output: options[:output],
1577
+ search: options[:search],
1460
1578
  section: section,
1461
1579
  sort_tags: options[:sort_tags],
1462
1580
  tag_filter: tag_filter,
@@ -1514,6 +1632,11 @@ command :archive do |c|
1514
1632
  c.arg_name 'QUERY'
1515
1633
  c.flag [:search]
1516
1634
 
1635
+ c.desc 'Archive entries older than date
1636
+ (Flexible date format, e.g. 1/27/2021, 2020-07-19, or Monday 3pm)'
1637
+ c.arg_name 'DATE_STRING'
1638
+ c.flag [:before]
1639
+
1517
1640
  c.action do |_global_options, options, args|
1518
1641
  if args.empty?
1519
1642
  section = wwid.current_section
@@ -1543,6 +1666,7 @@ command :archive do |c|
1543
1666
  'AND'
1544
1667
  end
1545
1668
  opts = {
1669
+ before: options[:before],
1546
1670
  bool: options[:bool],
1547
1671
  destination: options[:to],
1548
1672
  keep: options[:keep],
@@ -1553,17 +1677,64 @@ command :archive do |c|
1553
1677
  end
1554
1678
  end
1555
1679
 
1680
+ desc 'Move entries to archive file'
1681
+ command :rotate do |c|
1682
+ c.desc 'How many items to keep in each section (most recent)'
1683
+ c.arg_name 'X'
1684
+ c.flag %i[k keep], must_match: /^\d+$/, type: Integer
1685
+
1686
+ c.desc 'Section to rotate'
1687
+ c.arg_name 'SECTION_NAME'
1688
+ c.flag %i[s section], default_value: 'All'
1689
+
1690
+ c.desc 'Tag filter, combine multiple tags with a comma. Added for compatibility with other commands.'
1691
+ c.arg_name 'TAG'
1692
+ c.flag [:tag]
1693
+
1694
+ c.desc 'Tag boolean (AND|OR|NOT)'
1695
+ c.arg_name 'BOOLEAN'
1696
+ c.flag [:bool], must_match: /(?:and|all|any|or|not|none)/i, default_value: 'AND'
1697
+
1698
+ c.desc 'Search filter'
1699
+ c.arg_name 'QUERY'
1700
+ c.flag [:search]
1701
+
1702
+ c.desc 'Rotate entries older than date
1703
+ (Flexible date format, e.g. 1/27/2021, 2020-07-19, or Monday 3pm)'
1704
+ c.arg_name 'DATE_STRING'
1705
+ c.flag [:before]
1706
+
1707
+ c.action do |_global_options, options, args|
1708
+ if options[:section] && options[:section] !~ /^all$/i
1709
+ options[:section] = wwid.guess_section(options[:section])
1710
+ end
1711
+
1712
+ options[:bool] = case options[:bool]
1713
+ when /(and|all)/i
1714
+ 'AND'
1715
+ when /(any|or)/i
1716
+ 'OR'
1717
+ when /(not|none)/i
1718
+ 'NOT'
1719
+ else
1720
+ 'AND'
1721
+ end
1722
+
1723
+ wwid.rotate(options)
1724
+ end
1725
+ end
1726
+
1556
1727
  desc 'Open the "doing" file in an editor'
1557
1728
  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'})"
1558
1729
  command :open do |c|
1559
1730
  if `uname` =~ /Darwin/
1560
1731
  c.desc 'Open with app name'
1561
1732
  c.arg_name 'APP_NAME'
1562
- c.flag [:a]
1733
+ c.flag %i[a app]
1563
1734
 
1564
1735
  c.desc 'Open with app bundle id'
1565
1736
  c.arg_name 'BUNDLE_ID'
1566
- c.flag [:b]
1737
+ c.flag %i[b bundle_id]
1567
1738
  end
1568
1739
  c.desc "Open with $EDITOR (#{ENV['EDITOR']})"
1569
1740
  c.switch %i[e editor], negatable: false, default_value: false
@@ -1574,11 +1745,11 @@ command :open do |c|
1574
1745
  k.instance_of?(String) || v.nil? || v == false
1575
1746
  end
1576
1747
  if `uname` =~ /Darwin/
1577
- if options[:a]
1748
+ if options[:app]
1578
1749
  system %(open -a "#{options[:a]}" "#{File.expand_path(wwid.doing_file)}")
1579
- elsif options[:b]
1750
+ elsif options[:bundle_id]
1580
1751
  system %(open -b "#{options[:b]}" "#{File.expand_path(wwid.doing_file)}")
1581
- elsif options[:e]
1752
+ elsif options[:editor]
1582
1753
  exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
1583
1754
 
1584
1755
  system %($EDITOR "#{File.expand_path(wwid.doing_file)}")
@@ -1662,12 +1833,15 @@ command :import do |c|
1662
1833
 
1663
1834
  c.desc 'Target section'
1664
1835
  c.arg_name 'NAME'
1665
- c.flag %i[s section], default_value: wwid.current_section
1836
+ c.flag %i[s section]
1666
1837
 
1667
1838
  c.desc 'Tag all imported entries'
1668
1839
  c.arg_name 'TAGS'
1669
1840
  c.flag :tag
1670
1841
 
1842
+ c.desc 'Autotag entries'
1843
+ c.switch :autotag, negatable: true, default_value: true
1844
+
1671
1845
  c.desc 'Prefix entries with'
1672
1846
  c.arg_name 'PREFIX'
1673
1847
  c.flag :prefix
@@ -1677,11 +1851,22 @@ command :import do |c|
1677
1851
 
1678
1852
  c.action do |_global_options, options, args|
1679
1853
 
1680
- section = wwid.guess_section(options[:s]) || options[:s].cap_first
1854
+ if options[:section]
1855
+ section = wwid.guess_section(options[:section]) || options[:section].cap_first
1856
+ else
1857
+ section = wwid.config['current_section']
1858
+ end
1681
1859
 
1682
1860
  if options[:type] =~ /^tim/i
1683
1861
  args.each do |path|
1684
- wwid.import_timing(path, { section: section, tag: options[:tag], prefix: options[:prefix], no_overlap: !options[:overlap] })
1862
+ options = {
1863
+ autotag: options[:autotag],
1864
+ no_overlap: !options[:overlap],
1865
+ prefix: options[:prefix],
1866
+ section: section,
1867
+ tag: options[:tag]
1868
+ }
1869
+ wwid.import_timing(path, options)
1685
1870
  wwid.write(wwid.doing_file)
1686
1871
  end
1687
1872
  else
data/lib/doing/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Doing
2
- VERSION = '1.0.83'
2
+ VERSION = '1.0.87'
3
3
  end
data/lib/doing/wwid.rb CHANGED
@@ -124,7 +124,8 @@ class WWID
124
124
  @config['templates']['recent'] ||= {
125
125
  'date_format' => '%_I:%M%P',
126
126
  'template' => '%shortdate: %title (%section)',
127
- 'wrap_width' => 88
127
+ 'wrap_width' => 88,
128
+ 'count' => 10
128
129
  }
129
130
  @config['views'] ||= {
130
131
  'done' => {
@@ -425,8 +426,8 @@ class WWID
425
426
  ## @param guessed (Boolean) already guessed and failed
426
427
  ##
427
428
  def guess_section(frag, guessed: false)
428
- return 'All' if frag =~ /all/i
429
-
429
+ return 'All' if frag =~ /^all$/i
430
+ frag ||= @current_section
430
431
  sections.each { |section| return section.cap_first if frag.downcase == section.downcase }
431
432
  section = false
432
433
  re = frag.split('').join('.*?')
@@ -627,6 +628,7 @@ class WWID
627
628
  def import_timing(path, opt = {})
628
629
  section = opt[:section] || @current_section
629
630
  opt[:no_overlap] ||= false
631
+ opt[:autotag] ||= @auto_tag
630
632
 
631
633
  add_section(section) unless @content.has_key?(section)
632
634
 
@@ -657,7 +659,7 @@ class WWID
657
659
  title += " @#{tag}"
658
660
  end
659
661
  end
660
- title = autotag(title) if @auto_tag
662
+ title = autotag(title) if opt[:autotag]
661
663
  title += " @done(#{end_time.strftime('%Y-%m-%d %H:%M')})"
662
664
  title.gsub!(/ +/, ' ')
663
665
  title.strip!
@@ -1099,7 +1101,6 @@ class WWID
1099
1101
  items = @content[section]['items'].dup.sort_by { |item| item['date'] }.reverse
1100
1102
  idx = 0
1101
1103
  done_date = Time.now
1102
- next_start = Time.now
1103
1104
  count = (opt[:count]).zero? ? items.length : opt[:count]
1104
1105
  items.map! do |item|
1105
1106
  break if idx == count
@@ -1118,8 +1119,8 @@ class WWID
1118
1119
  end
1119
1120
  else
1120
1121
  if opt[:sequential]
1121
- done_date = next_start - 1
1122
- next_start = item['date']
1122
+ next_entry = next_item(item)
1123
+ done_date = next_entry['date'] - 60 if next_entry
1123
1124
  elsif opt[:took]
1124
1125
  if item['date'] + opt[:took] > Time.now
1125
1126
  item['date'] = Time.now - opt[:took]
@@ -1208,6 +1209,24 @@ class WWID
1208
1209
  return new_item
1209
1210
  end
1210
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
+
1211
1230
  ##
1212
1231
  ## @brief Delete an item from the index
1213
1232
  ##
@@ -1477,7 +1496,7 @@ class WWID
1477
1496
  ##
1478
1497
  ## @param file (String) The filepath to write to
1479
1498
  ##
1480
- def write(file = nil)
1499
+ def write(file = nil, backup: true)
1481
1500
  output = @other_content_top ? "#{@other_content_top.join("\n")}\n" : ''
1482
1501
 
1483
1502
  @content.each do |title, section|
@@ -1489,7 +1508,7 @@ class WWID
1489
1508
  $stdout.puts output
1490
1509
  else
1491
1510
  file = File.expand_path(file)
1492
- if File.exist?(file)
1511
+ if File.exist?(file) && backup
1493
1512
  # Create a backup copy for the undo command
1494
1513
  FileUtils.cp(file, "#{file}~")
1495
1514
  end
@@ -1521,6 +1540,88 @@ class WWID
1521
1540
  end
1522
1541
  end
1523
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
+
1524
1625
  ##
1525
1626
  ## @brief Generate a menu of sections and allow user selection
1526
1627
  ##
@@ -1641,6 +1742,24 @@ class WWID
1641
1742
  end
1642
1743
  end
1643
1744
 
1745
+ if opt[:before]
1746
+ time_string = opt[:before]
1747
+ time_string += ' 12am' if time_string !~ /(\d+:\d+|\d+[ap])/
1748
+ cutoff = chronify(time_string)
1749
+ if cutoff
1750
+ items.delete_if { |item| item['date'] >= cutoff }
1751
+ end
1752
+ end
1753
+
1754
+ if opt[:after]
1755
+ time_string = opt[:after]
1756
+ time_string += ' 11:59pm' if time_string !~ /(\d+:\d+|\d+[ap])/
1757
+ cutoff = chronify(time_string)
1758
+ if cutoff
1759
+ items.delete_if { |item| item['date'] <= cutoff }
1760
+ end
1761
+ end
1762
+
1644
1763
  if opt[:today]
1645
1764
  items.delete_if do |item|
1646
1765
  item['date'] < Date.today.to_time
@@ -1868,13 +1987,13 @@ class WWID
1868
1987
 
1869
1988
  output.sub!(/%shortdate/) do
1870
1989
  if item['date'] > Date.today.to_time
1871
- item['date'].strftime('%_I:%M%P')
1872
- elsif item['date'] > (Date.today - 7).to_time
1873
- item['date'].strftime('%a %-I:%M%P')
1990
+ item['date'].strftime(' %_I:%M%P')
1991
+ elsif item['date'] > (Date.today - 6).to_time
1992
+ item['date'].strftime('%a %_I:%M%P')
1874
1993
  elsif item['date'].year == Date.today.year
1875
- item['date'].strftime('%b %d, %-I:%M%P')
1994
+ item['date'].strftime('%m/%d %_I:%M%P')
1876
1995
  else
1877
- item['date'].strftime('%b %d %Y, %-I:%M%P')
1996
+ item['date'].strftime('%m/%d/%Y %_I:%M%P')
1878
1997
  end
1879
1998
  end
1880
1999
 
@@ -1925,7 +2044,7 @@ class WWID
1925
2044
  ## @param section (String) The source section
1926
2045
  ## @param options (Hash) Options
1927
2046
  ##
1928
- def archive(section = 'Currently', options = {})
2047
+ def archive(section = @current_section, options = {})
1929
2048
  count = options[:keep] || 0
1930
2049
  destination = options[:destination] || 'Archive'
1931
2050
  tags = options[:tags] || []
@@ -1940,7 +2059,7 @@ class WWID
1940
2059
  destination = guess_section(destination)
1941
2060
 
1942
2061
  if sections.include?(destination) && (sections.include?(section) || archive_all)
1943
- do_archive(section, destination, { count: count, tags: tags, bool: bool, search: options[:search], label: options[:label] })
2062
+ do_archive(section, destination, { count: count, tags: tags, bool: bool, search: options[:search], label: options[:label], before: options[:before] })
1944
2063
  write(doing_file)
1945
2064
  else
1946
2065
  exit_now! 'Either source or destination does not exist'
@@ -1973,9 +2092,15 @@ class WWID
1973
2092
  items = @content[section]['items'].dup
1974
2093
 
1975
2094
  moved_items = []
1976
- if !tags.empty? || opt[:search]
2095
+ if !tags.empty? || opt[:search] || opt[:before]
2096
+ if opt[:before]
2097
+ time_string = opt[:before]
2098
+ time_string += ' 12am' if time_string !~ /(\d+:\d+|\d+[ap])/
2099
+ cutoff = chronify(time_string)
2100
+ end
2101
+
1977
2102
  items.delete_if do |item|
1978
- if (!tags.empty? && item.has_tags?(tags, bool) || (opt[:search] && item.matches_search?(opt[:search].to_s)))
2103
+ if ((!tags.empty? && item.has_tags?(tags, bool)) || (opt[:search] && item.matches_search?(opt[:search].to_s)) || (opt[:before] && item['date'] < cutoff))
1979
2104
  moved_items.push(item)
1980
2105
  counter += 1
1981
2106
  true
@@ -1984,7 +2109,7 @@ class WWID
1984
2109
  end
1985
2110
  end
1986
2111
  moved_items.each do |item|
1987
- if label && section != 'Currently'
2112
+ if label && section != @current_section
1988
2113
  item['title'] =
1989
2114
  item['title'].sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{section})")
1990
2115
  end
@@ -1997,7 +2122,7 @@ class WWID
1997
2122
  count = items.length if items.length < count
1998
2123
 
1999
2124
  items.map! do |item|
2000
- if label && section != 'Currently'
2125
+ if label && section != @current_section
2001
2126
  item['title'] =
2002
2127
  item['title'].sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{section})")
2003
2128
  end
@@ -2082,8 +2207,22 @@ class WWID
2082
2207
  opt[:sort_tags] ||= false
2083
2208
 
2084
2209
  cfg = @config['templates']['today']
2085
- list_section({ section: opt[:section], wrap_width: cfg['wrap_width'], count: 0,
2086
- format: cfg['date_format'], template: cfg['template'], order: 'asc', today: true, times: times, output: output, totals: opt[:totals], sort_tags: opt[:sort_tags] })
2210
+ options = {
2211
+ after: opt[:after],
2212
+ before: opt[:before],
2213
+ count: 0,
2214
+ format: cfg['date_format'],
2215
+ order: 'asc',
2216
+ output: output,
2217
+ section: opt[:section],
2218
+ sort_tags: opt[:sort_tags],
2219
+ template: cfg['template'],
2220
+ times: times,
2221
+ today: true,
2222
+ totals: opt[:totals],
2223
+ wrap_width: cfg['wrap_width']
2224
+ }
2225
+ list_section(options)
2087
2226
  end
2088
2227
 
2089
2228
  ##
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.83
4
+ version: 1.0.87
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-10-13 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