openstax_kitchen 2.0.0 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (181) 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 -1
  9. data/Gemfile +5 -3
  10. data/Gemfile.lock +65 -17
  11. data/README.md +65 -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 +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_references/main.rb +16 -0
  45. data/lib/kitchen/directions/bake_chapter_references/v1.rb +35 -0
  46. data/lib/kitchen/directions/bake_chapter_section_exercises/main.rb +11 -0
  47. data/lib/kitchen/directions/bake_chapter_section_exercises/v1.rb +28 -0
  48. data/lib/kitchen/directions/bake_chapter_summary.rb +45 -36
  49. data/lib/kitchen/directions/bake_chapter_title/main.rb +11 -0
  50. data/lib/kitchen/directions/bake_chapter_title/v1.rb +24 -0
  51. data/lib/kitchen/directions/bake_checkpoint.rb +44 -0
  52. data/lib/kitchen/directions/bake_composite_chapters.rb +14 -0
  53. data/lib/kitchen/directions/bake_composite_pages.rb +2 -2
  54. data/lib/kitchen/directions/bake_equations.rb +37 -0
  55. data/lib/kitchen/directions/bake_example.rb +39 -11
  56. data/lib/kitchen/directions/bake_figure.rb +8 -5
  57. data/lib/kitchen/directions/bake_first_elements.rb +16 -0
  58. data/lib/kitchen/directions/bake_footnotes/main.rb +2 -2
  59. data/lib/kitchen/directions/bake_footnotes/v1.rb +6 -5
  60. data/lib/kitchen/directions/bake_free_response/free_response.xhtml.erb +10 -0
  61. data/lib/kitchen/directions/bake_free_response/main.rb +11 -0
  62. data/lib/kitchen/directions/bake_free_response/v1.rb +29 -0
  63. data/lib/kitchen/directions/bake_further_research.rb +59 -0
  64. data/lib/kitchen/directions/bake_index/main.rb +2 -2
  65. data/lib/kitchen/directions/bake_index/v1.rb +46 -18
  66. data/lib/kitchen/directions/bake_link_placeholders.rb +24 -0
  67. data/lib/kitchen/directions/bake_math_in_paragraph.rb +5 -3
  68. data/lib/kitchen/directions/bake_non_introduction_pages.rb +26 -0
  69. data/lib/kitchen/directions/bake_notes/bake_autotitled_notes.rb +29 -0
  70. data/lib/kitchen/directions/bake_notes/bake_note_subtitle.rb +22 -0
  71. data/lib/kitchen/directions/bake_notes/bake_numbered_notes.rb +51 -0
  72. data/lib/kitchen/directions/bake_notes/bake_unclassified_notes.rb +30 -0
  73. data/lib/kitchen/directions/bake_numbered_exercise/main.rb +15 -0
  74. data/lib/kitchen/directions/bake_numbered_exercise/v1.rb +47 -0
  75. data/lib/kitchen/directions/bake_numbered_table/main.rb +4 -4
  76. data/lib/kitchen/directions/bake_numbered_table/v1.rb +37 -18
  77. data/lib/kitchen/directions/bake_page_abstracts.rb +30 -0
  78. data/lib/kitchen/directions/bake_preface/main.rb +11 -0
  79. data/lib/kitchen/directions/bake_preface/v1.rb +18 -0
  80. data/lib/kitchen/directions/bake_references/main.rb +16 -0
  81. data/lib/kitchen/directions/bake_references/v1.rb +48 -0
  82. data/lib/kitchen/directions/bake_stepwise.rb +8 -12
  83. data/lib/kitchen/directions/bake_suggested_reading.rb +31 -0
  84. data/lib/kitchen/directions/bake_theorem/main.rb +11 -0
  85. data/lib/kitchen/directions/bake_theorem/v1.rb +28 -0
  86. data/lib/kitchen/directions/bake_toc.rb +49 -22
  87. data/lib/kitchen/directions/bake_unit_title/main.rb +11 -0
  88. data/lib/kitchen/directions/bake_unit_title/v1.rb +23 -0
  89. data/lib/kitchen/directions/bake_unnumbered_tables.rb +7 -5
  90. data/lib/kitchen/directions/book_answer_key_container/eob_solutions_container.xhtml.erb +9 -0
  91. data/lib/kitchen/directions/book_answer_key_container/main.rb +11 -0
  92. data/lib/kitchen/directions/book_answer_key_container/v1.rb +13 -0
  93. data/lib/kitchen/directions/chapter_review_container/chapter_review.xhtml.erb +9 -0
  94. data/lib/kitchen/directions/chapter_review_container/main.rb +11 -0
  95. data/lib/kitchen/directions/chapter_review_container/v1.rb +13 -0
  96. data/lib/kitchen/directions/eoc_section_title_link_snippet.rb +20 -0
  97. data/lib/kitchen/directions/move_exercises_to_eoc/main.rb +27 -0
  98. data/lib/kitchen/directions/move_exercises_to_eoc/v1.rb +36 -0
  99. data/lib/kitchen/directions/move_exercises_to_eoc/v2.rb +49 -0
  100. data/lib/kitchen/directions/move_solutions_to_answer_key/main.rb +14 -0
  101. data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/american_government.rb +19 -0
  102. data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/calculus.rb +41 -0
  103. data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/uphysics.rb +63 -0
  104. data/lib/kitchen/directions/move_solutions_to_answer_key/v1.rb +34 -0
  105. data/lib/kitchen/directions/move_title_text_into_span.rb +2 -2
  106. data/lib/kitchen/document.rb +83 -13
  107. data/lib/kitchen/element.rb +20 -3
  108. data/lib/kitchen/element_base.rb +373 -63
  109. data/lib/kitchen/element_enumerator.rb +8 -0
  110. data/lib/kitchen/element_enumerator_base.rb +297 -28
  111. data/lib/kitchen/element_enumerator_factory.rb +64 -53
  112. data/lib/kitchen/element_factory.rb +27 -12
  113. data/lib/kitchen/errors.rb +5 -0
  114. data/lib/kitchen/example_element.rb +21 -6
  115. data/lib/kitchen/example_element_enumerator.rb +9 -1
  116. data/lib/kitchen/exercise_element.rb +42 -0
  117. data/lib/kitchen/exercise_element_enumerator.rb +21 -0
  118. data/lib/kitchen/figure_element.rb +36 -5
  119. data/lib/kitchen/figure_element_enumerator.rb +9 -1
  120. data/lib/kitchen/metadata_element.rb +34 -0
  121. data/lib/kitchen/metadata_element_enumerator.rb +21 -0
  122. data/lib/kitchen/mixins/block_error_if.rb +24 -4
  123. data/lib/kitchen/note_element.rb +48 -20
  124. data/lib/kitchen/note_element_enumerator.rb +9 -1
  125. data/lib/kitchen/oven.rb +66 -15
  126. data/lib/kitchen/page_element.rb +84 -14
  127. data/lib/kitchen/page_element_enumerator.rb +9 -1
  128. data/lib/kitchen/pantry.rb +28 -1
  129. data/lib/kitchen/patches/nokogiri.rb +59 -2
  130. data/lib/kitchen/patches/renderable.rb +9 -3
  131. data/lib/kitchen/patches/string.rb +8 -0
  132. data/lib/kitchen/recipe.rb +69 -32
  133. data/lib/kitchen/reference_element.rb +27 -0
  134. data/lib/kitchen/references_element_enumerator.rb +20 -0
  135. data/lib/kitchen/search_history.rb +43 -4
  136. data/lib/kitchen/search_query.rb +106 -0
  137. data/lib/kitchen/selector.rb +24 -0
  138. data/lib/kitchen/selectors/base.rb +65 -0
  139. data/lib/kitchen/selectors/standard_1.rb +21 -0
  140. data/lib/kitchen/table_element.rb +55 -7
  141. data/lib/kitchen/table_element_enumerator.rb +9 -1
  142. data/lib/kitchen/templates/eob_section_title_template.xhtml.erb +10 -0
  143. data/lib/kitchen/templates/eoc_section_title_template.xhtml.erb +10 -0
  144. data/lib/kitchen/term_element.rb +15 -4
  145. data/lib/kitchen/term_element_enumerator.rb +9 -1
  146. data/lib/kitchen/transliterations.rb +7 -5
  147. data/lib/kitchen/type_casting_element_enumerator.rb +17 -1
  148. data/lib/kitchen/unit_element.rb +45 -0
  149. data/lib/kitchen/unit_element_enumerator.rb +20 -0
  150. data/lib/kitchen/utils.rb +10 -13
  151. data/lib/kitchen/version.rb +5 -1
  152. data/lib/locales/en.yml +18 -7
  153. data/lib/locales/pl.yml +24 -0
  154. data/lib/openstax_kitchen.rb +44 -42
  155. data/openstax_kitchen.gemspec +26 -20
  156. data/tutorials/00/solution1.rb +9 -0
  157. data/tutorials/00/solution2.rb +8 -0
  158. data/tutorials/01/solution1.rb +18 -0
  159. data/tutorials/01/solution2.rb +26 -0
  160. data/tutorials/02/solution1.rb +31 -0
  161. data/tutorials/03/{solution_1.rb → solution1.rb} +6 -4
  162. data/tutorials/03/solution2.rb +18 -0
  163. data/tutorials/04/{solution_1.rb → solution1.rb} +4 -2
  164. data/tutorials/04/{solution_2.rb → solution2.rb} +6 -4
  165. data/tutorials/05/solution1.rb +11 -0
  166. data/tutorials/check_it +16 -15
  167. data/tutorials/setup_my_recipes +7 -6
  168. metadata +149 -24
  169. data/Dockerfile +0 -19
  170. data/docker-compose.yml +0 -12
  171. data/docker/entrypoint +0 -9
  172. data/lib/kitchen/directions/bake_chapter_glossary.rb +0 -34
  173. data/lib/kitchen/directions/bake_exercises.rb +0 -164
  174. data/lib/kitchen/directions/bake_notes.rb +0 -58
  175. data/tutorials/00/solution_1.rb +0 -7
  176. data/tutorials/00/solution_2.rb +0 -6
  177. data/tutorials/01/solution_1.rb +0 -16
  178. data/tutorials/01/solution_2.rb +0 -24
  179. data/tutorials/02/solution_1.rb +0 -29
  180. data/tutorials/03/solution_2.rb +0 -15
  181. data/tutorials/05/solution_1.rb +0 -9
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen
4
+ module Directions
5
+ module BookAnswerKeyContainer
6
+ def self.v1(book:)
7
+ V1.new.bake(book: book)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen::Directions::BookAnswerKeyContainer
4
+ class V1
5
+ renderable
6
+
7
+ def bake(book:)
8
+ @metadata = book.metadata.children_to_keep.copy
9
+ book.body.append(child: render(file: 'eob_solutions_container.xhtml.erb'))
10
+ book.body.first('.os-eob.os-solutions-container')
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ <div class="os-eoc os-chapter-review-container" data-type="composite-chapter" data-uuid-key=".chapter-review">
2
+ <h2 data-type="document-title">
3
+ <span class="os-text"><%= I18n.t(:eoc_chapter_review) %></span>
4
+ </h2>
5
+ <div data-type="metadata" style="display: none;">
6
+ <h1 data-type="document-title" itemprop="name"><%= I18n.t(:eoc_chapter_review) %></h1>
7
+ <%= @metadata.paste %>
8
+ </div>
9
+ </div>
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen
4
+ module Directions
5
+ module ChapterReviewContainer
6
+ def self.v1(chapter:, metadata_source:)
7
+ V1.new.bake(chapter: chapter, metadata_source: metadata_source)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen::Directions::ChapterReviewContainer
4
+ class V1
5
+ renderable
6
+
7
+ def bake(chapter:, metadata_source:)
8
+ @metadata = metadata_source.children_to_keep.copy
9
+ chapter.append(child: render(file: 'chapter_review.xhtml.erb'))
10
+ chapter.first('.os-eoc.os-chapter-review-container')
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen
4
+ module Directions
5
+ module EocSectionTitleLinkSnippet
6
+ def self.v1(page:)
7
+ chapter = page.ancestor(:chapter)
8
+ <<~HTML
9
+ <a href="##{page.title.id}">
10
+ <h3 data-type="document-title" id="#{page.title.copied_id}">
11
+ <span class="os-number">#{chapter.count_in(:book)}.#{page.count_in(:chapter)}</span>
12
+ <span class="os-divider"> </span>
13
+ <span class="os-text" data-type="" itemprop="">#{page.title_text}</span>
14
+ </h3>
15
+ </a>
16
+ HTML
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen
4
+ module Directions
5
+ module MoveExercisesToEOC
6
+ def self.v1(chapter:, metadata_source:, klass:, append_to: nil, uuid_prefix: '.')
7
+ V1.new.bake(
8
+ chapter: chapter,
9
+ metadata_source: metadata_source,
10
+ append_to: append_to,
11
+ klass: klass,
12
+ uuid_prefix: uuid_prefix
13
+ )
14
+ end
15
+
16
+ def self.v2(chapter:, metadata_source:, klass:, append_to: nil, uuid_prefix: '.')
17
+ V2.new.bake(
18
+ chapter: chapter,
19
+ metadata_source: metadata_source,
20
+ append_to: append_to,
21
+ klass: klass,
22
+ uuid_prefix: uuid_prefix
23
+ )
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen::Directions::MoveExercisesToEOC
4
+ class V1
5
+ renderable
6
+
7
+ def bake(chapter:, metadata_source:, klass:, append_to: nil, uuid_prefix: '.')
8
+ @klass = klass
9
+ @metadata = metadata_source.children_to_keep.copy
10
+ @title = I18n.t(:"eoc.#{klass}")
11
+ @uuid_prefix = uuid_prefix
12
+
13
+ exercise_clipboard = Kitchen::Clipboard.new
14
+
15
+ chapter.non_introduction_pages.each do |page|
16
+ sections = page.search("section.#{@klass}")
17
+
18
+ sections.each do |exercise_section|
19
+ exercise_section.first("[data-type='title']")&.trash
20
+
21
+ exercise_section.cut(to: exercise_clipboard)
22
+ end
23
+ end
24
+
25
+ return if exercise_clipboard.none?
26
+
27
+ @content = exercise_clipboard.paste
28
+
29
+ append_to_element = append_to || chapter
30
+ @in_composite_chapter = append_to_element.is?(:composite_chapter)
31
+
32
+ append_to_element.append(child: render(file:
33
+ '../../templates/eoc_section_title_template.xhtml.erb'))
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen::Directions::MoveExercisesToEOC
4
+ # Main difference from v1 is the presence of a section title
5
+ # and some additional wrappers
6
+ class V2
7
+ renderable
8
+
9
+ def bake(chapter:, metadata_source:, klass:, append_to: nil, uuid_prefix: '.')
10
+ @klass = klass
11
+ @metadata = metadata_source.children_to_keep.copy
12
+ @title = I18n.t(:"eoc.#{klass}")
13
+ @uuid_prefix = uuid_prefix
14
+
15
+ exercise_clipboard = Kitchen::Clipboard.new
16
+
17
+ chapter.non_introduction_pages.each do |page|
18
+ sections = page.search("section.#{@klass}")
19
+
20
+ sections.each do |exercise_section|
21
+ exercise_section.first("[data-type='title']")&.trash
22
+
23
+ # Get parent page title
24
+ section_title = Kitchen::Directions::EocSectionTitleLinkSnippet.v1(page: page)
25
+
26
+ # Configure section title & wrappers
27
+ exercise_section.prepend(child: section_title)
28
+ exercise_section.wrap('<div class="os-section-area">')
29
+ exercise_section = exercise_section.parent
30
+ exercise_section.cut(to: exercise_clipboard)
31
+ end
32
+ end
33
+
34
+ return if exercise_clipboard.none?
35
+
36
+ @content = <<~HTML
37
+ <div class="os-#{@klass}">
38
+ #{exercise_clipboard.paste}
39
+ </div>
40
+ HTML
41
+
42
+ append_to_element = append_to || chapter
43
+ @in_composite_chapter = append_to_element[:'data-type'] == 'composite-chapter'
44
+
45
+ append_to_element.append(child: render(file:
46
+ '../../templates/eoc_section_title_template.xhtml.erb'))
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen
4
+ module Directions
5
+ module MoveSolutionsToAnswerKey
6
+ def self.v1(chapter:, metadata_source:, strategy:, append_to:)
7
+ V1.new.bake(
8
+ chapter: chapter,
9
+ metadata_source: metadata_source,
10
+ strategy: strategy, append_to: append_to)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen::Directions::MoveSolutionsToAnswerKey
4
+ module Strategies
5
+ class AmericanGovernment
6
+ def bake(chapter:, append_to:)
7
+ bake_section(chapter: chapter, append_to: append_to, klass: 'review-questions')
8
+ end
9
+
10
+ protected
11
+
12
+ def bake_section(chapter:, append_to:, klass:)
13
+ chapter.search(".#{klass} [data-type='solution']").each do |solution|
14
+ append_to.add_child(solution.cut.to_s)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen::Directions::MoveSolutionsToAnswerKey
4
+ module Strategies
5
+ class Calculus
6
+ def bake(chapter:, append_to:)
7
+ checkpoint_solutions = chapter.search('.checkpoint [data-type="solution"]').cut
8
+ append_solution_area(I18n.t(:checkpoint), checkpoint_solutions, append_to)
9
+
10
+ chapter.search('.section-exercises').each do |section|
11
+ section_solutions = section.search('[data-type="solution"]').cut
12
+ section_title = I18n.t(
13
+ :section_exercises,
14
+ number: "#{chapter.count_in(:book)}.#{section.count_in(:chapter)}"
15
+ )
16
+ append_solution_area(section_title, section_solutions, append_to)
17
+ end
18
+
19
+ chapter.search('.review-exercises').each do |section|
20
+ section_solutions = section.search('[data-type="solution"]').cut
21
+ append_solution_area(I18n.t(:review_exercises), section_solutions, append_to)
22
+ end
23
+ end
24
+
25
+ protected
26
+
27
+ def append_solution_area(title, clipboard, append_to)
28
+ append_to.add_child(
29
+ <<~HTML
30
+ <div class="os-solution-area">
31
+ <h3 data-type="title">
32
+ <span class="os-title-label">#{title}</span>
33
+ </h3>
34
+ #{clipboard.paste}
35
+ </div>
36
+ HTML
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen::Directions::MoveSolutionsToAnswerKey
4
+ module Strategies
5
+ class UPhysics
6
+ def bake(chapter:, append_to:)
7
+ bake_from_notes(chapter: chapter, append_to: append_to, klass: 'check-understanding')
8
+
9
+ classes = %w[review-conceptual-questions review-problems review-additional-problems
10
+ review-challenge]
11
+ classes.each do |klass|
12
+ bake_section(chapter: chapter, append_to: append_to, klass: klass)
13
+ end
14
+ end
15
+
16
+ protected
17
+
18
+ def bake_section(chapter:, append_to:, klass:)
19
+ section_solutions_set = []
20
+ chapter.search(".#{klass}").each do |section|
21
+ section.search('[data-type="solution"]').each do |solution|
22
+ section_solutions_set.push(solution.cut)
23
+ end
24
+ end
25
+
26
+ return if section_solutions_set.empty?
27
+
28
+ title = I18n.t(:"eoc.#{klass}")
29
+ append_solution_area(title, section_solutions_set, append_to)
30
+ end
31
+
32
+ def bake_from_notes(chapter:, append_to:, klass:)
33
+ solutions = []
34
+ chapter.notes(".#{klass}").each do |note|
35
+ note.exercises.each do |exercise|
36
+ solution = exercise.solution
37
+ solutions.push(solution.cut) if solution
38
+ end
39
+ end
40
+ return if solutions.empty?
41
+
42
+ title = I18n.t(:"notes.#{klass}")
43
+ append_solution_area(title, solutions, append_to)
44
+ end
45
+
46
+ def append_solution_area(title, solutions, append_to)
47
+ append_to = append_to.add_child(
48
+ <<~HTML
49
+ <div class="os-solution-area">
50
+ <h3 data-type="title">
51
+ <span class="os-title-label">#{title}</span>
52
+ </h3>
53
+ </div>
54
+ HTML
55
+ ).first
56
+
57
+ solutions.each do |solution|
58
+ append_to.add_child(solution.raw)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen::Directions::MoveSolutionsToAnswerKey
4
+ class V1
5
+ def bake(chapter:, metadata_source:, strategy:, append_to:)
6
+ strategy =
7
+ case strategy
8
+ when :calculus
9
+ Strategies::Calculus
10
+ when :uphysics
11
+ Strategies::UPhysics
12
+ when :american_government
13
+ Strategies::AmericanGovernment
14
+ else
15
+ raise 'No such strategy'
16
+ end
17
+
18
+ append_to.append(child:
19
+ <<~HTML
20
+ <div class="os-eob os-solutions-container" data-type="composite-page" data-uuid-key=".solutions#{chapter.count_in(:book)}">
21
+ <h2 data-type="document-title">
22
+ <span class="os-text">#{I18n.t(:chapter)} #{chapter.count_in(:book)}</span>
23
+ </h2>
24
+ <div data-type="metadata" style="display: none;">
25
+ <h1 data-type="document-title" itemprop="name">#{I18n.t(:chapter)} #{chapter.count_in(:book)}</h1>
26
+ #{metadata_source.children_to_keep.copy.paste}
27
+ </div>
28
+ </div>
29
+ HTML
30
+ )
31
+ strategy.new.bake(chapter: chapter, append_to: append_to.last_element)
32
+ end
33
+ end
34
+ end
@@ -1,7 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kitchen
2
4
  module Directions
3
5
  module MoveTitleTextIntoSpan
4
-
5
6
  def self.v1(title:)
6
7
  title.replace_children(with:
7
8
  <<~HTML
@@ -9,7 +10,6 @@ module Kitchen
9
10
  HTML
10
11
  )
11
12
  end
12
-
13
13
  end
14
14
  end
15
15
  end
@@ -1,25 +1,67 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
4
 
3
5
  module Kitchen
6
+ # Wrapper around a Nokogiri `Document`, adding search with Kitchen enumerators,
7
+ # clipboards, pantries, etc.
8
+ #
4
9
  class Document
5
10
  extend Forwardable
6
11
 
12
+ # @return [Element] the current element yielded from search results
7
13
  attr_accessor :location
14
+ # @return [Config] the configuration used in this document
8
15
  attr_reader :config
9
16
 
17
+ # @!method selectors
18
+ # The document's selectors
19
+ # @see Config#selectors
20
+ # @return [Selectors::Base]
10
21
  def_delegators :config, :selectors
22
+
23
+ # @!method to_xhtml
24
+ # @see https://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Node#to_xhtml-instance_method Nokogiri::XML::Node#to_xhtml
25
+ # @return [String] the document as an XHTML string
26
+ # @!method to_s
27
+ # Turn this node in to a string. If the document is HTML, this method returns html.
28
+ # If the document is XML, this method returns XML.
29
+ # @see https://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Node#to_s-instance_method Nokogiri::XML::Node#to_s
30
+ # @return [String]
31
+ # @!method to_xml
32
+ # @see https://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Node#to_xml-instance_method Nokogiri::XML::Node#to_xml
33
+ # @return [String] the document as an XML string
34
+ # @!method to_html
35
+ # @see https://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Node#to_html-instance_method Nokogiri::XML::Node#to_html
36
+ # @return [String] the document as an HTML string
11
37
  def_delegators :@nokogiri_document, :to_xhtml, :to_s, :to_xml, :to_html
12
38
 
39
+ # Return a new instance of Document
40
+ #
41
+ # @param nokogiri_document [Nokogiri::XML::Document]
42
+ # @param config [Config]
13
43
  def initialize(nokogiri_document:, config: nil)
14
44
  @nokogiri_document = nokogiri_document
15
45
  @location = nil
16
- @config = config || Config.new_default
46
+ @config = config || Config.new
17
47
  @next_paste_count_for_id = {}
18
- @id_copy_suffix = "_copy_"
48
+ @id_copy_suffix = '_copy_'
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
19
60
  end
20
61
 
21
62
  # Returns an enumerator that iterates over all children of this document
22
- # that match the provided selector or XPath arguments.
63
+ # that match the provided selector or XPath arguments. Updates `location`
64
+ # during iteration.
23
65
  #
24
66
  # @param selector_or_xpath_args [Array<String>] CSS selectors or XPath arguments
25
67
  # @return [ElementEnumerator]
@@ -29,9 +71,11 @@ module Kitchen
29
71
 
30
72
  ElementEnumerator.new do |block|
31
73
  nokogiri_document.search(*selector_or_xpath_args).each do |inner_node|
32
- element = Kitchen::Element.new(node: inner_node,
33
- document: self,
34
- short_type: Utils.search_path_to_type(selector_or_xpath_args))
74
+ element = Kitchen::Element.new(
75
+ node: inner_node,
76
+ document: self,
77
+ short_type: Utils.search_path_to_type(selector_or_xpath_args)
78
+ )
35
79
  self.location = element
36
80
  block.yield(element)
37
81
  end
@@ -92,28 +136,52 @@ module Kitchen
92
136
  )
