doing 2.1.30 → 2.1.34

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 (142) hide show
  1. checksums.yaml +4 -4
  2. data/.irbrc +1 -0
  3. data/CHANGELOG.md +4972 -0
  4. data/Dockerfile-2.6 +3 -1
  5. data/Dockerfile-2.7 +4 -2
  6. data/Dockerfile-3.0 +3 -1
  7. data/Gemfile.lock +1 -67
  8. data/README.md +1 -1
  9. data/bash_profile +13 -0
  10. data/bin/commands/again.rb +1 -1
  11. data/bin/commands/archive.rb +3 -3
  12. data/bin/commands/cancel.rb +1 -1
  13. data/bin/commands/commands.rb +8 -8
  14. data/bin/commands/completion.rb +61 -19
  15. data/bin/commands/config.rb +22 -19
  16. data/bin/commands/done.rb +2 -2
  17. data/bin/commands/flag.rb +1 -1
  18. data/bin/commands/grep.rb +6 -33
  19. data/bin/commands/last.rb +1 -1
  20. data/bin/commands/meanwhile.rb +2 -2
  21. data/bin/commands/now.rb +2 -2
  22. data/bin/commands/on.rb +6 -16
  23. data/bin/commands/open.rb +1 -1
  24. data/bin/commands/recent.rb +5 -17
  25. data/bin/commands/rotate.rb +17 -0
  26. data/bin/commands/sections.rb +82 -7
  27. data/bin/commands/show.rb +8 -28
  28. data/bin/commands/since.rb +5 -16
  29. data/bin/commands/tag_dir.rb +27 -3
  30. data/bin/commands/today.rb +3 -28
  31. data/bin/commands/view.rb +3 -3
  32. data/bin/commands/yesterday.rb +3 -36
  33. data/bin/doing +29 -139
  34. data/docs/doc/Array.html +1 -1
  35. data/docs/doc/BooleanTermParser/Clause.html +1 -1
  36. data/docs/doc/BooleanTermParser/Operator.html +1 -1
  37. data/docs/doc/BooleanTermParser/Query.html +1 -1
  38. data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
  39. data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
  40. data/docs/doc/BooleanTermParser.html +1 -1
  41. data/docs/doc/Doing/Color.html +1 -1
  42. data/docs/doc/Doing/Completion.html +324 -4
  43. data/docs/doc/Doing/Configuration.html +3 -3
  44. data/docs/doc/Doing/Errors/DoingNoTraceError.html +1 -1
  45. data/docs/doc/Doing/Errors/DoingRuntimeError.html +1 -1
  46. data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
  47. data/docs/doc/Doing/Errors/EmptyInput.html +1 -1
  48. data/docs/doc/Doing/Errors/NoResults.html +1 -1
  49. data/docs/doc/Doing/Errors/PluginException.html +1 -1
  50. data/docs/doc/Doing/Errors/UserCancelled.html +1 -1
  51. data/docs/doc/Doing/Errors/WrongCommand.html +1 -1
  52. data/docs/doc/Doing/Errors.html +1 -1
  53. data/docs/doc/Doing/Hooks.html +1 -1
  54. data/docs/doc/Doing/Item.html +144 -3
  55. data/docs/doc/Doing/Items.html +209 -1
  56. data/docs/doc/Doing/LogAdapter.html +1 -1
  57. data/docs/doc/Doing/Logger.html +1807 -0
  58. data/docs/doc/Doing/Note.html +109 -3
  59. data/docs/doc/Doing/Pager.html +1 -1
  60. data/docs/doc/Doing/Plugins.html +1 -1
  61. data/docs/doc/Doing/Prompt.html +1 -1
  62. data/docs/doc/Doing/Section.html +1 -1
  63. data/docs/doc/Doing/TemplateString.html +1 -1
  64. data/docs/doc/Doing/Types.html +3 -3
  65. data/docs/doc/Doing/Util/Backup.html +1 -1
  66. data/docs/doc/Doing/Util.html +1 -1
  67. data/docs/doc/Doing/WWID.html +8 -58
  68. data/docs/doc/Doing.html +4 -4
  69. data/docs/doc/FalseClass.html +1 -1
  70. data/docs/doc/GLI/Commands/Help.html +1 -1
  71. data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
  72. data/docs/doc/GLI/Commands.html +1 -1
  73. data/docs/doc/GLI.html +1 -1
  74. data/docs/doc/Hash.html +1 -1
  75. data/docs/doc/Object.html +1 -1
  76. data/docs/doc/PhraseParser/Operator.html +1 -1
  77. data/docs/doc/PhraseParser/PhraseClause.html +1 -1
  78. data/docs/doc/PhraseParser/Query.html +1 -1
  79. data/docs/doc/PhraseParser/QueryParser.html +1 -1
  80. data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
  81. data/docs/doc/PhraseParser/TermClause.html +1 -1
  82. data/docs/doc/PhraseParser.html +1 -1
  83. data/docs/doc/Status.html +1 -1
  84. data/docs/doc/String.html +1 -1
  85. data/docs/doc/Symbol.html +1 -1
  86. data/docs/doc/Time.html +1 -1
  87. data/docs/doc/TrueClass.html +1 -1
  88. data/docs/doc/_index.html +12 -10
  89. data/docs/doc/class_list.html +1 -1
  90. data/docs/doc/file.README.html +2 -2
  91. data/docs/doc/index.html +2 -2
  92. data/docs/doc/method_list.html +424 -304
  93. data/docs/doc/top-level-namespace.html +105 -1
  94. data/docs/index.md +1 -1
  95. data/doing.gemspec +24 -24
  96. data/doing.rdoc +259 -26
  97. data/example_plugin.rb +7 -5
  98. data/inputrc +57 -0
  99. data/lib/completion/_doing.zsh +48 -52
  100. data/lib/completion/doing.bash +14 -25
  101. data/lib/completion/doing.fish +41 -15
  102. data/lib/doing/add_options.rb +152 -0
  103. data/lib/doing/array/array.rb +16 -0
  104. data/lib/doing/changelog/changes.rb +1 -1
  105. data/lib/doing/chronify/string.rb +1 -1
  106. data/lib/doing/completion/bash_completion.rb +12 -51
  107. data/lib/doing/completion/fish_completion.rb +17 -53
  108. data/lib/doing/completion/zsh_completion.rb +21 -59
  109. data/lib/doing/completion.rb +203 -17
  110. data/lib/doing/configuration.rb +7 -1
  111. data/lib/doing/item.rb +30 -5
  112. data/lib/doing/items.rb +53 -5
  113. data/lib/doing/{log_adapter.rb → logger.rb} +8 -2
  114. data/lib/doing/note.rb +24 -8
  115. data/lib/doing/plugins/export/dayone_export.rb +8 -6
  116. data/lib/doing/plugins/export/html_export.rb +4 -4
  117. data/lib/doing/plugins/export/json_export.rb +19 -20
  118. data/lib/doing/plugins/export/markdown_export.rb +2 -2
  119. data/lib/doing/plugins/export/template_export.rb +4 -4
  120. data/lib/doing/plugins/import/calendar_import.rb +2 -2
  121. data/lib/doing/plugins/import/doing_import.rb +2 -2
  122. data/lib/doing/plugins/import/timing_import.rb +2 -2
  123. data/lib/doing/string/highlight.rb +3 -4
  124. data/lib/doing/string/string.rb +8 -0
  125. data/lib/doing/string/tags.rb +1 -1
  126. data/lib/doing/types.rb +2 -2
  127. data/lib/doing/util.rb +1 -1
  128. data/lib/doing/util_backup.rb +12 -12
  129. data/lib/doing/version.rb +1 -1
  130. data/lib/doing/wwid.rb +119 -120
  131. data/lib/doing.rb +61 -3
  132. data/lib/examples/commands/wiki.rb +27 -19
  133. data/lib/examples/plugins/capture_thing_import.rb +1 -1
  134. data/lib/helpers/threaded_tests.rb +2 -0
  135. data/scripts/setting_replace.rb +11 -0
  136. metadata +109 -124
  137. data/.yardoc/checksums +0 -29
  138. data/.yardoc/complete +0 -0
  139. data/.yardoc/object_types +0 -0
  140. data/.yardoc/objects/root.dat +0 -0
  141. data/.yardoc/proxy_types +0 -0
  142. data/bin/commands/add_section.rb +0 -15
