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,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