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,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "kitchen"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/inline"
4
+
5
+ gemfile do
6
+ gem 'nokogiri'
7
+ gem 'byebug'
8
+ end
9
+
10
+ require 'nokogiri'
11
+ require 'byebug'
12
+
13
+ # In HTML attribute order doesn't matter, but to make sure our diffs are useful resort all
14
+ # attributes.
15
+ def sort_attributes(document)
16
+ document.traverse do |child|
17
+ next if child.text? || child.document?
18
+ attributes = child.attributes
19
+ attributes.each do |key, value|
20
+ child.remove_attribute(key)
21
+ end
22
+ sorted_keys = attributes.keys.sort
23
+ sorted_keys.each do |key|
24
+ value = attributes[key].to_s
25
+ child[key] = value
26
+ end
27
+ end
28
+ end
29
+
30
+ # Legacy baked docs have a few issues that I think are unintentional; clean those
31
+ def clean_legacy_baked(document)
32
+ document.traverse do |child|
33
+ next if child.text? || child.document?
34
+
35
+ # Legacy bakings of unnumbered tables include a bogus number, delete it
36
+ if child.name == "table" && (child[:class] || "").include?("unnumbered")
37
+ child.remove_attribute("summary")
38
+ end
39
+
40
+ # Sometimes there is `class=' '`, get rid of these
41
+ child.attributes.each do |key, value|
42
+ child.remove_attribute(key) if key == "class" && value.to_s.strip == ""
43
+ end
44
+ end
45
+ end
46
+
47
+ def mask_copied_id_numbers(document)
48
+ document.traverse do |child|
49
+ if child[:id]
50
+ child[:id] = child[:id].gsub(/_copy_(\d+)$/,"_copy_XXX")
51
+ end
52
+ end
53
+ end
54
+
55
+ def process(input_file:, output_file:)
56
+ read_and_write(input_file: input_file, output_file: output_file) do |doc|
57
+ clean_legacy_baked(doc)
58
+ sort_attributes(doc)
59
+ mask_copied_id_numbers(doc)
60
+ end
61
+ end
62
+
63
+ def read_and_write(input_file:, output_file:)
64
+ doc = Nokogiri::XML(File.open(input_file)){|config| config.noblanks}
65
+
66
+ yield(doc) if block_given?
67
+
68
+ File.open(output_file, "w") do |f|
69
+ f.write doc.to_xhtml(indent:2)
70
+ end
71
+ end
72
+
73
+ ARGV.each do |input_file|
74
+ output_file = input_file.gsub(/(\.\w+)\z/,'.normalized\1')
75
+
76
+ process(input_file: input_file, output_file: output_file)
77
+
78
+ puts "Normalized '#{input_file}' to '#{output_file}'"
79
+ end
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/inline"
4
+
5
+ gemfile do
6
+ gem 'kitchen', path: '/Users/jps/dev/openstax/kitchen'
7
+ gem 'byebug'
8
+ end
9
+
10
+ require "kitchen"
11
+ require 'byebug'
12
+
13
+ include Kitchen::Directions
14
+
15
+ recipe = Kitchen::BookRecipe.new(book_short_name: :chemistry) do |doc|
16
+ book = doc.book
17
+
18
+ # Some stuff just goes away
19
+ book.search("cnx-pi").trash
20
+
21
+ # Update the preface title
22
+ book.pages("$.preface").search("div[data-type='document-title']").each do |title| # TODO add title method
23
+ title.replace_children(with:
24
+ <<~HTML
25
+ <span data-type="" itemprop="" class="os-text">#{title.text}</span>
26
+ HTML
27
+ )
28
+ title.name = "h1"
29
+ end
30
+
31
+ book.chapters.each do |chapter|
32
+ BakeChapterGlossary.v1(chapter: chapter, metadata_source: book.metadata)
33
+ BakeChapterKeyEquations.v1(chapter: chapter, metadata_source: book.metadata)
34
+ BakeChapterSummary.v1(chapter: chapter, metadata_source: book.metadata)
35
+ end
36
+
37
+ BakeExercises.v1(book: book)
38
+ BakeChapterIntroductions.v1(book: book)
39
+
40
+ book.chapters.each do |chapter|
41
+ # Fix up chapter titles - TODO put this in BakeChapter
42
+ heading = chapter.at("h1[2]")
43
+ heading[:id] = "chapTitle#{chapter.count_in(:book)}"
44
+ heading.replace_children(with:
45
+ <<~HTML
46
+ <span class="os-part-text">Chapter </span>
47
+ <span class="os-number">#{chapter.count_in(:book)}</span>
48
+ <span class="os-divider"> </span>
49
+ <span data-type="" itemprop="" class="os-text">#{heading.text}</span>
50
+ HTML
51
+ )
52
+
53
+ chapter.tables("$:not(.unnumbered)").each do |table|
54
+ BakeNumberedTable.v1(table: table, number: "#{chapter.count_in(:book)}.#{table.count_in(:chapter)}")
55
+ end
56
+
57
+ chapter.examples.each do |example|
58
+ BakeExample.v1(example: example,
59
+ number: "#{chapter.count_in(:book)}.#{example.count_in(:chapter)}",
60
+ title_tag: "h3")
61
+ end
62
+
63
+ chapter.pages("$:not(.introduction)").each do |page|
64
+ page.search("div[data-type='description']").each(&:trash)
65
+ page.add_class("chapter-content-module")
66
+
67
+ title = page.search("div[data-type='document-title']").first
68
+ title.name = "h2"
69
+ title.replace_children(with:
70
+ <<~HTML
71
+ <span class="os-number">#{chapter.count_in(:book)}.#{page.count_in(:chapter)}</span>
72
+ <span class="os-divider"> </span>
73
+ <span data-type="" itemprop="" class="os-text">#{title.text}</span>
74
+ HTML
75
+ )
76
+ end
77
+
78
+ chapter.figures.each do |figure|
79
+ BakeFigure.v1(figure: figure, number: "#{chapter.count_in(:book)}.#{figure.count_in(:chapter)}")
80
+ end
81
+ end
82
+
83
+ book.pages("$.appendix").each do |page|
84
+ appendix_letter = [*('A'..'Z')][page.count_in(:book)-1]
85
+
86
+ page.figures.each do |figure|
87
+ BakeFigure.v1(figure: figure, number: "#{appendix_letter}#{figure.count_in(:page)}")
88
+ end
89
+
90
+ page.tables("$:not(.unnumbered)").each do |table|
91
+ BakeNumberedTable.v1(table: table, number: "#{appendix_letter}#{table.count_in(:page)}")
92
+ end
93
+
94
+ page.examples.each do |example|
95
+ BakeExample.v1(example: example,
96
+ number: "#{appendix_letter}#{example.count_in(:page)}",
97
+ title_tag: "div")
98
+ end
99
+
100
+ BakeAppendix.v1(page: page, number: appendix_letter)
101
+ end
102
+
103
+ BakeNotes.v1(book: book)
104
+ BakeStepwise.v1(book: book)
105
+ BakeUnnumberedTables.v1(book: book)
106
+ BakeMathInParagraph.v1(book: book)
107
+ BakeIndex.v1(book: book)
108
+ BakeCompositePages.v1(book: book)
109
+ BakeFootnotes.v1(book: book)
110
+
111
+ BakeToc.v1(book: book)
112
+
113
+ # competing docs from elements - BakeLinkPlaceholders
114
+ book.search("a").each do |anchor|
115
+ next unless anchor.text == "[link]"
116
+ id = anchor[:href][1..-1]
117
+ replacement = doc.pantry(name: :link_text).get(id)
118
+ if replacement.present?
119
+ anchor.replace_children(with: replacement)
120
+ else
121
+ # TODO log a warning!
122
+ puts "warning! could not find a replacement for '[link]' on an element with ID '#{id}'"
123
+ end
124
+ end
125
+
126
+ end
127
+
128
+ puts Kitchen::Oven.bake(
129
+ # input_file: "collection.assembled.xhtml",
130
+ input_file: "entire.collection.assembled.xhtml",
131
+ recipes: recipe,
132
+ output_file: "collection.kitchen.xhtml"
133
+ )
@@ -0,0 +1,27 @@
1
+ codecov:
2
+ require_ci_to_pass: no
3
+
4
+ coverage:
5
+ precision: 2
6
+ round: down
7
+ range: "70...100"
8
+
9
+ parsers:
10
+ gcov:
11
+ branch_detection:
12
+ conditional: yes
13
+ loop: yes
14
+ method: no
15
+ macro: no
16
+
17
+ comment:
18
+ layout: "reach,diff,flags,tree"
19
+ behavior: default
20
+ require_changes: no
21
+
22
+ ignore:
23
+ - "spec"
24
+ - "tutorials"
25
+ - "bin"
26
+ - "docker"
27
+ - "books"
@@ -0,0 +1,12 @@
1
+ version: '3.7'
2
+ services:
3
+ app:
4
+ image: "openstax/kitchen"
5
+ build: .
6
+ volumes:
7
+ - .:/code
8
+ networks:
9
+ - openstax
10
+ networks:
11
+ openstax:
12
+ name: openstax
@@ -0,0 +1 @@
1
+ docker-compose exec app bash
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+
4
+ # uncomment this to load docker secrets into environment variables
5
+ # export $(egrep -v '^#' /run/secrets/* | xargs)
6
+
7
+ # exec "$@"
8
+
9
+ tail -f /dev/null
@@ -0,0 +1,57 @@
1
+ require "kitchen/version"
2
+
3
+ require "nokogiri"
4
+ require "active_support/all"
5
+
6
+ module Kitchen
7
+ module Directions; end
8
+ end
9
+
10
+ def file_glob(relative_folder_and_extension)
11
+ Dir[File.expand_path(__dir__ + "/" + relative_folder_and_extension)]
12
+ end
13
+
14
+ def require_all(relative_folder, file_matcher="**/*.rb")
15
+ file_glob(relative_folder + "/#{file_matcher}").each{|f| require f}
16
+ end
17
+
18
+ require_all("kitchen/patches")
19
+ require_all("kitchen/mixins")
20
+
21
+ require "kitchen/selectors/base"
22
+ require_all("kitchen/selectors")
23
+
24
+ require "kitchen/utils"
25
+ require "kitchen/transliterations"
26
+ require "kitchen/errors"
27
+ require "kitchen/ancestor"
28
+ require "kitchen/search_history"
29
+ require "kitchen/config"
30
+ require "kitchen/document"
31
+ require "kitchen/book_document"
32
+ require "kitchen/debug/print_recipe_error"
33
+ require "kitchen/recipe"
34
+ require "kitchen/book_recipe"
35
+ require "kitchen/oven"
36
+ require "kitchen/clipboard"
37
+ require "kitchen/pantry"
38
+ require "kitchen/counter"
39
+
40
+ require "kitchen/element_enumerator_base"
41
+ require "kitchen/element_enumerator_factory"
42
+ require_all("kitchen", "*enumerator.rb")
43
+
44
+ require "kitchen/element_base"
45
+ require_all("kitchen", "*element.rb")
46
+ require "kitchen/element_factory"
47
+
48
+
49
+ require_all("kitchen/directions")
50
+
51
+ I18n.load_path << file_glob("/locales/*.yml")
52
+
53
+
54
+ I18n.available_locales.each do |available_locale|
55
+ I18n.backend.store_translations(available_locale, Kitchen::TRANSLITERATIONS)
56
+ end
57
+
@@ -0,0 +1,30 @@
1
+ module Kitchen
2
+ class Ancestor
3
+
4
+ attr_accessor :type
5
+ attr_accessor :element
6
+
7
+ def initialize(element)
8
+ @element = element
9
+ @type = element.short_type
10
+ @descendant_counts = {}
11
+ end
12
+
13
+ def increment_descendant_count(descendant_type)
14
+ @descendant_counts[descendant_type.to_sym] = get_descendant_count(descendant_type) + 1
15
+ end
16
+
17
+ def decrement_descendant_count(descendant_type, by: 1)
18
+ @descendant_counts[descendant_type.to_sym] = get_descendant_count(descendant_type) - by
19
+ end
20
+
21
+ def get_descendant_count(descendant_type)
22
+ @descendant_counts[descendant_type.to_sym] || 0
23
+ end
24
+
25
+ def clone
26
+ Ancestor.new(element)
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,18 @@
1
+ module Kitchen
2
+ class BookDocument < Document
3
+
4
+ attr_reader :short_name
5
+
6
+ def initialize(document:, short_name: :not_set, config: nil)
7
+ @short_name = short_name
8
+
9
+ super(nokogiri_document: document.is_a?(Document) ? document.raw : document,
10
+ config: config)
11
+ end
12
+
13
+ def book
14
+ BookElement.new(node: nokogiri_document.search("html").first, document: self)
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ module Kitchen
2
+ class BookElement < ElementBase
3
+
4
+ def initialize(node:, document: nil)
5
+ super(node: node,
6
+ document: document,
7
+ enumerator_class: BookElementEnumerator,
8
+ short_type: :book)
9
+ end
10
+
11
+ def body
12
+ first!("body")
13
+ end
14
+
15
+ def metadata
16
+ first!("div[data-type='metadata']")
17
+ end
18
+
19
+ def toc
20
+ first!("nav#toc")
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ module Kitchen
2
+ class BookElementEnumerator < ElementEnumeratorBase
3
+ # Unsure if this class is needed; there is only one book element per document
4
+ end
5
+ end
@@ -0,0 +1,25 @@
1
+ module Kitchen
2
+ class BookRecipe < Recipe
3
+
4
+ attr_reader :book_short_name
5
+
6
+ # Make a new BookRecipe
7
+ #
8
+ # @yieldparam doc [BookDocument] an object representing an XML document
9
+ #
10
+ def initialize(book_short_name: :not_set, &block)
11
+ @book_short_name = book_short_name
12
+ super(&block)
13
+ end
14
+
15
+ def document=(document)
16
+ super(document)
17
+ @document = Kitchen::BookDocument.new(
18
+ document: @document,
19
+ short_name: book_short_name,
20
+ config: @document.config.clone
21
+ )
22
+ end
23
+
24
+ end
25
+ end