openstax_kitchen 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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