doing 1.0.76 → 1.0.78

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: 7b4ff1cca2f519037f002070068ed1267cd0083ae04e3b2b14e431bf8ee47baf
4
- data.tar.gz: da59f5f23f01b82a37e2ca56b552c26f638854597cc0f0beac21f30b2c1dd066
3
+ metadata.gz: 64076f13651c82428d05f490870a3a8a2507dc783cfaeebe7658538523aaef25
4
+ data.tar.gz: 691df494543c3ff71911a4ca87135adba9504fb445d51180e9c5914372c5d35b
5
5
  SHA512:
6
- metadata.gz: 550a05a4c6b0a432df01904d0cb2b78d7bdf9b41fa0ac8660e694f23b8f8384389395966cbb35cfbb6feb55ad248c39ec0010b8ca4e2626a892e029f553fb4dd
7
- data.tar.gz: e3b3d0789d9fba0f4cd63fcc1da2910d32a14142d25e367ede83fbee3956476991cfd74fc73f99642b1cf47c744c4e78765f6df75f3933ee2d1e619de8f2a733
6
+ metadata.gz: be005b1f54f3e170030f88a6df19567478985965b56bb50a842fe3d5c87d7afb750e165d4888da45466f3dba16205a94d1baf5cbfe723f0a18dffe960d944d00
7
+ data.tar.gz: 48e5cbec09157db3c974b5d0c242c394884a77f47ce5cf4e009b7dedf47daccba581709aefb534266415884ab71fad03c0a4d22385e3abc9d3e33576f3aaad17
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.75<!--END VER-->.
30
+ The current version of `doing` is <!--VER-->1.0.76<!--END VER-->.
31
31
 
32
32
  $ [sudo] gem install doing
33
33
 
