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.
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