openstax_kitchen 2.0.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/devcontainer.json +37 -17
  3. data/.github/config.yml +14 -0
  4. data/.github/workflows/tests.yml +5 -15
  5. data/.gitignore +1 -1
  6. data/.inch.yml +6 -0
  7. data/.rubocop.yml +65 -0
  8. data/CHANGELOG.md +16 -0
  9. data/Gemfile +5 -3
  10. data/Gemfile.lock +54 -5
  11. data/README.md +58 -11
  12. data/Rakefile +5 -3
  13. data/bin/console +4 -3
  14. data/docker/Dockerfile +36 -0
  15. data/docker/Dockerfile.ci +10 -0
  16. data/docker/bash +5 -1
  17. data/docker/build +10 -0
  18. data/docker/ci +16 -0
  19. data/docker/run +9 -0
  20. data/docker/tag_and_push_latest +17 -0
  21. data/lefthook.yml +6 -0
  22. data/lib/kitchen/ancestor.rb +38 -1
  23. data/lib/kitchen/book_document.rb +20 -2
  24. data/lib/kitchen/book_element.rb +24 -3
  25. data/lib/kitchen/book_element_enumerator.rb +4 -0
  26. data/lib/kitchen/book_recipe.rb +15 -1
  27. data/lib/kitchen/chapter_element.rb +43 -3
  28. data/lib/kitchen/chapter_element_enumerator.rb +9 -1
  29. data/lib/kitchen/clipboard.rb +35 -4
  30. data/lib/kitchen/composite_chapter_element.rb +20 -1
  31. data/lib/kitchen/composite_page_element.rb +25 -2
  32. data/lib/kitchen/composite_page_element_enumerator.rb +8 -0
  33. data/lib/kitchen/config.rb +14 -7
  34. data/lib/kitchen/counter.rb +9 -2
  35. data/lib/kitchen/debug/print_recipe_error.rb +53 -35
  36. data/lib/kitchen/directions/.rubocop.yml +22 -0
  37. data/lib/kitchen/directions/bake_appendix.rb +4 -4
  38. data/lib/kitchen/directions/bake_chapter_glossary.rb +10 -7
  39. data/lib/kitchen/directions/bake_chapter_introductions.rb +6 -6
  40. data/lib/kitchen/directions/bake_chapter_key_equations.rb +9 -6
  41. data/lib/kitchen/directions/bake_chapter_summary.rb +16 -13
  42. data/lib/kitchen/directions/bake_chapter_title/main.rb +11 -0
  43. data/lib/kitchen/directions/bake_chapter_title/v1.rb +24 -0
  44. data/lib/kitchen/directions/bake_composite_pages.rb +2 -2
  45. data/lib/kitchen/directions/bake_example.rb +6 -4
  46. data/lib/kitchen/directions/bake_exercises/main.rb +11 -0
  47. data/lib/kitchen/directions/bake_exercises/v1.rb +166 -0
  48. data/lib/kitchen/directions/bake_figure.rb +8 -5
  49. data/lib/kitchen/directions/bake_footnotes/main.rb +2 -2
  50. data/lib/kitchen/directions/bake_footnotes/v1.rb +4 -4
  51. data/lib/kitchen/directions/bake_index/main.rb +2 -2
  52. data/lib/kitchen/directions/bake_index/v1.rb +22 -15
  53. data/lib/kitchen/directions/bake_link_placeholders.rb +24 -0
  54. data/lib/kitchen/directions/bake_math_in_paragraph.rb +5 -3
  55. data/lib/kitchen/directions/bake_notes.rb +8 -8
  56. data/lib/kitchen/directions/bake_numbered_table/main.rb +2 -2
  57. data/lib/kitchen/directions/bake_numbered_table/v1.rb +21 -16
  58. data/lib/kitchen/directions/bake_page_abstracts.rb +14 -0
  59. data/lib/kitchen/directions/bake_preface/main.rb +11 -0
  60. data/lib/kitchen/directions/bake_preface/v1.rb +18 -0
  61. data/lib/kitchen/directions/bake_stepwise.rb +7 -7
  62. data/lib/kitchen/directions/bake_suggested_reading.rb +26 -0
  63. data/lib/kitchen/directions/bake_toc.rb +41 -22
  64. data/lib/kitchen/directions/bake_unit_title/main.rb +11 -0
  65. data/lib/kitchen/directions/bake_unit_title/v1.rb +23 -0
  66. data/lib/kitchen/directions/bake_unnumbered_tables.rb +7 -5
  67. data/lib/kitchen/directions/move_title_text_into_span.rb +2 -2
  68. data/lib/kitchen/document.rb +72 -13
  69. data/lib/kitchen/element.rb +11 -0
  70. data/lib/kitchen/element_base.rb +276 -56
  71. data/lib/kitchen/element_enumerator.rb +8 -0
  72. data/lib/kitchen/element_enumerator_base.rb +210 -28
  73. data/lib/kitchen/element_enumerator_factory.rb +59 -52
  74. data/lib/kitchen/element_factory.rb +27 -12
  75. data/lib/kitchen/errors.rb +5 -0
  76. data/lib/kitchen/example_element.rb +19 -1
  77. data/lib/kitchen/example_element_enumerator.rb +9 -1
  78. data/lib/kitchen/figure_element.rb +36 -2
  79. data/lib/kitchen/figure_element_enumerator.rb +9 -1
  80. data/lib/kitchen/metadata_element.rb +28 -0
  81. data/lib/kitchen/metadata_element_enumerator.rb +21 -0
  82. data/lib/kitchen/mixins/block_error_if.rb +24 -4
  83. data/lib/kitchen/note_element.rb +37 -7
  84. data/lib/kitchen/note_element_enumerator.rb +9 -1
  85. data/lib/kitchen/oven.rb +66 -15
  86. data/lib/kitchen/page_element.rb +62 -13
  87. data/lib/kitchen/page_element_enumerator.rb +9 -1
  88. data/lib/kitchen/pantry.rb +28 -1
  89. data/lib/kitchen/patches/nokogiri.rb +19 -2
  90. data/lib/kitchen/patches/renderable.rb +9 -3
  91. data/lib/kitchen/patches/string.rb +8 -0
  92. data/lib/kitchen/recipe.rb +38 -34
  93. data/lib/kitchen/search_history.rb +43 -4
  94. data/lib/kitchen/search_query.rb +84 -0
  95. data/lib/kitchen/selectors/base.rb +26 -0
  96. data/lib/kitchen/selectors/standard_1.rb +8 -0
  97. data/lib/kitchen/table_element.rb +54 -3
  98. data/lib/kitchen/table_element_enumerator.rb +9 -1
  99. data/lib/kitchen/term_element.rb +15 -1
  100. data/lib/kitchen/term_element_enumerator.rb +9 -1
  101. data/lib/kitchen/transliterations.rb +7 -5
  102. data/lib/kitchen/type_casting_element_enumerator.rb +17 -1
  103. data/lib/kitchen/unit_element.rb +39 -0
  104. data/lib/kitchen/unit_element_enumerator.rb +20 -0
  105. data/lib/kitchen/utils.rb +10 -13
  106. data/lib/kitchen/version.rb +5 -1
  107. data/lib/locales/en.yml +6 -0
  108. data/lib/openstax_kitchen.rb +43 -42
  109. data/openstax_kitchen.gemspec +26 -20
  110. data/tutorials/00/solution1.rb +9 -0
  111. data/tutorials/00/solution2.rb +8 -0
  112. data/tutorials/01/solution1.rb +18 -0
  113. data/tutorials/01/solution2.rb +26 -0
  114. data/tutorials/02/solution1.rb +31 -0
  115. data/tutorials/03/{solution_1.rb → solution1.rb} +6 -4
  116. data/tutorials/03/solution2.rb +18 -0
  117. data/tutorials/04/{solution_1.rb → solution1.rb} +4 -2
  118. data/tutorials/04/{solution_2.rb → solution2.rb} +6 -4
  119. data/tutorials/05/solution1.rb +11 -0
  120. data/tutorials/check_it +16 -15
  121. data/tutorials/setup_my_recipes +7 -6
  122. metadata +101 -22
  123. data/Dockerfile +0 -19
  124. data/docker-compose.yml +0 -12
  125. data/docker/entrypoint +0 -9
  126. data/lib/kitchen/directions/bake_exercises.rb +0 -164
  127. data/tutorials/00/solution_1.rb +0 -7
  128. data/tutorials/00/solution_2.rb +0 -6
  129. data/tutorials/01/solution_1.rb +0 -16
  130. data/tutorials/01/solution_2.rb +0 -24
  131. data/tutorials/02/solution_1.rb +0 -29
  132. data/tutorials/03/solution_2.rb +0 -15
  133. data/tutorials/05/solution_1.rb +0 -9
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kitchen
2
4
  # A place to store lists of things during recipe work. Essentially a
