doing 2.0.3.pre → 2.0.8.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 +40 -1
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/bin/doing +316 -114
- data/doing.rdoc +244 -19
- data/example_plugin.rb +1 -1
- 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/configuration.rb +2 -0
- data/lib/doing/errors.rb +22 -15
- data/lib/doing/item.rb +12 -11
- data/lib/doing/log_adapter.rb +27 -25
- data/lib/doing/plugin_manager.rb +1 -1
- data/lib/doing/plugins/export/json_export.rb +2 -2
- data/lib/doing/plugins/export/template_export.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 +9 -7
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +160 -92
- data/lib/examples/commands/autotag.rb +63 -0
- data/lib/examples/commands/wiki.rb +1 -0
- data/lib/examples/plugins/say_export.rb +1 -1
- 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 +7 -7
- 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,20 +686,26 @@ 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
|
search_match = opt[:search].nil? || opt[:search].empty? ? true : item.search(opt[:search])
|
698
704
|
keep = false unless search_match
|
705
|
+
keep = opt[:not] ? !keep : keep
|
699
706
|
end
|
700
707
|
|
708
|
+
|
701
709
|
if keep && opt[:date_filter]&.length == 2
|
702
710
|
start_date = opt[:date_filter][0]
|
703
711
|
end_date = opt[:date_filter][1]
|
@@ -708,30 +716,36 @@ module Doing
|
|
708
716
|
item.date.strftime('%F') == start_date.strftime('%F')
|
709
717
|
end
|
710
718
|
keep = false unless in_date_range
|
719
|
+
keep = opt[:not] ? !keep : keep
|
711
720
|
end
|
712
721
|
|
713
722
|
keep = false if keep && opt[:only_timed] && !item.interval
|
714
723
|
|
715
724
|
if keep && opt[:tag_filter] && !opt[:tag_filter]['tags'].empty?
|
716
725
|
keep = item.tags?(opt[:tag_filter]['tags'], opt[:tag_filter]['bool'])
|
726
|
+
keep = opt[:not] ? !keep : keep
|
717
727
|
end
|
718
728
|
|
719
729
|
if keep && opt[:before]
|
720
730
|
time_string = opt[:before]
|
721
731
|
cutoff = chronify(time_string, guess: :begin)
|
722
732
|
keep = cutoff && item.date <= cutoff
|
733
|
+
keep = opt[:not] ? !keep : keep
|
723
734
|
end
|
724
735
|
|
725
736
|
if keep && opt[:after]
|
726
737
|
time_string = opt[:after]
|
727
738
|
cutoff = chronify(time_string, guess: :end)
|
728
739
|
keep = cutoff && item.date >= cutoff
|
740
|
+
keep = opt[:not] ? !keep : keep
|
729
741
|
end
|
730
742
|
|
731
743
|
if keep && opt[:today]
|
732
744
|
keep = item.date >= Date.today.to_time && item.date < Date.today.next_day.to_time
|
745
|
+
keep = opt[:not] ? !keep : keep
|
733
746
|
elsif keep && opt[:yesterday]
|
734
747
|
keep = item.date >= Date.today.prev_day.to_time && item.date < Date.today.to_time
|
748
|
+
keep = opt[:not] ? !keep : keep
|
735
749
|
end
|
736
750
|
|
737
751
|
keep
|
@@ -753,16 +767,24 @@ module Doing
|
|
753
767
|
##
|
754
768
|
def interactive(opt = {})
|
755
769
|
section = opt[:section] ? guess_section(opt[:section]) : 'All'
|
770
|
+
|
771
|
+
search = nil
|
772
|
+
|
773
|
+
if opt[:search]
|
774
|
+
search = opt[:search]
|
775
|
+
search.sub!(/^'?/, "'") if opt[:exact]
|
776
|
+
search.downcase! if opt[:case] == false
|
777
|
+
opt[:search] = search
|
778
|
+
end
|
779
|
+
|
756
780
|
opt[:query] = opt[:search] if opt[:search] && !opt[:query]
|
781
|
+
opt[:query] = "!#{opt[:query]}" if opt[:not]
|
757
782
|
opt[:multiple] = true
|
758
783
|
items = filter_items([], opt: { section: section, search: opt[:search] })
|
759
784
|
|
760
785
|
selection = choose_from_items(items, opt, include_section: section =~ /^all$/i)
|
761
786
|
|
762
|
-
if selection.empty?
|
763
|
-
logger.debug('Skipped:', 'No selection')
|
764
|
-
return
|
765
|
-
end
|
787
|
+
raise NoResults, 'no items selected' if selection.empty?
|
766
788
|
|
767
789
|
act_on(selection, opt)
|
768
790
|
end
|
@@ -807,7 +829,7 @@ module Doing
|
|
807
829
|
fzf_args.push('-1') unless opt[:show_if_single]
|
808
830
|
|
809
831
|
unless opt[:menu]
|
810
|
-
raise
|
832
|
+
raise InvalidArgument, "Can't skip menu when no query is provided" unless opt[:query] && !opt[:query].empty?
|
811
833
|
|
812
834
|
fzf_args.concat([%(--filter="#{opt[:query]}"), opt[:sort] ? '' : '--no-sort'])
|
813
835
|
end
|
@@ -823,8 +845,10 @@ module Doing
|
|
823
845
|
end
|
824
846
|
|
825
847
|
def act_on(items, opt = {})
|
826
|
-
actions = %i[editor delete tag flag finish cancel archive output save_to]
|
848
|
+
actions = %i[editor delete tag flag finish cancel archive output save_to again resume]
|
827
849
|
has_action = false
|
850
|
+
single = items.count == 1
|
851
|
+
|
828
852
|
actions.each do |a|
|
829
853
|
if opt[a]
|
830
854
|
has_action = true
|
@@ -864,7 +888,7 @@ module Doing
|
|
864
888
|
opt[:reset] = true
|
865
889
|
when /(add|remove) tag/
|
866
890
|
type = action =~ /^add/ ? 'add' : 'remove'
|
867
|
-
raise
|
891
|
+
raise InvalidArgument, "'add tag' and 'remove tag' can not be used together" if opt[:tag]
|
868
892
|
|
869
893
|
print "#{Color.yellow}Tag to #{type}: #{Color.reset}"
|
870
894
|
tag = $stdin.gets
|
@@ -879,7 +903,7 @@ module Doing
|
|
879
903
|
next if tag =~ /^ *$/
|
880
904
|
|
881
905
|
unless output_format
|
882
|
-
raise
|
906
|
+
raise UserCancelled, 'Cancelled'
|
883
907
|
end
|
884
908
|
|
885
909
|
opt[:output] = output_format.strip
|
@@ -912,7 +936,7 @@ module Doing
|
|
912
936
|
|
913
937
|
if opt[:resume] || opt[:reset]
|
914
938
|
if items.count > 1
|
915
|
-
|
939
|
+
raise InvalidArgument, 'resume and restart can only be used on a single entry'
|
916
940
|
else
|
917
941
|
item = items[0]
|
918
942
|
if opt[:resume] && !opt[:reset]
|
@@ -942,7 +966,7 @@ module Doing
|
|
942
966
|
if opt[:flag]
|
943
967
|
tag = @config['marker_tag'] || 'flagged'
|
944
968
|
items.map! do |item|
|
945
|
-
tag_item(item, tag, date: false, remove: opt[:remove])
|
969
|
+
tag_item(item, tag, date: false, remove: opt[:remove], single: single)
|
946
970
|
end
|
947
971
|
end
|
948
972
|
|
@@ -951,7 +975,7 @@ module Doing
|
|
951
975
|
items.map! do |item|
|
952
976
|
if item.should_finish?
|
953
977
|
should_date = !opt[:cancel] && item.should_time?
|
954
|
-
tag_item(item, tag, date: should_date, remove: opt[:remove])
|
978
|
+
tag_item(item, tag, date: should_date, remove: opt[:remove], single: single)
|
955
979
|
end
|
956
980
|
end
|
957
981
|
end
|
@@ -959,7 +983,7 @@ module Doing
|
|
959
983
|
if opt[:tag]
|
960
984
|
tag = opt[:tag]
|
961
985
|
items.map! do |item|
|
962
|
-
tag_item(item, tag, date: false, remove: opt[:remove])
|
986
|
+
tag_item(item, tag, date: false, remove: opt[:remove], single: single)
|
963
987
|
end
|
964
988
|
end
|
965
989
|
|
@@ -1057,7 +1081,7 @@ module Doing
|
|
1057
1081
|
## @param remove (Boolean) remove tags
|
1058
1082
|
## @param date (Boolean) Include timestamp?
|
1059
1083
|
##
|
1060
|
-
def tag_item(item, tags, remove: false, date: false)
|
1084
|
+
def tag_item(item, tags, remove: false, date: false, single: false)
|
1061
1085
|
added = []
|
1062
1086
|
removed = []
|
1063
1087
|
|
@@ -1073,7 +1097,7 @@ module Doing
|
|
1073
1097
|
end
|
1074
1098
|
end
|
1075
1099
|
|
1076
|
-
log_change(tags_added: added, tags_removed: removed, count: 1)
|
1100
|
+
log_change(tags_added: added, tags_removed: removed, count: 1, item: item, single: single)
|
1077
1101
|
|
1078
1102
|
item
|
1079
1103
|
end
|
@@ -1097,21 +1121,22 @@ module Doing
|
|
1097
1121
|
|
1098
1122
|
items = filter_items([], opt: opt)
|
1099
1123
|
|
1100
|
-
logger.info('Skipped:', 'no items matched your search') if items.empty?
|
1101
|
-
|
1102
1124
|
if opt[:interactive]
|
1103
1125
|
items = choose_from_items(items, {
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1126
|
+
menu: true,
|
1127
|
+
header: '',
|
1128
|
+
prompt: 'Select entries to tag > ',
|
1129
|
+
multiple: true,
|
1130
|
+
sort: true,
|
1131
|
+
show_if_single: true
|
1132
|
+
}, include_section: opt[:section] =~ /^all$/i)
|
1133
|
+
|
1134
|
+
raise NoResults, 'no items selected' if items.empty?
|
1111
1135
|
|
1112
|
-
return if items.nil?
|
1113
1136
|
end
|
1114
1137
|
|
1138
|
+
raise NoResults, 'no items matched your search' if items.empty?
|
1139
|
+
|
1115
1140
|
items.each do |item|
|
1116
1141
|
added = []
|
1117
1142
|
removed = []
|
@@ -1123,7 +1148,7 @@ module Doing
|
|
1123
1148
|
# logger.debug('Autotag:', 'No changes')
|
1124
1149
|
else
|
1125
1150
|
logger.count(:added_tags)
|
1126
|
-
logger.
|
1151
|
+
logger.write(items.count == 1 ? :info : :debug, 'Tagged:', new_title)
|
1127
1152
|
item.title = new_title
|
1128
1153
|
end
|
1129
1154
|
else
|
@@ -1178,13 +1203,14 @@ module Doing
|
|
1178
1203
|
else
|
1179
1204
|
old_title = item.title.dup
|
1180
1205
|
should_date = opt[:date] && item.should_time?
|
1206
|
+
item.title.tag!('done', remove: true) if tag =~ /done/ && !should_date
|
1181
1207
|
item.title.tag!(tag, value: should_date ? done_date.strftime('%F %R') : nil)
|
1182
1208
|
added << tag if old_title != item.title
|
1183
1209
|
end
|
1184
1210
|
end
|
1185
1211
|
end
|
1186
1212
|
|
1187
|
-
log_change(tags_added: added, tags_removed: removed)
|
1213
|
+
log_change(tags_added: added, tags_removed: removed, item: item, single: items.count == 1)
|
1188
1214
|
|
1189
1215
|
item.note.add(opt[:note]) if opt[:note]
|
1190
1216
|
|
@@ -1216,7 +1242,7 @@ module Doing
|
|
1216
1242
|
@content[section][:items].concat([new_item])
|
1217
1243
|
|
1218
1244
|
logger.count(section == 'Archive' ? :archived : :moved)
|
1219
|
-
logger.debug("
|
1245
|
+
logger.debug("#{section == 'Archive' ? 'Archived' : 'Moved'}:",
|
1220
1246
|
"#{new_item.title.truncate(60)} from #{from} to #{section}")
|
1221
1247
|
new_item
|
1222
1248
|
end
|
@@ -1245,7 +1271,7 @@ module Doing
|
|
1245
1271
|
section_items = @content[section][:items]
|
1246
1272
|
deleted = section_items.delete(item)
|
1247
1273
|
logger.count(:deleted)
|
1248
|
-
logger.
|
1274
|
+
logger.info('Entry deleted:', deleted.title)
|
1249
1275
|
end
|
1250
1276
|
|
1251
1277
|
##
|
@@ -1260,16 +1286,13 @@ module Doing
|
|
1260
1286
|
section_items = @content[section][:items]
|
1261
1287
|
s_idx = section_items.index { |item| item.equal?(old_item) }
|
1262
1288
|
|
1263
|
-
unless s_idx
|
1264
|
-
Doing.logger.error('Fail to update:', 'Could not find item in index')
|
1265
|
-
raise Errors::ItemNotFound, 'Unable to find item in index, did it mutate?'
|
1266
|
-
end
|
1289
|
+
raise ItemNotFound, 'Unable to find item in index, did it mutate?' unless s_idx
|
1267
1290
|
|
1268
1291
|
return if section_items[s_idx].equal?(new_item)
|
1269
1292
|
|
1270
1293
|
section_items[s_idx] = new_item
|
1271
1294
|
logger.count(:updated)
|
1272
|
-
logger.
|
1295
|
+
logger.info('Entry updated:', section_items[s_idx].title.truncate(60))
|
1273
1296
|
new_item
|
1274
1297
|
end
|
1275
1298
|
|
@@ -1342,10 +1365,10 @@ module Doing
|
|
1342
1365
|
item.title = item.title.sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{item.section})")
|
1343
1366
|
move_item(item, 'Archive', label: false)
|
1344
1367
|
logger.count(:completed_archived)
|
1345
|
-
logger.
|
1368
|
+
logger.info('Completed/archived:', item.title)
|
1346
1369
|
else
|
1347
1370
|
logger.count(:completed)
|
1348
|
-
logger.
|
1371
|
+
logger.info('Completed:', item.title)
|
1349
1372
|
end
|
1350
1373
|
end
|
1351
1374
|
|
@@ -1477,10 +1500,10 @@ module Doing
|
|
1477
1500
|
if File.exist?(file)
|
1478
1501
|
init_doing_file(file)
|
1479
1502
|
@content.deep_merge(new_content)
|
1480
|
-
logger.warn('File update:', "
|
1503
|
+
logger.warn('File update:', "added entries to existing file: #{file}")
|
1481
1504
|
else
|
1482
1505
|
@content = new_content
|
1483
|
-
logger.warn('File update:', "
|
1506
|
+
logger.warn('File update:', "created new file: #{file}")
|
1484
1507
|
end
|
1485
1508
|
|
1486
1509
|
write(file, backup: false)
|
@@ -1564,17 +1587,13 @@ module Doing
|
|
1564
1587
|
|
1565
1588
|
items.reverse! if opt[:order] =~ /^d/i
|
1566
1589
|
|
1567
|
-
|
1568
1590
|
if opt[:interactive]
|
1569
1591
|
opt[:menu] = !opt[:force]
|
1570
1592
|
opt[:query] = '' # opt[:search]
|
1571
1593
|
opt[:multiple] = true
|
1572
1594
|
selected = choose_from_items(items, opt, include_section: opt[:section] =~ /^all$/i )
|
1573
1595
|
|
1574
|
-
if selected.empty?
|
1575
|
-
logger.debug('Skipped:', 'No selection')
|
1576
|
-
return
|
1577
|
-
end
|
1596
|
+
raise NoResults, 'no items selected' if selected.empty?
|
1578
1597
|
|
1579
1598
|
act_on(selected, opt)
|
1580
1599
|
return
|
@@ -1591,7 +1610,7 @@ module Doing
|
|
1591
1610
|
def output(items, title, is_single, opt = {})
|
1592
1611
|
out = nil
|
1593
1612
|
|
1594
|
-
raise
|
1613
|
+
raise InvalidArgument, 'Unknown output format' unless opt[:output] =~ Plugins.plugin_regex(type: :export)
|
1595
1614
|
|
1596
1615
|
export_options = { page_title: title, is_single: is_single, options: opt }
|
1597
1616
|
|
@@ -1645,7 +1664,7 @@ module Doing
|
|
1645
1664
|
do_archive(section, destination, { count: count, tags: tags, bool: bool, search: options[:search], label: options[:label], before: options[:before] })
|
1646
1665
|
write(doing_file)
|
1647
1666
|
else
|
1648
|
-
raise
|
1667
|
+
raise InvalidArgument, 'Either source or destination does not exist'
|
1649
1668
|
end
|
1650
1669
|
end
|
1651
1670
|
|
@@ -1704,7 +1723,12 @@ module Doing
|
|
1704
1723
|
@content[section][:items] = items
|
1705
1724
|
@content[destination][:items].concat(moved_items)
|
1706
1725
|
if moved_items.length.positive?
|
1707
|
-
logger.
|
1726
|
+
logger.count(destination == 'Archive' ? :archived : :moved,
|
1727
|
+
level: :info,
|
1728
|
+
count: moved_items.length,
|
1729
|
+
message: "%count %items from #{section} to #{destination}")
|
1730
|
+
else
|
1731
|
+
logger.info('Skipped:', 'No items were moved')
|
1708
1732
|
end
|
1709
1733
|
else
|
1710
1734
|
count = items.length if items.length < count
|
@@ -1732,10 +1756,11 @@ module Doing
|
|
1732
1756
|
else
|
1733
1757
|
items[0..count - 1]
|
1734
1758
|
end
|
1759
|
+
|
1735
1760
|
logger.count(destination == 'Archive' ? :archived : :moved,
|
1761
|
+
level: :info,
|
1736
1762
|
count: items.length - count,
|
1737
1763
|
message: "%count %items from #{section} to #{destination}")
|
1738
|
-
# logger.info('Archived:', "#{items.length - count} items from #{section} to #{destination}")
|
1739
1764
|
end
|
1740
1765
|
end
|
1741
1766
|
end
|
@@ -1872,7 +1897,7 @@ module Doing
|
|
1872
1897
|
end
|
1873
1898
|
|
1874
1899
|
opts[:search] = options[:search] if options[:search]
|
1875
|
-
|
1900
|
+
opts[:not] = options[:negate]
|
1876
1901
|
list_section(opts)
|
1877
1902
|
end
|
1878
1903
|
|
@@ -1934,11 +1959,11 @@ module Doing
|
|
1934
1959
|
end
|
1935
1960
|
end
|
1936
1961
|
|
1937
|
-
logger.debug('Autotag:', "
|
1962
|
+
logger.debug('Autotag:', "whitelisted tags: #{whitelisted.join(', ')}") unless whitelisted.empty?
|
1938
1963
|
new_tags = whitelisted
|
1939
1964
|
unless tail_tags.empty?
|
1940
1965
|
tags = tail_tags.uniq.map { |t| "@#{t}".cyan }.join(' ')
|
1941
|
-
logger.debug('Autotag:', "
|
1966
|
+
logger.debug('Autotag:', "synonym tags: #{tags}")
|
1942
1967
|
tags_a = tail_tags.map { |t| "@#{t}" }
|
1943
1968
|
text.add_tags!(tags_a.join(' '))
|
1944
1969
|
new_tags.concat(tags_a)
|
@@ -1947,7 +1972,7 @@ module Doing
|
|
1947
1972
|
unless text == original
|
1948
1973
|
logger.info('Autotag:', "added #{new_tags.join(', ')} to \"#{text}\"")
|
1949
1974
|
else
|
1950
|
-
logger.debug('
|
1975
|
+
logger.debug('Skipped:', "no change to \"#{text}\"")
|
1951
1976
|
end
|
1952
1977
|
|
1953
1978
|
text
|
@@ -1997,7 +2022,7 @@ module Doing
|
|
1997
2022
|
EOS
|
1998
2023
|
sorted_tags_data.reverse.each do |k, v|
|
1999
2024
|
if v > 0
|
2000
|
-
output += "<tr><td style='text-align:left;'>#{k}</td><td style='text-align:left;'>#{'%02d:%02d:%02d' %
|
2025
|
+
output += "<tr><td style='text-align:left;'>#{k}</td><td style='text-align:left;'>#{'%02d:%02d:%02d' % format_time(v)}</td></tr>\n"
|
2001
2026
|
end
|
2002
2027
|
end
|
2003
2028
|
tail = <<EOS
|
@@ -2008,7 +2033,7 @@ EOS
|
|
2008
2033
|
<tfoot>
|
2009
2034
|
<tr>
|
2010
2035
|
<td style="text-align:left;"><strong>Total</strong></td>
|
2011
|
-
<td style="text-align:left;">#{'%02d:%02d:%02d' %
|
2036
|
+
<td style="text-align:left;">#{'%02d:%02d:%02d' % format_time(total)}</td>
|
2012
2037
|
</tr>
|
2013
2038
|
</tfoot>
|
2014
2039
|
</table>
|
@@ -2022,7 +2047,7 @@ EOS
|
|
2022
2047
|
EOS
|
2023
2048
|
sorted_tags_data.reverse.each do |k, v|
|
2024
2049
|
if v > 0
|
2025
|
-
output += "| #{' ' * (pad - k.length)}#{k} | #{'%02d:%02d:%02d' %
|
2050
|
+
output += "| #{' ' * (pad - k.length)}#{k} | #{'%02d:%02d:%02d' % format_time(v)} |\n"
|
2026
2051
|
end
|
2027
2052
|
end
|
2028
2053
|
tail = "[Tag Totals]"
|
@@ -2030,7 +2055,7 @@ EOS
|
|
2030
2055
|
when :json
|
2031
2056
|
output = []
|
2032
2057
|
sorted_tags_data.reverse.each do |k, v|
|
2033
|
-
d, h, m =
|
2058
|
+
d, h, m = format_time(v)
|
2034
2059
|
output << {
|
2035
2060
|
'tag' => k,
|
2036
2061
|
'seconds' => v,
|
@@ -2038,6 +2063,39 @@ EOS
|
|
2038
2063
|
}
|
2039
2064
|
end
|
2040
2065
|
output
|
2066
|
+
when :human
|
2067
|
+
output = []
|
2068
|
+
sorted_tags_data.reverse.each do |k, v|
|
2069
|
+
spacer = ''
|
2070
|
+
(max - k.length).times do
|
2071
|
+
spacer += ' '
|
2072
|
+
end
|
2073
|
+
d, h, m = format_time(v, human: true)
|
2074
|
+
output.push("┃ #{spacer}#{k}:#{format('%<h> 4dh %<m>02dm', h: h, m: m)} ┃")
|
2075
|
+
end
|
2076
|
+
|
2077
|
+
header = '┏━━ Tag Totals '
|
2078
|
+
(max - 2).times { header += '━' }
|
2079
|
+
header += '┓'
|
2080
|
+
footer = '┗'
|
2081
|
+
(max + 12).times { footer += '━' }
|
2082
|
+
footer += '┛'
|
2083
|
+
divider = '┣'
|
2084
|
+
(max + 12).times { divider += '━' }
|
2085
|
+
divider += '┫'
|
2086
|
+
output = output.empty? ? '' : "\n#{header}\n#{output.join("\n")}"
|
2087
|
+
d, h, m = format_time(total, human: true)
|
2088
|
+
output += "\n#{divider}"
|
2089
|
+
spacer = ''
|
2090
|
+
(max - 6).times do
|
2091
|
+
spacer += ' '
|
2092
|
+
end
|
2093
|
+
total = "┃ #{spacer}total: "
|
2094
|
+
total += format('%<h> 4dh %<m>02dm', h: h, m: m)
|
2095
|
+
total += ' ┃'
|
2096
|
+
output += "\n#{total}"
|
2097
|
+
output += "\n#{footer}"
|
2098
|
+
output
|
2041
2099
|
else
|
2042
2100
|
output = []
|
2043
2101
|
sorted_tags_data.reverse.each do |k, v|
|
@@ -2045,12 +2103,12 @@ EOS
|
|
2045
2103
|
(max - k.length).times do
|
2046
2104
|
spacer += ' '
|
2047
2105
|
end
|
2048
|
-
d, h, m =
|
2106
|
+
d, h, m = format_time(v)
|
2049
2107
|
output.push("#{k}:#{spacer}#{format('%<d>02d:%<h>02d:%<m>02d', d: d, h: h, m: m)}")
|
2050
2108
|
end
|
2051
2109
|
|
2052
2110
|
output = output.empty? ? '' : "\n--- Tag Totals ---\n#{output.join("\n")}"
|
2053
|
-
d, h, m =
|
2111
|
+
d, h, m = format_time(total)
|
2054
2112
|
output += "\n\nTotal tracked: #{format('%<d>02d:%<h>02d:%<m>02d', d: d, h: h, m: m)}\n"
|
2055
2113
|
output
|
2056
2114
|
end
|
@@ -2076,7 +2134,7 @@ EOS
|
|
2076
2134
|
record_tag_times(item, seconds) if record
|
2077
2135
|
return seconds.positive? ? seconds : false unless formatted
|
2078
2136
|
|
2079
|
-
return seconds.positive? ? format('%02d:%02d:%02d', *
|
2137
|
+
return seconds.positive? ? format('%02d:%02d:%02d', *format_time(seconds)) : false
|
2080
2138
|
end
|
2081
2139
|
|
2082
2140
|
false
|
@@ -2106,7 +2164,7 @@ EOS
|
|
2106
2164
|
##
|
2107
2165
|
## @param seconds The seconds
|
2108
2166
|
##
|
2109
|
-
def
|
2167
|
+
def format_time(seconds, human: false)
|
2110
2168
|
return [0, 0, 0] if seconds.nil?
|
2111
2169
|
|
2112
2170
|
if seconds.class == String && seconds =~ /(\d+):(\d+):(\d+)/
|
@@ -2117,10 +2175,15 @@ EOS
|
|
2117
2175
|
end
|
2118
2176
|
minutes = (seconds / 60).to_i
|
2119
2177
|
hours = (minutes / 60).to_i
|
2120
|
-
|
2121
|
-
|
2122
|
-
|
2123
|
-
|
2178
|
+
if human
|
2179
|
+
minutes = (minutes % 60).to_i
|
2180
|
+
[0, hours, minutes]
|
2181
|
+
else
|
2182
|
+
days = (hours / 24).to_i
|
2183
|
+
hours = (hours % 24).to_i
|
2184
|
+
minutes = (minutes % 60).to_i
|
2185
|
+
[days, hours, minutes]
|
2186
|
+
end
|
2124
2187
|
end
|
2125
2188
|
|
2126
2189
|
private
|
@@ -2135,23 +2198,28 @@ EOS
|
|
2135
2198
|
logger.log_now(:error, 'STDERR output:', stderr)
|
2136
2199
|
end
|
2137
2200
|
|
2138
|
-
def log_change(tags_added: [], tags_removed: [], count: 1)
|
2201
|
+
def log_change(tags_added: [], tags_removed: [], count: 1, item: nil, single: false)
|
2139
2202
|
if tags_added.empty? && tags_removed.empty?
|
2140
2203
|
logger.count(:skipped, level: :debug, message: '%count %items with no change', count: count)
|
2141
2204
|
else
|
2142
|
-
|
2143
2205
|
if tags_added.empty?
|
2144
2206
|
logger.count(:skipped, level: :debug, message: 'no tags added to %count %items')
|
2145
|
-
# logger.debug('No tags added:', %("#{item.title}" in #{item.section}))
|
2146
2207
|
else
|
2147
|
-
|
2148
|
-
|
2208
|
+
if single && item
|
2209
|
+
logger.info('Tagged:', %(added #{tags_added.count == 1 ? 'tag' : 'tags'} #{tags_added.map {|t| "@#{t}"}.join(', ')} to #{item.title}))
|
2210
|
+
else
|
2211
|
+
logger.count(:added_tags, level: :info, tag: tags_added, message: '%tags added to %count %items')
|
2212
|
+
end
|
2149
2213
|
end
|
2150
2214
|
|
2151
2215
|
if tags_removed.empty?
|
2152
2216
|
logger.count(:skipped, level: :debug, message: 'no tags removed from %count %items')
|
2153
2217
|
else
|
2154
|
-
|
2218
|
+
if single && item
|
2219
|
+
logger.info('Untagged:', %(removed #{tags_removed.count == 1 ? 'tag' : 'tags'} #{tags_added.map {|t| "@#{t}"}.join(', ')} from #{item.title}))
|
2220
|
+
else
|
2221
|
+
logger.count(:removed_tags, level: :info, tag: tags_removed, message: '%tags removed from %count %items')
|
2222
|
+
end
|
2155
2223
|
end
|
2156
2224
|
end
|
2157
2225
|
end
|