@@ -268,7 +268,7 @@ You can add additional custom views. Just nest them under the `views` key (inden
268
268
  count: 5
269
269
  wrap_width: 0
270
270
  date_format: '%F %_I:%M%P'
271
- template: '%date | %title%note'
271
+ template: '%date | %title%note'
272
272
 
273
273
  The `section` key is the default section to pull entries from. Count and section can be overridden at runtime with the `-c` and `-s` flags. Setting `section` to `All` will combine all sections in the output.
274
274
 
@@ -505,7 +505,7 @@ You can include a `transform` section in the autotag config which contains pairs
505
505
  transform:
506
506
  - (\w+)-\d+:$1
507
507
 
508
- This creates a search pattern looking for a string of word characters followed by a hyphen and one or more digits, e.g. `@projecttag-12`. Do not include the @ symbol in the pattern. The replacement (`$1`) indicates that the first matched group (in parenthesis) should be used to generate the new tag, resulting in `@projecttag` being added to the entry.
508
+ This creates a search pattern looking for a string of word characters followed by a hyphen and one or more digits, e.g. `@projecttag-12`. Do not include the @ symbol in the pattern. The replacement (`$l`) indicates that the first matched group (in parenthesis) should be used to generate the new tag, resulting in `@projecttag` being added to the entry.
509
509
 
510
510
  ##### Annotating
511
511
 
data/bin/doing CHANGED
@@ -257,9 +257,9 @@ command :template do |c|
257
257
  end
258
258
 
259
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>
260
+ long_desc 'List all entries and select with typeahead fuzzy matching.
261
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.'
262
+ Multiple selections are allowed, hit tab to add the highlighted entry to the selection. Return processes the selected entries.'
263
263
  command :select do |c|
264
264
  c.desc 'Select from a specific section'
265
265
  c.arg_name 'SECTION'
@@ -269,6 +269,9 @@ command :select do |c|
269
269
  c.arg_name 'TAG'
270
270
  c.flag %i[t tag]
271
271
 
272
+ c.desc 'Reverse -c, -f, --flag, and -t (remove instead of adding)'
273
+ c.switch %i[r remove], negatable: false
274
+
272
275
  # c.desc 'Add @done to selected item(s), using start time of next item as the finish time'
273
276
  # c.switch %i[a auto], negatable: false, default_value: false
274
277
 
@@ -279,6 +282,10 @@ command :select do |c|
279
282
  c.arg_name 'SECTION'
280
283
  c.flag %i[m move]
281
284
 
285
+ c.desc 'Initial search query for filtering'
286
+ c.arg_name 'QUERY'
287
+ c.flag %i[q query]
288
+
282
289
  c.desc 'Cancel selected items (add @done without timestamp)'
283
290
  c.switch %i[c cancel], negatable: false, default_value: false
284
291
 
@@ -294,9 +301,15 @@ command :select do |c|
294
301
  c.desc 'Add flag to selected item(s)'
295
302
  c.switch %i[flag], negatable: false, default_value: false
296
303
 
304
+ c.desc 'Save selected entries to file using --output format'
305
+ c.arg_name 'FILE'
306
+ c.flag %i[save_to]
307
+
308
+ c.desc 'Output format for export (doing|taskpaper|csv|html|json|template|timeline)'
309
+ c.arg_name 'FORMAT'
310
+ c.flag %i[o output], must_match: /^(?:doing|taskpaper|html|csv|json|template|timeline)$/i
311
+
297
312
  c.action do |_global_options, options, args|
298
- section = options[:section] || 'All'
299
- edit = options[:editor]
300
313
  wwid.interactive(options)
301
314
  end
302
315
  end
data/lib/doing/helpers.rb CHANGED
@@ -92,7 +92,7 @@ class ::String
92
92
  def link_urls(opt = {})
93
93
  opt[:format] ||= :html
94
94
  if opt[:format] == :html
95
- gsub(%r{(?mi)((http|https)://)?([\w\-_]+(\.[\w\-_]+)+)([\w\-.,@?^=%&amp;:/~+#]*[\w\-@^=%&amp;/~+#])?}) do |_match|
95
+ gsub(%r{(?mi)((http|https)://)([\w\-_]+(\.[\w\-_]+)+)([\w\-.,@?^=%&amp;:/~+#]*[\w\-@^=%&amp;/~+#])?}) do |_match|
96
96
  m = Regexp.last_match
97
97
  proto = m[1].nil? ? 'http://' : ''
98
98
  %(<a href="#{proto}#{m[0]}" title="Link to #{m[0]}">[#{m[3]}]</a>)
data/lib/doing/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Doing
2
- VERSION = '1.0.76'
2
+ VERSION = '1.0.78'
3
3
  end
data/lib/doing/wwid.rb CHANGED
@@ -462,7 +462,7 @@ class WWID
462
462
  ## @return (Bool) yes or no
463
463
  ##
464
464
  def yn(question, default_response: false)
465
- default = default_response ? 'y' : 'n'
465
+ default = default_response ? default_response : 'n'
466
466
 
467
467
  # if this isn't an interactive shell, answer default
468
468
  return default.downcase == 'y' unless $stdout.isatty
@@ -766,6 +766,22 @@ class WWID
766
766
  all_items.max_by { |item| item['date'] }
767
767
  end
768
768
 
769
+ ##
770
+ ## @brief Generate a menu of options and allow user selection
771
+ ##
772
+ ## @return (String) The selected option
773
+ ##
774
+ def choose_from(options, prompt)
775
+ puts prompt
776
+ options.each_with_index do |section, i|
777
+ puts format('% 3d: %s', i + 1, section)
778
+ end
779
+ print "#{colors['green']}> #{colors['default']}"
780
+ num = STDIN.gets
781
+ return false if num =~ /^[a-z ]*$/i
782
+
783
+ options[num.to_i - 1]
784
+ end
769
785
 
770
786
  ##
771
787
  ## @brief Display an interactive menu of entries
@@ -773,7 +789,7 @@ class WWID
773
789
  ## @param opt (Hash) Additional options
774
790
  ##
775
791
  def interactive(opt = {})
776
- exit_now! "Select command requires that fzf be installed" unless exec_available('fzf')
792
+ fzf = File.join(File.dirname(__FILE__), '../helpers/fuzzyfilefinder')
777
793
 
778
794
  section = opt[:section] ? guess_section(opt[:section]) : 'All'
779
795
 
@@ -807,7 +823,7 @@ class WWID
807
823
  out.join('')
808
824
  end
809
825
 
810
- res = `echo #{Shellwords.escape(options.join("\n"))}|fzf -m --bind ctrl-a:select-all`
826
+ res = `echo #{Shellwords.escape(options.join("\n"))}|#{fzf} -m --bind ctrl-a:select-all -q "#{opt[:query]}"`
811
827
  selected = []
812
828
  res.split(/\n/).each do |item|
813
829
  idx = item.match(/^(\d+)\)/)[1].to_i
@@ -819,6 +835,59 @@ class WWID
819
835
  return
820
836
  end
821
837
 
838
+ unless opt[:delete] || opt[:flag] || opt[:finish] || opt[:cancel] || opt[:tag] || opt[:archive] || opt[:output] || opt[:save_to]
839
+ action = choose_from(
840
+ [
841
+ "add tag",
842
+ "remove tag",
843
+ "archive",
844
+ "cancel",
845
+ "delete",
846
+ "edit",
847
+ "finish",
848
+ "flag",
849
+ "move",
850
+ "output format"
851
+ ],
852
+ 'What do you want to do with the selected items?')
853
+ case action
854
+ when /(add|remove) tag/
855
+ print "Enter tag: "
856
+ tag = STDIN.gets
857
+ return if tag =~ /^ *$/
858
+ opt[:tag] = tag.strip
859
+ opt[:remove] = true if action =~ /remove tag/
860
+ when /output format/
861
+ output_format = choose_from(%w[doing taskpaper json timeline html csv], 'Which output format?')
862
+ return if tag =~ /^ *$/
863
+ opt[:output] = output_format.strip
864
+ res = yn("Save to file?", default_response: 'n')
865
+ if res
866
+ print "File path/name: "
867
+ filename = STDIN.gets.strip
868
+ return if filename.empty?
869
+ opt[:save_to] = filename
870
+ end
871
+ when /archive/
872
+ opt[:archive] = true
873
+ when /delete/
874
+ opt[:delete] = true
875
+ when /edit/
876
+ opt[:editor] = true
877
+ when /finish/
878
+ opt[:finish] = true
879
+ when /cancel/
880
+ opt[:cancel] = true
881
+ when /move/
882
+ section = choose_section.strip
883
+ return if section =~ /^ *$/
884
+ opt[:move] = section.strip
885
+ when /flag/
886
+ opt[:flag] = true
887
+ end
888
+ end
889
+
890
+
822
891
  if opt[:delete]
823
892
  res = yn("Delete #{selected.size} items?", default_response: 'y')
824
893
  if res
@@ -830,17 +899,35 @@ class WWID
830
899
 
831
900
  if opt[:flag]
832
901
  tag = @config['marker_tag'] || 'flagged'
833
- selected.map! {|item| tag_item(item, tag, date: false) }
902
+ selected.map! do |item|
903
+ if opt[:remove]
904
+ untag_item(item, tag)
905
+ else
906
+ tag_item(item, tag, date: false)
907
+ end
908
+ end
834
909
  end
835
910
 
836
911
  if opt[:finish] || opt[:cancel]
837
912
  tag = 'done'
838
- selected.map! {|item| tag_item(item, tag, date: !opt[:cancel])}
913
+ selected.map! do |item|
914
+ if opt[:remove]
915
+ untag_item(item, tag)
916
+ else
917
+ tag_item(item, tag, date: !opt[:cancel])
918
+ end
919
+ end
839
920
  end
840
921
 
841
922
  if opt[:tag]
842
923
  tag = opt[:tag]
843
- selected.map! {|item| tag_item(item, tag, date: false)}
924
+ selected.map! do |item|
925
+ if opt[:remove]
926
+ untag_item(item, tag)
927
+ else
928
+ tag_item(item, tag, date: false)
929
+ end
930
+ end
844
931
  end
845
932
 
846
933
  if opt[:archive] || opt[:move]
@@ -891,6 +978,40 @@ class WWID
891
978
 
892
979
  write(@doing_file)
893
980
  end
981
+
982
+ if opt[:output]
983
+ selected.map! do |item|
984
+ item['title'] = "#{item['title']} @project(#{item['section']})"
985
+ item
986
+ end
987
+
988
+ @content = {'Export' => {'original' => 'Export:', 'items' => selected}}
989
+ options = {section: 'Export'}
990
+
991
+ if opt[:output] !~ /(doing|taskpaper)/
992
+ options[:output] = opt[:output]
993
+ else
994
+ options[:template] = '- %date | %title%note'
995
+ end
996
+
997
+ output = list_section(options)
998
+
999
+ if opt[:save_to]
1000
+ file = File.expand_path(opt[:save_to])
1001
+ if File.exist?(file)
1002
+ # Create a backup copy for the undo command
1003
+ FileUtils.cp(file, "#{file}~")
1004
+ end
1005
+
1006
+ File.open(file, 'w+') do |f|
1007
+ f.puts output
1008
+ end
1009
+
1010
+ @results.push("Export saved to #{file}")
1011
+ else
1012
+ puts output
1013
+ end
1014
+ end
894
1015
  end
895
1016
 
896
1017
  ##
@@ -1072,6 +1193,28 @@ class WWID
1072
1193
  @content[section]['items'] = section_items
1073
1194
  end
1074
1195
 
1196
+ ##
1197
+ ## @brief Remove a tag on an item from the index
1198
+ ##
1199
+ ## @param old_item (Item) The item to tag
1200
+ ## @param tag (string) The tag to remove
1201
+ ##
1202
+ def untag_item(old_item, tag)
1203
+ title = old_item['title'].dup
1204
+
1205
+ if title =~ /@#{tag}/
1206
+ title.chomp!
1207
+ title.gsub!(/ +@#{tag}(\(.*?\))?/, '')
1208
+ new_item = old_item.dup
1209
+ new_item['title'] = title
1210
+ update_item(old_item, new_item)
1211
+ return new_item
1212
+ else
1213
+ @results.push(%(Item isn't tagged @#{tag}: "#{title}" in #{old_item['section']}))
1214
+ return old_item
1215
+ end
1216
+ end
1217
+
1075
1218
  ##
1076
1219
  ## @brief Tag an item from the index
1077
1220
  ##
@@ -1079,7 +1222,7 @@ class WWID
1079
1222
  ## @param tag (string) The tag to apply
1080
1223
  ## @param date (Boolean) Include timestamp?
1081
1224
  ##
1082
- def tag_item(old_item, tag, date: false)
1225
+ def tag_item(old_item, tag, remove: false, date: false)
1083
1226
  title = old_item['title'].dup
1084
1227
  done_date = Time.now
1085
1228
  if title !~ /@#{tag}/
@@ -1309,10 +1452,10 @@ class WWID
1309
1452
  if File.exist?(file)
1310
1453
  # Create a backup copy for the undo command
1311
1454
  FileUtils.cp(file, "#{file}~")
1455
+ end
1312
1456
 
1313
- File.open(file, 'w+') do |f|
1314
- f.puts output
1315
- end
1457
+ File.open(file, 'w+') do |f|
1458
+ f.puts output
1316
1459
  end
1317
1460
 
1318
1461
  if @config.key?('run_after')
@@ -1526,13 +1669,16 @@ class WWID
1526
1669
  note ||= ''
1527
1670
 
1528
1671
  tags = []
1672
+ attributes = {}
1529
1673
  skip_tags = %w[meanwhile done cancelled flagged]
1530
1674
  i['title'].scan(/@([^(\s]+)(?:\((.*?)\))?/).each do |tag|
1531
1675
  tags.push(tag[0]) unless skip_tags.include?(tag[0])
1676
+ attributes[tag[0]] = tag[1] if tag[1]
1532
1677
  end
1678
+
1533
1679
  if opt[:output] == 'json'
1534
1680
 
1535
- items_out << {
1681
+ i = {
1536
1682
  date: i['date'],
1537
1683
  end_date: end_date,
1538
1684
  title: title.strip, #+ " #{note}"
@@ -1541,6 +1687,10 @@ class WWID
1541
1687
  tags: tags
1542
1688
  }
1543
1689
 
1690
+ attributes.each { |attr, val| i[attr.to_sym] = val }
1691
+
1692
+ items_out << i
1693
+
1544
1694
  elsif opt[:output] == 'timeline'
1545
1695
  new_item = {
1546
1696
  'id' => index + 1,
@@ -1644,7 +1794,7 @@ class WWID
1644
1794
 
1645
1795
  totals = opt[:totals] ? tag_times('html', opt[:sort_tags]) : ''
1646
1796
  engine = Haml::Engine.new(template)
1647
- puts engine.render(Object.new,
1797
+ out = engine.render(Object.new,
1648
1798
  { :@items => items_out, :@page_title => page_title, :@style => style, :@totals => totals })
1649
1799
  else
1650
1800
  items.each do |item|
@@ -1656,11 +1806,12 @@ class WWID
1656
1806
  reset = ''
1657
1807
  end
1658
1808
 
1659
- if (item.has_key?('note') && !item['note'].empty?) && @config[:include_notes]
1809
+ if (item.key?('note') && !item['note'].empty?) && @config[:include_notes]
1660
1810
  note_lines = item['note'].delete_if do |line|
1661
- line =~ /^\s*$/
1662
- end.map { |line| "\t\t" + line.sub(/^\t*/, '').sub(/^-/, '—') + ' ' }
1663
- if opt[:wrap_width] && opt[:wrap_width] > 0
1811
+ line =~ /^\s*$/
1812
+ end
1813
+ note_lines.map! { |line| "\t\t#{line.sub(/^\t*/, '').sub(/^-/, '—')} " }
1814
+ if opt[:wrap_width]&.positive?
1664
1815
  width = opt[:wrap_width]
1665
1816
  note_lines.map! do |line|
1666
1817
  line.strip.gsub(/(.{1,#{width}})(\s+|\Z)/, "\t\\1\n")
@@ -1673,7 +1824,7 @@ class WWID
1673
1824
  output = opt[:template].dup
1674
1825
 
1675
1826
  output.gsub!(/%[a-z]+/) do |m|
1676
- if colors.has_key?(m.sub(/^%/, ''))
1827
+ if colors.key?(m.sub(/^%/, ''))
1677
1828
  colors[m.sub(/^%/, '')]
1678
1829
  else
1679
1830
  m
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: doing
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.76
4
+ version: 1.0.78
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-16 00:00:00.000000000 Z
11
+ date: 2021-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake