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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/bin/doing +133 -98
- data/doing.rdoc +88 -8
- data/generate_completions.sh +1 -0
- data/lib/completion/_doing.zsh +179 -127
- data/lib/completion/doing.bash +60 -27
- data/lib/completion/doing.fish +74 -23
- data/lib/doing/cli_status.rb +4 -0
- data/lib/doing/errors.rb +22 -15
- data/lib/doing/log_adapter.rb +27 -25
- data/lib/doing/plugin_manager.rb +1 -1
- data/lib/doing/string.rb +7 -6
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +83 -76
- data/lib/examples/commands/autotag.rb +63 -0
- data/scripts/generate_bash_completions.rb +3 -2
- data/scripts/generate_fish_completions.rb +4 -1
- data/scripts/generate_zsh_completions.rb +42 -38
- metadata +2 -2
- data/doing.fish +0 -278
data/lib/doing/plugin_manager.rb
CHANGED
@@ -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' :
|
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.
|
245
|
+
Doing.logger.write(log_level, 'Tag:', %(renamed #{f} to #{t} in "#{title}"))
|
245
246
|
else
|
246
247
|
f = "@#{tag}".cyan
|
247
|
-
Doing.logger.
|
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
|
-
|
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
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
|
126
|
+
# raise NonInteractive, 'Non-interactive terminal' unless $stdout.isatty || ENV['DOING_EDITOR_TEST']
|
127
127
|
|
128
|
-
raise
|
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
|
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
|
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
|
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
|
-
|
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
|
315
|
-
|
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
|
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
|
-
|
402
|
-
|
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
|
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.
|
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
|
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
|
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
|
-
|
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.
|
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.
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
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.
|
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("
|
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.
|
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.
|
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.
|
1345
|
+
logger.info('Completed/archived:', item.title)
|
1347
1346
|
else
|
1348
1347
|
logger.count(:completed)
|
1349
|
-
logger.
|
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:', "
|
1480
|
+
logger.warn('File update:', "added entries to existing file: #{file}")
|
1482
1481
|
else
|
1483
1482
|
@content = new_content
|
1484
|
-
logger.warn('File update:', "
|
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
|
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
|
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.
|
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:', "
|
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:', "
|
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('
|
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
|
-
|
2187
|
-
|
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
|
-
|
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 = `
|
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 = `
|
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
|