openstax_kitchen 2.0.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/devcontainer.json +37 -17
  3. data/.github/config.yml +14 -0
  4. data/.github/workflows/tests.yml +5 -15
  5. data/.gitignore +1 -1
  6. data/.inch.yml +6 -0
  7. data/.rubocop.yml +65 -0
  8. data/CHANGELOG.md +16 -0
  9. data/Gemfile +5 -3
  10. data/Gemfile.lock +54 -5
  11. data/README.md +58 -11
  12. data/Rakefile +5 -3
  13. data/bin/console +4 -3
  14. data/docker/Dockerfile +36 -0
  15. data/docker/Dockerfile.ci +10 -0
  16. data/docker/bash +5 -1
  17. data/docker/build +10 -0
  18. data/docker/ci +16 -0
  19. data/docker/run +9 -0
  20. data/docker/tag_and_push_latest +17 -0
  21. data/lefthook.yml +6 -0
  22. data/lib/kitchen/ancestor.rb +38 -1
  23. data/lib/kitchen/book_document.rb +20 -2
  24. data/lib/kitchen/book_element.rb +24 -3
  25. data/lib/kitchen/book_element_enumerator.rb +4 -0
  26. data/lib/kitchen/book_recipe.rb +15 -1
  27. data/lib/kitchen/chapter_element.rb +43 -3
  28. data/lib/kitchen/chapter_element_enumerator.rb +9 -1
  29. data/lib/kitchen/clipboard.rb +35 -4
  30. data/lib/kitchen/composite_chapter_element.rb +20 -1
  31. data/lib/kitchen/composite_page_element.rb +25 -2
  32. data/lib/kitchen/composite_page_element_enumerator.rb +8 -0
  33. data/lib/kitchen/config.rb +14 -7
  34. data/lib/kitchen/counter.rb +9 -2
  35. data/lib/kitchen/debug/print_recipe_error.rb +53 -35
  36. data/lib/kitchen/directions/.rubocop.yml +22 -0
  37. data/lib/kitchen/directions/bake_appendix.rb +4 -4
  38. data/lib/kitchen/directions/bake_chapter_glossary.rb +10 -7
  39. data/lib/kitchen/directions/bake_chapter_introductions.rb +6 -6
  40. data/lib/kitchen/directions/bake_chapter_key_equations.rb +9 -6
  41. data/lib/kitchen/directions/bake_chapter_summary.rb +16 -13
  42. data/lib/kitchen/directions/bake_chapter_title/main.rb +11 -0
  43. data/lib/kitchen/directions/bake_chapter_title/v1.rb +24 -0
  44. data/lib/kitchen/directions/bake_composite_pages.rb +2 -2
  45. data/lib/kitchen/directions/bake_example.rb +6 -4
  46. data/lib/kitchen/directions/bake_exercises/main.rb +11 -0
  47. data/lib/kitchen/directions/bake_exercises/v1.rb +166 -0
  48. data/lib/kitchen/directions/bake_figure.rb +8 -5
  49. data/lib/kitchen/directions/bake_footnotes/main.rb +2 -2
  50. data/lib/kitchen/directions/bake_footnotes/v1.rb +4 -4
  51. data/lib/kitchen/directions/bake_index/main.rb +2 -2
  52. data/lib/kitchen/directions/bake_index/v1.rb +22 -15
  53. data/lib/kitchen/directions/bake_link_placeholders.rb +24 -0
  54. data/lib/kitchen/directions/bake_math_in_paragraph.rb +5 -3
  55. data/lib/kitchen/directions/bake_notes.rb +8 -8
  56. data/lib/kitchen/directions/bake_numbered_table/main.rb +2 -2
  57. data/lib/kitchen/directions/bake_numbered_table/v1.rb +21 -16
  58. data/lib/kitchen/directions/bake_page_abstracts.rb +14 -0
  59. data/lib/kitchen/directions/bake_preface/main.rb +11 -0
  60. data/lib/kitchen/directions/bake_preface/v1.rb +18 -0
  61. data/lib/kitchen/directions/bake_stepwise.rb +7 -7
  62. data/lib/kitchen/directions/bake_suggested_reading.rb +26 -0
  63. data/lib/kitchen/directions/bake_toc.rb +41 -22
  64. data/lib/kitchen/directions/bake_unit_title/main.rb +11 -0
  65. data/lib/kitchen/directions/bake_unit_title/v1.rb +23 -0
  66. data/lib/kitchen/directions/bake_unnumbered_tables.rb +7 -5
  67. data/lib/kitchen/directions/move_title_text_into_span.rb +2 -2
  68. data/lib/kitchen/document.rb +72 -13
  69. data/lib/kitchen/element.rb +11 -0
  70. data/lib/kitchen/element_base.rb +276 -56
  71. data/lib/kitchen/element_enumerator.rb +8 -0
  72. data/lib/kitchen/element_enumerator_base.rb +210 -28
  73. data/lib/kitchen/element_enumerator_factory.rb +59 -52
  74. data/lib/kitchen/element_factory.rb +27 -12
  75. data/lib/kitchen/errors.rb +5 -0
  76. data/lib/kitchen/example_element.rb +19 -1
  77. data/lib/kitchen/example_element_enumerator.rb +9 -1
  78. data/lib/kitchen/figure_element.rb +36 -2
  79. data/lib/kitchen/figure_element_enumerator.rb +9 -1
  80. data/lib/kitchen/metadata_element.rb +28 -0
  81. data/lib/kitchen/metadata_element_enumerator.rb +21 -0
  82. data/lib/kitchen/mixins/block_error_if.rb +24 -4
  83. data/lib/kitchen/note_element.rb +37 -7
  84. data/lib/kitchen/note_element_enumerator.rb +9 -1
  85. data/lib/kitchen/oven.rb +66 -15
  86. data/lib/kitchen/page_element.rb +62 -13
  87. data/lib/kitchen/page_element_enumerator.rb +9 -1
  88. data/lib/kitchen/pantry.rb +28 -1
  89. data/lib/kitchen/patches/nokogiri.rb +19 -2
  90. data/lib/kitchen/patches/renderable.rb +9 -3
  91. data/lib/kitchen/patches/string.rb +8 -0
  92. data/lib/kitchen/recipe.rb +38 -34
  93. data/lib/kitchen/search_history.rb +43 -4
  94. data/lib/kitchen/search_query.rb +84 -0
  95. data/lib/kitchen/selectors/base.rb +26 -0
  96. data/lib/kitchen/selectors/standard_1.rb +8 -0
  97. data/lib/kitchen/table_element.rb +54 -3
  98. data/lib/kitchen/table_element_enumerator.rb +9 -1
  99. data/lib/kitchen/term_element.rb +15 -1
  100. data/lib/kitchen/term_element_enumerator.rb +9 -1
  101. data/lib/kitchen/transliterations.rb +7 -5
  102. data/lib/kitchen/type_casting_element_enumerator.rb +17 -1
  103. data/lib/kitchen/unit_element.rb +39 -0
  104. data/lib/kitchen/unit_element_enumerator.rb +20 -0
  105. data/lib/kitchen/utils.rb +10 -13
  106. data/lib/kitchen/version.rb +5 -1
  107. data/lib/locales/en.yml +6 -0
  108. data/lib/openstax_kitchen.rb +43 -42
  109. data/openstax_kitchen.gemspec +26 -20
  110. data/tutorials/00/solution1.rb +9 -0
  111. data/tutorials/00/solution2.rb +8 -0
  112. data/tutorials/01/solution1.rb +18 -0
  113. data/tutorials/01/solution2.rb +26 -0
  114. data/tutorials/02/solution1.rb +31 -0
  115. data/tutorials/03/{solution_1.rb → solution1.rb} +6 -4
  116. data/tutorials/03/solution2.rb +18 -0
  117. data/tutorials/04/{solution_1.rb → solution1.rb} +4 -2
  118. data/tutorials/04/{solution_2.rb → solution2.rb} +6 -4
  119. data/tutorials/05/solution1.rb +11 -0
  120. data/tutorials/check_it +16 -15
  121. data/tutorials/setup_my_recipes +7 -6
  122. metadata +101 -22
  123. data/Dockerfile +0 -19
  124. data/docker-compose.yml +0 -12
  125. data/docker/entrypoint +0 -9
  126. data/lib/kitchen/directions/bake_exercises.rb +0 -164
  127. data/tutorials/00/solution_1.rb +0 -7
  128. data/tutorials/00/solution_2.rb +0 -6
  129. data/tutorials/01/solution_1.rb +0 -16
  130. data/tutorials/01/solution_2.rb +0 -24
  131. data/tutorials/02/solution_1.rb +0 -29
  132. data/tutorials/03/solution_2.rb +0 -15
  133. data/tutorials/05/solution_1.rb +0 -9
