doing 2.1.29 → 2.1.33

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 (136) hide show
  1. checksums.yaml +4 -4
  2. data/.irbrc +1 -0
  3. data/CHANGELOG.md +4962 -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 +2 -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/changes.rb +2 -2
  14. data/bin/commands/commands.rb +8 -8
  15. data/bin/commands/completion.rb +61 -19
  16. data/bin/commands/config.rb +20 -17
  17. data/bin/commands/done.rb +1 -1
  18. data/bin/commands/flag.rb +1 -1
  19. data/bin/commands/grep.rb +5 -5
  20. data/bin/commands/last.rb +1 -1
  21. data/bin/commands/meanwhile.rb +1 -1
  22. data/bin/commands/now.rb +1 -1
  23. data/bin/commands/on.rb +1 -1
  24. data/bin/commands/open.rb +4 -4
  25. data/bin/commands/recent.rb +4 -4
  26. data/bin/commands/show.rb +8 -8
  27. data/bin/commands/since.rb +1 -1
  28. data/bin/commands/tag_dir.rb +27 -3
  29. data/bin/commands/today.rb +1 -1
  30. data/bin/commands/view.rb +3 -3
  31. data/bin/commands/yesterday.rb +2 -2
  32. data/bin/doing +26 -135
  33. data/docs/doc/Array.html +1 -1
  34. data/docs/doc/BooleanTermParser/Clause.html +1 -1
  35. data/docs/doc/BooleanTermParser/Operator.html +1 -1
  36. data/docs/doc/BooleanTermParser/Query.html +1 -1
  37. data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
  38. data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
  39. data/docs/doc/BooleanTermParser.html +1 -1
  40. data/docs/doc/Doing/Color.html +1 -1
  41. data/docs/doc/Doing/Completion.html +324 -4
  42. data/docs/doc/Doing/Configuration.html +3 -3
  43. data/docs/doc/Doing/Errors/DoingNoTraceError.html +1 -1
  44. data/docs/doc/Doing/Errors/DoingRuntimeError.html +1 -1
  45. data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
  46. data/docs/doc/Doing/Errors/EmptyInput.html +1 -1
  47. data/docs/doc/Doing/Errors/NoResults.html +1 -1
  48. data/docs/doc/Doing/Errors/PluginException.html +1 -1
  49. data/docs/doc/Doing/Errors/UserCancelled.html +1 -1
  50. data/docs/doc/Doing/Errors/WrongCommand.html +1 -1
  51. data/docs/doc/Doing/Errors.html +1 -1
  52. data/docs/doc/Doing/Hooks.html +1 -1
  53. data/docs/doc/Doing/Item.html +125 -1
  54. data/docs/doc/Doing/Items.html +1 -1
  55. data/docs/doc/Doing/LogAdapter.html +1 -1
  56. data/docs/doc/Doing/Note.html +109 -3
  57. data/docs/doc/Doing/Pager.html +1 -1
  58. data/docs/doc/Doing/Plugins.html +1 -1
  59. data/docs/doc/Doing/Prompt.html +1 -1
  60. data/docs/doc/Doing/Section.html +1 -1
  61. data/docs/doc/Doing/TemplateString.html +1 -1
  62. data/docs/doc/Doing/Types.html +2 -2
  63. data/docs/doc/Doing/Util/Backup.html +1 -1
  64. data/docs/doc/Doing/Util.html +1 -1
  65. data/docs/doc/Doing/WWID.html +6 -6
  66. data/docs/doc/Doing.html +2 -2
  67. data/docs/doc/FalseClass.html +1 -1
  68. data/docs/doc/GLI/Commands/Help.html +1 -1
  69. data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
  70. data/docs/doc/GLI/Commands.html +1 -1
  71. data/docs/doc/GLI.html +1 -1
  72. data/docs/doc/Hash.html +1 -1
  73. data/docs/doc/Object.html +1 -1
  74. data/docs/doc/PhraseParser/Operator.html +1 -1
  75. data/docs/doc/PhraseParser/PhraseClause.html +1 -1
  76. data/docs/doc/PhraseParser/Query.html +1 -1
  77. data/docs/doc/PhraseParser/QueryParser.html +1 -1
  78. data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
  79. data/docs/doc/PhraseParser/TermClause.html +1 -1
  80. data/docs/doc/PhraseParser.html +1 -1
  81. data/docs/doc/Status.html +1 -1
  82. data/docs/doc/String.html +1 -1
  83. data/docs/doc/Symbol.html +1 -1
  84. data/docs/doc/Time.html +1 -1
  85. data/docs/doc/TrueClass.html +1 -1
  86. data/docs/doc/_index.html +3 -1
  87. data/docs/doc/file.README.html +2 -2
  88. data/docs/doc/index.html +2 -2
  89. data/docs/doc/method_list.html +337 -241
  90. data/docs/doc/top-level-namespace.html +105 -1
  91. data/docs/index.md +1 -1
  92. data/doing.gemspec +24 -23
  93. data/doing.rdoc +34 -13
  94. data/example_plugin.rb +7 -5
  95. data/inputrc +57 -0
  96. data/lib/completion/_doing.zsh +46 -46
  97. data/lib/completion/doing.bash +2 -2
  98. data/lib/completion/doing.fish +4 -4
  99. data/lib/doing/add_options.rb +117 -0
  100. data/lib/doing/array/array.rb +16 -0
  101. data/lib/doing/changelog/changes.rb +8 -6
  102. data/lib/doing/changelog/version.rb +11 -3
  103. data/lib/doing/completion/bash_completion.rb +12 -51
  104. data/lib/doing/completion/fish_completion.rb +17 -53
  105. data/lib/doing/completion/zsh_completion.rb +21 -54
  106. data/lib/doing/completion.rb +203 -17
  107. data/lib/doing/configuration.rb +12 -6
  108. data/lib/doing/item.rb +21 -3
  109. data/lib/doing/items.rb +5 -5
  110. data/lib/doing/note.rb +24 -8
  111. data/lib/doing/plugins/export/dayone_export.rb +8 -6
  112. data/lib/doing/plugins/export/html_export.rb +4 -4
  113. data/lib/doing/plugins/export/json_export.rb +19 -20
  114. data/lib/doing/plugins/export/markdown_export.rb +2 -2
  115. data/lib/doing/plugins/export/template_export.rb +4 -4
  116. data/lib/doing/plugins/import/calendar_import.rb +1 -1
  117. data/lib/doing/plugins/import/doing_import.rb +1 -1
  118. data/lib/doing/plugins/import/timing_import.rb +1 -1
  119. data/lib/doing/section.rb +1 -1
  120. data/lib/doing/string/highlight.rb +3 -4
  121. data/lib/doing/string/string.rb +8 -0
  122. data/lib/doing/types.rb +1 -1
  123. data/lib/doing/util.rb +1 -1
  124. data/lib/doing/util_backup.rb +12 -12
  125. data/lib/doing/version.rb +1 -1
  126. data/lib/doing/wwid.rb +75 -76
  127. data/lib/doing.rb +58 -0
  128. data/lib/examples/commands/wiki.rb +27 -19
  129. data/lib/helpers/threaded_tests.rb +2 -0
  130. data/scripts/setting_replace.rb +11 -0
  131. metadata +107 -102
  132. data/.yardoc/checksums +0 -29
  133. data/.yardoc/complete +0 -0
  134. data/.yardoc/object_types +0 -0
  135. data/.yardoc/objects/root.dat +0 -0
  136. data/.yardoc/proxy_types +0 -0
data/lib/doing/wwid.rb CHANGED
@@ -48,7 +48,7 @@ module Doing
48
48
  ## @param path [String] Override path to a doing file, optional
49
49
  ##
50
50
  def init_doing_file(path = nil)
51
- @doing_file = File.expand_path(@config['doing_file'])
51
+ @doing_file = File.expand_path(Doing.setting('doing_file'))
52
52
 
53
53
  if path.nil?
54
54
  create(@doing_file) unless File.exist?(@doing_file)
@@ -117,7 +117,7 @@ module Doing
117
117
  FileUtils.mkdir_p(File.dirname(filename)) unless File.directory?(File.dirname(filename))
118
118
 
119
119
  File.open(filename, 'w+') do |f|
120
- f.puts "#{@config['current_section']}:"
120
+ f.puts "#{Doing.setting('current_section')}:"
121
121
  end
122
122
  end
123
123
 
@@ -189,7 +189,7 @@ module Doing
189
189
 
190
190
  raise EmptyInput, 'No content' if title.sub(/^.*?\| */, '').strip.empty?
191
191
 
192
- title.expand_date_tags(@config['date_tags'])
192
+ title.expand_date_tags(Doing.setting('date_tags'))
193
193
 
194
194
  if title =~ date_rx
195
195
  m = title.match(date_rx)
@@ -236,7 +236,7 @@ module Doing
236
236
  ##
237
237
  def guess_section(frag, guessed: false, suggest: false)
238
238
  return 'All' if frag =~ /^all$/i
239
- frag ||= @config['current_section']
239
+ frag ||= Doing.setting('current_section')
240
240
 
241
241
  return frag.cap_first if @content.section?(frag)
242
242
 
@@ -342,7 +342,7 @@ module Doing
342
342
  ##
343
343
  def add_item(title, section = nil, opt)
344
344
  opt ||= {}
345
- section ||= @config['current_section']
345
+ section ||= Doing.setting('current_section')
346
346
  @content.add_section(section, log: false)
347
347
  opt[:back] ||= opt[:date] ? opt[:date] : Time.now
348
348
  opt[:date] ||= Time.now
@@ -356,7 +356,7 @@ module Doing
356
356
 
357
357
  if @auto_tag
358
358
  title = autotag(title)
359
- title.add_tags!(@config['default_tags']) unless @config['default_tags'].empty?
359
+ title.add_tags!(Doing.setting('default_tags')) if Doing.setting('default_tags').good?
360
360
  end
361
361
 
362
362
  title.compress!
@@ -553,7 +553,7 @@ module Doing
553
553
  def last_entry(opt)
554
554
  opt ||= {}
555
555
  opt[:tag_bool] ||= :and
556
- opt[:section] ||= @config['current_section']
556
+ opt[:section] ||= Doing.setting('current_section')
557
557
 
558
558
  items = filter_items(Items.new, opt: opt)
559
559
 
@@ -1076,7 +1076,7 @@ module Doing
1076
1076
  end
1077
1077
 
1078
1078
  if opt[:flag]
1079
- tag = @config['marker_tag'] || 'flagged'
1079
+ tag = Doing.setting('marker_tag', 'flagged')
1080
1080
  items.map! do |i|
1081
1081
  old_item = i.clone
1082
1082
  i.tag(tag, date: false, remove: opt[:remove], single: single)
@@ -1117,7 +1117,7 @@ module Doing
1117
1117
  items.map! do |i|
1118
1118
  old_item = i.clone
1119
1119
  i.tag(tag, date: false, remove: opt[:remove], single: single)
1120
- i.expand_date_tags(@config['date_tags'])
1120
+ i.expand_date_tags(Doing.setting('date_tags'))
1121
1121
  Hooks.trigger :post_entry_updated, self, i, old_item
1122
1122
  end
1123
1123
  end
@@ -1176,7 +1176,7 @@ module Doing
1176
1176
  end
1177
1177
 
1178
1178
  def verify_duration(date, finish_date, title: nil)
1179
- max_elapsed = @config.dig('interaction', 'confirm_longer_than') || 0
1179
+ max_elapsed = Doing.setting('interaction.confirm_longer_than', 0)
1180
1180
  max_elapsed = max_elapsed.chronify_qty if max_elapsed.is_a?(String)
1181
1181
  date = date.chronify(guess: :end, context: :today) if finish_date.is_a?(String)
1182
1182
 
@@ -1289,7 +1289,7 @@ module Doing
1289
1289
  tag = tag.strip
1290
1290
 
1291
1291
  if tag =~ /^done$/ && opt[:date] && item.should_time?
1292
- max_elapsed = @config.dig('interaction', 'confirm_longer_than') || 0
1292
+ max_elapsed = Doing.setting('interaction.confirm_longer_than', 0)
1293
1293
  max_elapsed = max_elapsed.chronify_qty if max_elapsed.is_a?(String)
1294
1294
  elapsed = done_date - item.date
1295
1295
 
@@ -1344,7 +1344,7 @@ module Doing
1344
1344
  logger.warn('Skipped:', 'Archiving is skipped when operating on all entries')
1345
1345
  end
1346
1346
 
1347
- item.expand_date_tags(@config['date_tags'])
1347
+ item.expand_date_tags(Doing.setting('date_tags'))
1348
1348
  Hooks.trigger :post_entry_updated, self, item, old_item
1349
1349
  end
1350
1350
 
@@ -1424,7 +1424,7 @@ module Doing
1424
1424
  def stop_start(target_tag, opt)
1425
1425
  opt ||= {}
1426
1426
  tag = target_tag.dup
1427
- opt[:section] ||= @config['current_section']
1427
+ opt[:section] ||= Doing.setting('current_section')
1428
1428
  opt[:archive] ||= false
1429
1429
  opt[:back] ||= Time.now
1430
1430
  opt[:new_item] ||= false
@@ -1485,7 +1485,7 @@ module Doing
1485
1485
  $stdout.puts output
1486
1486
  else
1487
1487
  Util.write_to_file(file, output, backup: backup)
1488
- run_after if @config.key?('run_after')
1488
+ run_after if Doing.setting('run_after')
1489
1489
  end
1490
1490
  end
1491
1491
 
@@ -1593,7 +1593,7 @@ module Doing
1593
1593
  ## @return [Array] View names
1594
1594
  ##
1595
1595
  def views
1596
- @config.has_key?('views') ? @config['views'].keys : []
1596
+ Doing.setting('views') ? Doing.setting('views').keys : []
1597
1597
  end
1598
1598
 
1599
1599
  ##
@@ -1612,7 +1612,7 @@ module Doing
1612
1612
  ## @param title [String] The title of the view to retrieve
1613
1613
  ##
1614
1614
  def get_view(title)
1615
- return @config['views'][title] if @config['views'].has_key?(title)
1615
+ return Doing.setting(['views', title], nil)
1616
1616
 
1617
1617
  false
1618
1618
  end
@@ -1626,21 +1626,21 @@ module Doing
1626
1626
  logger.benchmark(:list_section, :start)
1627
1627
  opt[:config_template] ||= 'default'
1628
1628
 
1629
- tpl_cfg = @config.dig('templates', opt[:config_template])
1629
+ tpl_cfg = Doing.setting(['templates', opt[:config_template]])
1630
1630
 
1631
1631
  cfg = if opt[:view_template]
1632
- @config.dig('views', opt[:view_template]).deep_merge(tpl_cfg, { extend_existing_arrays: true, sort_merged_arrays: true })
1632
+ Doing.setting(['views', opt[:view_template]]).deep_merge(tpl_cfg, { extend_existing_arrays: true, sort_merged_arrays: true })
1633
1633
  else
1634
1634
  tpl_cfg
1635
1635
  end
1636
1636
 
1637
1637
  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']
1638
+ 'wrap_width' => Doing.setting('wrap_width') || 0,
1639
+ 'date_format' => Doing.setting('default_date_format'),
1640
+ 'order' => Doing.setting('order') || :asc,
1641
+ 'tags_color' => Doing.setting('tags_color'),
1642
+ 'duration' => Doing.setting('duration'),
1643
+ 'interval_format' => Doing.setting('interval_format')
1644
1644
  }, { extend_existing_arrays: true, sort_merged_arrays: true })
1645
1645
 
1646
1646
  opt[:duration] ||= cfg['duration'] || false
@@ -1697,7 +1697,7 @@ module Doing
1697
1697
  end
1698
1698
 
1699
1699
  opt[:output] ||= 'template'
1700
- opt[:wrap_width] ||= @config['templates']['default']['wrap_width'] || 0
1700
+ opt[:wrap_width] ||= Doing.setting('templates.default.wrap_width', 0)
1701
1701
 
1702
1702
  logger.benchmark(:list_section, :finish)
1703
1703
  output(items, title, is_single, opt)
@@ -1710,7 +1710,7 @@ module Doing
1710
1710
  ## @param section [String] The source section
1711
1711
  ## @param options [Hash] Options
1712
1712
  ##
1713
- def archive(section = @config['current_section'], options)
1713
+ def archive(section = Doing.setting('current_section'), options)
1714
1714
  options ||= {}
1715
1715
  count = options[:keep] || 0
