doing 1.0.72 → 1.0.76

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ab378bf9ba26ec5c6f852afa702d74bd8323bb9e4aca92e3e94b081c578b637
4
- data.tar.gz: 1c868eb6ac0dbb438f69679abfcb2fc587465ce9848c4da607f537b8c98d60ca
3
+ metadata.gz: 7b4ff1cca2f519037f002070068ed1267cd0083ae04e3b2b14e431bf8ee47baf
4
+ data.tar.gz: da59f5f23f01b82a37e2ca56b552c26f638854597cc0f0beac21f30b2c1dd066
5
5
  SHA512:
6
- metadata.gz: 257898f624de2463259c5b49e46bc1687edf56f428aa16d297d212554efabbff82f59f0ae98da9772fc1aa30673623d70a2903326dca6ffd510d6afec846ff43
7
- data.tar.gz: f635bc6bbb42de40a4f5bdcd1abd1d4caeec1a94af7553f1bcff1d0fffcda6898c248941a38f7abfba807721a18d39fd1632d0ec228c56b940c502f036ac7f19
6
+ metadata.gz: 550a05a4c6b0a432df01904d0cb2b78d7bdf9b41fa0ac8660e694f23b8f8384389395966cbb35cfbb6feb55ad248c39ec0010b8ca4e2626a892e029f553fb4dd
7
+ data.tar.gz: e3b3d0789d9fba0f4cd63fcc1da2910d32a14142d25e367ede83fbee3956476991cfd74fc73f99642b1cf47c744c4e78765f6df75f3933ee2d1e619de8f2a733
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.70<!--END VER-->.
30
+ The current version of `doing` is <!--VER-->1.0.75<!--END VER-->.
31
31
 
32
32
  $ [sudo] gem install doing
33
33
 
@@ -536,7 +536,7 @@ If you have a use for it, you can use `-o csv` on the show or view commands to o
536
536
 