3
5
  # slightly fancy array.
@@ -5,33 +7,62 @@ module Kitchen
5
7
  class Clipboard
6
8
  include Enumerable
7
9
 
8
- attr_reader :items
10
+ # The underlying array
11
+ # @return [Array<ElementBase>]
12
+ #
13
+ def items
14
+ @items.clone
15
+ end
9
16
 
17
+ # Creates a new +Clipboard+
18
+ #
10
19
  def initialize
11
20
  clear
12
21
  end
13
22
 
23
+ # Add an element to the clipboard
24
+ #
25
+ # @param item [ElementBase]
26
+ #
14
27
  def add(item)
15
28
  @items.push(item)
29
+ self
16
30
  end
17
31
 
32
+ # Clears the clipboard
33
+ #
18
34
  def clear
19
35
  @items = []
36
+ self
20
37
  end
21
38
 
39
+ # Returns a concatenation of the pasting of each item on the clipboard
40
+ # @return [String]
41
+ #
22
42
  def paste
23
- @items.map(&:paste).join("")
43
+ @items.map(&:paste).join('')
24
44
  end
25
45
 
46
+ # Iterates over each item on the clipboard
47
+ # @yield each item
48
+ # @return [Clipboard] self
49
+ #
26
50
  def each(&block)
27
- @items.each do |item|
28
- block.call(item)
51
+ if block_given?
52
+ @items.each do |item|
53
+ block.call(item)
54
+ end
29
55
  end