@@ -1,6 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kitchen
4
+ # An enumerator for any old non-specific element
5
+ #
2
6
  class ElementEnumerator < ElementEnumeratorBase
3
7
 
8
+ # Returns a factory for this enumerator
9
+ #
10
+ # @return [ElementEnumeratorFactory]
11
+ #
4
12
  def self.factory
5
13
  ElementEnumeratorFactory.new(
6
14
  sub_element_class: Element,
@@ -1,56 +1,227 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kitchen
4
+ # Base class for all element enumerators
5
+ #
2
6
  class ElementEnumeratorBase < Enumerator
7
+ include Mixins::BlockErrorIf
3
8
 
4
- def initialize(size=nil, css_or_xpath: nil, upstream_enumerator: nil)
5
- @css_or_xpath = css_or_xpath
9
+ # Return the selectors or other search strings that this enumerator uses to
10
+ # search through the document
11
+ #
12
+ # @return [String]
13
+ #
14
+ attr_reader :search_query
15
+
16
+ # Creates a new instance
17
+ #
18
+ # @param size [Integer, Proc] How to calculate the size lazily, either a value
19
+ # or a callable object
20
+ # @param search_query [String] the selectors or other search strings that this
21
+ # enumerator uses to search through the document
22
+ # @param upstream_enumerator [ElementEnumeratorBase] the enumerator to which this
23
+ # enumerator is chained, used to access the upstream search history
24
+ #
25
+ def initialize(size=nil, search_query: nil, upstream_enumerator: nil)
26
+ @search_query = search_query
6
27
  @upstream_enumerator = upstream_enumerator
7
28
  super(size)
8
29
  end
9
30
 
31
+ # Return the search history based on this enumerator and any upstream enumerators
32
+ #
33
+ # @return [SearchHistory]
34
+ #
10
35
  def search_history
11
- (@upstream_enumerator&.search_history || SearchHistory.empty).add(@css_or_xpath)
36
+ (@upstream_enumerator&.search_history || SearchHistory.empty).add(@search_query)
37
+ end
38
+
39
+ # Returns an enumerator that iterates through terms within the scope of this enumerator
40
+ #
41
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
42
+ # a "$" in this argument will be replaced with the default selector for the element being
43
+ # iterated over.
44
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
45
+ # lambda or proc that accepts an element; elements will only be included in the
46
+ # search results if the method or callable returns true
47
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
48
+ # lambda or proc that accepts an element; elements will not be included in the
49
+ # search results if the method or callable returns false
50
+ #
51
+ def terms(css_or_xpath=nil, only: nil, except: nil)
52
+ block_error_if(block_given?)
53
+ chain_to(TermElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
12
54
  end
13
55
 
14
- def terms(css_or_xpath=nil, &block)
15
- chain_to(TermElementEnumerator, css_or_xpath: css_or_xpath, &block)
56
+ # Returns an enumerator that iterates through pages within the scope of this enumerator
57
+ #
58
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
59
+ # a "$" in this argument will be replaced with the default selector for the element being
60
+ # iterated over.
61
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
62
+ # lambda or proc that accepts an element; elements will only be included in the
63
+ # search results if the method or callable returns true
64
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
65
+ # lambda or proc that accepts an element; elements will not be included in the
66
+ # search results if the method or callable returns false
67
+ #
68
+ def pages(css_or_xpath=nil, only: nil, except: nil)
69
+ block_error_if(block_given?)
70
+ chain_to(PageElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
16
71
  end
17
72
 
18
- def pages(css_or_xpath=nil, &block)
19
- chain_to(PageElementEnumerator, css_or_xpath: css_or_xpath, &block)
73
+ # Returns an enumerator that iterates through chapters within the scope of this enumerator
74
+ #
75
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
76
+ # a "$" in this argument will be replaced with the default selector for the element being
77
+ # iterated over.
78
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
79
+ # lambda or proc that accepts an element; elements will only be included in the
80
+ # search results if the method or callable returns true
81
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
82
+ # lambda or proc that accepts an element; elements will not be included in the
83
+ # search results if the method or callable returns false
84
+ #
85
+ def chapters(css_or_xpath=nil, only: nil, except: nil)
86
+ block_error_if(block_given?)
87
+ chain_to(ChapterElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
20
88
  end
21
89
 
22
- def chapters(css_or_xpath=nil, &block)
23
- chain_to(ChapterElementEnumerator, css_or_xpath: css_or_xpath, &block)
90
+ # Returns an enumerator that iterates through units within the scope of this enumerator
91
+ #
92
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
93
+ # a "$" in this argument will be replaced with the default selector for the element being
94
+ # iterated over.
95
+ #
96
+ def units(css_or_xpath=nil, only: nil, except: nil)
97
+ block_error_if(block_given?)
98
+ chain_to(UnitElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
24
99
  end
25
100
 
26
- # use block_error_if
27
- def figures(css_or_xpath=nil, &block)
28
- chain_to(FigureElementEnumerator, css_or_xpath: css_or_xpath, &block)
101
+ # Returns an enumerator that iterates through figures within the scope of this enumerator
102
+ #
103
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
104
+ # a "$" in this argument will be replaced with the default selector for the element being
105
+ # iterated over.
106
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
107
+ # lambda or proc that accepts an element; elements will only be included in the
108
+ # search results if the method or callable returns true
109
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
110
+ # lambda or proc that accepts an element; elements will not be included in the
111
+ # search results if the method or callable returns false
112
+ #
113
+ def figures(css_or_xpath=nil, only: nil, except: nil)
114
+ block_error_if(block_given?)
115
+ chain_to(FigureElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
29
116
  end
30
117
 
31
- def notes(css_or_xpath=nil, &block)
32
- chain_to(NoteElementEnumerator, css_or_xpath: css_or_xpath, &block)
118
+ # Returns an enumerator that iterates through notes within the scope of this enumerator
119
+ #
120
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
121
+ # a "$" in this argument will be replaced with the default selector for the element being
122
+ # iterated over.
123
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
124
+ # lambda or proc that accepts an element; elements will only be included in the
125
+ # search results if the method or callable returns true
126
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
127
+ # lambda or proc that accepts an element; elements will not be included in the
128
+ # search results if the method or callable returns false
129
+ #
130
+ def notes(css_or_xpath=nil, only: nil, except: nil)
131
+ block_error_if(block_given?)
132
+ chain_to(NoteElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
33
133
  end
34
134
 
35
- def tables(css_or_xpath=nil, &block)
36
- chain_to(TableElementEnumerator, css_or_xpath: css_or_xpath, &block)
135
+ # Returns an enumerator that iterates through tables within the scope of this enumerator
136
+ #
137
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
138
+ # a "$" in this argument will be replaced with the default selector for the element being
139
+ # iterated over.
140
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
141
+ # lambda or proc that accepts an element; elements will only be included in the
142
+ # search results if the method or callable returns true
143
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
144
+ # lambda or proc that accepts an element; elements will not be included in the
145
+ # search results if the method or callable returns false
146
+ #
147
+ def tables(css_or_xpath=nil, only: nil, except: nil)
148
+ block_error_if(block_given?)
149
+ chain_to(TableElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
37
150
  end
38
151
 
39
- def examples(css_or_xpath=nil, &block)
40
- chain_to(ExampleElementEnumerator, css_or_xpath: css_or_xpath, &block)
152
+ # Returns an enumerator that iterates through examples within the scope of this enumerator
153
+ #
154
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
155
+ # a "$" in this argument will be replaced with the default selector for the element being
156
+ # iterated over.
157
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
158
+ # lambda or proc that accepts an element; elements will only be included in the
159
+ # search results if the method or callable returns true
160
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
161
+ # lambda or proc that accepts an element; elements will not be included in the
162
+ # search results if the method or callable returns false
163
+ #
164
+ def examples(css_or_xpath=nil, only: nil, except: nil)
165
+ block_error_if(block_given?)
166
+ chain_to(ExampleElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
41
167
  end
42
168
 
43
- def search(css_or_xpath=nil, &block)
44
- chain_to(ElementEnumerator, css_or_xpath: css_or_xpath, &block)
169
+ # Returns an enumerator that iterates through metadata within the scope of this enumerator
170
+ #
171
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
172
+ # a "$" in this argument will be replaced with the default selector for the element being
173
+ # iterated over.
174
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
175
+ # lambda or proc that accepts an element; elements will only be included in the
176
+ # search results if the method or callable returns true
177
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
178
+ # lambda or proc that accepts an element; elements will not be included in the
179
+ # search results if the method or callable returns false
180
+ #
181
+ def metadatas(css_or_xpath=nil, only: nil, except: nil)
182
+ block_error_if(block_given?)
183
+ chain_to(MetadataElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
45
184
  end
46
185
 
47
- def chain_to(enumerator_class, css_or_xpath: nil, &block)
48
- raise(RecipeError, "Did you forget a `.each` call on this enumerator?") if block_given?
186
+ # Returns an enumerator that iterates within the scope of this enumerator
187
+ #
188
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over
189
+ #
190
+ def search(css_or_xpath=nil, only: nil, except: nil)
191
+ block_error_if(block_given?)
192
+ chain_to(ElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
193
+ end
49
194
 
50
- enumerator_class.factory.build_within(self, css_or_xpath: css_or_xpath)
195
+ # Returns an enumerator that iterates through elements within the scope of this enumerator
196
+ #
197
+ # @param enumerator_class [ElementEnumeratorBase] the enumerator to use for the iteration
198
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over
199
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
200
+ # lambda or proc that accepts an element; elements will only be included in the
201
+ # search results if the method or callable returns true
202
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
203
+ # lambda or proc that accepts an element; elements will not be included in the
204
+ # search results if the method or callable returns false
205
+ #
206
+ def chain_to(enumerator_class, css_or_xpath: nil, only: nil, except: nil)
207
+ block_error_if(block_given?)
208
+ enumerator_class.factory.build_within(
209
+ self,
210
+ search_query: SearchQuery.new(
211
+ css_or_xpath: css_or_xpath,
212
+ only: only,
213
+ except: except
214
+ )
215
+ )
51
216
  end
52
217
 
53
- def first!(missing_message: "Could not return a first result")
218
+ # Returns the first element in this enumerator
219
+ #
220
+ # @param missing_message [String] the message to raise if a first element isn't available
221
+ # @raise [RecipeError] if a first element isn't available
222
+ # @return [Element]
223
+ #
224
+ def first!(missing_message: 'Could not return a first result')
54
225
  first || raise(RecipeError, "#{missing_message} matching #{search_history.latest} " \
55
226
  "inside [#{search_history.upstream}]")
56
227
  end
@@ -64,7 +235,7 @@ module Kitchen
64
235
  #
65
236
  def cut(to: nil)
66
237
  to ||= Clipboard.new
67
- self.each do |element|
238
+ each do |element|
68
239
  element.cut(to: to)
69
240
  end
70
241
  to
@@ -79,22 +250,33 @@ module Kitchen
79
250
  #
80
251
  def copy(to: nil)
81
252
  to ||= Clipboard.new
82
- self.each do |element|
253
+ each do |element|
83
254
  element.copy(to: to)
84
255
  end
85
256
  to
86
257
  end
87
258
 
259
+ # Removes all matching elements from the document
260
+ #
88
261
  def trash
89
- self.each(&:trash)
262
+ each(&:trash)
90
263
  end
91
264
 
265
+ # Returns the element at the provided index
266
+ #
267
+ # @param index [Integer]
268
+ # @return [Element]
269
+ #
92
270
  def [](index)
93
271
  to_a[index]
94
272
  end
95
273
 
274
+ # Returns a concatenation of +to_s+ for all elements in the enumerator
275
+ #
276
+ # @return [String]
277
+ #
96
278
  def to_s
97
- self.map(&:to_s).join("")
279
+ map(&:to_s).join('')
98
280
  end
99
281
 
100
282
  end
@@ -1,4 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kitchen
4
+ # Builds specific subclasses of ElementEnumeratorBase
5
+ #
2
6
  class ElementEnumeratorFactory
3
7
 
4
8
  attr_reader :default_css_or_xpath
@@ -6,35 +10,52 @@ module Kitchen
6
10
  attr_reader :sub_element_class
7
11
  attr_reader :detect_sub_element_class
8
12
 
9
- def initialize(default_css_or_xpath: nil, sub_element_class: nil,
10
- enumerator_class:, detect_sub_element_class: false)
13
+ # Creates a new instance
14
+ #
15
+ # @param default_css_or_xpath [String] The selectors to substitute for the "$" character
16
+ # when this factory is used to build an enumerator.
17
+ # @param sub_element_class [ElementBase] The element class to use for what the enumerator finds.
18
+ # @param enumerator_class [ElementEnumeratorBase] The enumerator class to return
19
+ # @param detect_sub_element_class [Boolean] If true, infers the sub_element_class from the node
20
+ #
21
+ def initialize(enumerator_class:, default_css_or_xpath: nil, sub_element_class: nil,
22
+ detect_sub_element_class: false)
11
23
  @default_css_or_xpath = default_css_or_xpath
12
24
  @sub_element_class = sub_element_class
13
25
  @enumerator_class = enumerator_class
14
26
  @detect_sub_element_class = detect_sub_element_class
15
27
  end
16
28
 
17
- # TODO spec this!
18
- def apply_default_css_or_xpath_and_normalize(css_or_xpath=nil)
19
- css_or_xpath ||= "$"
20
- [css_or_xpath].flatten.each {|item| item.gsub!(/\$/, [default_css_or_xpath].flatten.join(", ")) }
21
- [css_or_xpath].flatten
22
- end
23
-
24
- def build_within(enumerator_or_element, css_or_xpath: nil)
25
- css_or_xpath = apply_default_css_or_xpath_and_normalize(css_or_xpath)
29
+ # Builds a new enumerator within the scope of the provided argument (either
30
+ # an enumerator or a class). Accepts optional selectors to further limit
31
+ # the scope of results found.
32
+ #
33
+ # @param enumerator_or_element [ElementEnumeratorBase, ElementBase] the object
34
+ # within which to iterate
35
+ # @param search_query [SearchQuery] search directives to limit iteration results
36
+ # @return [ElementEnumeratorBase] actually returns the concrete enumerator class
37
+ # given to the factory in its constructor.
38
+ #
39
+ def build_within(enumerator_or_element, search_query: SearchQuery.new)
40
+ search_query.apply_default_css_or_xpath_and_normalize(default_css_or_xpath)
26
41
 
27
42
  case enumerator_or_element
28
43
  when ElementBase
29
- build_within_element(enumerator_or_element, css_or_xpath: css_or_xpath)
44
+ build_within_element(enumerator_or_element, search_query: search_query)
30
45
  when ElementEnumeratorBase
31
- build_within_other_enumerator(enumerator_or_element, css_or_xpath: css_or_xpath)
46
+ build_within_other_enumerator(enumerator_or_element, search_query: search_query)
32
47
  end
33
48
  end
34
49
 
50
+ # Builds a new enumerator that finds elements matching either this factory's or the provided
51
+ # factory's selectors.
52
+ #
53
+ # @param other_factory [ElementEnumeratorFactory]
54
+ # @return [ElementEnumeratorFactory]
55
+ #
35
56
  def or_with(other_factory)
36
57
  self.class.new(
37
- default_css_or_xpath: default_css_or_xpath + ", " + other_factory.default_css_or_xpath,
58
+ default_css_or_xpath: "#{default_css_or_xpath}, #{other_factory.default_css_or_xpath}",
38
59
  enumerator_class: TypeCastingElementEnumerator,
39
60
  detect_sub_element_class: true
40
61
  )
@@ -42,65 +63,51 @@ module Kitchen
42
63
 
43
64
  protected
44
65
 
45
- def build_within_element(element, css_or_xpath:)
46
- enumerator_class.new(css_or_xpath: css_or_xpath) do |block|
66
+ def build_within_element(element, search_query:)
67
+ enumerator_class.new(search_query: search_query) do |block|
47
68
  grand_ancestors = element.ancestors
48
69
  parent_ancestor = Ancestor.new(element)
49
70
 
50
- num_sub_elements = 0
71
+ # If the provided `search_query` has already been iterated through on this element,
72
+ # we need to undo any counting on the ancestors so that when they are counted again
73
+ # below, the counts are correct.
74
+ element.uncount(search_query)
51
75
 
52
- element.raw.search(*css_or_xpath).each_with_index do |sub_node, index|
76
+ element.raw.search(*search_query.css_or_xpath).each do |sub_node|
53
77
  sub_element = ElementFactory.build_from_node(
54
- node: sub_node,
55
- document: element.document,
56
- element_class: sub_element_class,
57
- default_short_type: Utils.search_path_to_type(css_or_xpath),
58
- detect_element_class: detect_sub_element_class
59
- )
60
-
61
- # If the provided `css_or_xpath` has already been counted, we need to uncount
62
- # them on the ancestors so that when they are counted again below, the counts
63
- # are correct. Only do this on the first loop!
64
- if index == 0
65
- if element.have_sub_elements_already_been_counted?(css_or_xpath)
66
- grand_ancestors.values.each do |ancestor|
67
- ancestor.decrement_descendant_count(
68
- sub_element.short_type,
69
- by: element.number_of_sub_elements_already_counted(css_or_xpath)
70
- )
71
- end
72
- end
73
- end
78
+ node: sub_node,
79
+ document: element.document,
80
+ element_class: sub_element_class,
81
+ default_short_type: search_query.as_type,
82
+ detect_element_class: detect_sub_element_class
83
+ )
84
+
85
+ next unless search_query.conditions_match?(sub_element)
74
86
 
75
87
  # Record this sub element's ancestors and increment their descendant counts
76
88
  sub_element.add_ancestors(grand_ancestors, parent_ancestor)
77
- sub_element.count_as_descendant
78
89
 
79
- # Remember how this sub element was found so can trace search history given
80
- # any element.
81
- sub_element.css_or_xpath_that_found_me = css_or_xpath
90
+ # Remember that we counted this sub element in case we need to later reset the counts
91
+ element.remember_that_a_sub_element_was_counted(search_query, sub_element.short_type)
82
92
 
83
- # Count runs through this loop for below
84
- num_sub_elements += 1
93
+ # Remember how this sub element was found so can trace search history given any element.
94
+ sub_element.search_query_that_found_me = search_query
85
95
 
86
96
  # Mark the location so that if there's an error we can show the developer where.
87
- sub_element.document.location = sub_element
97
+ sub_element.mark_as_current_location!
88
98
 
89
99
  block.yield(sub_element)
90
100
  end
91
-
92
- element.remember_that_sub_elements_are_already_counted(
93
- css_or_xpath: css_or_xpath, count: num_sub_elements
94
- )
95
101
  end
96
102
  end
97
103
 
98
- def build_within_other_enumerator(other_enumerator, css_or_xpath:)
104
+ def build_within_other_enumerator(other_enumerator, search_query:)
99
105
  # Return a new enumerator instance that internally iterates over `other_enumerator`
100
106
  # running a new enumerator for each element returned by that other enumerator.
101
- enumerator_class.new(css_or_xpath: css_or_xpath, upstream_enumerator: other_enumerator) do |block|
107
+ enumerator_class.new(search_query: search_query,
108
+ upstream_enumerator: other_enumerator) do |block|
102
109
  other_enumerator.each do |element|
103
- build_within_element(element, css_or_xpath: css_or_xpath).each do |sub_element|
110
+ build_within_element(element, search_query: search_query).each do |sub_element|
104
111
  block.yield(sub_element)
105
112
  end
106
113
  end