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,35 @@
1
+ module Kitchen
2
+ class ChapterElement < ElementBase
3
+
4
+ def initialize(node:, document: nil)
5
+ super(node: node,
6
+ document: document,
7
+ enumerator_class: ChapterElementEnumerator,
8
+ short_type: :chapter)
9
+ end
10
+
11
+ def title
12
+ # Get the title in the immediate children, not the one in the metadata. Could use
13
+ # CSS of ":not([data-type='metadata']) > [data-type='document-title'], [data-type='document-title']"
14
+ # but xpath is shorter
15
+ first!("./*[@data-type = 'document-title']")
16
+ end
17
+
18
+ def introduction_page
19
+ pages('.introduction').first
20
+ end
21
+
22
+ def glossaries
23
+ search("div[data-type='glossary']")
24
+ end
25
+
26
+ def key_equations
27
+ search("section.key-equations")
28
+ end
29
+
30
+ def self.is_the_element_class_for?(node)
31
+ node['data-type'] == "chapter"
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,13 @@
1
+ module Kitchen
2
+ class ChapterElementEnumerator < ElementEnumeratorBase
3
+
4
+ def self.factory
5
+ ElementEnumeratorFactory.new(
6
+ default_css_or_xpath: "div[data-type='chapter']", # TODO element.document.selectors.chapter
7
+ sub_element_class: ChapterElement,
8
+ enumerator_class: self
9
+ )
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,37 @@
1
+ module Kitchen
2
+ # A place to store lists of things during recipe work. Essentially a
3
+ # slightly fancy array.
4
+ #
5
+ class Clipboard
6
+ include Enumerable
7
+
8
+ attr_reader :items
9
+
10
+ def initialize
11
+ clear
12
+ end
13
+
14
+ def add(item)
15
+ @items.push(item)
16
+ end
17
+
18
+ def clear
19
+ @items = []
20
+ end
21
+
22
+ def paste
23
+ @items.map(&:paste).join("")
24
+ end
25
+
26
+ def each(&block)
27
+ @items.each do |item|
28
+ block.call(item)
29
+ end
30
+ self
31
+ end
32
+
33
+ def sort_by!(&block)
34
+ @items.sort_by!(&block)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,23 @@
1
+ module Kitchen
2
+ class CompositeChapterElement < ElementBase
3
+
4
+ def initialize(node:, document: nil)
5
+ super(node: node,
6
+ document: document,
7
+ enumerator_class: ElementEnumerator,
8
+ short_type: :composite_chapter)
9
+ end
10
+
11
+ def title
12
+ # Get the title in the immediate children, not the one in the metadata. Could use
13
+ # CSS of ":not([data-type='metadata']) > [data-type='document-title'], [data-type='document-title']"
14
+ # but xpath is shorter
15
+ first!("./*[@data-type = 'document-title']")
16
+ end
17
+
18
+ def self.is_the_element_class_for?(node)
19
+ node['data-type'] == "composite-chapter"
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ module Kitchen
2
+ class CompositePageElement < ElementBase
3
+
4
+ def initialize(node:, document: nil)
5
+ super(node: node,
6
+ document: document,
7
+ enumerator_class: CompositePageElementEnumerator,
8
+ short_type: :composite_page)
9
+ end
10
+
11
+ def title
12
+ # Get the title in the immediate children, not the one in the metadata. Could use
13
+ # CSS of ":not([data-type='metadata']) > [data-type='document-title'], [data-type='document-title']"
14
+ # but xpath is shorter
15
+ first!("./*[@data-type = 'document-title']")
16
+ end
17
+
18
+ def self.is_the_element_class_for?(node)
19
+ node['data-type'] == "composite-page"
20
+ end
21
+
22
+ def is_index?
23
+ has_class?("os-index-container")
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ module Kitchen
2
+ class CompositePageElementEnumerator < ElementEnumeratorBase
3
+
4
+ def self.factory
5
+ ElementEnumeratorFactory.new(
6
+ default_css_or_xpath: "div[data-type='composite-page']",
7
+ sub_element_class: CompositePageElement,
8
+ enumerator_class: self
9
+ )
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ module Kitchen
2
+ class Config
3
+
4
+ attr_reader :selectors
5
+
6
+ def self.new_from_file(file)
7
+ raise "NYI"
8
+ end
9
+
10
+ def self.new_default
11
+ new
12
+ end
13
+
14
+ def initialize(hash: {}, selectors: nil)
15
+ @selectors = selectors || Kitchen::Selectors::Standard1.new
16
+ @hash = hash
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,34 @@
1
+ module Kitchen
2
+ class Counter # hehe
3
+
4
+ def initialize
5
+ reset
6
+ end
7
+
8
+ # Increase the value of the counter
9
+ #
10
+ # @param by [Integer] the amount to increase by
11
+ #
12
+ def increment(by: 1)
13
+ @value += by
14
+ end
15
+
16
+ # (see #increment)
17
+ alias_method :inc, :increment
18
+
19
+ # Returns the value of the counter
20
+ #
21
+ # @return [Integer]
22
+ def get
23
+ @value
24
+ end
25
+
26
+ # Reset the value of the counter
27
+ #
28
+ # @param to [Integer] the value to reset to
29
+ def reset(to: 0)
30
+ @value = to
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,80 @@
1
+ require 'rainbow'
2
+
3
+ module Kitchen
4
+ module Debug
5
+
6
+ CONTEXT_LINES = 2
7
+
8
+ def self.print_recipe_error(error:, source_location:, document:)
9
+ error_location = error.backtrace.detect do |entry|
10
+ entry.start_with?(source_location) || entry.match?(/kitchen\/lib\/kitchen\/directions/)
11
+ end
12
+
13
+ error_filename, error_line_number = error_location.match(/(.*):(\d+):/)[1..2]
14
+ error_line_number = error_line_number.to_i
15
+
16
+ puts "The recipe has an error: " + Rainbow(error.message).bright
17
+ puts "at or near the following #{Rainbow('highlighted').red} line"
18
+ puts "\n"
19
+ puts "-----+ #{Rainbow(error_filename).bright} -----"
20
+
21
+ line_numbers_before = (error_line_number-CONTEXT_LINES..error_line_number-1)
22
+ line_numbers_after = (error_line_number+1..error_line_number+CONTEXT_LINES)
23
+
24
+ File.readlines(error_filename).each.with_index do |line, line_index|
25
+ line_number = line_index + 1
26
+
27
+ if line_numbers_before.include?(line_number) ||
28
+ line_numbers_after.include?(line_number)
29
+ print_file_line(line_number, line)
30
+ end
31
+
32
+ if line_number == error_line_number
33
+ print_file_line(line_number, Rainbow(line.chomp).red)
34
+ end
35
+ end
36
+
37
+ puts "\n"
38
+
39
+ print_specific_help_line(error)
40
+
41
+ current_node = document.location&.raw
42
+
43
+ if current_node
44
+ puts "Encountered on line #{Rainbow(current_node.line).red} in the input document on element:"
45
+ puts current_node.dup.tap{|node| node.inner_html="..." if node.inner_html != ""}.to_s
46
+ puts "\n"
47
+ end
48
+
49
+ if ENV['VERBOSE']
50
+ puts "Full backtrace:\n"
51
+ puts error.backtrace.map{|line| Rainbow(line).blue}
52
+ else
53
+ puts "Full backtrace suppressed (enable by setting the VERBOSE environment variable to something)"
54
+ end
55
+ end
56
+
57
+ protected
58
+
59
+ def self.print_file_line(line_number, line)
60
+ puts "#{"%5s" % line_number}| #{line}"
61
+ end
62
+
63
+ def self.print_specific_help_line(error)
64
+ # No specific help lines at the moment, an example is shown for the future
65
+
66
+ # help_line = case error.message
67
+ # when /some string in error/
68
+ # "`foo` needs to be called on a Bar object"
69
+ # else
70
+ # nil
71
+ # end
72
+
73
+ # if !help_line.nil?
74
+ # puts help_line
75
+ # puts "\n"
76
+ # end
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,26 @@
1
+ module Kitchen
2
+ module Directions
3
+ module BakeAppendix
4
+
5
+ def self.v1(page:, number:)
6
+ title = page.title
7
+ title.name = "h1"
8
+ title.replace_children(with:
9
+ <<~HTML
10
+ <span class="os-part-text">#{I18n.t(:appendix)} </span>
11
+ <span class="os-number">#{number}</span>
12
+ <span class="os-divider"> </span>
13
+ <span data-type="" itemprop="" class="os-text">#{title.children}</span>
14
+ HTML
15
+ )
16
+
17
+ # Make a section with data-depth of X have a header level of X+1
18
+ page.search("section").each do |section|
19
+ title = section.first!("[data-type='title']")
20
+ title.name = "h#{section['data-depth'].to_i + 1}"
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,34 @@
1
+ module Kitchen
2
+ module Directions
3
+ module BakeChapterGlossary
4
+
5
+ def self.v1(chapter:, metadata_source:)
6
+ metadata_elements = metadata_source.search(%w(.authors .publishers .print-style
7
+ .permissions [data-type='subject'])).copy
8
+
9
+ definitions = chapter.glossaries.search("dl").cut
10
+ definitions.sort_by! do |definition|
11
+ [definition.first("dt").text.downcase, definition.first("dd").text.downcase]
12
+ end
13
+
14
+ chapter.glossaries.trash
15
+
16
+ chapter.append(child:
17
+ <<~HTML
18
+ <div class="os-eoc os-glossary-container" data-type="composite-page" data-uuid-key="glossary">
19
+ <h2 data-type="document-title">
20
+ <span class="os-text">#{I18n.t(:eoc_key_terms_title)}</span>
21
+ </h2>
22
+ <div data-type="metadata" style="display: none;">
23
+ <h1 data-type="document-title" itemprop="name">#{I18n.t(:eoc_key_terms_title)}</h1>
24
+ #{metadata_elements.paste}
25
+ </div>
26
+ #{definitions.paste}
27
+ </div>
28
+ HTML
29
+ ) unless definitions.none?
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,58 @@
1
+ module Kitchen
2
+ module Directions
3
+ module BakeChapterIntroductions
4
+
5
+ def self.v1(book:)
6
+ book.chapters.each do |chapter|
7
+ outline_items_html = chapter.pages.map do |page|
8
+ next if page.is_introduction?
9
+
10
+ <<~HTML
11
+ <div class="os-chapter-objective">
12
+ <a class="os-chapter-objective" href="##{page.title[:id]}">
13
+ <span class="os-number">#{chapter.count_in(:book)}.#{page.count_in(:chapter)-1}</span>
14
+ <span class="os-divider"> </span>
15
+ <span data-type="" itemprop="" class="os-text">#{page.title.children[0].text}</span>
16
+ </a>
17
+ </div>
18
+ HTML
19
+ end.join("")
20
+
21
+ introduction_page = chapter.introduction_page
22
+
23
+ introduction_page.search("div[data-type='description']").trash
24
+ introduction_page.search("div[data-type='abstract']").trash
25
+
26
+ title = introduction_page.title.cut
27
+ title.name = "h2"
28
+ MoveTitleTextIntoSpan.v1(title: title)
29
+
30
+ intro_content = introduction_page.search("> :not([data-type='metadata']):not(figure)").cut
31
+
32
+ introduction_page.append(child:
33
+ <<~HTML
34
+ <div class="intro-body">
35
+ <div class="os-chapter-outline">
36
+ <h3 class="os-title">Chapter Outline</h3>
37
+ #{outline_items_html}
38
+ </div>
39
+ <div class="intro-text">
40
+ #{title.paste}
41
+ #{intro_content.paste}
42
+ </div>
43
+ </div>
44
+ HTML
45
+ )
46
+ end
47
+
48
+ v1_update_selectors(book)
49
+ end
50
+
51
+ def self.v1_update_selectors(something_with_selectors)
52
+ something_with_selectors.selectors.title_in_introduction_page =
53
+ ".intro-text > [data-type='document-title']"
54
+ end
55
+
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,30 @@
1
+ module Kitchen
2
+ module Directions
3
+ module BakeChapterKeyEquations
4
+
5
+ def self.v1(chapter:, metadata_source:)
6
+ metadata_elements = metadata_source.search(%w(.authors .publishers .print-style
7
+ .permissions [data-type='subject'])).copy
8
+
9
+ chapter.key_equations.search("h3").trash
10
+ key_equations = chapter.key_equations.cut
11
+
12
+ chapter.append(child:
13
+ <<~HTML
14
+ <div class="os-eoc os-key-equations-container" data-type="composite-page" data-uuid-key=".key-equations">
15
+ <h2 data-type="document-title">
16
+ <span class="os-text">#{I18n.t(:eoc_key_equations)}</span>
17
+ </h2>
18
+ <div data-type="metadata" style="display: none;">
19
+ <h1 data-type="document-title" itemprop="name">#{I18n.t(:eoc_key_equations)}</h1>
20
+ #{metadata_elements.paste}
21
+ </div>
22
+ #{key_equations.paste}
23
+ </div>
24
+ HTML
25
+ ) unless key_equations.none?
26
+ end
27
+
28
+ end
29
+ end
30
+ end