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.
@@ -113,7 +113,7 @@ module Doing
113
113
  ##
114
114
  def list_plugins(options = {})
115
115
  separator = options[:column] ? "\n" : "\t"
116
- type = options[:type].nil? || options[:type] =~ /all/i ? 'all' : [valid_type(options[:type])]
116
+ type = options[:type].nil? || options[:type] =~ /all/i ? 'all' : valid_type(options[:type])
117
117
 
118
118
  case type
119
119
  when :import
data/lib/doing/string.rb CHANGED
@@ -209,11 +209,12 @@ module Doing
209
209
  title
210
210
  end
211
211
 
212
- def tag!(tag, value: nil, remove: false, rename_to: nil, regex: false)
213
- replace tag(tag, value: value, remove: remove, rename_to: rename_to, regex: regex)
212
+ def tag!(tag, value: nil, remove: false, rename_to: nil, regex: false, single: false)
213
+ replace tag(tag, value: value, remove: remove, rename_to: rename_to, regex: regex, single: single)
214
214
  end
215
215
 
216
- def tag(tag, value: nil, remove: false, rename_to: nil, regex: false)
216
+ def tag(tag, value: nil, remove: false, rename_to: nil, regex: false, single: false)
217
+ log_level = single ? :info : :debug
217
218
  title = dup
218
219
  title.chomp!
219
220
  tag = tag.sub(/^@?/, '')
@@ -241,10 +242,10 @@ module Doing
241
242
  if rename_to
242
243
  f = "@#{tag}".cyan
243
244
  t = "@#{rename_to}".cyan
