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 +4 -4
- data/README.md +3 -3
- data/bin/doing +17 -4
- data/lib/doing/helpers.rb +1 -1
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +168 -17
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 64076f13651c82428d05f490870a3a8a2507dc783cfaeebe7658538523aaef25
|
4
|
+
data.tar.gz: 691df494543c3ff71911a4ca87135adba9504fb445d51180e9c5914372c5d35b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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 (`$
|
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 '
|
260
|
+
long_desc 'List all entries and select with typeahead fuzzy matching.
|
261
261
|
|
262
|
-
|
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)://)
|
95
|
+
gsub(%r{(?mi)((http|https)://)([\w\-_]+(\.[\w\-_]+)+)([\w\-.,@?^=%&:/~+#]*[\w\-@^=%&/~+#])?}) 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
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 ?
|
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
|
-
|
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"))}
|
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!
|
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!
|
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!
|
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
|
-
|
1314
|
-
|
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
|
-
|
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
|
-
|
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.
|
1809
|
+
if (item.key?('note') && !item['note'].empty?) && @config[:include_notes]
|
1660
1810
|
note_lines = item['note'].delete_if do |line|
|
1661
|
-
|
1662
|
-
|
1663
|
-
|
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.
|
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.
|
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-
|
11
|
+
date: 2021-09-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|