doing 2.0.5.pre → 2.0.6.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -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