openstax_kitchen 4.1.0 → 4.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dacc1aae587e492dc42e693e46195c0ed10ba749f62929e322b0744f2f82e94c
4
- data.tar.gz: 67b4f9691c4431fc172d48381527c1fd75c62dc866ea056ff6666205e73b0e22
3
+ metadata.gz: b80bf07271b311949246bf6a63a9d10fe5f14396175f66a129debcc9e9ef15e3
4
+ data.tar.gz: 7a3a7050d134a0b106d3a80dc18add9209742fca1eea4873f351eb0dd4d05034
5
5
  SHA512:
6
- metadata.gz: 4a788770e4176617a260d8b2ce265303a59490dd1e95727bd38102732ce6459e3600a9eca807e6c80d364b3fb710c2dbce4ec71b66dbec6570b2d61ed8e00e40
7
- data.tar.gz: 6df9ea776c2718b1d7fdc8508210774c29f407391e0802ac6e39a94b98aadfbdb6d47447f0f2a164a66413e122ba7b46e7669fd4af5caeeda26ac0cc591919ff
6
+ metadata.gz: 7fee3b2b4dd92639af2e20520c3f65a31255d944df649e91c5c0e5c8f0a8e1e1f6d19b4477480c5cbc899f7124727c8a40b5a0759985ec94225885baf36b3287
7
+ data.tar.gz: ed62932aa44e597135158cc182daefd2773b89070e1d82750669d4d1f760fa8179f1197c67eddcdec73c1cdc2b1bd7537ca28c8f5603d3a2a7962457d777141f
data/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [4.1.1] - 2021-05-24
10
+
11
+ * Adds low level Nokogiri caching, disabled by default (patch)
12
+ * Cache Selector objects since they don't change (patch)
13
+ * Use more specific selectors when to reduce bake time (patch)
14
+
9
15
  ## [4.1.0] - 2021-05-18
10
16
 
11
17
  * Fixed performance problem with element class detection (patch)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- openstax_kitchen (4.1.0)
4
+ openstax_kitchen (4.1.1)
5
5
  activesupport
6
6
  i18n
7
7
  nokogiri
data/README.md CHANGED
@@ -641,6 +641,22 @@ expect(book_1).to match_normalized_html("some string of HTML here")
641
641
  expect(book_1).to match_html_nodes("some string of HTML here")
