openstax_kitchen 1.0.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (182) 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 +2 -2
  6. data/.inch.yml +6 -0
  7. data/.rubocop.yml +65 -0
  8. data/CHANGELOG.md +85 -2
  9. data/Gemfile +5 -3
  10. data/Gemfile.lock +66 -18
  11. data/README.md +69 -15
  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 +15 -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 +40 -5
  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 -6
  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 +21 -6
  31. data/lib/kitchen/composite_page_element.rb +35 -7
  32. data/lib/kitchen/composite_page_element_enumerator.rb +9 -1
  33. data/lib/kitchen/config.rb +20 -6
  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/main.rb +18 -0
  39. data/lib/kitchen/directions/bake_chapter_glossary/v1.rb +30 -0
  40. data/lib/kitchen/directions/bake_chapter_introductions.rb +7 -7
  41. data/lib/kitchen/directions/bake_chapter_key_concepts/main.rb +16 -0
  42. data/lib/kitchen/directions/bake_chapter_key_concepts/v1.rb +35 -0
  43. data/lib/kitchen/directions/bake_chapter_key_equations.rb +30 -20
  44. data/lib/kitchen/directions/bake_chapter_section_exercises/main.rb +11 -0
  45. data/lib/kitchen/directions/bake_chapter_section_exercises/v1.rb +28 -0
  46. data/lib/kitchen/directions/bake_chapter_summary.rb +45 -36
  47. data/lib/kitchen/directions/bake_chapter_title/main.rb +11 -0
  48. data/lib/kitchen/directions/bake_chapter_title/v1.rb +24 -0
  49. data/lib/kitchen/directions/bake_checkpoint.rb +44 -0
  50. data/lib/kitchen/directions/bake_composite_chapters.rb +14 -0
  51. data/lib/kitchen/directions/bake_composite_pages.rb +2 -2
  52. data/lib/kitchen/directions/bake_equations.rb +37 -0
  53. data/lib/kitchen/directions/bake_example.rb +39 -11
  54. data/lib/kitchen/directions/bake_figure.rb +8 -5
  55. data/lib/kitchen/directions/bake_first_elements.rb +16 -0
  56. data/lib/kitchen/directions/bake_footnotes/main.rb +2 -2
  57. data/lib/kitchen/directions/bake_footnotes/v1.rb +6 -5
  58. data/lib/kitchen/directions/bake_free_response/free_response.xhtml.erb +10 -0
  59. data/lib/kitchen/directions/bake_free_response/main.rb +11 -0
  60. data/lib/kitchen/directions/bake_free_response/v1.rb +29 -0
  61. data/lib/kitchen/directions/bake_further_research.rb +59 -0
  62. data/lib/kitchen/directions/bake_index/main.rb +2 -2
  63. data/lib/kitchen/directions/bake_index/v1.rb +46 -18
  64. data/lib/kitchen/directions/bake_link_placeholders.rb +24 -0
  65. data/lib/kitchen/directions/bake_math_in_paragraph.rb +5 -3
  66. data/lib/kitchen/directions/bake_non_introduction_pages.rb +26 -0
  67. data/lib/kitchen/directions/bake_notes/bake_autotitled_notes.rb +29 -0
  68. data/lib/kitchen/directions/bake_notes/bake_note_subtitle.rb +22 -0
  69. data/lib/kitchen/directions/bake_notes/bake_numbered_notes.rb +51 -0
  70. data/lib/kitchen/directions/bake_notes/bake_unclassified_notes.rb +30 -0
  71. data/lib/kitchen/directions/bake_numbered_exercise/main.rb +15 -0
  72. data/lib/kitchen/directions/bake_numbered_exercise/v1.rb +47 -0
  73. data/lib/kitchen/directions/bake_numbered_table/main.rb +4 -4
  74. data/lib/kitchen/directions/bake_numbered_table/v1.rb +37 -18
  75. data/lib/kitchen/directions/bake_page_abstracts.rb +30 -0
  76. data/lib/kitchen/directions/bake_preface/main.rb +11 -0
  77. data/lib/kitchen/directions/bake_preface/v1.rb +18 -0
  78. data/lib/kitchen/directions/bake_references/main.rb +16 -0
  79. data/lib/kitchen/directions/bake_references/v1.rb +48 -0
  80. data/lib/kitchen/directions/bake_stepwise.rb +8 -12
  81. data/lib/kitchen/directions/bake_suggested_reading.rb +31 -0
  82. data/lib/kitchen/directions/bake_theorem/main.rb +11 -0
  83. data/lib/kitchen/directions/bake_theorem/v1.rb +28 -0
  84. data/lib/kitchen/directions/bake_toc.rb +49 -22
  85. data/lib/kitchen/directions/bake_unit_title/main.rb +11 -0
  86. data/lib/kitchen/directions/bake_unit_title/v1.rb +23 -0
  87. data/lib/kitchen/directions/bake_unnumbered_tables.rb +7 -5
  88. data/lib/kitchen/directions/book_answer_key_container/eob_solutions_container.xhtml.erb +9 -0
  89. data/lib/kitchen/directions/book_answer_key_container/main.rb +11 -0
  90. data/lib/kitchen/directions/book_answer_key_container/v1.rb +13 -0
  91. data/lib/kitchen/directions/chapter_review_container/chapter_review.xhtml.erb +9 -0
  92. data/lib/kitchen/directions/chapter_review_container/main.rb +11 -0
  93. data/lib/kitchen/directions/chapter_review_container/v1.rb +13 -0
  94. data/lib/kitchen/directions/eoc_section_title_link_snippet.rb +20 -0
  95. data/lib/kitchen/directions/move_exercises_to_eoc/main.rb +27 -0
  96. data/lib/kitchen/directions/move_exercises_to_eoc/v1.rb +36 -0
  97. data/lib/kitchen/directions/move_exercises_to_eoc/v2.rb +49 -0
  98. data/lib/kitchen/directions/move_solutions_to_answer_key/main.rb +14 -0
  99. data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/american_government.rb +19 -0
  100. data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/calculus.rb +41 -0
  101. data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/uphysics.rb +63 -0
  102. data/lib/kitchen/directions/move_solutions_to_answer_key/v1.rb +34 -0
  103. data/lib/kitchen/directions/move_title_text_into_span.rb +2 -2
  104. data/lib/kitchen/document.rb +83 -13
  105. data/lib/kitchen/element.rb +20 -3
  106. data/lib/kitchen/element_base.rb +373 -63
  107. data/lib/kitchen/element_enumerator.rb +8 -0
  108. data/lib/kitchen/element_enumerator_base.rb +297 -28
  109. data/lib/kitchen/element_enumerator_factory.rb +64 -53
  110. data/lib/kitchen/element_factory.rb +27 -12
  111. data/lib/kitchen/errors.rb +5 -0
  112. data/lib/kitchen/example_element.rb +21 -6
  113. data/lib/kitchen/example_element_enumerator.rb +9 -1
  114. data/lib/kitchen/exercise_element.rb +42 -0
  115. data/lib/kitchen/exercise_element_enumerator.rb +21 -0
  116. data/lib/kitchen/figure_element.rb +36 -5
  117. data/lib/kitchen/figure_element_enumerator.rb +9 -1
  118. data/lib/kitchen/metadata_element.rb +34 -0
  119. data/lib/kitchen/metadata_element_enumerator.rb +21 -0
  120. data/lib/kitchen/mixins/block_error_if.rb +24 -4
  121. data/lib/kitchen/note_element.rb +48 -20
  122. data/lib/kitchen/note_element_enumerator.rb +9 -1
  123. data/lib/kitchen/oven.rb +66 -15
  124. data/lib/kitchen/page_element.rb +84 -14
  125. data/lib/kitchen/page_element_enumerator.rb +9 -1
  126. data/lib/kitchen/pantry.rb +28 -1
  127. data/lib/kitchen/patches/nokogiri.rb +19 -2
  128. data/lib/kitchen/patches/renderable.rb +9 -3
  129. data/lib/kitchen/patches/string.rb +8 -0
  130. data/lib/kitchen/recipe.rb +69 -32
  131. data/lib/kitchen/reference_element.rb +27 -0
  132. data/lib/kitchen/references_element_enumerator.rb +20 -0
  133. data/lib/kitchen/search_history.rb +43 -4
  134. data/lib/kitchen/search_query.rb +106 -0
  135. data/lib/kitchen/selector.rb +24 -0
  136. data/lib/kitchen/selectors/base.rb +65 -0
  137. data/lib/kitchen/selectors/standard_1.rb +21 -0
  138. data/lib/kitchen/table_element.rb +55 -7
  139. data/lib/kitchen/table_element_enumerator.rb +9 -1
  140. data/lib/kitchen/templates/eob_section_title_template.xhtml.erb +10 -0
  141. data/lib/kitchen/templates/eoc_section_title_template.xhtml.erb +10 -0
  142. data/lib/kitchen/term_element.rb +15 -4
  143. data/lib/kitchen/term_element_enumerator.rb +9 -1
  144. data/lib/kitchen/transliterations.rb +7 -5
  145. data/lib/kitchen/type_casting_element_enumerator.rb +17 -1
  146. data/lib/kitchen/unit_element.rb +45 -0
  147. data/lib/kitchen/unit_element_enumerator.rb +20 -0
  148. data/lib/kitchen/utils.rb +10 -13
  149. data/lib/kitchen/version.rb +5 -1
  150. data/lib/locales/en.yml +18 -7
  151. data/lib/locales/pl.yml +24 -0
  152. data/lib/openstax_kitchen.rb +59 -0
  153. data/openstax_kitchen.gemspec +26 -20
  154. data/tutorials/00/solution1.rb +9 -0
  155. data/tutorials/00/solution2.rb +8 -0
  156. data/tutorials/01/solution1.rb +18 -0
  157. data/tutorials/01/solution2.rb +26 -0
  158. data/tutorials/02/solution1.rb +31 -0
  159. data/tutorials/03/{solution_1.rb → solution1.rb} +6 -4
  160. data/tutorials/03/solution2.rb +18 -0
  161. data/tutorials/04/{solution_1.rb → solution1.rb} +4 -2
  162. data/tutorials/04/{solution_2.rb → solution2.rb} +6 -4
  163. data/tutorials/05/solution1.rb +11 -0
  164. data/tutorials/check_it +16 -15
  165. data/tutorials/setup_my_recipes +7 -6
  166. metadata +148 -27
  167. data/Dockerfile +0 -19
  168. data/bin/normalize +0 -79
  169. data/books/chemistry2e/bake.rb +0 -133
  170. data/docker-compose.yml +0 -12
  171. data/docker/entrypoint +0 -9
  172. data/lib/kitchen.rb +0 -57
  173. data/lib/kitchen/directions/bake_chapter_glossary.rb +0 -34
  174. data/lib/kitchen/directions/bake_exercises.rb +0 -164
  175. data/lib/kitchen/directions/bake_notes.rb +0 -58
  176. data/tutorials/00/solution_1.rb +0 -7
  177. data/tutorials/00/solution_2.rb +0 -6
  178. data/tutorials/01/solution_1.rb +0 -16
  179. data/tutorials/01/solution_2.rb +0 -24
  180. data/tutorials/02/solution_1.rb +0 -29
  181. data/tutorials/03/solution_2.rb +0 -15
  182. 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,314 @@
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
8
+
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
3
15
 
