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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 10dc5d92f3e7079a9525d39c47fe0f69ef22f64ae5400cd9da1925f9b0fc1964
4
- data.tar.gz: ec08df722ce07bbfcee808bfd4bd9eabe5470ef173f5d11e47fc52356b2c2c95
3
+ metadata.gz: 8ab378bf9ba26ec5c6f852afa702d74bd8323bb9e4aca92e3e94b081c578b637
4
+ data.tar.gz: 1c868eb6ac0dbb438f69679abfcb2fc587465ce9848c4da607f537b8c98d60ca
5
5
  SHA512:
6
- metadata.gz: c56a9a328d2ed31e70633333be5f7666d9586154fba7cc59f34116f1a972156fb4dd7d087e59452bf860167e618dad815da8a113fe0e4cf9f12faafa98d68d72
7
- data.tar.gz: 3152021d8017af18e4046736f1f994bd1821f95bece924ca9ad78de997ad3473cbfd411389ede1dfe6313a1c4fde3debdc9907f3f2da899b1bab823a02a932e7
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.69<!--END VER-->.
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 options[:t]
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
@@ -1,3 +1,3 @@
1
1
  module Doing
2
- VERSION = '1.0.70'
2
+ VERSION = '1.0.72'
3
3
  end
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: doing
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.70
4
+ version: 1.0.72
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra