doing 2.1.21 → 2.1.25

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.yardoc/checksums +19 -16
  3. data/.yardoc/object_types +0 -0
  4. data/.yardoc/objects/root.dat +0 -0
  5. data/CHANGELOG.md +51 -13
  6. data/Gemfile.lock +1 -1
  7. data/README.md +1 -1
  8. data/Rakefile +6 -3
  9. data/bin/doing +254 -103
  10. data/docs/doc/Array.html +74 -34
  11. data/docs/doc/BooleanTermParser/Clause.html +5 -5
  12. data/docs/doc/BooleanTermParser/Operator.html +4 -4
  13. data/docs/doc/BooleanTermParser/Query.html +8 -8
  14. data/docs/doc/BooleanTermParser/QueryParser.html +2 -2
  15. data/docs/doc/BooleanTermParser/QueryTransformer.html +2 -2
  16. data/docs/doc/BooleanTermParser.html +1 -1
  17. data/docs/doc/Doing/Color.html +4 -4
  18. data/docs/doc/Doing/Completion.html +2 -2
  19. data/docs/doc/Doing/Configuration.html +16 -18
  20. data/docs/doc/Doing/Errors/DoingNoTraceError.html +2 -2
  21. data/docs/doc/Doing/Errors/DoingRuntimeError.html +2 -2
  22. data/docs/doc/Doing/Errors/DoingStandardError.html +2 -2
  23. data/docs/doc/Doing/Errors/EmptyInput.html +2 -2
  24. data/docs/doc/Doing/Errors/NoResults.html +2 -2
  25. data/docs/doc/Doing/Errors/PluginException.html +3 -3
  26. data/docs/doc/Doing/Errors/UserCancelled.html +2 -2
  27. data/docs/doc/Doing/Errors/WrongCommand.html +2 -2
  28. data/docs/doc/Doing/Errors.html +1 -1
  29. data/docs/doc/Doing/Hooks.html +6 -6
  30. data/docs/doc/Doing/Item.html +50 -16
  31. data/docs/doc/Doing/Items.html +10 -10
  32. data/docs/doc/Doing/LogAdapter.html +24 -24
  33. data/docs/doc/Doing/Note.html +7 -7
  34. data/docs/doc/Doing/Pager.html +4 -4
  35. data/docs/doc/Doing/Plugins.html +7 -7
  36. data/docs/doc/Doing/Prompt.html +16 -16
  37. data/docs/doc/Doing/Section.html +6 -6
  38. data/docs/doc/Doing/TemplateString.html +8 -8
  39. data/docs/doc/Doing/Types.html +206 -0
  40. data/docs/doc/Doing/Util/Backup.html +10 -10
  41. data/docs/doc/Doing/Util.html +16 -19
  42. data/docs/doc/Doing/WWID.html +65 -53
  43. data/docs/doc/Doing.html +4 -4
  44. data/docs/doc/GLI/Commands/Help.html +185 -0
  45. data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +17 -17
  46. data/docs/doc/GLI/Commands.html +5 -3
  47. data/docs/doc/GLI.html +4 -2
  48. data/docs/doc/Hash.html +119 -21
  49. data/docs/doc/Numeric.html +5 -5
  50. data/docs/doc/PhraseParser/Operator.html +4 -4
  51. data/docs/doc/PhraseParser/PhraseClause.html +5 -5
  52. data/docs/doc/PhraseParser/Query.html +10 -10
  53. data/docs/doc/PhraseParser/QueryParser.html +2 -2
  54. data/docs/doc/PhraseParser/QueryTransformer.html +2 -2
  55. data/docs/doc/PhraseParser/TermClause.html +5 -5
  56. data/docs/doc/PhraseParser.html +1 -1
  57. data/docs/doc/Status.html +7 -7
  58. data/docs/doc/String.html +206 -51
  59. data/docs/doc/Symbol.html +8 -8
  60. data/docs/doc/Time.html +6 -6
  61. data/docs/doc/_index.html +51 -14
  62. data/docs/doc/class_list.html +1 -1
  63. data/docs/doc/file.README.html +2 -2
  64. data/docs/doc/index.html +2 -2
  65. data/docs/doc/method_list.html +348 -252
  66. data/docs/doc/top-level-namespace.html +2 -93
  67. data/docs/index.md +1 -1
  68. data/doing.rdoc +177 -20
  69. data/example_plugin.rb +2 -2
  70. data/lib/completion/_doing.zsh +24 -24
  71. data/lib/completion/doing.bash +31 -20
  72. data/lib/completion/doing.fish +32 -10
  73. data/lib/doing/array.rb +5 -4
  74. data/lib/doing/array_chronify.rb +4 -3
  75. data/lib/doing/changelog/change.rb +115 -0
  76. data/lib/doing/changelog/changes.rb +73 -0
  77. data/lib/doing/changelog/entry.rb +21 -0
  78. data/lib/doing/changelog/version.rb +97 -0
  79. data/lib/doing/changelog.rb +6 -0
  80. data/lib/doing/completion/fish_completion.rb +2 -1
  81. data/lib/doing/configuration.rb +20 -14
  82. data/lib/doing/good.rb +64 -0
  83. data/lib/doing/hash.rb +28 -5
  84. data/lib/doing/help_monkey_patch.rb +31 -0
  85. data/lib/doing/hooks.rb +8 -4
  86. data/lib/doing/item.rb +24 -35
  87. data/lib/doing/log_adapter.rb +1 -1
  88. data/lib/doing/pager.rb +1 -1
  89. data/lib/doing/plugins/export/template_export.rb +9 -3
  90. data/lib/doing/plugins/import/calendar_import.rb +1 -1
  91. data/lib/doing/plugins/import/doing_import.rb +1 -1
  92. data/lib/doing/plugins/import/timing_import.rb +1 -1
  93. data/lib/doing/prompt.rb +4 -2
  94. data/lib/doing/string.rb +30 -12
  95. data/lib/doing/string_chronify.rb +1 -1
  96. data/lib/doing/template_string.rb +9 -2
  97. data/lib/doing/types.rb +23 -16
  98. data/lib/doing/util.rb +12 -11
  99. data/lib/doing/version.rb +1 -1
  100. data/lib/doing/wwid.rb +65 -46
  101. data/lib/doing.rb +3 -0
  102. data/lib/helpers/threaded_tests.rb +106 -99
  103. data/lib/helpers/threaded_tests_string.rb +50 -0
  104. metadata +12 -2
data/lib/doing/wwid.rb CHANGED
@@ -338,6 +338,7 @@ module Doing
338
338
  ## @option opt :note [Array] item note (will be converted if value is String)
339
339
  ## @option opt :back [Date] backdate
340
340
  ## @option opt :timed [Boolean] new item is timed entry, marks previous entry as @done
341
+ ## @option opt :done [Date] If set, adds a @done tag to new entry
341
342
  ##
342
343
  def add_item(title, section = nil, opt)
343
344
  opt ||= {}
@@ -360,9 +361,18 @@ module Doing
360
361
 
361
362
  title.compress!
362
363
  entry = Item.new(opt[:back], title.strip, section)
364
+
365
+ if opt[:done] && entry.should_finish?
366
+ if entry.should_time?
367
+ entry.tag('done', value: opt[:done])
368
+ else
369
+ entry.tag('done')
370
+ end
371
+ end
372
+
363
373
  entry.note = note
364
374
 
365
- items = @content.dup
375
+ items = @content.clone
366
376
  if opt[:timed]
367
377
  items.reverse!
368
378
  items.each_with_index do |i, x|
@@ -380,7 +390,8 @@ module Doing
380
390
  # logger.count(:added, level: :debug)
381
391
  logger.info('New entry:', %(added "#{entry.date.relative_date}: #{entry.title}" to #{section}))
382
392
 
383
- Hooks.trigger :post_entry_added, self, entry.dup
393
+ Hooks.trigger :post_entry_added, self, entry
394
+ entry
384
395
  end
385
396
 
386
397
  ##
@@ -474,6 +485,7 @@ module Doing
474
485
  #
475
486
  def repeat_item(item, opt)
476
487
  opt ||= {}
488
+ old_item = item.clone
477
489
  if item.should_finish?
478
490
  if item.should_time?
479
491
  finish_date = verify_duration(item.date, Time.now, title: item.title)
@@ -481,7 +493,7 @@ module Doing
481
493
  else
482
494
  item.title.tag!('done')
483
495
  end
484
- Hooks.trigger :post_entry_updated, self, item
496
+ Hooks.trigger :post_entry_updated, self, item, old_item
485
497
  end
486
498
 
487
499
  # Remove @done tag
@@ -651,20 +663,14 @@ module Doing
651
663
 
652
664
  if items.nil? || items.empty?
653
665
  section = opt[:section] ? guess_section(opt[:section]) : 'All'
654
- items = section =~ /^all$/i ? @content.dup : @content.in_section(section)
666
+ items = section =~ /^all$/i ? @content.clone : @content.in_section(section)
655
667
  end
656
668
 
657
669
  opt[:time_filter] = [nil, nil]
658
670
  if opt[:from] && !opt[:date_filter]
659
671
  if opt[:from][0].is_a?(String) && opt[:from][0] =~ time_rx
660
- time_start, time_end = opt[:from]
661
- elsif opt[:from].is_a?(Time)
662
- start, finish = opt[:from]
663
- end
664
-
665
- if time_start
666
- opt[:time_filter] = [time_start, time_end]
667
- else
672
+ opt[:time_filter] = opt[:from]
673
+ elsif opt[:from][0].is_a?(Time)
668
674
  opt[:date_filter] = opt[:from]
669
675
  end
670
676
  end
@@ -853,7 +859,7 @@ module Doing
853
859
  note.map!(&:strip)
854
860
  note.delete_if(&:ignore?)
855
861
  item = items[i]
856
- old_item = item.dup
862
+ old_item = item.clone
857
863
  item.date = date || items[i].date
858
864
  item.title = title
859
865
  item.note = note
@@ -861,7 +867,7 @@ module Doing
861
867
  Doing.logger.count(:skipped, level: :debug)
862
868
  else
863
869
  Doing.logger.count(:updated)
864
- Hooks.trigger :post_entry_updated, self, item
870
+ Hooks.trigger :post_entry_updated, self, item, old_item
865
871
  end
866
872
  end
867
873
  end
@@ -1051,9 +1057,10 @@ module Doing
1051
1057
  else
1052
1058
  opt[:resume]
1053
1059
  end
1060
+ old_item = item.clone
1054
1061
  new_entry = reset_item(item, date: date, resume: res)
1055
1062
  @content.update_item(item, new_entry)
1056
- Hooks.trigger :post_entry_updated, self, new_entry
1063
+ Hooks.trigger :post_entry_updated, self, new_entry, old_item
1057
1064
  end
1058
1065
  write(@doing_file)
1059
1066
 
@@ -1068,8 +1075,9 @@ module Doing
1068
1075
  if opt[:flag]
1069
1076
  tag = @config['marker_tag'] || 'flagged'
1070
1077
  items.map! do |i|
1078
+ old_item = i.clone
1071
1079
  i.tag(tag, date: false, remove: opt[:remove], single: single)
1072
- Hooks.trigger :post_entry_updated, self, i
1080
+ Hooks.trigger :post_entry_updated, self, i, old_item
1073
1081
  end
1074
1082
  end
1075
1083
 
@@ -1077,9 +1085,10 @@ module Doing
1077
1085
  tag = 'done'
1078
1086
  items.map! do |i|
1079
1087
  if i.should_finish?
1088
+ old_item = i.clone
1080
1089
  should_date = !opt[:cancel] && i.should_time?
1081
1090
  i.tag(tag, date: should_date, remove: opt[:remove], single: single)
1082
- Hooks.trigger :post_entry_updated, self, i
1091
+ Hooks.trigger :post_entry_updated, self, i, old_item
1083
1092
  end
1084
1093
  end
1085
1094
  end
@@ -1093,8 +1102,9 @@ module Doing
1093
1102
  else
1094
1103
  logger.count(:added_tags)
1095
1104
  logger.write(items.count == 1 ? :info : :debug, 'Tagged:', new_title)
1105
+ old_item = i.clone
1096
1106
  i.title = new_title
1097
- Hooks.trigger :post_entry_updated, self, i
1107
+ Hooks.trigger :post_entry_updated, self, i, old_item
1098
1108
  end
1099
1109
  end
1100
1110
  end
@@ -1102,17 +1112,19 @@ module Doing
1102
1112
  if opt[:tag]
1103
1113
  tag = opt[:tag]
1104
1114
  items.map! do |i|
1115
+ old_item = i.clone
1105
1116
  i.tag(tag, date: false, remove: opt[:remove], single: single)
1106
1117
  i.expand_date_tags(@config['date_tags'])
1107
- Hooks.trigger :post_entry_updated, self, i
1118
+ Hooks.trigger :post_entry_updated, self, i, old_item
1108
1119
  end
1109
1120
  end
1110
1121
 
1111
1122
  if opt[:archive] || opt[:move]
1112
1123
  section = opt[:archive] ? 'Archive' : guess_section(opt[:move])
1113
1124
  items.map! do |i|
1125
+ old_item = i.clone
1114
1126
  i.move_to(section, label: true)
1115
- Hooks.trigger :post_entry_updated, self, i
1127
+ Hooks.trigger :post_entry_updated, self, i, old_item
1116
1128
  end
1117
1129
  end
1118
1130
 
@@ -1234,6 +1246,7 @@ module Doing
1234
1246
  end
1235
1247
 
1236
1248
  items.each do |item|
1249
+ old_item = item.clone
1237
1250
  added = []
1238
1251
  removed = []
1239
1252
 
@@ -1271,7 +1284,7 @@ module Doing
1271
1284
 
1272
1285
  tag = tag.strip
1273
1286
 
1274
- if tag =~ /^done$/
1287
+ if tag =~ /^done$/ && opt[:date] && item.should_time?
1275
1288
  max_elapsed = @config.dig('interaction', 'confirm_longer_than') || 0
1276
1289
  max_elapsed = max_elapsed.chronify_qty if max_elapsed.is_a?(String)
1277
1290
  elapsed = done_date - item.date
@@ -1328,7 +1341,7 @@ module Doing
1328
1341
  end
1329
1342
 
1330
1343
  item.expand_date_tags(@config['date_tags'])
1331
- Hooks.trigger :post_entry_updated, self, item
1344
+ Hooks.trigger :post_entry_updated, self, item, old_item
1332
1345
  end
1333
1346
 
1334
1347
  write(@doing_file)
@@ -1367,6 +1380,7 @@ module Doing
1367
1380
  return
1368
1381
  end
1369
1382
 
1383
+ old_item = item.clone
1370
1384
  content = ["#{item.date.strftime('%F %R')} | #{item.title.dup}"]
1371
1385
  content << item.note.strip_lines.join("\n") unless item.note.empty?
1372
1386
  new_item = fork_editor(content.join("\n"))
@@ -1382,7 +1396,7 @@ module Doing
1382
1396
  item.title = title
1383
1397
  item.note.add(note, replace: true)
1384
1398
  logger.info('Edited:', item.title)
1385
- Hooks.trigger :post_entry_updated, self, item.dup
1399
+ Hooks.trigger :post_entry_updated, self, item, old_item
1386
1400
 
1387
1401
  write(@doing_file)
1388
1402
  end
@@ -1419,6 +1433,7 @@ module Doing
1419
1433
  found_items = 0
1420
1434
 
1421
1435
  @content.each_with_index do |item, i|
1436
+ old_item = i.clone
1422
1437
  next unless item.section == opt[:section] || opt[:section] =~ /all/i
1423
1438
 
1424
1439
  next unless item.title =~ /@#{tag}/
@@ -1437,7 +1452,7 @@ module Doing
1437
1452
  logger.count(:completed)
1438
1453
  logger.info('Completed:', item.title)
1439
1454
  end
1440
- Hooks.trigger :post_entry_updated, self, item
1455
+ Hooks.trigger :post_entry_updated, self, item, old_item
1441
1456
  end
1442
1457
 
1443
1458
 
@@ -1498,7 +1513,7 @@ module Doing
1498
1513
 
1499
1514
  unless ((!tags.empty? && !item.tags?(tags, bool)) || (opt[:search] && !item.search(opt[:search].to_s)) || (opt[:before] && item.date >= cutoff))
1500
1515
  new_item = @content.delete(item)
1501
- Hooks.trigger :post_entry_removed, self, item.dup
1516
+ Hooks.trigger :post_entry_removed, self, item.clone
1502
1517
  raise DoingRuntimeError, "Error deleting item: #{item}" if new_item.nil?
1503
1518
 
1504
1519
  new_content.add_section(new_item.section, log: false)
@@ -1622,6 +1637,7 @@ module Doing
1622
1637
  'duration' => @config['duration'],
1623
1638
  'interval_format' => @config['interval_format']
1624
1639
  }, { extend_existing_arrays: true, sort_merged_arrays: true })
1640
+
1625
1641
  opt[:duration] ||= cfg['duration'] || false
1626
1642
  opt[:interval_format] ||= cfg['interval_format'] || 'text'
1627
1643
  opt[:count] ||= 0
@@ -1724,7 +1740,7 @@ module Doing
1724
1740
  opt[:totals] ||= false
1725
1741
  opt[:sort_tags] ||= false
1726
1742
 
1727
- cfg = @config['templates']['today'].deep_merge(@config['templates']['default'], { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
1743
+ cfg = @config['templates'][opt[:config_template]].deep_merge(@config['templates']['default'], { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
1728
1744
  'wrap_width' => @config['wrap_width'] || 0,
1729
1745
  'date_format' => @config['default_date_format'],
1730
1746
  'order' => @config['order'] || 'asc',
@@ -1733,6 +1749,8 @@ module Doing
1733
1749
  'interval_format' => @config['interval_format']
1734
1750
  }, { extend_existing_arrays: true, sort_merged_arrays: true })
1735
1751
 
1752
+ template = opt[:template] || cfg['template']
1753
+
1736
1754
  opt[:duration] ||= cfg['duration'] || false
1737
1755
  opt[:interval_format] ||= cfg['interval_format'] || 'text'
1738
1756
 
@@ -1748,13 +1766,13 @@ module Doing
1748
1766
  output: output,
1749
1767
  section: opt[:section],
1750
1768
  sort_tags: opt[:sort_tags],
1751
- template: cfg['template'],
1769
+ template: template,
1752
1770
  times: times,
1753
1771
  today: true,
1754
1772
  totals: opt[:totals],
1755
1773
  wrap_width: cfg['wrap_width'],
1756
1774
  tags_color: cfg['tags_color'],
1757
- config_template: 'today'
1775
+ config_template: opt[:config_template]
1758
1776
  }
1759
1777
  list_section(options)
1760
1778
  end
@@ -1786,7 +1804,8 @@ module Doing
1786
1804
  totals: opt[:totals],
1787
1805
  duration: opt[:duration],
1788
1806
  sort_tags: opt[:sort_tags],
1789
- config_template: 'default'
1807
+ template: opt[:template],
1808
+ config_template: opt[:config_template]
1790
1809
  })
