nanoc 4.7.1 → 4.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -3
  3. data/Gemfile.lock +4 -4
  4. data/NEWS.md +6 -0
  5. data/Rakefile +2 -2
  6. data/lib/nanoc/base/memoization.rb +3 -0
  7. data/lib/nanoc/base/services/outdatedness_rules.rb +17 -5
  8. data/lib/nanoc/base/views/post_compile_item_rep_view.rb +4 -0
  9. data/lib/nanoc/checking/check.rb +1 -1
  10. data/lib/nanoc/cli/commands/compile_listeners/timing_recorder.rb +36 -0
  11. data/lib/nanoc/cli/commands/show-plugins.rb +1 -1
  12. data/lib/nanoc/filters/colorize_syntax.rb +1 -1
  13. data/lib/nanoc/telemetry/labelled_counter.rb +7 -1
  14. data/lib/nanoc/version.rb +1 -1
  15. data/spec/nanoc/base/entities/outdatedness_status_spec.rb +1 -1
  16. data/spec/nanoc/base/entities/processing_actions/snapshot_spec.rb +1 -1
  17. data/spec/nanoc/base/entities/props_spec.rb +8 -8
  18. data/spec/nanoc/base/entities/rule_memory_spec.rb +2 -2
  19. data/spec/nanoc/base/memoization_spec.rb +75 -0
  20. data/spec/nanoc/base/services/item_rep_selector_spec.rb +12 -12
  21. data/spec/nanoc/base/views/post_compile_item_rep_view_spec.rb +58 -0
  22. data/spec/nanoc/cli/commands/compile/timing_recorder_spec.rb +22 -0
  23. data/spec/nanoc/helpers/tagging_spec.rb +1 -1
  24. data/spec/nanoc/regressions/gh_1130_spec.rb +19 -0
  25. data/spec/nanoc/rule_dsl/rule_memory_calculator_spec.rb +4 -4
  26. data/spec/nanoc/telemetry/labelled_counter_spec.rb +36 -0
  27. data/spec/spec_helper.rb +31 -0
  28. data/test/base/core_ext/array_spec.rb +2 -2
  29. data/test/cli/test_cleaning_stream.rb +1 -1
  30. data/test/filters/colorize_syntax/test_common.rb +1 -1
  31. metadata +4 -3
  32. data/test/base/test_memoization.rb +0 -71
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3697f07907752b5c0010bbf3fb01e701d6128bba
4
- data.tar.gz: 70dfb18ac4973838113380a201ddde4468f2b13b
3
+ metadata.gz: db20da1ff9f55058bb62c68ac59ec1abd74e5afe
4
+ data.tar.gz: fc4c46dfd186f22446ad8a39c457fc72fcd93994
5
5
  SHA512:
6
- metadata.gz: e8810bd89fcd0e3c65f647ec3b99a76454dc2fa35169b5b4e66317f178b260e2e8229686342aee8c83929e7de04b323c98137c8c8b690c7b1259fabed99e2147
7
- data.tar.gz: 144aacfa4f5981109e514c8c33cde796598e86ec27c2b6d115ff2c4b497dc25b5d7a5950a071ad4e23dce5c68063b699b3fb42258f6b0f7c31ded9a81c311177
6
+ metadata.gz: b8f484dac9493470c64172ac234a026f17413a0fe688782426a606396e51b343c866165e24e58a574dfd948a86ea4ae1006114dbb1e495454fa27bd83f468e13
7
+ data.tar.gz: bc68076395418f75d02e6ece28d8af476423007b63d3badc18238c52c711352f02eab2ca1ec13ea79865154fca17ba15da4b0d2653bf1d32bc261ce615757ae0
data/Gemfile CHANGED
@@ -49,11 +49,11 @@ group :plugins do
49
49
  gem 'mustache', '~> 1.0'
50
50
  gem 'nokogiri', '~> 1.6'
51
51
  gem 'pandoc-ruby'
52
- gem 'pygments.rb', github: 'tmm1/pygments.rb', platforms: [:ruby, :mswin]
52
+ gem 'pygments.rb', github: 'tmm1/pygments.rb', platforms: %i(ruby mswin)
53
53
  gem 'rack'
54
54
  gem 'rainpress'
55
- gem 'rdiscount', '~> 2.2', platforms: [:ruby, :mswin]
56
- gem 'redcarpet', platforms: [:ruby, :mswin]
55
+ gem 'rdiscount', '~> 2.2', platforms: %i(ruby mswin)
56
+ gem 'redcarpet', platforms: %i(ruby mswin)
57
57
  gem 'RedCloth', platforms: :ruby
58
58
  gem 'rouge'
59
59
  gem 'rubypants'
@@ -1,6 +1,6 @@
1
1
  GIT
2
2
  remote: https://github.com/bbatsov/rubocop.git
3
- revision: a047a11633e104d5ee9ca4d2957e4b1cf571f30b
3
+ revision: 7917f22cb5f1e2ce6c81a1cc07b2b4a14fcd4fa8
4
4
  specs:
5
5
  rubocop (0.47.1)
6
6
  parser (>= 2.3.3.1, < 3.0)
@@ -27,7 +27,7 @@ GIT
27
27
  PATH