30
56
  self
31
57
  end
32
58
 
59
+ # Sorts the clipboard using the provided block
60
+ # @yield each item
61
+ # @return [Clipboard] self
62
+ #
33
63
  def sort_by!(&block)
34
64
  @items.sort_by!(&block)
65
+ self
35
66
  end
36
67
  end
37
68
  end
@@ -1,6 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kitchen
4
+ # An element for a composite chapter
5
+ #
2
6
  class CompositeChapterElement < ElementBase
3
7
 
8
+ # Creates a new +CompositeChapterElement+
9
+ #
10
+ # @param node [Nokogiri::XML::Node] the node this element wraps
11
+ # @param document [Document] this element's document
12
+ #
4
13
  def initialize(node:, document: nil)
5
14
  super(node: node,
6
15
  document: document,
@@ -8,6 +17,11 @@ module Kitchen
8
17
  short_type: :composite_chapter)
9
18
  end
10
19
 
20
+ # Returns the title element (the one in the immediate children, not the one in the metadata)
21
+ #
22
+ # @raise [ElementNotFoundError] if no matching element is found
23
+ # @return [Element]
24
+ #
11
25
  def title
12
26
  # Get the title in the immediate children, not the one in the metadata. Could use
13
27
  # CSS of ":not([data-type='metadata']) > [data-type='document-title'], [data-type='document-title']"
@@ -15,8 +29,13 @@ module Kitchen
15
29
  first!("./*[@data-type = 'document-title']")
16
30
  end
17
31
 
32
+ # Returns true if this class represents the element for the given node
33
+ #
34
+ # @param node [Nokogiri::XML::Node] the underlying node
35
+ # @return [Boolean]
36
+ #
18
37
  def self.is_the_element_class_for?(node)
19
- node['data-type'] == "composite-chapter"
38
+ node['data-type'] == 'composite-chapter'
20
39
  end
21
40
 
22
41
  end
@@ -1,6 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kitchen
4
+ # An element for a composite page
5
+ #
2
6
  class CompositePageElement < ElementBase
3
7
 
8
+ # Creates a new +CompositePageElement+
9
+ #
10
+ # @param node [Nokogiri::XML::Node] the node this element wraps
11
+ # @param document [Document] this element's document
12
+ #
4
13
  def initialize(node:, document: nil)
5
14
  super(node: node,
6
15
  document: document,
@@ -8,6 +17,11 @@ module Kitchen
8
17
  short_type: :composite_page)
9
18
  end
10
19
 
20
+ # Returns the title element (the one in the immediate children, not the one in the metadata)
21
+ #
22
+ # @raise [ElementNotFoundError] if no matching element is found
23
+ # @return [Element]
24
+ #
11
25
  def title
12
26
  # Get the title in the immediate children, not the one in the metadata. Could use
13
27
  # CSS of ":not([data-type='metadata']) > [data-type='document-title'], [data-type='document-title']"
@@ -15,12 +29,21 @@ module Kitchen
15
29
  first!("./*[@data-type = 'document-title']")
16
30
  end
17
31
 
32
+ # Returns true if this class represents the element for the given node
33
+ #
34
+ # @param node [Nokogiri::XML::Node] the underlying node
35
+ # @return [Boolean]
36
+ #
18
37
  def self.is_the_element_class_for?(node)
19
- node['data-type'] == "composite-page"
38
+ node['data-type'] == 'composite-page'
20
39
  end