1791
1810
  end
1792
1811
 
@@ -1821,7 +1840,8 @@ module Doing
1821
1840
  times: times,
1822
1841
  totals: opt[:totals],
1823
1842
  yesterday: true,
1824
- config_template: 'today'
1843
+ config_template: opt[:config_template] || 'today',
1844
+ template: opt[:template]
1825
1845
  }
1826
1846
 
1827
1847
  list_section(options)
@@ -1840,7 +1860,7 @@ module Doing
1840
1860
  opt[:totals] ||= false
1841
1861
  opt[:sort_tags] ||= false
1842
1862
 
1843
- cfg = @config['templates']['recent'].deep_merge(@config['templates']['default'], { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
1863
+ cfg = @config['templates'][opt[:config_template]].deep_merge(@config['templates']['default'], { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
1844
1864
  'wrap_width' => @config['wrap_width'] || 0,
1845
1865
  'date_format' => @config['default_date_format'],
1846
1866
  'order' => @config['order'] || 'asc',
@@ -1858,10 +1878,9 @@ module Doing
1858
1878
  opt[:wrap_width] = cfg['wrap_width']
1859
1879
  opt[:count] = count
1860
1880
  opt[:format] = cfg['date_format']
1861
- opt[:template] = cfg['template']
1881
+ opt[:template] = opt[:template] || cfg['template']
1862
1882
  opt[:order] = 'asc'
1863
1883
  opt[:times] = times
1864
- opt[:config_template] = 'recent'
1865
1884
 
1866
1885
  list_section(opt)
1867
1886
  end
@@ -1874,7 +1893,7 @@ module Doing
1874
1893
  ##
1875
1894
  def last(times: true, section: nil, options: {})
1876
1895
  section = section.nil? || section =~ /all/i ? 'All' : guess_section(section)
1877
- cfg = @config['templates']['last'].deep_merge(@config['templates']['default'], { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
1896
+ cfg = @config['templates'][options[:config_template]].deep_merge(@config['templates']['default'], { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
1878
1897
  'wrap_width' => @config['wrap_width'] || 0,
1879
1898
  'date_format' => @config['default_date_format'],
1880
1899
  'order' => @config['order'] || 'asc',
@@ -1886,19 +1905,19 @@ module Doing
1886
1905
  options[:interval_format] ||= cfg['interval_format'] || 'text'
1887
1906
 
1888
1907
  opts = {
1889
- section: section,
1890
- wrap_width: cfg['wrap_width'],
1908
+ case: options[:case],
1909
+ config_template: 'last',
1891
1910
  count: 1,
1892
- format: cfg['date_format'],
1893
- template: cfg['template'],
1894
- times: times,
1911
+ delete: options[:delete],
1895
1912
  duration: options[:duration],
1913
+ format: cfg['date_format'],
1896
1914
  interval_format: options[:interval_format],
1897
- case: options[:case],
1898
1915
  not: options[:negate],
1899
- config_template: 'last',
1900
- delete: options[:delete],
1901
- val: options[:val]
1916
+ section: section,
1917
+ template: options[:template] || cfg['template'],
1918
+ times: times,
1919
+ val: options[:val],
1920
+ wrap_width: cfg['wrap_width']
1902
1921
  }
1903
1922
 
1904
1923
  if options[:tag]
@@ -2204,7 +2223,7 @@ EOS
2204
2223
 
2205
2224
  filename ||= @config['doing_file']
2206
2225
  init_doing_file(filename)
2207
- current_content = @content.dup
2226
+ current_content = @content.clone
2208
2227
  backup_file = Util::Backup.last_backup(filename, count: 1)
2209
2228
  raise DoingRuntimeError, 'No undo history to diff' if backup_file.nil?
2210
2229
 
@@ -2307,7 +2326,6 @@ EOS
2307
2326
 
2308
2327
  section_items = @content.in_section(section)
2309
2328
  max = section_items.count - count.to_i
2310
- moved_items = []
2311
2329
 
2312
2330
  counter = 0
2313
2331
 
@@ -2324,8 +2342,9 @@ EOS
2324
2342
  item
2325
2343
  else
2326
2344
  counter += 1
2345
+ old_item = item.clone
2327
2346
  item.move_to(destination, label: label, log: false)
2328
- Hooks.trigger :post_entry_updated, self, item.dup
2347
+ Hooks.trigger :post_entry_updated, self, item, old_item
2329
2348
  item
2330
2349
  end
2331
2350
  end
data/lib/doing.rb CHANGED
@@ -23,12 +23,15 @@ require 'tty-markdown'
23
23
  require 'tty-reader'
24
24
  require 'tty-screen'
25
25
 
26
+ require_relative 'doing/changelog'
26
27
  require_relative 'doing/hash'
28
+ require_relative 'doing/types'
27
29
  require_relative 'doing/colors'
28
30
  require_relative 'doing/template_string'
29
31
  require_relative 'doing/string'
30
32
  require_relative 'doing/time'
31
33
  require_relative 'doing/array'
34
+ require_relative 'doing/good'
32
35
  require_relative 'doing/symbol'
33
36
  require_relative 'doing/util'
34
37
  require_relative 'doing/util_backup'
@@ -2,66 +2,23 @@
2
2
 
3
3
  require 'tty-spinner'
4
4
  require 'tty-progressbar'
5
- require './lib/doing'
6
5
  require 'open3'
7
6
  require 'shellwords'
7
+ require 'fileutils'
8
8
 
9
- class ::String
10
- include Doing::Color
11
-
12
- def highlight_errors
13
- cols = `tput cols`.strip.to_i
14
-
15
- string = dup
16
-
17
- errs = string.scan(/(?<==\n)(?:Failure|Error):.*?(?=\n=+)/m)
18
-
19
- errs.map! do |error|
20
- err = error.dup
21
-
22
- err.gsub!(%r{^(/.*?/)([^/:]+):(\d+):in (.*?)$}) do
23
- m = Regexp.last_match
24
- "#{m[1].white}#{m[2].bold.white}:#{m[3].yellow}:in #{m[4].cyan}"
25
- end
26
- err.gsub!(/(Failure|Error): (.*?)\((.*?)\):\n (.*?)(?=\n)/m) do
27
- m = Regexp.last_match
28
- [
29
- m[1].bold.boldbgred.white,
30
- m[3].bold.boldbgcyan.white,
31
- m[2].bold.boldbgyellow.black,
32
- " #{m[4]} ".bold.boldbgwhite.black.reset
33
- ].join(':'.boldblack.boldbgblack.reset)
34
- end
35
- err.gsub!(/(<.*?>) (was expected to) (.*?)\n( *<.*?>)./m) do
36
- m = Regexp.last_match
37
- "#{m[1].bold.green} #{m[2].white} #{m[3].boldwhite.boldbgred.reset}\n#{m[4].bold.white}"
38
- end
39
- err.gsub!(/(Finished in) ([\d.]+) (seconds)/) do
40
- m = Regexp.last_match
41
- "#{m[1].green} #{m[2].bold.white} #{m[3].green}"
42
- end
43
- err.gsub!(/(\d+) (failures)/) do
44
- m = Regexp.last_match
45
- "#{m[1].bold.red} #{m[2].red}"
46
- end
47
- err.gsub!(/100% passed/) do |m|
48
- m.bold.green
49
- end
50
-
51
- err
52
- end
53
-
54
- errs.join("\n#{('=' * cols).blue}\n")
55
- end
56
- end
9
+ $LOAD_PATH.unshift File.join(__dir__, '..')
10
+ require 'doing'
11
+ require 'helpers/threaded_tests_string'
57
12
 
58
13
  class ThreadedTests
59
14
  include Doing::Color
15
+ include ThreadedTestString
60
16
 
61
- def run(pattern: '*', max_threads: 24, max_tests: 0)
17
+ def run(pattern: '*', max_threads: 8, max_tests: 0)
62
18
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
19
+ @results = File.expand_path('results.log')
63
20
 
64
- max_threads = 24 if max_threads == 0
21
+ max_threads = 1000 if max_threads == 0
65
22
 
66
23
  c = Doing::Color
67
24
  c.coloring = true
@@ -90,12 +47,21 @@ class ThreadedTests
90
47
  ].join('')
91
48
  progress = TTY::ProgressBar::Multi.new(banner,
92
49
  width: 12,
50
+ clear: true,
93
51
  hide_cursor: true)
94
- children = []
52
+ @children = []
95
53
  tests.each do |t|
96
54
  test_name = File.basename(t, '.rb').sub(/doing_(.*?)_test/, '\1')
97
- new_sp = progress.register("[#{':bar'.cyan}] #{test_name.bold.white}:status", total: 2, width: 1, head: '.', hide_cursor: true)
98
- children.push([test_name, new_sp, nil])
55
+ new_sp = progress.register("[#{':bar'.cyan}] #{test_name.bold.white}:status",
56
+ total: tests.count + 8,
57
+ width: 1,
58
+ head: ' ',
59
+ unknown: ' ',
60
+ hide_cursor: true,
61
+ clear: true)
62
+ status = ': waiting'.dark.yellow.reset
63
+ @children.push([test_name, new_sp, status])
64
+ # new_sp.advance(status: ': waiting'.dark.yellow.reset)
99
65
  end
100
66
 
101
67
  @elapsed = 0.0
@@ -103,57 +69,21 @@ class ThreadedTests
103
69
  @assrt_total = 0
104
70
  @error_out = []
105
71
  # progress.start
72
+ @threads = []
73
+ @running_tests = []
106
74
 
107
75
  begin
108
- while children.count.positive?
109
- threads = []
110
- slices = children.slice!(0, max_threads)
76
+ while @children.count.positive?
77
+
78
+ slices = @children.slice!(0, max_threads)
111
79
  slices.each { |c| c[1].start }
112
80
  slices.each do |s|
113
- bar = s[1]
114
- bar.advance(status: ": #{'running'.green}")
115
-
116
- threads << Thread.new do
117
- out, _err, status = Open3.capture3(ENV, 'rake', "test:#{s[0]}", stdin_data: nil)
118
- unless status.success?
119
- m = out.match(/(?<fail>\d+) failures, (?<err>\d+) errors/)
120
- status = ": #{m['fail'].bold.red} #{'failures'.red}, #{m['err'].bold.red} #{'errors'.red}"
121
- bar.update(head: '✖'.boldred)
122
- bar.advance(head: '✖'.boldred, status: status)
123
-
124
- # errs = out.scan(/(?:Failure|Error): [\w_]+\((?:.*?)\):(?:.*?)(?=\n=======)/m)
125
- @error_out.push(out.highlight_errors)
126
- bar.finish
127
-
128
- Thread.exit
129
- end
130
-
131
- time = out.match(/^Finished in (?<time>\d+\.\d+) seconds\./)
132
- count = out.match(/^(?<tests>\d+) tests, (?<assrt>\d+) assertions, (?<fails>\d+) failures, (?<errs>\d+) errors/)
133
- status = [
134
- ': ',
135
- count['tests'].green,
136
- '/',
137
- count['assrt'].cyan,
138
- # ' (',
139
- # count['fails'].to_i == 0 ? '-'.dark.white.reset : count['fails'].bold.red,
140
- # '/',
141
- # count['errs'].to_i == 0 ? '-'.dark.white.reset : count['errs'].bold.red,
142
- # ') ',
143
- ' ',
144
- time['time'].to_f.round(3).to_s.yellow,
145
- 's'
146
- ].join('')
147
- bar.update(head: '✔'.boldgreen)
148
- bar.advance(head: '✔'.boldgreen, status: status)
149
- @test_total += count['tests'].to_i
150
- @assrt_total += count['assrt'].to_i
151
- @elapsed += time['time'].to_f
152
-
153
- bar.finish
81
+ @threads << Thread.new do
82
+ run_test(s)
154
83
  end
155
84
  end
156
- threads.each { |t| t.join }
85
+
86
+ @threads.each { |t| t.join }
157
87
  end
158
88
 
159
89
  finish_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
@@ -175,6 +105,83 @@ class ThreadedTests
175
105
  rescue
176
106
  progress.stop
177
107
  end
108
+ ensure
109
+ FileUtils.rm(@results)
110
+ end
111
+
112
+ def run_test(s)
113
+ bar = s[1]
114
+ s[2] = ": #{'running'.green}"
115
+ bar.advance(status: s[2])
116
+
117
+ if @running_tests.count.positive?
118
+ @running_tests.each do |b|
119
+ prev_bar = b[1]
120
+ if prev_bar.complete?
121
+ prev_bar.reset
122
+ prev_bar.advance(status: b[2])
123
+ prev_bar.finish
124
+ else
125
+ prev_bar.update(head: ' ', unfinished: ' ')
126
+ prev_bar.advance(status: b[2])
127
+ end
128
+ end
129
+ end
130
+
131
+ @running_tests.push(s)
132
+ out, _err, status = Open3.capture3(ENV, "rake test:#{s[0]} | tee #{@results}", stdin_data: nil)
133
+ unless status.success?
134
+ m = out.match(/(?<fail>\d+) failures, (?<err>\d+) errors/)
135
+ s[2] = ": #{m['fail'].bold.red} #{'failures'.red}, #{m['err'].bold.red} #{'errors'.red}"
136
+ bar.update(head: '✖'.boldred)
137
+ bar.advance(head: '✖'.boldred, status: s[2])
138
+
139
+ # errs = out.scan(/(?:Failure|Error): [\w_]+\((?:.*?)\):(?:.*?)(?=\n=======)/m)
140
+ @error_out.push(out.highlight_errors)
141
+ bar.finish
142
+
143
+ next_test
144
+ Thread.exit
145
+ end
146
+
147
+ time = out.match(/^Finished in (?<time>\d+\.\d+) seconds\./)
148
+ count = out.match(/^(?<tests>\d+) tests, (?<assrt>\d+) assertions, (?<fails>\d+) failures, (?<errs>\d+) errors/)
149
+ s[2] = [
150
+ ': ',
151
+ count['tests'].green,
152
+ '/',
153
+ count['assrt'].cyan,
154
+ # ' (',
155
+ # count['fails'].to_i == 0 ? '-'.dark.white.reset : count['fails'].bold.red,
156
+ # '/',
157
+ # count['errs'].to_i == 0 ? '-'.dark.white.reset : count['errs'].bold.red,
158
+ # ') ',
159
+ ' ',
160
+ time['time'].to_f.round(3).to_s.yellow,
161
+ 's'
162
+ ].join('')
163
+ bar.update(head: '✔'.boldgreen)
164
+ bar.advance(head: '✔'.boldgreen, status: s[2])
165
+ @test_total += count['tests'].to_i
166
+ @assrt_total += count['assrt'].to_i
167
+ @elapsed += time['time'].to_f
168
+
169
+ bar.finish
170
+
171
+ next_test
172
+ end
173
+
174
+ def next_test
175
+ if @children.count.positive?
176
+ t = Thread.new do
177
+ s = @children.shift
178
+ # s[1].start
179
+ # s[1].advance(status: ": #{'running'.green}")
180
+ run_test(s)
181
+ end
182
+
183
+ t.join
184
+ end
178
185
  end
179
186
  end
180
187
 
@@ -0,0 +1,50 @@
1
+ module ThreadedTestString
2
+ class ::String
3
+ include Doing::Color
4
+
5
+ def highlight_errors
6
+ cols = `tput cols`.strip.to_i
7
+
8
+ string = dup
9
+
10
+ errs = string.scan(/(?<==\n)(?:Failure|Error):.*?(?=\n=+)/m)
11
+
12
+ errs.map! do |error|
13
+ err = error.dup
14
+
15
+ err.gsub!(%r{^(/.*?/)([^/:]+):(\d+):in (.*?)$}) do
16
+ m = Regexp.last_match
17
+ "#{m[1].white}#{m[2].bold.white}:#{m[3].yellow}:in #{m[4].cyan}"
18
+ end
19
+ err.gsub!(/(Failure|Error): (.*?)\((.*?)\):\n (.*?)(?=\n)/m) do
20
+ m = Regexp.last_match
21
+ [
22
+ m[1].bold.boldbgred.white,
23
+ m[3].bold.boldbgcyan.white,
24
+ m[2].bold.boldbgyellow.black,
25
+ " #{m[4]} ".bold.boldbgwhite.black.reset
26
+ ].join(':'.boldblack.boldbgblack.reset)
27
+ end
28
+ err.gsub!(/(<.*?>) (was expected to) (.*?)\n( *<.*?>)./m) do
29
+ m = Regexp.last_match
30
+ "#{m[1].bold.green} #{m[2].white} #{m[3].boldwhite.boldbgred.reset}\n#{m[4].bold.white}"
31
+ end
32
+ err.gsub!(/(Finished in) ([\d.]+) (seconds)/) do
33
+ m = Regexp.last_match
34
+ "#{m[1].green} #{m[2].bold.white} #{m[3].green}"
35
+ end
36
+ err.gsub!(/(\d+) (failures)/) do
37
+ m = Regexp.last_match
38
+ "#{m[1].bold.red} #{m[2].red}"
39
+ end
40
+ err.gsub!(/100% passed/) do |m|
41
+ m.bold.green
42
+ end
43
+
44
+ err
45
+ end
46
+
47
+ errs.join("\n#{('=' * cols).blue}\n")
48
+ end
49
+ end
50
+ end