doing 1.0.70 → 1.0.72
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +25 -1
- data/bin/doing +46 -1
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +199 -2
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8ab378bf9ba26ec5c6f852afa702d74bd8323bb9e4aca92e3e94b081c578b637
|
4
|
+
data.tar.gz: 1c868eb6ac0dbb438f69679abfcb2fc587465ce9848c4da607f537b8c98d60ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 257898f624de2463259c5b49e46bc1687edf56f428aa16d297d212554efabbff82f59f0ae98da9772fc1aa30673623d70a2903326dca6ffd510d6afec846ff43
|
7
|
+
data.tar.gz: f635bc6bbb42de40a4f5bdcd1abd1d4caeec1a94af7553f1bcff1d0fffcda6898c248941a38f7abfba807721a18d39fd1632d0ec228c56b940c502f036ac7f19
|
data/README.md
CHANGED
@@ -27,7 +27,7 @@ If there's something I want to look at later but doesn't need to be added to a t
|
|
27
27
|
|
28
28
|
## Installation
|
29
29
|
|
30
|
-
The current version of `doing` is <!--VER-->1.0.
|
30
|
+
The current version of `doing` is <!--VER-->1.0.70<!--END VER-->.
|
31
31
|
|
32
32
|
$ [sudo] gem install doing
|
33
33
|
|
@@ -617,6 +617,30 @@ Now you can run `doing import --type timing -s SECTION PATH`, where SECTION is t
|
|
617
617
|
# Import to default section (Currently) and prefix entries with '[Imported]'
|
618
618
|
doing import --prefix="[Imported]" "~/Desktop/All Activities.json"
|
619
619
|
|
620
|
+
#### Interactive Usage
|
621
|
+
|
622
|
+
If you have `fzf` installed (<https://github.com/junegunn/fzf>), you can use `doing select` to get a menu of all your items (or items in a given section) which can be searched with fuzzy matching. The menu allows multiple selections to be acted on directly.
|
623
|
+
|
624
|
+
To use the menu, type a search string or use the arrow keys to navigate up and down. Press tab on an entry you'd like to perform an action on. A marker will show up on the left indicating the entry is selected. Repeat the process and select as many entries as needed. When you hit Return, the selection will be passed back to doing.
|
625
|
+
|
626
|
+
Doing can perform several functions with this menu. Not all of doing's features are available, but the core functionality you'd need is there, plus you can open all selected entries in your editor, make changes to them, and when you save and close the entries are updated accordingly. This allows editing of everything from timestamps to tags to notes.
|
627
|
+
|
628
|
+
Run `doing help select` for a list of options:
|
629
|
+
|
630
|
+
-a, --archive - Archive selected items
|
631
|
+
-c, --cancel - Cancel selected items (add @done without timestamp)
|
632
|
+
-d, --delete - Delete selected items
|
633
|
+
-e, --editor - Edit selected item(s)
|
634
|
+
-f, --finish - Add @done with current time to selected item(s)
|
635
|
+
--flag - Add flag to selected item(s)
|
636
|
+
-m, --move=SECTION - Move selected items to section (default: none)
|
637
|
+
-s, --section=SECTION - Select from a specific section (default: none)
|
638
|
+
-t, --tag=TAG - Tag selected entries (default: none)
|
639
|
+
|
640
|
+
For example, `doing select -d -a` would present the menu, and then mark selected entries as @done (with timestamp) and move them to the Archive section.
|
641
|
+
|
642
|
+
Multiple actions can be performed at once by combining options. You can also combine the `--editor` switch with any other options. Other actions will be performed first, then the entries --- with any modifications performed --- will be presented in the editor for tweaking.
|
643
|
+
|
620
644
|
---
|
621
645
|
|
622
646
|
## Extras
|
data/bin/doing
CHANGED
@@ -245,7 +245,7 @@ command :template do |c|
|
|
245
245
|
c.action do |_global_options, options, args|
|
246
246
|
raise 'No type specified, use `doing template [HAML|CSS]`' if args.empty?
|
247
247
|
|
248
|
-
case
|
248
|
+
case args[0]
|
249
249
|
when /html|haml/i
|
250
250
|
$stdout.puts wwid.haml_template
|
251
251
|
when /css/i
|
@@ -256,6 +256,51 @@ command :template do |c|
|
|
256
256
|
end
|
257
257
|
end
|
258
258
|
|
259
|
+
desc 'Display an interactive menu to perform operations (requires fzf)'
|
260
|
+
long_desc 'Requires that `fzf` be installed and available in your path. <https://github.com/junegunn/fzf>
|
261
|
+
|
262
|
+
List all entries and select with typeahead fuzzy matching. Multiple selections are allowed, hit tab to add the highlighted entry to the selection. Return processes the selected entries.'
|
263
|
+
command :select do |c|
|
264
|
+
c.desc 'Select from a specific section'
|
265
|
+
c.arg_name 'SECTION'
|
266
|
+
c.flag %i[s section]
|
267
|
+
|
268
|
+
c.desc 'Tag selected entries'
|
269
|
+
c.arg_name 'TAG'
|
270
|
+
c.flag %i[t tag]
|
271
|
+
|
272
|
+
# c.desc 'Add @done to selected item(s), using start time of next item as the finish time'
|
273
|
+
# c.switch %i[a auto], negatable: false, default_value: false
|
274
|
+
|
275
|
+
c.desc 'Archive selected items'
|
276
|
+
c.switch %i[a archive], negatable: false, default_value: false
|
277
|
+
|
278
|
+
c.desc 'Move selected items to section'
|
279
|
+
c.arg_name 'SECTION'
|
280
|
+
c.flag %i[m move]
|
281
|
+
|
282
|
+
c.desc 'Cancel selected items (add @done without timestamp)'
|
283
|
+
c.switch %i[c cancel], negatable: false, default_value: false
|
284
|
+
|
285
|
+
c.desc 'Delete selected items'
|
286
|
+
c.switch %i[d delete], negatable: false, default_value: false
|
287
|
+
|
288
|
+
c.desc 'Edit selected item(s)'
|
289
|
+
c.switch %i[e editor], negatable: false, default_value: false
|
290
|
+
|
291
|
+
c.desc 'Add @done with current time to selected item(s)'
|
292
|
+
c.switch %i[f finish], negatable: false, default_value: false
|
293
|
+
|
294
|
+
c.desc 'Add flag to selected item(s)'
|
295
|
+
c.switch %i[flag], negatable: false, default_value: false
|
296
|
+
|
297
|
+
c.action do |_global_options, options, args|
|
298
|
+
section = options[:section] || 'All'
|
299
|
+
edit = options[:editor]
|
300
|
+
wwid.interactive(options)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
259
304
|
desc 'Add an item to the Later section'
|
260
305
|
arg_name 'ENTRY'
|
261
306
|
command :later do |c|
|
data/lib/doing/version.rb
CHANGED
data/lib/doing/wwid.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'deep_merge'
|
4
4
|
require 'open3'
|
5
5
|
require 'pp'
|
6
|
+
require 'shellwords'
|
6
7
|
|
7
8
|
##
|
8
9
|
## @brief Main "What Was I Doing" methods
|
@@ -318,8 +319,8 @@ class WWID
|
|
318
319
|
|
319
320
|
note = input_lines.length > 1 ? input_lines[1..-1] : []
|
320
321
|
# If title line ends in a parenthetical, use that as the note
|
321
|
-
if note.empty? && title =~ /\(.*?\)$/
|
322
|
-
title.sub!(/\((.*?)\)$/) do
|
322
|
+
if note.empty? && title =~ /\s+\(.*?\)$/
|
323
|
+
title.sub!(/\s+\((.*?)\)$/) do
|
323
324
|
m = Regexp.last_match
|
324
325
|
note.push(m[1])
|
325
326
|
''
|
@@ -765,6 +766,129 @@ class WWID
|
|
765
766
|
all_items.max_by { |item| item['date'] }
|
766
767
|
end
|
767
768
|
|
769
|
+
|
770
|
+
##
|
771
|
+
## @brief Display an interactive menu of entries
|
772
|
+
##
|
773
|
+
## @param opt (Hash) Additional options
|
774
|
+
##
|
775
|
+
def interactive(opt = {})
|
776
|
+
raise "Select command requires that fzf be installed" unless exec_available('fzf')
|
777
|
+
|
778
|
+
section = opt[:section] ? guess_section(opt[:section]) : 'All'
|
779
|
+
|
780
|
+
|
781
|
+
if section =~ /^all$/i
|
782
|
+
combined = { 'items' => [] }
|
783
|
+
@content.each do |_k, v|
|
784
|
+
combined['items'] += v['items']
|
785
|
+
end
|
786
|
+
items = combined['items'].dup.sort_by { |item| item['date'] }.reverse
|
787
|
+
else
|
788
|
+
items = @content[section]['items']
|
789
|
+
end
|
790
|
+
|
791
|
+
|
792
|
+
options = items.map.with_index do |item, i|
|
793
|
+
out = [
|
794
|
+
i,
|
795
|
+
') ',
|
796
|
+
item['date'],
|
797
|
+
' | ',
|
798
|
+
item['title'],
|
799
|
+
]
|
800
|
+
if opt[:section] =~ /^all/i
|
801
|
+
out.concat([
|
802
|
+
' (',
|
803
|
+
item['section'],
|
804
|
+
') '
|
805
|
+
])
|
806
|
+
end
|
807
|
+
out.join('')
|
808
|
+
end
|
809
|
+
|
810
|
+
res = `echo #{Shellwords.escape(options.join("\n"))}|fzf -m`
|
811
|
+
selected = []
|
812
|
+
res.split(/\n/).each do |item|
|
813
|
+
idx = item.match(/^(\d+)\)/)[1].to_i
|
814
|
+
selected.push(items[idx])
|
815
|
+
end
|
816
|
+
|
817
|
+
if selected.empty?
|
818
|
+
@results.push("No selection")
|
819
|
+
return
|
820
|
+
end
|
821
|
+
|
822
|
+
if opt[:delete]
|
823
|
+
res = yn("Delete #{selected.size} items?", default_response: 'y')
|
824
|
+
if res
|
825
|
+
selected.each {|item| delete_item(item) }
|
826
|
+
write(@doing_file)
|
827
|
+
end
|
828
|
+
return
|
829
|
+
end
|
830
|
+
|
831
|
+
if opt[:flag]
|
832
|
+
tag = @config['marker_tag'] || 'flagged'
|
833
|
+
selected.map! {|item| tag_item(item, tag, date: false) }
|
834
|
+
end
|
835
|
+
|
836
|
+
if opt[:finish] || opt[:cancel]
|
837
|
+
tag = 'done'
|
838
|
+
selected.map! {|item| tag_item(item, tag, date: !opt[:cancel])}
|
839
|
+
end
|
840
|
+
|
841
|
+
if opt[:tag]
|
842
|
+
tag = opt[:tag]
|
843
|
+
selected.map! {|item| tag_item(item, tag, date: false)}
|
844
|
+
end
|
845
|
+
|
846
|
+
if opt[:archive] || opt[:move]
|
847
|
+
section = opt[:archive] ? 'Archive' : guess_section(opt[:move])
|
848
|
+
selected.map! {|item| move_item(item, section) }
|
849
|
+
end
|
850
|
+
|
851
|
+
write(@doing_file)
|
852
|
+
|
853
|
+
if opt[:editor]
|
854
|
+
|
855
|
+
editable_items = []
|
856
|
+
|
857
|
+
selected.each do |item|
|
858
|
+
editable = "#{item['date']} | #{item['title']}"
|
859
|
+
old_note = item['note'] ? item['note'].map(&:strip).join("\n") : nil
|
860
|
+
editable += "\n#{old_note}" unless old_note.nil?
|
861
|
+
editable_items << editable
|
862
|
+
end
|
863
|
+
|
864
|
+
new_items = fork_editor(editable_items.join("\n---\n") + "\n\n# You may delete entries, but leave all --- lines in place").split(/\n---\n/)
|
865
|
+
|
866
|
+
new_items.each_with_index do |new_item, i|
|
867
|
+
input_lines = new_item.split(/[\n\r]+/).delete_if {|line| line =~ /^#/ || line =~ /^\s*$/ }
|
868
|
+
title = input_lines[0]&.strip
|
869
|
+
if title.nil? || title =~ /^---$/ || title.strip.empty?
|
870
|
+
delete_item(selected[i])
|
871
|
+
else
|
872
|
+
note = input_lines.length > 1 ? input_lines[1..-1] : []
|
873
|
+
|
874
|
+
note.map!(&:strip)
|
875
|
+
note.delete_if { |line| line =~ /^\s*$/ || line =~ /^#/ }
|
876
|
+
|
877
|
+
date = title.match(/^([\d\-: ]+) \| /)[1]
|
878
|
+
title.sub!(/^([\d\-: ]+) \| /, '')
|
879
|
+
|
880
|
+
item = selected[i].dup
|
881
|
+
item['title'] = title
|
882
|
+
item['note'] = note
|
883
|
+
item['date'] = Time.parse(date)
|
884
|
+
update_item(selected[i], item)
|
885
|
+
end
|
886
|
+
end
|
887
|
+
|
888
|
+
write(@doing_file)
|
889
|
+
end
|
890
|
+
end
|
891
|
+
|
768
892
|
##
|
769
893
|
## @brief Tag the last entry or X entries
|
770
894
|
##
|
@@ -904,6 +1028,71 @@ class WWID
|
|
904
1028
|
write(@doing_file)
|
905
1029
|
end
|
906
1030
|
|
1031
|
+
def move_item(item, section)
|
1032
|
+
old_section = item['section']
|
1033
|
+
new_item = item.dup
|
1034
|
+
new_item['section'] = section
|
1035
|
+
|
1036
|
+
section_items = @content[old_section]['items']
|
1037
|
+
section_items.delete(item)
|
1038
|
+
@content[old_section]['items'] = section_items
|
1039
|
+
|
1040
|
+
archive_items = @content[section]['items']
|
1041
|
+
archive_items.push(new_item)
|
1042
|
+
# archive_items = archive_items.sort_by { |item| item['date'] }
|
1043
|
+
@content[section]['items'] = archive_items
|
1044
|
+
|
1045
|
+
@results.push("Entry moved to #{section}: #{new_item['title']}")
|
1046
|
+
return new_item
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
##
|
1050
|
+
## @brief Delete an item from the index
|
1051
|
+
##
|
1052
|
+
## @param old_item
|
1053
|
+
##
|
1054
|
+
def delete_item(old_item)
|
1055
|
+
section = old_item['section']
|
1056
|
+
|
1057
|
+
section_items = @content[section]['items']
|
1058
|
+
deleted = section_items.delete(old_item)
|
1059
|
+
@results.push("Entry deleted: #{deleted['title']}")
|
1060
|
+
@content[section]['items'] = section_items
|
1061
|
+
end
|
1062
|
+
|
1063
|
+
##
|
1064
|
+
## @brief Tag an item from the index
|
1065
|
+
##
|
1066
|
+
## @param old_item (Item) The item to tag
|
1067
|
+
## @param tag (string) The tag to apply
|
1068
|
+
## @param date (Boolean) Include timestamp?
|
1069
|
+
##
|
1070
|
+
def tag_item(old_item, tag, date: false)
|
1071
|
+
title = old_item['title'].dup
|
1072
|
+
done_date = Time.now
|
1073
|
+
if title !~ /@#{tag}/
|
1074
|
+
title.chomp!
|
1075
|
+
if date
|
1076
|
+
title += " @#{tag}(#{done_date.strftime('%F %R')})"
|
1077
|
+
else
|
1078
|
+
title += " @#{tag}"
|
1079
|
+
end
|
1080
|
+
new_item = old_item.dup
|
1081
|
+
new_item['title'] = title
|
1082
|
+
update_item(old_item, new_item)
|
1083
|
+
return new_item
|
1084
|
+
else
|
1085
|
+
@results.push(%(Item already @#{tag}: "#{title}" in #{old_item['section']}))
|
1086
|
+
return old_item
|
1087
|
+
end
|
1088
|
+
end
|
1089
|
+
|
1090
|
+
##
|
1091
|
+
## @brief Update an item in the index with a modified item
|
1092
|
+
##
|
1093
|
+
## @param old_item The old item
|
1094
|
+
## @param new_item The new item
|
1095
|
+
##
|
907
1096
|
def update_item(old_item, new_item)
|
908
1097
|
section = old_item['section']
|
909
1098
|
|
@@ -2005,4 +2194,12 @@ EOS
|
|
2005
2194
|
minutes = (minutes % 60).to_i
|
2006
2195
|
[days, hours, minutes]
|
2007
2196
|
end
|
2197
|
+
|
2198
|
+
def exec_available(cli)
|
2199
|
+
if File.exists?(File.expand_path(cli))
|
2200
|
+
File.executable?(File.expand_path(cli))
|
2201
|
+
else
|
2202
|
+
system "which #{cli}", :out => File::NULL, :err => File::NULL
|
2203
|
+
end
|
2204
|
+
end
|
2008
2205
|
end
|