28
28
  remote: .
29
29
  specs:
30
- nanoc (4.7.1)
30
+ nanoc (4.7.2)
31
31
  cri (~> 2.3)
32
32
  ddplugin (~> 1.0)
33
33
  hamster (~> 3.0)
@@ -341,13 +341,13 @@ GEM
341
341
  term-ansicolor (1.4.0)
342
342
  tins (~> 1.0)
343
343
  thor (0.19.4)
344
- tilt (2.0.6)
344
+ tilt (2.0.7)
345
345
  timecop (0.8.1)
346
346
  tins (1.13.2)
347
347
  trollop (2.1.2)
348
348
  typogruby (1.0.18)
349
349
  rubypants
350
- uglifier (3.1.8)
350
+ uglifier (3.1.9)
351
351
  execjs (>= 0.3.0, < 3)
352
352
  unicode-display_width (1.1.3)
353
353
  url (0.3.2)
data/NEWS.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Nanoc news
2
2
 
3
+ ## 4.7.2 (2017-03-21)
4
+
5
+ Fixes:
6
+
7
+ * Fixed crash when calling `#raw_path` in the Checks file (#1130, #1131)
8
+
3
9
  ## 4.7.1 (2017-03-19)
4
10
 
5
11
  Fixes:
data/Rakefile CHANGED
@@ -14,7 +14,7 @@ end
14
14
 
15
15
  RSpec::Core::RakeTask.new(:spec)
16
16
 
17
- task test: [:spec, :test_all, :rubocop]
18
- task test_ci: [:test, :'coveralls:push']
17
+ task test: %i(spec test_all rubocop)
18
+ task test_ci: %i(test coveralls:push)
19
19
 
20
20
  task default: :test
@@ -63,11 +63,14 @@ module Nanoc::Int
63
63
  value = object ? object.value : NONE
64
64
  end
65
65
 
66
+ counter_label = is_a?(Class) ? "#{self}.#{method_name}" : "#{self.class}##{method_name}"
66
67
  if value.equal?(NONE)
68
+ Nanoc::Int::NotificationCenter.post(:memoization_miss, counter_label)
67
69
  send(original_method_name, *args).tap do |r|
68
70
  instance_method_cache[args] = Ref::SoftReference.new(Value.new(r))
69
71
  end
70
72
  else
73
+ Nanoc::Int::NotificationCenter.post(:memoization_hit, counter_label)
71
74
  value
72
75
  end
73
76
  end
@@ -74,17 +74,29 @@ module Nanoc::Int
74
74
  end
75
75
 
76
76
  class AttributesModified < OutdatednessRule
77
+ extend Nanoc::Int::Memoization
78
+
79
+ include Nanoc::Int::ContractsSupport
80
+
77
81
  def reason
78
82
  Nanoc::Int::OutdatednessReasons::AttributesModified
79
83
  end
80
84
 
85
+ contract C::Or[Nanoc::Int::ItemRep, Nanoc::Int::Item, Nanoc::Int::Layout], C::Named['Nanoc::Int::OutdatednessChecker'] => C::Bool
81
86
  def apply(obj, outdatedness_checker)
82
- obj = obj.item if obj.is_a?(Nanoc::Int::ItemRep)
83
-
84
- ch_old = outdatedness_checker.checksum_store.attributes_checksum_for(obj)
85
- ch_new = Nanoc::Int::Checksummer.calc_for_attributes_of(obj)
86
- ch_old != ch_new
87
+ case obj
88
+ when Nanoc::Int::ItemRep
89
+ apply(obj.item, outdatedness_checker)
90
+ when Nanoc::Int::Item, Nanoc::Int::Layout
91
+ ch_old = outdatedness_checker.checksum_store.attributes_checksum_for(obj)
92
+ ch_new = Nanoc::Int::Checksummer.calc_for_attributes_of(obj)
93
+ res = ch_old != ch_new
94
+ res
95
+ else
96
+ raise ArgumentError
97
+ end
87
98
  end
99
+ memoize :apply
88
100
  end
89
101
 
90
102
  class RulesModified < OutdatednessRule
@@ -16,5 +16,9 @@ module Nanoc
16
16
 
17
17
  content.string
18
18
  end
19
+
20
+ def raw_path(snapshot: :last)
21
+ @item_rep.raw_path(snapshot: snapshot)
22
+ end
19
23
  end
20
24
  end
@@ -23,7 +23,7 @@ module Nanoc::Checking
23
23
  view_context = site.compiler.compilation_context.create_view_context(Nanoc::Int::DependencyTracker::Null.new)
24
24
 
25
25
  context = {
26
- items: Nanoc::ItemCollectionWithRepsView.new(site.items, view_context),
26
+ items: Nanoc::PostCompileItemCollectionView.new(site.items, view_context),
27
27
  layouts: Nanoc::LayoutCollectionView.new(site.layouts, view_context),
28
28
  config: Nanoc::ConfigView.new(site.config, view_context),
29
29
  output_filenames: output_filenames,
@@ -96,6 +96,14 @@ module Nanoc::CLI::Commands::CompileListeners
96
96
 
97
97
  @telemetry.summary(:phases).observe(stopwatch.duration, phase_name)
98
98
  end
99
+
100
+ on(:memoization_miss) do |label|
101
+ @telemetry.counter(:memoization).increment([label, :miss])
102
+ end
103
+
104
+ on(:memoization_hit) do |label|
105
+ @telemetry.counter(:memoization).increment([label, :hit])
106
+ end
99
107
  end
100
108
 
101
109
  # @see Listener#stop
@@ -134,11 +142,32 @@ module Nanoc::CLI::Commands::CompileListeners
134
142
  [headers] + rows
135
143
  end
136
144
 
145
+ def table_for_memoization
146
+ headers = %w(memoization hit miss %)
147
+
148
+ rows_raw = @telemetry.counter(:memoization).map do |(name, type), counter|
149
+ { name: name, type: type, count: counter.value }
150
+ end
151
+
152
+ rows = rows_raw.group_by { |r| r[:name] }.map do |name, rows_for_name|
153
+ rows_by_type = rows_for_name.group_by { |r| r[:type] }
154
+
155
+ num_hit = rows_by_type.fetch(:hit, []).fetch(0, {}).fetch(:count, 0)
156
+ num_miss = rows_by_type.fetch(:miss, []).fetch(0, {}).fetch(:count, 0)
157
+ pct = num_hit.to_f / (num_hit + num_miss).to_f
158
+
159
+ [name, num_hit.to_s, num_miss.to_s, "#{format('%3.1f', pct * 100)}%"]
160
+ end
161
+
162
+ [headers] + rows
163
+ end
164
+
137
165
  def print_profiling_feedback
138
166
  print_table_for_summary(:filters)
139
167
  print_table_for_summary(:phases) if Nanoc::CLI.verbosity >= 2
140
168
  print_table_for_summary_duration(:stages) if Nanoc::CLI.verbosity >= 2
141
169
  print_table_for_summary(:outdatedness_rules) if Nanoc::CLI.verbosity >= 2
170
+ print_table_for_memoization if Nanoc::CLI.verbosity >= 2
142
171
  end
143
172
 
144
173
  def print_table_for_summary(name)
@@ -155,6 +184,13 @@ module Nanoc::CLI::Commands::CompileListeners
155
184
  print_table(table_for_summary_durations(name))
156
185
  end
157
186
 
187
+ def print_table_for_memoization
188
+ return if @telemetry.counter(:memoization).empty?
189
+
190
+ puts
191
+ print_table(table_for_memoization)
192
+ end
193
+
158
194
  def print_table(table)
159
195
  lengths = table.transpose.map { |r| r.map(&:size).max }
160
196
 
@@ -41,7 +41,7 @@ module Nanoc::CLI::Commands
41
41
  puts
42
42
 
43
43
  # Print plugins organised by subtype
44
- [:builtin, :custom].each do |type|
44
+ %i(builtin custom).each do |type|
45
45
  # Find relevant plugins
46
46
  relevant_plugins = plugins_with_this_superclass[type]
47
47
 
@@ -358,7 +358,7 @@ module Nanoc::Filters
358
358
 
359
359
  protected
360
360
 
361
- KNOWN_COLORIZERS = [:coderay, :dummy, :pygmentize, :pygmentsrb, :simon_highlight, :rouge].freeze
361
+ KNOWN_COLORIZERS = %i(coderay dummy pygmentize pygmentsrb simon_highlight rouge).freeze
362
362
 
363
363
  # Removes the first blank lines and any whitespace at the end.
364
364
  def strip(s)
@@ -12,7 +12,9 @@ module Nanoc::Telemetry
12
12
  @counters.fetch(label) { @counters[label] = Counter.new }
13
13
  end
14
14
 
15
- # TODO: add #empty?
15
+ def empty?
16
+ @counters.empty?
17
+ end
16
18
 
17
19
  def value(label)
18
20
  get(label).value
@@ -23,5 +25,9 @@ module Nanoc::Telemetry
23
25
  res[label] = counter.value
24
26
  end
25
27
  end
28
+
29
+ def map
30
+ @counters.map { |(label, counter)| yield(label, counter) }
31
+ end
26
32
  end
27
33
  end
@@ -1,4 +1,4 @@
1
1
  module Nanoc
2
2
  # The current Nanoc version.
3
- VERSION = '4.7.1'.freeze
3
+ VERSION = '4.7.2'.freeze
4
4
  end
@@ -106,7 +106,7 @@ describe Nanoc::Int::OutdatednessStatus do
106
106
  let(:status) { described_class.new(props: Nanoc::Int::Props.new(attributes: true)) }
107
107
 
108
108
  it 'updates props' do
109
- expect(subject.props.active).to eql(Set.new([:raw_content, :attributes, :compiled_content]))
109
+ expect(subject.props.active).to eql(Set.new(%i(raw_content attributes compiled_content)))
110
110
  end
111
111
  end
112
112
  end
@@ -25,7 +25,7 @@ describe Nanoc::Int::ProcessingActions::Snapshot do
25
25
 
26
26
  context 'with snapshot name' do
27
27
  subject { action.update(snapshot_names: [:zebra]) }
28
- its(:snapshot_names) { is_expected.to eql([:before_layout, :zebra]) }
28
+ its(:snapshot_names) { is_expected.to eql(%i(before_layout zebra)) }
29
29
  its(:paths) { is_expected.to eql(['/foo.md']) }
30
30
  end
31
31
 
@@ -89,7 +89,7 @@ describe Nanoc::Int::Props do
89
89
  let(:props) { described_class.new }
90
90
  let(:other_props) { props_all }
91
91
 
92
- it { is_expected.to eql(Set.new([:raw_content, :attributes, :compiled_content, :path])) }
92
+ it { is_expected.to eql(Set.new(%i(raw_content attributes compiled_content path))) }
93
93
  end
94
94
 
95
95
  context 'some + nothing' do
@@ -103,35 +103,35 @@ describe Nanoc::Int::Props do
103
103
  let(:props) { described_class.new(compiled_content: true) }
104
104
  let(:other_props) { described_class.new(raw_content: true) }
105
105
 
106
- it { is_expected.to eql(Set.new([:raw_content, :compiled_content])) }
106
+ it { is_expected.to eql(Set.new(%i(raw_content compiled_content))) }
107
107
  end
108
108
 
109
109
  context 'some + all' do
110
110
  let(:props) { described_class.new(compiled_content: true) }
111
111
  let(:other_props) { props_all }
112
112
 
113
- it { is_expected.to eql(Set.new([:raw_content, :attributes, :compiled_content, :path])) }
113
+ it { is_expected.to eql(Set.new(%i(raw_content attributes compiled_content path))) }
114
114
  end
115
115
 
116
116
  context 'all + nothing' do
117
117
  let(:props) { props_all }
118
118
  let(:other_props) { described_class.new }
119
119
 
120
- it { is_expected.to eql(Set.new([:raw_content, :attributes, :compiled_content, :path])) }
120
+ it { is_expected.to eql(Set.new(%i(raw_content attributes compiled_content path))) }
121
121
  end
122
122
 
123
123
  context 'some + all' do
124
124
  let(:props) { props_all }
125
125
  let(:other_props) { described_class.new(compiled_content: true) }
126
126
 
127
- it { is_expected.to eql(Set.new([:raw_content, :attributes, :compiled_content, :path])) }
127
+ it { is_expected.to eql(Set.new(%i(raw_content attributes compiled_content path))) }
128
128
  end
129
129
 
130
130
  context 'all + all' do
131
131
  let(:props) { props_all }
132
132
  let(:other_props) { props_all }
133
133
 
134
- it { is_expected.to eql(Set.new([:raw_content, :attributes, :compiled_content, :path])) }
134
+ it { is_expected.to eql(Set.new(%i(raw_content attributes compiled_content path))) }
135
135
  end
136
136
  end
137
137
 
@@ -165,12 +165,12 @@ describe Nanoc::Int::Props do
165
165
 
166
166
  context 'attributes and compiled_content active' do
167
167
  let(:props) { described_class.new(attributes: true, compiled_content: true) }
168
- it { is_expected.to eql(Set.new([:attributes, :compiled_content])) }
168
+ it { is_expected.to eql(Set.new(%i(attributes compiled_content))) }
169
169
  end
170
170
 
171
171
  context 'all active' do
172
172
  let(:props) { described_class.new(raw_content: true, attributes: true, compiled_content: true, path: true) }
173
- it { is_expected.to eql(Set.new([:raw_content, :attributes, :compiled_content, :path])) }
173
+ it { is_expected.to eql(Set.new(%i(raw_content attributes compiled_content path))) }
174
174
  end
175
175
  end
176
176
 
@@ -146,13 +146,13 @@ describe Nanoc::Int::RuleMemory do
146
146
 
147
147
  example do
148
148
  expect(subject[0]).to be_a(Nanoc::Int::ProcessingActions::Snapshot)
149
- expect(subject[0].snapshot_names).to eql([:a1, :a2, :a3])
149
+ expect(subject[0].snapshot_names).to eql(%i(a1 a2 a3))
150
150
  expect(subject[0].paths).to eql(['/a2.md'])
151
151
 
152
152
  expect(subject[1]).to be_a(Nanoc::Int::ProcessingActions::Filter)
153
153
 
154
154
  expect(subject[2]).to be_a(Nanoc::Int::ProcessingActions::Snapshot)
155
- expect(subject[2].snapshot_names).to eql([:b1, :b2, :b3])
155
+ expect(subject[2].snapshot_names).to eql(%i(b1 b2 b3))
156
156
  expect(subject[2].paths).to eql(['/b1.md', '/b3.md'])
157
157
 
158
158
  expect(subject[3]).to be_a(Nanoc::Int::ProcessingActions::Filter)
@@ -0,0 +1,75 @@
1
+ describe Nanoc::Int::Memoization do
2
+ class MemoizationSpecSample1
3
+ extend Nanoc::Int::Memoization
4
+
5
+ def initialize(value)
6
+ @value = value
7
+ end
8
+
9
+ def run(n)
10
+ @value * 10 + n
11
+ end
12
+ memoize :run
13
+ end
14
+
15
+ class MemoizationSpecSample2
16
+ extend Nanoc::Int::Memoization
17
+
18
+ def initialize(value)
19
+ @value = value
20
+ end
21
+
22
+ def run(n)
23
+ @value * 100 + n
24
+ end
25
+ memoize :run
26
+ end
27
+
28
+ class MemoizationSpecUpcaser
29
+ extend Nanoc::Int::Memoization
30
+
31
+ def run(value)
32
+ value.upcase
33
+ end
34
+ memoize :run
35
+ end
36
+
37
+ example do
38
+ sample1a = MemoizationSpecSample1.new(10)
39
+ sample1b = MemoizationSpecSample1.new(15)
40
+ sample2a = MemoizationSpecSample2.new(20)
41
+ sample2b = MemoizationSpecSample2.new(25)
42
+
43
+ 3.times do
44
+ expect(sample1a.run(5)).to eq(10 * 10 + 5)
45
+ expect(sample1b.run(7)).to eq(10 * 15 + 7)
46
+ expect(sample2a.run(5)).to eq(100 * 20 + 5)
47
+ expect(sample2b.run(7)).to eq(100 * 25 + 7)
48
+ end
49
+ end
50
+
51
+ it 'supports frozen objects' do
52
+ sample = MemoizationSpecSample1.new(10)
53
+ sample.freeze
54
+ sample.run(5)
55
+ end
56
+
57
+ it 'does not crash on #inspect' do
58
+ upcaser = MemoizationSpecUpcaser.new
59
+ 10_000.times do |i|
60
+ upcaser.run("hello world #{i}")
61
+ end
62
+
63
+ GC.start
64
+ GC.start
65
+
66
+ upcaser.inspect
67
+ end
68
+
69
+ it 'sends notifications' do
70
+ sample = MemoizationSpecSample1.new(10)
71
+ expect { sample.run(5) }.to send_notification(:memoization_miss, 'MemoizationSpecSample1#run')
72
+ expect { sample.run(5) }.to send_notification(:memoization_hit, 'MemoizationSpecSample1#run')
73
+ expect { sample.run(5) }.to send_notification(:memoization_hit, 'MemoizationSpecSample1#run')
74
+ end
75
+ end
@@ -122,8 +122,8 @@ describe Nanoc::Int::ItemRepSelector do
122
122
  end
123
123
 
124
124
  example do
125
- expect(successfully_yielded).to eq [:e, :d, :c, :b, :a]
126
- expect(tentatively_yielded).to eq [:a, :b, :c, :d, :e, :d, :c, :b, :a]
125
+ expect(successfully_yielded).to eq %i(e d c b a)
126
+ expect(tentatively_yielded).to eq %i(a b c d e d c b a)
127
127
  end
128
128
  end
129
129
 
@@ -133,21 +133,21 @@ describe Nanoc::Int::ItemRepSelector do
133
133
  end
134
134
 
135
135
  example do
136
- expect(successfully_yielded).to eq [:a, :b, :c, :d, :e]
137
- expect(tentatively_yielded).to eq [:a, :b, :c, :d, :e]
136
+ expect(successfully_yielded).to eq %i(a b c d e)
137
+ expect(tentatively_yielded).to eq %i(a b c d e)
138
138
  end
139
139
  end
140
140
 
141
141
  context 'star dependencies' do
142
142
  let(:dependencies) do
143
143
  {
144
- a: [:b, :c, :d, :e],
144
+ a: %i(b c d e),
145
145
  }
146
146
  end
147
147
 
148
148
  example do
149
- expect(successfully_yielded).to eq [:b, :c, :d, :e, :a]
150
- expect(tentatively_yielded).to eq [:a, :b, :a, :c, :a, :d, :a, :e, :a]
149
+ expect(successfully_yielded).to eq %i(b c d e a)
150
+ expect(tentatively_yielded).to eq %i(a b a c a d a e a)
151
151
  end
152
152
  end
153
153
 
@@ -156,13 +156,13 @@ describe Nanoc::Int::ItemRepSelector do
156
156
 
157
157
  let(:dependencies) do
158
158
  {
159
- a: [:b, :c, :d, :e],
159
+ a: %i(b c d e),
160
160
  }
161
161
  end
162
162
 
163
163
  example do
164
- expect(successfully_yielded).to eq [:b, :c, :d, :e, :a]
165
- expect(tentatively_yielded).to eq [:a, :b, :a, :c, :a, :d, :a, :e, :a]
164
+ expect(successfully_yielded).to eq %i(b c d e a)
165
+ expect(tentatively_yielded).to eq %i(a b a c a d a e a)
166
166
  end
167
167
  end
168
168
 
@@ -176,8 +176,8 @@ describe Nanoc::Int::ItemRepSelector do
176
176
  end
177
177
 
178
178
  it 'picks prioritised roots' do
179
- expect(successfully_yielded).to eq [:d, :a, :e, :b, :c]
180
- expect(tentatively_yielded).to eq [:a, :d, :a, :b, :e, :b, :c]
179
+ expect(successfully_yielded).to eq %i(d a e b c)
180
+ expect(tentatively_yielded).to eq %i(a d a b e b c)
181
181
  end
182
182
  end
183
183
  end
@@ -34,6 +34,64 @@ describe Nanoc::PostCompileItemRepView do
34
34
  end
35
35
  end
36
36
 
37
+ describe '#raw_path' do
38
+ context 'no args' do
39
+ subject { view.raw_path }
40
+
41
+ it 'does not raise' do
42
+ subject
43
+ end
44
+
45
+ context 'no path specified' do
46
+ it { is_expected.to be_nil }
47
+ end
48
+
49
+ context 'path for default snapshot specified' do
50
+ before do
51
+ item_rep.raw_paths = { last: ['output/about/index.html'] }
52
+ end
53
+
54
+ it { is_expected.to eql('output/about/index.html') }
55
+ end
56
+
57
+ context 'path specified, but not for default snapshot' do
58
+ before do
59
+ item_rep.raw_paths = { pre: ['output/about/index.html'] }
60
+ end
61
+
62
+ it { is_expected.to be_nil }
63
+ end
64
+ end
65
+
66
+ context 'snapshot arg' do
67
+ subject { view.raw_path(snapshot: :special) }
68
+
69
+ it 'does not raise' do
70
+ subject
71
+ end
72
+
73
+ context 'no path specified' do
74
+ it { is_expected.to be_nil }
75
+ end
76
+
77
+ context 'path for default snapshot specified' do
78
+ before do
79
+ item_rep.raw_paths = { special: ['output/about/index.html'] }
80
+ end
81
+
82
+ it { is_expected.to eql('output/about/index.html') }
83
+ end
84
+
85
+ context 'path specified, but not for default snapshot' do
86
+ before do
87
+ item_rep.raw_paths = { pre: ['output/about/index.html'] }
88
+ end
89
+
90
+ it { is_expected.to be_nil }
91
+ end
92
+ end
93
+ end
94
+
37
95
  describe '#compiled_content' do
38
96
  subject { view.compiled_content }
39
97
 
@@ -260,4 +260,26 @@ describe Nanoc::CLI::Commands::CompileListeners::TimingRecorder, stdio: true do
260
260
  expect(listener.telemetry.summary(:outdatedness_rules).get('CodeSnippetsModified').sum).to eq(4.00)
261
261
  expect(listener.telemetry.summary(:outdatedness_rules).get('CodeSnippetsModified').count).to eq(2.00)
262
262
  end
263
+
264
+ it 'records memoization usage' do
265
+ Nanoc::Int::NotificationCenter.post(:memoization_hit, 'Foo#bar', rep)
266
+ Nanoc::Int::NotificationCenter.post(:memoization_miss, 'Foo#bar', rep)
267
+ Nanoc::Int::NotificationCenter.post(:memoization_miss, 'Foo#bar', rep)
268
+ Nanoc::Int::NotificationCenter.post(:memoization_miss, 'Foo#bar', rep)
269
+ Nanoc::Int::NotificationCenter.post(:memoization_miss, 'Foo#bar', rep)
270
+
271
+ expect(listener.telemetry.counter(:memoization).get(['Foo#bar', :hit]).value).to eq(1)
272
+ expect(listener.telemetry.counter(:memoization).get(['Foo#bar', :miss]).value).to eq(4)
273
+ end
274
+
275
+ it 'prints memoization table' do
276
+ Nanoc::Int::NotificationCenter.post(:memoization_hit, 'Foo#bar', rep)
277
+ Nanoc::Int::NotificationCenter.post(:memoization_miss, 'Foo#bar', rep)
278
+ Nanoc::Int::NotificationCenter.post(:memoization_miss, 'Foo#bar', rep)
279
+ Nanoc::Int::NotificationCenter.post(:memoization_miss, 'Foo#bar', rep)
280
+ Nanoc::Int::NotificationCenter.post(:memoization_miss, 'Foo#bar', rep)
281
+
282
+ expect { listener.stop }
283
+ .to output(/^\s*Foo#bar │ 1 4 20\.0%$/).to_stdout
284
+ end
263
285
  end
@@ -72,7 +72,7 @@ describe Nanoc::Helpers::Tagging, helper: true do
72
72
  before do
73
73
  ctx.create_item('item 1', { tags: [:foo] }, '/item1.md')
74
74
  ctx.create_item('item 2', { tags: [:bar] }, '/item2.md')
75
- ctx.create_item('item 3', { tags: [:foo, :bar] }, '/item3.md')
75
+ ctx.create_item('item 3', { tags: %i(foo bar) }, '/item3.md')
76
76
  ctx.create_item('item 4', { tags: nil }, '/item4.md')
77
77
  ctx.create_item('item 5', {}, '/item5.md')
78
78
  end
@@ -0,0 +1,19 @@
1
+ describe 'GH-1130', site: true, stdio: true do
2
+ before do
3
+ File.write('content/foo', 'asdf')
4
+
5
+ File.write('Rules', <<EOS)
6
+ passthrough '/**/*'
7
+ EOS
8
+
9
+ File.write('Checks', <<EOS)
10
+ check :wat do
11
+ @items.flat_map(&:reps).map(&:raw_path)
12
+ end
13
+ EOS
14
+ end
15
+
16
+ it 'does not raise fiber error' do
17
+ Nanoc::CLI.run(%w(check wat))
18
+ end
19
+ end
@@ -67,7 +67,7 @@ describe(Nanoc::RuleDSL::RuleMemoryCalculator) do
67
67
  expect(subject[4].params).to eql({})
68
68
 
69
69
  expect(subject[5]).to be_a(Nanoc::Int::ProcessingActions::Snapshot)
70
- expect(subject[5].snapshot_names).to eql([:post, :last])
70
+ expect(subject[5].snapshot_names).to eql(%i(post last))
71
71
  expect(subject[5].paths).to be_empty
72
72
 
73
73
  expect(subject.size).to eql(6)
@@ -85,7 +85,7 @@ describe(Nanoc::RuleDSL::RuleMemoryCalculator) do
85
85
  subject
86
86
 
87
87
  expect(subject[0]).to be_a(Nanoc::Int::ProcessingActions::Snapshot)
88
- expect(subject[0].snapshot_names).to eql([:raw, :last, :pre])
88
+ expect(subject[0].snapshot_names).to eql(%i(raw last pre))
89
89
  expect(subject[0].paths).to be_empty
90
90
 
91
91
  expect(subject.size).to eql(1)
@@ -107,7 +107,7 @@ describe(Nanoc::RuleDSL::RuleMemoryCalculator) do
107
107
  subject
108
108
 
109
109
  expect(subject[0]).to be_a(Nanoc::Int::ProcessingActions::Snapshot)
110
- expect(subject[0].snapshot_names).to eql([:raw, :last, :pre])
110
+ expect(subject[0].snapshot_names).to eql(%i(raw last pre))
111
111
  expect(subject[0].paths).to eq(['/foo.md'])
112
112
 
113
113
  expect(subject.size).to eql(1)
@@ -129,7 +129,7 @@ describe(Nanoc::RuleDSL::RuleMemoryCalculator) do
129
129
  subject
130
130
 
131
131
  expect(subject[0]).to be_a(Nanoc::Int::ProcessingActions::Snapshot)
132
- expect(subject[0].snapshot_names).to eql([:raw, :last, :pre])
132
+ expect(subject[0].snapshot_names).to eql(%i(raw last pre))
133
133
  expect(subject[0].paths).to be_empty
134
134
 
135
135
  expect(subject.size).to eql(1)
@@ -53,4 +53,40 @@ describe Nanoc::Telemetry::LabelledCounter do
53
53
  its(:value) { is_expected.to eq(0) }
54
54
  end
55
55
  end
56
+
57
+ describe '#empty?' do
58
+ subject { counter.empty? }
59
+
60
+ context 'not incremented' do
61
+ it { is_expected.to be }
62
+ end
63
+
64
+ context 'incremented' do
65
+ before { counter.increment(:erb) }
66
+ it { is_expected.not_to be }
67
+ end
68
+ end
69
+
70
+ describe '#map' do
71
+ subject { counter.map { |label, counter| [label, counter.value] } }
72
+
73
+ context 'not incremented' do
74
+ it { is_expected.to be_empty }
75
+ end
76
+
77
+ context 'incremented once' do
78
+ before { counter.increment(:erb) }
79
+ it { is_expected.to eq [[:erb, 1]] }
80
+ end
81
+
82
+ context 'both incremental multiple times' do
83
+ before do
84
+ counter.increment(:erb)
85
+ counter.increment(:erb)
86
+ counter.increment(:haml)
87
+ end
88
+
89
+ it { is_expected.to eq [[:erb, 2], [:haml, 1]] }
90
+ end
91
+ end
56
92
  end
@@ -245,3 +245,34 @@ end
245
245
 
246
246
  RSpec::Matchers.alias_matcher :some_textual_content, :be_some_textual_content
247
247
  RSpec::Matchers.alias_matcher :some_binary_content, :be_some_binary_content
248
+
249
+ RSpec::Matchers.define :send_notification do |name, *expected_args|
250
+ supports_block_expectations
251
+
252
+ include RSpec::Matchers::Composable
253
+
254
+ match do |actual|
255
+ @actual_notifications = []
256
+ Nanoc::Int::NotificationCenter.on(name, self) do |*actual_args|
257
+ @actual_notifications << actual_args
258
+ end
259
+ actual.call
260
+ @actual_notifications.any? { |c| c == expected_args }
261
+ end
262
+
263
+ description do
264
+ "send notification #{name.inspect} with args #{expected_args.inspect}"
265
+ end
266
+
267
+ failure_message do |_actual|
268
+ s = "expected that proc would send notification #{name.inspect} with args #{expected_args.inspect}"
269
+ if @actual_notifications.any?
270
+ s << " (received #{@actual_notifications.size} times with other arguments: #{@actual_notifications.map(&:inspect).join(', ')})"
271
+ end
272
+ s
273
+ end
274
+
275
+ failure_message_when_negated do |_actual|
276
+ "expected that proc would not send notification #{name.inspect} with args #{expected_args.inspect}"
277
+ end
278
+ end
@@ -12,7 +12,7 @@ describe 'Array#__nanoc_freeze_recursively' do
12
12
  include Nanoc::TestHelpers
13
13
 
14
14
  it 'should prevent first-level elements from being modified' do
15
- array = [:a, [:b, :c], :d]
15
+ array = [:a, %i(b c), :d]
16
16
  array.__nanoc_freeze_recursively
17
17
 
18
18
  assert_raises_frozen_error do
@@ -21,7 +21,7 @@ describe 'Array#__nanoc_freeze_recursively' do
21
21
  end
22
22
 
23
23
  it 'should prevent second-level elements from being modified' do
24
- array = [:a, [:b, :c], :d]
24
+ array = [:a, %i(b c), :d]
25
25
  array.__nanoc_freeze_recursively
26
26
 
27
27
  assert_raises_frozen_error do
@@ -20,7 +20,7 @@ class Nanoc::CLI::CleaningStreamTest < Nanoc::TestCase
20
20
  end
21
21
 
22
22
  def test_forward
23
- methods = [:write, :<<, :tty?, :tty?, :flush, :tell, :print, :puts, :string, :reopen, :exist?, :exists?, :close]
23
+ methods = %i(write << tty? tty? flush tell print puts string reopen exist? exists? close)
24
24
 
25
25
  s = Stream.new
26
26
  cs = Nanoc::CLI::CleaningStream.new(s)
@@ -64,7 +64,7 @@ EOS
64
64
  input = '<pre><code class="language-ruby">puts "foo"</code></pre>'
65
65
 
66
66
  # Run filter
67
- [:albino, :pygmentize, :simon_highlight].each do |colorizer|
67
+ %i(albino pygmentize simon_highlight).each do |colorizer|
68
68
  begin
69
69
  input = '<pre><code class="language-ruby">puts "foo"</code></pre>'
70
70
  filter.setup_and_run(
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nanoc
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.7.1
4
+ version: 4.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Defreyne
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-03-19 00:00:00.000000000 Z
11
+ date: 2017-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cri
@@ -377,6 +377,7 @@ files:
377
377
  - spec/nanoc/base/feature_spec.rb
378
378
  - spec/nanoc/base/filter_spec.rb
379
379
  - spec/nanoc/base/item_rep_writer_spec.rb
380
+ - spec/nanoc/base/memoization_spec.rb
380
381
  - spec/nanoc/base/repos/checksum_store_spec.rb
381
382
  - spec/nanoc/base/repos/compiled_content_cache_spec.rb
382
383
  - spec/nanoc/base/repos/config_loader_spec.rb
@@ -466,6 +467,7 @@ files:
466
467
  - spec/nanoc/regressions/gh_1100_spec.rb
467
468
  - spec/nanoc/regressions/gh_1102_spec.rb
468
469
  - spec/nanoc/regressions/gh_1107_spec.rb
470
+ - spec/nanoc/regressions/gh_1130_spec.rb
469
471
  - spec/nanoc/regressions/gh_761_spec.rb
470
472
  - spec/nanoc/regressions/gh_767_spec.rb
471
473
  - spec/nanoc/regressions/gh_769_spec.rb
@@ -524,7 +526,6 @@ files:
524
526
  - test/base/test_item.rb
525
527
  - test/base/test_item_array.rb
526
528
  - test/base/test_layout.rb
527
- - test/base/test_memoization.rb
528
529
  - test/base/test_notification_center.rb
529
530
  - test/base/test_outdatedness_checker.rb
530
531
  - test/base/test_site.rb
@@ -1,71 +0,0 @@
1
- require 'helper'
2
-
3
- class Nanoc::Int::MemoizationTest < Nanoc::TestCase
4
- class Sample1
5
- extend Nanoc::Int::Memoization
6
-
7
- def initialize(value)
8
- @value = value
9
- end
10
-
11
- def run(n)
12
- @value * 10 + n
13
- end
14
- memoize :run
15
- end
16
-
17
- class Sample2
18
- extend Nanoc::Int::Memoization
19
-
20
- def initialize(value)
21
- @value = value
22
- end
23
-
24
- def run(n)
25
- @value * 100 + n
26
- end
27
- memoize :run
28
- end
29
-
30
- class Upcaser
31
- extend Nanoc::Int::Memoization
32
-
33
- def run(value)
34
- value.upcase
35
- end
36
- memoize :run
37
- end
38
-
39
- def test_normal
40
- sample1a = Sample1.new(10)
41
- sample1b = Sample1.new(15)
42
- sample2a = Sample2.new(20)
43
- sample2b = Sample2.new(25)
44
-
45
- 3.times do
46
- assert_equal 10 * 10 + 5, sample1a.run(5)
47
- assert_equal 10 * 15 + 7, sample1b.run(7)
48
- assert_equal 100 * 20 + 5, sample2a.run(5)
49
- assert_equal 100 * 25 + 7, sample2b.run(7)
50
- end
51
- end
52
-
53
- def test_frozen
54
- sample = Sample1.new(10)
55
- sample.freeze
56
- sample.run(5)
57
- end
58
-
59
- def test_weak_inspect
60
- upcaser = Upcaser.new
61
- 10_000.times do |i|
62
- upcaser.run("hello world #{i}")
63
- end
64
-
65
- GC.start
66
- GC.start
67
-
68
- # Should not raise
69
- upcaser.inspect
70
- end
71
- end