93
137
  end
94
138
 
139
+ # Create a new Element from a string
140
+ #
141
+ # @param string [String] the element as a string
142
+ #
143
+ # @example
144
+ # create_element_from_string("<div class='foo'>bar</div>") #=> <div class="foo">bar</div>
145
+ #
146
+ # @return [Element]
147
+ #
95
148
  def create_element_from_string(string)
96
- Kitchen::Element.new(
97
- node: @nokogiri_document.create_element("div"),
98
- document: self,
99
- short_type: "created_element_#{SecureRandom.hex(4)}"
100
- ).element_children.first
149
+ children = Nokogiri::XML("<foo>#{string}</foo>").search('foo').first.element_children
150
+ raise('new_element must only make one top-level element') if children.many?
151
+
152
+ node = children.first
153
+
154
+ create_element(node.name, node.attributes).tap do |element|
155
+ element.inner_html = node.children
156
+ end
101
157
  end
102
158
 
159
+ # Keeps track that an element with the given ID has been copied. When such
160
+ # elements are pasted, this information is used to give those elements unique
161
+ # IDs that don't duplicate the original element.
162
+ #
163
+ # @param id [String] the ID
164
+ #
103
165
  def record_id_copied(id)
104
166
  return if id.blank?
167
+
105
168
  @next_paste_count_for_id[id] ||= 1
