openstax_kitchen 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. checksums.yaml +7 -0
  2. data/.devcontainer/devcontainer.json +19 -0
  3. data/.github/workflows/tests.yml +36 -0
  4. data/.gitignore +20 -0
  5. data/.rspec +3 -0
  6. data/.solargraph.yml +15 -0
  7. data/CHANGELOG.md +11 -0
  8. data/CODE_OF_CONDUCT.md +74 -0
  9. data/Dockerfile +19 -0
  10. data/Gemfile +9 -0
  11. data/Gemfile.lock +74 -0
  12. data/LICENSE.txt +21 -0
  13. data/README.md +674 -0
  14. data/Rakefile +6 -0
  15. data/bin/console +14 -0
  16. data/bin/normalize +79 -0
  17. data/bin/setup +8 -0
  18. data/books/chemistry2e/bake.rb +133 -0
  19. data/codecov.yaml +27 -0
  20. data/docker-compose.yml +12 -0
  21. data/docker/bash +1 -0
  22. data/docker/entrypoint +9 -0
  23. data/lib/kitchen.rb +57 -0
  24. data/lib/kitchen/ancestor.rb +30 -0
  25. data/lib/kitchen/book_document.rb +18 -0
  26. data/lib/kitchen/book_element.rb +24 -0
  27. data/lib/kitchen/book_element_enumerator.rb +5 -0
  28. data/lib/kitchen/book_recipe.rb +25 -0
  29. data/lib/kitchen/chapter_element.rb +35 -0
  30. data/lib/kitchen/chapter_element_enumerator.rb +13 -0
  31. data/lib/kitchen/clipboard.rb +37 -0
  32. data/lib/kitchen/composite_chapter_element.rb +23 -0
  33. data/lib/kitchen/composite_page_element.rb +27 -0
  34. data/lib/kitchen/composite_page_element_enumerator.rb +13 -0
  35. data/lib/kitchen/config.rb +20 -0
  36. data/lib/kitchen/counter.rb +34 -0
  37. data/lib/kitchen/debug/print_recipe_error.rb +80 -0
  38. data/lib/kitchen/directions/bake_appendix.rb +26 -0
  39. data/lib/kitchen/directions/bake_chapter_glossary.rb +34 -0
  40. data/lib/kitchen/directions/bake_chapter_introductions.rb +58 -0
  41. data/lib/kitchen/directions/bake_chapter_key_equations.rb +30 -0
  42. data/lib/kitchen/directions/bake_chapter_summary.rb +52 -0
  43. data/lib/kitchen/directions/bake_composite_pages.rb +13 -0
  44. data/lib/kitchen/directions/bake_example.rb +31 -0
  45. data/lib/kitchen/directions/bake_exercises.rb +164 -0
  46. data/lib/kitchen/directions/bake_figure.rb +25 -0
  47. data/lib/kitchen/directions/bake_footnotes/main.rb +11 -0
  48. data/lib/kitchen/directions/bake_footnotes/v1.rb +38 -0
  49. data/lib/kitchen/directions/bake_index/main.rb +11 -0
  50. data/lib/kitchen/directions/bake_index/v1.rb +138 -0
  51. data/lib/kitchen/directions/bake_index/v1.xhtml.erb +28 -0
  52. data/lib/kitchen/directions/bake_math_in_paragraph.rb +13 -0
  53. data/lib/kitchen/directions/bake_notes.rb +58 -0
  54. data/lib/kitchen/directions/bake_numbered_table/main.rb +11 -0
  55. data/lib/kitchen/directions/bake_numbered_table/v1.rb +47 -0
  56. data/lib/kitchen/directions/bake_stepwise.rb +27 -0
  57. data/lib/kitchen/directions/bake_toc.rb +103 -0
  58. data/lib/kitchen/directions/bake_unnumbered_tables.rb +14 -0
  59. data/lib/kitchen/directions/move_title_text_into_span.rb +15 -0
  60. data/lib/kitchen/document.rb +142 -0
  61. data/lib/kitchen/element.rb +15 -0
  62. data/lib/kitchen/element_base.rb +444 -0
  63. data/lib/kitchen/element_enumerator.rb +12 -0
  64. data/lib/kitchen/element_enumerator_base.rb +101 -0
  65. data/lib/kitchen/element_enumerator_factory.rb +111 -0
  66. data/lib/kitchen/element_factory.rb +32 -0
  67. data/lib/kitchen/errors.rb +4 -0
  68. data/lib/kitchen/example_element.rb +20 -0
  69. data/lib/kitchen/example_element_enumerator.rb +13 -0
  70. data/lib/kitchen/figure_element.rb +20 -0
  71. data/lib/kitchen/figure_element_enumerator.rb +13 -0
  72. data/lib/kitchen/mixins/block_error_if.rb +19 -0
  73. data/lib/kitchen/note_element.rb +43 -0
  74. data/lib/kitchen/note_element_enumerator.rb +13 -0
  75. data/lib/kitchen/oven.rb +61 -0
  76. data/lib/kitchen/page_element.rb +51 -0
  77. data/lib/kitchen/page_element_enumerator.rb +13 -0
  78. data/lib/kitchen/pantry.rb +35 -0
  79. data/lib/kitchen/patches/nokogiri.rb +31 -0
  80. data/lib/kitchen/patches/renderable.rb +31 -0
  81. data/lib/kitchen/patches/string.rb +5 -0
  82. data/lib/kitchen/recipe.rb +78 -0
  83. data/lib/kitchen/search_history.rb +33 -0
  84. data/lib/kitchen/selectors/base.rb +8 -0
  85. data/lib/kitchen/selectors/standard_1.rb +12 -0
  86. data/lib/kitchen/table_element.rb +36 -0
  87. data/lib/kitchen/table_element_enumerator.rb +13 -0
  88. data/lib/kitchen/term_element.rb +16 -0
  89. data/lib/kitchen/term_element_enumerator.rb +13 -0
  90. data/lib/kitchen/transliterations.rb +19 -0
  91. data/lib/kitchen/type_casting_element_enumerator.rb +23 -0
  92. data/lib/kitchen/utils.rb +19 -0
  93. data/lib/kitchen/version.rb +3 -0
  94. data/lib/locales/en.yml +21 -0
  95. data/lib/notes.md +9 -0
  96. data/openstax_kitchen.gemspec +39 -0
  97. data/tutorials/00/expected_baked.html +3 -0
  98. data/tutorials/00/raw.html +3 -0
  99. data/tutorials/00/solution_1.rb +7 -0
  100. data/tutorials/00/solution_2.rb +6 -0
  101. data/tutorials/01/expected_baked.html +66 -0
  102. data/tutorials/01/raw.html +62 -0
  103. data/tutorials/01/solution_1.rb +16 -0
  104. data/tutorials/01/solution_2.rb +24 -0
  105. data/tutorials/02/expected_baked.html +207 -0
  106. data/tutorials/02/raw.html +201 -0
  107. data/tutorials/02/solution_1.rb +29 -0
  108. data/tutorials/03/expected_baked.html +33 -0
  109. data/tutorials/03/raw.html +31 -0
  110. data/tutorials/03/solution_1.rb +16 -0
  111. data/tutorials/03/solution_2.rb +15 -0
  112. data/tutorials/04/expected_baked.html +36 -0
  113. data/tutorials/04/raw.html +36 -0
  114. data/tutorials/04/solution_1.rb +20 -0
  115. data/tutorials/04/solution_2.rb +25 -0
  116. data/tutorials/05/expected_baked.html +11 -0
  117. data/tutorials/05/raw.html +11 -0
  118. data/tutorials/05/solution_1.rb +9 -0
  119. data/tutorials/check_it +64 -0
  120. data/tutorials/setup_my_recipes +30 -0
  121. metadata +278 -0
