doing 2.1.21 → 2.1.25
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardoc/checksums +19 -16
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/CHANGELOG.md +51 -13
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/Rakefile +6 -3
- data/bin/doing +254 -103
- data/docs/doc/Array.html +74 -34
- data/docs/doc/BooleanTermParser/Clause.html +5 -5
- data/docs/doc/BooleanTermParser/Operator.html +4 -4
- data/docs/doc/BooleanTermParser/Query.html +8 -8
- data/docs/doc/BooleanTermParser/QueryParser.html +2 -2
- data/docs/doc/BooleanTermParser/QueryTransformer.html +2 -2
- data/docs/doc/BooleanTermParser.html +1 -1
- data/docs/doc/Doing/Color.html +4 -4
- data/docs/doc/Doing/Completion.html +2 -2
- data/docs/doc/Doing/Configuration.html +16 -18
- data/docs/doc/Doing/Errors/DoingNoTraceError.html +2 -2
- data/docs/doc/Doing/Errors/DoingRuntimeError.html +2 -2
- data/docs/doc/Doing/Errors/DoingStandardError.html +2 -2
- data/docs/doc/Doing/Errors/EmptyInput.html +2 -2
- data/docs/doc/Doing/Errors/NoResults.html +2 -2
- data/docs/doc/Doing/Errors/PluginException.html +3 -3
- data/docs/doc/Doing/Errors/UserCancelled.html +2 -2
- data/docs/doc/Doing/Errors/WrongCommand.html +2 -2
- data/docs/doc/Doing/Errors.html +1 -1
- data/docs/doc/Doing/Hooks.html +6 -6
- data/docs/doc/Doing/Item.html +50 -16
- data/docs/doc/Doing/Items.html +10 -10
- data/docs/doc/Doing/LogAdapter.html +24 -24
- data/docs/doc/Doing/Note.html +7 -7
- data/docs/doc/Doing/Pager.html +4 -4
- data/docs/doc/Doing/Plugins.html +7 -7
- data/docs/doc/Doing/Prompt.html +16 -16
- data/docs/doc/Doing/Section.html +6 -6
- data/docs/doc/Doing/TemplateString.html +8 -8
- data/docs/doc/Doing/Types.html +206 -0
- data/docs/doc/Doing/Util/Backup.html +10 -10
- data/docs/doc/Doing/Util.html +16 -19
- data/docs/doc/Doing/WWID.html +65 -53
- data/docs/doc/Doing.html +4 -4
- data/docs/doc/GLI/Commands/Help.html +185 -0
- data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +17 -17
- data/docs/doc/GLI/Commands.html +5 -3
- data/docs/doc/GLI.html +4 -2
- data/docs/doc/Hash.html +119 -21
- data/docs/doc/Numeric.html +5 -5
- data/docs/doc/PhraseParser/Operator.html +4 -4
- data/docs/doc/PhraseParser/PhraseClause.html +5 -5
- data/docs/doc/PhraseParser/Query.html +10 -10
- data/docs/doc/PhraseParser/QueryParser.html +2 -2
- data/docs/doc/PhraseParser/QueryTransformer.html +2 -2
- data/docs/doc/PhraseParser/TermClause.html +5 -5
- data/docs/doc/PhraseParser.html +1 -1
- data/docs/doc/Status.html +7 -7
- data/docs/doc/String.html +206 -51
- data/docs/doc/Symbol.html +8 -8
- data/docs/doc/Time.html +6 -6
- data/docs/doc/_index.html +51 -14
- data/docs/doc/class_list.html +1 -1
- data/docs/doc/file.README.html +2 -2
- data/docs/doc/index.html +2 -2
- data/docs/doc/method_list.html +348 -252
- data/docs/doc/top-level-namespace.html +2 -93
- data/docs/index.md +1 -1
- data/doing.rdoc +177 -20
- data/example_plugin.rb +2 -2
- data/lib/completion/_doing.zsh +24 -24
- data/lib/completion/doing.bash +31 -20
- data/lib/completion/doing.fish +32 -10
- data/lib/doing/array.rb +5 -4
- data/lib/doing/array_chronify.rb +4 -3
- data/lib/doing/changelog/change.rb +115 -0
- data/lib/doing/changelog/changes.rb +73 -0
- data/lib/doing/changelog/entry.rb +21 -0
- data/lib/doing/changelog/version.rb +97 -0
- data/lib/doing/changelog.rb +6 -0
- data/lib/doing/completion/fish_completion.rb +2 -1
- data/lib/doing/configuration.rb +20 -14
- data/lib/doing/good.rb +64 -0
- data/lib/doing/hash.rb +28 -5
- data/lib/doing/help_monkey_patch.rb +31 -0
- data/lib/doing/hooks.rb +8 -4
- data/lib/doing/item.rb +24 -35
- data/lib/doing/log_adapter.rb +1 -1
- data/lib/doing/pager.rb +1 -1
- data/lib/doing/plugins/export/template_export.rb +9 -3
- data/lib/doing/plugins/import/calendar_import.rb +1 -1
- data/lib/doing/plugins/import/doing_import.rb +1 -1
- data/lib/doing/plugins/import/timing_import.rb +1 -1
- data/lib/doing/prompt.rb +4 -2
- data/lib/doing/string.rb +30 -12
- data/lib/doing/string_chronify.rb +1 -1
- data/lib/doing/template_string.rb +9 -2
- data/lib/doing/types.rb +23 -16
- data/lib/doing/util.rb +12 -11
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +65 -46
- data/lib/doing.rb +3 -0
- data/lib/helpers/threaded_tests.rb +106 -99
- data/lib/helpers/threaded_tests_string.rb +50 -0
- 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.
|
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
|
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.
|
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
|
-
|
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.
|
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
|
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.
|
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'][
|
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:
|
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:
|
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
|
-
|
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'][
|
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'][
|
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
|
-
|
1890
|
-
|
1908
|
+
case: options[:case],
|
1909
|
+
config_template: 'last',
|
1891
1910
|
count: 1,
|
1892
|
-
|
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
|
-
|
1900
|
-
|
1901
|
-
|
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.
|
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
|
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
|
-
|
10
|
-
|
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:
|
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 =
|
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",
|
98
|
-
|
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
|
-
|
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
|
-
|
114
|
-
|
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
|
-
|
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
|