data/lib/doing/wwid.rb CHANGED
@@ -14,7 +14,7 @@ module Doing
14
14
  class WWID
15
15
  attr_reader :additional_configs, :current_section, :doing_file, :content
16
16
 
17
- attr_accessor :config, :config_file, :auto_tag, :default_option
17
+ attr_accessor :config, :config_file, :default_option
18
18
 
19
19
  include Color
20
20
  # include Util
@@ -26,7 +26,12 @@ module Doing
26
26
  @timers = {}
27
27
  @recorded_items = []
28
28
  @content = Items.new
29
- @auto_tag = true
29
+ Doing.auto_tag = true
30
+ end
31
+
32
+ # For backwards compatibility where @wwid.config was accessed instead of Doing.config.settings
33
+ def config
34
+ Doing.config.settings
30
35
  end
31
36
 
32
37
  ##
@@ -48,7 +53,7 @@ module Doing
48
53
  ## @param path [String] Override path to a doing file, optional
49
54
  ##
50
55
  def init_doing_file(path = nil)
51
- @doing_file = File.expand_path(@config['doing_file'])
56
+ @doing_file = File.expand_path(Doing.setting('doing_file'))
52
57
 
53
58
  if path.nil?
54
59
  create(@doing_file) unless File.exist?(@doing_file)
@@ -77,7 +82,7 @@ module Doing
77
82
  lines.each do |line|
78
83
  next if line =~ /^\s*$/
79
84
 
80
- if line =~ /^(\S[\S ]+):( .*)?$/
85
+ if line =~ /^(\S[\S ]+):\s*(@\S+\s*)*$/
81
86
  section = Regexp.last_match(1)
82
87
  @content.add_section(Section.new(section, original: line), log: false)
83
88
  elsif line =~ /^\s*- (\d{4}-\d\d-\d\d \d\d:\d\d) \| (.*)/
