doing 2.1.0pre → 2.1.4pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardoc/checksums +13 -9
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/CHANGELOG.md +42 -10
- data/Gemfile.lock +23 -1
- data/README.md +1 -1
- data/Rakefile +2 -0
- data/bin/doing +421 -156
- data/doc/Array.html +1 -1
- data/doc/Doing/Color.html +1 -1
- data/doc/Doing/Completion.html +1 -1
- data/doc/Doing/Configuration.html +81 -90
- data/doc/Doing/Errors/DoingNoTraceError.html +1 -1
- data/doc/Doing/Errors/DoingRuntimeError.html +1 -1
- data/doc/Doing/Errors/DoingStandardError.html +1 -1
- data/doc/Doing/Errors/EmptyInput.html +1 -1
- data/doc/Doing/Errors/NoResults.html +1 -1
- data/doc/Doing/Errors/PluginException.html +1 -1
- data/doc/Doing/Errors/UserCancelled.html +1 -1
- data/doc/Doing/Errors/WrongCommand.html +1 -1
- data/doc/Doing/Errors.html +1 -1
- data/doc/Doing/Hooks.html +1 -1
- data/doc/Doing/Item.html +84 -20
- data/doc/Doing/Items.html +35 -1
- data/doc/Doing/LogAdapter.html +1 -1
- data/doc/Doing/Note.html +1 -1
- data/doc/Doing/Pager.html +1 -1
- data/doc/Doing/Plugins.html +1 -1
- data/doc/Doing/Prompt.html +70 -18
- data/doc/Doing/Section.html +1 -1
- data/doc/Doing/Util.html +16 -4
- data/doc/Doing/WWID.html +27 -147
- data/doc/Doing.html +3 -3
- data/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
- data/doc/GLI/Commands.html +1 -1
- data/doc/GLI.html +1 -1
- data/doc/Hash.html +1 -1
- data/doc/Status.html +1 -1
- data/doc/String.html +344 -4
- data/doc/Symbol.html +1 -1
- data/doc/Time.html +70 -2
- data/doc/_index.html +125 -4
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +2 -2
- data/doc/index.html +2 -2
- data/doc/method_list.html +537 -193
- data/doc/top-level-namespace.html +2 -2
- data/doing.gemspec +2 -0
- data/doing.rdoc +276 -75
- data/lib/completion/doing.bash +20 -20
- data/lib/doing/boolean_term_parser.rb +86 -0
- data/lib/doing/configuration.rb +36 -19
- data/lib/doing/item.rb +102 -9
- data/lib/doing/items.rb +6 -0
- data/lib/doing/phrase_parser.rb +124 -0
- data/lib/doing/plugins/export/template_export.rb +29 -2
- data/lib/doing/prompt.rb +21 -11
- data/lib/doing/string.rb +47 -3
- data/lib/doing/string_chronify.rb +85 -0
- data/lib/doing/time.rb +32 -0
- data/lib/doing/util.rb +2 -5
- data/lib/doing/util_backup.rb +235 -0
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +224 -124
- data/lib/doing.rb +7 -0
- metadata +46 -2
data/lib/doing/wwid.rb
CHANGED
@@ -183,14 +183,32 @@ module Doing
|
|
183
183
|
|
184
184
|
date = nil
|
185
185
|
iso_rx = /\d{4}-\d\d-\d\d \d\d:\d\d/
|
186
|
-
|
186
|
+
watch_tags = [
|
187
|
+
'start(?:ed)?',
|
188
|
+
'beg[ia]n',
|
189
|
+
'done',
|
190
|
+
'finished',
|
191
|
+
'completed?',
|
192
|
+
'waiting',
|
193
|
+
'defer(?:red)?'
|
194
|
+
]
|
195
|
+
if @config['date_tags']
|
196
|
+
date_tags = @config['date_tags']
|
197
|
+
date_tags = date_tags.split(/ *, */) if date_tags.is_a?(String)
|
198
|
+
date_tags.map! do |tag|
|
199
|
+
tag.sub(/^@/, '').gsub(/\((?!\?:)(.*?)\)/, '(?:\1)').strip
|
200
|
+
end
|
201
|
+
watch_tags.concat(date_tags).uniq!
|
202
|
+
end
|
203
|
+
|
204
|
+
done_rx = /(?<=^| )@(?<tag>#{watch_tags.join('|')})\((?<date>.*?)\)/i
|
187
205
|
date_rx = /^(?:\s*- )?(?<date>.*?) \| (?=\S)/
|
188
206
|
|
189
207
|
title.gsub!(done_rx) do
|
190
208
|
m = Regexp.last_match
|
191
209
|
t = m['tag']
|
192
210
|
d = m['date']
|
193
|
-
parsed_date = d =~ date_rx ? Time.parse(d) : chronify(
|
211
|
+
parsed_date = d =~ date_rx ? Time.parse(d) : d.chronify(guess: :begin)
|
194
212
|
parsed_date.nil? ? m[0] : "@#{t}(#{parsed_date.strftime('%F %R')})"
|
195
213
|
end
|
196
214
|
|
@@ -200,7 +218,7 @@ module Doing
|
|
200
218
|
date = if d =~ iso_rx
|
201
219
|
Time.parse(d)
|
202
220
|
else
|
203
|
-
chronify(
|
221
|
+
d.chronify(guess: :begin)
|
204
222
|
end
|
205
223
|
title.sub!(date_rx, '').strip!
|
206
224
|
end
|
@@ -222,71 +240,6 @@ module Doing
|
|
222
240
|
[date, title, note]
|
223
241
|
end
|
224
242
|
|
225
|
-
##
|
226
|
-
## Converts input string into a Time object when input takes on the
|
227
|
-
## following formats:
|
228
|
-
## - interval format e.g. '1d2h30m', '45m' etc.
|
229
|
-
## - a semantic phrase e.g. 'yesterday 5:30pm'
|
230
|
-
## - a strftime e.g. '2016-03-15 15:32:04 PDT'
|
231
|
-
##
|
232
|
-
## @param input [String] String to chronify
|
233
|
-
##
|
234
|
-
## @return [DateTime] result
|
235
|
-
##
|
236
|
-
def chronify(input, future: false, guess: :begin)
|
237
|
-
now = Time.now
|
238
|
-
raise InvalidTimeExpression, "Invalid time expression #{input.inspect}" if input.to_s.strip == ''
|
239
|
-
|
240
|
-
secs_ago = if input.match(/^(\d+)$/)
|
241
|
-
# plain number, assume minutes
|
242
|
-
Regexp.last_match(1).to_i * 60
|
243
|
-
elsif (m = input.match(/^(?:(?<day>\d+)d)?(?:(?<hour>\d+)h)?(?:(?<min>\d+)m)?$/i))
|
244
|
-
# day/hour/minute format e.g. 1d2h30m
|
245
|
-
[[m['day'], 24 * 3600],
|
246
|
-
[m['hour'], 3600],
|
247
|
-
[m['min'], 60]].map { |qty, secs| qty ? (qty.to_i * secs) : 0 }.reduce(0, :+)
|
248
|
-
end
|
249
|
-
|
250
|
-
if secs_ago
|
251
|
-
now - secs_ago
|
252
|
-
else
|
253
|
-
Chronic.parse(input, { guess: guess, context: future ? :future : :past, ambiguous_time_range: 8 })
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
##
|
258
|
-
## Converts simple strings into seconds that can be added to a Time
|
259
|
-
## object
|
260
|
-
##
|
261
|
-
## @param qty [String] HH:MM or XX[dhm][[XXhm][XXm]] (1d2h30m, 45m,
|
262
|
-
## 1.5d, 1h20m, etc.)
|
263
|
-
##
|
264
|
-
## @return [Integer] seconds
|
265
|
-
##
|
266
|
-
def chronify_qty(qty)
|
267
|
-
minutes = 0
|
268
|
-
case qty.strip
|
269
|
-
when /^(\d+):(\d\d)$/
|
270
|
-
minutes += Regexp.last_match(1).to_i * 60
|
271
|
-
minutes += Regexp.last_match(2).to_i
|
272
|
-
when /^(\d+(?:\.\d+)?)([hmd])?$/
|
273
|
-
amt = Regexp.last_match(1)
|
274
|
-
type = Regexp.last_match(2).nil? ? 'm' : Regexp.last_match(2)
|
275
|
-
|
276
|
-
minutes = case type.downcase
|
277
|
-
when 'm'
|
278
|
-
amt.to_i
|
279
|
-
when 'h'
|
280
|
-
(amt.to_f * 60).round
|
281
|
-
when 'd'
|
282
|
-
(amt.to_f * 60 * 24).round
|
283
|
-
else
|
284
|
-
minutes
|
285
|
-
end
|
286
|
-
end
|
287
|
-
minutes * 60
|
288
|
-
end
|
289
|
-
|
290
243
|
##
|
291
244
|
## List sections
|
292
245
|
##
|
@@ -488,8 +441,9 @@ module Doing
|
|
488
441
|
# @param item [Item] the item to reset/resume
|
489
442
|
# @param resume [Boolean] removing @done tag if true
|
490
443
|
#
|
491
|
-
def reset_item(item, resume: false)
|
492
|
-
|
444
|
+
def reset_item(item, date: nil, resume: false)
|
445
|
+
date ||= Time.now
|
446
|
+
item.date = date
|
493
447
|
item.tag('done', remove: true) if resume
|
494
448
|
logger.info('Reset:', %(Reset #{resume ? 'and resumed ' : ''} "#{item.title}" in #{item.section}))
|
495
449
|
item
|
@@ -593,10 +547,25 @@ module Doing
|
|
593
547
|
last_entry
|
594
548
|
end
|
595
549
|
|
596
|
-
def all_tags(items, opt: {})
|
597
|
-
|
598
|
-
|
599
|
-
|
550
|
+
def all_tags(items, opt: {}, counts: false)
|
551
|
+
if counts
|
552
|
+
all_tags = {}
|
553
|
+
items.each do |item|
|
554
|
+
item.tags.each do |tag|
|
555
|
+
if all_tags.key?(tag.downcase)
|
556
|
+
all_tags[tag.downcase] += 1
|
557
|
+
else
|
558
|
+
all_tags[tag.downcase] = 1
|
559
|
+
end
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
all_tags.sort_by { |tag, count| count }
|
564
|
+
else
|
565
|
+
all_tags = []
|
566
|
+
items.each { |item| all_tags.concat(item.tags.map(&:downcase)).uniq! }
|
567
|
+
all_tags.sort
|
568
|
+
end
|
600
569
|
end
|
601
570
|
|
602
571
|
def tag_groups(items, opt: {})
|
@@ -661,12 +630,55 @@ module Doing
|
|
661
630
|
## @option opt [String] :age ('old' or 'new')
|
662
631
|
##
|
663
632
|
def filter_items(items = Items.new, opt: {})
|
633
|
+
time_rx = /^(\d{1,2}+(:\d{1,2}+)?( *(am|pm))?|midnight|noon)$/
|
634
|
+
|
664
635
|
if items.nil? || items.empty?
|
665
636
|
section = opt[:section] ? guess_section(opt[:section]) : 'All'
|
666
|
-
|
667
637
|
items = section =~ /^all$/i ? @content.dup : @content.in_section(section)
|
668
638
|
end
|
669
639
|
|
640
|
+
opt[:time_filter] = [nil, nil]
|
641
|
+
if opt[:from] && !opt[:date_filter]
|
642
|
+
date_string = opt[:from]
|
643
|
+
case date_string
|
644
|
+
when / (to|through|thru|(un)?til|-+) /
|
645
|
+
dates = date_string.split(/ (?:to|through|thru|(?:un)?til|-+) /)
|
646
|
+
if dates[0].strip =~ time_rx && dates[-1].strip =~ time_rx
|
647
|
+
time_start = dates[0].strip
|
648
|
+
time_end = dates[-1].strip
|
649
|
+
else
|
650
|
+
start = dates[0].chronify(guess: :begin)
|
651
|
+
finish = dates[-1].chronify(guess: :end)
|
652
|
+
end
|
653
|
+
when time_rx
|
654
|
+
time_start = date_string
|
655
|
+
time_end = nil
|
656
|
+
else
|
657
|
+
start = date_string.chronify(guess: :begin)
|
658
|
+
finish = false
|
659
|
+
end
|
660
|
+
|
661
|
+
if time_start
|
662
|
+
opt[:time_filter] = [time_start, time_end]
|
663
|
+
Doing.logger.debug('Parser:', "--from string interpreted as time span, from #{time_start ? time_start : '12am'} to #{time_end ? time_end : '11:59pm'}")
|
664
|
+
else
|
665
|
+
raise InvalidTimeExpression, 'Unrecognized date string' unless start
|
666
|
+
|
667
|
+
opt[:date_filter] = [start, finish]
|
668
|
+
Doing.logger.debug('Parser:', "--from string interpreted as #{start.strftime('%F %R')} -- #{finish ? finish.strftime('%F %R') : 'now'}")
|
669
|
+
end
|
670
|
+
end
|
671
|
+
|
672
|
+
if opt[:before] =~ time_rx
|
673
|
+
opt[:time_filter][1] = opt[:before]
|
674
|
+
opt[:before] = nil
|
675
|
+
end
|
676
|
+
|
677
|
+
if opt[:after] =~ time_rx
|
678
|
+
opt[:time_filter][0] = opt[:after]
|
679
|
+
opt[:after] = nil
|
680
|
+
end
|
681
|
+
|
670
682
|
items.sort_by! { |item| [item.date, item.title.downcase] }.reverse
|
671
683
|
|
672
684
|
filtered_items = items.select do |item|
|
@@ -678,6 +690,7 @@ module Doing
|
|
678
690
|
end
|
679
691
|
|
680
692
|
if keep && opt[:tag]
|
693
|
+
opt[:tag_bool] = opt[:bool].normalize_bool if opt[:bool]
|
681
694
|
opt[:tag_bool] ||= :and
|
682
695
|
tag_match = opt[:tag].nil? || opt[:tag].empty? ? true : item.tags?(opt[:tag], opt[:tag_bool])
|
683
696
|
keep = false unless tag_match
|
@@ -688,7 +701,7 @@ module Doing
|
|
688
701
|
search_match = if opt[:search].nil? || opt[:search].empty?
|
689
702
|
true
|
690
703
|
else
|
691
|
-
item.search(opt[:search], case_type: opt[:case].normalize_case
|
704
|
+
item.search(opt[:search], case_type: opt[:case].normalize_case)
|
692
705
|
end
|
693
706
|
|
694
707
|
keep = false unless search_match
|
@@ -708,23 +721,51 @@ module Doing
|
|
708
721
|
keep = opt[:not] ? !keep : keep
|
709
722
|
end
|
710
723
|
|
724
|
+
if keep && opt[:time_filter][0] || opt[:time_filter][1]
|
725
|
+
start_string = if opt[:time_filter][0].nil?
|
726
|
+
"#{item.date.strftime('%Y-%m-%d')} 12am"
|
727
|
+
else
|
728
|
+
"#{item.date.strftime('%Y-%m-%d')} #{opt[:time_filter][0]}"
|
729
|
+
end
|
730
|
+
start_time = start_string.chronify(guess: :begin)
|
731
|
+
|
732
|
+
end_string = if opt[:time_filter][1].nil?
|
733
|
+
"#{item.date.next_day.strftime('%Y-%m-%d')} 12am"
|
734
|
+
else
|
735
|
+
"#{item.date.strftime('%Y-%m-%d')} #{opt[:time_filter][1]}"
|
736
|
+
end
|
737
|
+
end_time = end_string.chronify(guess: :end)
|
738
|
+
|
739
|
+
in_time_range = item.date >= start_time && item.date <= end_time
|
740
|
+
keep = false unless in_time_range
|
741
|
+
keep = opt[:not] ? !keep : keep
|
742
|
+
end
|
743
|
+
|
711
744
|
keep = false if keep && opt[:only_timed] && !item.interval
|
712
745
|
|
713
|
-
if keep && opt[:tag_filter]
|
746
|
+
if keep && opt[:tag_filter]
|
714
747
|
keep = item.tags?(opt[:tag_filter]['tags'], opt[:tag_filter]['bool'])
|
715
748
|
keep = opt[:not] ? !keep : keep
|
716
749
|
end
|
717
750
|
|
718
751
|
if keep && opt[:before]
|
719
752
|
time_string = opt[:before]
|
720
|
-
|
753
|
+
if time_string =~ time_rx
|
754
|
+
cutoff = "#{item.date.strftime('%Y-%m-%d')} #{time_string}".chronify(guess: :begin)
|
755
|
+
else
|
756
|
+
cutoff = time_string.chronify(guess: :begin)
|
757
|
+
end
|
721
758
|
keep = cutoff && item.date <= cutoff
|
722
759
|
keep = opt[:not] ? !keep : keep
|
723
760
|
end
|
724
761
|
|
725
762
|
if keep && opt[:after]
|
726
763
|
time_string = opt[:after]
|
727
|
-
|
764
|
+
if time_string =~ time_rx
|
765
|
+
cutoff = "#{item.date.strftime('%Y-%m-%d')} #{time_string}".chronify(guess: :end)
|
766
|
+
else
|
767
|
+
cutoff = time_string.chronify(guess: :end)
|
768
|
+
end
|
728
769
|
keep = cutoff && item.date >= cutoff
|
729
770
|
keep = opt[:not] ? !keep : keep
|
730
771
|
end
|
@@ -760,7 +801,7 @@ module Doing
|
|
760
801
|
## Options hash is shared with #filter_items and #act_on
|
761
802
|
##
|
762
803
|
def interactive(opt = {})
|
763
|
-
section = opt[:section] ? guess_section(opt[:section]) : 'All'
|
804
|
+
opt[:section] = opt[:section] ? guess_section(opt[:section]) : 'All'
|
764
805
|
|
765
806
|
search = nil
|
766
807
|
|
@@ -774,9 +815,12 @@ module Doing
|
|
774
815
|
opt[:query] = "!#{opt[:query]}" if opt[:not]
|
775
816
|
opt[:multiple] = true
|
776
817
|
opt[:show_if_single] = true
|
777
|
-
|
818
|
+
filter_options = %i[after before case date_filter from fuzzy not search section].each_with_object({}) {
|
819
|
+
|k, hsh| hsh[k] = opt[k]
|
820
|
+
}
|
821
|
+
items = filter_items(Items.new, opt: filter_options)
|
778
822
|
|
779
|
-
selection = Prompt.choose_from_items(items, include_section: section =~ /^all$/i, **opt)
|
823
|
+
selection = Prompt.choose_from_items(items, include_section: opt[:section] =~ /^all$/i, **opt)
|
780
824
|
|
781
825
|
raise NoResults, 'no items selected' if selection.nil? || selection.empty?
|
782
826
|
|
@@ -905,12 +949,19 @@ module Doing
|
|
905
949
|
if opt[:resume] && !opt[:reset]
|
906
950
|
repeat_item(item, { editor: opt[:editor] })
|
907
951
|
elsif opt[:reset]
|
952
|
+
res = Prompt.enter_text('Start date (blank for current time)', default_response: '')
|
953
|
+
if res =~ /^ *$/
|
954
|
+
date = Time.now
|
955
|
+
else
|
956
|
+
date = res.chronify(guess: :begin)
|
957
|
+
end
|
958
|
+
|
908
959
|
res = if item.tags?('done', :and) && !opt[:resume]
|
909
960
|
opt[:force] ? true : Prompt.yn('Remove @done tag?', default_response: 'y')
|
910
961
|
else
|
911
962
|
opt[:resume]
|
912
963
|
end
|
913
|
-
@content.update_item(item, reset_item(item, resume: res))
|
964
|
+
@content.update_item(item, reset_item(item, date: date, resume: res))
|
914
965
|
end
|
915
966
|
write(@doing_file)
|
916
967
|
|
@@ -1300,20 +1351,6 @@ module Doing
|
|
1300
1351
|
end
|
1301
1352
|
end
|
1302
1353
|
|
1303
|
-
##
|
1304
|
-
## Restore a backed up version of a file
|
1305
|
-
##
|
1306
|
-
## @param file [String] The filepath to restore
|
1307
|
-
##
|
1308
|
-
def restore_backup(file)
|
1309
|
-
if File.exist?("#{file}~")
|
1310
|
-
FileUtils.cp("#{file}~", file)
|
1311
|
-
logger.warn('File update:', "Restored #{file.sub(/^#{Util.user_home}/, '~')}")
|
1312
|
-
else
|
1313
|
-
logger.error('Restore error:', 'No backup file found')
|
1314
|
-
end
|
1315
|
-
end
|
1316
|
-
|
1317
1354
|
##
|
1318
1355
|
## Rename doing file with date and start fresh one
|
1319
1356
|
##
|
@@ -1336,7 +1373,7 @@ module Doing
|
|
1336
1373
|
break if counter >= max
|
1337
1374
|
if opt[:before]
|
1338
1375
|
time_string = opt[:before]
|
1339
|
-
cutoff = chronify(
|
1376
|
+
cutoff = time_string.chronify(guess: :begin)
|
1340
1377
|
end
|
1341
1378
|
|
1342
1379
|
unless ((!tags.empty? && !item.tags?(tags, bool)) || (opt[:search] && !item.search(opt[:search].to_s)) || (opt[:before] && item.date >= cutoff))
|
@@ -1378,8 +1415,35 @@ module Doing
|
|
1378
1415
|
##
|
1379
1416
|
## @return [String] The selected section name
|
1380
1417
|
##
|
1381
|
-
def choose_section
|
1382
|
-
|
1418
|
+
def choose_section(include_all: false)
|
1419
|
+
options = @content.section_titles.sort
|
1420
|
+
options.unshift('All') if include_all
|
1421
|
+
choice = Prompt.choose_from(options, prompt: 'Choose a section > ', fzf_args: ['--height=60%'])
|
1422
|
+
choice ? choice.strip : choice
|
1423
|
+
end
|
1424
|
+
|
1425
|
+
##
|
1426
|
+
## Generate a menu of tags and allow user selection
|
1427
|
+
##
|
1428
|
+
## @return [String] The selected tag name
|
1429
|
+
##
|
1430
|
+
def choose_tag(section = 'All', include_all: false)
|
1431
|
+
items = @content.in_section(section)
|
1432
|
+
tags = all_tags(items, counts: true).map { |t, c| "@#{t} (#{c})" }
|
1433
|
+
tags.unshift('No tag filter') if include_all
|
1434
|
+
choice = Prompt.choose_from(tags, sorted: false, multiple: true, prompt: 'Choose a tag > ', fzf_args: ['--height=60%'])
|
1435
|
+
choice ? choice.split(/\n/).map { |t| t.strip.sub(/ \(.*?\)$/, '')}.join(' ') : choice
|
1436
|
+
end
|
1437
|
+
|
1438
|
+
##
|
1439
|
+
## Generate a menu of sections and tags and allow user selection
|
1440
|
+
##
|
1441
|
+
## @return [String] The selected section or tag name
|
1442
|
+
##
|
1443
|
+
def choose_section_tag
|
1444
|
+
options = @content.section_titles.sort
|
1445
|
+
options.concat(@content.all_tags.sort.map { |t| "@#{t}" })
|
1446
|
+
choice = Prompt.choose_from(options, prompt: 'Choose a section or tag > ', fzf_args: ['--height=60%'])
|
1383
1447
|
choice ? choice.strip : choice
|
1384
1448
|
end
|
1385
1449
|
|
@@ -1420,20 +1484,23 @@ module Doing
|
|
1420
1484
|
##
|
1421
1485
|
def list_section(opt = {})
|
1422
1486
|
opt[:config_template] ||= 'default'
|
1423
|
-
cfg = @config.dig('templates',
|
1424
|
-
|
1425
|
-
|
1426
|
-
|
1427
|
-
|
1428
|
-
|
1487
|
+
cfg = @config.dig('templates',
|
1488
|
+
opt[:config_template]).deep_merge({
|
1489
|
+
'wrap_width' => @config['wrap_width'] || 0,
|
1490
|
+
'date_format' => @config['default_date_format'],
|
1491
|
+
'order' => @config['order'] || 'asc',
|
1492
|
+
'tags_color' => @config['tags_color'],
|
1493
|
+
'duration' => @config['duration'],
|
1494
|
+
'interval_format' => @config['interval_format']
|
1495
|
+
})
|
1496
|
+
opt[:duration] ||= cfg['duration'] || false
|
1497
|
+
opt[:interval_format] ||= cfg['interval_format'] || 'text'
|
1429
1498
|
opt[:count] ||= 0
|
1430
1499
|
opt[:age] ||= 'newest'
|
1431
1500
|
opt[:format] ||= cfg['date_format']
|
1432
1501
|
opt[:order] ||= cfg['order'] || 'asc'
|
1433
1502
|
opt[:tag_order] ||= 'asc'
|
1434
|
-
if opt[:tags_color].nil?
|
1435
|
-
opt[:tags_color] = cfg['tags_color'] || false
|
1436
|
-
end
|
1503
|
+
opt[:tags_color] = cfg['tags_color'] || false if opt[:tags_color].nil?
|
1437
1504
|
opt[:template] ||= cfg['template']
|
1438
1505
|
|
1439
1506
|
# opt[:highlight] ||= true
|
@@ -1443,17 +1510,17 @@ module Doing
|
|
1443
1510
|
opt[:section] = choose_section
|
1444
1511
|
title = opt[:section]
|
1445
1512
|
elsif opt[:section].instance_of?(String)
|
1446
|
-
if opt[:section] =~ /^all$/i
|
1447
|
-
|
1513
|
+
title = if opt[:section] =~ /^all$/i
|
1514
|
+
if opt[:page_title]
|
1448
1515
|
opt[:page_title]
|
1449
1516
|
elsif opt[:tag_filter] && opt[:tag_filter]['bool'].normalize_bool != :not
|
1450
1517
|
opt[:tag_filter]['tags'].map { |tag| "@#{tag}" }.join(' + ')
|
1451
1518
|
else
|
1452
1519
|
'doing'
|
1453
1520
|
end
|
1454
|
-
|
1455
|
-
|
1456
|
-
|
1521
|
+
else
|
1522
|
+
guess_section(opt[:section])
|
1523
|
+
end
|
1457
1524
|
end
|
1458
1525
|
|
1459
1526
|
items = filter_items(Items.new, opt: opt).reverse
|
@@ -1522,13 +1589,22 @@ module Doing
|
|
1522
1589
|
'wrap_width' => @config['wrap_width'] || 0,
|
1523
1590
|
'date_format' => @config['default_date_format'],
|
1524
1591
|
'order' => @config['order'] || 'asc',
|
1525
|
-
'tags_color' => @config['tags_color']
|
1592
|
+
'tags_color' => @config['tags_color'],
|
1593
|
+
'duration' => @config['duration'],
|
1594
|
+
'interval_format' => @config['interval_format']
|
1526
1595
|
})
|
1596
|
+
|
1597
|
+
opt[:duration] ||= cfg['duration'] || false
|
1598
|
+
opt[:interval_format] ||= cfg['interval_format'] || 'text'
|
1599
|
+
|
1527
1600
|
options = {
|
1528
1601
|
after: opt[:after],
|
1529
1602
|
before: opt[:before],
|
1530
1603
|
count: 0,
|
1604
|
+
duration: opt[:duration],
|
1605
|
+
from: opt[:from],
|
1531
1606
|
format: cfg['date_format'],
|
1607
|
+
interval_format: opt[:interval_format],
|
1532
1608
|
order: cfg['order'] || 'asc',
|
1533
1609
|
output: output,
|
1534
1610
|
section: opt[:section],
|
@@ -1560,8 +1636,18 @@ module Doing
|
|
1560
1636
|
# :date_filter expects an array with start and end date
|
1561
1637
|
dates = [dates, dates] if dates.instance_of?(String)
|
1562
1638
|
|
1563
|
-
list_section({
|
1564
|
-
|
1639
|
+
list_section({
|
1640
|
+
section: section,
|
1641
|
+
count: 0,
|
1642
|
+
order: 'asc',
|
1643
|
+
date_filter: dates,
|
1644
|
+
times: times,
|
1645
|
+
output: output,
|
1646
|
+
totals: opt[:totals],
|
1647
|
+
duration: opt[:duration],
|
1648
|
+
sort_tags: opt[:sort_tags],
|
1649
|
+
config_template: 'default'
|
1650
|
+
})
|
1565
1651
|
end
|
1566
1652
|
|
1567
1653
|
##
|
@@ -1584,6 +1670,8 @@ module Doing
|
|
1584
1670
|
after: opt[:after],
|
1585
1671
|
before: opt[:before],
|
1586
1672
|
count: 0,
|
1673
|
+
duration: opt[:duration],
|
1674
|
+
from: opt[:from],
|
1587
1675
|
order: opt[:order],
|
1588
1676
|
output: output,
|
1589
1677
|
section: section,
|
@@ -1614,8 +1702,13 @@ module Doing
|
|
1614
1702
|
'wrap_width' => @config['wrap_width'] || 0,
|
1615
1703
|
'date_format' => @config['default_date_format'],
|
1616
1704
|
'order' => @config['order'] || 'asc',
|
1617
|
-
'tags_color' => @config['tags_color']
|
1705
|
+
'tags_color' => @config['tags_color'],
|
1706
|
+
'duration' => @config['duration'],
|
1707
|
+
'interval_format' => @config['interval_format']
|
1618
1708
|
})
|
1709
|
+
opt[:duration] ||= cfg['duration'] || false
|
1710
|
+
opt[:interval_format] ||= cfg['interval_format'] || 'text'
|
1711
|
+
|
1619
1712
|
section ||= @config['current_section']
|
1620
1713
|
section = guess_section(section)
|
1621
1714
|
|
@@ -1637,8 +1730,12 @@ module Doing
|
|
1637
1730
|
'wrap_width' => @config['wrap_width'] || 0,
|
1638
1731
|
'date_format' => @config['default_date_format'],
|
1639
1732
|
'order' => @config['order'] || 'asc',
|
1640
|
-
'tags_color' => @config['tags_color']
|
1733
|
+
'tags_color' => @config['tags_color'],
|
1734
|
+
'duration' => @config['duration'],
|
1735
|
+
'interval_format' => @config['interval_format']
|
1641
1736
|
})
|
1737
|
+
options[:duration] ||= cfg['duration'] || false
|
1738
|
+
options[:interval_format] ||= cfg['interval_format'] || 'text'
|
1642
1739
|
|
1643
1740
|
opts = {
|
1644
1741
|
section: section,
|
@@ -1646,7 +1743,12 @@ module Doing
|
|
1646
1743
|
count: 1,
|
1647
1744
|
format: cfg['date_format'],
|
1648
1745
|
template: cfg['template'],
|
1649
|
-
times: times
|
1746
|
+
times: times,
|
1747
|
+
duration: options[:duration],
|
1748
|
+
interval_format: options[:interval_format],
|
1749
|
+
case: options[:case],
|
1750
|
+
not: options[:negate],
|
1751
|
+
config_template: 'last'
|
1650
1752
|
}
|
1651
1753
|
|
1652
1754
|
if options[:tag]
|
@@ -1657,9 +1759,7 @@ module Doing
|
|
1657
1759
|
end
|
1658
1760
|
|
1659
1761
|
opts[:search] = options[:search] if options[:search]
|
1660
|
-
|
1661
|
-
opts[:not] = options[:negate]
|
1662
|
-
opts[:config_template] = 'last'
|
1762
|
+
|
1663
1763
|
list_section(opts)
|
1664
1764
|
end
|
1665
1765
|
|
@@ -2044,7 +2144,7 @@ EOS
|
|
2044
2144
|
break if counter >= max
|
2045
2145
|
if opt[:before]
|
2046
2146
|
time_string = opt[:before]
|
2047
|
-
cutoff = chronify(
|
2147
|
+
cutoff = time_string.chronify(guess: :begin)
|
2048
2148
|
end
|
2049
2149
|
|
2050
2150
|
if (item.section.downcase != section.downcase && section != /^all$/i) || item.section.downcase == destination.downcase
|
data/lib/doing.rb
CHANGED
@@ -6,9 +6,12 @@ require 'yaml'
|
|
6
6
|
require 'pp'
|
7
7
|
require 'csv'
|
8
8
|
require 'tempfile'
|
9
|
+
require 'zlib'
|
10
|
+
require 'base64'
|
9
11
|
require 'chronic'
|
10
12
|
require 'tty-link'
|
11
13
|
require 'tty-which'
|
14
|
+
require 'tty-markdown'
|
12
15
|
# require 'amatch'
|
13
16
|
require 'haml'
|
14
17
|
require 'json'
|
@@ -17,10 +20,12 @@ require 'safe_yaml/load'
|
|
17
20
|
require 'doing/hash'
|
18
21
|
require 'doing/colors'
|
19
22
|
require 'doing/string'
|
23
|
+
require 'doing/string_chronify'
|
20
24
|
require 'doing/time'
|
21
25
|
require 'doing/array'
|
22
26
|
require 'doing/symbol'
|
23
27
|
require 'doing/util'
|
28
|
+
require 'doing/util_backup'
|
24
29
|
require 'doing/configuration'
|
25
30
|
require 'doing/section'
|
26
31
|
require 'doing/items'
|
@@ -34,6 +39,8 @@ require 'doing/hooks'
|
|
34
39
|
require 'doing/plugin_manager'
|
35
40
|
require 'doing/pager'
|
36
41
|
require 'doing/completion'
|
42
|
+
require 'doing/boolean_term_parser'
|
43
|
+
require 'doing/phrase_parser'
|
37
44
|
# require 'doing/markdown_document_listener'
|
38
45
|
|
39
46
|
# Main doing module
|
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: 2.1.
|
4
|
+
version: 2.1.4pre
|
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-
|
11
|
+
date: 2021-12-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: safe_yaml
|
@@ -326,6 +326,46 @@ dependencies:
|
|
326
326
|
- - ">="
|
327
327
|
- !ruby/object:Gem::Version
|
328
328
|
version: 0.5.0
|
329
|
+
- !ruby/object:Gem::Dependency
|
330
|
+
name: tty-markdown
|
331
|
+
requirement: !ruby/object:Gem::Requirement
|
332
|
+
requirements:
|
333
|
+
- - "~>"
|
334
|
+
- !ruby/object:Gem::Version
|
335
|
+
version: '0.7'
|
336
|
+
- - ">="
|
337
|
+
- !ruby/object:Gem::Version
|
338
|
+
version: 0.7.0
|
339
|
+
type: :runtime
|
340
|
+
prerelease: false
|
341
|
+
version_requirements: !ruby/object:Gem::Requirement
|
342
|
+
requirements:
|
343
|
+
- - "~>"
|
344
|
+
- !ruby/object:Gem::Version
|
345
|
+
version: '0.7'
|
346
|
+
- - ">="
|
347
|
+
- !ruby/object:Gem::Version
|
348
|
+
version: 0.7.0
|
349
|
+
- !ruby/object:Gem::Dependency
|
350
|
+
name: parslet
|
351
|
+
requirement: !ruby/object:Gem::Requirement
|
352
|
+
requirements:
|
353
|
+
- - "~>"
|
354
|
+
- !ruby/object:Gem::Version
|
355
|
+
version: '2.0'
|
356
|
+
- - ">="
|
357
|
+
- !ruby/object:Gem::Version
|
358
|
+
version: 2.0.0
|
359
|
+
type: :runtime
|
360
|
+
prerelease: false
|
361
|
+
version_requirements: !ruby/object:Gem::Requirement
|
362
|
+
requirements:
|
363
|
+
- - "~>"
|
364
|
+
- !ruby/object:Gem::Version
|
365
|
+
version: '2.0'
|
366
|
+
- - ">="
|
367
|
+
- !ruby/object:Gem::Version
|
368
|
+
version: 2.0.0
|
329
369
|
description: A tool for managing a TaskPaper-like file of recent activites. Perfect
|
330
370
|
for the late-night hacker on too much caffeine to remember what they accomplished
|
331
371
|
at 2 in the morning.
|
@@ -413,6 +453,7 @@ files:
|
|
413
453
|
- lib/completion/doing.fish
|
414
454
|
- lib/doing.rb
|
415
455
|
- lib/doing/array.rb
|
456
|
+
- lib/doing/boolean_term_parser.rb
|
416
457
|
- lib/doing/cli_status.rb
|
417
458
|
- lib/doing/colors.rb
|
418
459
|
- lib/doing/completion.rb
|
@@ -430,6 +471,7 @@ files:
|
|
430
471
|
- lib/doing/markdown_document_listener.rb
|
431
472
|
- lib/doing/note.rb
|
432
473
|
- lib/doing/pager.rb
|
474
|
+
- lib/doing/phrase_parser.rb
|
433
475
|
- lib/doing/plugin_manager.rb
|
434
476
|
- lib/doing/plugins/export/csv_export.rb
|
435
477
|
- lib/doing/plugins/export/html_export.rb
|
@@ -444,9 +486,11 @@ files:
|
|
444
486
|
- lib/doing/prompt.rb
|
445
487
|
- lib/doing/section.rb
|
446
488
|
- lib/doing/string.rb
|
489
|
+
- lib/doing/string_chronify.rb
|
447
490
|
- lib/doing/symbol.rb
|
448
491
|
- lib/doing/time.rb
|
449
492
|
- lib/doing/util.rb
|
493
|
+
- lib/doing/util_backup.rb
|
450
494
|
- lib/doing/version.rb
|
451
495
|
- lib/doing/wwid.rb
|
452
496
|
- lib/examples/commands/autotag.rb
|