1716
1716
  destination = options[:destination] || 'Archive'
@@ -1746,13 +1746,13 @@ module Doing
1746
1746
  opt[:totals] ||= false
1747
1747
  opt[:sort_tags] ||= false
1748
1748
 
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']
1749
+ cfg = Doing.setting('templates').deep_merge(Doing.setting('templates.default'), { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
1750
+ 'wrap_width' => Doing.setting('wrap_width') || 0,
1751
+ 'date_format' => Doing.setting('default_date_format'),
1752
+ 'order' => Doing.setting('order') || :asc,
1753
+ 'tags_color' => Doing.setting('tags_color'),
1754
+ 'duration' => Doing.setting('duration'),
1755
+ 'interval_format' => Doing.setting('interval_format')
1756
1756
  }, { extend_existing_arrays: true, sort_merged_arrays: true })
1757
1757
 
1758
1758
  template = opt[:template] || cfg['template']
@@ -1866,18 +1866,18 @@ module Doing
1866
1866
  opt[:totals] ||= false
1867
1867
  opt[:sort_tags] ||= false
1868
1868
 
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']
1869
+ cfg = Doing.setting('templates.recent').deep_merge(Doing.setting('templates.default'), { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
1870
+ 'wrap_width' => Doing.setting('wrap_width') || 0,
1871
+ 'date_format' => Doing.setting('default_date_format'),
1872
+ 'order' => Doing.setting('order') || :asc,
1873
+ 'tags_color' => Doing.setting('tags_color'),
1874
+ 'duration' => Doing.setting('duration'),
1875
+ 'interval_format' => Doing.setting('interval_format')
1876
1876
  }, { extend_existing_arrays: true, sort_merged_arrays: true })
1877
1877
  opt[:duration] ||= cfg['duration'] || false
1878
1878
  opt[:interval_format] ||= cfg['interval_format'] || 'text'
1879
1879
 
1880
- section ||= @config['current_section']
1880
+ section ||= Doing.setting('current_section')
1881
1881
  section = guess_section(section)
1882
1882
 
1883
1883
  opt[:section] = section
@@ -1899,13 +1899,13 @@ module Doing
1899
1899
  ##
1900
1900
  def last(times: true, section: nil, options: {})
1901
1901
  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']
1902
+ cfg = Doing.setting(['templates', options[:config_template]]).deep_merge(Doing.setting('templates.default'), { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
1903
+ 'wrap_width' => Doing.setting('wrap_width', 0),
1904
+ 'date_format' => Doing.setting('default_date_format'),
1905
+ 'order' => Doing.setting('order', :asc),
1906
+ 'tags_color' => Doing.setting('tags_color'),
1907
+ 'duration' => Doing.setting('duration'),
1908
+ 'interval_format' => Doing.setting('interval_format')
1909
1909
  }, { extend_existing_arrays: true, sort_merged_arrays: true })
1910
1910
  options[:duration] ||= cfg['duration'] || false
1911
1911
  options[:interval_format] ||= cfg['interval_format'] || 'text'
@@ -1960,7 +1960,7 @@ module Doing
1960
1960
  replaced: []
1961
1961
  }
1962
1962
 
1963
- @config['autotag']['whitelist'].each do |tag|
1963
+ Doing.setting('autotag.whitelist').each do |tag|
1964
1964
  next if text =~ /@#{tag}\b/i
1965
1965
 