244
- Doing.logger.debug('Tag:', %(renamed #{f} to #{t} in "#{title}"))
245
+ Doing.logger.write(log_level, 'Tag:', %(renamed #{f} to #{t} in "#{title}"))
245
246
  else
246
247
  f = "@#{tag}".cyan
247
- Doing.logger.debug('Tag:', %(removed #{f} from "#{title}"))
248
+ Doing.logger.write(log_level, 'Tag:', %(removed #{f} from "#{title}"))
248
249
  end
249
250
  else
250
251
  Doing.logger.debug('Skipped:', "not tagged #{"@#{tag}".cyan}")
@@ -260,7 +261,7 @@ module Doing
260
261
 
261
262
  title.dedup_tags!
262
263
  title.chomp!
263
- # Doing.logger.debug('Added tag:', %(#{('@' + tag).cyan} to "#{title}"))
264
+ Doing.logger.write(log_level, 'Tag:', %(added #{('@' + tag).cyan} to "#{title}"))
264
265
  end
265
266
 
266
267
  title.gsub(/ +/, ' ')
data/lib/doing/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Doing
2
- VERSION = '2.0.5.pre'
2
+ VERSION = '2.0.6.pre'
3
3
  end
data/lib/doing/wwid.rb CHANGED
@@ -123,9 +123,9 @@ module Doing
123
123
  ## @param input (String) Text input for editor
124
124
  ##
125
125
  def fork_editor(input = '')
126
- # raise Errors::NonInteractive, 'Non-interactive terminal' unless $stdout.isatty || ENV['DOING_EDITOR_TEST']
126
+ # raise NonInteractive, 'Non-interactive terminal' unless $stdout.isatty || ENV['DOING_EDITOR_TEST']
127
127
 
128
- raise Errors::MissingEditor, 'No EDITOR variable defined in environment' if Util.default_editor.nil?
128
+ raise MissingEditor, 'No EDITOR variable defined in environment' if Util.default_editor.nil?
129
129
 
130
130
  tmpfile = Tempfile.new(['doing', '.md'])
131
131
 
@@ -173,11 +173,11 @@ module Doing
173
173
  ## @return (Array) [(String)title, (Note)note]
174
174
  ##
175
175
  def format_input(input)
176
- raise Errors::EmptyInput, 'No content in entry' if input.nil? || input.strip.empty?
176
+ raise EmptyInput, 'No content in entry' if input.nil? || input.strip.empty?
177
177
 
178
178
  input_lines = input.split(/[\n\r]+/).delete_if(&:ignore?)
179
179
  title = input_lines[0]&.strip
180
- raise Errors::EmptyInput, 'No content in first line' if title.nil? || title.strip.empty?
180
+ raise EmptyInput, 'No content in first line' if title.nil? || title.strip.empty?
181
181
 
182
182
  note = Note.new
183
183
  note.add(input_lines[1..-1]) if input_lines.length > 1
@@ -209,7 +209,7 @@ module Doing
209
209
  ##
210
210
  def chronify(input, future: false, guess: :begin)
211
211
  now = Time.now
212
- raise Errors::InvalidTimeExpression, "Invalid time expression #{input.inspect}" if input.to_s.strip == ''
212
+ raise InvalidTimeExpression, "Invalid time expression #{input.inspect}" if input.to_s.strip == ''
213
213
 
214
214
  secs_ago = if input.match(/^(\d+)$/)
215
215
  # plain number, assume minutes
@@ -277,8 +277,7 @@ module Doing
277
277
  ##
278
278
  def add_section(title)
279
279
  if @content.key?(title.cap_first)
280
- logger.debug('Skipped': 'Section already exists')
281
- return
280
+ raise InvalidSection, %(section "#{title.cap_first}" already exists)
282
281
  end
283
282
 
284
283
  @content[title.cap_first] = { :original => "#{title}:", :items => [] }
@@ -311,11 +310,13 @@ module Doing
311
310
  unless section || guessed
312
311
  alt = guess_view(frag, guessed: true, suggest: true)
313
312
  if alt
314
- meant_view = yn("Did you mean `doing view #{alt}`?", default_response: 'n')
315
- raise Errors::InvalidSection, "Run again with `doing view #{alt}`" if meant_view
313
+ meant_view = yn("#{Color.boldwhite}Did you mean `#{Color.yellow}doing view #{alt}#{Color.boldwhite}`?", default_response: 'n')
314
+
315
+ raise WrongCommand.new("run again with #{"doing view #{alt}".boldwhite}", topic: 'Try again:') if meant_view
316
+
316
317
  end
317
318
 
318
- res = yn("Section #{frag} not found, create it", default_response: 'n')
319
+ res = yn("#{Color.boldwhite}Section #{frag.yellow}#{Color.boldwhite} not found, create it", default_response: 'n')
319
320
 
320
321
  if res
321
322
  add_section(frag.cap_first)
@@ -323,7 +324,7 @@ module Doing
323
324
  return frag.cap_first
324
325
  end
325
326
 
326
- raise Errors::InvalidSection, "Unknown section: #{frag}"
327
+ raise InvalidSection.new("unknown section #{frag.yellow}", topic: 'Missing:')
327
328
  end
328
329
  section ? section.cap_first : guessed
329
330
  end
@@ -398,11 +399,12 @@ module Doing
398
399
  break
399
400
  end
400
401
  unless view || guessed
401
- guess = guess_section(frag, guessed: true, suggest: true)
402
- exit_now! "Did you mean `doing show #{guess}`?" if guess
402
+ alt = guess_section(frag, guessed: true, suggest: true)
403
+ meant_view = yn("Did you mean `doing show #{alt}`?", default_response: 'n')
403
404
 
404
- raise Errors::InvalidView, "Unknown view: #{frag}"
405
+ raise WrongCommand.new("run again with #{"doing show #{alt}".yellow}", topic: 'Try again:') if meant_view
405
406
 
407
+ raise InvalidView.new(%(unkown view #{alt.yellow}), topic: 'Missing:')
406
408
  end
407
409
  view
408
410
  end
@@ -448,8 +450,8 @@ module Doing
448
450
  end
449
451
 
450
452
  items.push(entry)
451
- logger.count(:added)
452
- logger.debug('Entry added:', %("#{entry.title}" to #{section}))
453
+ # logger.count(:added, level: :debug)
454
+ logger.info('New entry:', %(added "#{entry.title}" to #{section}))
453
455
  end
454
456
 
455
457
  ##
@@ -471,8 +473,8 @@ module Doing
471
473
  duped = no_overlap ? item.overlapping_time?(comp) : item.same_time?(comp)
472
474
  break if duped
473
475
  end
474
- logger.count(:skipped, level: :debug, message: 'overlapping entry') if duped
475
- logger.log_now(:debug, 'Skipped:', "overlapping entry: #{item.title}") if duped
476
+ logger.count(:skipped, level: :debug, message: 'overlapping %item') if duped
477
+ # logger.log_now(:debug, 'Skipped:', "overlapping entry: #{item.title}") if duped
476
478
  duped
477
479
  end
478
480
  end
@@ -509,7 +511,7 @@ module Doing
509
511
 
510
512
  last_item = last_entry({ section: section })
511
513
 
512
- raise Errors::NoEntryError, 'No entry found' unless last_item
514
+ raise NoEntryError, 'No entry found' unless last_item
513
515
 
514
516
  logger.log_now(:info, 'Edit note:', last_item.title)
515
517
 
@@ -522,7 +524,7 @@ module Doing
522
524
  if resume
523
525
  item.tag('done', remove: true)
524
526
  end
525
- Doing.logger.info('Reset:', %(Reset #{resume ? 'and resumed ' : ''} "#{item.title}" in #{item.section}))
527
+ logger.info('Reset:', %(Reset #{resume ? 'and resumed ' : ''} "#{item.title}" in #{item.section}))
526
528
  item
527
529
  end
528
530
 
@@ -550,7 +552,7 @@ module Doing
550
552
  title, note = format_input(new_item)
551
553
 
552
554
  if title.nil? || title.empty?
553
- logger.debug('Skipped:', 'No content provided')
555
+ logger.warn('Skipped:', 'No content provided')
554
556
  return
555
557
  end
556
558
  end
@@ -573,7 +575,7 @@ module Doing
573
575
 
574
576
  last = last_entry(opt)
575
577
  if last.nil?
576
- logger.debug('Skipped:', 'No previous entry found')
578
+ logger.warn('Skipped:', 'No previous entry found')
577
579
  return
578
580
  end
579
581
 
@@ -759,10 +761,7 @@ module Doing
759
761
 
760
762
  selection = choose_from_items(items, opt, include_section: section =~ /^all$/i)
761
763
 
762
- if selection.empty?
763
- logger.debug('Skipped:', 'No selection')
764
- return
765
- end
764
+ raise NoResults, 'no items selected' if selection.empty?
766
765
 
767
766
  act_on(selection, opt)
768
767
  end
@@ -807,7 +806,7 @@ module Doing
807
806
  fzf_args.push('-1') unless opt[:show_if_single]
808
807
 
809
808
  unless opt[:menu]
810
- raise Errors::InvalidArgument, "Can't skip menu when no query is provided" unless opt[:query]
809
+ raise InvalidArgument, "Can't skip menu when no query is provided" unless opt[:query] && !opt[:query].empty?
811
810
 
812
811
  fzf_args.concat([%(--filter="#{opt[:query]}"), opt[:sort] ? '' : '--no-sort'])
813
812
  end
@@ -823,8 +822,10 @@ module Doing
823
822
  end
824
823
 
825
824
  def act_on(items, opt = {})
826
- actions = %i[editor delete tag flag finish cancel archive output save_to]
825
+ actions = %i[editor delete tag flag finish cancel archive output save_to again resume]
827
826
  has_action = false
827
+ single = items.count == 1
828
+
828
829
  actions.each do |a|
829
830
  if opt[a]
830
831
  has_action = true
@@ -864,7 +865,7 @@ module Doing
864
865
  opt[:reset] = true
865
866
  when /(add|remove) tag/
866
867
  type = action =~ /^add/ ? 'add' : 'remove'
867
- raise Errors::InvalidArgument, "'add tag' and 'remove tag' can not be used together" if opt[:tag]
868
+ raise InvalidArgument, "'add tag' and 'remove tag' can not be used together" if opt[:tag]
868
869
 
869
870
  print "#{Color.yellow}Tag to #{type}: #{Color.reset}"
870
871
  tag = $stdin.gets
@@ -879,7 +880,7 @@ module Doing
879
880
  next if tag =~ /^ *$/
880
881
 
881
882
  unless output_format
882
- raise Errors::UserCancelled, 'Cancelled'
883
+ raise UserCancelled, 'Cancelled'
883
884
  end
884
885
 
885
886
  opt[:output] = output_format.strip
@@ -912,7 +913,7 @@ module Doing
912
913
 
913
914
  if opt[:resume] || opt[:reset]
914
915
  if items.count > 1
915
- logger.error('Error:', 'resume and restart can only be used on a single entry')
916
+ raise InvalidArgument, 'resume and restart can only be used on a single entry'
916
917
  else
917
918
  item = items[0]
918
919
  if opt[:resume] && !opt[:reset]
@@ -942,7 +943,7 @@ module Doing
942
943
  if opt[:flag]
943
944
  tag = @config['marker_tag'] || 'flagged'
944
945
  items.map! do |item|
945
- tag_item(item, tag, date: false, remove: opt[:remove])
946
+ tag_item(item, tag, date: false, remove: opt[:remove], single: single)
946
947
  end
947
948
  end
948
949
 
@@ -951,7 +952,7 @@ module Doing
951
952
  items.map! do |item|
952
953
  if item.should_finish?
953
954
  should_date = !opt[:cancel] && item.should_time?
954
- tag_item(item, tag, date: should_date, remove: opt[:remove])
955
+ tag_item(item, tag, date: should_date, remove: opt[:remove], single: single)
955
956
  end
956
957
  end
957
958
  end
@@ -959,7 +960,7 @@ module Doing
959
960
  if opt[:tag]
960
961
  tag = opt[:tag]
961
962
  items.map! do |item|
962
- tag_item(item, tag, date: false, remove: opt[:remove])
963
+ tag_item(item, tag, date: false, remove: opt[:remove], single: single)
963
964
  end
964
965
  end
965
966
 
@@ -1057,7 +1058,7 @@ module Doing
1057
1058
  ## @param remove (Boolean) remove tags
1058
1059
  ## @param date (Boolean) Include timestamp?
1059
1060
  ##
1060
- def tag_item(item, tags, remove: false, date: false)
1061
+ def tag_item(item, tags, remove: false, date: false, single: false)
1061
1062
  added = []
1062
1063
  removed = []
1063
1064
 
@@ -1073,7 +1074,7 @@ module Doing
1073
1074
  end
1074
1075
  end
1075
1076
 
1076
- log_change(tags_added: added, tags_removed: removed, count: 1)
1077
+ log_change(tags_added: added, tags_removed: removed, count: 1, item: item, single: single)
1077
1078
 
1078
1079
  item
1079
1080
  end
@@ -1097,21 +1098,22 @@ module Doing
1097
1098
 
1098
1099
  items = filter_items([], opt: opt)
1099
1100
 
1100
- logger.info('Skipped:', 'no items matched your search') if items.empty?
1101
-
1102
1101
  if opt[:interactive]
1103
1102
  items = choose_from_items(items, {
1104
- menu: true,
1105
- header: '',
1106
- prompt: 'Select entries to tag > ',
1107
- multiple: true,
1108
- sort: true,
1109
- show_if_single: true
1110
- }, include_section: opt[:section] =~ /^all$/i )
1103
+ menu: true,
1104
+ header: '',
1105
+ prompt: 'Select entries to tag > ',
1106
+ multiple: true,
1107
+ sort: true,
1108
+ show_if_single: true
1109
+ }, include_section: opt[:section] =~ /^all$/i)
1110
+
1111
+ raise NoResults, 'no items selected' if items.empty?
1111
1112
 
1112
- return if items.nil?
1113
1113
  end
1114
1114
 
1115
+ raise NoResults, 'no items matched your search' if items.empty?
1116
+
1115
1117
  items.each do |item|
1116
1118
  added = []
1117
1119
  removed = []
@@ -1123,7 +1125,7 @@ module Doing
1123
1125
  # logger.debug('Autotag:', 'No changes')
1124
1126
  else
1125
1127
  logger.count(:added_tags)
1126
- logger.debug('Tags updated:', new_title)
1128
+ logger.write(items.count == 1 ? :info : :debug, 'Tagged:', new_title)
1127
1129
  item.title = new_title
1128
1130
  end
1129
1131
  else
@@ -1185,7 +1187,7 @@ module Doing
1185
1187
  end
1186
1188
  end
1187
1189
 
1188
- log_change(tags_added: added, tags_removed: removed)
1190
+ log_change(tags_added: added, tags_removed: removed, item: item, single: items.count == 1)
1189
1191
 
1190
1192
  item.note.add(opt[:note]) if opt[:note]
1191
1193
 
@@ -1217,7 +1219,7 @@ module Doing
1217
1219
  @content[section][:items].concat([new_item])
1218
1220
 
1219
1221
  logger.count(section == 'Archive' ? :archived : :moved)
1220
- logger.debug("Entry #{section == 'Archive' ? 'archived' : 'moved'}:",
1222
+ logger.debug("#{section == 'Archive' ? 'Archived' : 'Moved'}:",
1221
1223
  "#{new_item.title.truncate(60)} from #{from} to #{section}")
1222
1224
  new_item
1223
1225
  end
@@ -1246,7 +1248,7 @@ module Doing
1246
1248
  section_items = @content[section][:items]
1247
1249
  deleted = section_items.delete(item)
1248
1250
  logger.count(:deleted)
1249
- logger.debug('Entry deleted:', deleted.title)
1251
+ logger.info('Entry deleted:', deleted.title)
1250
1252
  end
1251
1253
 
1252
1254
  ##
@@ -1261,16 +1263,13 @@ module Doing
1261
1263
  section_items = @content[section][:items]
1262
1264
  s_idx = section_items.index { |item| item.equal?(old_item) }
1263
1265
 
1264
- unless s_idx
1265
- Doing.logger.error('Fail to update:', 'Could not find item in index')
1266
- raise Errors::ItemNotFound, 'Unable to find item in index, did it mutate?'
1267
- end
1266
+ raise ItemNotFound, 'Unable to find item in index, did it mutate?' unless s_idx
1268
1267
 
1269
1268
  return if section_items[s_idx].equal?(new_item)
1270
1269
 
1271
1270
  section_items[s_idx] = new_item
1272
1271
  logger.count(:updated)
1273
- logger.debug('Entry updated:', section_items[s_idx].title.truncate(60))
1272
+ logger.info('Entry updated:', section_items[s_idx].title.truncate(60))
1274
1273
  new_item
1275
1274
  end
1276
1275
 
@@ -1343,10 +1342,10 @@ module Doing
1343
1342
  item.title = item.title.sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{item.section})")
1344
1343
  move_item(item, 'Archive', label: false)
1345
1344
  logger.count(:completed_archived)
1346
- logger.debug('Completed/archived:', item.title)
1345
+ logger.info('Completed/archived:', item.title)
1347
1346
  else
1348
1347
  logger.count(:completed)
1349
- logger.debug('Completed:', item.title)
1348
+ logger.info('Completed:', item.title)
1350
1349
  end
1351
1350
  end
1352
1351
 
@@ -1478,10 +1477,10 @@ module Doing
1478
1477
  if File.exist?(file)
1479
1478
  init_doing_file(file)
1480
1479
  @content.deep_merge(new_content)
1481
- logger.warn('File update:', "Added entries to existing file: #{file}")
1480
+ logger.warn('File update:', "added entries to existing file: #{file}")
1482
1481
  else
1483
1482
  @content = new_content
1484
- logger.warn('File update:', "Created new file: #{file}")
1483
+ logger.warn('File update:', "created new file: #{file}")
1485
1484
  end
1486
1485
 
1487
1486
  write(file, backup: false)
@@ -1572,10 +1571,7 @@ module Doing
1572
1571
  opt[:multiple] = true
1573
1572
  selected = choose_from_items(items, opt, include_section: opt[:section] =~ /^all$/i )
1574
1573
 
1575
- if selected.empty?
1576
- logger.debug('Skipped:', 'No selection')
1577
- return
1578
- end
1574
+ raise NoResults, 'no items selected' if selected.empty?
1579
1575
 
1580
1576
  act_on(selected, opt)
1581
1577
  return
@@ -1592,7 +1588,7 @@ module Doing
1592
1588
  def output(items, title, is_single, opt = {})
1593
1589
  out = nil
1594
1590
 
1595
- raise Errors::InvalidArgument, 'Unknown output format' unless opt[:output] =~ Plugins.plugin_regex(type: :export)
1591
+ raise InvalidArgument, 'Unknown output format' unless opt[:output] =~ Plugins.plugin_regex(type: :export)
1596
1592
 
1597
1593
  export_options = { page_title: title, is_single: is_single, options: opt }
1598
1594
 
@@ -1646,7 +1642,7 @@ module Doing
1646
1642
  do_archive(section, destination, { count: count, tags: tags, bool: bool, search: options[:search], label: options[:label], before: options[:before] })
1647
1643
  write(doing_file)
1648
1644
  else
1649
- raise Errors::InvalidArgument, 'Either source or destination does not exist'
1645
+ raise InvalidArgument, 'Either source or destination does not exist'
1650
1646
  end
1651
1647
  end
1652
1648
 
@@ -1705,7 +1701,12 @@ module Doing
1705
1701
  @content[section][:items] = items
1706
1702
  @content[destination][:items].concat(moved_items)
1707
1703
  if moved_items.length.positive?
1708
- logger.info('Archived:', "#{moved_items.length} items from #{section} to #{destination}")
1704
+ logger.count(destination == 'Archive' ? :archived : :moved,
1705
+ level: :info,
1706
+ count: moved_items.length,
1707
+ message: "%count %items from #{section} to #{destination}")
1708
+ else
1709
+ logger.info('Skipped:', 'No items were moved')
1709
1710
  end
1710
1711
  else
1711
1712
  count = items.length if items.length < count
@@ -1733,10 +1734,11 @@ module Doing
1733
1734
  else
1734
1735
  items[0..count - 1]
1735
1736
  end
1737
+
1736
1738
  logger.count(destination == 'Archive' ? :archived : :moved,
1739
+ level: :info,
1737
1740
  count: items.length - count,
1738
1741
  message: "%count %items from #{section} to #{destination}")
1739
- # logger.info('Archived:', "#{items.length - count} items from #{section} to #{destination}")
1740
1742
  end
1741
1743
  end
1742
1744
  end
@@ -1935,11 +1937,11 @@ module Doing
1935
1937
  end
1936
1938
  end
1937
1939
 
1938
- logger.debug('Autotag:', "Whitelisted tags: #{whitelisted.join(', ')}") unless whitelisted.empty?
1940
+ logger.debug('Autotag:', "whitelisted tags: #{whitelisted.join(', ')}") unless whitelisted.empty?
1939
1941
  new_tags = whitelisted
1940
1942
  unless tail_tags.empty?
1941
1943
  tags = tail_tags.uniq.map { |t| "@#{t}".cyan }.join(' ')
1942
- logger.debug('Autotag:', "Synonym tags: #{tags}")
1944
+ logger.debug('Autotag:', "synonym tags: #{tags}")
1943
1945
  tags_a = tail_tags.map { |t| "@#{t}" }
1944
1946
  text.add_tags!(tags_a.join(' '))
1945
1947
  new_tags.concat(tags_a)
@@ -1948,7 +1950,7 @@ module Doing
1948
1950
  unless text == original
1949
1951
  logger.info('Autotag:', "added #{new_tags.join(', ')} to \"#{text}\"")
1950
1952
  else
1951
- logger.debug('Autotag:', "no change to \"#{text}\"")
1953
+ logger.debug('Skipped:', "no change to \"#{text}\"")
1952
1954
  end
1953
1955
 
1954
1956
  text
@@ -2174,23 +2176,28 @@ EOS
2174
2176
  logger.log_now(:error, 'STDERR output:', stderr)
2175
2177
  end
2176
2178
 
2177
- def log_change(tags_added: [], tags_removed: [], count: 1)
2179
+ def log_change(tags_added: [], tags_removed: [], count: 1, item: nil, single: false)
2178
2180
  if tags_added.empty? && tags_removed.empty?
2179
2181
  logger.count(:skipped, level: :debug, message: '%count %items with no change', count: count)
2180
2182
  else
2181
-
2182
2183
  if tags_added.empty?
2183
2184
  logger.count(:skipped, level: :debug, message: 'no tags added to %count %items')
2184
- # logger.debug('No tags added:', %("#{item.title}" in #{item.section}))
2185
2185
  else
2186
- logger.count(:added_tags, tag: tags_added, message: '%tags added to %count %items')
2187
- # logger.info('Added tags:', %(#{did_add} to "#{item.title}" in #{item.section}))
2186
+ if single && item
2187
+ logger.info('Tagged:', %(added #{tags_added.count == 1 ? 'tag' : 'tags'} #{tags_added.map {|t| "@#{t}"}.join(', ')} to #{item.title}))
2188
+ else
2189
+ logger.count(:added_tags, level: :info, tag: tags_added, message: '%tags added to %count %items')
2190
+ end
2188
2191
  end
2189
2192
 
2190
2193
  if tags_removed.empty?
2191
2194
  logger.count(:skipped, level: :debug, message: 'no tags removed from %count %items')
2192
2195
  else
2193
- logger.count(:removed_tags, tag: tags_removed, message: '%tags removed from %count %items')
2196
+ if single && item
2197
+ logger.info('Untagged:', %(removed #{tags_removed.count == 1 ? 'tag' : 'tags'} #{tags_added.map {|t| "@#{t}"}.join(', ')} from #{item.title}))
2198
+ else
2199
+ logger.count(:removed_tags, level: :info, tag: tags_removed, message: '%tags removed from %count %items')
2200
+ end
2194
2201
  end
2195
2202
  end
2196
2203
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Example command that calls an existing command (tag) with
4
+ # preset options
5
+ desc 'Autotag last entry or filtered entries'
6
+ command :autotag do |c|
7
+ # Preserve some switches and flags. Values will be passed
8
+ # to tag command.
9
+ c.desc 'Section'
10
+ c.arg_name 'SECTION_NAME'
11
+ c.flag %i[s section], default_value: 'All'
12
+
13
+ c.desc 'How many recent entries to autotag (0 for all)'
14
+ c.arg_name 'COUNT'
15
+ c.flag %i[c count], default_value: 1, must_match: /^\d+$/, type: Integer
16
+
17
+ c.desc 'Don\'t ask permission to autotag all entries when count is 0'
18
+ c.switch %i[force], negatable: false, default_value: false
19
+
20
+ c.desc 'Autotag last entry (or entries) not marked @done'
21
+ c.switch %i[u unfinished], negatable: false, default_value: false
22
+
23
+ c.desc 'Autotag the last X entries containing TAG.
24
+ Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool'
25
+ c.arg_name 'TAG'
26
+ c.flag [:tag]
27
+
28
+ c.desc 'Autotag entries matching search filter,
29
+ surround with slashes for regex (e.g. "/query.*/"),
30
+ start with single quote for exact match ("\'query")'
31
+ c.arg_name 'QUERY'
32
+ c.flag [:search]
33
+
34
+ c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
35
+ c.arg_name 'BOOLEAN'
36
+ c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
37
+
38
+ c.desc 'Select item(s) to tag from a menu of matching entries'
39
+ c.switch %i[i interactive], negatable: false, default_value: false
40
+
41
+ c.action do |global, options, _args|
42
+ # Force some switches and flags. We're using the tag
43
+ # command with settings that would invoke autotagging.
44
+
45
+ # Force enable autotag
46
+ options[:a] = true
47
+ options[:autotag] = true
48
+
49
+ # No need for date values
50
+ options[:d] = false
51
+ options[:date] = false
52
+
53
+ # Don't remove any tags
54
+ options[:rename] = nil
55
+ options[:regex] = false
56
+ options[:r] = false
57
+ options[:remove] = false
58
+
59
+ cmd = commands[:tag]
60
+ action = cmd.send(:get_action, nil)
61
+ action.call(global, options, [])
62
+ end
63
+ end
@@ -82,7 +82,7 @@ class BashCompletions
82
82
  fi
83
83
  }
84
84
  EOFUNC
85
-
85
+ clear
86
86
  out.join("\n")
87
87
  end
88
88
 
@@ -151,7 +151,7 @@ class BashCompletions
151
151
 
152
152
 
153
153
  def get_help_sections(command = '')
154
- res = `bundle exec bin/doing help #{command}`.strip
154
+ res = `doing help #{command}`.strip
155
155
  scanned = res.scan(/(?m-i)^([A-Z ]+)\n([\s\S]*?)(?=\n+[A-Z]+|\Z)/)
156
156
  sections = {}
157
157
  scanned.each do |sect|
@@ -203,6 +203,7 @@ class BashCompletions
203
203
  out = []
204
204
  out << main_function
205
205
  out << 'complete -F _doing doing'
206
+ status('Complete', reset: false)
206
207
  out.join("\n")
207
208
  end
208
209
  end
@@ -86,11 +86,12 @@ class FishCompletions
86
86
  complete -f -c doing -n '__fish_doing_using_command template' -a '(__fish_doing_complete_templates)'
87
87
  complete -f -c doing -s t -l type -x -n '__fish_doing_using_command import' -a '(__fish_doing_import_plugins)'
88
88
 
89
+ complete -xc doing -n '__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from (doing help -c)' -a "(doing help -c)"
89
90
  EOFUNCTIONS
90
91
  end
91
92
 
92
93
  def get_help_sections(command = '')
93
- res = `bundle exec bin/doing help #{command}`.strip
94
+ res = `doing help #{command}`.strip
94
95
  scanned = res.scan(/(?m-i)^([A-Z ]+)\n([\s\S]*?)(?=\n+[A-Z]+|\Z)/)
95
96
  sections = {}
96
97
  scanned.each do |sect|
@@ -179,6 +180,7 @@ class FishCompletions
179
180
  out << "complete -f -c doing -s o -l output -x -n '__fish_doing_using_command #{need_export.join(' ')}' -a '(__fish_doing_export_plugins)'"
180
181
  end
181
182
 
183
+ clear
182
184
  out.join("\n")
183
185
  end
184
186
 
@@ -194,6 +196,7 @@ class FishCompletions
194
196
  out << generate_helpers
195
197
  out << generate_subcommand_completions
196
198
  out << generate_subcommand_option_completions
199
+ status('Complete', reset: false)
197
200
  out.join("\n")
198
201
  end
199
202
  end