doing 2.0.5.pre → 2.0.6.pre

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.
data/bin/doing CHANGED
@@ -57,8 +57,8 @@ config = Doing.config
57
57
  settings = config.settings
58
58
  wwid.config = settings
59
59
 
60
- if config.settings.dig('plugins', 'command_path')
61
- commands_from File.expand_path(config.settings.dig('plugins', 'command_path'))
60
+ if settings.dig('plugins', 'command_path')
61
+ commands_from File.expand_path(settings.dig('plugins', 'command_path'))
62
62
  end
63
63
 
64
64
  program_desc 'A CLI for a What Was I Doing system'
@@ -137,7 +137,7 @@ command %i[now next] do |c|
137
137
  if options[:back]
138
138
  date = wwid.chronify(options[:back], guess: :begin)
139
139
 
140
- raise Doing::Errors::InvalidTimeExpression, 'Unable to parse date string' if date.nil?
140
+ raise InvalidTimeExpression.new('unable to parse date string', topic: 'Date parser:') if date.nil?
141
141
  else
142
142
  date = Time.now
143
143
  end
@@ -149,13 +149,13 @@ command %i[now next] do |c|
149
149
  end
150
150
 
151
151
  if options[:e] || (args.empty? && $stdin.stat.size.zero?)
152
- raise Doing::Errors::MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
152
+ raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
153
153
 
154
154
  input = ''
155
155
  input += args.join(' ') unless args.empty?
156
156
  input = wwid.fork_editor(input).strip
157
157
 
158
- raise Doing::Errors::EmptyInput, 'No content' if input.empty?
158
+ raise EmptyInput, 'No content' if input.empty?
159
159
 
160
160
  title, note = wwid.format_input(input)
161
161
  note.push(options[:n]) if options[:n]
@@ -173,7 +173,7 @@ command %i[now next] do |c|
173
173
  wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:f] })
174
174
  wwid.write(wwid.doing_file)
175
175
  else
176
- raise Doing::Errors::EmptyInput, 'You must provide content when creating a new entry'
176
+ raise EmptyInput, 'You must provide content when creating a new entry'
177
177
  end
178
178
  end
179
179
  end
@@ -291,7 +291,7 @@ command :note do |c|
291
291
  new_note = Doing::Note.new
292
292
 
293
293
  if options[:e] || (args.empty? && $stdin.stat.size.zero? && !options[:r])
294
- raise Doing::Errors::MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
294
+ raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
295
295
 
296
296
  input = !args.empty? ? args.join(' ') : ''
297
297
 
@@ -312,7 +312,7 @@ command :note do |c|
312
312
  elsif $stdin.stat.size.positive?
313
313
  new_note.add($stdin.read)
314
314
  else
315
- raise Doing::Errors::EmptyInput, 'You must provide content when adding a note' unless options[:remove]
315
+ raise EmptyInput, 'You must provide content when adding a note' unless options[:remove]
316
316
  end
317
317
 
318
318
  if last_note.equal?(new_note)
@@ -338,7 +338,7 @@ command :meanwhile do |c|
338
338
  c.switch %i[e editor], negatable: false, default_value: false
339
339
 
340
340
  c.desc 'Archive previous @meanwhile entry'
341
- c.switch %i[a archive], default_value: false
341
+ c.switch %i[a archive], negatable: false, default_value: false
342
342
 
343
343
  c.desc 'Backdate start date for new entry to date string [4pm|20m|2h|yesterday noon]'
344
344
  c.arg_name 'DATE_STRING'
@@ -352,7 +352,7 @@ command :meanwhile do |c|
352
352
  if options[:back]
353
353
  date = wwid.chronify(options[:back], guess: :begin)
354
354
 
355
- raise Doing::Errors::InvalidTimeExpression, 'Unable to parse date string' if date.nil?
355
+ raise InvalidTimeExpression, 'Unable to parse date string' if date.nil?
356
356
  else
357
357
  date = Time.now
358
358
  end
@@ -365,7 +365,7 @@ command :meanwhile do |c|
365
365
  input = ''
366
366
 
367
367
  if options[:e]
368
- raise Doing::Errors::MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
368
+ raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
369
369
 
370
370
  input += args.join(' ') unless args.empty?
