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,51 @@
1
+ module Kitchen
2
+ class PageElement < ElementBase
3
+
4
+ def initialize(node:, document: nil)
5
+ super(node: node,
6
+ document: document,
7
+ enumerator_class: PageElementEnumerator,
8
+ short_type: :page)
9
+ end
10
+
11
+ def title
12
+ # The selector for intro titles changes during the baking process
13
+ first!(is_introduction? ?
14
+ selectors.title_in_introduction_page :
15
+ selectors.title_in_page)
16
+ end
17
+
18
+ def is_introduction?
19
+ has_class?("introduction")
20
+ end
21
+
22
+ def is_preface?
23
+ has_class?("preface")
24
+ end
25
+
26
+ def is_appendix?
27
+ has_class?("appendix")
28
+ end
29
+
30
+ def metadata
31
+ first!("div[data-type='metadata']")
32
+ end
33
+
34
+ def summary
35
+ first!("section.summary")
36
+ end
37
+
38
+ def exercises
39
+ first!("section.exercises")
40
+ end
41
+
42
+ def exercises_section
43
+ search("")
44
+ end
45
+
46
+ def self.is_the_element_class_for?(node)
47
+ node['data-type'] == "page"
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,13 @@
1
+ module Kitchen
2
+ class PageElementEnumerator < ElementEnumeratorBase
3
+
4
+ def self.factory
5
+ ElementEnumeratorFactory.new(
6
+ default_css_or_xpath: "div[data-type='page']", # TODO get from config?
7
+ sub_element_class: PageElement,
8
+ enumerator_class: self
9
+ )
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,35 @@
1
+ module Kitchen
2
+ # A place to store labeled items during recipe work. Essentially, a slightly
3
+ # improved hash.
4
+ #
5
+ class Pantry
6
+ include Enumerable
7
+
8
+ def store(item, label:)
9
+ @hash[label.to_sym] = item
10
+ end
11
+
12
+ def get(label)
13
+ @hash[label.to_sym]
14
+ end
15
+
16
+ def get!(label)
17
+ get(label) || raise(RecipeError, "There is no pantry item labeled '#{label}'")
18
+ end
19
+
20
+ def each(&block)
21
+ @hash.each{|k,v| block.call(k,v)}
22
+ end
23
+
24
+ def size
25
+ @hash.keys.size
26
+ end
27
+
28
+ protected
29
+
30
+ def initialize
31
+ @hash = {}
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,31 @@
1
+ # Make debug output more useful (dumping entire document out is not useful)
2
+ module Nokogiri
3
+ module XML
4
+ class Document
5
+ def inspect
6
+ "Nokogiri::XML::Document <hidden for brevity>"
7
+ end
8
+
9
+ def alphabetize_attributes!
10
+ traverse do |child|
11
+ next if child.text? || child.document?
12
+ child_attributes = child.attributes
13
+ child_attributes.each do |key, value|
14
+ child.remove_attribute(key)
15
+ end
16
+ sorted_keys = child_attributes.keys.sort
17
+ sorted_keys.each do |key|
18
+ value = child_attributes[key].to_s
19
+ child[key] = value
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ class Node
26
+ def inspect
27
+ to_s
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ class Object
2
+
3
+ # Adds a `render` method to a class for rendering an ERB template to a string.
4
+ def self.renderable(dir: nil)
5
+ dir ||= begin
6
+ this_patch_file = __FILE__
7
+ this_patch_file_caller_index = caller_locations.find_index do |location|
8
+ location.absolute_path == this_patch_file
9
+ end
10
+
11
+ location_that_called_renderable = caller_locations[(this_patch_file_caller_index || -1)+1]
12
+ File.dirname(location_that_called_renderable.path)
13
+ end
14
+
15
+ class_eval <<~METHOD
16
+ def renderable_base_dir
17
+ "#{dir}"
18
+ end
19
+ METHOD
20
+
21
+ class_eval do
22
+ def render(file:)
23
+ file = File.absolute_path(file, renderable_base_dir)
24
+ template = File.open(file, 'rb', &:read)
25
+ ERB.new(template).result(binding)
26
+ end
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,5 @@
1
+ class String
2
+ def uncapitalize
3
+ sub(/^[A-Z]/, &:downcase)
4
+ end
5
+ end
@@ -0,0 +1,78 @@
1
+ module Kitchen
2
+ class Recipe
3
+
4
+ attr_reader :document
5
+ attr_reader :source_location
6
+
7
+ def document=(document)
8
+ @document =
9
+ case document
10
+ when Kitchen::Document
11
+ document
12
+ else
13
+ raise "Unsupported document type `#{document.class}`"
14
+ end
15
+ end
16
+
17
+ # Make a new Recipe
18
+ #
19
+ # @yieldparam doc [Document] an object representing an XML document
20
+ #
21
+ def initialize(&block)
22
+ @source_location = block.source_location[0]
23
+ @block = block
24
+ end
25
+
26
+ def node!
27
+ node || raise("The recipe's node has not been set")
28
+ end
29
+
30
+ def bake
31
+ begin
32
+ @block.to_proc.call(document)
33
+ rescue RecipeError => ee
34
+ print_recipe_error_and_exit(ee)
35
+ rescue ArgumentError => ee
36
+ if if_any_stack_file_matches_source_location?(ee)
37
+ print_recipe_error_and_exit(ee)
38
+ else
39
+ raise
40
+ end
41
+ rescue NoMethodError => ee
42
+ if if_any_stack_file_matches_source_location?(ee)
43
+ print_recipe_error_and_exit(ee)
44
+ else
45
+ raise
46
+ end
47
+ rescue NameError => ee
48
+ if if_stack_starts_with_source_location?(ee)
49
+ print_recipe_error_and_exit(ee)
50
+ else
51
+ raise
52
+ end
53
+ rescue ElementNotFoundError => ee
54
+ print_recipe_error_and_exit(ee)
55
+ rescue Nokogiri::CSS::SyntaxError => ee
56
+ print_recipe_error_and_exit(ee)
57
+ end
58
+ end
59
+
60
+ protected
61
+
62
+ def if_stack_starts_with_source_location?(error)
63
+ error.backtrace.first.start_with?(source_location)
64
+ end
65
+
66
+ def if_any_stack_file_matches_source_location?(error)
67
+ error.backtrace.any? {|entry| entry.start_with?(@source_location)}
68
+ end
69
+
70
+ def print_recipe_error_and_exit(error)
71
+ Kitchen::Debug.print_recipe_error(error: error,
72
+ source_location: source_location,
73
+ document: document)
74
+ exit(1)
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,33 @@
1
+ module Kitchen
2
+ class SearchHistory
3
+ attr_reader :latest
4
+ attr_reader :upstream
5
+
6
+ def self.empty
7
+ new
8
+ end
9
+
10
+ def add(css_or_xpath)
11
+ self.class.new(self, css_or_xpath.nil? ? nil : [css_or_xpath].join(", "))
12
+ end
13
+
14
+ def to_s(missing_string="?")
15
+ to_a.map{|item| "[#{item || missing_string}]"}.join(" ")
16
+ end
17
+
18
+ def to_a
19
+ empty? ? [] : [upstream&.to_a || [], latest].flatten
20
+ end
21
+
22
+ def empty?
23
+ upstream.nil? && latest.nil?
24
+ end
25
+
26
+ protected
27
+
28
+ def initialize(upstream=nil, latest=nil)
29
+ @upstream = upstream
30
+ @latest = latest
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,8 @@
1
+ module Kitchen
2
+ module Selectors
3
+ class Base
4
+ attr_accessor :title_in_page
5
+ attr_accessor :title_in_introduction_page
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ module Kitchen
2
+ module Selectors
3
+ class Standard1 < Base
4
+
5
+ def initialize
6
+ self.title_in_page = "./*[@data-type = 'document-title']"
7
+ self.title_in_introduction_page = "./*[@data-type = 'document-title']"
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,36 @@
1
+ module Kitchen
2
+ class TableElement < ElementBase
3
+
4
+ def initialize(node:, document: nil)
5
+ super(node: node,
6
+ document: document,
7
+ enumerator_class: TableElementEnumerator,
8
+ short_type: :table)
9
+ end
10
+
11
+ def title_row
12
+ top_titled? ? first('thead').first('tr') : nil
13
+ end
14
+
15
+ def title
16
+ title_row&.first('th').children
17
+ end
18
+
19
+ def top_titled?
20
+ has_class?('top-titled')
21
+ end
22
+
23
+ def unnumbered?
24
+ has_class?('unnumbered')
25
+ end
26
+
27
+ def caption
28
+ first("caption")
29
+ end
30
+
31
+ def self.is_the_element_class_for?(node)
32
+ node.name == "table"
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,13 @@
1
+ module Kitchen
2
+ class TableElementEnumerator < ElementEnumeratorBase
3
+
4
+ def self.factory
5
+ ElementEnumeratorFactory.new(
6
+ default_css_or_xpath: "table", # TODO get from config?
7
+ sub_element_class: TableElement,
8
+ enumerator_class: self
9
+ )
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ module Kitchen
2
+ class TermElement < ElementBase
3
+
4
+ def initialize(node:, document: nil)
5
+ super(node: node,
6
+ document: document,
7
+ enumerator_class: TermElementEnumerator,
8
+ short_type: :term)
9
+ end
10
+
11
+ def self.is_the_element_class_for?(node)
12
+ node['data-type'] == "term"
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ module Kitchen
2
+ class TermElementEnumerator < ElementEnumeratorBase
3
+
4
+ def self.factory
5
+ ElementEnumeratorFactory.new(
6
+ default_css_or_xpath: "span[data-type='term']", # TODO get from config?
7
+ sub_element_class: TermElement,
8
+ enumerator_class: self
9
+ )
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ module Kitchen
2
+ # These are added to every translation locale, including the `test` locale
3
+ # set by `stub_locales`. When we sort strings with accent marks, we use
4
+ # `ActiveSupport::Inflector.transliterate` to ensure that the sorting is
5
+ # sensible. This method does not know about Greek characters by default so
6
+ # we teach it about them by adding the rules below to the i18n configuration.
7
+
8
+ TRANSLITERATIONS = {
9
+ i18n: {
10
+ transliterate: {
11
+ rule: {
12
+ σ: "σ",
13
+ Δ: "Δ",
14
+ π: "π",
15
+ }
16
+ }
17
+ }
18
+ }
19
+ end
@@ -0,0 +1,23 @@
1
+ module Kitchen
2
+ class TypeCastingElementEnumerator < ElementEnumeratorBase
3
+
4
+ def self.factory
5
+ ElementEnumeratorFactory.new(
6
+ enumerator_class: self,
7
+ detect_sub_element_class: true
8
+ )
9
+ end
10
+
11
+ def only(*element_classes)
12
+ element_classes.flatten!
13
+
14
+ TypeCastingElementEnumerator.new do |block|
15
+ self.each do |element|
16
+ next unless element_classes.include?(element.class)
17
+ block.yield(element)
18
+ end
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ module Kitchen
2
+ module Utils
3
+
4
+ def self.search_path_to_type(search_path)
5
+ [search_path].flatten.join(",")
6
+ end
7
+
8
+ def self.normalized_xhtml_string(xml_thing_with_to_s)
9
+ doc = Nokogiri::XML(xml_thing_with_to_s.to_s) do |config|
10
+ config.noblanks
11
+ end
12
+
13
+ doc.alphabetize_attributes!
14
+
15
+ doc.to_xhtml(indent: 2)
16
+ end
17
+
18
+ end
19
+ end