doing 2.0.5.pre → 2.0.9.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 +21 -1
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/bin/doing +446 -111
- data/doing.rdoc +371 -13
- 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/item.rb +18 -12
- data/lib/doing/log_adapter.rb +27 -25
- data/lib/doing/plugin_manager.rb +1 -1
- data/lib/doing/plugins/import/calendar_import.rb +7 -1
- data/lib/doing/plugins/import/doing_import.rb +6 -6
- data/lib/doing/plugins/import/timing_import.rb +7 -1
- data/lib/doing/string.rb +29 -6
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +120 -86
- data/lib/examples/commands/autotag.rb +63 -0
- data/lib/examples/plugins/{templates → wiki_export/templates}/wiki.css +0 -0
- data/lib/examples/plugins/{templates → wiki_export/templates}/wiki.haml +0 -0
- data/lib/examples/plugins/{templates → wiki_export/templates}/wiki_index.haml +0 -0
- data/lib/examples/plugins/{wiki_export.rb → wiki_export/wiki_export.rb} +0 -0
- data/scripts/generate_bash_completions.rb +3 -2
- data/scripts/generate_fish_completions.rb +4 -1
- data/scripts/generate_zsh_completions.rb +44 -39
- metadata +6 -6
- data/doing.fish +0 -278
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: '%count overlapping %items') 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
|
|
@@ -684,18 +686,29 @@ module Doing
|
|
684
686
|
items.sort_by! { |item| [item.date, item.title.downcase] }.reverse
|
685
687
|
filtered_items = items.select do |item|
|
686
688
|
keep = true
|
687
|
-
|
688
|
-
|
689
|
+
if opt[:unfinished]
|
690
|
+
finished = item.tags?('done', :and)
|
691
|
+
finished = opt[:not] ? !finished : finished
|
692
|
+
keep = false if finished
|
693
|
+
end
|
689
694
|
|
690
695
|
if keep && opt[:tag]
|
691
696
|
opt[:tag_bool] ||= :and
|
692
697
|
tag_match = opt[:tag].nil? || opt[:tag].empty? ? true : item.tags?(opt[:tag], opt[:tag_bool])
|
693
698
|
keep = false unless tag_match
|
699
|
+
keep = opt[:not] ? !keep : keep
|
694
700
|
end
|
695
701
|
|
696
702
|
if keep && opt[:search]
|
697
|
-
|
703
|
+
opt[:case] = opt[:case].normalize_case unless opt[:case].is_a?(Symbol)
|
704
|
+
search_match = if opt[:search].nil? || opt[:search].empty?
|
705
|
+
true
|
706
|
+
else
|
707
|
+
item.search(opt[:search], case_type: opt[:case])
|
708
|
+
end
|
709
|
+
|
698
710
|
keep = false unless search_match
|
711
|
+
keep = opt[:not] ? !keep : keep
|
699
712
|
end
|
700
713
|
|
701
714
|
if keep && opt[:date_filter]&.length == 2
|
@@ -708,30 +721,36 @@ module Doing
|
|
708
721
|
item.date.strftime('%F') == start_date.strftime('%F')
|
709
722
|
end
|
710
723
|
keep = false unless in_date_range
|
724
|
+
keep = opt[:not] ? !keep : keep
|
711
725
|
end
|
712
726
|
|
713
727
|
keep = false if keep && opt[:only_timed] && !item.interval
|
714
728
|
|
715
729
|
if keep && opt[:tag_filter] && !opt[:tag_filter]['tags'].empty?
|
716
730
|
keep = item.tags?(opt[:tag_filter]['tags'], opt[:tag_filter]['bool'])
|
731
|
+
keep = opt[:not] ? !keep : keep
|
717
732
|
end
|
718
733
|
|
719
734
|
if keep && opt[:before]
|
720
735
|
time_string = opt[:before]
|
721
736
|
cutoff = chronify(time_string, guess: :begin)
|
722
737
|
keep = cutoff && item.date <= cutoff
|
738
|
+
keep = opt[:not] ? !keep : keep
|
723
739
|
end
|
724
740
|
|
725
741
|
if keep && opt[:after]
|
726
742
|
time_string = opt[:after]
|
727
743
|
cutoff = chronify(time_string, guess: :end)
|
728
744
|
keep = cutoff && item.date >= cutoff
|
745
|
+
keep = opt[:not] ? !keep : keep
|
729
746
|
end
|
730
747
|
|
731
748
|
if keep && opt[:today]
|
732
749
|
keep = item.date >= Date.today.to_time && item.date < Date.today.next_day.to_time
|
750
|
+
keep = opt[:not] ? !keep : keep
|
733
751
|
elsif keep && opt[:yesterday]
|
734
752
|
keep = item.date >= Date.today.prev_day.to_time && item.date < Date.today.to_time
|
753
|
+
keep = opt[:not] ? !keep : keep
|
735
754
|
end
|
736
755
|
|
737
756
|
keep
|
@@ -753,16 +772,23 @@ module Doing
|
|
753
772
|
##
|
754
773
|
def interactive(opt = {})
|
755
774
|
section = opt[:section] ? guess_section(opt[:section]) : 'All'
|
775
|
+
|
776
|
+
search = nil
|
777
|
+
|
778
|
+
if opt[:search]
|
779
|
+
search = opt[:search]
|
780
|
+
search.sub!(/^'?/, "'") if opt[:exact]
|
781
|
+
opt[:search] = search
|
782
|
+
end
|
783
|
+
|
756
784
|
opt[:query] = opt[:search] if opt[:search] && !opt[:query]
|
785
|
+
opt[:query] = "!#{opt[:query]}" if opt[:not]
|
757
786
|
opt[:multiple] = true
|
758
|
-
items = filter_items([], opt: { section: section, search: opt[:search] })
|
787
|
+
items = filter_items([], opt: { section: section, search: opt[:search], case: opt[:case] })
|
759
788
|
|
760
789
|
selection = choose_from_items(items, opt, include_section: section =~ /^all$/i)
|
761
790
|
|
762
|
-
if selection.empty?
|
763
|
-
logger.debug('Skipped:', 'No selection')
|
764
|
-
return
|
765
|
-
end
|
791
|
+
raise NoResults, 'no items selected' if selection.empty?
|
766
792
|
|
767
793
|
act_on(selection, opt)
|
768
794
|
end
|
@@ -807,7 +833,7 @@ module Doing
|
|
807
833
|
fzf_args.push('-1') unless opt[:show_if_single]
|
808
834
|
|
809
835
|
unless opt[:menu]
|
810
|
-
raise
|
836
|
+
raise InvalidArgument, "Can't skip menu when no query is provided" unless opt[:query] && !opt[:query].empty?
|
811
837
|
|
812
838
|
fzf_args.concat([%(--filter="#{opt[:query]}"), opt[:sort] ? '' : '--no-sort'])
|
813
839
|
end
|
@@ -823,8 +849,10 @@ module Doing
|
|
823
849
|
end
|
824
850
|
|
825
851
|
def act_on(items, opt = {})
|
826
|
-
actions = %i[editor delete tag flag finish cancel archive output save_to]
|
852
|
+
actions = %i[editor delete tag flag finish cancel archive output save_to again resume]
|
827
853
|
has_action = false
|
854
|
+
single = items.count == 1
|
855
|
+
|
828
856
|
actions.each do |a|
|
829
857
|
if opt[a]
|
830
858
|
has_action = true
|
@@ -864,7 +892,7 @@ module Doing
|
|
864
892
|
opt[:reset] = true
|
865
893
|
when /(add|remove) tag/
|
866
894
|
type = action =~ /^add/ ? 'add' : 'remove'
|
867
|
-
raise
|
895
|
+
raise InvalidArgument, "'add tag' and 'remove tag' can not be used together" if opt[:tag]
|
868
896
|
|
869
897
|
print "#{Color.yellow}Tag to #{type}: #{Color.reset}"
|
870
898
|
tag = $stdin.gets
|
@@ -879,7 +907,7 @@ module Doing
|
|
879
907
|
next if tag =~ /^ *$/
|
880
908
|
|
881
909
|
unless output_format
|
882
|
-
raise
|
910
|
+
raise UserCancelled, 'Cancelled'
|
883
911
|
end
|
884
912
|
|
885
913
|
opt[:output] = output_format.strip
|
@@ -912,7 +940,7 @@ module Doing
|
|
912
940
|
|
913
941
|
if opt[:resume] || opt[:reset]
|
914
942
|
if items.count > 1
|
915
|
-
|
943
|
+
raise InvalidArgument, 'resume and restart can only be used on a single entry'
|
916
944
|
else
|
917
945
|
item = items[0]
|
918
946
|
if opt[:resume] && !opt[:reset]
|
@@ -933,7 +961,7 @@ module Doing
|
|
933
961
|
if opt[:delete]
|
934
962
|
res = opt[:force] ? true : yn("Delete #{items.size} items?", default_response: 'y')
|
935
963
|
if res
|
936
|
-
items.each { |item| delete_item(item) }
|
964
|
+
items.each { |item| delete_item(item, single: items.count == 1) }
|
937
965
|
write(@doing_file)
|
938
966
|
end
|
939
967
|
return
|
@@ -942,7 +970,7 @@ module Doing
|
|
942
970
|
if opt[:flag]
|
943
971
|
tag = @config['marker_tag'] || 'flagged'
|
944
972
|
items.map! do |item|
|
945
|
-
tag_item(item, tag, date: false, remove: opt[:remove])
|
973
|
+
tag_item(item, tag, date: false, remove: opt[:remove], single: single)
|
946
974
|
end
|
947
975
|
end
|
948
976
|
|
@@ -951,7 +979,7 @@ module Doing
|
|
951
979
|
items.map! do |item|
|
952
980
|
if item.should_finish?
|
953
981
|
should_date = !opt[:cancel] && item.should_time?
|
954
|
-
tag_item(item, tag, date: should_date, remove: opt[:remove])
|
982
|
+
tag_item(item, tag, date: should_date, remove: opt[:remove], single: single)
|
955
983
|
end
|
956
984
|
end
|
957
985
|
end
|
@@ -959,7 +987,7 @@ module Doing
|
|
959
987
|
if opt[:tag]
|
960
988
|
tag = opt[:tag]
|
961
989
|
items.map! do |item|
|
962
|
-
tag_item(item, tag, date: false, remove: opt[:remove])
|
990
|
+
tag_item(item, tag, date: false, remove: opt[:remove], single: single)
|
963
991
|
end
|
964
992
|
end
|
965
993
|
|
@@ -991,7 +1019,7 @@ module Doing
|
|
991
1019
|
title = input_lines[0]&.strip
|
992
1020
|
|
993
1021
|
if title.nil? || title =~ /^#{divider.strip}$/ || title.strip.empty?
|
994
|
-
delete_item(items[i])
|
1022
|
+
delete_item(items[i], single: new_items.count == 1)
|
995
1023
|
else
|
996
1024
|
note = input_lines.length > 1 ? input_lines[1..-1] : []
|
997
1025
|
|
@@ -1057,7 +1085,7 @@ module Doing
|
|
1057
1085
|
## @param remove (Boolean) remove tags
|
1058
1086
|
## @param date (Boolean) Include timestamp?
|
1059
1087
|
##
|
1060
|
-
def tag_item(item, tags, remove: false, date: false)
|
1088
|
+
def tag_item(item, tags, remove: false, date: false, single: false)
|
1061
1089
|
added = []
|
1062
1090
|
removed = []
|
1063
1091
|
|
@@ -1073,7 +1101,7 @@ module Doing
|
|
1073
1101
|
end
|
1074
1102
|
end
|
1075
1103
|
|
1076
|
-
log_change(tags_added: added, tags_removed: removed, count: 1)
|
1104
|
+
log_change(tags_added: added, tags_removed: removed, count: 1, item: item, single: single)
|
1077
1105
|
|
1078
1106
|
item
|
1079
1107
|
end
|
@@ -1097,21 +1125,22 @@ module Doing
|
|
1097
1125
|
|
1098
1126
|
items = filter_items([], opt: opt)
|
1099
1127
|
|
1100
|
-
logger.info('Skipped:', 'no items matched your search') if items.empty?
|
1101
|
-
|
1102
1128
|
if opt[:interactive]
|
1103
1129
|
items = choose_from_items(items, {
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1130
|
+
menu: true,
|
1131
|
+
header: '',
|
1132
|
+
prompt: 'Select entries to tag > ',
|
1133
|
+
multiple: true,
|
1134
|
+
sort: true,
|
1135
|
+
show_if_single: true
|
1136
|
+
}, include_section: opt[:section] =~ /^all$/i)
|
1137
|
+
|
1138
|
+
raise NoResults, 'no items selected' if items.empty?
|
1111
1139
|
|
1112
|
-
return if items.nil?
|
1113
1140
|
end
|
1114
1141
|
|
1142
|
+
raise NoResults, 'no items matched your search' if items.empty?
|
1143
|
+
|
1115
1144
|
items.each do |item|
|
1116
1145
|
added = []
|
1117
1146
|
removed = []
|
@@ -1123,7 +1152,7 @@ module Doing
|
|
1123
1152
|
# logger.debug('Autotag:', 'No changes')
|
1124
1153
|
else
|
1125
1154
|
logger.count(:added_tags)
|
1126
|
-
logger.
|
1155
|
+
logger.write(items.count == 1 ? :info : :debug, 'Tagged:', new_title)
|
1127
1156
|
item.title = new_title
|
1128
1157
|
end
|
1129
1158
|
else
|
@@ -1185,7 +1214,7 @@ module Doing
|
|
1185
1214
|
end
|
1186
1215
|
end
|
1187
1216
|
|
1188
|
-
log_change(tags_added: added, tags_removed: removed)
|
1217
|
+
log_change(tags_added: added, tags_removed: removed, item: item, single: items.count == 1)
|
1189
1218
|
|
1190
1219
|
item.note.add(opt[:note]) if opt[:note]
|
1191
1220
|
|
@@ -1217,7 +1246,7 @@ module Doing
|
|
1217
1246
|
@content[section][:items].concat([new_item])
|
1218
1247
|
|
1219
1248
|
logger.count(section == 'Archive' ? :archived : :moved)
|
1220
|
-
logger.debug("
|
1249
|
+
logger.debug("#{section == 'Archive' ? 'Archived' : 'Moved'}:",
|
1221
1250
|
"#{new_item.title.truncate(60)} from #{from} to #{section}")
|
1222
1251
|
new_item
|
1223
1252
|
end
|
@@ -1240,13 +1269,13 @@ module Doing
|
|
1240
1269
|
##
|
1241
1270
|
## @param item The item
|
1242
1271
|
##
|
1243
|
-
def delete_item(item)
|
1272
|
+
def delete_item(item, single: false)
|
1244
1273
|
section = item.section
|
1245
1274
|
|
1246
1275
|
section_items = @content[section][:items]
|
1247
1276
|
deleted = section_items.delete(item)
|
1248
1277
|
logger.count(:deleted)
|
1249
|
-
logger.
|
1278
|
+
logger.info('Entry deleted:', deleted.title) if single
|
1250
1279
|
end
|
1251
1280
|
|
1252
1281
|
##
|
@@ -1261,16 +1290,13 @@ module Doing
|
|
1261
1290
|
section_items = @content[section][:items]
|
1262
1291
|
s_idx = section_items.index { |item| item.equal?(old_item) }
|
1263
1292
|
|
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
|
1293
|
+
raise ItemNotFound, 'Unable to find item in index, did it mutate?' unless s_idx
|
1268
1294
|
|
1269
1295
|
return if section_items[s_idx].equal?(new_item)
|
1270
1296
|
|
1271
1297
|
section_items[s_idx] = new_item
|
1272
1298
|
logger.count(:updated)
|
1273
|
-
logger.
|
1299
|
+
logger.info('Entry updated:', section_items[s_idx].title.truncate(60))
|
1274
1300
|
new_item
|
1275
1301
|
end
|
1276
1302
|
|
@@ -1343,10 +1369,10 @@ module Doing
|
|
1343
1369
|
item.title = item.title.sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{item.section})")
|
1344
1370
|
move_item(item, 'Archive', label: false)
|
1345
1371
|
logger.count(:completed_archived)
|
1346
|
-
logger.
|
1372
|
+
logger.info('Completed/archived:', item.title)
|
1347
1373
|
else
|
1348
1374
|
logger.count(:completed)
|
1349
|
-
logger.
|
1375
|
+
logger.info('Completed:', item.title)
|
1350
1376
|
end
|
1351
1377
|
end
|
1352
1378
|
|
@@ -1478,10 +1504,10 @@ module Doing
|
|
1478
1504
|
if File.exist?(file)
|
1479
1505
|
init_doing_file(file)
|
1480
1506
|
@content.deep_merge(new_content)
|
1481
|
-
logger.warn('File update:', "
|
1507
|
+
logger.warn('File update:', "added entries to existing file: #{file}")
|
1482
1508
|
else
|
1483
1509
|
@content = new_content
|
1484
|
-
logger.warn('File update:', "
|
1510
|
+
logger.warn('File update:', "created new file: #{file}")
|
1485
1511
|
end
|
1486
1512
|
|
1487
1513
|
write(file, backup: false)
|
@@ -1565,17 +1591,13 @@ module Doing
|
|
1565
1591
|
|
1566
1592
|
items.reverse! if opt[:order] =~ /^d/i
|
1567
1593
|
|
1568
|
-
|
1569
1594
|
if opt[:interactive]
|
1570
1595
|
opt[:menu] = !opt[:force]
|
1571
1596
|
opt[:query] = '' # opt[:search]
|
1572
1597
|
opt[:multiple] = true
|
1573
1598
|
selected = choose_from_items(items, opt, include_section: opt[:section] =~ /^all$/i )
|
1574
1599
|
|
1575
|
-
if selected.empty?
|
1576
|
-
logger.debug('Skipped:', 'No selection')
|
1577
|
-
return
|
1578
|
-
end
|
1600
|
+
raise NoResults, 'no items selected' if selected.empty?
|
1579
1601
|
|
1580
1602
|
act_on(selected, opt)
|
1581
1603
|
return
|
@@ -1592,7 +1614,7 @@ module Doing
|
|
1592
1614
|
def output(items, title, is_single, opt = {})
|
1593
1615
|
out = nil
|
1594
1616
|
|
1595
|
-
raise
|
1617
|
+
raise InvalidArgument, 'Unknown output format' unless opt[:output] =~ Plugins.plugin_regex(type: :export)
|
1596
1618
|
|
1597
1619
|
export_options = { page_title: title, is_single: is_single, options: opt }
|
1598
1620
|
|
@@ -1646,7 +1668,7 @@ module Doing
|
|
1646
1668
|
do_archive(section, destination, { count: count, tags: tags, bool: bool, search: options[:search], label: options[:label], before: options[:before] })
|
1647
1669
|
write(doing_file)
|
1648
1670
|
else
|
1649
|
-
raise
|
1671
|
+
raise InvalidArgument, 'Either source or destination does not exist'
|
1650
1672
|
end
|
1651
1673
|
end
|
1652
1674
|
|
@@ -1705,7 +1727,12 @@ module Doing
|
|
1705
1727
|
@content[section][:items] = items
|
1706
1728
|
@content[destination][:items].concat(moved_items)
|
1707
1729
|
if moved_items.length.positive?
|
1708
|
-
logger.
|
1730
|
+
logger.count(destination == 'Archive' ? :archived : :moved,
|
1731
|
+
level: :info,
|
1732
|
+
count: moved_items.length,
|
1733
|
+
message: "%count %items from #{section} to #{destination}")
|
1734
|
+
else
|
1735
|
+
logger.info('Skipped:', 'No items were moved')
|
1709
1736
|
end
|
1710
1737
|
else
|
1711
1738
|
count = items.length if items.length < count
|
@@ -1733,10 +1760,11 @@ module Doing
|
|
1733
1760
|
else
|
1734
1761
|
items[0..count - 1]
|
1735
1762
|
end
|
1763
|
+
|
1736
1764
|
logger.count(destination == 'Archive' ? :archived : :moved,
|
1765
|
+
level: :info,
|
1737
1766
|
count: items.length - count,
|
1738
1767
|
message: "%count %items from #{section} to #{destination}")
|
1739
|
-
# logger.info('Archived:', "#{items.length - count} items from #{section} to #{destination}")
|
1740
1768
|
end
|
1741
1769
|
end
|
1742
1770
|
end
|
@@ -1873,7 +1901,8 @@ module Doing
|
|
1873
1901
|
end
|
1874
1902
|
|
1875
1903
|
opts[:search] = options[:search] if options[:search]
|
1876
|
-
|
1904
|
+
opts[:case] = options[:case]
|
1905
|
+
opts[:not] = options[:negate]
|
1877
1906
|
list_section(opts)
|
1878
1907
|
end
|
1879
1908
|
|
@@ -1935,11 +1964,11 @@ module Doing
|
|
1935
1964
|
end
|
1936
1965
|
end
|
1937
1966
|
|
1938
|
-
logger.debug('Autotag:', "
|
1967
|
+
logger.debug('Autotag:', "whitelisted tags: #{whitelisted.join(', ')}") unless whitelisted.empty?
|
1939
1968
|
new_tags = whitelisted
|
1940
1969
|
unless tail_tags.empty?
|
1941
1970
|
tags = tail_tags.uniq.map { |t| "@#{t}".cyan }.join(' ')
|
1942
|
-
logger.debug('Autotag:', "
|
1971
|
+
logger.debug('Autotag:', "synonym tags: #{tags}")
|
1943
1972
|
tags_a = tail_tags.map { |t| "@#{t}" }
|
1944
1973
|
text.add_tags!(tags_a.join(' '))
|
1945
1974
|
new_tags.concat(tags_a)
|
@@ -1948,7 +1977,7 @@ module Doing
|
|
1948
1977
|
unless text == original
|
1949
1978
|
logger.info('Autotag:', "added #{new_tags.join(', ')} to \"#{text}\"")
|
1950
1979
|
else
|
1951
|
-
logger.debug('
|
1980
|
+
logger.debug('Skipped:', "no change to \"#{text}\"")
|
1952
1981
|
end
|
1953
1982
|
|
1954
1983
|
text
|
@@ -2046,7 +2075,7 @@ EOS
|
|
2046
2075
|
(max - k.length).times do
|
2047
2076
|
spacer += ' '
|
2048
2077
|
end
|
2049
|
-
|
2078
|
+
_d, h, m = format_time(v, human: true)
|
2050
2079
|
output.push("┃ #{spacer}#{k}:#{format('%<h> 4dh %<m>02dm', h: h, m: m)} ┃")
|
2051
2080
|
end
|
2052
2081
|
|
@@ -2174,23 +2203,28 @@ EOS
|
|
2174
2203
|
logger.log_now(:error, 'STDERR output:', stderr)
|
2175
2204
|
end
|
2176
2205
|
|
2177
|
-
def log_change(tags_added: [], tags_removed: [], count: 1)
|
2206
|
+
def log_change(tags_added: [], tags_removed: [], count: 1, item: nil, single: false)
|
2178
2207
|
if tags_added.empty? && tags_removed.empty?
|
2179
2208
|
logger.count(:skipped, level: :debug, message: '%count %items with no change', count: count)
|
2180
2209
|
else
|
2181
|
-
|
2182
2210
|
if tags_added.empty?
|
2183
2211
|
logger.count(:skipped, level: :debug, message: 'no tags added to %count %items')
|
2184
|
-
# logger.debug('No tags added:', %("#{item.title}" in #{item.section}))
|
2185
2212
|
else
|
2186
|
-
|
2187
|
-
|
2213
|
+
if single && item
|
2214
|
+
logger.info('Tagged:', %(added #{tags_added.count == 1 ? 'tag' : 'tags'} #{tags_added.map {|t| "@#{t}"}.join(', ')} to #{item.title}))
|
2215
|
+
else
|
2216
|
+
logger.count(:added_tags, level: :info, tag: tags_added, message: '%tags added to %count %items')
|
2217
|
+
end
|
2188
2218
|
end
|
2189
2219
|
|
2190
2220
|
if tags_removed.empty?
|
2191
2221
|
logger.count(:skipped, level: :debug, message: 'no tags removed from %count %items')
|
2192
2222
|
else
|
2193
|
-
|
2223
|
+
if single && item
|
2224
|
+
logger.info('Untagged:', %(removed #{tags_removed.count == 1 ? 'tag' : 'tags'} #{tags_added.map {|t| "@#{t}"}.join(', ')} from #{item.title}))
|
2225
|
+
else
|
2226
|
+
logger.count(:removed_tags, level: :info, tag: tags_removed, message: '%tags removed from %count %items')
|
2227
|
+
end
|
2194
2228
|
end
|
2195
2229
|
end
|
2196
2230
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|