537
537
  `doing yesterday` is great for stand-ups (thanks to [Sean Collins](https://github.com/sc68cal) for that!). Note that you can show yesterday's activity from an alternate section by using the section name as an argument (e.g. `doing yesterday archive`).
538
538
 
539
- `doing on` allows for full date ranges and filtering. `doing on saturday`, or `doing on one month to today` will give you ranges. You can use the same terms with the `show` command by adding the `-f` or `--from` flag. `doing show @done --from "monday to friday"` will give you all of your completed items for the last week (assuming it's the weekend).
539
+ `doing on` allows for full date ranges and filtering. `doing on saturday`, or `doing on one month to today` will give you ranges. You can use the same terms with the `show` command by adding the `-f` or `--from` flag. `doing show @done --from "monday to friday"` will give you all of your completed items for the last week (assuming it's the weekend). There's also `doing since` a simple alias for `doing on PAST_DATE to now`, e.g. `doing since monday`.
540
540
 
541
541
  You can also show entries matching a search string with `doing grep` (synonym `doing search`). If you want to search with regular expressions or for an exact match, surround your search query with forward slashes, e.g. `doing search /project name/`. If you pass a search string without slashes, it's treated as a fuzzy search string, meaning matches can be found as long as the characters in the search string are in order and with no more than three other characters between each. By default searches are across all sections, but you can limit it to one with the `-s SECTION_NAME` flag. Searches can be displayed with the default template, or output as HTML, CSV, or JSON.
542
542
 
@@ -621,9 +621,9 @@ Now you can run `doing import --type timing -s SECTION PATH`, where SECTION is t
621
621
 
622
622
  If you have `fzf` installed (<https://github.com/junegunn/fzf>), you can use `doing select` to get a menu of all your items (or items in a given section) which can be searched with fuzzy matching. The menu allows multiple selections to be acted on directly.
623
623
 
624
- To use the menu, type a search string or use the arrow keys to navigate up and down. Press tab on an entry you'd like to perform an action on. A marker will show up on the left indicating the entry is selected. Repeat the process and select as many entries as needed. When you hit Return, the selection will be passed back to doing.
624
+ To use the menu, type a search string or use the arrow keys to navigate up and down. Press tab on an entry you'd like to perform an action on. A marker will show up on the left indicating the entry is selected. Repeat the process and select as many entries as needed. When you hit Return, the selection will be passed back to doing. Use Control-A to select all visible entries.
625
625
 
626
- Doing can perform several functions with this menu. Not all of doing's features are available, but the core functionality you'd need is there, plus you can open all selected entries in your editor, make changes to them, and when you save and close the entries are updated accordingly. This allows editing of everything from timestamps to tags to notes.
626
+ Doing can perform several functions with this menu. Not all of doing's features are available, but the core functionality you'd need is there, plus you can open the selected entries on one page in your text editor, make changes to them, and when you save and close the entries are updated accordingly. This allows editing of everything from timestamps to tags to notes.
627
627
 
628
628
  Run `doing help select` for a list of options:
629
629
 
@@ -641,6 +641,8 @@ For example, `doing select -d -a` would present the menu, and then mark selected
641
641
 
642
642
  Multiple actions can be performed at once by combining options. You can also combine the `--editor` switch with any other options. Other actions will be performed first, then the entries --- with any modifications performed --- will be presented in the editor for tweaking.
643
643
 
644
+ **Note:** when using the `--editor` flag to open selections in your text editor, entries will be separated by `---` lines. These must remain in place for doing to track the changes. You can do anything you want to the entries, modify dates, change text, add notes, etc., as long as you leave the dividers in place. You can even delete an entry entirely, leaving the dividers around the missing line and the entry will be removed from your doing file when you save and exit the editor.
645
+
644
646
  ---
645
647
 
646
648
  ## Extras
data/bin/doing CHANGED
@@ -79,7 +79,7 @@ command %i[now next] do |c|
79
79
  if options[:back]
80
80
  date = wwid.chronify(options[:back])
81
81
 
82
- raise 'Unable to parse date string' if date.nil?
82
+ exit_now! 'Unable to parse date string' if date.nil?
83
83
  else
84
84
  date = Time.now
85
85
  end
@@ -87,13 +87,13 @@ command %i[now next] do |c|
87
87
  section = wwid.guess_section(options[:s]) || options[:s].cap_first
88
88
 
89
89
  if options[:e] || (args.empty? && $stdin.stat.size.zero?)
90
- raise 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
90
+ exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
91
91
 
92
92
  input = ''
93
93
  input += args.join(' ') unless args.empty?
94
94
  input = wwid.fork_editor(input).strip
95
95
 
96
- raise 'No content' if input.empty?
96
+ exit_now! 'No content' if input.empty?
97
97
 
98
98
  title, note = wwid.format_input(input)
99
99
  note.push(options[:n]) if options[:n]
@@ -111,7 +111,7 @@ command %i[now next] do |c|
111
111
  wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:f] })
112
112
  wwid.write(wwid.doing_file)
113
113
  else
114
- raise 'You must provide content when creating a new entry'
114
+ exit_now! 'You must provide content when creating a new entry'
115
115
  end
116
116
  end
117
117
  end
@@ -138,7 +138,7 @@ command :note do |c|
138
138
  section = wwid.guess_section(options[:s]) || options[:s].cap_first
139
139
 
140
140
  if options[:e] || (args.empty? && $stdin.stat.size.zero? && !options[:r])
141
- raise 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
141
+ exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
142
142
 
143
143
  input = !args.empty? ? args.join(' ') : ''
144
144
 
@@ -147,11 +147,11 @@ command :note do |c|
147
147
  input = prev_input + input
148
148
 
149
149
  input = wwid.fork_editor(input).strip
150
- raise 'No content, cancelled' unless input
150
+ exit_now! 'No content, cancelled' unless input
151
151
 
152
152
  _title, note = wwid.format_input(input)
153
153
 
154
- raise 'No note content' unless note
154
+ exit_now! 'No note content' unless note
155
155
 
156
156
  wwid.note_last(section, note, replace: true)
157
157
  elsif !args.empty?
@@ -165,7 +165,7 @@ command :note do |c|
165
165
  elsif options[:r]
166
166
  wwid.note_last(section, [], replace: true)
167
167
  else
168
- raise 'You must provide content when adding a note'
168
+ exit_now! 'You must provide content when adding a note'
169
169
  end
170
170
  wwid.write(wwid.doing_file)
171
171
  end
@@ -196,7 +196,7 @@ command :meanwhile do |c|
196
196
  if options[:back]
197
197
  date = wwid.chronify(options[:back])
198
198
 
199
- raise 'Unable to parse date string' if date.nil?
199
+ exit_now! 'Unable to parse date string' if date.nil?
200
200
  else
201
201
  date = Time.now
202
202
  end
@@ -205,7 +205,7 @@ command :meanwhile do |c|
205
205
  input = ''
206
206
 
207
207
  if options[:e]
208
- raise 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
208
+ exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
209
209
 
210
210
  input += args.join(' ') unless args.empty?
211
211
  input = wwid.fork_editor(input).strip
@@ -243,7 +243,7 @@ long_desc %(
243
243
  arg_name 'TYPE', must_match: /^(?:html|haml|css)/i
244
244
  command :template do |c|
245
245
  c.action do |_global_options, options, args|
246
- raise 'No type specified, use `doing template [HAML|CSS]`' if args.empty?
246
+ exit_now! 'No type specified, use `doing template [HAML|CSS]`' if args.empty?
247
247
 
248
248
  case args[0]
249
249
  when /html|haml/i
@@ -251,7 +251,7 @@ command :template do |c|
251
251
  when /css/i
252
252
  $stdout.puts wwid.css_template
253
253
  else
254
- raise 'Invalid type specified, must be HAML or CSS'
254
+ exit_now! 'Invalid type specified, must be HAML or CSS'
255
255
  end
256
256
  end
257
257
  end
@@ -322,17 +322,17 @@ command :later do |c|
322
322
  c.action do |_global_options, options, args|
323
323
  if options[:back]
324
324
  date = wwid.chronify(options[:back])
325
- raise 'Unable to parse date string' if date.nil?
325
+ exit_now! 'Unable to parse date string' if date.nil?
326
326
  else
327
327
  date = Time.now
328
328
  end
329
329
 
330
330
  if options[:e] || (args.empty? && $stdin.stat.size.zero?)
331
- raise 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
331
+ exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
332
332
 
333
333
  input = args.empty? ? '' : args.join(' ')
334
334
  input = wwid.fork_editor(input).strip
335
- raise 'No content' unless input && !input.empty?
335
+ exit_now! 'No content' unless input && !input.empty?
336
336
 
337
337
  title, note = wwid.format_input(input)
338
338
  note.push(options[:n]) if options[:n]
@@ -349,7 +349,7 @@ command :later do |c|
349
349
  wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
350
350
  wwid.write(wwid.doing_file)
351
351
  else
352
- raise 'You must provide content when creating a new entry'
352
+ exit_now! 'You must provide content when creating a new entry'
353
353
  end
354
354
  end
355
355
  end
@@ -397,19 +397,19 @@ command %i[done did] do |c|
397
397
 
398
398
  if options[:took]
399
399
  took = wwid.chronify_qty(options[:took])
400
- raise 'Unable to parse date string for --took' if took.nil?
400
+ exit_now! 'Unable to parse date string for --took' if took.nil?
401
401
  end
402
402
 
403
403
  if options[:back]
404
404
  date = wwid.chronify(options[:back])
405
- raise 'Unable to parse date string for --back' if date.nil?
405
+ exit_now! 'Unable to parse date string for --back' if date.nil?
406
406
  else
407
407
  date = options[:took] ? Time.now - took : Time.now
408
408
  end
409
409
 
410
410
  if options[:at]
411
411
  finish_date = wwid.chronify(options[:at])
412
- raise 'Unable to parse date string for --at' if finish_date.nil?
412
+ exit_now! 'Unable to parse date string for --at' if finish_date.nil?
413
413
 
414
414
  date = options[:took] ? finish_date - took : finish_date
415
415
  elsif options[:took]
@@ -427,12 +427,12 @@ command %i[done did] do |c|
427
427
  section = wwid.guess_section(options[:s]) || options[:s].cap_first
428
428
 
429
429
  if options[:e]
430
- raise 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
430
+ exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
431
431
 
432
432
  input = ''
433
433
  input += args.join(' ') unless args.empty?
434
434
  input = wwid.fork_editor(input).strip
435
- raise 'No content' unless input && !input.empty?
435
+ exit_now! 'No content' unless input && !input.empty?
436
436
 
437
437
  title, note = wwid.format_input(input)
438
438
  title += " @done#{donedate}"
@@ -467,7 +467,7 @@ command %i[done did] do |c|
467
467
  wwid.add_item(title.cap_first, section.cap_first, { note: note, back: date })
468
468
  wwid.write(wwid.doing_file)
469
469
  else
470
- raise 'You must provide content when creating a new entry'
470
+ exit_now! 'You must provide content when creating a new entry'
471
471
  end
472
472
  end
473
473
  end
@@ -513,9 +513,9 @@ command :cancel do |c|
513
513
  end
514
514
  end
515
515
 
516
- raise 'Only one argument allowed' if args.length > 1
516
+ exit_now! 'Only one argument allowed' if args.length > 1
517
517
 
518
- raise 'Invalid argument (specify number of recent items to mark @done)' unless args.empty? || args[0] =~ /\d+/
518
+ exit_now! 'Invalid argument (specify number of recent items to mark @done)' unless args.empty? || args[0] =~ /\d+/
519
519
 
520
520
  count = args[0] ? args[0].to_i : 1
521
521
  opts = {
@@ -580,14 +580,14 @@ command :finish do |c|
580
580
  section = wwid.guess_section(options[:s]) || options[:s].cap_first
581
581
 
582
582
  unless options[:auto]
583
- raise '--back and --took cannot be used together' if options[:back] && options[:took]
583
+ exit_now! '--back and --took cannot be used together' if options[:back] && options[:took]
584
584
 
585
- raise '--search and --tag cannot be used together' if options[:search] && options[:tag]
585
+ exit_now! '--search and --tag cannot be used together' if options[:search] && options[:tag]
586
586
 
587
587
  if options[:back]
588
588
  date = wwid.chronify(options[:back])
589
589
 
590
- raise 'Unable to parse date string' if date.nil?
590
+ exit_now! 'Unable to parse date string' if date.nil?
591
591
  elsif options[:took]
592
592
  date = wwid.chronify_qty(options[:took])
593
593
  else
@@ -611,9 +611,9 @@ command :finish do |c|
611
611
  end
612
612
  end
613
613
 
614
- raise 'Only one argument allowed' if args.length > 1
614
+ exit_now! 'Only one argument allowed' if args.length > 1
615
615
 
616
- raise 'Invalid argument (specify number of recent items to mark @done)' unless args.length == 0 || args[0] =~ /\d+/
616
+ exit_now! 'Invalid argument (specify number of recent items to mark @done)' unless args.length == 0 || args[0] =~ /\d+/
617
617
 
618
618
  count = args[0] ? args[0].to_i : 1
619
619
  opts = {
@@ -695,6 +695,9 @@ command :tag do |c|
695
695
  c.arg_name 'COUNT'
696
696
  c.flag %i[c count], default_value: 1
697
697
 
698
+ c.desc 'Don\'t ask permission to tag all entries when count is 0'
699
+ c.switch %i[force], negatable: false, default_value: false
700
+
698
701
  c.desc 'Include current date/time with tag'
699
702
  c.switch %i[d date], negatable: false, default_value: false
700
703
 
@@ -707,12 +710,48 @@ command :tag do |c|
707
710
  c.desc 'Autotag entries based on autotag configuration in ~/.doingrc'
708
711
  c.switch %i[a autotag], negatable: false, default_value: false
709
712
 
713
+ c.desc 'Tag the last X entries containing TAG.
714
+ Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool'
715
+ c.arg_name 'TAG'
716
+ c.flag [:tag]
717
+
718
+ c.desc 'Tag entries matching search filter, surround with slashes for regex (e.g. "/query.*/")'
719
+ c.arg_name 'QUERY'
720
+ c.flag [:search]
721
+
722
+ c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
723
+ c.arg_name 'BOOLEAN'
724
+ c.flag [:bool], must_match: /(?:and|all|any|or|not|none)/i, default_value: 'AND'
725
+
710
726
  c.action do |_global_options, options, args|
711
- raise 'You must specify at least one tag' if args.empty? && !options[:a]
727
+ exit_now! 'You must specify at least one tag' if args.empty? && !options[:a]
712
728
 
713
- section = wwid.guess_section(options[:s]) || options[:s].cap_first
729
+ exit_now! '--search and --tag cannot be used together' if options[:search] && options[:tag]
730
+
731
+ section = 'All'
732
+
733
+ if options[:section]
734
+ section = wwid.guess_section(options[:section]) || options[:section].cap_first
735
+ end
736
+
737
+
738
+ if options[:tag].nil?
739
+ search_tags = []
740
+ else
741
+ search_tags = options[:tag].split(/ *, */).map { |t| t.strip.sub(/^@/, '') }
742
+ options[:bool] = case options[:bool]
743
+ when /(and|all)/i
744
+ 'AND'
745
+ when /(any|or)/i
746
+ 'OR'
747
+ when /(not|none)/i
748
+ 'NOT'
749
+ else
750
+ 'AND'
751
+ end
752
+ end
714
753
 
715
- if options[:a]
754
+ if options[:autotag]
716
755
  tags = []
717
756
  else
718
757
  tags = if args.join('') =~ /,/
@@ -724,10 +763,19 @@ command :tag do |c|
724
763
  tags.map! { |tag| tag.sub(/^@/, '').strip }
725
764
  end
726
765
 
727
- count = options[:c].to_i
766
+ count = options[:count].to_i
767
+
768
+ if count.zero? && !options[:force]
769
+ if options[:search]
770
+ section_q = ' matching your search terms'
771
+ elsif options[:tag]
772
+ section_q = ' matching your tag search'
773
+ elsif section == 'All'
774
+ section_q = ''
775
+ else
776
+ section_q = " in section #{section}"
777
+ end
728
778
 
729
- if count.zero?
730
- section_q = section == 'All' ? '' : " in section #{section}"
731
779
 
732
780
  question = if options[:a]
733
781
  "Are you sure you want to autotag all records#{section_q}"
@@ -739,14 +787,18 @@ command :tag do |c|
739
787
 
740
788
  res = wwid.yn(question, default_response: false)
741
789
 
742
- raise 'Cancelled' unless res
790
+ exit_now! 'Cancelled' unless res
743
791
  end
792
+
744
793
  opts = {
745
794
  autotag: options[:a],
746
795
  count: count,
747
796
  date: options[:date],
748
797
  remove: options[:r],
798
+ search: options[:search],
749
799
  section: section,
800
+ tag: search_tags,
801
+ tag_bool: options[:bool],
750
802
  tags: tags,
751
803
  unfinished: options[:unfinished]
752
804
  }
@@ -813,10 +865,10 @@ command :show do |c|
813
865
  c.flag %i[f from]
814
866
 
815
867
  c.desc 'Show time intervals on @done tasks'
816
- c.switch %i[t times], default_value: true
868
+ c.switch %i[t times], default_value: true, negatable: true
817
869
 
818
870
  c.desc 'Show intervals with totals at the end of output'
819
- c.switch [:totals], default_value: false, negatable: true
871
+ c.switch [:totals], default_value: false, negatable: false
820
872
 
821
873
  c.desc 'Sort tags by (name|time)'
822
874
  default = 'time'
@@ -845,7 +897,7 @@ command :show do |c|
845
897
  section = 'All'
846
898
  else
847
899
  section = wwid.guess_section(args[0])
848
- raise "No such section: #{args[0]}" unless section
900
+ exit_now! "No such section: #{args[0]}" unless section
849
901
 
850
902
  args.shift
851
903
  end
@@ -935,10 +987,10 @@ command [:grep, :search] do |c|
935
987
  c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
936
988
 
937
989
  c.desc 'Show time intervals on @done tasks'
938
- c.switch %i[t times], default_value: true
990
+ c.switch %i[t times], default_value: true, negatable: true
939
991
 
940
992
  c.desc 'Show intervals with totals at the end of output'
941
- c.switch [:totals], default_value: false, negatable: true
993
+ c.switch [:totals], default_value: false, negatable: false
942
994
 
943
995
  c.desc 'Sort tags by (name|time)'
944
996
  default = 'time'
@@ -982,10 +1034,10 @@ command :recent do |c|
982
1034
  c.flag %i[s section], default_value: 'All'
983
1035
 
984
1036
  c.desc 'Show time intervals on @done tasks'
985
- c.switch %i[t times], default_value: true
1037
+ c.switch %i[t times], default_value: true, negatable: true
986
1038
 
987
1039
  c.desc 'Show intervals with totals at the end of output'
988
- c.switch [:totals], default_value: false, negatable: true
1040
+ c.switch [:totals], default_value: false, negatable: false
989
1041
 
990
1042
  c.desc 'Sort tags by (name|time)'
991
1043
  default = 'time'
@@ -1022,10 +1074,10 @@ command :today do |c|
1022
1074
  c.flag %i[s section], default_value: 'All'
1023
1075
 
1024
1076
  c.desc 'Show time intervals on @done tasks'
1025
- c.switch %i[t times], default_value: true
1077
+ c.switch %i[t times], default_value: true, negatable: true
1026
1078
 
1027
1079
  c.desc 'Show time totals at the end of output'
1028
- c.switch [:totals], default_value: false, negatable: true
1080
+ c.switch [:totals], default_value: false, negatable: false
1029
1081
 
1030
1082
  c.desc 'Sort tags by (name|time)'
1031
1083
  default = 'time'
@@ -1057,10 +1109,10 @@ command :on do |c|
1057
1109
  c.flag %i[s section], default_value: 'All'
1058
1110
 
1059
1111
  c.desc 'Show time intervals on @done tasks'
1060
- c.switch %i[t times], default_value: true
1112
+ c.switch %i[t times], default_value: true, negatable: true
1061
1113
 
1062
1114
  c.desc 'Show time totals at the end of output'
1063
- c.switch [:totals], default_value: false, negatable: true
1115
+ c.switch [:totals], default_value: false, negatable: false
1064
1116
 
1065
1117
  c.desc 'Sort tags by (name|time)'
1066
1118
  default = 'time'
@@ -1100,6 +1152,55 @@ command :on do |c|
1100
1152
  end
1101
1153
  end
1102
1154
 
1155
+ desc 'List entries since a date'
1156
+ long_desc %(Date argument can be natural language and are always interpreted as being in the past. "thursday" would be interpreted as "last thursday,"
1157
+ and "2d" would be interpreted as "two days ago.")
1158
+ arg_name 'DATE_STRING'
1159
+ command :since do |c|
1160
+ c.desc 'Section'
1161
+ c.arg_name 'NAME'
1162
+ c.flag %i[s section], default_value: 'All'
1163
+
1164
+ c.desc 'Show time intervals on @done tasks'
1165
+ c.switch %i[t times], default_value: true, negatable: true
1166
+
1167
+ c.desc 'Show time totals at the end of output'
1168
+ c.switch [:totals], default_value: false, negatable: false
1169
+
1170
+ c.desc 'Sort tags by (name|time)'
1171
+ default = 'time'
1172
+ default = wwid.config['tag_sort'] if wwid.config.key?('tag_sort')
1173
+ c.arg_name 'KEY'
1174
+ c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
1175
+
1176
+ c.desc 'Output to export format (csv|html|json|template|timeline)'
1177
+ c.arg_name 'FORMAT'
1178
+ c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
1179
+
1180
+ c.action do |_global_options, options, args|
1181
+ exit_now! 'Missing date argument' if args.empty?
1182
+
1183
+ date_string = args.join(' ')
1184
+
1185
+ date_string += ' at midnight' unless date_string =~ /(\d:|\d *[ap]m?|midnight|noon)/i
1186
+ date_string.sub!(/(day) (\d)/, '\1 at \2') if date_string =~ /day \d/
1187
+
1188
+ start = wwid.chronify(date_string)
1189
+ finish = Time.now
1190
+
1191
+ exit_now! 'Unrecognized date string' unless start
1192
+
1193
+ message = "Date interpreted as #{start} through the current time"
1194
+ wwid.results.push(message)
1195
+
1196
+ options[:t] = true if options[:totals]
1197
+ options[:sort_tags] = options[:tag_sort] =~ /^n/i
1198
+
1199
+ puts wwid.list_date([start, finish], options[:s], options[:t], options[:output],
1200
+ { totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
1201
+ end
1202
+ end
1203
+
1103
1204
  desc 'List entries from yesterday'
1104
1205
  command :yesterday do |c|
1105
1206
  c.desc 'Specify a section'
@@ -1111,10 +1212,10 @@ command :yesterday do |c|
1111
1212
  c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
1112
1213
 
1113
1214
  c.desc 'Show time intervals on @done tasks'
1114
- c.switch %i[t times], default_value: true
1215
+ c.switch %i[t times], default_value: true, negatable: true
1115
1216
 
1116
1217
  c.desc 'Show time totals at the end of output'
1117
- c.switch [:totals], default_value: false, negatable: true
1218
+ c.switch [:totals], default_value: false, negatable: false
1118
1219
 
1119
1220
  c.desc 'Sort tags by (name|time)'
1120
1221
  default = 'time'
@@ -1151,7 +1252,7 @@ command :last do |c|
1151
1252
  c.flag [:search]
1152
1253
 
1153
1254
  c.action do |_global_options, options, _args|
1154
- raise '--tag and --search cannot be used together' if options[:tag] && options[:search]
1255
+ exit_now! '--tag and --search cannot be used together' if options[:tag] && options[:search]
1155
1256
 
1156
1257
  if options[:tag].nil?
1157
1258
  tags = []
@@ -1200,7 +1301,7 @@ desc 'Add a new section to the "doing" file'
1200
1301
  arg_name 'SECTION_NAME'
1201
1302
  command :add_section do |c|
1202
1303
  c.action do |_global_options, _options, args|
1203
- raise "Section #{args[0]} already exists" if wwid.sections.include?(args[0])
1304
+ exit_now! "Section #{args[0]} already exists" if wwid.sections.include?(args[0])
1204
1305
 
1205
1306
  wwid.add_section(args[0].cap_first)
1206
1307
  wwid.write(wwid.doing_file)
@@ -1241,10 +1342,10 @@ command :view do |c|
1241
1342
  c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
1242
1343
 
1243
1344
  c.desc 'Show time intervals on @done tasks'
1244
- c.switch %i[t times], default_value: true
1345
+ c.switch %i[t times], default_value: true, negatable: true
1245
1346
 
1246
1347
  c.desc 'Show intervals with totals at the end of output'
1247
- c.switch [:totals], default_value: false, negatable: true
1348
+ c.switch [:totals], default_value: false, negatable: false
1248
1349
 
1249
1350
  c.desc 'Include colors in output'
1250
1351
  c.switch [:color], default_value: true, negatable: true
@@ -1256,7 +1357,7 @@ command :view do |c|
1256
1357
  c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
1257
1358
 
1258
1359
  c.desc 'Only show items with recorded time intervals'
1259
- c.switch [:only_timed], default_value: false, negatable: true
1360
+ c.switch [:only_timed], default_value: false, negatable: false
1260
1361
 
1261
1362
  c.action do |_global_options, options, args|
1262
1363
  title = if args.empty?
@@ -1328,7 +1429,7 @@ command :view do |c|
1328
1429
  elsif title.instance_of?(FalseClass)
1329
1430
  exit_now! 'Cancelled'
1330
1431
  else
1331
- raise "View #{title} not found in config"
1432
+ exit_now! "View #{title} not found in config"
1332
1433
  end
1333
1434
  end
1334
1435
  end
@@ -1385,7 +1486,7 @@ command :archive do |c|
1385
1486
  tags = args.length > 1 ? args[1..].map { |t| t.sub(/^@/, '').strip } : []
1386
1487
  end
1387
1488
 
1388
- raise '--keep and --count can\'t be used together' if options[:keep] && options[:count]
1489
+ exit_now! '--keep and --count can\'t be used together' if options[:keep] && options[:count]
1389
1490
 
1390
1491
  tags.concat(options[:tag].split(/ *, */).map { |t| t.sub(/^@/, '').strip }) if options[:tag]
1391
1492
 
@@ -1436,7 +1537,7 @@ command :open do |c|
1436
1537
  elsif options[:b]
1437
1538
  system %(open -b "#{options[:b]}" "#{File.expand_path(wwid.doing_file)}")
1438
1539
  elsif options[:e]
1439
- raise 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
1540
+ exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
1440
1541
 
1441
1542
  system %($EDITOR "#{File.expand_path(wwid.doing_file)}")
1442
1543
  elsif wwid.config.key?('editor_app') && !wwid.config['editor_app'].nil?
@@ -1446,7 +1547,7 @@ command :open do |c|
1446
1547
  end
1447
1548
 
1448
1549
  else
1449
- raise 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
1550
+ exit_now! 'No EDITOR variable defined in environment' if ENV['EDITOR'].nil?
1450
1551
 
1451
1552
  system %($EDITOR "#{File.expand_path(wwid.doing_file)}")
1452
1553
  end
@@ -1483,13 +1584,13 @@ command :config do |c|
1483
1584
  `open -b #{options[:b]} "#{wwid.config_file}"`
1484
1585
  end
1485
1586
  else
1486
- raise 'No EDITOR variable defined in environment' if options[:e].nil? && ENV['EDITOR'].nil?
1587
+ exit_now! 'No EDITOR variable defined in environment' if options[:e].nil? && ENV['EDITOR'].nil?
1487
1588
 
1488
1589
  editor = options[:e].nil? ? ENV['EDITOR'] : options[:e]
1489
1590
  system %(#{editor} "#{wwid.config_file}")
1490
1591
  end
1491
1592
  else
1492
- raise 'No EDITOR variable defined in environment' if options[:e].nil? && ENV['EDITOR'].nil?
1593
+ exit_now! 'No EDITOR variable defined in environment' if options[:e].nil? && ENV['EDITOR'].nil?
1493
1594
 
1494
1595
  editor = options[:e].nil? ? ENV['EDITOR'] : options[:e]
1495
1596
  system %(#{editor} "#{wwid.config_file}")
@@ -1542,7 +1643,7 @@ command :import do |c|
1542
1643
  wwid.write(wwid.doing_file)
1543
1644
  end
1544
1645
  else
1545
- raise 'Invalid import type'
1646
+ exit_now! 'Invalid import type'
1546
1647
  end
1547
1648
  end
1548
1649
  end
data/lib/doing/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Doing
2
- VERSION = '1.0.72'
2
+ VERSION = '1.0.76'
3
3
  end
data/lib/doing/wwid.rb CHANGED
@@ -73,7 +73,7 @@ class WWID
73
73
  rescue StandardError
74
74
  @config = {}
75
75
  @local_config = {}
76
- # raise "error reading config"
76
+ # exit_now! "error reading config"
77
77
  end
78
78
  end
79
79
 
@@ -158,7 +158,7 @@ class WWID
158
158
 
159
159
  # if ENV['DOING_DEBUG'].to_i == 3
160
160
  # if @config['default_tags'].length > 0
161
- # raise "DEFAULT CONFIG CHANGED"
161
+ # exit_now! "DEFAULT CONFIG CHANGED"
162
162
  # end
163
163
  # end
164
164
 
@@ -293,14 +293,14 @@ class WWID
293
293
  if $?.exitstatus == 0
294
294
  input = IO.read(tmpfile.path)
295
295
  else
296
- raise 'Cancelled'
296
+ exit_now! 'Cancelled'
297
297
  end
298
298
  ensure
299
299
  tmpfile.close
300
300
  tmpfile.unlink
301
301
  end
302
302
 
303
- input.split(/\n/).delete_if {|line| line =~ /^#/ }.join("\n").strip
303
+ input.split(/\n/).delete_if {|line| line =~ /^#/ }.join("\n")
304
304
  end
305
305
 
306
306
  #
@@ -311,11 +311,11 @@ class WWID
311
311
  # @param input (String) The string to parse
312
312
  #
313
313
  def format_input(input)
314
- raise 'No content in entry' if input.nil? || input.strip.empty?
314
+ exit_now! 'No content in entry' if input.nil? || input.strip.empty?
315
315
 
316
316
  input_lines = input.split(/[\n\r]+/).delete_if {|line| line =~ /^#/ || line =~ /^\s*$/ }
317
317
  title = input_lines[0]&.strip
318
- raise 'No content in first line' if title.nil? || title.strip.empty?
318
+ exit_now! 'No content in first line' if title.nil? || title.strip.empty?
319
319
 
320
320
  note = input_lines.length > 1 ? input_lines[1..-1] : []
321
321
  # If title line ends in a parenthetical, use that as the note
@@ -346,7 +346,7 @@ class WWID
346
346
  #
347
347
  def chronify(input)
348
348
  now = Time.now
349
- raise "Invalid time expression #{input.inspect}" if input.to_s.strip == ''
349
+ exit_now! "Invalid time expression #{input.inspect}" if input.to_s.strip == ''
350
350
 
351
351
  secs_ago = if input.match(/^(\d+)$/)
352
352
  # plain number, assume minutes
@@ -438,7 +438,7 @@ class WWID
438
438
  end
439
439
  unless section || guessed
440
440
  alt = guess_view(frag, true)
441
- raise "Did you mean `doing view #{alt}`?" if alt
441
+ exit_now! "Did you mean `doing view #{alt}`?" if alt
442
442
 
443
443
  res = yn("Section #{frag} not found, create it", default_response: false)
444
444
 
@@ -448,7 +448,7 @@ class WWID
448
448
  return frag.cap_first
449
449
  end
450
450
 
451
- raise "Unknown section: #{frag}"
451
+ exit_now! "Unknown section: #{frag}"
452
452
  end
453
453
  section ? section.cap_first : guessed
454
454
  end
@@ -518,9 +518,9 @@ class WWID
518
518
  unless view || guessed
519
519
  alt = guess_section(frag, guessed: true)
520
520
  if alt
521
- raise "Did you mean `doing show #{alt}`?"
521
+ exit_now! "Did you mean `doing show #{alt}`?"
522
522
  else
523
- raise "Unknown view: #{frag}"
523
+ exit_now! "Unknown view: #{frag}"
524
524
  end
525
525
  end
526
526
  view
@@ -631,7 +631,7 @@ class WWID
631
631
 
632
632
  add_tags = opt[:tag] ? opt[:tag].split(/[ ,]+/).map { |t| t.sub(/^@?/, '@') }.join(' ') : ''
633
633
  prefix = opt[:prefix] ? opt[:prefix] : '[Timing.app]'
634
- raise "File not found" unless File.exist?(File.expand_path(path))
634
+ exit_now! "File not found" unless File.exist?(File.expand_path(path))
635
635
 
636
636
  data = JSON.parse(IO.read(File.expand_path(path)))
637
637
  new_items = []
@@ -688,7 +688,7 @@ class WWID
688
688
  section = combined['items'].dup.sort_by { |item| item['date'] }.reverse[0]['section']
689
689
  end
690
690
 
691
- raise "Section #{section} not found" unless @content.key?(section)
691
+ exit_now! "Section #{section} not found" unless @content.key?(section)
692
692
 
693
693
  last_item = @content[section]['items'].dup.sort_by { |item| item['date'] }.reverse[0]
694
694
  warn "Editing note for #{last_item['title']}"
@@ -773,7 +773,7 @@ class WWID
773
773
  ## @param opt (Hash) Additional options
774
774
  ##
775
775
  def interactive(opt = {})
776
- raise "Select command requires that fzf be installed" unless exec_available('fzf')
776
+ exit_now! "Select command requires that fzf be installed" unless exec_available('fzf')
777
777
 
778
778
  section = opt[:section] ? guess_section(opt[:section]) : 'All'
779
779
 
@@ -807,7 +807,7 @@ class WWID
807
807
  out.join('')
808
808
  end
809
809
 
810
- res = `echo #{Shellwords.escape(options.join("\n"))}|fzf -m`
810
+ res = `echo #{Shellwords.escape(options.join("\n"))}|fzf -m --bind ctrl-a:select-all`
811
811
  selected = []
812
812
  res.split(/\n/).each do |item|
813
813
  idx = item.match(/^(\d+)\)/)[1].to_i
@@ -860,13 +860,17 @@ class WWID
860
860
  editable += "\n#{old_note}" unless old_note.nil?
861
861
  editable_items << editable
862
862
  end
863
+ divider = "\n-----------\n"
864
+ input = editable_items.map(&:strip).join(divider) + "\n\n# You may delete entries, but leave all divider lines in place"
863
865
 
864
- new_items = fork_editor(editable_items.join("\n---\n") + "\n\n# You may delete entries, but leave all --- lines in place").split(/\n---\n/)
866
+ new_items = fork_editor(input).split(/#{divider}/)
865
867
 
866
868
  new_items.each_with_index do |new_item, i|
869
+
867
870
  input_lines = new_item.split(/[\n\r]+/).delete_if {|line| line =~ /^#/ || line =~ /^\s*$/ }
868
871
  title = input_lines[0]&.strip
869
- if title.nil? || title =~ /^---$/ || title.strip.empty?
872
+
873
+ if title.nil? || title =~ /^#{divider.strip}$/ || title.strip.empty?
870
874
  delete_item(selected[i])
871
875
  else
872
876
  note = input_lines.length > 1 ? input_lines[1..-1] : []
@@ -880,7 +884,7 @@ class WWID
880
884
  item = selected[i].dup
881
885
  item['title'] = title
882
886
  item['note'] = note
883
- item['date'] = Time.parse(date)
887
+ item['date'] = Time.parse(date) || selected[i]['date']
884
888
  update_item(selected[i], item)
885
889
  end
886
890
  end
@@ -895,7 +899,7 @@ class WWID
895
899
  ## @param opt (Hash) Additional Options
896
900
  ##
897
901
  def tag_last(opt = {})
898
- opt[:section] ||= @current_section
902
+ opt[:section] ||= nil
899
903
  opt[:count] ||= 1
900
904
  opt[:archive] ||= false
901
905
  opt[:tags] ||= ['done']
@@ -910,7 +914,11 @@ class WWID
910
914
  sec_arr = []
911
915
 
912
916
  if opt[:section].nil?
913
- sec_arr = [@current_section]
917
+ if opt[:search] || opt[:tag]
918
+ sec_arr = sections
919
+ else
920
+ sec_arr = [@current_section]
921
+ end
914
922
  elsif opt[:section].instance_of?(String)
915
923
  if opt[:section] =~ /^all$/i
916
924
  if opt[:count] == 1
@@ -921,7 +929,11 @@ class WWID
921
929
  items = combined['items'].dup.sort_by { |item| item['date'] }.reverse
922
930
  sec_arr.push(items[0]['section'])
923
931
  elsif opt[:count] > 1
924
- raise 'A count greater than one requires a section to be specified'
932
+ if opt[:search] || opt[:tag]
933
+ sec_arr = sections
934
+ else
935
+ exit_now! 'A count greater than one requires a section to be specified'
936
+ end
925
937
  else
926
938
  sec_arr = sections
927
939
  end
@@ -1021,7 +1033,7 @@ class WWID
1021
1033
  @results.push('Archiving is skipped when operating on all entries') if (opt[:count]).zero?
1022
1034
  end
1023
1035
  else
1024
- raise "Section not found: #{section}"
1036
+ exit_now! "Section not found: #{section}"
1025
1037
  end
1026
1038
  end
1027
1039
 
@@ -1191,7 +1203,7 @@ class WWID
1191
1203
  section = combined['items'].dup.sort_by { |item| item['date'] }.reverse[0]['section']
1192
1204
  end
1193
1205
 
1194
- raise "Section #{section} not found" unless @content.key?(section)
1206
+ exit_now! "Section #{section} not found" unless @content.key?(section)
1195
1207
 
1196
1208
  # sort_section(opt[:section])
1197
1209
  items = @content[section]['items'].dup.sort_by { |item| item['date'] }.reverse
@@ -1427,7 +1439,7 @@ class WWID
1427
1439
  end
1428
1440
  end
1429
1441
 
1430
- raise 'Invalid section object' unless opt[:section].instance_of? Hash
1442
+ exit_now! 'Invalid section object' unless opt[:section].instance_of? Hash
1431
1443
 
1432
1444
  items = opt[:section]['items'].sort_by { |item| item['date'] }
1433
1445
 
@@ -1477,7 +1489,7 @@ class WWID
1477
1489
 
1478
1490
  out = ''
1479
1491
 
1480
- raise 'Unknown output format' if opt[:output] && (opt[:output] !~ /^(template|html|csv|json|timeline)$/i)
1492
+ exit_now! 'Unknown output format' if opt[:output] && (opt[:output] !~ /^(template|html|csv|json|timeline)$/i)
1481
1493
 
1482
1494
  case opt[:output]
1483
1495
  when /^csv$/i
@@ -1534,13 +1546,13 @@ class WWID
1534
1546
  'id' => index + 1,
1535
1547
  'content' => title.strip, #+ " #{note}"
1536
1548
  'title' => title.strip + " (#{'%02d:%02d:%02d' % fmt_time(interval)})",
1537
- 'start' => i['date'].strftime('%F'),
1549
+ 'start' => i['date'].strftime('%F %T'),
1538
1550
  'type' => 'point'
1539
1551
  }
1540
1552
 
1541
- if interval && interval > 0
1542
- new_item['end'] = end_date.strftime('%F')
1543
- new_item['type'] = 'range' if interval > 3600 * 3
1553
+ if interval && interval.to_i > 0
1554
+ new_item['end'] = end_date.strftime('%F %T')
1555
+ new_item['type'] = 'range' if interval.to_i > 3600 * 3
1544
1556
  end
1545
1557
  items_out.push(new_item)
1546
1558
  end
@@ -1556,8 +1568,8 @@ class WWID
1556
1568
  <!doctype html>
1557
1569
  <html>
1558
1570
  <head>
1559
- <link href="http://visjs.org/dist/vis.css" rel="stylesheet" type="text/css" />
1560
- <script src="http://visjs.org/dist/vis.js"></script>
1571
+ <link href="https://unpkg.com/vis-timeline@7.4.9/dist/vis-timeline-graph2d.min.css" rel="stylesheet" type="text/css" />
1572
+ <script src="https://unpkg.com/vis-timeline@7.4.9/dist/vis-timeline-graph2d.min.js"></script>
1561
1573
  </head>
1562
1574
  <body>
1563
1575
  <div id="mytimeline"></div>
@@ -1750,7 +1762,7 @@ class WWID
1750
1762
  do_archive(section, destination, { count: count, tags: tags, bool: bool, search: options[:search], label: options[:label] })
1751
1763
  write(doing_file)
1752
1764
  else
1753
- raise 'Either source or destination does not exist'
1765
+ exit_now! 'Either source or destination does not exist'
1754
1766
  end
1755
1767
  end
1756
1768
 
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.72
4
+ version: 1.0.76
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-15 00:00:00.000000000 Z
11
+ date: 2021-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -36,14 +36,14 @@ dependencies:
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: 6.2.1
39
+ version: 6.3.1
40
40
  type: :development
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: 6.2.1
46
+ version: 6.3.1
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: aruba
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -62,16 +62,16 @@ dependencies:
62
62
  name: test-unit
63
63
  requirement: !ruby/object:Gem::Requirement
64
64
  requirements:
65
- - - ">="
65
+ - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: '0'
67
+ version: 3.4.4
68
68
  type: :development
69
69
  prerelease: false
70
70
  version_requirements: !ruby/object:Gem::Requirement
71
71
  requirements:
72
- - - ">="
72
+ - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: '0'
74
+ version: 3.4.4
75
75
  - !ruby/object:Gem::Dependency
76
76
  name: gli
77
77
  requirement: !ruby/object:Gem::Requirement