106
169
  end
107
170
 
171
+ # Returns a unique ID given the ID of an element that was copied and is about
172
+ # to be pasted
173
+ #
174
+ # @param original_id [String]
175
+ #
108
176
  def modified_id_to_paste(original_id)
109
177
  return nil if original_id.nil?
110
- return "" if original_id.blank?
178
+ return '' if original_id.blank?
111
179
 
112
180
  count = next_count_for_pasted_id(original_id)
113
181
 
114
182
  # A count of 0 means the element was cut and this is the first paste, do not
115
183
  # modify the ID; otherwise, use the uniquified ID.
116
- if count == 0
184
+ if count.zero?
117
185
  original_id
118
186
  else
119
187
  "#{original_id}#{@id_copy_suffix}#{count}"
@@ -123,6 +191,7 @@ module Kitchen
123
191
  # Returns the underlying Nokogiri Document object
124
192
  #
125
193
  # @return [Nokogiri::XML::Document]
194
+ #
126
195
  def raw
127
196
  @nokogiri_document
128
197
  end
@@ -131,6 +200,7 @@ module Kitchen
131
200
 
132
201
  def next_count_for_pasted_id(id)
133
202
  return if id.blank?
203
+
134
204
  (@next_paste_count_for_id[id] ||= 0).tap do
135
205
  @next_paste_count_for_id[id] += 1
136
206
  end