1966
1966
  text.sub!(/(?<= |\A)(#{tag.strip})(?= |\Z)/i) do |m|
@@ -1970,7 +1970,7 @@ module Doing
1970
1970
  end
1971
1971
  end
1972
1972
 
1973
- @config['autotag']['synonyms'].each do |tag, v|
1973
+ Doing.setting('autotag.synonyms').each do |tag, v|
1974
1974
  v.each do |word|
1975
1975
  word = word.wildcard_to_rx
1976
1976
  next unless text =~ /\b#{word}\b/i
@@ -1982,8 +1982,8 @@ module Doing
1982
1982
  end
1983
1983
  end
1984
1984
 
1985
- if @config['autotag'].key? 'transform'
1986
- @config['autotag']['transform'].each do |tag|
1985
+ if Doing.setting('autotag.transform')
1986
+ Doing.setting('autotag.transform').each do |tag|
1987
1987
  next unless tag =~ /\S+:\S+/
1988
1988
 
1989
1989
  if tag =~ /::/
@@ -2055,11 +2055,11 @@ module Doing
2055
2055
  def tag_times(format: :text, sort_by: :time, sort_order: :asc)
2056
2056
  return '' if @timers.empty?
2057
2057
 
2058
- max = @timers.keys.sort_by { |k| k.length }.reverse[0].length + 1
2058
+ max = @timers.keys.sort_by(&:length).reverse[0].length + 1
2059
2059
 
2060
2060
  total = @timers.delete('All')
2061
2061
 
2062
- tags_data = @timers.delete_if { |_k, v| v == 0 }
2062
+ tags_data = @timers.delete_if { |_k, v| v.zero? }
2063
2063
  sorted_tags_data = if sort_by.normalize_tag_sort == :name
2064
2064
  tags_data.sort_by { |k, _v| k }
2065
2065
  else
@@ -2070,7 +2070,7 @@ module Doing
2070
2070
  case format
2071
2071
  when :html
2072
2072
 
2073
- output = <<EOS
2073
+ output = <<EOHEAD
2074
2074
  <table>
2075
2075
  <caption id="tagtotals">Tag Totals</caption>
2076
2076
  <colgroup>
@@ -2084,13 +2084,13 @@ module Doing
2084
2084
  </tr>
2085
2085
  </thead>
2086
2086
  <tbody>
2087
- EOS
2087
+ EOHEAD
2088
2088
  sorted_tags_data.reverse.each do |k, v|
2089
- if v > 0
2089
+ if v.positive?
2090
2090
  output += "<tr><td style='text-align:left;'>#{k}</td><td style='text-align:left;'>#{v.time_string(format: :clock)}</td></tr>\n"
2091
2091
  end
2092
2092
  end
2093
- tail = <<EOS
2093
+ tail = <<EOTAIL
2094
2094
  <tr>
2095
2095
  <td style="text-align:left;" colspan="2"></td>
2096
2096
  </tr>
@@ -2102,21 +2102,21 @@ EOS
2102
2102
  </tr>
2103
2103
  </tfoot>
2104
2104
  </table>
2105
- EOS
2105
+ EOTAIL
2106
2106
  output + tail
2107
2107
  when :markdown
2108
- pad = sorted_tags_data.map {|k, v| k }.group_by(&:size).max.last[0].length
2108
+ pad = sorted_tags_data.map { |k, _| k }.group_by(&:size).max.last[0].length
2109
2109
  pad = 7 if pad < 7
2110
- output = <<~EOS
2111
- | #{' ' * (pad - 7) }project | time |
2110
+ output = <<~EOHEADER
2111
+ | #{' ' * (pad - 7)}project | time |
2112
2112
  | #{'-' * (pad - 1)}: | :------- |
2113
- EOS
2113
+ EOHEADER
2114
2114
  sorted_tags_data.reverse.each do |k, v|
2115
- if v > 0
2115
+ if v.positive?
2116
2116
  output += "| #{' ' * (pad - k.length)}#{k} | #{v.time_string(format: :clock)} |\n"
2117
2117
  end
2118
2118
  end
2119
- tail = "[Tag Totals]"
2119
+ tail = '[Tag Totals]'
2120
2120
  output + tail
2121
2121
  when :json
2122
2122
  output = []
@@ -2203,38 +2203,37 @@ EOS
2203
2203
  end
2204
2204
 
2205
2205
  ##
2206
- ## Load configuration files and updated the @config
2206
+ ## Load configuration files and updated the @settings
2207
2207
  ## attribute with a Doing::Configuration object
2208
2208
  ##
2209
2209
  ## @param filename [String] (optional) path to
2210
2210
  ## alternative config file
2211
2211
  ##
2212
2212
  def configure(filename = nil)
2213
+ logger.benchmark(:configure, :start)
2214
+
2213
2215
  if filename
2214
2216
  Doing.config_with(filename, { ignore_local: true })
2215
2217
  elsif ENV['DOING_CONFIG']
2216
2218
  Doing.config_with(ENV['DOING_CONFIG'], { ignore_local: true })
2217
2219
  end
2218
2220
 
2219
- logger.benchmark(:configure, :start)
2220
- config = Doing.config
2221
2221
  logger.benchmark(:configure, :finish)
2222
2222
 
2223
- config.settings['backup_dir'] = ENV['DOING_BACKUP_DIR'] if ENV['DOING_BACKUP_DIR']
2224
- @config = config.settings
2223
+ Doing.set('backup_dir', ENV['DOING_BACKUP_DIR']) if ENV['DOING_BACKUP_DIR']
2225
2224
  end
2226
2225
 
2227
2226
  def get_diff(filename = nil)
2228
- configure if @config.nil?
2227
+ configure if Doing.settings.nil?
2229
2228
 
2230
- filename ||= @config['doing_file']
2229
+ filename ||= Doing.setting('doing_file')
2231
2230
  init_doing_file(filename)
2232
2231
  current_content = @content.clone
2233
2232
  backup_file = Util::Backup.last_backup(filename, count: 1)
2234
2233
  raise DoingRuntimeError, 'No undo history to diff' if backup_file.nil?
2235
2234
 
2236
2235
  backup = WWID.new
2237
- backup.config = @config
2236
+ backup.config = Doing.settings
2238
2237
  backup.init_doing_file(backup_file)
2239
2238
  current_content.diff(backup.content)
2240
2239
  end
@@ -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
@@ -22,6 +22,7 @@ require 'tty-which'
22
22
  require 'tty-markdown'
23
23
  require 'tty-reader'
24
24
  require 'tty-screen'
25
+ require 'sys-uname'
25
26
 
26
27
  require_relative 'doing/changelog'
27
28
  require_relative 'doing/hash'
@@ -65,10 +66,67 @@ module Doing
65
66
  @logger ||= LogAdapter.new((ENV['DOING_LOG_LEVEL'] || :info).to_sym)
66
67
  end
67
68
 
69
+ ##
70
+ ## Holds a Configuration object with methods and a @settings hash
71
+ ##
72
+ ## @return [Configuration] Configuration object
73
+ ##
68
74
  def config
69
75
  @config ||= Configuration.new
70
76
  end
71
77
 
78
+ ##
79
+ ## Shortcut for Doing.config.settings
80
+ ##
81
+ ## @return [Hash] Settings hash
82
+ ##
83
+ def settings
84
+ config.settings
85
+ end
86
+
87
+ ##
88
+ ## Fetch a config setting using a dot-separated keypath
89
+ ## or array of keys
90
+ ##
91
+ ## @param keypath [String|Array] Either a
92
+ ## dot-separated key path
93
+ ## (search.case) or array of keys
94
+ ## (['search', 'case'])
95
+ ## @param default A default value to return if the
96
+ ## provided path returns nil result
97
+ ##
98
+ def setting(keypath, default = nil)
99
+ cfg = config.settings
100
+ case keypath
101
+ when Array
102
+ cfg.dig(*keypath) || default
103
+ when String
104
+ unless keypath =~ /^[.*]?$/
105
+ real_path = config.resolve_key_path(keypath, create: false)
106
+ return default unless real_path&.count&.positive?
107
+
108
+ cfg = cfg.dig(*real_path)
109
+ end
110
+
111
+ cfg.nil? ? default : cfg
112
+ end
113
+ end
114
+
115
+ def set(keypath, value)
116
+ real_path = config.resolve_key_path(keypath, create: false)
117
+ return nil unless real_path&.count&.positive?
118
+
119
+ config.settings.deep_set(real_path, value)
120
+ end
121
+
122
+ ##
123
+ ## Update configuration from specified file
124
+ ##
125
+ ## @param file [String] Path to new config file
126
+ ## @param options [Hash] options
127
+ ##
128
+ ## @option options :ignore_local Ignore local configuration files
129
+ ##
72
130
  def config_with(file, options = {})
73
131
  @config = Configuration.new(file, options: options)
74
132
  end
@@ -35,7 +35,7 @@ command :wiki do |c|
35
35
  c.desc 'Only show items with recorded time intervals'
36
36
  c.switch [:only_timed], default_value: false, negatable: false
37
37
 
38
- c.action do |global, options, args|
38
+ c.action do |_global, options, _args|
39
39
  tags = @wwid.tag_groups([], opt: options)
40
40
 
41
41
  wiki = Doing::Plugins.plugins.dig(:export, 'wiki', :class)
@@ -43,38 +43,46 @@ command :wiki do |c|
43
43
  tags.each do |tag, items|
44
44
  export_options = { page_title: tag, is_single: false, options: options }
45
45
 
46
- raise RuntimeError, 'Missing plugin "wiki"' unless wiki
46
+ raise 'Missing plugin "wiki"' unless wiki
47
47
 
48
48
  out = wiki.render(@wwid, items, variables: export_options)
49
49
 
50
50
  if out
51
51
  FileUtils.mkdir_p('doing_wiki')
52
- File.open(File.join('doing_wiki', tag + '.html'), 'w') do |f|
53
- f.puts out
54
- end
52
+ File.open(File.join('doing_wiki', "#{tag}.html"), 'w') { |f| f.puts out }
55
53
  end
56
54
  end
57
55
 
58
- template = if @settings['export_templates']['wiki_index'] && File.exist?(File.expand_path(@settings['export_templates']['wiki_index']))
59
- IO.read(File.expand_path(@settings['export_templates']['wiki_index']))
60
- else
61
- wiki.template('wiki_index')
62
- end
63
- style = if @settings['export_templates']['wiki_css'] && File.exist?(File.expand_path(@settings['export_templates']['wiki_css']))
64
- IO.read(File.expand_path(@settings['export_templates']['wiki_css']))
65
- else
66
- wiki.template('wiki_css')
67
- end
68
- tags_out = tags.map { |t| {url: "#{t}.html"} }
69
- engine = Haml::Engine.new(template)
56
+ engine = Haml::Engine.new(wiki_template(wiki))
57
+ tag_arr = tags.each_with_object([]) { |(tag, items), arr| arr << { name: tag, count: items.count } }
70
58
  index_out = engine.render(Object.new,
71
- { :@tags => tags.each_with_object([]) { |(tag, items), arr| arr << { name: tag, count: items.count } }, :@page_title => "Tags wiki", :@style => style })
59
+ { :@tags => tag_arr,
60
+ :@page_title => 'Tags wiki',
61
+ :@style => wiki_style(wiki) })
72
62
 
73
63
  if index_out
74
64
  File.open(File.join('doing_wiki', 'index.html'), 'w') do |f|
75
65
  f.puts index_out
76
66
  end
77
- Doing.logger.warn("Wiki written to doing_wiki directory")
67
+ Doing.logger.warn('Wiki written to doing_wiki directory')
68
+ end
69
+ end
70
+
71
+ def wiki_template(wiki)
72
+ if Doing.setting('export_templates.wiki_index') &&
73
+ File.exist?(File.expand_path(Doing.setting('export_templates.wiki_index')))
74
+ IO.read(File.expand_path(Doing.setting('export_templates.wiki_index')))
75
+ else
76
+ wiki.template('wiki_index')
77
+ end
78
+ end
79
+
80
+ def wiki_style(wiki)
81
+ if Doing.setting('export_templates.wiki_css') &&
82
+ File.exist?(File.expand_path(Doing.setting('export_templates.wiki_css')))
83
+ IO.read(File.expand_path(Doing.setting('export_templates.wiki_css')))
84
+ else
85
+ wiki.template('wiki_css')
78
86
  end
79
87
  end
80
88
  end
@@ -82,6 +82,7 @@ class ThreadedTests
82
82
  @running_tests = []
83
83
 
84
84
  begin
85
+ finish_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
85
86
  while @children.count.positive?
86
87
 
87
88
  slices = @children.slice!(0, max_threads)
@@ -89,6 +90,7 @@ class ThreadedTests
89
90
  slices.each do |s|
90
91
  @threads << Thread.new do
91
92
  run_test(s)
93
+ finish_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
92
94
  end
93
95
  end
94
96
 
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ content = IO.read(ARGV[0])
4
+
5
+ content.gsub!(/Doing.settings((\[.*?\])+)/) do
6
+ m = Regexp.last_match
7
+ keypath = m[0].scan(/\['([^\]]+)'\]/).map { |e| e[0] }.join('.')
8
+ "Doing.setting('#{keypath}')"
9
+ end
10
+
11
+ puts content