doing 2.1.0pre → 2.1.4pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.yardoc/checksums +13 -9
  3. data/.yardoc/object_types +0 -0
  4. data/.yardoc/objects/root.dat +0 -0
  5. data/CHANGELOG.md +42 -10
  6. data/Gemfile.lock +23 -1
  7. data/README.md +1 -1
  8. data/Rakefile +2 -0
  9. data/bin/doing +421 -156
  10. data/doc/Array.html +1 -1
  11. data/doc/Doing/Color.html +1 -1
  12. data/doc/Doing/Completion.html +1 -1
  13. data/doc/Doing/Configuration.html +81 -90
  14. data/doc/Doing/Errors/DoingNoTraceError.html +1 -1
  15. data/doc/Doing/Errors/DoingRuntimeError.html +1 -1
  16. data/doc/Doing/Errors/DoingStandardError.html +1 -1
  17. data/doc/Doing/Errors/EmptyInput.html +1 -1
  18. data/doc/Doing/Errors/NoResults.html +1 -1
  19. data/doc/Doing/Errors/PluginException.html +1 -1
  20. data/doc/Doing/Errors/UserCancelled.html +1 -1
  21. data/doc/Doing/Errors/WrongCommand.html +1 -1
  22. data/doc/Doing/Errors.html +1 -1
  23. data/doc/Doing/Hooks.html +1 -1
  24. data/doc/Doing/Item.html +84 -20
  25. data/doc/Doing/Items.html +35 -1
  26. data/doc/Doing/LogAdapter.html +1 -1
  27. data/doc/Doing/Note.html +1 -1
  28. data/doc/Doing/Pager.html +1 -1
  29. data/doc/Doing/Plugins.html +1 -1
  30. data/doc/Doing/Prompt.html +70 -18
  31. data/doc/Doing/Section.html +1 -1
  32. data/doc/Doing/Util.html +16 -4
  33. data/doc/Doing/WWID.html +27 -147
  34. data/doc/Doing.html +3 -3
  35. data/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
  36. data/doc/GLI/Commands.html +1 -1
  37. data/doc/GLI.html +1 -1
  38. data/doc/Hash.html +1 -1
  39. data/doc/Status.html +1 -1
  40. data/doc/String.html +344 -4
  41. data/doc/Symbol.html +1 -1
  42. data/doc/Time.html +70 -2
  43. data/doc/_index.html +125 -4
  44. data/doc/class_list.html +1 -1
  45. data/doc/file.README.html +2 -2
  46. data/doc/index.html +2 -2
  47. data/doc/method_list.html +537 -193
  48. data/doc/top-level-namespace.html +2 -2
  49. data/doing.gemspec +2 -0
  50. data/doing.rdoc +276 -75
  51. data/lib/completion/doing.bash +20 -20
  52. data/lib/doing/boolean_term_parser.rb +86 -0
  53. data/lib/doing/configuration.rb +36 -19
  54. data/lib/doing/item.rb +102 -9
  55. data/lib/doing/items.rb +6 -0
  56. data/lib/doing/phrase_parser.rb +124 -0
  57. data/lib/doing/plugins/export/template_export.rb +29 -2
  58. data/lib/doing/prompt.rb +21 -11
  59. data/lib/doing/string.rb +47 -3
  60. data/lib/doing/string_chronify.rb +85 -0
  61. data/lib/doing/time.rb +32 -0
  62. data/lib/doing/util.rb +2 -5
  63. data/lib/doing/util_backup.rb +235 -0
  64. data/lib/doing/version.rb +1 -1
  65. data/lib/doing/wwid.rb +224 -124
  66. data/lib/doing.rb +7 -0
  67. 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
- done_rx = /(?<=^| )@(?<tag>done|finished|completed?)\((?<date>.*?)\)/i
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(d, guess: :begin)
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(d, guess: :begin)
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
- item.date = Time.now
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
- all_tags = []
598
- items.each { |item| all_tags.concat(item.tags).uniq! }
599
- all_tags.sort
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, fuzzy: opt[:fuzzy])
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] && !opt[:tag_filter]['tags'].empty?
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
- cutoff = chronify(time_string, guess: :begin)
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
- cutoff = chronify(time_string, guess: :end)
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
- items = filter_items(Items.new, opt: { section: section, search: opt[:search], fuzzy: opt[:fuzzy], case: opt[:case], not: opt[:not] })
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(time_string, guess: :begin)
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
- choice = Prompt.choose_from(@content.section_titles.sort, prompt: 'Choose a section > ', fzf_args: ['--height=60%'])
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', opt[:config_template]).deep_merge({
1424
- 'wrap_width' => @config['wrap_width'] || 0,
1425
- 'date_format' => @config['default_date_format'],
1426
- 'order' => @config['order'] || 'asc',
1427
- 'tags_color' => @config['tags_color']
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
- title = if opt[:page_title]
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
- else
1455
- title = guess_section(opt[:section])
1456
- end
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({ section: section, count: 0, order: 'asc', date_filter: dates, times: times,
1564
- output: output, totals: opt[:totals], sort_tags: opt[:sort_tags], config_template: 'default' })
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
- opts[:case] = options[:case]
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(time_string, guess: :begin)
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.0pre
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-27 00:00:00.000000000 Z
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