@@ -0,0 +1,28 @@
1
+ <div class="os-eob os-index-container " data-type="composite-page" data-uuid-key="index">
2
+ <h1 data-type="document-title">
3
+ <span class="os-text"><%= I18n.t(:eob_index_title) %></span>
4
+ </h1>
5
+ <div data-type="metadata" style="display: none;">
6
+ <h1 data-type="document-title" itemprop="name"><%= I18n.t(:eob_index_title) %></h1>
7
+ <%= @metadata_elements.paste %>
8
+ </div>
9
+ <% @index.sections.each do |section| %>
10
+ <div class="group-by">
11
+ <span class="group-label"><%= section.name %></span>
12
+ <% section.items.each do |item| %>
13
+ <div class="os-index-item">
14
+ <% item.terms.each_with_index do |term, ii| %>
15
+ <% if ii == 0 %>
16
+ <span class="os-term" group-by="<%= term.group_by %>"><%= term.text %></span>
17
+ <% else %>
18
+ <span class="os-index-link-separator">, </span>
19
+ <% end %>
20
+ <a class="os-term-section-link" href="#<%= term.id %>">
21
+ <span class="os-term-section"><%= term.page_title %></span>
22
+ </a>
23
+ <% end %>
24
+ </div>
25
+ <% end %>
26
+ </div>
27
+ <% end %>
28
+ </div>
@@ -0,0 +1,13 @@
1
+ module Kitchen
2
+ module Directions
3
+ module BakeMathInParagraph
4
+
5
+ def self.v1(book:)
6
+ book.search("p m|math").each do |math|
7
+ math.wrap("<span class='os-math-in-para'>")
8
+ end
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,58 @@
1
+ module Kitchen
2
+ module Directions
3
+ module BakeNotes
4
+
5
+ def self.v1(book:)
6
+ book.notes.each do |note|
7
+ title = note.title&.cut
8
+
9
+ note.replace_children(with:
10
+ <<~HTML
11
+ <div class="os-note-body">#{note.children}</div>
12
+ HTML
13
+ )
14
+
15
+ if title
16
+ if note.indicates_autogenerated_title?
17
+ note.prepend(child:
18
+ <<~HTML
19
+ <h3 class="os-title" data-type="title">
20
+ <span class="os-title-label">#{note.autogenerated_title}</span>
21
+ </h3>
22
+ HTML
23
+ )
24
+
25
+ title.name = "h4"
26
+ title.add_class("os-subtitle")
27
+ title.replace_children(with:
28
+ <<~HTML
29
+ <span class="os-subtitle-label">#{title.children}</span>
30
+ HTML
31
+ )
32
+ note.first!(".os-note-body").prepend(child: title.raw)
33
+ else
34
+ title.name = "h3"
35
+ title.add_class("os-title")
36
+ title.replace_children(with:
37
+ <<~HTML
38
+ <span data-type="" id="#{title.id}" class="os-title-label">#{title.children}</span>
39
+ HTML
40
+ )
41
+ title.remove_attribute("id")
42
+ note.prepend(child: title.raw)
43
+ end
44
+ else
45
+ note.prepend(child:
46
+ <<~HTML
47
+ <h3 class="os-title" data-type="title">
48
+ <span class="os-title-label">#{note.autogenerated_title}</span>
49
+ </h3>
50
+ HTML
51
+ )
52
+ end
53
+ end
54
+ end
55
+
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,11 @@
1
+ module Kitchen
2
+ module Directions
3
+ module BakeNumberedTable
4
+
5
+ def self.v1(table:, number:)
6
+ V1.new.bake(table: table, number: number)
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,47 @@
1
+ module Kitchen::Directions::BakeNumberedTable
2
+ class V1
3
+
4
+ def bake(table:, number:)
5
+ table.wrap(%Q(<div class="os-table">))
6
+
7
+ table_label = "#{I18n.t(:table_label)} #{number}"
8
+ table.document.pantry(name: :link_text).store table_label, label: table.id
9
+
10
+ if table.top_titled?
11
+ table.prepend(sibling:
12
+ <<~HTML
13
+ <div class="os-table-title">#{table.title}</div>
14
+ HTML
15
+ )
16
+ table.title_row.trash
17
+ end
18
+
19
+ # TODO extra spaces added here to match legacy implementation, but probably not meaningful?
20
+ new_summary = table_label + " "
21
+ new_caption = ""
22
+
23
+ if (caption = table.caption&.cut)
24
+ new_summary += caption.text
25
+ new_caption = <<~HTML
26
+ \n<span class="os-caption">#{caption.children}</span>
27
+ HTML
28
+ end
29
+
30
+ table[:summary] = new_summary
31
+
32
+ if !table.unnumbered?
33
+ table.append(sibling:
34
+ <<~HTML
35
+ <div class="os-caption-container">
36
+ <span class="os-title-label">#{I18n.t(:table_label)} </span>
37
+ <span class="os-number">#{number}</span>
38
+ <span class="os-divider"> </span>
39
+ <span class="os-divider"> </span>#{new_caption}
40
+ </div>
41
+ HTML
42
+ )
43
+ end
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,27 @@
1
+ module Kitchen
2
+ module Directions
3
+ module BakeStepwise
4
+
5
+ def self.v1(book:)
6
+ book.search("ol.stepwise").each do |ol|
7
+ ol.remove_class("stepwise")
8
+ ol.add_class("os-stepwise")
9
+
10
+ ol.search("li").each_with_index do |li, ii|
11
+ li.replace_children(with:
12
+ <<~HTML
13
+ <span class="os-stepwise-content">#{li.children}</span>
14
+ HTML
15
+ )
16
+ li.prepend(child:
17
+ <<~HTML
18
+ <span class="os-stepwise-token">#{I18n.t(:stepwise_step_label)} #{ii+1}. </span>
19
+ HTML
20
+ )
21
+ end
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,103 @@
1
+ module Kitchen
2
+ module Directions
3
+ module BakeToc
4
+
5
+ def self.v1(book:)
6
+ li_tags = book.body.element_children.map do |element|
7
+ case element
8
+ when ChapterElement
9
+ li_for_chapter(element)
10
+ when PageElement, CompositePageElement
11
+ li_for_page(element)
12
+ when CompositeChapterElement
13
+ li_for_composite_chapter(element)
14
+ end
15
+ end.compact.join("\n")
16
+
17
+ book.first!("nav").replace_children(with: <<~HTML
18
+ <h1 class="os-toc-title">Contents</h1>
19
+ <ol>
20
+ #{li_tags}
21
+ </ol>
22
+ HTML
23
+ )
24
+ end
25
+
26
+ def self.li_for_composite_chapter(chapter)
27
+ pages = chapter.element_children.only(CompositePageElement)
28
+
29
+ <<~HTML
30
+ <li class="os-toc-composite-chapter" cnx-archive-shortid="" cnx-archive-uri="">
31
+ <a href="##{chapter.title.id}">
32
+ #{chapter.title.children.to_s}
33
+ </a>
34
+ <ol class="os-chapter">
35
+ #{pages.map{|page| li_for_page(page)}.join("\n")}
36
+ </ol>
37
+ </li>
38
+ HTML
39
+ end
40
+
41
+ def self.li_for_chapter(chapter)
42
+ pages = chapter.element_children.only(PageElement, CompositePageElement)
43
+
44
+ <<~HTML
45
+ <li class="os-toc-chapter" cnx-archive-shortid="" cnx-archive-uri="">
46
+ <a href="##{chapter.title.id}">
47
+ <span class="os-number"><span class="os-part-text">Chapter </span>#{chapter.count_in(:book)}</span>
48
+ <span class="os-divider"> </span>
49
+ <span class="os-text" data-type="" itemprop="">#{chapter.title.first!(".os-text").text}</span>
50
+ </a>
51
+ <ol class="os-chapter">
52
+ #{pages.map{|page| li_for_page(page)}.join("\n")}
53
+ </ol>
54
+ </li>
55
+ HTML
56
+ end
57
+
58
+ def self.li_for_page(page)
59
+ li_class =
60
+ if page.is_a?(PageElement)
61
+ if page.has_ancestor?(:chapter)
62
+ "os-toc-chapter-page"
63
+ elsif page.is_appendix?
64
+ "os-toc-appendix"
65
+ elsif page.is_preface?
66
+ "os-toc-preface"
67
+ else
68
+ raise "do not know what TOC class to use for page with classes #{page.classes}"
69
+ end
70
+ elsif page.is_a?(CompositePageElement)
71
+ if page.is_index?
72
+ "os-toc-index"
73
+ elsif page.has_ancestor?(:composite_chapter) || page.has_ancestor?(:chapter)
74
+ "os-toc-chapter-composite-page"
75
+ else
76
+ raise "do not know what TOC class to use for page with classes #{page.classes}"
77
+ end
78
+ else
79
+ raise(ArgumentError, "don't know how to put `#{page.class}` into the TOC")
80
+ end
81
+
82
+ title = page.title.copy
83
+
84
+ # The part text gets inserted as a child to the number span
85
+ part_text = title.first(".os-part-text")
86
+ number = title.first(".os-number")
87
+ if part_text && number
88
+ part_text = part_text.cut
89
+ number.prepend(child: part_text.paste)
90
+ end
91
+
92
+ <<~HTML
93
+ <li class="#{li_class}" cnx-archive-shortid="" cnx-archive-uri="#{page.id}">
94
+ <a href="##{page.id}">
95
+ #{title.element_children.copy.paste}
96
+ </a>
97
+ </li>
98
+ HTML
99
+ end
100
+
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,14 @@
1
+ module Kitchen
2
+ module Directions
3
+ module BakeUnnumberedTables
4
+
5
+ def self.v1(book:)
6
+ book.tables("$.unnumbered").each do |table|
7
+ table.wrap(%Q(<div class="os-table">))
8
+ table.remove_attribute("summary")
9
+ end
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module Kitchen
2
+ module Directions
3
+ module MoveTitleTextIntoSpan
4
+
5
+ def self.v1(title:)
6
+ title.replace_children(with:
7
+ <<~HTML
8
+ <span data-type="" itemprop="" class="os-text">#{title.text}</span>
9
+ HTML
10
+ )
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,142 @@
1
+ require 'forwardable'
2
+
3
+ module Kitchen
4
+ class Document
5
+ extend Forwardable
6
+
7
+ attr_accessor :location
8
+ attr_reader :config
9
+
10
+ def_delegators :config, :selectors
11
+ def_delegators :@nokogiri_document, :to_xhtml, :to_s, :to_xml, :to_html
12
+
13
+ def initialize(nokogiri_document:, config: nil)
14
+ @nokogiri_document = nokogiri_document
15
+ @location = nil
16
+ @config = config || Config.new_default
17
+ @next_paste_count_for_id = {}
18
+ @id_copy_suffix = "_copy_"
19
+ end
20
+
21
+ # Returns an enumerator that iterates over all children of this document
22
+ # that match the provided selector or XPath arguments.
23
+ #
24
+ # @param selector_or_xpath_args [Array<String>] CSS selectors or XPath arguments
25
+ # @return [ElementEnumerator]
26
+ #
27
+ def search(*selector_or_xpath_args)
28
+ selector_or_xpath_args = [selector_or_xpath_args].flatten
29
+
30
+ ElementEnumerator.new do |block|
31
+ 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))
35
+ self.location = element
36
+ block.yield(element)
37
+ end
38
+ end
39
+ end
40
+
41
+ # Returns the document's clipboard with the given name.
42
+ #
43
+ # @param name [Symbol, String] the name of the clipboard. String values are
44
+ # converted to symbols.
45
+ # @return [Clipboard]
46
+ #
47
+ def clipboard(name: :default)
48
+ (@clipboards ||= {})[name.to_sym] ||= Clipboard.new
49
+ end
50
+
51
+ # Returns the document's pantry with the given name.
52
+ #
53
+ # @param name [Symbol, String] the name of the pantry. String values are
54
+ # converted to symbols.
55
+ # @return [Pantry]
56
+ #
57
+ def pantry(name: :default)
58
+ (@pantries ||= {})[name.to_sym] ||= Pantry.new
59
+ end
60
+
61
+ # Returns the document's counter with the given name.
62
+ #
63
+ # @param name [Symbol, String] the name of the counter. String values are
64
+ # converted to symbols.
65
+ # @return [Counter]
66
+ #
67
+ def counter(name)
68
+ (@counters ||= {})[name.to_sym] ||= Counter.new
69
+ end
70
+
71
+ # Create a new Element
72
+ #
73
+ # TODO don't know if we need this
74
+ #
75
+ # @param name [String] the tag name
76
+ # @param args [Array<String, Hash>]
77
+ #
78
+ # @example div with a class
79
+ # create_element("div", class: "foo") #=> <div class="foo"></div>
80
+ # @example div with some content and a class
81
+ # cretae_element("div", "contents", class: "foo") #=> <div class="foo">contents</div>
82
+ # @example div created by block
83
+ # create_element("div") {|elem| elem['class'] = 'foo'} #=> <div class="foo"></div>
84
+ #
85
+ # @return [Element]
86
+ #
87
+ def create_element(name, *args, &block)
88
+ Kitchen::Element.new(
89
+ node: @nokogiri_document.create_element(name, *args, &block),
90
+ document: self,
91
+ short_type: "created_element_#{SecureRandom.hex(4)}"
92
+ )
93
+ end
94
+
95
+ 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
101
+ end
102
+
103
+ def record_id_copied(id)
104
+ return if id.blank?
105
+ @next_paste_count_for_id[id] ||= 1
106
+ end
107
+
108
+ def modified_id_to_paste(original_id)
109
+ return nil if original_id.nil?
110
+ return "" if original_id.blank?
111
+
112
+ count = next_count_for_pasted_id(original_id)
113
+
114
+ # A count of 0 means the element was cut and this is the first paste, do not
115
+ # modify the ID; otherwise, use the uniquified ID.
116
+ if count == 0
117
+ original_id
118
+ else
119
+ "#{original_id}#{@id_copy_suffix}#{count}"
120
+ end
121
+ end
122
+
123
+ # Returns the underlying Nokogiri Document object
124
+ #
125
+ # @return [Nokogiri::XML::Document]
126
+ def raw
127
+ @nokogiri_document
128
+ end
129
+
130
+ protected
131
+
132
+ def next_count_for_pasted_id(id)
133
+ return if id.blank?
134
+ (@next_paste_count_for_id[id] ||= 0).tap do
135
+ @next_paste_count_for_id[id] += 1
136
+ end
137
+ end
138
+
139
+ attr_reader :nokogiri_document
140
+
141
+ end
142
+ end