371
371
  input = wwid.fork_editor(input).strip
@@ -403,7 +403,7 @@ long_desc %(
403
403
  arg_name 'TYPE', must_match: Doing::Plugins.template_regex
404
404
  command :template do |c|
405
405
  c.desc 'List all available templates'
406
- c.switch %i[l list]
406
+ c.switch %i[l list], negatable: false
407
407
 
408
408
  c.desc 'List in single column for completion'
409
409
  c.switch %i[c]
@@ -424,7 +424,7 @@ command :template do |c|
424
424
  type = args[0]
425
425
  end
426
426
 
427
- raise Doing::Errors::InvalidPluginType, "No type specified, use `doing template [#{Doing::Plugins.plugin_templates.join('|')}]`" unless type
427
+ raise InvalidPluginType, "No type specified, use `doing template [#{Doing::Plugins.plugin_templates.join('|')}]`" unless type
428
428
 
429
429
  $stdout.puts Doing::Plugins.template_for_trigger(type)
430
430
 
@@ -503,12 +503,12 @@ command :select do |c|
503
503
  c.flag %i[o output]
504
504
 
505
505
  c.desc "Copy selection as a new entry with current time and no @done tag. Only works with single selections. Can be combined with --editor."
506
- c.switch %i[again resume]
506
+ c.switch %i[again resume], negatable: false, default_value: false
507
507
 
508
508
  c.action do |_global_options, options, args|
509
- raise InvalidExportType, "Invalid export type: #{options[:output]}" if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
509
+ raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
510
510
 
511
- raise Doing::Errors::InvalidArgument, '--no-menu requires --query' if !options[:menu] && !options[:query]
511
+ raise InvalidArgument, '--no-menu requires --query' if !options[:menu] && !options[:query]
512
512
 
513
513
  wwid.interactive(options)
514
514
  end
@@ -531,17 +531,17 @@ command :later do |c|
531
531
  c.action do |_global_options, options, args|
532
532
  if options[:back]
533
533
  date = wwid.chronify(options[:back], guess: :begin)
534
- raise Doing::Errors::InvalidTimeExpression, 'Unable to parse date string' if date.nil?
534
+ raise InvalidTimeExpression, 'Unable to parse date string' if date.nil?
535
535
  else
536
536
  date = Time.now
537
537
  end
538
538
 
539
539
  if options[:editor] || (args.empty? && $stdin.stat.size.zero?)
540
- raise Doing::Errors::MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
540
+ raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
541
541
 
542
542
  input = args.empty? ? '' : args.join(' ')
543
543
  input = wwid.fork_editor(input).strip
544
- raise Doing::Errors::EmptyInput, 'No content' unless input && !input.empty?
544
+ raise EmptyInput, 'No content' unless input && !input.empty?
545
545
 
546
546
  title, note = wwid.format_input(input)
547
547
  note.push(options[:n]) if options[:n]
@@ -558,7 +558,7 @@ command :later do |c|
558
558
  wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
559
559
  wwid.write(wwid.doing_file)
560
560
  else
561
- raise Doing::Errors::EmptyInput, 'You must provide content when creating a new entry'
561
+ raise EmptyInput, 'You must provide content when creating a new entry'
562
562
  end
563
563
  end
564
564
  end
@@ -614,19 +614,19 @@ command %i[done did] do |c|
614
614
 
615
615
  if options[:took]
616
616
  took = wwid.chronify_qty(options[:took])
617
- raise Doing::Errors::InvalidTimeExpression, 'Unable to parse date string for --took' if took.nil?
617
+ raise InvalidTimeExpression, 'Unable to parse date string for --took' if took.nil?
618
618
  end
619
619
 
620
620
  if options[:back]
621
621
  date = wwid.chronify(options[:back], guess: :begin)
622
- raise Doing::Errors::InvalidTimeExpression, 'Unable to parse date string for --back' if date.nil?
622
+ raise InvalidTimeExpression, 'Unable to parse date string for --back' if date.nil?
623
623
  else
624
624
  date = options[:took] ? Time.now - took : Time.now
625
625
  end
626
626
 
627
627
  if options[:at]
628
628
  finish_date = wwid.chronify(options[:at], guess: :begin)
629
- raise Doing::Errors::InvalidTimeExpression, 'Unable to parse date string for --at' if finish_date.nil?
629
+ raise InvalidTimeExpression, 'Unable to parse date string for --at' if finish_date.nil?
630
630
 
631
631
  date = options[:took] ? finish_date - took : finish_date
632
632
  elsif options[:took]
@@ -651,7 +651,7 @@ command %i[done did] do |c|
651
651
  note.add(options[:note]) if options[:note]
652
652
 
653
653
  if options[:editor]
654
- raise Doing::Errors::MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
654
+ raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
655
655
  is_new = false
656
656
 
657
657
  if args.empty?
@@ -659,7 +659,7 @@ command %i[done did] do |c|
659
659
 
660
660
  unless last_entry
661
661
  Doing.logger.debug('Skipped:', options[:unfinished] ? 'No unfinished entry' : 'Last entry already @done')
662
- raise Doing::Errors::NoResults, 'No results'
662
+ raise NoResults, 'No results'
663
663
  end
664
664
 
665
665
  old_entry = last_entry.dup
@@ -671,7 +671,7 @@ command %i[done did] do |c|
671
671
  end
672
672
 
673
673
  input = wwid.fork_editor(input).strip
674
- raise Doing::Errors::EmptyInput, 'No content' unless input && !input.empty?
674
+ raise EmptyInput, 'No content' unless input && !input.empty?
675
675
 
676
676
  title, note = wwid.format_input(input)
677
677
  new_entry = Doing::Item.new(date, title, section, note)
@@ -746,7 +746,7 @@ command %i[done did] do |c|
746
746
  wwid.write(wwid.doing_file)
747
747
  Doing.logger.info('Entry Added:', new_entry.title)
748
748
  else
749
- raise Doing::Errors::EmptyInput, 'You must provide content when creating a new entry'
749
+ raise EmptyInput, 'You must provide content when creating a new entry'
750
750
  end
751
751
  end
752
752
  end
@@ -789,9 +789,9 @@ command :cancel do |c|
789
789
  tags = options[:tag].to_tags
790
790
  end
791
791
 
792
- raise Doing::Errors::InvalidArgument, 'Only one argument allowed' if args.length > 1
792
+ raise InvalidArgument, 'Only one argument allowed' if args.length > 1
793
793
 
794
- raise Doing::Errors::InvalidArgument, 'Invalid argument (specify number of recent items to mark @done)' unless args.empty? || args[0] =~ /\d+/
794
+ raise InvalidArgument, 'Invalid argument (specify number of recent items to mark @done)' unless args.empty? || args[0] =~ /\d+/
795
795
 
796
796
  if options[:interactive]
797
797
  count = 0
@@ -873,22 +873,22 @@ command :finish do |c|
873
873
  unless options[:auto]
874
874
  if options[:took]
875
875
  took = wwid.chronify_qty(options[:took])
876
- raise Doing::Errors::InvalidTimeExpression, 'Unable to parse date string for --took' if took.nil?
876
+ raise InvalidTimeExpression, 'Unable to parse date string for --took' if took.nil?
877
877
  end
878
878
 
879
- raise Doing::Errors::InvalidArgument, '--back and --took can not be used together' if options[:back] && options[:took]
879
+ raise InvalidArgument, '--back and --took can not be used together' if options[:back] && options[:took]
880
880
 
881
- raise Doing::Errors::InvalidArgument, '--search and --tag can not be used together' if options[:search] && options[:tag]
881
+ raise InvalidArgument, '--search and --tag can not be used together' if options[:search] && options[:tag]
882
882
 
883
883
  if options[:at]
884
884
  finish_date = wwid.chronify(options[:at], guess: :begin)
885
- raise Doing::Errors::InvalidTimeExpression, 'Unable to parse date string for --at' if finish_date.nil?
885
+ raise InvalidTimeExpression, 'Unable to parse date string for --at' if finish_date.nil?
886
886
 
887
887
  date = options[:took] ? finish_date - took : finish_date
888
888
  elsif options[:back]
889
889
  date = wwid.chronify(options[:back])
890
890
 
891
- raise Doing::Errors::InvalidTimeExpression, 'Unable to parse date string' if date.nil?
891
+ raise InvalidTimeExpression, 'Unable to parse date string' if date.nil?
892
892
  elsif options[:took]
893
893
  date = wwid.chronify_qty(options[:took])
894
894
  else
@@ -902,9 +902,9 @@ command :finish do |c|
902
902
  tags = options[:tag].to_tags
903
903
  end
904
904
 
905
- raise Doing::Errors::InvalidArgument, 'Only one argument allowed' if args.length > 1
905
+ raise InvalidArgument, 'Only one argument allowed' if args.length > 1
906
906
 
907
- raise Doing::Errors::InvalidArgument, 'Invalid argument (specify number of recent items to mark @done)' unless args.length == 0 || args[0] =~ /\d+/
907
+ raise InvalidArgument, 'Invalid argument (specify number of recent items to mark @done)' unless args.length == 0 || args[0] =~ /\d+/
908
908
 
909
909
  if options[:interactive]
910
910
  count = 0
@@ -1045,9 +1045,9 @@ command :tag do |c|
1045
1045
  c.switch %i[i interactive], negatable: false, default_value: false
1046
1046
 
1047
1047
  c.action do |_global_options, options, args|
1048
- raise Doing::Errors::MissingArgument, 'You must specify at least one tag' if args.empty? && !options[:a]
1048
+ raise MissingArgument, 'You must specify at least one tag' if args.empty? && !options[:autotag]
1049
1049
 
1050
- raise Doing::Errors::InvalidArgument, '--search and --tag can not be used together' if options[:search] && options[:tag]
1050
+ raise InvalidArgument, '--search and --tag can not be used together' if options[:search] && options[:tag]
1051
1051
 
1052
1052
  section = 'All'
1053
1053
 
@@ -1117,19 +1117,6 @@ command :tag do |c|
1117
1117
  end
1118
1118
  end
1119
1119
 
1120
- # desc 'Autotag last X entries'
1121
- # arg_name 'COUNT'
1122
- # command :autotag do |c|
1123
- # c.action do |global_options, options, args|
1124
- # options = {
1125
- # autotag: true,
1126
- # count: args[0].to_i
1127
- # }
1128
- # cmd = commands[:tag]
1129
- # cmd.action.(global_options, options, [])
1130
- # end
1131
- # end
1132
-
1133
1120
  desc 'Mark last entry as flagged'
1134
1121
  command [:mark, :flag] do |c|
1135
1122
  c.example 'doing flag', desc: 'Add @flagged to the last entry created'
@@ -1175,7 +1162,7 @@ command [:mark, :flag] do |c|
1175
1162
  c.action do |_global_options, options, _args|
1176
1163
  mark = settings['marker_tag'] || 'flagged'
1177
1164
 
1178
- raise Doing::Errors::InvalidArgument, '--search and --tag can not be used together' if options[:search] && options[:tag]
1165
+ raise InvalidArgument, '--search and --tag can not be used together' if options[:search] && options[:tag]
1179
1166
 
1180
1167
  section = 'All'
1181
1168
 
@@ -1307,8 +1294,8 @@ command :show do |c|
1307
1294
  c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
1308
1295
  c.arg_name 'FORMAT'
1309
1296
  c.flag %i[o output]
1310
- c.action do |_global_options, options, args|
1311
- raise InvalidExportType, "Invalid export type: #{options[:output]}" if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
1297
+ c.action do |global_options, options, args|
1298
+ raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
1312
1299
 
1313
1300
  tag_filter = false
1314
1301
  tags = []
@@ -1323,8 +1310,15 @@ command :show do |c|
1323
1310
  when /^@/
1324
1311
  section = 'All'
1325
1312
  else
1326
- section = wwid.guess_section(args[0])
1327
- raise Doing::Errors::InvalidSection, "No such section: #{args[0]}" unless section
1313
+ begin
1314
+ section = wwid.guess_section(args[0])
1315
+ rescue WrongCommand => exception
1316
+ cmd = commands[:view]
1317
+ action = cmd.send(:get_action, nil)
1318
+ return action.call(global_options, options, args)
1319
+ end
1320
+
1321
+ raise InvalidSection, "No such section: #{args[0]}" unless section
1328
1322
 
1329
1323
  args.shift
1330
1324
  end
@@ -1359,7 +1353,7 @@ command :show do |c|
1359
1353
  start = wwid.chronify(date_string, guess: :begin)
1360
1354
  finish = false
1361
1355
  end
1362
- raise Doing::Errors::InvalidTimeExpression, 'Unrecognized date string' unless start
1356
+ raise InvalidTimeExpression, 'Unrecognized date string' unless start
1363
1357
  dates = [start, finish]
1364
1358
  end
1365
1359
 
@@ -1433,7 +1427,7 @@ command %i[grep search] do |c|
1433
1427
  c.switch %i[i interactive], default_value: false, negatable: false
1434
1428
 
1435
1429
  c.action do |_global_options, options, args|
1436
- raise InvalidExportType, "Invalid export type: #{options[:output]}" if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
1430
+ raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
1437
1431
 
1438
1432
  tags_color = settings.key?('tags_color') ? settings['tags_color'] : nil
1439
1433
 
@@ -1548,7 +1542,7 @@ command :today do |c|
1548
1542
  c.flag [:after]
1549
1543
 
1550
1544
  c.action do |_global_options, options, _args|
1551
- raise InvalidExportType, "Invalid export type: #{options[:output]}" if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
1545
+ raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
1552
1546
 
1553
1547
  options[:t] = true if options[:totals]
1554
1548
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
@@ -1595,9 +1589,9 @@ command :on do |c|
1595
1589
  c.flag %i[o output]
1596
1590
 
1597
1591
  c.action do |_global_options, options, args|
1598
- raise InvalidExportType, "Invalid export type: #{options[:output]}" if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
1592
+ raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
1599
1593
 
1600
- raise Doing::Errors::MissingArgument, 'Missing date argument' if args.empty?
1594
+ raise MissingArgument, 'Missing date argument' if args.empty?
1601
1595
 
1602
1596
  date_string = args.join(' ')
1603
1597
 
@@ -1610,7 +1604,7 @@ command :on do |c|
1610
1604
  finish = false
1611
1605
  end
1612
1606
 
1613
- raise Doing::Errors::InvalidTimeExpression, 'Unrecognized date string' unless start
1607
+ raise InvalidTimeExpression, 'Unrecognized date string' unless start
1614
1608
 
1615
1609
  message = "Date interpreted as #{start}"
1616
1610
  message += " to #{finish}" if finish
@@ -1653,9 +1647,9 @@ command :since do |c|
1653
1647
  c.flag %i[o output]
1654
1648
 
1655
1649
  c.action do |_global_options, options, args|
1656
- raise InvalidExportType, "Invalid export type: #{options[:output]}" if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
1650
+ raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
1657
1651
 
1658
- raise Doing::Errors::MissingArgument, 'Missing date argument' if args.empty?
1652
+ raise MissingArgument, 'Missing date argument' if args.empty?
1659
1653
 
1660
1654
  date_string = args.join(' ')
1661
1655
 
@@ -1665,7 +1659,7 @@ command :since do |c|
1665
1659
  start = wwid.chronify(date_string, guess: :begin)
1666
1660
  finish = Time.now
1667
1661
 
1668
- raise Doing::Errors::InvalidTimeExpression, 'Unrecognized date string' unless start
1662
+ raise InvalidTimeExpression, 'Unrecognized date string' unless start
1669
1663
 
1670
1664
  Doing.logger.debug("Date interpreted as #{start} through the current time")
1671
1665
 
@@ -1698,7 +1692,6 @@ command :yesterday do |c|
1698
1692
  c.switch [:totals], default_value: false, negatable: false
1699
1693
 
1700
1694
  c.desc 'Sort tags by (name|time)'
1701
- default = 'time'
1702
1695
  default = settings['tag_sort'] || 'name'
1703
1696
  c.arg_name 'KEY'
1704
1697
  c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
@@ -1716,7 +1709,7 @@ command :yesterday do |c|
1716
1709
  c.flag [:tag_order], must_match: REGEX_SORT_ORDER, default_value: 'asc'
1717
1710
 
1718
1711
  c.action do |_global_options, options, _args|
1719
- raise InvalidExportType, "Invalid export type: #{options[:output]}" if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
1712
+ raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
1720
1713
 
1721
1714
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
1722
1715
 
@@ -1762,7 +1755,7 @@ command :last do |c|
1762
1755
  c.flag [:search]
1763
1756
 
1764
1757
  c.action do |global_options, options, _args|
1765
- raise Doing::Errors::InvalidArgument, '--tag and --search can not be used together' if options[:tag] && options[:search]
1758
+ raise InvalidArgument, '--tag and --search can not be used together' if options[:tag] && options[:search]
1766
1759
 
1767
1760
  if options[:tag].nil?
1768
1761
  tags = []
@@ -1791,7 +1784,7 @@ end
1791
1784
  desc 'List sections'
1792
1785
  command :sections do |c|
1793
1786
  c.desc 'List in single column'
1794
- c.switch %i[c column], default_value: false
1787
+ c.switch %i[c column], negatable: false, default_value: false
1795
1788
 
1796
1789
  c.action do |_global_options, options, _args|
1797
1790
  joiner = options[:c] ? "\n" : "\t"
@@ -1814,7 +1807,7 @@ command :add_section do |c|
1814
1807
  c.example 'doing add_section Ideas', desc: 'Add a section called Ideas to the doing file'
1815
1808
 
1816
1809
  c.action do |_global_options, _options, args|
1817
- raise Doing::Errors::InvalidArgument, "Section #{args[0]} already exists" if wwid.sections.include?(args[0])
1810
+ raise InvalidArgument, "Section #{args[0]} already exists" if wwid.sections.include?(args[0])
1818
1811
 
1819
1812
  wwid.add_section(args.join(' ').cap_first)
1820
1813
  wwid.write(wwid.doing_file)
@@ -1853,16 +1846,54 @@ command :plugins do |c|
1853
1846
 
1854
1847
  c.desc 'List plugins of type (import, export)'
1855
1848
  c.arg_name 'TYPE'
1856
- c.flag %i[t type], must_match: /^[iea].*$/i, default_value: 'all'
1849
+ c.flag %i[t type], must_match: /^(?:[iea].*)$/i, default_value: 'all'
1857
1850
 
1858
1851
  c.desc 'List in single column for completion'
1859
- c.switch %i[c column], default_value: false
1852
+ c.switch %i[c column], negatable: false, default_value: false
1860
1853
 
1861
1854
  c.action do |_global_options, options, _args|
1862
1855
  Doing::Plugins.list_plugins(options)
1863
1856
  end
1864
1857
  end
1865
1858
 
1859
+ desc 'Generate shell completion scripts'
1860
+ command :completion do |c|
1861
+ c.example 'doing completion', desc: 'Output zsh (default) to STDOUT'
1862
+ c.example 'doing completion --type zsh --file ~/.zsh-completions/_doing.zsh', desc: 'Output zsh completions to file'
1863
+ c.example 'doing completion --type fish --file ~/.config/fish/completions/doing.fish', desc: 'Output fish completions to file'
1864
+ c.example 'doing completion --type bash --file ~/.bash_it/completion/enabled/doing.bash', desc: 'Output bash completions to file'
1865
+
1866
+ c.desc 'Shell to generate for (bash, zsh, fish)'
1867
+ c.arg_name 'SHELL'
1868
+ c.flag %i[t type], must_match: /^[bzf](?:[ai]?sh)?$/i, default_value: 'zsh'
1869
+
1870
+ c.desc 'File to write output to'
1871
+ c.arg_name 'PATH'
1872
+ c.flag %i[f file], default_value: 'stdout'
1873
+
1874
+ c.action do |_global_options, options, _args|
1875
+ script_dir = File.join(File.dirname(__FILE__), '..', 'scripts')
1876
+
1877
+ case options[:type]
1878
+ when /^b/
1879
+ result = `ruby #{File.join(script_dir, 'generate_bash_completions.rb')}`
1880
+ when /^z/
1881
+ result = `ruby #{File.join(script_dir, 'generate_zsh_completions.rb')}`
1882
+ when /^f/
1883
+ result = `ruby #{File.join(script_dir, 'generate_fish_completions.rb')}`
1884
+ end
1885
+
1886
+ if options[:file] =~ /^stdout$/i
1887
+ $stdout.puts result
1888
+ else
1889
+ File.open(File.expand_path(options[:file]), 'w') do |f|
1890
+ f.puts result
1891
+ end
1892
+ Doing.logger.warn('File written:', "#{options[:type]} completions written to #{options[:file]}")
1893
+ end
1894
+ end
1895
+ end
1896
+
1866
1897
  desc 'Display a user-created view'
1867
1898
  long_desc 'Command line options override view configuration'
1868
1899
  arg_name 'VIEW_NAME'
@@ -1925,15 +1956,22 @@ command :view do |c|
1925
1956
  c.desc 'Select from a menu of matching entries to perform additional operations'
1926
1957
  c.switch %i[i interactive], negatable: false, default_value: false
1927
1958
 
1928
- c.action do |_global_options, options, args|
1929
- raise InvalidExportType, "Invalid export type: #{options[:output]}" if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
1959
+ c.action do |global_options, options, args|
1960
+ raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
1930
1961
 
1931
- raise Doing::Errors::InvalidArgument, '--tag and --search can not be used together' if options[:tag] && options[:search]
1962
+ raise InvalidArgument, '--tag and --search can not be used together' if options[:tag] && options[:search]
1932
1963
 
1933
1964
  title = if args.empty?
1934
1965
  wwid.choose_view
1935
1966
  else
1936
- wwid.guess_view(args[0])
1967
+ begin
1968
+ wwid.guess_view(args[0])
1969
+ rescue WrongCommand => exception
1970
+ cmd = commands[:show]
1971
+ options[:sort] = 'asc'
1972
+ action = cmd.send(:get_action, nil)
1973
+ return action.call(global_options, options, args)
1974
+ end
1937
1975
  end
1938
1976
 
1939
1977
  if options[:section]
@@ -2023,7 +2061,7 @@ command :view do |c|
2023
2061
  start = wwid.chronify(date_string, guess: :begin)
2024
2062
  finish = false
2025
2063
  end
2026
- raise Doing::Errors::InvalidTimeExpression, 'Unrecognized date string' unless start
2064
+ raise InvalidTimeExpression, 'Unrecognized date string' unless start
2027
2065
  dates = [start, finish]
2028
2066
  end
2029
2067
 
@@ -2046,9 +2084,9 @@ command :view do |c|
2046
2084
 
2047
2085
  Doing::Pager.page wwid.list_section(opts)
2048
2086
  elsif title.instance_of?(FalseClass)
2049
- exit_now! 'Cancelled'
2087
+ raise UserCancelled, 'Cancelled' unless res
2050
2088
  else
2051
- raise Doing::Errors::InvalidView, "View #{title} not found in config"
2089
+ raise InvalidView, "View #{title} not found in config"
2052
2090
  end
2053
2091
  end
2054
2092
  end
@@ -2119,7 +2157,7 @@ command %i[archive move] do |c|
2119
2157
  tags = args.length > 1 ? args[1..].map { |t| t.sub(/^@/, '').strip } : []
2120
2158
  end
2121
2159
 
2122
- raise Doing::Errors::InvalidArgument, '--keep and --count can not be used together' if options[:keep] && options[:count]
2160
+ raise InvalidArgument, '--keep and --count can not be used together' if options[:keep] && options[:count]
2123
2161
 
2124
2162
  tags.concat(options[:tag].to_tags) if options[:tag]
2125
2163
 
@@ -2208,7 +2246,7 @@ command :open do |c|
2208
2246
  system %(open "#{File.expand_path(wwid.doing_file)}")
2209
2247
  end
2210
2248
  else
2211
- raise Doing::Errors::MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
2249
+ raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
2212
2250
 
2213
2251
  system %(#{Doing::Util.default_editor} "#{File.expand_path(wwid.doing_file)}")
2214
2252
  end
@@ -2236,7 +2274,7 @@ command :config do |c|
2236
2274
  c.flag %i[e editor], default_value: nil
2237
2275
 
2238
2276
  c.desc 'Show a config key value based on arguments. Separate key paths with colons or dots, e.g. "export_templates.html". Empty arguments outputs the entire config.'
2239
- c.switch %i[d dump]
2277
+ c.switch %i[d dump], negatable: false
2240
2278
 
2241
2279
  c.desc 'Format for --dump (json|yaml|raw)'
2242
2280
  c.arg_name 'FORMAT'
@@ -2261,7 +2299,6 @@ command :config do |c|
2261
2299
  c.action do |_global_options, options, args|
2262
2300
  if options[:update]
2263
2301
  config.configure({rewrite: true, ignore_local: true})
2264
- # Doing.logger.warn("Config file rewritten: #{config.config_file}")
2265
2302
  return
2266
2303
  end
2267
2304
 
@@ -2276,7 +2313,6 @@ command :config do |c|
2276
2313
  when /^r/
2277
2314
  cfg
2278
2315
  else
2279
- # cfg = { last_key => cfg } unless last_key.nil?
2280
2316
  YAML.dump(cfg)
2281
2317
  end
2282
2318
  else
@@ -2291,7 +2327,7 @@ command :config do |c|
2291
2327
  choices.concat(config.additional_configs)
2292
2328
  res = wwid.choose_from(choices.uniq.sort.reverse, sorted: false, prompt: 'Local configs found, select which to edit > ')
2293
2329
 
2294
- raise Doing::Errors::UserCancelled, 'Cancelled' unless res
2330
+ raise UserCancelled, 'Cancelled' unless res
2295
2331
 
2296
2332
  config_file = res.strip || config.config_file
2297
2333
  else
@@ -2308,7 +2344,7 @@ command :config do |c|
2308
2344
  `open -a "#{editor}" "#{config_file}"`
2309
2345
  end
2310
2346
  else
2311
- raise Doing::Errors::InvalidArgument, 'No viable editor found in config or environment.'
2347
+ raise InvalidArgument, 'No viable editor found in config or environment.'
2312
2348
  end
2313
2349
  elsif options[:a] || options[:b]
2314
2350
  if options[:a]
@@ -2319,7 +2355,7 @@ command :config do |c|
2319
2355
  else
2320
2356
  editor = options[:e] || Doing::Util.find_default_editor('config')
2321
2357
 
2322
- raise Doing::Errors::MissingEditor, 'No viable editor defined in config or environment' unless editor
2358
+ raise MissingEditor, 'No viable editor defined in config or environment' unless editor
2323
2359
 
2324
2360
  if Doing::Util.exec_available(editor)
2325
2361
  system %(#{editor} "#{config_file}")
@@ -2329,7 +2365,7 @@ command :config do |c|
2329
2365
  end
2330
2366
  else
2331
2367
  editor = options[:e] || Doing::Util.default_editor
2332
- raise Doing::Errors::MissingEditor, 'No EDITOR variable defined in environment' unless editor && Doing::Util.exec_available(editor)
2368
+ raise MissingEditor, 'No EDITOR variable defined in environment' unless editor && Doing::Util.exec_available(editor)
2333
2369
 
2334
2370
  system %(#{editor} "#{config_file}")
2335
2371
  end
@@ -2413,7 +2449,7 @@ command :import do |c|
2413
2449
  start = wwid.chronify(date_string, guess: :begin)
2414
2450
  finish = false
2415
2451
  end
2416
- raise Doing::Errors::InvalidTimeExpression, 'Unrecognized date string' unless start
2452
+ raise InvalidTimeExpression, 'Unrecognized date string' unless start
2417
2453
  dates = [start, finish]
2418
2454
  end
2419
2455
 
@@ -2423,7 +2459,7 @@ command :import do |c|
2423
2459
  wwid.import(args, options)
2424
2460
  wwid.write(wwid.doing_file)
2425
2461
  else
2426
- raise Doing::Errors::InvalidPluginType, "Invalid import type: #{options[:type]}"
2462
+ raise InvalidPluginType, "Invalid import type: #{options[:type]}"
2427
2463
  end
2428
2464
  end
2429
2465
  end
@@ -2448,14 +2484,13 @@ pre do |global, _command, _options, _args|
2448
2484
  end
2449
2485
 
2450
2486
  on_error do |exception|
2451
- # if exception.kind_of?(SystemExit)
2452
- # false
2453
- # else
2454
- # p exception.inspect
2455
- # Doing.logger.output_results
2456
- # true
2457
- # end
2458
- false
2487
+ if exception.kind_of?(SystemExit)
2488
+ false
2489
+ else
2490
+ # Doing.logger.error('Fatal:', exception)
2491
+ Doing.logger.output_results
2492
+ true
2493
+ end
2459
2494
  end
2460
2495
 
2461
2496
  post do |global, _command, _options, _args|