doing 2.0.6.pre → 2.0.10

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.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/.yardoc/checksums +20 -0
  3. data/.yardoc/complete +0 -0
  4. data/.yardoc/object_types +0 -0
  5. data/.yardoc/objects/root.dat +0 -0
  6. data/.yardoc/proxy_types +0 -0
  7. data/.yardopts +1 -0
  8. data/CHANGELOG.md +7 -7
  9. data/Gemfile.lock +30 -10
  10. data/README.md +1 -1
  11. data/Rakefile +8 -1
  12. data/bin/doing +367 -21
  13. data/doc/Array.html +135 -0
  14. data/doc/Doing/Color.html +506 -0
  15. data/doc/Doing/Configuration.html +680 -0
  16. data/doc/Doing/Errors/DoingNoTraceError.html +186 -0
  17. data/doc/Doing/Errors/DoingRuntimeError.html +186 -0
  18. data/doc/Doing/Errors/DoingStandardError.html +186 -0
  19. data/doc/Doing/Errors/EmptyInput.html +186 -0
  20. data/doc/Doing/Errors/NoResults.html +186 -0
  21. data/doc/Doing/Errors/PluginException.html +248 -0
  22. data/doc/Doing/Errors/UserCancelled.html +186 -0
  23. data/doc/Doing/Errors/WrongCommand.html +186 -0
  24. data/doc/Doing/Errors.html +191 -0
  25. data/doc/Doing/Hooks.html +364 -0
  26. data/doc/Doing/Item.html +1385 -0
  27. data/doc/Doing/Items.html +393 -0
  28. data/doc/Doing/LogAdapter.html +1650 -0
  29. data/doc/Doing/Note.html +535 -0
  30. data/doc/Doing/Pager.html +268 -0
  31. data/doc/Doing/Plugins.html +849 -0
  32. data/doc/Doing/Util.html +870 -0
  33. data/doc/Doing/WWID.html +4827 -0
  34. data/doc/Doing.html +145 -0
  35. data/doc/GLI/Commands/MarkdownDocumentListener.html +763 -0
  36. data/doc/GLI/Commands.html +115 -0
  37. data/doc/GLI.html +115 -0
  38. data/doc/Hash.html +332 -0
  39. data/doc/Status.html +292 -0
  40. data/doc/String.html +1714 -0
  41. data/doc/Symbol.html +250 -0
  42. data/doc/Time.html +182 -0
  43. data/doc/_index.html +411 -0
  44. data/doc/class_list.html +51 -0
  45. data/doc/css/common.css +1 -0
  46. data/doc/css/full_list.css +58 -0
  47. data/doc/css/style.css +497 -0
  48. data/doc/file.README.html +123 -0
  49. data/doc/file_list.html +56 -0
  50. data/doc/frames.html +17 -0
  51. data/doc/index.html +123 -0
  52. data/doc/js/app.js +314 -0
  53. data/doc/js/full_list.js +216 -0
  54. data/doc/js/jquery.js +4 -0
  55. data/doc/method_list.html +1867 -0
  56. data/doc/top-level-namespace.html +112 -0
  57. data/doing.gemspec +5 -1
  58. data/doing.rdoc +354 -6
  59. data/example_plugin.rb +6 -6
  60. data/lib/doing/array.rb +1 -1
  61. data/lib/doing/configuration.rb +14 -12
  62. data/lib/doing/errors.rb +1 -1
  63. data/lib/doing/hash.rb +1 -1
  64. data/lib/doing/item.rb +113 -23
  65. data/lib/doing/log_adapter.rb +123 -113
  66. data/lib/doing/note.rb +1 -1
  67. data/lib/doing/plugin_manager.rb +5 -5
  68. data/lib/doing/plugins/export/csv_export.rb +1 -1
  69. data/lib/doing/plugins/export/template_export.rb +5 -7
  70. data/lib/doing/plugins/import/calendar_import.rb +8 -2
  71. data/lib/doing/plugins/import/doing_import.rb +10 -10
  72. data/lib/doing/plugins/import/timing_import.rb +12 -4
  73. data/lib/doing/string.rb +94 -19
  74. data/lib/doing/symbol.rb +9 -5
  75. data/lib/doing/time.rb +1 -1
  76. data/lib/doing/util.rb +18 -11
  77. data/lib/doing/version.rb +1 -1
  78. data/lib/doing/wwid.rb +455 -335
  79. data/lib/doing/wwidfile.rb +5 -5
  80. data/lib/doing.rb +2 -1
  81. data/lib/examples/plugins/say_export.rb +6 -6
  82. data/lib/examples/plugins/{templates → wiki_export/templates}/wiki.css +0 -0
  83. data/lib/examples/plugins/{templates → wiki_export/templates}/wiki.haml +0 -0
  84. data/lib/examples/plugins/{templates → wiki_export/templates}/wiki_index.haml +0 -0
  85. data/lib/examples/plugins/{wiki_export.rb → wiki_export/wiki_export.rb} +0 -0
  86. data/rdocfixer.rb +1 -1
  87. data/scripts/generate_zsh_completions.rb +3 -2
  88. data/yard_templates/default/method_details/setup.rb +3 -0
  89. metadata +121 -8