@@ -117,7 +122,7 @@ module Doing
117
122
  FileUtils.mkdir_p(File.dirname(filename)) unless File.directory?(File.dirname(filename))
118
123
 
119
124
  File.open(filename, 'w+') do |f|
120
- f.puts "#{@config['current_section']}:"
125
+ f.puts "#{Doing.setting('current_section')}:"
121
126
  end
122
127
  end
123
128
 
@@ -189,7 +194,7 @@ module Doing
189
194
 
190
195
  raise EmptyInput, 'No content' if title.sub(/^.*?\| */, '').strip.empty?
191
196
 
192
- title.expand_date_tags(@config['date_tags'])
197
+ title.expand_date_tags(Doing.setting('date_tags'))
193
198
 
194
199
  if title =~ date_rx
195
200
  m = title.match(date_rx)
@@ -236,7 +241,7 @@ module Doing
236
241
  ##
237
242
  def guess_section(frag, guessed: false, suggest: false)
238
243
  return 'All' if frag =~ /^all$/i
239
- frag ||= @config['current_section']
244
+ frag ||= Doing.setting('current_section')
240
245
 
241
246
  return frag.cap_first if @content.section?(frag)
242
247
 
@@ -342,7 +347,7 @@ module Doing
342
347
  ##
343
348
  def add_item(title, section = nil, opt)
344
349
  opt ||= {}
345
- section ||= @config['current_section']
350
+ section ||= Doing.setting('current_section')
346
351
  @content.add_section(section, log: false)
347
352
  opt[:back] ||= opt[:date] ? opt[:date] : Time.now
348
353
  opt[:date] ||= Time.now
@@ -354,9 +359,9 @@ module Doing
354
359
  title = [title.strip.cap_first]
355
360
  title = title.join(' ')
356
361
 
357
- if @auto_tag
362
+ if Doing.auto_tag
358
363
  title = autotag(title)
359
- title.add_tags!(@config['default_tags']) unless @config['default_tags'].empty?
364
+ title.add_tags!(Doing.setting('default_tags')) if Doing.setting('default_tags').good?
360
365
  end
361
366
 
362
367
  title.compress!
@@ -499,7 +504,7 @@ module Doing
499
504
  # Remove @done tag
500
505
  title = item.title.sub(/\s*@done(\(.*?\))?/, '').chomp
501
506
  section = opt[:in].nil? ? item.section : guess_section(opt[:in])
502
- @auto_tag = false
507
+ Doing.auto_tag = false
503
508
 
504
509
  note = opt[:note] || Note.new
505
510
 
@@ -553,7 +558,7 @@ module Doing
553
558
  def last_entry(opt)
554
559
  opt ||= {}
555
560
  opt[:tag_bool] ||= :and
556
- opt[:section] ||= @config['current_section']
561
+ opt[:section] ||= Doing.setting('current_section')
557
562
 
558
563
  items = filter_items(Items.new, opt: opt)
559
564
 
@@ -667,12 +672,14 @@ module Doing
667
672
  items = section =~ /^all$/i ? @content.clone : @content.in_section(section)
668
673
  end
669
674
 
670
- opt[:time_filter] = [nil, nil]
671
- if opt[:from] && !opt[:date_filter]
672
- if opt[:from][0].is_a?(String) && opt[:from][0] =~ time_rx
673
- opt[:time_filter] = opt[:from]
674
- elsif opt[:from][0].is_a?(Time)
675
- opt[:date_filter] = opt[:from]
675
+ if !opt[:time_filter]
676
+ opt[:time_filter] = [nil, nil]
677
+ if opt[:from] && !opt[:date_filter]
678
+ if opt[:from][0].is_a?(String) && opt[:from][0] =~ time_rx
679
+ opt[:time_filter] = opt[:from]
680
+ elsif opt[:from][0].is_a?(Time)
681
+ opt[:date_filter] = opt[:from]
682
+ end
676
683
  end
677
684
  end
678
685
 
@@ -1076,7 +1083,7 @@ module Doing
1076
1083
  end
1077
1084
 
1078
1085
  if opt[:flag]
1079
- tag = @config['marker_tag'] || 'flagged'
1086
+ tag = Doing.setting('marker_tag', 'flagged')
1080
1087
  items.map! do |i|
1081
1088
  old_item = i.clone
1082
1089
  i.tag(tag, date: false, remove: opt[:remove], single: single)
@@ -1117,7 +1124,7 @@ module Doing
1117
1124
  items.map! do |i|
1118
1125
  old_item = i.clone
1119
1126
  i.tag(tag, date: false, remove: opt[:remove], single: single)
1120
- i.expand_date_tags(@config['date_tags'])
1127
+ i.expand_date_tags(Doing.setting('date_tags'))
1121
1128
  Hooks.trigger :post_entry_updated, self, i, old_item
1122
1129
  end
1123
1130
  end
@@ -1176,7 +1183,7 @@ module Doing
1176
1183
  end
1177
1184
 
1178
1185
  def verify_duration(date, finish_date, title: nil)
