doing 1.0.76 → 1.0.78

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 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