642
642
  ```
643
643
 
644
+ ### Profiling
645
+
646
+ If you set the `PROFILE` environment variable to something before you run specs or a recipe, search query profile data will be collected and printed, e.g.
647
+
648
+ ```bash
649
+ %> PROFILE=1 rspec
650
+ ```
651
+
652
+ ### Caching
653
+
654
+ There's a low-level CSS query caching tool that saves repeated queries. In some tests, it saves 15% of query time. It is disabled by default (because we aren't super sure that it is completely safe) but can be turned on with
655
+
656
+ ```ruby
657
+ doc.config.enable_search_cache = true
658
+ ```
659
+
644
660
  ### VSCode
645
661
 
646
662
  1. Visit `vscode:extension/ms-vscode-remote.remote-containers` in a browser
data/codecov.yaml CHANGED
@@ -25,3 +25,4 @@ ignore:
25
25
  - "bin"
26
26
  - "docker"
27
27
  - "books"
28
+ - "lib/kitchen/patches/nokogiri_profiling"
@@ -29,7 +29,7 @@ module Kitchen
29
29
  # @return [BookElement]
30
30
  #
31
31
  def book
32
- BookElement.new(node: nokogiri_document.search('html').first, document: self)
32
+ BookElement.new(node: nokogiri_document.root, document: self)
33
33
  end
34
34
 
35
35
  end
@@ -41,7 +41,7 @@ module Kitchen
41
41
  # @return [Element, nil]
42
42
  #
43
43
  def introduction_page
44
- pages('.introduction').first
44
+ pages('$.introduction').first
45
45
  end
46
46
 
47
47
  # Returns an enumerator for the glossaries
@@ -65,7 +65,7 @@ module Kitchen
65
65
  # @return [ElementEnumerator]
66
66
  #
67
67
  def abstracts
68
- search('[data-type="abstract"]')
68
+ search('div[data-type="abstract"]')
69
69
  end
70
70
 
71
71
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen
4
+ # An enumerator for composite page elements
5
+ #
6
+ class CompositeChapterElementEnumerator < ElementEnumeratorBase
7
+
8
+ # Returns a factory for this enumerator
9
+ #
10
+ # @return [ElementEnumeratorFactory]
11
+ #
12
+ def self.factory
13
+ ElementEnumeratorFactory.new(
14
+ default_css_or_xpath: Selector.named(:composite_chapter),
15
+ sub_element_class: CompositeChapterElement,
16
+ enumerator_class: self
17
+ )
18
+ end
19
+
20
+ end
21
+ end
@@ -17,6 +17,12 @@ module Kitchen
17
17
  #
18
18
  attr_accessor :enable_all_namespaces
19
19
 
20
+ # @!attribute [rw] enable_search_cache
21
+ #
22
+ # @return [Boolean]
23
+ #
24
+ attr_accessor :enable_search_cache
25
+
20
26
  # Creates a new config from a file (not implemented)
21
27
  #
22
28
  def self.new_from_file(_file)
@@ -28,6 +34,7 @@ module Kitchen
28
34
  def initialize(hash: {}, selectors: nil)
29
35
  @selectors = selectors || Kitchen::Selectors::Standard1.new
30
36
  @enable_all_namespaces = hash[:enable_all_namespaces] || true
37
+ @enable_search_cache = hash[:enable_search_cache] || false
31
38
  @hash = hash
32
39
  end
33
40
  end
@@ -19,7 +19,7 @@ module Kitchen::Directions::BakeChapterKeyConcepts
19
19
  key_concepts.each do |key_concept|
20
20
  key_concept.prepend(child: title)
21
21
  key_concept.wrap("<div class='os-section-area'>")
22
- page.search('.os-section-area').first.cut(to: key_concepts_clipboard)
22
+ page.search('div.os-section-area').first.cut(to: key_concepts_clipboard)
23
23
  end
24
24
  end
25
25
 
@@ -4,7 +4,7 @@ module Kitchen
4
4
  module Directions
5
5
  module BakeCompositeChapters
6
6
  def self.v1(book:)
7
- book.search("[data-type='composite-chapter']").each do |chapter|
7
+ book.composite_chapters.each do |chapter|
8
8
  chapter.first("[data-type='document-title']").id =
9
9
  "composite-chapter-#{chapter.count_in(:book)}"
10
10
  end
@@ -4,7 +4,7 @@ module Kitchen
4
4
  module Directions
5
5
  module BakeCompositePages
6
6
  def self.v1(book:)
7
- book.search("[data-type='composite-page']").each do |page|
7
+ book.composite_pages.each do |page|
8
8
  page.id = "composite-page-#{page.count_in(:book)}"
9
9
  end
10
10
  end
@@ -4,7 +4,7 @@ module Kitchen
4
4
  module Directions
5
5
  module BakeEquations
6
6
  def self.v1(book:, number_decorator: :none)
7
- book.chapters.search('[data-type="equation"]:not(.unnumbered)').each do |eq|
7
+ book.chapters.search('div[data-type="equation"]:not(.unnumbered)').each do |eq|
8
8
  chapter = eq.ancestor(:chapter)
9
9
  number = "#{chapter.count_in(:book)}.#{eq.count_in(:chapter)}"
10
10
 
@@ -7,7 +7,7 @@ module Kitchen::Directions::BookAnswerKeyContainer
7
7
  def bake(book:)
8
8
  @metadata = book.metadata.children_to_keep.copy
9
9
  book.body.append(child: render(file: 'eob_solutions_container.xhtml.erb'))
10
- book.body.first('.os-eob.os-solutions-container')
10
+ book.body.first('div.os-eob.os-solutions-container')
11
11
  end
12
12
  end
13
13
  end
@@ -7,7 +7,7 @@ module Kitchen::Directions::ChapterReviewContainer
7
7
  def bake(chapter:, metadata_source:)
8
8
  @metadata = metadata_source.children_to_keep.copy
9
9
  chapter.append(child: render(file: 'chapter_review.xhtml.erb'))
10
- chapter.first('.os-eoc.os-chapter-review-container')
10
+ chapter.first('div.os-eoc.os-chapter-review-container')
11
11
  end
12
12
  end
13
13
  end
@@ -18,7 +18,7 @@ module Kitchen::Directions::MoveSolutionsToAnswerKey
18
18
  def bake_section(chapter:, append_to:, klass:)
19
19
  section_solutions_set = []
20
20
  chapter.search(".#{klass}").each do |section|
21
- section.search('[data-type="solution"]').each do |solution|
21
+ section.search('div[data-type="solution"]').each do |solution|
22
22
  section_solutions_set.push(solution.cut)
23
23
  end
24
24
  end
@@ -31,7 +31,7 @@ module Kitchen::Directions::MoveSolutionsToAnswerKey
31
31
 
32
32
  def bake_from_notes(chapter:, append_to:, klass:)
33
33
  solutions = []
34
- chapter.notes(".#{klass}").each do |note|
34
+ chapter.notes("$.#{klass}").each do |note|
35
35
  note.exercises.each do |exercise|
36
36
  solution = exercise.solution
37
37
  solutions.push(solution.cut) if solution
@@ -47,16 +47,8 @@ module Kitchen
47
47
  @next_paste_count_for_id = {}
48
48
  @id_copy_suffix = '_copy_'
49
49
 
50
- # Nokogiri by default only recognizes the namespaces on the root node. Collect all
51
- # namespaces and add them manually.
52
- return unless @config.enable_all_namespaces && raw.present?
53
-
54
- raw.collect_namespaces.each do |namespace, url|
55
- prefix, name = namespace.split(':')
56
- next unless prefix == 'xmlns' && name.present?
57
-
58
- raw.root.add_namespace_definition(name, url)
59
- end
50
+ # Nokogiri by default only recognizes the namespaces on the root node. Add all others.
51
+ raw&.add_all_namespaces! if @config.enable_all_namespaces
60
52
  end
61
53
 
62
54
  # Returns an enumerator that iterates over all children of this document
@@ -132,6 +132,7 @@ module Kitchen
132
132
  @ancestors = HashWithIndifferentAccess.new
133
133
  @search_query_matches_that_have_been_counted = {}
134
134
  @is_a_clone = false
135
+ @search_cache = {}
135
136
  end
136
137
 
137
138
  # Returns ElementBase descendent type or nil if none found
@@ -345,7 +346,7 @@ module Kitchen
345
346
  # search results if the method or callable returns false
346
347
  # @return [ElementEnumerator]
347
348
  #
348
- def search(*selector_or_xpath_args, only: nil, except: nil)
349
+ def search(*selector_or_xpath_args, only: nil, except: nil, reload: false)
349
350
  block_error_if(block_given?)
350
351
 
351
352
  ElementEnumerator.factory.build_within(
@@ -354,19 +355,29 @@ module Kitchen
354
355
  css_or_xpath: selector_or_xpath_args,
355
356
  only: only,
356
357
  except: except
357
- )
358
+ ),
359
+ reload: reload
358
360
  )
359
361
  end
360
362
 
363
+ def raw_search(*selector_or_xpath_args, reload: false)
364
+ key = selector_or_xpath_args
365
+ @search_cache[key] = nil if reload || !config.enable_search_cache
366
+ # cache nil search results with a fake -1 value
367
+ @search_cache[key] ||= raw.search(*selector_or_xpath_args) || -1
368
+ @search_cache[key] == -1 ? nil : @search_cache[key]
369
+ end
370
+
361
371
  # Yields and returns the first child element that matches the provided
362
372
  # selector or XPath arguments.
363
373
  #
364
374
  # @param selector_or_xpath_args [Array<String>] CSS selectors or XPath arguments
375
+ # @param reload [Boolean] ignores cache if true
365
376
  # @yieldparam [Element] the matched XML element
366
377
  # @return [Element, nil] the matched XML element or nil if no match found
367
378
  #
368
- def first(*selector_or_xpath_args)
369
- search(*selector_or_xpath_args).first.tap do |element|
379
+ def first(*selector_or_xpath_args, reload: false)
380
+ search(*selector_or_xpath_args, reload: reload).first.tap do |element|
370
381
  yield(element) if block_given?
371
382
  end
372
383
  end
@@ -375,12 +386,13 @@ module Kitchen
375
386
  # selector or XPath arguments.
376
387
  #
377
388
  # @param selector_or_xpath_args [Array<String>] CSS selectors or XPath arguments
389
+ # @param reload [Boolean] ignores cache if true
378
390
  # @yieldparam [Element] the matched XML element
379
391
  # @raise [ElementNotFoundError] if no matching element is found
380
392
  # @return [Element] the matched XML element
381
393
  #
382
- def first!(*selector_or_xpath_args)
383
- search(*selector_or_xpath_args).first!.tap do |element|
394
+ def first!(*selector_or_xpath_args, reload: false)
395
+ search(*selector_or_xpath_args, reload: reload).first!.tap do |element|
384
396
  yield(element) if block_given?
385
397
  end
386
398
  end
@@ -694,7 +706,7 @@ module Kitchen
694
706
  # Returns a pages enumerator
695
707
  def_delegators :as_enumerator, :pages, :chapters, :terms, :figures, :notes, :tables, :examples,
696
708
  :metadatas, :non_introduction_pages, :units, :titles, :exercises, :references,
697
- :composite_pages
709
+ :composite_pages, :composite_chapters
698
710
 
699
711
  # Returns this element as an enumerator (over only one element, itself)
700
712
  #
@@ -84,7 +84,30 @@ module Kitchen
84
84
  #
85
85
  def composite_pages(css_or_xpath=nil, only: nil, except: nil)
86
86
  block_error_if(block_given?)
87
- chain_to(CompositePageElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
87
+ chain_to(CompositePageElementEnumerator,
88
+ css_or_xpath: css_or_xpath,
89
+ only: only,
90
+ except: except)
91
+ end
92
+
93
+ # Returns an enumerator that iterates through composite chapters within the scope of this enumerator
94
+ #
95
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
96
+ # a "$" in this argument will be replaced with the default selector for the element being
97
+ # iterated over.
98
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
99
+ # lambda or proc that accepts an element; elements will only be included in the
100
+ # search results if the method or callable returns true
101
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
102
+ # lambda or proc that accepts an element; elements will not be included in the
103
+ # search results if the method or callable returns false
104
+ #
105
+ def composite_chapters(css_or_xpath=nil, only: nil, except: nil)
106
+ block_error_if(block_given?)
107
+ chain_to(CompositeChapterElementEnumerator,
108
+ css_or_xpath: css_or_xpath,
109
+ only: only,
110
+ except: except)
88
111
  end
89
112
 
90
113
  # Returns an enumerator that iterates through pages that arent the introduction page within the scope of this enumerator
@@ -39,12 +39,22 @@ module Kitchen
39
39
  # @return [ElementEnumeratorBase] actually returns the concrete enumerator class
40
40
  # given to the factory in its constructor.
41
41
  #
42
- def build_within(enumerator_or_element, search_query: SearchQuery.new)
42
+ def build_within(enumerator_or_element, search_query: SearchQuery.new, reload: false)
43
43
  case enumerator_or_element
44
44
  when ElementBase
45
- build_within_element(enumerator_or_element, search_query: search_query)
45
+ build_within_element(enumerator_or_element,
46
+ search_query: search_query,
47
+ reload: reload)
46
48
  when ElementEnumeratorBase
47
- build_within_other_enumerator(enumerator_or_element, search_query: search_query)
49
+ if enumerator_class != ElementEnumerator && !search_query.expects_substitution?
50
+ raise "Query #{search_query} is missing the substitution character ('$') but " \
51
+ "is run with an enumerator #{enumerator_class.name} that has its own " \
52
+ "selectors for substitution."
53
+ end
54
+
55
+ build_within_other_enumerator(enumerator_or_element,
56
+ search_query: search_query,
57
+ reload: reload)
48
58
  end
49
59
  end
50
60
 
@@ -64,7 +74,7 @@ module Kitchen
64
74
 
65
75
  protected
66
76
 
67
- def build_within_element(element, search_query:)
77
+ def build_within_element(element, search_query:, reload:)
68
78
  search_query.apply_default_css_or_xpath_and_normalize(default_css_or_xpath,
69
79
  config: element.config)
70
80
 
@@ -77,7 +87,7 @@ module Kitchen
77
87
  # below, the counts are correct.
78
88
  element.uncount(search_query)
79
89
 
80
- element.raw.search(*search_query.css_or_xpath).each do |sub_node|
90
+ element.raw_search(*search_query.css_or_xpath, reload: reload).each do |sub_node|
81
91
  sub_element = ElementFactory.build_from_node(
82
92
  node: sub_node,
83
93
  document: element.document,
@@ -105,13 +115,15 @@ module Kitchen
105
115
  end
106
116
  end
107
117
 
108
- def build_within_other_enumerator(other_enumerator, search_query:)
118
+ def build_within_other_enumerator(other_enumerator, search_query:, reload:)
109
119
  # Return a new enumerator instance that internally iterates over `other_enumerator`
110
120
  # running a new enumerator for each element returned by that other enumerator.
111
121
  enumerator_class.new(search_query: search_query,
112
122
  upstream_enumerator: other_enumerator) do |block|
113
123
  other_enumerator.each do |element|
114
- build_within_element(element, search_query: search_query).each do |sub_element|
124
+ build_within_element(element,
125
+ search_query: search_query,
126
+ reload: reload).each do |sub_element|
115
127
  block.yield(sub_element)
116
128
  end
117
129
  end
@@ -28,7 +28,7 @@ module Kitchen
28
28
  # @return ElementEnumerator
29
29
  #
30
30
  def problem
31
- first("[data-type='problem']")
31
+ first("div[data-type='problem']")
32
32
  end
33
33
 
34
34
  # Returns the enumerator for solution.
@@ -36,7 +36,7 @@ module Kitchen
36
36
  # @return ElementEnumerator
37
37
  #
38
38
  def solution
39
- first("[data-type='solution']")
39
+ first("div[data-type='solution']")
40
40
  end
41
41
  end
42
42
  end
data/lib/kitchen/oven.rb CHANGED
@@ -39,6 +39,8 @@ module Kitchen
39
39
  end
40
40
  profile.written!
41
41
 
42
+ Nokogiri::XML.print_profile_data if ENV['PROFILE'] && !ENV['TESTING']
43
+
42
44
  profile
43
45
  end
44
46
 
@@ -29,9 +29,12 @@ module Kitchen
29
29
  # @raise [ElementNotFoundError] if no matching element is found
30
30
  # @return [Element]
31
31
  #
32
- def title
32
+ def title(reload: false)
33
33
  # The selector for intro titles changes during the baking process
34
- first!(is_introduction? ? selectors.title_in_introduction_page : selectors.title_in_page)
34
+ @title ||= begin
35
+ selector = is_introduction? ? selectors.title_in_introduction_page : selectors.title_in_page
36
+ first!(selector, reload: reload)
37
+ end
35
38
  end
36
39
 
37
40
  # Returns the title's text regardless of whether the title has been baked
@@ -31,6 +31,21 @@ module Nokogiri
31
31
  end
32
32
  end
33
33
  end
34
+
35
+ def add_all_namespaces!
36
+ # Nokogiri by default only recognizes the namespaces on the root node. Collect all
37
+ # namespaces and add them manually.
38
+ return if @all_namespaces_added
39
+
40
+ collect_namespaces.each do |namespace, url|
41
+ prefix, name = namespace.split(':')
42
+ next unless prefix == 'xmlns' && name.present?
43
+
44
+ root.add_namespace_definition(name, url)
45
+ end
46
+
47
+ @all_namespaces_added = true
48
+ end
34
49
  end
35
50
 
36
51
  # Monkey patches for Nokogiri::XML::Node
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Style/Documentation
4
+
5
+ # Make debug output more useful (dumping entire document out is not useful)
6
+ module Nokogiri
7
+ module XML
8
+ # rubocop:disable Style/MutableConstant
9
+ PROFILE_DATA = {}
10
+ # rubocop:enable Style/MutableConstant
11
+
12
+ if ENV['PROFILE']
13
+ ENV['VERBOSE_PROFILE'] = 1 if ENV['PROFILE'].to_s.downcase == 'verbose'
14
+
15
+ # Patches inside Nokogiri to count, time, and print searches. At end of baking
16
+ # you can `puts Nokogiri::XML::PROFILE_DATA` to see the totals. The counts
17
+ # hash is defined outside of the if block so that code that prints it doesn't
18
+ # explode if run without the env var. The `print_profile_data` method is also
19
+ # provided for a nice printout
20
+
21
+ def self.print_profile_data
22
+ total_duration = 0
23
+
24
+ sorted_profile_data = PROFILE_DATA.sort_by { |_, data| data[:time] / data[:count] }.reverse
25
+
26
+ puts "\nSearch Profile Data"
27
+ puts '-----------------------------------------------------------------'
28
+ puts "#{'Total Time (ms)'.ljust(17)}#{'Avg Time (ms)'.ljust(15)}#{'Count'.ljust(7)}Query"
29
+ puts '-----------------------------------------------------------------'
30
+
31
+ sorted_profile_data.each do |search_path, data|
32
+ total_time = format('%0.4f', (data[:time] * 1000))
33
+ avg_time = format('%0.4f', ((data[:time] / data[:count]) * 1000))
34
+ puts total_time.ljust(17) + avg_time.to_s.ljust(15) + data[:count].to_s.ljust(7) + \
35
+ search_path
36
+ total_duration += data[:time] * 1000
37
+ end
38
+
39
+ puts "\nTotal time across all searches (ms): #{total_duration}"
40
+ end
41
+
42
+ class XPathContext
43
+ alias_method :original_evaluate, :evaluate
44
+ def evaluate(search_path, handler=nil)
45
+ puts search_path if ENV['VERBOSE_PROFILE']
46
+
47
+ PROFILE_DATA[search_path] ||= Hash.new(0)
48
+ PROFILE_DATA[search_path][:count] += 1
49
+
50
+ start_time = Time.now
51
+ original_evaluate(search_path, handler).tap do
52
+ PROFILE_DATA[search_path][:time] += Time.now - start_time
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ # rubocop:enable Style/Documentation
@@ -82,6 +82,12 @@ module Kitchen
82
82
  as_type
83
83
  end
84
84
 
85
+ # Returns true if the query has the substitution character ('$')
86
+ #
87
+ def expects_substitution?
88
+ css_or_xpath.nil? || [css_or_xpath].flatten.all? { |item| item.include?('$') }
89
+ end
90
+
85
91
  protected
86
92
 
87
93
  def condition_passes?(method_or_callable, element, success_outcome)
@@ -7,7 +7,8 @@ module Kitchen
7
7
  attr_reader :name
8
8
 
9
9
  def self.named(name)
10
- new(name) { |config| config.selectors.send(name) }
10
+ @instances ||= {}
11
+ @instances[name] ||= new(name) { |config| config.selectors.send(name) }
11
12
  end
12
13
 
13
14
  def initialize(name, &block)
@@ -3,5 +3,5 @@
3
3
  # A library for modifying the structure of OpenStax book XML.
4
4
  #
5
5
  module Kitchen
6
- VERSION = '4.1.0'
6
+ VERSION = '4.1.1'
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openstax_kitchen
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.0
4
+ version: 4.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - JP Slavinsky
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-20 00:00:00.000000000 Z
11
+ date: 2021-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -220,6 +220,7 @@ files:
220
220
  - lib/kitchen/chapter_element_enumerator.rb
221
221
  - lib/kitchen/clipboard.rb
222
222
  - lib/kitchen/composite_chapter_element.rb
223
+ - lib/kitchen/composite_chapter_element_enumerator.rb
223
224
  - lib/kitchen/composite_page_element.rb
224
225
  - lib/kitchen/composite_page_element_enumerator.rb
225
226
  - lib/kitchen/config.rb
@@ -320,6 +321,7 @@ files:
320
321
  - lib/kitchen/page_element_enumerator.rb
321
322
  - lib/kitchen/pantry.rb
322
323
  - lib/kitchen/patches/nokogiri.rb
324
+ - lib/kitchen/patches/nokogiri_profiling.rb
323
325
  - lib/kitchen/patches/renderable.rb
324
326
  - lib/kitchen/patches/string.rb
325
327
  - lib/kitchen/recipe.rb