1179
- max_elapsed = @config.dig('interaction', 'confirm_longer_than') || 0
1186
+ max_elapsed = Doing.setting('interaction.confirm_longer_than', 0)
1180
1187
  max_elapsed = max_elapsed.chronify_qty if max_elapsed.is_a?(String)
1181
1188
  date = date.chronify(guess: :end, context: :today) if finish_date.is_a?(String)
1182
1189
 
@@ -1255,7 +1262,7 @@ module Doing
1255
1262
  removed = []
1256
1263
 
1257
1264
  if opt[:autotag]
1258
- new_title = autotag(item.title) if @auto_tag
1265
+ new_title = autotag(item.title) if Doing.auto_tag
1259
1266
  if new_title == item.title
1260
1267
  logger.count(:skipped, level: :debug, message: '%count unchaged %items')
1261
1268
  # logger.debug('Autotag:', 'No changes')
@@ -1289,7 +1296,7 @@ module Doing
1289
1296
  tag = tag.strip
1290
1297
 
1291
1298
  if tag =~ /^done$/ && opt[:date] && item.should_time?
1292
- max_elapsed = @config.dig('interaction', 'confirm_longer_than') || 0
1299
+ max_elapsed = Doing.setting('interaction.confirm_longer_than', 0)
1293
1300
  max_elapsed = max_elapsed.chronify_qty if max_elapsed.is_a?(String)
1294
1301
  elapsed = done_date - item.date
1295
1302
 
@@ -1344,7 +1351,7 @@ module Doing
1344
1351
  logger.warn('Skipped:', 'Archiving is skipped when operating on all entries')
1345
1352
  end
1346
1353
 
1347
- item.expand_date_tags(@config['date_tags'])
1354
+ item.expand_date_tags(Doing.setting('date_tags'))
1348
1355
  Hooks.trigger :post_entry_updated, self, item, old_item
1349
1356
  end
1350
1357
 
@@ -1424,7 +1431,7 @@ module Doing
1424
1431
  def stop_start(target_tag, opt)
1425
1432
  opt ||= {}
1426
1433
  tag = target_tag.dup
1427
- opt[:section] ||= @config['current_section']
1434
+ opt[:section] ||= Doing.setting('current_section')
1428
1435
  opt[:archive] ||= false
1429
1436
  opt[:back] ||= Time.now
1430
1437
  opt[:new_item] ||= false
@@ -1485,7 +1492,7 @@ module Doing
1485
1492
  $stdout.puts output
1486
1493
  else
1487
1494
  Util.write_to_file(file, output, backup: backup)
1488
- run_after if @config.key?('run_after')
1495
+ run_after if Doing.setting('run_after')
1489
1496
  end
1490
1497
  end
1491
1498
 
@@ -1593,7 +1600,7 @@ module Doing
1593
1600
  ## @return [Array] View names
1594
1601
  ##
1595
1602
  def views
1596
- @config.has_key?('views') ? @config['views'].keys : []
1603
+ Doing.setting('views') ? Doing.setting('views').keys : []
1597
1604
  end
1598
1605
 
1599
1606
  ##
@@ -1612,7 +1619,7 @@ module Doing
1612
1619
  ## @param title [String] The title of the view to retrieve
1613
1620
  ##
1614
1621
  def get_view(title)
1615
- return @config['views'][title] if @config['views'].has_key?(title)
1622
+ return Doing.setting(['views', title], nil)
1616
1623
 
1617
1624
  false
1618
1625
  end
@@ -1626,21 +1633,21 @@ module Doing
1626
1633
  logger.benchmark(:list_section, :start)
1627
1634
  opt[:config_template] ||= 'default'
1628
1635
 
1629
- tpl_cfg = @config.dig('templates', opt[:config_template])
1636
+ tpl_cfg = Doing.setting(['templates', opt[:config_template]])
1630
1637
 
1631
1638
  cfg = if opt[:view_template]
1632
- @config.dig('views', opt[:view_template]).deep_merge(tpl_cfg, { extend_existing_arrays: true, sort_merged_arrays: true })
1639
+ Doing.setting(['views', opt[:view_template]]).deep_merge(tpl_cfg, { extend_existing_arrays: true, sort_merged_arrays: true })
1633
1640
  else
1634
1641
  tpl_cfg
1635
1642
  end
1636
1643
 
1637
1644
  cfg.deep_merge({
1638
- 'wrap_width' => @config['wrap_width'] || 0,
1639
- 'date_format' => @config['default_date_format'],
1640
- 'order' => @config['order'] || :asc,
1641
- 'tags_color' => @config['tags_color'],
1642
- 'duration' => @config['duration'],
1643
- 'interval_format' => @config['interval_format']
1645
+ 'wrap_width' => Doing.setting('wrap_width') || 0,
1646
+ 'date_format' => Doing.setting('default_date_format'),
1647
+ 'order' => Doing.setting('order') || :asc,
1648
+ 'tags_color' => Doing.setting('tags_color'),
1649
+ 'duration' => Doing.setting('duration'),
1650
+ 'interval_format' => Doing.setting('interval_format')
1644
1651
  }, { extend_existing_arrays: true, sort_merged_arrays: true })
