doing 2.1.0pre → 2.1.4pre
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 +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
|