21
40
 
41
+ # Returns true if this page is a book index
42
+ #
43
+ # @return [Boolean]
44
+ #
22
45
  def is_index?
23
- has_class?("os-index-container")
46
+ has_class?('os-index-container')
24
47
  end
25
48
 
26
49
  end
@@ -1,6 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kitchen
4
+ # An enumerator for composite page elements
5
+ #
2
6
  class CompositePageElementEnumerator < ElementEnumeratorBase
3
7
 
8
+ # Returns a factory for this enumerator
9
+ #
10
+ # @return [ElementEnumeratorFactory]
11
+ #
4
12
  def self.factory
5
13
  ElementEnumeratorFactory.new(
6
14
  default_css_or_xpath: "div[data-type='composite-page']",
@@ -1,20 +1,27 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kitchen
4
+ # Kitchen configuration
5
+ #
2
6
  class Config
3
7
 
8
+ # Named CSS or XPath selectors
9
+ #
10
+ # @return [Selectors::Base]
11
+ #
4
12
  attr_reader :selectors
5
13
 
6
- def self.new_from_file(file)
7
- raise "NYI"
8
- end
9
-
10
- def self.new_default
11
- new
14
+ # Creates a new config from a file (not implemented)
15
+ #
16
+ def self.new_from_file(_file)
17
+ raise 'NYI'
12
18
  end
13
19
 
20
+ # Creates a new Config instance
21
+ #
14
22
  def initialize(hash: {}, selectors: nil)
15
23
  @selectors = selectors || Kitchen::Selectors::Standard1.new
16
24
  @hash = hash
17
25
  end
18
-
19
26
  end
20
27
  end
@@ -1,6 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kitchen
2
- class Counter # hehe
4
+ # A simple counting object
5
+ #
6
+ # hehe
7
+ class Counter
3
8
 
9
+ # Creates a new +Counter+ instance
4
10
  def initialize
5
11
  reset
6
12
  end
@@ -13,7 +19,8 @@ module Kitchen
13
19
  @value += by
14
20
  end
15
21
 
16
- # (see #increment)
22
+ # @!method inc
23
+ # @see increment
17
24
  alias_method :inc, :increment
18
25
 
19
26
  # Returns the value of the counter
@@ -1,63 +1,82 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rainbow'
2
4
 
3
5
  module Kitchen
6
+ # Debug helpers
4
7
  module Debug
5
-
6
8
  CONTEXT_LINES = 2
7
9
 
8
10
  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
11
+ print_error_message(error)
12
12
 
13
- error_filename, error_line_number = error_location.match(/(.*):(\d+):/)[1..2]
14
- error_line_number = error_line_number.to_i
13
+ error_line_number, error_filename = get_error_location(error, source_location)
14
+ print_file_line_with_context(error_line_number, error_filename)
15
15
 
16
- puts "The recipe has an error: " + Rainbow(error.message).bright
17
- puts "at or near the following #{Rainbow('highlighted').red} line"
18
16
  puts "\n"
19
- puts "-----+ #{Rainbow(error_filename).bright} -----"
20
17
 
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)
18
+ print_specific_help_line(error)
19
+ print_line_in_document_where_error_occurred(document)
20
+ print_backtrace_info(error)
21
+ end
23
22
 
24
- File.readlines(error_filename).each.with_index do |line, line_index|
25
- line_number = line_index + 1
23
+ def self.verbose?
24
+ !ENV['VERBOSE'].nil?
25
+ end
26
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
27
+ def self.print_error_message(error)
28
+ puts "The recipe has an error: #{Rainbow(error.message).bright}"
29
+ end
31
30
 
32
- if line_number == error_line_number
33
- print_file_line(line_number, Rainbow(line.chomp).red)
34
- end
31
+ def self.print_backtrace_info(error)
32
+ if verbose?
33
+ puts "Full backtrace:\n"
34
+ puts(error.backtrace.map { |line| Rainbow(line).blue })
35
+ else
36
+ puts 'Full backtrace suppressed; enable by setting the VERBOSE env variable to something'
35
37
  end
38
+ end
36
39
 
40
+ def self.print_line_in_document_where_error_occurred(document)
41
+ current_node = document&.location&.raw
42
+ return unless current_node
43
+
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
37
46
  puts "\n"
47
+ end
38
48
 
39
- print_specific_help_line(error)
49
+ def self.print_file_line_with_context(line_number, filename)
50
+ puts "at or near the following #{Rainbow('highlighted').red} line"
51
+ puts "\n"
52
+ puts "-----+ #{Rainbow(filename).bright} -----"
40
53
 