4
- def initialize(size=nil, css_or_xpath: nil, upstream_enumerator: nil)
5
- @css_or_xpath = css_or_xpath
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)
54
+ end
55
+
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)
71
+ end
72
+
73
+ # Returns an enumerator that iterates through composite pages 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 composite_pages(css_or_xpath=nil, only: nil, except: nil)
86
+ block_error_if(block_given?)
87
+ chain_to(CompositePageElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
88
+ end
89
+
90
+ # Returns an enumerator that iterates through pages that arent the introduction page 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 non_introduction_pages(only: nil, except: nil)
97
+ block_error_if(block_given?)
98
+ chain_to(PageElementEnumerator,
99
+ css_or_xpath: '$:not(.introduction)',
100
+ only: only,
101
+ except: except)
102
+ end
103
+
104
+ # Returns an enumerator that iterates through chapters within the scope of this enumerator
105
+ #
106
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
107
+ # a "$" in this argument will be replaced with the default selector for the element being
108
+ # iterated over.
109
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
110
+ # lambda or proc that accepts an element; elements will only be included in the
111
+ # search results if the method or callable returns true
112
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
113
+ # lambda or proc that accepts an element; elements will not be included in the
114
+ # search results if the method or callable returns false
115
+ #
116
+ def chapters(css_or_xpath=nil, only: nil, except: nil)
117
+ block_error_if(block_given?)
118
+ chain_to(ChapterElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
119
+ end
120
+
121
+ # Returns an enumerator that iterates through units within the scope of this enumerator
122
+ #
123
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
124
+ # a "$" in this argument will be replaced with the default selector for the element being
125
+ # iterated over.
126
+ #
127
+ def units(css_or_xpath=nil, only: nil, except: nil)
128
+ block_error_if(block_given?)
129
+ chain_to(UnitElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
12
130
  end
13
131
 
14
- def terms(css_or_xpath=nil, &block)
15
- chain_to(TermElementEnumerator, css_or_xpath: css_or_xpath, &block)
132
+ # Returns an enumerator that iterates through figures within the scope of this enumerator
133
+ #
134
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
135
+ # a "$" in this argument will be replaced with the default selector for the element being
136
+ # iterated over.
137
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
138
+ # lambda or proc that accepts an element; elements will only be included in the
139
+ # search results if the method or callable returns true
140
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
141
+ # lambda or proc that accepts an element; elements will not be included in the
142
+ # search results if the method or callable returns false
143
+ #
144
+ def figures(css_or_xpath=nil, only: nil, except: nil)
145
+ block_error_if(block_given?)
146
+ chain_to(FigureElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
147
+ end
148
+
149
+ # Returns an enumerator that iterates through notes within the scope of this enumerator
150
+ #
151
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
152
+ # a "$" in this argument will be replaced with the default selector for the element being
153
+ # iterated over.
154
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
155
+ # lambda or proc that accepts an element; elements will only be included in the
156
+ # search results if the method or callable returns true
157
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
158
+ # lambda or proc that accepts an element; elements will not be included in the
159
+ # search results if the method or callable returns false
160
+ #
161
+ def notes(css_or_xpath=nil, only: nil, except: nil)
162
+ block_error_if(block_given?)
163
+ chain_to(NoteElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
16
164
  end
17
165
 
18
- def pages(css_or_xpath=nil, &block)
19
- chain_to(PageElementEnumerator, css_or_xpath: css_or_xpath, &block)
166
+ # Returns an enumerator that iterates through tables within the scope of this enumerator
167
+ #
168
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
169
+ # a "$" in this argument will be replaced with the default selector for the element being
170
+ # iterated over.
171
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
172
+ # lambda or proc that accepts an element; elements will only be included in the
173
+ # search results if the method or callable returns true
174
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
175
+ # lambda or proc that accepts an element; elements will not be included in the
176
+ # search results if the method or callable returns false
177
+ #
178
+ def tables(css_or_xpath=nil, only: nil, except: nil)
179
+ block_error_if(block_given?)
180
+ chain_to(TableElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
20
181
  end
21
182
 
22
- def chapters(css_or_xpath=nil, &block)
23
- chain_to(ChapterElementEnumerator, css_or_xpath: css_or_xpath, &block)
183
+ # Returns an enumerator that iterates through examples within the scope of this enumerator
184
+ #
185
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
186
+ # a "$" in this argument will be replaced with the default selector for the element being
187
+ # iterated over.
188
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
189
+ # lambda or proc that accepts an element; elements will only be included in the
190
+ # search results if the method or callable returns true
191
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
192
+ # lambda or proc that accepts an element; elements will not be included in the
193
+ # search results if the method or callable returns false
194
+ #
195
+ def examples(css_or_xpath=nil, only: nil, except: nil)
196
+ block_error_if(block_given?)
197
+ chain_to(ExampleElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
24
198
  end
25
199
 
26
- # use block_error_if
27
- def figures(css_or_xpath=nil, &block)
28
- chain_to(FigureElementEnumerator, css_or_xpath: css_or_xpath, &block)
200
+ # Returns an enumerator that iterates through titles within the scope of this enumerator
201
+ #
202
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
203
+ # a "$" in this argument will be replaced with the default selector for the element being
204
+ # iterated over.
205
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
206
+ # lambda or proc that accepts an element; elements will only be included in the
207
+ # search results if the method or callable returns true
208
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
209
+ # lambda or proc that accepts an element; elements will not be included in the
210
+ # search results if the method or callable returns false
211
+ #
212
+ def titles(css_or_xpath=nil, only: nil, except: nil)
213
+ block_error_if(block_given?)
214
+ chain_to(ElementEnumerator,
215
+ default_css_or_xpath: '[data-type="title"]',
216
+ css_or_xpath: css_or_xpath,
217
+ only: only,
218
+ except: except)
29
219
  end
30
220
 
31
- def notes(css_or_xpath=nil, &block)
32
- chain_to(NoteElementEnumerator, css_or_xpath: css_or_xpath, &block)
221
+ # Returns an enumerator that iterates through references within the scope of this enumerator
222
+ #
223
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
224
+ # a "$" in this argument will be replaced with the default selector for the element being
225
+ # iterated over.
226
+ #
227
+ def references(css_or_xpath=nil)
228
+ block_error_if(block_given?)
229
+ chain_to(ReferenceElementEnumerator, css_or_xpath: css_or_xpath)
33
230
  end
34
231
 
35
- def tables(css_or_xpath=nil, &block)
36
- chain_to(TableElementEnumerator, css_or_xpath: css_or_xpath, &block)
232
+ # Returns an enumerator that iterates through exercises within the scope of this enumerator
233
+ #
234
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
235
+ # lambda or proc that accepts an element; elements will only be included in the
236
+ # search results if the method or callable returns true
237
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
238
+ # lambda or proc that accepts an element; elements will not be included in the
239
+ # search results if the method or callable returns false
240
+ #
241
+ def exercises(css_or_xpath=nil, only: nil, except: nil)
242
+ block_error_if(block_given?)
243
+ chain_to(ExerciseElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
37
244
  end
38
245
 
39
- def examples(css_or_xpath=nil, &block)
40
- chain_to(ExampleElementEnumerator, css_or_xpath: css_or_xpath, &block)
246
+ # Returns an enumerator that iterates through metadata within the scope of this enumerator
247
+ #
248
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over;
249
+ # a "$" in this argument will be replaced with the default selector for the element being
250
+ # iterated over.
251
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
252
+ # lambda or proc that accepts an element; elements will only be included in the
253
+ # search results if the method or callable returns true
254
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
255
+ # lambda or proc that accepts an element; elements will not be included in the
256
+ # search results if the method or callable returns false
257
+ #
258
+ def metadatas(css_or_xpath=nil, only: nil, except: nil)
259
+ block_error_if(block_given?)
260
+ chain_to(MetadataElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
41
261
  end
42
262
 
43
- def search(css_or_xpath=nil, &block)
44
- chain_to(ElementEnumerator, css_or_xpath: css_or_xpath, &block)
263
+ # Returns an enumerator that iterates within the scope of this enumerator
264
+ #
265
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over
266
+ #
267
+ def search(css_or_xpath=nil, only: nil, except: nil)
268
+ block_error_if(block_given?)
269
+ chain_to(ElementEnumerator, css_or_xpath: css_or_xpath, only: only, except: except)
45
270
  end
46
271
 
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?
272
+ # Returns an enumerator that iterates through elements within the scope of this enumerator
273
+ #
274
+ # @param enumerator_class [ElementEnumeratorBase] the enumerator to use for the iteration
275
+ # @param default_css_or_xpath [String] the default CSS or xpath to use when iterating. Normally,
276
+ # this value is provided by the `enumerator_class`, but that isn't always the case, e.g.
277
+ # when that class is a generic `ElementEnumerator`.
278
+ # @param css_or_xpath [String] additional selectors to further narrow the element iterated over
279
+ # a "$" in this argument will be replaced with the default selector for the element being
280
+ # iterated over.
281
+ # @param only [Symbol, Callable] the name of a method to call on an element or a
282
+ # lambda or proc that accepts an element; elements will only be included in the
283
+ # search results if the method or callable returns true
284
+ # @param except [Symbol, Callable] the name of a method to call on an element or a
285
+ # lambda or proc that accepts an element; elements will not be included in the
286
+ # search results if the method or callable returns false
287
+ #
288
+ def chain_to(enumerator_class, default_css_or_xpath: nil, css_or_xpath: nil,
289
+ only: nil, except: nil)
290
+ block_error_if(block_given?)
291
+
292
+ search_query = SearchQuery.new(
293
+ css_or_xpath: css_or_xpath,
294
+ only: only,
295
+ except: except
296
+ )
297
+
298
+ if default_css_or_xpath
299
+ search_query.apply_default_css_or_xpath_and_normalize(default_css_or_xpath)
300
+ end
49
301
 
50
- enumerator_class.factory.build_within(self, css_or_xpath: css_or_xpath)
302
+ enumerator_class.factory.build_within(self, search_query: search_query)
51
303
  end
52
304
 
53
- def first!(missing_message: "Could not return a first result")
305
+ # Returns the first element in this enumerator
306
+ #
307
+ # @param missing_message [String] the message to raise if a first element isn't available
308
+ # @raise [RecipeError] if a first element isn't available
309
+ # @return [Element]
310
+ #
311
+ def first!(missing_message: 'Could not return a first result')
54
312
  first || raise(RecipeError, "#{missing_message} matching #{search_history.latest} " \
55
313
  "inside [#{search_history.upstream}]")
56
314
  end
@@ -64,7 +322,7 @@ module Kitchen
64
322
  #
65
323
  def cut(to: nil)
66
324
  to ||= Clipboard.new
67
- self.each do |element|
325
+ each do |element|
68
326
  element.cut(to: to)
69
327
  end
70
328
  to
@@ -79,22 +337,33 @@ module Kitchen
79
337
  #
80
338
  def copy(to: nil)
81
339
  to ||= Clipboard.new
82
- self.each do |element|
340
+ each do |element|
83
341
  element.copy(to: to)
84
342
  end
85
343
  to
86
344
  end
87
345
 
346
+ # Removes all matching elements from the document
347
+ #
88
348
  def trash
89
- self.each(&:trash)
349
+ each(&:trash)
90
350
  end
91
351
 
352
+ # Returns the element at the provided index
353
+ #
354
+ # @param index [Integer]
355
+ # @return [Element]
356
+ #
92
357
  def [](index)
93
358
  to_a[index]
94
359
  end
95
360
 
361
+ # Returns a concatenation of +to_s+ for all elements in the enumerator
362
+ #
363
+ # @return [String]
364
+ #
96
365
  def to_s
97
- self.map(&:to_s).join("")
366
+ map(&:to_s).join('')
98
367
  end
99
368
 
100
369
  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,53 @@ 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, Proc, Symbol] The selectors to substitute for the "$" character
16
+ # when this factory is used to build an enumerator. A string argument is used literally. A proc
17
+ # is eventually called given the document's Config object (for accessing selectors). A symbol
18
+ # is interpreted as the name of a selector and is called on the document's Config object's
19
+ # selectors object. The easiest way to get a Proc is to pass `Selector.named(:name_of_selector)`
20
+ # @param sub_element_class [ElementBase] The element class to use for what the enumerator finds.
21
+ # @param enumerator_class [ElementEnumeratorBase] The enumerator class to return
22
+ # @param detect_sub_element_class [Boolean] If true, infers the sub_element_class from the node
23
+ #
24
+ def initialize(enumerator_class:, default_css_or_xpath: nil, sub_element_class: nil,
25
+ detect_sub_element_class: false)
11
26
  @default_css_or_xpath = default_css_or_xpath
12
27
  @sub_element_class = sub_element_class
13
28
  @enumerator_class = enumerator_class
14
29
  @detect_sub_element_class = detect_sub_element_class
15
30
  end
16
31
 
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)
26
-
32
+ # Builds a new enumerator within the scope of the provided argument (either
33
+ # an enumerator or a class). Accepts optional selectors to further limit
34
+ # the scope of results found.
35
+ #
36
+ # @param enumerator_or_element [ElementEnumeratorBase, ElementBase] the object
37
+ # within which to iterate
38
+ # @param search_query [SearchQuery] search directives to limit iteration results
39
+ # @return [ElementEnumeratorBase] actually returns the concrete enumerator class
40
+ # given to the factory in its constructor.
41
+ #
42
+ def build_within(enumerator_or_element, search_query: SearchQuery.new)
27
43
  case enumerator_or_element
28
44
  when ElementBase
29
- build_within_element(enumerator_or_element, css_or_xpath: css_or_xpath)
45
+ build_within_element(enumerator_or_element, search_query: search_query)
30
46
  when ElementEnumeratorBase
31
- build_within_other_enumerator(enumerator_or_element, css_or_xpath: css_or_xpath)
47
+ build_within_other_enumerator(enumerator_or_element, search_query: search_query)
32
48
  end
33
49
  end
34
50
 
51
+ # Builds a new enumerator that finds elements matching either this factory's or the provided
52
+ # factory's selectors.
53
+ #
54
+ # @param other_factory [ElementEnumeratorFactory]
55
+ # @return [ElementEnumeratorFactory]
56
+ #
35
57
  def or_with(other_factory)
36
58
  self.class.new(
37
- default_css_or_xpath: default_css_or_xpath + ", " + other_factory.default_css_or_xpath,
59
+ default_css_or_xpath: [default_css_or_xpath, other_factory.default_css_or_xpath],
38
60
  enumerator_class: TypeCastingElementEnumerator,
39
61
  detect_sub_element_class: true
40
62
  )
@@ -42,65 +64,54 @@ module Kitchen
42
64
 
43
65
  protected
44
66
 
45
- def build_within_element(element, css_or_xpath:)
46
- enumerator_class.new(css_or_xpath: css_or_xpath) do |block|
67
+ def build_within_element(element, search_query:)
68
+ search_query.apply_default_css_or_xpath_and_normalize(default_css_or_xpath,
69
+ config: element.config)
70
+
71
+ enumerator_class.new(search_query: search_query) do |block|
47
72
  grand_ancestors = element.ancestors
48
73
  parent_ancestor = Ancestor.new(element)
49
74
 
50
- num_sub_elements = 0
75
+ # If the provided `search_query` has already been iterated through on this element,
76
+ # we need to undo any counting on the ancestors so that when they are counted again
77
+ # below, the counts are correct.
78
+ element.uncount(search_query)
51
79
 
52
- element.raw.search(*css_or_xpath).each_with_index do |sub_node, index|
80
+ element.raw.search(*search_query.css_or_xpath).each do |sub_node|
53
81
  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
82
+ node: sub_node,
83
+ document: element.document,
84
+ element_class: sub_element_class,
85
+ default_short_type: search_query.as_type,
86
+ detect_element_class: detect_sub_element_class
87
+ )
88
+
89
+ next unless search_query.conditions_match?(sub_element)
74
90
 
75
91
  # Record this sub element's ancestors and increment their descendant counts
76
92
  sub_element.add_ancestors(grand_ancestors, parent_ancestor)
77
- sub_element.count_as_descendant
78
93
 
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
94
+ # Remember that we counted this sub element in case we need to later reset the counts
95
+ element.remember_that_a_sub_element_was_counted(search_query, sub_element.short_type)
82
96
 
83
- # Count runs through this loop for below
84
- num_sub_elements += 1
97
+ # Remember how this sub element was found so can trace search history given any element.
98
+ sub_element.search_query_that_found_me = search_query
85
99
 
86
100
  # Mark the location so that if there's an error we can show the developer where.
87
- sub_element.document.location = sub_element
101
+ sub_element.mark_as_current_location!
88
102
 
89
103
  block.yield(sub_element)
90
104
  end
91
-
92
- element.remember_that_sub_elements_are_already_counted(
93
- css_or_xpath: css_or_xpath, count: num_sub_elements
94
- )
95
105
  end
96
106
  end
97
107
 
98
- def build_within_other_enumerator(other_enumerator, css_or_xpath:)
108
+ def build_within_other_enumerator(other_enumerator, search_query:)
99
109
  # Return a new enumerator instance that internally iterates over `other_enumerator`
100
110
  # 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|
111
+ enumerator_class.new(search_query: search_query,
112
+ upstream_enumerator: other_enumerator) do |block|
102
113
  other_enumerator.each do |element|
103
- build_within_element(element, css_or_xpath: css_or_xpath).each do |sub_element|
114
+ build_within_element(element, search_query: search_query).each do |sub_element|
104
115
  block.yield(sub_element)
105
116
  end
106
117
  end