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