1645
1652
 
1646
1653
  opt[:duration] ||= cfg['duration'] || false
@@ -1653,6 +1660,7 @@ module Doing
1653
1660
  opt[:tag_order] ||= :asc
1654
1661
  opt[:tags_color] = cfg['tags_color'] || false if opt[:tags_color].nil?
1655
1662
  opt[:template] ||= cfg['template']
1663
+ opt[:sort_tags] ||= opt[:tag_sort]
1656
1664
 
1657
1665
  # opt[:highlight] ||= true
1658
1666
  title = ''
@@ -1697,7 +1705,7 @@ module Doing
1697
1705
  end
1698
1706
 
1699
1707
  opt[:output] ||= 'template'
1700
- opt[:wrap_width] ||= @config['templates']['default']['wrap_width'] || 0
1708
+ opt[:wrap_width] ||= Doing.setting('templates.default.wrap_width', 0)
1701
1709
 
1702
1710
  logger.benchmark(:list_section, :finish)
1703
1711
  output(items, title, is_single, opt)
@@ -1710,7 +1718,7 @@ module Doing
1710
1718
  ## @param section [String] The source section
1711
1719
  ## @param options [Hash] Options
1712
1720
  ##
1713
- def archive(section = @config['current_section'], options)
1721
+ def archive(section = Doing.setting('current_section'), options)
1714
1722
  options ||= {}
1715
1723
  count = options[:keep] || 0
1716
1724
  destination = options[:destination] || 'Archive'
@@ -1746,13 +1754,13 @@ module Doing
1746
1754
  opt[:totals] ||= false
1747
1755
  opt[:sort_tags] ||= false
1748
1756
 