41
- current_node = document.location&.raw
54
+ line_numbers_before = (line_number - CONTEXT_LINES..line_number - 1)
55
+ line_numbers_after = (line_number + 1..line_number + CONTEXT_LINES)
42
56
 
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
57
+ File.readlines(filename).each.with_index do |line, line_index|
58
+ current_line_number = line_index + 1
48
59
 
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)"
60
+ if line_numbers_before.include?(current_line_number) ||
61
+ line_numbers_after.include?(current_line_number)
62
+ print_file_line(current_line_number, line)
63
+ elsif current_line_number == line_number
64
+ print_file_line(current_line_number, Rainbow(line.chomp).red)
65
+ end
54
66
  end
55
67
  end
56
68
 
57
- protected
69
+ def self.get_error_location(error, source_location)
70
+ error_location = error.backtrace.detect do |entry|
71
+ entry.start_with?(source_location) || entry.match?(/kitchen\/lib\/kitchen\/directions/)
72
+ end
73
+
74
+ error_filename, error_line_number = error_location.match(/(.*):(\d+):/)[1..2]
75
+ [error_line_number.to_i, error_filename]
76
+ end
58
77
 
59
78
  def self.print_file_line(line_number, line)
60
- puts "#{"%5s" % line_number}| #{line}"
79
+ puts "#{format('%5s', line_number)}| #{line}"
61
80
  end
62
81
 
63
82
  def self.print_specific_help_line(error)
@@ -75,6 +94,5 @@ module Kitchen
75
94
  # puts "\n"
76
95
  # end
77
96
  end
78
-
79
97
  end
80
98
  end
@@ -0,0 +1,22 @@
1
+ inherit_from: ../../../.rubocop.yml
2
+
3
+ # Directions are supposed to be a big list of steps, so don't let Rubocop penalize
4
+ # us for that
5
+
6
+ Metrics/MethodLength:
7
+ Enabled: false
8
+
9
+ Metrics/AbcSize:
10
+ Enabled: false
11
+
12
+ Metrics/CyclomaticComplexity:
13
+ Enabled: false
14
+
15
+ Metrics/PerceivedComplexity:
16
+ Enabled: false
17
+
18
+ Style/ClassAndModuleChildren:
19
+ Enabled: false
20
+
21
+ Style/Documentation:
22
+ Enabled: false # module names here are pretty clear
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kitchen
2
4
  module Directions
3
5
  module BakeAppendix
4
-
5
6
  def self.v1(page:, number:)
6
7
  title = page.title
7
- title.name = "h1"
8
+ title.name = 'h1'
8
9
  title.replace_children(with:
9
10
  <<~HTML
10
11
  <span class="os-part-text">#{I18n.t(:appendix)} </span>
@@ -15,12 +16,11 @@ module Kitchen
15
16
  )
16
17
 
17
18
  # Make a section with data-depth of X have a header level of X+1
18
- page.search("section").each do |section|
19
+ page.search('section').each do |section|
19
20
  title = section.first!("[data-type='title']")
20
21
  title.name = "h#{section['data-depth'].to_i + 1}"
21
22
  end
22
23
  end
23
-
24
24
  end
25
25
  end
26
26
  end
@@ -1,18 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Kitchen
2
4
  module Directions
5
+ # Bake directons for eoc glossary
6
+ #
3
7
  module BakeChapterGlossary
4
-
5
8
  def self.v1(chapter:, metadata_source:)
6
- metadata_elements = metadata_source.search(%w(.authors .publishers .print-style
7
- .permissions [data-type='subject'])).copy
9
+ metadata_elements = metadata_source.children_to_keep.copy
8
10
 
9
- definitions = chapter.glossaries.search("dl").cut
11
+ definitions = chapter.glossaries.search('dl').cut
10
12
  definitions.sort_by! do |definition|
11
- [definition.first("dt").text.downcase, definition.first("dd").text.downcase]
13
+ [definition.first('dt').text.downcase, definition.first('dd').text.downcase]
12
14
  end
13
15
 
14
16
  chapter.glossaries.trash
15
17
 
18
+ return if definitions.none?
19
+
16
20
  chapter.append(child:
17
21
  <<~HTML
18
22
  <div class="os-eoc os-glossary-container" data-type="composite-page" data-uuid-key="glossary">
@@ -26,9 +30,8 @@ module Kitchen
26
30
  #{definitions.paste}
27
31
  </div>
28
32
  HTML
29
- ) unless definitions.none?
33
+ )
30
34
  end
31
-
32
35
  end
33
36
  end
34
37
  end