doing 1.0.76 → 1.0.81
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +12 -5
- data/bin/doing +23 -6
- data/lib/doing/helpers.rb +1 -1
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +229 -51
- data/lib/doing.rb +3 -2
- data/lib/helpers/fuzzyfilefinder +0 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 860b1b1c3f8f1ffd33fc344cabb37a7a060cdcb80d54f65a8f3598450b4c22cf
|
4
|
+
data.tar.gz: 83767946e503d9bd2515aa99a7e11de87c2910f48a1b2c6e5a745c9e7dada0be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d78baf84b0bef668b7c877e908b6f1ee4ea05ce3914735f8d2e069abed215dfe5c9fc1fc3a9eb0dc5e4f5e251420d2454fb14a1b7465a5df8cf688e4b16b24ef
|
7
|
+
data.tar.gz: b38cf4b873baeb35ecab15ef10b3c1b8be4a7d2e28f9b34511d30f50def0b47ef127829bc5755bb0b2ba424183959dd009a222958df03ee8e54680e2ce267ab6
|
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.79<!--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
|
|
@@ -619,12 +619,15 @@ Now you can run `doing import --type timing -s SECTION PATH`, where SECTION is t
|
|
619
619
|
|
620
620
|
#### Interactive Usage
|
621
621
|
|
622
|
-
|
622
|
+
FuzzyFileFinder (`fzf`) is included with doing (<https://github.com/junegunn/fzf>), and 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
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. Use Control-A to select all visible entries.
|
624
|
+
To use the menu, type a search string or use the arrow keys to navigate up and down. Tip: searching is "fuzzy" by default, but you can start your search with a single quote (`'`) to force an exact match. 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. Use Control-A to select all visible entries.
|
625
625
|
|
626
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 the selected entries on one page in your text 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
627
|
|
628
|
+
If no actions are specified, an interactive menu of options will be
|
629
|
+
presented.
|
630
|
+
|
628
631
|
Run `doing help select` for a list of options:
|
629
632
|
|
630
633
|
-a, --archive - Archive selected items
|
@@ -634,7 +637,11 @@ Run `doing help select` for a list of options:
|
|
634
637
|
-f, --finish - Add @done with current time to selected item(s)
|
635
638
|
--flag - Add flag to selected item(s)
|
636
639
|
-m, --move=SECTION - Move selected items to section (default: none)
|
640
|
+
-o, --output=FORMAT - Output format for export (doing|taskpaper|csv|html|json|template|timeline) (default: none)
|
641
|
+
-q, --query=QUERY - Initial search query for filtering (default: none)
|
642
|
+
-r, --remove - Reverse -c, -f, --flag, and -t (remove instead of adding)
|
637
643
|
-s, --section=SECTION - Select from a specific section (default: none)
|
644
|
+
--save_to=FILE - Save selected entries to file using --output format (default: none)
|
638
645
|
-t, --tag=TAG - Tag selected entries (default: none)
|
639
646
|
|
640
647
|
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.
|
data/bin/doing
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
1
|
+
#!/usr/bin/env ruby -W1
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
$LOAD_PATH.unshift File.join(__dir__, '..', 'lib')
|
@@ -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,18 @@ 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 'Perform action without confirmation'
|
305
|
+
c.switch %i[force], negatable: false, default_value: false
|
306
|
+
|
307
|
+
c.desc 'Save selected entries to file using --output format'
|
308
|
+
c.arg_name 'FILE'
|
309
|
+
c.flag %i[save_to]
|
310
|
+
|
311
|
+
c.desc 'Output format for export (doing|taskpaper|csv|html|json|template|timeline)'
|
312
|
+
c.arg_name 'FORMAT'
|
313
|
+
c.flag %i[o output], must_match: /^(?:doing|taskpaper|html|csv|json|template|timeline)$/i
|
314
|
+
|
297
315
|
c.action do |_global_options, options, args|
|
298
|
-
section = options[:section] || 'All'
|
299
|
-
edit = options[:editor]
|
300
316
|
wwid.interactive(options)
|
301
317
|
end
|
302
318
|
end
|
@@ -1293,7 +1309,8 @@ desc 'Select a section to display from a menu'
|
|
1293
1309
|
command :choose do |c|
|
1294
1310
|
c.action do |_global_options, _options, _args|
|
1295
1311
|
section = wwid.choose_section
|
1296
|
-
|
1312
|
+
|
1313
|
+
puts wwid.list_section({ section: section.cap_first, count: 0 }) if section
|
1297
1314
|
end
|
1298
1315
|
end
|
1299
1316
|
|
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
|
@@ -757,7 +757,7 @@ class WWID
|
|
757
757
|
all_items.concat(@content[section]['items'].dup) if @content.key?(section)
|
758
758
|
end
|
759
759
|
|
760
|
-
if opt[:tag]
|
760
|
+
if opt[:tag]&.length
|
761
761
|
all_items.select! { |item| item.has_tags?(opt[:tag], opt[:tag_bool]) }
|
762
762
|
elsif opt[:search]&.length
|
763
763
|
all_items.select! { |item| item.matches_search?(opt[:search]) }
|
@@ -766,6 +766,23 @@ 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: 'Make a selection: ', multiple: false, fzf_args: [])
|
775
|
+
fzf = File.join(File.dirname(__FILE__), '../helpers/fuzzyfilefinder')
|
776
|
+
fzf_args << '-1'
|
777
|
+
fzf_args << %(--prompt "#{prompt}")
|
778
|
+
fzf_args << '--multi' if multiple
|
779
|
+
header = "esc: cancel,#{multiple ? ' tab: multi-select, ctrl-a: select all,' : ''} return: confirm"
|
780
|
+
fzf_args << %(--header "#{header}")
|
781
|
+
res = `echo #{Shellwords.escape(options.join("\n"))}|#{fzf} #{fzf_args.join(' ')}`
|
782
|
+
return false if res.strip.size.zero?
|
783
|
+
|
784
|
+
res
|
785
|
+
end
|
769
786
|
|
770
787
|
##
|
771
788
|
## @brief Display an interactive menu of entries
|
@@ -773,7 +790,7 @@ class WWID
|
|
773
790
|
## @param opt (Hash) Additional options
|
774
791
|
##
|
775
792
|
def interactive(opt = {})
|
776
|
-
|
793
|
+
fzf = File.join(File.dirname(__FILE__), '../helpers/fuzzyfilefinder')
|
777
794
|
|
778
795
|
section = opt[:section] ? guess_section(opt[:section]) : 'All'
|
779
796
|
|
@@ -795,7 +812,7 @@ class WWID
|
|
795
812
|
') ',
|
796
813
|
item['date'],
|
797
814
|
' | ',
|
798
|
-
item['title']
|
815
|
+
item['title']
|
799
816
|
]
|
800
817
|
if opt[:section] =~ /^all/i
|
801
818
|
out.concat([
|
@@ -806,8 +823,15 @@ class WWID
|
|
806
823
|
end
|
807
824
|
out.join('')
|
808
825
|
end
|
809
|
-
|
810
|
-
|
826
|
+
fzf_args = [
|
827
|
+
%(--header="Arrows: navigate, tab: mark for selection, ctrl-a: select all, enter: commit"),
|
828
|
+
%(--prompt="Select entries to act on > "),
|
829
|
+
'-1',
|
830
|
+
'-m',
|
831
|
+
'--bind ctrl-a:select-all',
|
832
|
+
%(-q "#{opt[:query]}")
|
833
|
+
]
|
834
|
+
res = `echo #{Shellwords.escape(options.join("\n"))}|#{fzf} #{fzf_args.join(' ')}`
|
811
835
|
selected = []
|
812
836
|
res.split(/\n/).each do |item|
|
813
837
|
idx = item.match(/^(\d+)\)/)[1].to_i
|
@@ -819,10 +843,81 @@ class WWID
|
|
819
843
|
return
|
820
844
|
end
|
821
845
|
|
846
|
+
actions = %i[editor delete tag flag finish cancel tag archive output save_to]
|
847
|
+
has_action = false
|
848
|
+
actions.each do |a|
|
849
|
+
if opt[a]
|
850
|
+
has_action = true
|
851
|
+
break
|
852
|
+
end
|
853
|
+
end
|
854
|
+
|
855
|
+
unless has_action
|
856
|
+
choice = choose_from([
|
857
|
+
'add tag',
|
858
|
+
'remove tag',
|
859
|
+
'cancel',
|
860
|
+
'delete',
|
861
|
+
'finish',
|
862
|
+
'flag',
|
863
|
+
'archive',
|
864
|
+
'move',
|
865
|
+
'edit',
|
866
|
+
'output formatted'
|
867
|
+
],
|
868
|
+
prompt: 'What do you want to do with the selected items? > ',
|
869
|
+
multiple: true,
|
870
|
+
fzf_args: ['--height=60%', '--tac', '--no-sort'])
|
871
|
+
return unless choice
|
872
|
+
|
873
|
+
to_do = choice.strip.split(/\n/)
|
874
|
+
to_do.each do |action|
|
875
|
+
case action
|
876
|
+
when /(add|remove) tag/
|
877
|
+
type = action =~ /^add/ ? 'add' : 'remove'
|
878
|
+
if opt[:tag]
|
879
|
+
warn "'add tag' and 'remove tag' can not be used together"
|
880
|
+
Process.exit 1
|
881
|
+
end
|
882
|
+
print "#{colors['yellow']}Tag to #{type}: #{colors['reset']}"
|
883
|
+
tag = STDIN.gets
|
884
|
+
return if tag =~ /^ *$/
|
885
|
+
opt[:tag] = tag.strip.sub(/^@/, '')
|
886
|
+
opt[:remove] = true if type == 'remove'
|
887
|
+
when /output formatted/
|
888
|
+
output_format = choose_from(%w[doing taskpaper json timeline html csv].sort, prompt: 'Which output format? > ', fzf_args: ['--height=60%', '--tac', '--no-sort'])
|
889
|
+
return if tag =~ /^ *$/
|
890
|
+
opt[:output] = output_format.strip
|
891
|
+
res = opt[:force] ? false : yn('Save to file?', default_response: 'n')
|
892
|
+
if res
|
893
|
+
print "#{colors['yellow']}File path/name: #{colors['reset']}"
|
894
|
+
filename = STDIN.gets.strip
|
895
|
+
return if filename.empty?
|
896
|
+
opt[:save_to] = filename
|
897
|
+
end
|
898
|
+
when /archive/
|
899
|
+
opt[:archive] = true
|
900
|
+
when /delete/
|
901
|
+
opt[:delete] = true
|
902
|
+
when /edit/
|
903
|
+
opt[:editor] = true
|
904
|
+
when /finish/
|
905
|
+
opt[:finish] = true
|
906
|
+
when /cancel/
|
907
|
+
opt[:cancel] = true
|
908
|
+
when /move/
|
909
|
+
section = choose_section.strip
|
910
|
+
opt[:move] = section.strip unless section =~ /^ *$/
|
911
|
+
when /flag/
|
912
|
+
opt[:flag] = true
|
913
|
+
end
|
914
|
+
end
|
915
|
+
end
|
916
|
+
|
822
917
|
if opt[:delete]
|
823
|
-
res = yn("Delete #{selected.size} items?", default_response: 'y')
|
918
|
+
res = opt[:force] ? true : yn("Delete #{selected.size} items?", default_response: 'y')
|
824
919
|
if res
|
825
|
-
selected.each {|item| delete_item(item) }
|
920
|
+
selected.each { |item| delete_item(item) }
|
826
921
|
write(@doing_file)
|
827
922
|
end
|
828
923
|
return
|
@@ -830,17 +925,35 @@ class WWID
|
|
830
925
|
|
831
926
|
if opt[:flag]
|
832
927
|
tag = @config['marker_tag'] || 'flagged'
|
833
|
-
selected.map!
|
928
|
+
selected.map! do |item|
|
929
|
+
if opt[:remove]
|
930
|
+
untag_item(item, tag)
|
931
|
+
else
|
932
|
+
tag_item(item, tag, date: false)
|
933
|
+
end
|
934
|
+
end
|
834
935
|
end
|
835
936
|
|
836
937
|
if opt[:finish] || opt[:cancel]
|
837
938
|
tag = 'done'
|
838
|
-
selected.map!
|
939
|
+
selected.map! do |item|
|
940
|
+
if opt[:remove]
|
941
|
+
untag_item(item, tag)
|
942
|
+
else
|
943
|
+
tag_item(item, tag, date: !opt[:cancel])
|
944
|
+
end
|
945
|
+
end
|
839
946
|
end
|
840
947
|
|
841
948
|
if opt[:tag]
|
842
949
|
tag = opt[:tag]
|
843
|
-
selected.map!
|
950
|
+
selected.map! do |item|
|
951
|
+
if opt[:remove]
|
952
|
+
untag_item(item, tag)
|
953
|
+
else
|
954
|
+
tag_item(item, tag, date: false)
|
955
|
+
end
|
956
|
+
end
|
844
957
|
end
|
845
958
|
|
846
959
|
if opt[:archive] || opt[:move]
|
@@ -891,6 +1004,43 @@ class WWID
|
|
891
1004
|
|
892
1005
|
write(@doing_file)
|
893
1006
|
end
|
1007
|
+
|
1008
|
+
if opt[:output]
|
1009
|
+
selected.map! do |item|
|
1010
|
+
item['title'] = "#{item['title']} @project(#{item['section']})"
|
1011
|
+
item
|
1012
|
+
end
|
1013
|
+
|
1014
|
+
@content = { 'Export' => { 'original' => 'Export:', 'items' => selected } }
|
1015
|
+
options = { section: 'Export' }
|
1016
|
+
|
1017
|
+
case opt[:output]
|
1018
|
+
when /doing/
|
1019
|
+
options[:template] = '- %date | %title%note'
|
1020
|
+
when /taskpaper/
|
1021
|
+
options[:template] = '- %title @date(%date)%note'
|
1022
|
+
else
|
1023
|
+
options[:output] = opt[:output]
|
1024
|
+
end
|
1025
|
+
|
1026
|
+
output = list_section(options)
|
1027
|
+
|
1028
|
+
if opt[:save_to]
|
1029
|
+
file = File.expand_path(opt[:save_to])
|
1030
|
+
if File.exist?(file)
|
1031
|
+
# Create a backup copy for the undo command
|
1032
|
+
FileUtils.cp(file, "#{file}~")
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
File.open(file, 'w+') do |f|
|
1036
|
+
f.puts output
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
@results.push("Export saved to #{file}")
|
1040
|
+
else
|
1041
|
+
puts output
|
1042
|
+
end
|
1043
|
+
end
|
894
1044
|
end
|
895
1045
|
|
896
1046
|
##
|
@@ -952,7 +1102,6 @@ class WWID
|
|
952
1102
|
count = (opt[:count]).zero? ? items.length : opt[:count]
|
953
1103
|
items.map! do |item|
|
954
1104
|
break if idx == count
|
955
|
-
|
956
1105
|
finished = opt[:unfinished] && item.has_tags?('done', :and)
|
957
1106
|
tag_match = opt[:tag].nil? || opt[:tag].empty? ? true : item.has_tags?(opt[:tag], opt[:tag_bool])
|
958
1107
|
search_match = opt[:search].nil? || opt[:search].empty? ? true : item.matches_search?(opt[:search])
|
@@ -1072,6 +1221,33 @@ class WWID
|
|
1072
1221
|
@content[section]['items'] = section_items
|
1073
1222
|
end
|
1074
1223
|
|
1224
|
+
##
|
1225
|
+
## @brief Remove a tag on an item from the index
|
1226
|
+
##
|
1227
|
+
## @param old_item (Item) The item to tag
|
1228
|
+
## @param tag (string) The tag to remove
|
1229
|
+
##
|
1230
|
+
def untag_item(old_item, tags)
|
1231
|
+
title = old_item['title'].dup
|
1232
|
+
if tags.is_a? ::String
|
1233
|
+
tags = tags.split(/ *, */).map {|t| t.strip.gsub(/\*/,'[^ (]*') }
|
1234
|
+
end
|
1235
|
+
|
1236
|
+
tags.each do |tag|
|
1237
|
+
if title =~ /@#{tag}/
|
1238
|
+
title.chomp!
|
1239
|
+
title.gsub!(/ +@#{tag}(\(.*?\))?/, '')
|
1240
|
+
new_item = old_item.dup
|
1241
|
+
new_item['title'] = title
|
1242
|
+
update_item(old_item, new_item)
|
1243
|
+
return new_item
|
1244
|
+
else
|
1245
|
+
@results.push(%(Item isn't tagged @#{tag}: "#{title}" in #{old_item['section']}))
|
1246
|
+
return old_item
|
1247
|
+
end
|
1248
|
+
end
|
1249
|
+
end
|
1250
|
+
|
1075
1251
|
##
|
1076
1252
|
## @brief Tag an item from the index
|
1077
1253
|
##
|
@@ -1079,23 +1255,29 @@ class WWID
|
|
1079
1255
|
## @param tag (string) The tag to apply
|
1080
1256
|
## @param date (Boolean) Include timestamp?
|
1081
1257
|
##
|
1082
|
-
def tag_item(old_item,
|
1258
|
+
def tag_item(old_item, tags, remove: false, date: false)
|
1083
1259
|
title = old_item['title'].dup
|
1260
|
+
if tags.is_a? ::String
|
1261
|
+
tags = tags.split(/ *, */).map(&:strip)
|
1262
|
+
end
|
1263
|
+
|
1084
1264
|
done_date = Time.now
|
1085
|
-
|
1086
|
-
title
|
1087
|
-
|
1088
|
-
|
1265
|
+
tags.each do |tag|
|
1266
|
+
if title !~ /@#{tag}/
|
1267
|
+
title.chomp!
|
1268
|
+
if date
|
1269
|
+
title += " @#{tag}(#{done_date.strftime('%F %R')})"
|
1270
|
+
else
|
1271
|
+
title += " @#{tag}"
|
1272
|
+
end
|
1273
|
+
new_item = old_item.dup
|
1274
|
+
new_item['title'] = title
|
1275
|
+
update_item(old_item, new_item)
|
1276
|
+
return new_item
|
1089
1277
|
else
|
1090
|
-
|
1278
|
+
@results.push(%(Item already @#{tag}: "#{title}" in #{old_item['section']}))
|
1279
|
+
return old_item
|
1091
1280
|
end
|
1092
|
-
new_item = old_item.dup
|
1093
|
-
new_item['title'] = title
|
1094
|
-
update_item(old_item, new_item)
|
1095
|
-
return new_item
|
1096
|
-
else
|
1097
|
-
@results.push(%(Item already @#{tag}: "#{title}" in #{old_item['section']}))
|
1098
|
-
return old_item
|
1099
1281
|
end
|
1100
1282
|
end
|
1101
1283
|
|
@@ -1309,10 +1491,10 @@ class WWID
|
|
1309
1491
|
if File.exist?(file)
|
1310
1492
|
# Create a backup copy for the undo command
|
1311
1493
|
FileUtils.cp(file, "#{file}~")
|
1494
|
+
end
|
1312
1495
|
|
1313
|
-
|
1314
|
-
|
1315
|
-
end
|
1496
|
+
File.open(file, 'w+') do |f|
|
1497
|
+
f.puts output
|
1316
1498
|
end
|
1317
1499
|
|
1318
1500
|
if @config.key?('run_after')
|
@@ -1344,14 +1526,8 @@ class WWID
|
|
1344
1526
|
## @return (String) The selected section name
|
1345
1527
|
##
|
1346
1528
|
def choose_section
|
1347
|
-
sections.
|
1348
|
-
|
1349
|
-
end
|
1350
|
-
print "#{colors['green']}> #{colors['default']}"
|
1351
|
-
num = STDIN.gets
|
1352
|
-
return false if num =~ /^[a-z ]*$/i
|
1353
|
-
|
1354
|
-
sections[num.to_i - 1]
|
1529
|
+
choice = choose_from(sections.sort, prompt: 'Choose a section > ', fzf_args: ['--height=60%'])
|
1530
|
+
choice ? choice.strip : choice
|
1355
1531
|
end
|
1356
1532
|
|
1357
1533
|
##
|
@@ -1369,14 +1545,8 @@ class WWID
|
|
1369
1545
|
## @return (String) The selected view name
|
1370
1546
|
##
|
1371
1547
|
def choose_view
|
1372
|
-
views.
|
1373
|
-
|
1374
|
-
end
|
1375
|
-
print '> '
|
1376
|
-
num = STDIN.gets
|
1377
|
-
return false if num =~ /^[a-z ]*$/i
|
1378
|
-
|
1379
|
-
views[num.to_i - 1]
|
1548
|
+
choice = choose_from(views.sort, prompt: 'Choose a view > ', fzf_args: ['--height=60%'])
|
1549
|
+
choice ? choice.strip : choice
|
1380
1550
|
end
|
1381
1551
|
|
1382
1552
|
##
|
@@ -1526,13 +1696,16 @@ class WWID
|
|
1526
1696
|
note ||= ''
|
1527
1697
|
|
1528
1698
|
tags = []
|
1699
|
+
attributes = {}
|
1529
1700
|
skip_tags = %w[meanwhile done cancelled flagged]
|
1530
1701
|
i['title'].scan(/@([^(\s]+)(?:\((.*?)\))?/).each do |tag|
|
1531
1702
|
tags.push(tag[0]) unless skip_tags.include?(tag[0])
|
1703
|
+
attributes[tag[0]] = tag[1] if tag[1]
|
1532
1704
|
end
|
1705
|
+
|
1533
1706
|
if opt[:output] == 'json'
|
1534
1707
|
|
1535
|
-
|
1708
|
+
i = {
|
1536
1709
|
date: i['date'],
|
1537
1710
|
end_date: end_date,
|
1538
1711
|
title: title.strip, #+ " #{note}"
|
@@ -1541,6 +1714,10 @@ class WWID
|
|
1541
1714
|
tags: tags
|
1542
1715
|
}
|
1543
1716
|
|
1717
|
+
attributes.each { |attr, val| i[attr.to_sym] = val }
|
1718
|
+
|
1719
|
+
items_out << i
|
1720
|
+
|
1544
1721
|
elsif opt[:output] == 'timeline'
|
1545
1722
|
new_item = {
|
1546
1723
|
'id' => index + 1,
|
@@ -1644,7 +1821,7 @@ class WWID
|
|
1644
1821
|
|
1645
1822
|
totals = opt[:totals] ? tag_times('html', opt[:sort_tags]) : ''
|
1646
1823
|
engine = Haml::Engine.new(template)
|
1647
|
-
|
1824
|
+
out = engine.render(Object.new,
|
1648
1825
|
{ :@items => items_out, :@page_title => page_title, :@style => style, :@totals => totals })
|
1649
1826
|
else
|
1650
1827
|
items.each do |item|
|
@@ -1656,11 +1833,12 @@ class WWID
|
|
1656
1833
|
reset = ''
|
1657
1834
|
end
|
1658
1835
|
|
1659
|
-
if (item.
|
1836
|
+
if (item.key?('note') && !item['note'].empty?) && @config[:include_notes]
|
1660
1837
|
note_lines = item['note'].delete_if do |line|
|
1661
|
-
|
1662
|
-
|
1663
|
-
|
1838
|
+
line =~ /^\s*$/
|
1839
|
+
end
|
1840
|
+
note_lines.map! { |line| "\t\t#{line.sub(/^\t*/, '').sub(/^-/, '—')} " }
|
1841
|
+
if opt[:wrap_width]&.positive?
|
1664
1842
|
width = opt[:wrap_width]
|
1665
1843
|
note_lines.map! do |line|
|
1666
1844
|
line.strip.gsub(/(.{1,#{width}})(\s+|\Z)/, "\t\\1\n")
|
@@ -1673,7 +1851,7 @@ class WWID
|
|
1673
1851
|
output = opt[:template].dup
|
1674
1852
|
|
1675
1853
|
output.gsub!(/%[a-z]+/) do |m|
|
1676
|
-
if colors.
|
1854
|
+
if colors.key?(m.sub(/^%/, ''))
|
1677
1855
|
colors[m.sub(/^%/, '')]
|
1678
1856
|
else
|
1679
1857
|
m
|
data/lib/doing.rb
CHANGED
Binary file
|
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.81
|
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-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -168,6 +168,7 @@ files:
|
|
168
168
|
- lib/doing/helpers.rb
|
169
169
|
- lib/doing/version.rb
|
170
170
|
- lib/doing/wwid.rb
|
171
|
+
- lib/helpers/fuzzyfilefinder
|
171
172
|
- lib/templates/doing.css
|
172
173
|
- lib/templates/doing.haml
|
173
174
|
homepage: http://brettterpstra.com/project/doing/
|