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 +4 -4
- data/README.md +4 -1
- data/bin/doing +230 -45
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +161 -22
- 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: 2540726a17efa50ef0cddc29353d8c14a4d921a89fb302711225053faacfbc4d
|
4
|
+
data.tar.gz: e4f59cf1e2feda50df653bbbd981e1ab4ad75d807eaf47a8afbad9d98032d27b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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]
|
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
|
@@ -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
|
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[:
|
951
|
-
date_string = options[:
|
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[:
|
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
|
-
|
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[:
|
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[:
|
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
|
-
|
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
|
-
|
1113
|
-
|
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[:
|
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
|
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
|
-
|
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
|
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
|
-
|
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[:
|
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 [
|
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 [
|
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[:
|
1748
|
+
if options[:app]
|
1578
1749
|
system %(open -a "#{options[:a]}" "#{File.expand_path(wwid.doing_file)}")
|
1579
|
-
elsif options[:
|
1750
|
+
elsif options[:bundle_id]
|
1580
1751
|
system %(open -b "#{options[:b]}" "#{File.expand_path(wwid.doing_file)}")
|
1581
|
-
elsif options[:
|
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]
|
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
|
-
|
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
|
-
|
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
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 =~
|
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
|
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
|
-
|
1122
|
-
|
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 -
|
1873
|
-
item['date'].strftime('%a
|
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('%
|
1994
|
+
item['date'].strftime('%m/%d %_I:%M%P')
|
1876
1995
|
else
|
1877
|
-
item['date'].strftime('%
|
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 =
|
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 !=
|
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 !=
|
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
|
-
|
2086
|
-
|
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.
|
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-
|
11
|
+
date: 2021-10-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|