1749
- cfg = @config['templates'][opt[:config_template]].deep_merge(@config['templates']['default'], { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
1750
- 'wrap_width' => @config['wrap_width'] || 0,
1751
- 'date_format' => @config['default_date_format'],
1752
- 'order' => @config['order'] || :asc,
1753
- 'tags_color' => @config['tags_color'],
1754
- 'duration' => @config['duration'],
1755
- 'interval_format' => @config['interval_format']
1757
+ cfg = Doing.setting('templates').deep_merge(Doing.setting('templates.default'), { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
1758
+ 'wrap_width' => Doing.setting('wrap_width') || 0,
1759
+ 'date_format' => Doing.setting('default_date_format'),
1760
+ 'order' => Doing.setting('order') || :asc,
1761
+ 'tags_color' => Doing.setting('tags_color'),
1762
+ 'duration' => Doing.setting('duration'),
1763
+ 'interval_format' => Doing.setting('interval_format')
1756
1764
  }, { extend_existing_arrays: true, sort_merged_arrays: true })
1757
1765
 
1758
1766
  template = opt[:template] || cfg['template']
@@ -1768,6 +1776,7 @@ module Doing
1768
1776
  from: opt[:from],
1769
1777
  format: cfg['date_format'],
1770
1778
  interval_format: opt[:interval_format],
1779
+ only_timed: opt[:only_timed],
1771
1780
  order: cfg['order'] || :asc,
1772
1781
  output: output,
1773
1782
  section: opt[:section],
@@ -1800,19 +1809,19 @@ module Doing
1800
1809
  # :date_filter expects an array with start and end date
1801
1810
  dates = dates.split_date_range if dates.instance_of?(String)
1802
1811
 
1803
- list_section({
1804
- section: section,
1805
- count: 0,
1806
- order: :asc,
1807
- date_filter: dates,
1808
- times: times,
1809
- output: output,
1810
- totals: opt[:totals],
1811
- duration: opt[:duration],
1812
- sort_tags: opt[:sort_tags],
1813
- template: opt[:template],
1814
- config_template: opt[:config_template]
1815
- })
1812
+ opt[:section] = section
1813
+ opt[:count] = 0
1814
+ opt[:order] = :asc
1815
+ opt[:date_filter] = dates
1816
+ opt[:times] = times
1817
+ opt[:output] = output
1818
+
1819
+ time_rx = /^(\d{1,2}+(:\d{1,2}+)?( *(am|pm))?|midnight|noon)$/
1820
+ if opt[:from] && opt[:from][0].is_a?(String) && opt[:from][0] =~ time_rx
1821
+ opt[:time_filter] = opt[:from]
1822
+ end
1823
+
1824
+ list_section(opt)
1816
1825
  end
1817
1826
 
1818
1827
  ##
@@ -1827,30 +1836,20 @@ module Doing
1827
1836
  opt ||= {}
1828
1837
  opt[:totals] ||= false
1829
1838
  opt[:sort_tags] ||= false
1839
+ opt[:config_template] ||= 'today'
1840
+ opt[:yesterday] = true
1841
+
1830
1842
  section = guess_section(section)
1831
1843
  y = (Time.now - (60 * 60 * 24)).strftime('%Y-%m-%d')
1832
1844
  opt[:after] = "#{y} #{opt[:after]}" if opt[:after]
1833
1845
  opt[:before] = "#{y} #{opt[:before]}" if opt[:before]
1834
1846
 
1835
- options = {
1836
- after: opt[:after],
1837
- before: opt[:before],
1838
- count: 0,
1839
- duration: opt[:duration],
1840
- from: opt[:from],
1841
- order: opt[:order],
1842
- output: output,
1843
- section: section,
1844
- sort_tags: opt[:sort_tags],
1845
- tag_order: opt[:tag_order],
1846
- times: times,
1847
- totals: opt[:totals],
1848
- yesterday: true,
1849
- config_template: opt[:config_template] || 'today',
1850
- template: opt[:template]
1851
- }
1847
+ opt[:output] = output
1848
+ opt[:section] = section
1849
+ opt[:times] = times
1850
+ opt[:count] = 0
1852
1851
 
1853
- list_section(options)
1852
+ list_section(opt)
1854
1853
  end
1855
1854
 
1856
1855
  ##
@@ -1866,18 +1865,18 @@ module Doing
1866
1865
  opt[:totals] ||= false
1867
1866
  opt[:sort_tags] ||= false
1868
1867
 
1869
- cfg = @config['templates'][opt[:config_template]].deep_merge(@config['templates']['default'], { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
1870
- 'wrap_width' => @config['wrap_width'] || 0,
1871
- 'date_format' => @config['default_date_format'],
1872
- 'order' => @config['order'] || :asc,
1873
- 'tags_color' => @config['tags_color'],
1874
- 'duration' => @config['duration'],
1875
- 'interval_format' => @config['interval_format']
1868
+ cfg = Doing.setting('templates.recent').deep_merge(Doing.setting('templates.default'), { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
1869
+ 'wrap_width' => Doing.setting('wrap_width') || 0,
1870
+ 'date_format' => Doing.setting('default_date_format'),
1871
+ 'order' => Doing.setting('order') || :asc,
1872
+ 'tags_color' => Doing.setting('tags_color'),
1873
+ 'duration' => Doing.setting('duration'),
1874
+ 'interval_format' => Doing.setting('interval_format')
1876
1875
  }, { extend_existing_arrays: true, sort_merged_arrays: true })
1877
1876
  opt[:duration] ||= cfg['duration'] || false
1878
1877
  opt[:interval_format] ||= cfg['interval_format'] || 'text'
1879
1878
 
1880
- section ||= @config['current_section']
1879
+ section ||= Doing.setting('current_section')
1881
1880
  section = guess_section(section)
1882
1881
 
1883
1882
  opt[:section] = section
@@ -1899,13 +1898,13 @@ module Doing
1899
1898
  ##
1900
1899
  def last(times: true, section: nil, options: {})
1901
1900
  section = section.nil? || section =~ /all/i ? 'All' : guess_section(section)
1902
- cfg = @config['templates'][options[:config_template]].deep_merge(@config['templates']['default'], { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
1903
- 'wrap_width' => @config['wrap_width'] || 0,
1904
- 'date_format' => @config['default_date_format'],
1905
- 'order' => @config['order'] || :asc,
1906
- 'tags_color' => @config['tags_color'],
1907
- 'duration' => @config['duration'],
1908
- 'interval_format' => @config['interval_format']
1901
+ cfg = Doing.setting(['templates', options[:config_template]]).deep_merge(Doing.setting('templates.default'), { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
1902
+ 'wrap_width' => Doing.setting('wrap_width', 0),
1903
+ 'date_format' => Doing.setting('default_date_format'),
1904
+ 'order' => Doing.setting('order', :asc),
1905
+ 'tags_color' => Doing.setting('tags_color'),
1906
+ 'duration' => Doing.setting('duration'),
1907
+ 'interval_format' => Doing.setting('interval_format')
1909
1908
  }, { extend_existing_arrays: true, sort_merged_arrays: true })
1910
1909
  options[:duration] ||= cfg['duration'] || false
1911
1910
  options[:interval_format] ||= cfg['interval_format'] || 'text'
@@ -1947,7 +1946,7 @@ module Doing
1947
1946
  ##
1948
1947
  def autotag(string)
1949
1948
  return unless string
1950
- return string unless @auto_tag
1949
+ return string unless Doing.auto_tag
1951
1950
 
1952
1951
  original = string.dup
1953
1952
  text = string.dup
@@ -1960,7 +1959,7 @@ module Doing
1960
1959
  replaced: []
1961
1960
  }
1962
1961
 
1963
- @config['autotag']['whitelist'].each do |tag|
1962
+ Doing.setting('autotag.whitelist').each do |tag|
1964
1963
  next if text =~ /@#{tag}\b/i
1965
1964
 
1966
1965
  text.sub!(/(?<= |\A)(#{tag.strip})(?= |\Z)/i) do |m|
@@ -1970,7 +1969,7 @@ module Doing
1970
1969
  end
1971
1970
  end
1972
1971
 
1973
- @config['autotag']['synonyms'].each do |tag, v|
1972
+ Doing.setting('autotag.synonyms').each do |tag, v|
1974
1973
  v.each do |word|
1975
1974
  word = word.wildcard_to_rx
1976
1975
  next unless text =~ /\b#{word}\b/i
@@ -1982,8 +1981,8 @@ module Doing
1982
1981
  end
1983
1982
  end
1984
1983
 
1985
- if @config['autotag'].key? 'transform'
1986
- @config['autotag']['transform'].each do |tag|
1984
+ if Doing.setting('autotag.transform')
1985
+ Doing.setting('autotag.transform').each do |tag|
1987
1986
  next unless tag =~ /\S+:\S+/
1988
1987
 
1989
1988
  if tag =~ /::/
@@ -2055,11 +2054,11 @@ module Doing
2055
2054
  def tag_times(format: :text, sort_by: :time, sort_order: :asc)
2056
2055
  return '' if @timers.empty?
2057
2056
 
2058
- max = @timers.keys.sort_by { |k| k.length }.reverse[0].length + 1
2057
+ max = @timers.keys.sort_by(&:length).reverse[0].length + 1
2059
2058
 
2060
2059
  total = @timers.delete('All')
2061
2060
 
2062
- tags_data = @timers.delete_if { |_k, v| v == 0 }
2061
+ tags_data = @timers.delete_if { |_k, v| v.zero? }
2063
2062
  sorted_tags_data = if sort_by.normalize_tag_sort == :name
2064
2063
  tags_data.sort_by { |k, _v| k }
2065
2064
  else
@@ -2070,7 +2069,7 @@ module Doing
2070
2069
  case format
2071
2070
  when :html
2072
2071
 
2073
- output = <<EOS
2072
+ output = <<EOHEAD
2074
2073
  <table>
2075
2074
  <caption id="tagtotals">Tag Totals</caption>
2076
2075
  <colgroup>
@@ -2084,13 +2083,13 @@ module Doing
2084
2083
  </tr>
2085
2084
  </thead>
2086
2085
  <tbody>
2087
- EOS
2086
+ EOHEAD
2088
2087
  sorted_tags_data.reverse.each do |k, v|
2089
- if v > 0
2088
+ if v.positive?
2090
2089
  output += "<tr><td style='text-align:left;'>#{k}</td><td style='text-align:left;'>#{v.time_string(format: :clock)}</td></tr>\n"
2091
2090
  end
2092
2091
  end
2093
- tail = <<EOS
2092
+ tail = <<EOTAIL
2094
2093
  <tr>
2095
2094
  <td style="text-align:left;" colspan="2"></td>
2096
2095
  </tr>
@@ -2102,21 +2101,21 @@ EOS
2102
2101
  </tr>
2103
2102
  </tfoot>
2104
2103
  </table>
2105
- EOS
2104
+ EOTAIL
2106
2105
  output + tail
2107
2106
  when :markdown
2108
- pad = sorted_tags_data.map {|k, v| k }.group_by(&:size).max.last[0].length
2107
+ pad = sorted_tags_data.map { |k, _| k }.group_by(&:size).max.last[0].length
2109
2108
  pad = 7 if pad < 7
2110
- output = <<~EOS
2111
- | #{' ' * (pad - 7) }project | time |
2109
+ output = <<~EOHEADER
2110
+ | #{' ' * (pad - 7)}project | time |
2112
2111
  | #{'-' * (pad - 1)}: | :------- |
2113
- EOS
2112
+ EOHEADER
2114
2113
  sorted_tags_data.reverse.each do |k, v|
2115
- if v > 0
2114
+ if v.positive?
2116
2115
  output += "| #{' ' * (pad - k.length)}#{k} | #{v.time_string(format: :clock)} |\n"
2117
2116
  end
2118
2117
  end
2119
- tail = "[Tag Totals]"
2118
+ tail = '[Tag Totals]'
2120
2119
  output + tail
2121
2120
  when :json
2122
2121
  output = []
@@ -2203,38 +2202,37 @@ EOS
2203
2202
  end
2204
2203
 
2205
2204
  ##
2206
- ## Load configuration files and updated the @config
2205
+ ## Load configuration files and updated the @settings
2207
2206
  ## attribute with a Doing::Configuration object
2208
2207
  ##
2209
2208
  ## @param filename [String] (optional) path to
2210
2209
  ## alternative config file
2211
2210
  ##
2212
2211
  def configure(filename = nil)
2212
+ logger.benchmark(:configure, :start)
2213
+
2213
2214
  if filename
2214
2215
  Doing.config_with(filename, { ignore_local: true })
2215
2216
  elsif ENV['DOING_CONFIG']
2216
2217
  Doing.config_with(ENV['DOING_CONFIG'], { ignore_local: true })
2217
2218
  end
2218
2219
 
2219
- logger.benchmark(:configure, :start)
2220
- config = Doing.config
2221
2220
  logger.benchmark(:configure, :finish)
2222
2221
 
2223
- config.settings['backup_dir'] = ENV['DOING_BACKUP_DIR'] if ENV['DOING_BACKUP_DIR']
2224
- @config = config.settings
2222
+ Doing.set('backup_dir', ENV['DOING_BACKUP_DIR']) if ENV['DOING_BACKUP_DIR']
2225
2223
  end
2226
2224
 
2227
2225
  def get_diff(filename = nil)
2228
- configure if @config.nil?
2226
+ configure if Doing.settings.nil?
2229
2227
 
2230
- filename ||= @config['doing_file']
2228
+ filename ||= Doing.setting('doing_file')
2231
2229
  init_doing_file(filename)
2232
2230
  current_content = @content.clone
2233
2231
  backup_file = Util::Backup.last_backup(filename, count: 1)
2234
2232
  raise DoingRuntimeError, 'No undo history to diff' if backup_file.nil?
2235
2233
 
2236
2234
  backup = WWID.new
2237
- backup.config = @config
2235
+ backup.config = Doing.settings
2238
2236
  backup.init_doing_file(backup_file)
2239
2237
  current_content.diff(backup.content)
2240
2238
  end
@@ -2251,6 +2249,7 @@ EOS
2251
2249
  output = @other_content_top ? "#{@other_content_top.join("\n")}\n" : ''
2252
2250
  was_color = Color.coloring?
2253
2251
  Color.coloring = false
2252
+ @content.dedup!(match_section: true)
2254
2253
  output += @content.to_s
2255
2254
  output += @other_content_bottom.join("\n") unless @other_content_bottom.nil?
2256
2255
  # Just strip all ANSI colors from the content before writing to doing file
@@ -2378,12 +2377,12 @@ EOS
2378
2377
  end
2379
2378
 
2380
2379
  def run_after
2381
- return unless @config.key?('run_after')
2380
+ return unless Doing.setting('run_after')
2382
2381
 
2383
- _, stderr, status = Open3.capture3(@config['run_after'])
2382
+ _, stderr, status = Open3.capture3(Doing.setting('run_after'))
2384
2383
  return unless status.exitstatus.positive?
2385
2384
 
2386
- logger.log_now(:error, 'Script error:', "Error running #{@config['run_after']}")
2385
+ logger.log_now(:error, 'Script error:', "Error running #{Doing.setting('run_after')}")
2387
2386
  logger.log_now(:error, 'STDERR output:', stderr)
2388
2387
  end
2389
2388
  end
data/lib/doing.rb CHANGED
@@ -42,7 +42,7 @@ require_relative 'doing/items'
42
42
  require_relative 'doing/note'
43
43
  require_relative 'doing/item'
44
44
  require_relative 'doing/wwid'
45
- require_relative 'doing/log_adapter'
45
+ require_relative 'doing/logger'
46
46
  require_relative 'doing/prompt'
47
47
  require_relative 'doing/errors'
48
48
  require_relative 'doing/hooks'
@@ -57,19 +57,77 @@ require_relative 'doing/chronify/chronify'
57
57
  # Main doing module
58
58
  module Doing
59
59
  class << self
60
+ attr_accessor :auto_tag
60
61
  #
61
62
  # Fetch the logger
62
63
  #
63
- # @return the LogAdapter instance.
64
+ # @return the Logger instance.
64
65
  #
65
66
  def logger
66
- @logger ||= LogAdapter.new((ENV['DOING_LOG_LEVEL'] || :info).to_sym)
67
+ @logger ||= Logger.new((ENV['DOING_LOG_LEVEL'] || :info).to_sym)
67
68
  end
68
69
 
70
+ ##
71
+ ## Holds a Configuration object with methods and a @settings hash
72
+ ##
73
+ ## @return [Configuration] Configuration object
74
+ ##
69
75
  def config
70
76
  @config ||= Configuration.new
71
77
  end
72
78
 
79
+ ##
80
+ ## Shortcut for Doing.config.settings
81
+ ##
82
+ ## @return [Hash] Settings hash
83
+ ##
84
+ def settings
85
+ config.settings
86
+ end
87
+
88
+ ##
89
+ ## Fetch a config setting using a dot-separated keypath
90
+ ## or array of keys
91
+ ##
92
+ ## @param keypath [String|Array] Either a
93
+ ## dot-separated key path
94
+ ## (search.case) or array of keys
95
+ ## (['search', 'case'])
96
+ ## @param default A default value to return if the
97
+ ## provided path returns nil result
98
+ ##
99
+ def setting(keypath, default = nil)
100
+ cfg = config.settings
101
+ case keypath
102
+ when Array
103
+ cfg.dig(*keypath) || default
104
+ when String
105
+ unless keypath =~ /^[.*]?$/
106
+ real_path = config.resolve_key_path(keypath, create: false)
107
+ return default unless real_path&.count&.positive?
108
+
109
+ cfg = cfg.dig(*real_path)
110
+ end
111
+
112
+ cfg.nil? ? default : cfg
113
+ end
114
+ end
115
+
116
+ def set(keypath, value)
117
+ real_path = config.resolve_key_path(keypath, create: false)
118
+ return nil unless real_path&.count&.positive?
119
+
120
+ config.settings.deep_set(real_path, value)
121
+ end
122
+
123
+ ##
124
+ ## Update configuration from specified file
125
+ ##
126
+ ## @param file [String] Path to new config file
127
+ ## @param options [Hash] options
128
+ ##
129
+ ## @option options :ignore_local Ignore local configuration files
130
+ ##
73
131
  def config_with(file, options = {})
74
132
  @config = Configuration.new(file, options: options)
75
133
  end