data/bin/doing CHANGED
@@ -62,7 +62,10 @@ if settings.dig('plugins', 'command_path')
62
62
  end
63
63
 
64
64
  program_desc 'A CLI for a What Was I Doing system'
65
- program_long_desc %(Doing uses a TaskPaper-like formatting to keep a plain text record of what you've been doing, complete with tag-based time tracking. The command line tool allows you to add entries, annotate with tags and notes, and view your entries with myriad options, with a focus on a "natural" language syntax.)
65
+ program_long_desc %(Doing uses a TaskPaper-like formatting to keep a plain text
66
+ record of what you've been doing, complete with tag-based time tracking. The
67
+ command line tool allows you to add entries, annotate with tags and notes, and
68
+ view your entries with myriad options, with a focus on a "natural" language syntax.)
66
69
 
67
70
  default_command :recent
68
71
  # sort_help :manually
@@ -180,7 +183,7 @@ end
180
183
 
181
184
  desc 'Reset the start time of an entry'
182
185
  command %i[reset begin] do |c|
183
- c.desc 'Set the start date of an item to now'
186
+ c.desc 'Limit search to section'
184
187
  c.arg_name 'NAME'
185
188
  c.flag %i[s section], default_value: 'All'
186
189
 
@@ -195,6 +198,19 @@ command %i[reset begin] do |c|
195
198
  c.arg_name 'QUERY'
196
199
  c.flag [:search]
197
200
 
201
+ c.desc 'Use alternative fuzzy matching for search string'
202
+ c.switch [:fuzzy], default_value: false, negatable: false
203
+
204
+ c.desc 'Force exact search string matching (case sensitive)'
205
+ c.switch %i[x exact], default_value: false, negatable: false
206
+
207
+ c.desc 'Reset items that *don\'t* match search/tag filters'
208
+ c.switch [:not], default_value: false, negatable: false
209
+
210
+ c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
211
+ c.arg_name 'TYPE'
212
+ c.flag [:case], must_match: /^[csi]/, default_value: 'smart'
213
+
198
214
  c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
199
215
  c.arg_name 'BOOLEAN'
200
216
  c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
@@ -207,7 +223,16 @@ command %i[reset begin] do |c|
207
223
  options[:section] = wwid.guess_section(options[:section]) || options[:section].cap_first
208
224
  end
209
225
 
210
- options[:tag_bool] = options[:bool].normalize_bool
226
+ options[:bool] = options[:bool].normalize_bool
227
+
228
+ options[:case] = options[:case].normalize_case
229
+
230
+ if options[:search]
231
+ search = options[:search]
232
+ search.sub!(/^'?/, "'") if options[:exact]
233
+ options[:search] = search
234
+ end
235
+
211
236
 
212
237
  items = wwid.filter_items([], opt: options)
213
238
 
@@ -248,6 +273,11 @@ long_desc %(
248
273
  )
249
274
  arg_name 'NOTE_TEXT'
250
275
  command :note do |c|
276
+ c.example 'doing note', desc: 'Open the last entry in $EDITOR to append a note'
277
+ c.example 'doing note "Just a quick annotation"', desc: 'Add a quick note to the last entry'
278
+ c.example 'doing note --tag done "Keeping it real or something"', desc: 'Add a note to the last item tagged @done'
279
+ c.example 'doing note --search "late night" -e', desc: 'Open $EDITOR to add a note to the last item containing "late night" (fuzzy matched)'
280
+
251
281
  c.desc 'Section'
252
282
  c.arg_name 'NAME'
253
283
  c.flag %i[s section], default_value: 'All'
@@ -266,6 +296,19 @@ command :note do |c|
266
296
  c.arg_name 'QUERY'
267
297
  c.flag [:search]
268
298
 
299
+ c.desc 'Use alternative fuzzy matching for search string'
300
+ c.switch [:fuzzy], default_value: false, negatable: false
301
+
302
+ c.desc 'Force exact search string matching (case sensitive)'
303
+ c.switch %i[x exact], default_value: false, negatable: false
304
+
305
+ c.desc 'Add note to item that *doesn\'t* match search/tag filters'
306
+ c.switch [:not], default_value: false, negatable: false
307
+
308
+ c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
309
+ c.arg_name 'TYPE'
310
+ c.flag [:case], must_match: /^[csi]/, default_value: 'smart'
311
+
269
312
  c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
270
313
  c.arg_name 'BOOLEAN'
271
314
  c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
@@ -280,6 +323,15 @@ command :note do |c|
280
323
 
281
324
  options[:tag_bool] = options[:bool].normalize_bool
282
325
 
326
+ options[:case] = options[:case].normalize_case
327
+
328
+ if options[:search]
329
+ search = options[:search]
330
+ search.sub!(/^'?/, "'") if options[:exact]
331
+ options[:search] = search
332
+ end
333
+
334
+
283
335
  last_entry = wwid.last_entry(options)
284
336
 
285
337
  unless last_entry
@@ -330,6 +382,11 @@ end
330
382
  desc 'Finish any running @meanwhile tasks and optionally create a new one'
331
383
  arg_name 'ENTRY'
332
384
  command :meanwhile do |c|
385
+ c.example 'doing meanwhile "Long task that will have others after it before it\'s done"', desc: 'Add a new long-running entry, completing any current @meanwhile entry'
386
+ c.example 'doing meanwhile', desc: 'Finish any open @meanwhile entry'
387
+ c.example 'doing meanwhile --archive', desc: 'Finish any open @meanwhile entry and archive it'
388
+ c.example 'doing meanwhile --back 2h "Something I\'ve been working on for a while', desc: 'Add a @meanwhile entry with a start date 2 hours ago'
389
+
333
390
  c.desc 'Section'
334
391
  c.arg_name 'NAME'
335
392
  c.flag %i[s section]
@@ -396,12 +453,12 @@ end
396
453
  desc 'Output HTML, CSS, and Markdown (ERB) templates for customization'
397
454
  long_desc %(
398
455
  Templates are printed to STDOUT for piping to a file.
399
- Save them and use them in the configuration file under html_template.
400
-
401
- Example `doing template haml > ~/styles/my_doing.haml`
456
+ Save them and use them in the configuration file under export_templates.
402
457
  )
403
458
  arg_name 'TYPE', must_match: Doing::Plugins.template_regex
404
459
  command :template do |c|
460
+ c.example 'doing template haml > ~/styles/my_doing.haml', desc: 'Output the haml template and save it to a file'
461
+
405
462
  c.desc 'List all available templates'
406
463
  c.switch %i[l list], negatable: false
407
464
 
@@ -473,6 +530,16 @@ command :select do |c|
473
530
  c.arg_name 'QUERY'
474
531
  c.flag %i[q query search]
475
532
 
533
+ c.desc 'Force exact search string matching (case sensitive)'
534
+ c.switch %i[x exact], default_value: false, negatable: false
535
+
536
+ c.desc 'Select items that *don\'t* match search/tag filters'
537
+ c.switch [:not], default_value: false, negatable: false
538
+
539
+ c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
540
+ c.arg_name 'TYPE'
541
+ c.flag [:case], must_match: /^[csi]/, default_value: 'smart'
542
+
476
543
  c.desc 'Use --no-menu to skip the interactive menu. Use with --query to filter items and act on results automatically. Test with `--output doing` to preview matches.'
477
544
  c.switch %i[menu], negatable: true, default_value: true
478
545
 
@@ -510,6 +577,8 @@ command :select do |c|
510
577
 
511
578
  raise InvalidArgument, '--no-menu requires --query' if !options[:menu] && !options[:query]
512
579
 
580
+ options[:case] = options[:case].normalize_case
581
+
513
582
  wwid.interactive(options)
514
583
  end
515
584
  end
@@ -517,6 +586,9 @@ end
517
586
  desc 'Add an item to the Later section'
518
587
  arg_name 'ENTRY'
519
588
  command :later do |c|
589
+ c.example 'doing later "Something I\'ll think about tomorrow"', desc: 'Add an entry to the Later section'
590
+ c.example 'doing later -e', desc: 'Open $EDITOR to create an entry and optional note'
591
+
520
592
  c.desc "Edit entry with #{Doing::Util.default_editor}"
521
593
  c.switch %i[e editor], negatable: false, default_value: false
522
594
 
@@ -566,6 +638,11 @@ end
566
638
  desc 'Add a completed item with @done(date). No argument finishes last entry.'
567
639
  arg_name 'ENTRY'
568
640
  command %i[done did] do |c|
641
+ c.example 'doing done', desc: 'Tag the last entry @done'
642
+ c.example 'doing done I already finished this', desc: 'Add a new entry and immediately mark it @done'
643
+ c.example 'doing done --back 30m This took me half an hour', desc: 'Add an entry with a start date 30 minutes ago and a @done date of right now'
644
+ c.example 'doing done --at 3pm --took 1h Started and finished this afternoon', desc: 'Add an entry with a @done date of 3pm and a start date of 2pm (3pm - 1h)'
645
+
569
646
  c.desc 'Remove @done tag'
570
647
  c.switch %i[r remove], negatable: false, default_value: false
571
648
 
@@ -755,6 +832,9 @@ desc 'End last X entries with no time tracked'
755
832
  long_desc 'Adds @done tag without datestamp so no elapsed time is recorded. Alias for `doing finish --no-date`.'
756
833
  arg_name 'COUNT'
757
834
  command :cancel do |c|
835
+ c.example 'doing cancel', desc: 'Cancel the last entry'
836
+ c.example 'doing cancel --tag project1 -u 5', desc: 'Cancel the last 5 unfinished entries containing @project1'
837
+
758
838
  c.desc 'Archive entries'
759
839
  c.switch %i[a archive], negatable: false, default_value: false
760
840
 
@@ -770,6 +850,23 @@ command :cancel do |c|
770
850
  c.arg_name 'BOOLEAN'
771
851
  c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
772
852
 
853
+ c.desc 'Cancel the last X entries matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
854
+ c.arg_name 'QUERY'
855
+ c.flag [:search]
856
+
857
+ c.desc 'Use alternative fuzzy matching for search string'
858
+ c.switch [:fuzzy], default_value: false, negatable: false
859
+
860
+ c.desc 'Force exact search string matching (case sensitive)'
861
+ c.switch %i[x exact], default_value: false, negatable: false
862
+
863
+ c.desc 'Finish items that *don\'t* match search/tag filters'
864
+ c.switch [:not], default_value: false, negatable: false
865
+
866
+ c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
867
+ c.arg_name 'TYPE'
868
+ c.flag [:case], must_match: /^[csi]/, default_value: 'smart'
869
+
773
870
  c.desc 'Cancel last entry (or entries) not already marked @done'
774
871
  c.switch %i[u unfinished], negatable: false, default_value: false
775
872
 
@@ -799,17 +896,28 @@ command :cancel do |c|
799
896
  count = args[0] ? args[0].to_i : 1
800
897
  end
801
898
 
899
+ search = nil
900
+
901
+ if options[:search]
902
+ search = options[:search]
903
+ search.sub!(/^'?/, "'") if options[:exact]
904
+ end
905
+
802
906
  opts = {
803
907
  archive: options[:a],
908
+ case: options[:case].normalize_case,
804
909
  count: count,
805
910
  date: false,
911
+ fuzzy: options[:fuzzy],
912
+ interactive: options[:interactive],
913
+ not: options[:not],
914
+ search: search,
806
915
  section: section,
807
916
  sequential: false,
808
917
  tag: tags,
809
918
  tag_bool: options[:bool].normalize_bool,
810
919
  tags: ['done'],
811
- unfinished: options[:unfinished],
812
- interactive: options[:interactive]
920
+ unfinished: options[:unfinished]
813
921
  }
814
922
 
815
923
  wwid.tag_last(opts)
@@ -820,6 +928,10 @@ desc 'Mark last X entries as @done'
820
928
  long_desc 'Marks the last X entries with a @done tag and current date. Does not alter already completed entries.'
821
929
  arg_name 'COUNT'
822
930
  command :finish do |c|
931
+ c.example 'doing finish', desc: 'Mark the last entry @done'
932
+ c.example 'doing finish --auto --section Later 10', desc: 'Add @done to any unfinished entries in the last 10 in Later, setting the finish time based on the start time of the task after it'
933
+ c.example 'doing finish --search "a specific entry" --at "yesterday 3pm"', desc: 'Search for an entry containing string and set its @done time to yesterday at 3pm'
934
+
823
935
  c.desc 'Include date'
824
936
  c.switch [:date], negatable: true, default_value: true
825
937
 
@@ -844,6 +956,19 @@ command :finish do |c|
844
956
  c.arg_name 'QUERY'
845
957
  c.flag [:search]
846
958
 
959
+ c.desc 'Use alternative fuzzy matching for search string'
960
+ c.switch [:fuzzy], default_value: false, negatable: false
961
+
962
+ c.desc 'Force exact search string matching (case sensitive)'
963
+ c.switch %i[x exact], default_value: false, negatable: false
964
+
965
+ c.desc 'Finish items that *don\'t* match search/tag filters'
966
+ c.switch [:not], default_value: false, negatable: false
967
+
968
+ c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
969
+ c.arg_name 'TYPE'
970
+ c.flag [:case], must_match: /^[csi]/, default_value: 'smart'
971
+
847
972
  c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
848
973
  c.arg_name 'BOOLEAN'
849
974
  c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
@@ -912,20 +1037,30 @@ command :finish do |c|
912
1037
  count = args[0] ? args[0].to_i : 1
913
1038
  end
914
1039
 
1040
+ search = nil
1041
+
1042
+ if options[:search]
1043
+ search = options[:search]
1044
+ search.sub!(/^'?/, "'") if options[:exact]
1045
+ end
1046
+
915
1047
  opts = {
916
1048
  archive: options[:archive],
917
1049
  back: date,
1050
+ case: options[:case].normalize_case,
918
1051
  count: count,
919
1052
  date: options[:date],
920
- search: options[:search],
1053
+ fuzzy: options[:fuzzy],
1054
+ interactive: options[:interactive],
1055
+ not: options[:not],
1056
+ remove: options[:remove],
1057
+ search: search,
921
1058
  section: options[:section],
922
1059
  sequential: options[:auto],
923
1060
  tag: tags,
924
1061
  tag_bool: options[:bool].normalize_bool,
925
1062
  tags: ['done'],
926
- unfinished: options[:unfinished],
927
- remove: options[:remove],
928
- interactive: options[:interactive]
1063
+ unfinished: options[:unfinished]
929
1064
  }
930
1065
 
931
1066
  wwid.tag_last(opts)
@@ -951,6 +1086,19 @@ command %i[again resume] do |c|
951
1086
  c.arg_name 'QUERY'
952
1087
  c.flag [:search]
953
1088
 
1089
+ c.desc 'Use alternative fuzzy matching for search string'
1090
+ c.switch [:fuzzy], default_value: false, negatable: false
1091
+
1092
+ c.desc 'Force exact search string matching (case sensitive)'
1093
+ c.switch %i[x exact], default_value: false, negatable: false
1094
+
1095
+ c.desc 'Resume items that *don\'t* match search/tag filters'
1096
+ c.switch [:not], default_value: false, negatable: false
1097
+
1098
+ c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
1099
+ c.arg_name 'TYPE'
1100
+ c.flag [:case], must_match: /^[csi]/, default_value: 'smart'
1101
+
954
1102
  c.desc 'Boolean used to combine multiple tags'
955
1103
  c.arg_name 'BOOLEAN'
956
1104
  c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
@@ -967,7 +1115,17 @@ command %i[again resume] do |c|
967
1115
 
968
1116
  c.action do |_global_options, options, _args|
969
1117
  tags = options[:tag].nil? ? [] : options[:tag].to_tags
970
- opts = options
1118
+
1119
+ options[:case] = options[:case].normalize_case
1120
+
1121
+ if options[:search]
1122
+ search = options[:search]
1123
+ search.sub!(/^'?/, "'") if options[:exact]
1124
+ options[:search] = search
1125
+ end
1126
+
1127
+ opts = options.dup
1128
+
971
1129
  opts[:tag] = tags
972
1130
  opts[:tag_bool] = options[:bool].normalize_bool
973
1131
  opts[:interactive] = options[:interactive]
@@ -1037,6 +1195,19 @@ command :tag do |c|
1037
1195
  c.arg_name 'QUERY'
1038
1196
  c.flag [:search]
1039
1197
 
1198
+ c.desc 'Use alternative fuzzy matching for search string'
1199
+ c.switch [:fuzzy], default_value: false, negatable: false
1200
+
1201
+ c.desc 'Force exact search string matching (case sensitive)'
1202
+ c.switch %i[x exact], default_value: false, negatable: false
1203
+
1204
+ c.desc 'Tag items that *don\'t* match search/tag filters'
1205
+ c.switch [:not], default_value: false, negatable: false
1206
+
1207
+ c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
1208
+ c.arg_name 'TYPE'
1209
+ c.flag [:case], must_match: /^[csi]/, default_value: 'smart'
1210
+
1040
1211
  c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
1041
1212
  c.arg_name 'BOOLEAN'
1042
1213
  c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
@@ -1081,6 +1252,13 @@ command :tag do |c|
1081
1252
  count = options[:count].to_i
1082
1253
  end
1083
1254
 
1255
+ options[:case] = options[:case].normalize_case
1256
+
1257
+ if options[:search]
1258
+ search = options[:search]
1259
+ search.sub!(/^'?/, "'") if options[:exact]
1260
+ options[:search] = search
1261
+ end
1084
1262
 
1085
1263
  if count.zero? && !options[:force]
1086
1264
  if options[:search]
@@ -1104,7 +1282,7 @@ command :tag do |c|
1104
1282
 
1105
1283
  res = wwid.yn(question, default_response: false)
1106
1284
 
1107
- exit_now! 'Cancelled' unless res
1285
+ raise UserCancelled unless res
1108
1286
  end
1109
1287
 
1110
1288
  options[:count] = count
@@ -1152,6 +1330,19 @@ command [:mark, :flag] do |c|
1152
1330
  c.arg_name 'QUERY'
1153
1331
  c.flag [:search]
1154
1332
 
1333
+ c.desc 'Use alternative fuzzy matching for search string'
1334
+ c.switch [:fuzzy], default_value: false, negatable: false
1335
+
1336
+ c.desc 'Force exact search string matching (case sensitive)'
1337
+ c.switch %i[x exact], default_value: false, negatable: false
1338
+
1339
+ c.desc 'Flag items that *don\'t* match search/tag/date filters'
1340
+ c.switch [:not], default_value: false, negatable: false
1341
+
1342
+ c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
1343
+ c.arg_name 'TYPE'
1344
+ c.flag [:case], must_match: /^[csi]/, default_value: 'smart'
1345
+
1155
1346
  c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
1156
1347
  c.arg_name 'BOOLEAN'
1157
1348
  c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
@@ -1183,6 +1374,14 @@ command [:mark, :flag] do |c|
1183
1374
  count = options[:count].to_i
1184
1375
  end
1185
1376
 
1377
+ options[:case] = options[:case].normalize_case
1378
+
1379
+ if options[:search]
1380
+ search = options[:search]
1381
+ search.sub!(/^'?/, "'") if options[:exact]
1382
+ options[:search] = search
1383
+ end
1384
+
1186
1385
  if count.zero? && !options[:force]
1187
1386
  if options[:search]
1188
1387
  section_q = ' matching your search terms'
@@ -1226,7 +1425,9 @@ command :show do |c|
1226
1425
  c.example 'doing show Currently', desc: 'Show entries in the Currently section'
1227
1426
  c.example 'doing show @project1', desc: 'Show entries tagged @project1'
1228
1427
  c.example 'doing show Later @doing', desc: 'Show entries from the Later section tagged @doing'
1229
- c.example 'doing show Ideas --from "mon to fri" --tag doing', desc: 'Show entries tagged @doing from the Ideas section added between monday and friday of the current week.'
1428
+ c.example 'doing show @oracle @writing --bool and', desc: 'Show entries tagged both @oracle and @writing'
1429
+ c.example 'doing show Currently @devo --bool not', desc: 'Show entries in Currently NOT tagged @devo'
1430
+ c.example 'doing show Ideas @doing --from "mon to fri"', desc: 'Show entries tagged @doing from the Ideas section added between monday and friday of the current week.'
1230
1431
  c.example 'doing show --interactive Later @doing', desc: 'Create a menu from entries from the Later section tagged @doing to perform batch actions'
1231
1432
 
1232
1433
  c.desc 'Tag filter, combine multiple tags with a comma. Added for compatibility with other commands.'
@@ -1257,6 +1458,19 @@ command :show do |c|
1257
1458
  c.arg_name 'QUERY'
1258
1459
  c.flag [:search]
1259
1460
 
1461
+ c.desc 'Use alternative fuzzy matching for search string'
1462
+ c.switch [:fuzzy], default_value: false, negatable: false
1463
+
1464
+ c.desc 'Force exact search string matching (case sensitive)'
1465
+ c.switch %i[x exact], default_value: false, negatable: false
1466
+
1467
+ c.desc 'Show items that *don\'t* match search/tag/date filters'
1468
+ c.switch [:not], default_value: false, negatable: false
1469
+
1470
+ c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
1471
+ c.arg_name 'TYPE'
1472
+ c.flag [:case], must_match: /^[csi]/, default_value: 'smart'
1473
+
1260
1474
  c.desc 'Sort order (asc/desc)'
1261
1475
  c.arg_name 'ORDER'
1262
1476
  c.flag %i[s sort], must_match: REGEX_SORT_ORDER, default_value: 'asc'
@@ -1361,6 +1575,14 @@ command :show do |c|
1361
1575
 
1362
1576
  tags_color = settings.key?('tags_color') ? settings['tags_color'] : nil
1363
1577
 
1578
+ options[:case] = options[:case].normalize_case
1579
+
1580
+ if options[:search]
1581
+ search = options[:search]
1582
+ search.sub!(/^'?/, "'") if options[:exact]
1583
+ options[:search] = search
1584
+ end
1585
+
1364
1586
  opt = options.dup
1365
1587
 
1366
1588
  opt[:sort_tags] = options[:tag_sort] =~ /^n/i
@@ -1423,6 +1645,19 @@ command %i[grep search] do |c|
1423
1645
  c.desc 'Only show items with recorded time intervals'
1424
1646
  c.switch [:only_timed], default_value: false, negatable: false
1425
1647
 
1648
+ c.desc 'Use alternative fuzzy matching for search string'
1649
+ c.switch [:fuzzy], default_value: false, negatable: false
1650
+
1651
+ c.desc 'Force exact string matching (case sensitive)'
1652
+ c.switch %i[x exact], default_value: false, negatable: false
1653
+
1654
+ c.desc 'Show items that *don\'t* match search string'
1655
+ c.switch [:not], default_value: false, negatable: false
1656
+
1657
+ c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
1658
+ c.arg_name 'TYPE'
1659
+ c.flag [:case], must_match: /^[csi]/, default_value: 'smart'
1660
+
1426
1661
  c.desc 'Display an interactive menu of results to perform further operations'
1427
1662
  c.switch %i[i interactive], default_value: false, negatable: false
1428
1663
 
@@ -1433,10 +1668,15 @@ command %i[grep search] do |c|
1433
1668
 
1434
1669
  section = wwid.guess_section(options[:section]) if options[:section]
1435
1670
 
1671
+ options[:case] = options[:case].normalize_case
1672
+
1673
+ search = args.join(' ')
1674
+ search.sub!(/^'?/, "'") if options[:exact]
1675
+
1436
1676
  options[:times] = true if options[:totals]
1437
1677
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
1438
1678
  options[:highlight] = true
1439
- options[:search] = args.join(' ')
1679
+ options[:search] = search
1440
1680
  options[:section] = section
1441
1681
  options[:tags_color] = tags_color
1442
1682
 
@@ -1754,6 +1994,19 @@ command :last do |c|
1754
1994
  c.arg_name 'QUERY'
1755
1995
  c.flag [:search]
1756
1996
 
1997
+ c.desc 'Use alternative fuzzy matching for search string'
1998
+ c.switch [:fuzzy], default_value: false, negatable: false
1999
+
2000
+ c.desc 'Force exact search string matching (case sensitive)'
2001
+ c.switch %i[x exact], default_value: false, negatable: false
2002
+
2003
+ c.desc 'Show items that *don\'t* match search string or tag filter'
2004
+ c.switch [:not], default_value: false, negatable: false
2005
+
2006
+ c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
2007
+ c.arg_name 'TYPE'
2008
+ c.flag [:case], must_match: /^[csi]/, default_value: 'smart'
2009
+
1757
2010
  c.action do |global_options, options, _args|
1758
2011
  raise InvalidArgument, '--tag and --search can not be used together' if options[:tag] && options[:search]
1759
2012
 
@@ -1772,11 +2025,20 @@ command :last do |c|
1772
2025
 
1773
2026
  end
1774
2027
 
2028
+ options[:case] = options[:case].normalize_case
2029
+
2030
+ search = nil
2031
+
2032
+ if options[:search]
2033
+ search = options[:search]
2034
+ search.sub!(/^'?/, "'") if options[:exact]
2035
+ end
2036
+
1775
2037
  if options[:editor]
1776
- wwid.edit_last(section: options[:s], options: { search: options[:search], tag: tags, tag_bool: options[:bool] })
2038
+ wwid.edit_last(section: options[:s], options: { search: search, fuzzy: options[:fuzzy], case: options[:case], tag: tags, tag_bool: options[:bool], not: options[:not] })
1777
2039
  else
1778
2040
  Doing::Pager::page wwid.last(times: true, section: options[:s],
1779
- options: { search: options[:search], tag: tags, tag_bool: options[:bool] }).strip
2041
+ options: { search: search, fuzzy: options[:fuzzy], case: options[:case], negate: options[:not], tag: tags, tag_bool: options[:bool] }).strip
1780
2042
  end
1781
2043
  end
1782
2044
  end
@@ -1934,6 +2196,19 @@ command :view do |c|
1934
2196
  c.arg_name 'QUERY'
1935
2197
  c.flag [:search]
1936
2198
 
2199
+ c.desc 'Use alternative fuzzy matching for search string'
2200
+ c.switch [:fuzzy], default_value: false, negatable: false
2201
+
2202
+ c.desc 'Force exact search string matching (case sensitive)'
2203
+ c.switch %i[x exact], default_value: false, negatable: false
2204
+
2205
+ c.desc 'Show items that *don\'t* match search string'
2206
+ c.switch [:not], default_value: false, negatable: false
2207
+
2208
+ c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
2209
+ c.arg_name 'TYPE'
2210
+ c.flag [:case], must_match: /^[csi]/, default_value: 'smart'
2211
+
1937
2212
  c.desc 'Sort tags by (name|time)'
1938
2213
  c.arg_name 'KEY'
1939
2214
  c.flag [:tag_sort], must_match: /^(?:name|time)$/i
@@ -2065,7 +2340,17 @@ command :view do |c|
2065
2340
  dates = [start, finish]
2066
2341
  end
2067
2342
 
2068
- opts = options
2343
+ options[:case] = options[:case].normalize_case
2344
+
2345
+ search = nil
2346
+
2347
+ if options[:search]
2348
+ search = options[:search]
2349
+ search.sub!(/^'?/, "'") if options[:exact]
2350
+ end
2351
+
2352
+ opts = options.dup
2353
+ opts[:search] = search
2069
2354
  opts[:output] = output_format
2070
2355
  opts[:count] = count
2071
2356
  opts[:format] = date_format
@@ -2138,6 +2423,19 @@ command %i[archive move] do |c|
2138
2423
  c.arg_name 'QUERY'
2139
2424
  c.flag [:search]
2140
2425
 
2426
+ c.desc 'Use alternative fuzzy matching for search string'
2427
+ c.switch [:fuzzy], default_value: false, negatable: false
2428
+
2429
+ c.desc 'Force exact search string matching (case sensitive)'
2430
+ c.switch %i[x exact], default_value: false, negatable: false
2431
+
2432
+ c.desc 'Show items that *don\'t* match search string'
2433
+ c.switch [:not], default_value: false, negatable: false
2434
+
2435
+ c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
2436
+ c.arg_name 'TYPE'
2437
+ c.flag [:case], must_match: /^[csi]/, default_value: 'smart'
2438
+
2141
2439
  c.desc 'Archive entries older than date
2142
2440
  (Flexible date format, e.g. 1/27/2021, 2020-07-19, or Monday 3pm)'
2143
2441
  c.arg_name 'DATE_STRING'
@@ -2161,7 +2459,17 @@ command %i[archive move] do |c|
2161
2459
 
2162
2460
  tags.concat(options[:tag].to_tags) if options[:tag]
2163
2461
 
2164
- opts = options
2462
+ search = nil
2463
+
2464
+ options[:case] = options[:case].normalize_case
2465
+
2466
+ if options[:search]
2467
+ search = options[:search]
2468
+ search.sub!(/^'?/, "'") if options[:exact]
2469
+ end
2470
+
2471
+ opts = options.dup
2472
+ opts[:search] = search
2165
2473
  opts[:bool] = options[:bool].normalize_bool
2166
2474
  opts[:destination] = options[:to]
2167
2475
  opts[:tags] = tags
@@ -2196,6 +2504,19 @@ command :rotate do |c|
2196
2504
  c.arg_name 'QUERY'
2197
2505
  c.flag [:search]
2198
2506
 
2507
+ c.desc 'Use alternative fuzzy matching for search string'
2508
+ c.switch [:fuzzy], default_value: false, negatable: false
2509
+
2510
+ c.desc 'Force exact search string matching (case sensitive)'
2511
+ c.switch %i[x exact], default_value: false, negatable: false
2512
+
2513
+ c.desc 'Rotate items that *don\'t* match search string or tag filter'
2514
+ c.switch [:not], default_value: false, negatable: false
2515
+
2516
+ c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
2517
+ c.arg_name 'TYPE'
2518
+ c.flag [:case], must_match: /^[csi]/, default_value: 'smart'
2519
+
2199
2520
  c.desc 'Rotate entries older than date
2200
2521
  (Flexible date format, e.g. 1/27/2021, 2020-07-19, or Monday 3pm)'
2201
2522
  c.arg_name 'DATE_STRING'
@@ -2208,6 +2529,16 @@ command :rotate do |c|
2208
2529
 
2209
2530
  options[:bool] = options[:bool].normalize_bool
2210
2531
 
2532
+ options[:case] = options[:case].normalize_case
2533
+
2534
+ search = nil
2535
+
2536
+ if options[:search]
2537
+ search = options[:search]
2538
+ search.sub!(/^'?/, "'") if options[:exact]
2539
+ options[:search] = search
2540
+ end
2541
+
2211
2542
  wwid.rotate(options)
2212
2543
  end
2213
2544
  end
@@ -2311,7 +2642,7 @@ command :config do |c|
2311
2642
  when /^j/
2312
2643
  JSON.pretty_generate(cfg)
2313
2644
  when /^r/
2314
- cfg
2645
+ cfg.map {|k, v| v.to_s }
2315
2646
  else
2316
2647
  YAML.dump(cfg)
2317
2648
  end
@@ -2396,6 +2727,19 @@ command :import do |c|
2396
2727
  c.arg_name 'QUERY'
2397
2728
  c.flag [:search]
2398
2729
 
2730
+ c.desc 'Use alternative fuzzy matching for search string'
2731
+ c.switch [:fuzzy], default_value: false, negatable: false
2732
+
2733
+ c.desc 'Force exact search string matching (case sensitive)'
2734
+ c.switch %i[x exact], default_value: false, negatable: false
2735
+
2736
+ c.desc 'Import items that *don\'t* match search/tag/date filters'
2737
+ c.switch [:not], default_value: false, negatable: false
2738
+
2739
+ c.desc 'Case sensitivity for search string matching [(c)ase-sensitive, (i)gnore, (s)mart]'
2740
+ c.arg_name 'TYPE'
2741
+ c.flag [:case], must_match: /^[csi]/, default_value: 'smart'
2742
+
2399
2743
  c.desc 'Only import items with recorded time intervals'
2400
2744
  c.switch [:only_timed], default_value: false, negatable: false
2401
2745
 
@@ -2453,6 +2797,8 @@ command :import do |c|
2453
2797
  dates = [start, finish]
2454
2798
  end
2455
2799
 
2800
+ options[:case] = options[:case].normalize_case
2801
+
2456
2802
  if options[:type] =~ Doing::Plugins.plugin_regex(type: :import)
2457
2803
  options[:no_overlap] = !options[:overlap]
2458
2804
  options[:date_filter] = dates