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.
- checksums.yaml +7 -0
- data/.devcontainer/devcontainer.json +19 -0
- data/.github/workflows/tests.yml +36 -0
- data/.gitignore +20 -0
- data/.rspec +3 -0
- data/.solargraph.yml +15 -0
- data/CHANGELOG.md +11 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Dockerfile +19 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +74 -0
- data/LICENSE.txt +21 -0
- data/README.md +674 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/normalize +79 -0
- data/bin/setup +8 -0
- data/books/chemistry2e/bake.rb +133 -0
- data/codecov.yaml +27 -0
- data/docker-compose.yml +12 -0
- data/docker/bash +1 -0
- data/docker/entrypoint +9 -0
- data/lib/kitchen.rb +57 -0
- data/lib/kitchen/ancestor.rb +30 -0
- data/lib/kitchen/book_document.rb +18 -0
- data/lib/kitchen/book_element.rb +24 -0
- data/lib/kitchen/book_element_enumerator.rb +5 -0
- data/lib/kitchen/book_recipe.rb +25 -0
- data/lib/kitchen/chapter_element.rb +35 -0
- data/lib/kitchen/chapter_element_enumerator.rb +13 -0
- data/lib/kitchen/clipboard.rb +37 -0
- data/lib/kitchen/composite_chapter_element.rb +23 -0
- data/lib/kitchen/composite_page_element.rb +27 -0
- data/lib/kitchen/composite_page_element_enumerator.rb +13 -0
- data/lib/kitchen/config.rb +20 -0
- data/lib/kitchen/counter.rb +34 -0
- data/lib/kitchen/debug/print_recipe_error.rb +80 -0
- data/lib/kitchen/directions/bake_appendix.rb +26 -0
- data/lib/kitchen/directions/bake_chapter_glossary.rb +34 -0
- data/lib/kitchen/directions/bake_chapter_introductions.rb +58 -0
- data/lib/kitchen/directions/bake_chapter_key_equations.rb +30 -0
- data/lib/kitchen/directions/bake_chapter_summary.rb +52 -0
- data/lib/kitchen/directions/bake_composite_pages.rb +13 -0
- data/lib/kitchen/directions/bake_example.rb +31 -0
- data/lib/kitchen/directions/bake_exercises.rb +164 -0
- data/lib/kitchen/directions/bake_figure.rb +25 -0
- data/lib/kitchen/directions/bake_footnotes/main.rb +11 -0
- data/lib/kitchen/directions/bake_footnotes/v1.rb +38 -0
- data/lib/kitchen/directions/bake_index/main.rb +11 -0
- data/lib/kitchen/directions/bake_index/v1.rb +138 -0
- data/lib/kitchen/directions/bake_index/v1.xhtml.erb +28 -0
- data/lib/kitchen/directions/bake_math_in_paragraph.rb +13 -0
- data/lib/kitchen/directions/bake_notes.rb +58 -0
- data/lib/kitchen/directions/bake_numbered_table/main.rb +11 -0
- data/lib/kitchen/directions/bake_numbered_table/v1.rb +47 -0
- data/lib/kitchen/directions/bake_stepwise.rb +27 -0
- data/lib/kitchen/directions/bake_toc.rb +103 -0
- data/lib/kitchen/directions/bake_unnumbered_tables.rb +14 -0
- data/lib/kitchen/directions/move_title_text_into_span.rb +15 -0
- data/lib/kitchen/document.rb +142 -0
- data/lib/kitchen/element.rb +15 -0
- data/lib/kitchen/element_base.rb +444 -0
- data/lib/kitchen/element_enumerator.rb +12 -0
- data/lib/kitchen/element_enumerator_base.rb +101 -0
- data/lib/kitchen/element_enumerator_factory.rb +111 -0
- data/lib/kitchen/element_factory.rb +32 -0
- data/lib/kitchen/errors.rb +4 -0
- data/lib/kitchen/example_element.rb +20 -0
- data/lib/kitchen/example_element_enumerator.rb +13 -0
- data/lib/kitchen/figure_element.rb +20 -0
- data/lib/kitchen/figure_element_enumerator.rb +13 -0
- data/lib/kitchen/mixins/block_error_if.rb +19 -0
- data/lib/kitchen/note_element.rb +43 -0
- data/lib/kitchen/note_element_enumerator.rb +13 -0
- data/lib/kitchen/oven.rb +61 -0
- data/lib/kitchen/page_element.rb +51 -0
- data/lib/kitchen/page_element_enumerator.rb +13 -0
- data/lib/kitchen/pantry.rb +35 -0
- data/lib/kitchen/patches/nokogiri.rb +31 -0
- data/lib/kitchen/patches/renderable.rb +31 -0
- data/lib/kitchen/patches/string.rb +5 -0
- data/lib/kitchen/recipe.rb +78 -0
- data/lib/kitchen/search_history.rb +33 -0
- data/lib/kitchen/selectors/base.rb +8 -0
- data/lib/kitchen/selectors/standard_1.rb +12 -0
- data/lib/kitchen/table_element.rb +36 -0
- data/lib/kitchen/table_element_enumerator.rb +13 -0
- data/lib/kitchen/term_element.rb +16 -0
- data/lib/kitchen/term_element_enumerator.rb +13 -0
- data/lib/kitchen/transliterations.rb +19 -0
- data/lib/kitchen/type_casting_element_enumerator.rb +23 -0
- data/lib/kitchen/utils.rb +19 -0
- data/lib/kitchen/version.rb +3 -0
- data/lib/locales/en.yml +21 -0
- data/lib/notes.md +9 -0
- data/openstax_kitchen.gemspec +39 -0
- data/tutorials/00/expected_baked.html +3 -0
- data/tutorials/00/raw.html +3 -0
- data/tutorials/00/solution_1.rb +7 -0
- data/tutorials/00/solution_2.rb +6 -0
- data/tutorials/01/expected_baked.html +66 -0
- data/tutorials/01/raw.html +62 -0
- data/tutorials/01/solution_1.rb +16 -0
- data/tutorials/01/solution_2.rb +24 -0
- data/tutorials/02/expected_baked.html +207 -0
- data/tutorials/02/raw.html +201 -0
- data/tutorials/02/solution_1.rb +29 -0
- data/tutorials/03/expected_baked.html +33 -0
- data/tutorials/03/raw.html +31 -0
- data/tutorials/03/solution_1.rb +16 -0
- data/tutorials/03/solution_2.rb +15 -0
- data/tutorials/04/expected_baked.html +36 -0
- data/tutorials/04/raw.html +36 -0
- data/tutorials/04/solution_1.rb +20 -0
- data/tutorials/04/solution_2.rb +25 -0
- data/tutorials/05/expected_baked.html +11 -0
- data/tutorials/05/raw.html +11 -0
- data/tutorials/05/solution_1.rb +9 -0
- data/tutorials/check_it +64 -0
- data/tutorials/setup_my_recipes +30 -0
- metadata +278 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
module Kitchen
|
|
2
|
+
class ElementEnumeratorBase < Enumerator
|
|
3
|
+
|
|
4
|
+
def initialize(size=nil, css_or_xpath: nil, upstream_enumerator: nil)
|
|
5
|
+
@css_or_xpath = css_or_xpath
|
|
6
|
+
@upstream_enumerator = upstream_enumerator
|
|
7
|
+
super(size)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def search_history
|
|
11
|
+
(@upstream_enumerator&.search_history || SearchHistory.empty).add(@css_or_xpath)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def terms(css_or_xpath=nil, &block)
|
|
15
|
+
chain_to(TermElementEnumerator, css_or_xpath: css_or_xpath, &block)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def pages(css_or_xpath=nil, &block)
|
|
19
|
+
chain_to(PageElementEnumerator, css_or_xpath: css_or_xpath, &block)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def chapters(css_or_xpath=nil, &block)
|
|
23
|
+
chain_to(ChapterElementEnumerator, css_or_xpath: css_or_xpath, &block)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# use block_error_if
|
|
27
|
+
def figures(css_or_xpath=nil, &block)
|
|
28
|
+
chain_to(FigureElementEnumerator, css_or_xpath: css_or_xpath, &block)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def notes(css_or_xpath=nil, &block)
|
|
32
|
+
chain_to(NoteElementEnumerator, css_or_xpath: css_or_xpath, &block)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def tables(css_or_xpath=nil, &block)
|
|
36
|
+
chain_to(TableElementEnumerator, css_or_xpath: css_or_xpath, &block)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def examples(css_or_xpath=nil, &block)
|
|
40
|
+
chain_to(ExampleElementEnumerator, css_or_xpath: css_or_xpath, &block)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def search(css_or_xpath=nil, &block)
|
|
44
|
+
chain_to(ElementEnumerator, css_or_xpath: css_or_xpath, &block)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def chain_to(enumerator_class, css_or_xpath: nil, &block)
|
|
48
|
+
raise(RecipeError, "Did you forget a `.each` call on this enumerator?") if block_given?
|
|
49
|
+
|
|
50
|
+
enumerator_class.factory.build_within(self, css_or_xpath: css_or_xpath)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def first!(missing_message: "Could not return a first result")
|
|
54
|
+
first || raise(RecipeError, "#{missing_message} matching #{search_history.latest} " \
|
|
55
|
+
"inside [#{search_history.upstream}]")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Removes enumerated elements from their parent and places them on the specified clipboard
|
|
59
|
+
#
|
|
60
|
+
# @param to [Symbol, String, Clipboard, nil] the name of the clipboard (or a Clipboard
|
|
61
|
+
# object) to cut to. String values are converted to symbols. If not provided, the
|
|
62
|
+
# elements are placed on a new clipboard.
|
|
63
|
+
# @return [Clipboard] the clipboard
|
|
64
|
+
#
|
|
65
|
+
def cut(to: nil)
|
|
66
|
+
to ||= Clipboard.new
|
|
67
|
+
self.each do |element|
|
|
68
|
+
element.cut(to: to)
|
|
69
|
+
end
|
|
70
|
+
to
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Makes a copy of the enumerated elements and places them on the specified clipboard.
|
|
74
|
+
#
|
|
75
|
+
# @param to [Symbol, String, Clipboard, nil] the name of the clipboard (or a Clipboard
|
|
76
|
+
# object) to copy to. String values are converted to symbols. If not provided, the
|
|
77
|
+
# copies are placed on a new clipboard.
|
|
78
|
+
# @return [Clipboard] the clipboard
|
|
79
|
+
#
|
|
80
|
+
def copy(to: nil)
|
|
81
|
+
to ||= Clipboard.new
|
|
82
|
+
self.each do |element|
|
|
83
|
+
element.copy(to: to)
|
|
84
|
+
end
|
|
85
|
+
to
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def trash
|
|
89
|
+
self.each(&:trash)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def [](index)
|
|
93
|
+
to_a[index]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def to_s
|
|
97
|
+
self.map(&:to_s).join("")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
module Kitchen
|
|
2
|
+
class ElementEnumeratorFactory
|
|
3
|
+
|
|
4
|
+
attr_reader :default_css_or_xpath
|
|
5
|
+
attr_reader :enumerator_class
|
|
6
|
+
attr_reader :sub_element_class
|
|
7
|
+
attr_reader :detect_sub_element_class
|
|
8
|
+
|
|
9
|
+
def initialize(default_css_or_xpath: nil, sub_element_class: nil,
|
|
10
|
+
enumerator_class:, detect_sub_element_class: false)
|
|
11
|
+
@default_css_or_xpath = default_css_or_xpath
|
|
12
|
+
@sub_element_class = sub_element_class
|
|
13
|
+
@enumerator_class = enumerator_class
|
|
14
|
+
@detect_sub_element_class = detect_sub_element_class
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# TODO spec this!
|
|
18
|
+
def apply_default_css_or_xpath_and_normalize(css_or_xpath=nil)
|
|
19
|
+
css_or_xpath ||= "$"
|
|
20
|
+
[css_or_xpath].flatten.each {|item| item.gsub!(/\$/, [default_css_or_xpath].flatten.join(", ")) }
|
|
21
|
+
[css_or_xpath].flatten
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def build_within(enumerator_or_element, css_or_xpath: nil)
|
|
25
|
+
css_or_xpath = apply_default_css_or_xpath_and_normalize(css_or_xpath)
|
|
26
|
+
|
|
27
|
+
case enumerator_or_element
|
|
28
|
+
when ElementBase
|
|
29
|
+
build_within_element(enumerator_or_element, css_or_xpath: css_or_xpath)
|
|
30
|
+
when ElementEnumeratorBase
|
|
31
|
+
build_within_other_enumerator(enumerator_or_element, css_or_xpath: css_or_xpath)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def or_with(other_factory)
|
|
36
|
+
self.class.new(
|
|
37
|
+
default_css_or_xpath: default_css_or_xpath + ", " + other_factory.default_css_or_xpath,
|
|
38
|
+
enumerator_class: TypeCastingElementEnumerator,
|
|
39
|
+
detect_sub_element_class: true
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
protected
|
|
44
|
+
|
|
45
|
+
def build_within_element(element, css_or_xpath:)
|
|
46
|
+
enumerator_class.new(css_or_xpath: css_or_xpath) do |block|
|
|
47
|
+
grand_ancestors = element.ancestors
|
|
48
|
+
parent_ancestor = Ancestor.new(element)
|
|
49
|
+
|
|
50
|
+
num_sub_elements = 0
|
|
51
|
+
|
|
52
|
+
element.raw.search(*css_or_xpath).each_with_index do |sub_node, index|
|
|
53
|
+
sub_element = ElementFactory.build_from_node(
|
|
54
|
+
node: sub_node,
|
|
55
|
+
document: element.document,
|
|
56
|
+
element_class: sub_element_class,
|
|
57
|
+
default_short_type: Utils.search_path_to_type(css_or_xpath),
|
|
58
|
+
detect_element_class: detect_sub_element_class
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# If the provided `css_or_xpath` has already been counted, we need to uncount
|
|
62
|
+
# them on the ancestors so that when they are counted again below, the counts
|
|
63
|
+
# are correct. Only do this on the first loop!
|
|
64
|
+
if index == 0
|
|
65
|
+
if element.have_sub_elements_already_been_counted?(css_or_xpath)
|
|
66
|
+
grand_ancestors.values.each do |ancestor|
|
|
67
|
+
ancestor.decrement_descendant_count(
|
|
68
|
+
sub_element.short_type,
|
|
69
|
+
by: element.number_of_sub_elements_already_counted(css_or_xpath)
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Record this sub element's ancestors and increment their descendant counts
|
|
76
|
+
sub_element.add_ancestors(grand_ancestors, parent_ancestor)
|
|
77
|
+
sub_element.count_as_descendant
|
|
78
|
+
|
|
79
|
+
# Remember how this sub element was found so can trace search history given
|
|
80
|
+
# any element.
|
|
81
|
+
sub_element.css_or_xpath_that_found_me = css_or_xpath
|
|
82
|
+
|
|
83
|
+
# Count runs through this loop for below
|
|
84
|
+
num_sub_elements += 1
|
|
85
|
+
|
|
86
|
+
# Mark the location so that if there's an error we can show the developer where.
|
|
87
|
+
sub_element.document.location = sub_element
|
|
88
|
+
|
|
89
|
+
block.yield(sub_element)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
element.remember_that_sub_elements_are_already_counted(
|
|
93
|
+
css_or_xpath: css_or_xpath, count: num_sub_elements
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def build_within_other_enumerator(other_enumerator, css_or_xpath:)
|
|
99
|
+
# Return a new enumerator instance that internally iterates over `other_enumerator`
|
|
100
|
+
# running a new enumerator for each element returned by that other enumerator.
|
|
101
|
+
enumerator_class.new(css_or_xpath: css_or_xpath, upstream_enumerator: other_enumerator) do |block|
|
|
102
|
+
other_enumerator.each do |element|
|
|
103
|
+
build_within_element(element, css_or_xpath: css_or_xpath).each do |sub_element|
|
|
104
|
+
block.yield(sub_element)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Kitchen
|
|
2
|
+
class ElementFactory
|
|
3
|
+
|
|
4
|
+
ELEMENT_CLASSES = ElementBase.descendants
|
|
5
|
+
|
|
6
|
+
def self.specific_element_class_for_node(node)
|
|
7
|
+
ELEMENT_CLASSES.find do |klass|
|
|
8
|
+
klass.is_the_element_class_for?(node)
|
|
9
|
+
end || Element
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.build_from_node(node:,
|
|
13
|
+
document:,
|
|
14
|
+
element_class: nil,
|
|
15
|
+
default_short_type: nil,
|
|
16
|
+
detect_element_class: false)
|
|
17
|
+
element_class ||= detect_element_class ?
|
|
18
|
+
specific_element_class_for_node(node) :
|
|
19
|
+
Element
|
|
20
|
+
|
|
21
|
+
if element_class == Element
|
|
22
|
+
element_class.new(node: node,
|
|
23
|
+
document: document,
|
|
24
|
+
short_type: default_short_type)
|
|
25
|
+
else
|
|
26
|
+
element_class.new(node: node,
|
|
27
|
+
document: document)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Kitchen
|
|
2
|
+
class ExampleElement < ElementBase
|
|
3
|
+
|
|
4
|
+
def initialize(node:, document: nil)
|
|
5
|
+
super(node: node,
|
|
6
|
+
document: document,
|
|
7
|
+
enumerator_class: ExampleElementEnumerator,
|
|
8
|
+
short_type: :example)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def titles
|
|
12
|
+
search("span[data-type='title']")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.is_the_element_class_for?(node)
|
|
16
|
+
node['data-type'] == "example"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Kitchen
|
|
2
|
+
class ExampleElementEnumerator < ElementEnumeratorBase
|
|
3
|
+
|
|
4
|
+
def self.factory
|
|
5
|
+
ElementEnumeratorFactory.new(
|
|
6
|
+
default_css_or_xpath: "div[data-type='example']", # TODO element.document.selectors.example
|
|
7
|
+
sub_element_class: ExampleElement,
|
|
8
|
+
enumerator_class: self
|
|
9
|
+
)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Kitchen
|
|
2
|
+
class FigureElement < ElementBase
|
|
3
|
+
|
|
4
|
+
def initialize(node:, document: nil)
|
|
5
|
+
super(node: node,
|
|
6
|
+
document: document,
|
|
7
|
+
enumerator_class: FigureElementEnumerator,
|
|
8
|
+
short_type: :figure)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def caption
|
|
12
|
+
first("figcaption")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.is_the_element_class_for?(node)
|
|
16
|
+
node.name == "figure"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Kitchen
|
|
2
|
+
class FigureElementEnumerator < ElementEnumeratorBase
|
|
3
|
+
|
|
4
|
+
def self.factory
|
|
5
|
+
ElementEnumeratorFactory.new(
|
|
6
|
+
default_css_or_xpath: "figure", # TODO get from config?
|
|
7
|
+
sub_element_class: FigureElement,
|
|
8
|
+
enumerator_class: self
|
|
9
|
+
)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Kitchen
|
|
2
|
+
module Mixins
|
|
3
|
+
module BlockErrorIf
|
|
4
|
+
|
|
5
|
+
def block_error_if(block_given)
|
|
6
|
+
calling_method = begin
|
|
7
|
+
this_method_location_index = caller_locations.find_index do |location|
|
|
8
|
+
location.label == "block_error_if"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
caller_locations[(this_method_location_index || -1) + 1].label
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
raise(RecipeError, "The `#{calling_method}` method does not take a block argument") if block_given
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Kitchen
|
|
2
|
+
class NoteElement < ElementBase
|
|
3
|
+
|
|
4
|
+
TITLE_TRANSLATION_KEYS = %w(
|
|
5
|
+
link-to-learning
|
|
6
|
+
everyday-life
|
|
7
|
+
sciences-interconnect
|
|
8
|
+
chemist-portrait
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
def initialize(node:, document: nil)
|
|
12
|
+
super(node: node,
|
|
13
|
+
document: document,
|
|
14
|
+
enumerator_class: NoteElementEnumerator,
|
|
15
|
+
short_type: :note)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def title
|
|
19
|
+
block_error_if(block_given?)
|
|
20
|
+
first("[data-type='title']")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def indicates_autogenerated_title?
|
|
24
|
+
translation_key_in(TITLE_TRANSLATION_KEYS).present?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def autogenerated_title
|
|
28
|
+
translation_key = translation_key_in(TITLE_TRANSLATION_KEYS)
|
|
29
|
+
I18n.t(:"notes.#{document.short_name}.#{translation_key}", default: :"notes.#{translation_key}")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def translation_key_in(possible_translation_keys)
|
|
33
|
+
keys = possible_translation_keys & classes
|
|
34
|
+
raise("too many translation keys: #{keys.join(', ')}") if keys.many?
|
|
35
|
+
keys.first
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.is_the_element_class_for?(node)
|
|
39
|
+
node['data-type'] == "note"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Kitchen
|
|
2
|
+
class NoteElementEnumerator < ElementEnumeratorBase
|
|
3
|
+
|
|
4
|
+
def self.factory
|
|
5
|
+
ElementEnumeratorFactory.new(
|
|
6
|
+
default_css_or_xpath: "div[data-type='note']", # TODO get from config?
|
|
7
|
+
sub_element_class: NoteElement,
|
|
8
|
+
enumerator_class: self
|
|
9
|
+
)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/kitchen/oven.rb
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Kitchen
|
|
2
|
+
class Oven
|
|
3
|
+
|
|
4
|
+
def self.bake(input_file:,
|
|
5
|
+
config_file: nil,
|
|
6
|
+
recipes:,
|
|
7
|
+
output_file:)
|
|
8
|
+
|
|
9
|
+
profile = BakeProfile.new
|
|
10
|
+
profile.started!
|
|
11
|
+
|
|
12
|
+
nokogiri_doc = File.open(input_file) do |f|
|
|
13
|
+
profile.opened!
|
|
14
|
+
Nokogiri::XML(f).tap { profile.parsed! }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
config = config_file.nil? ? nil : Config.new_from_file(File.open(config_file))
|
|
18
|
+
|
|
19
|
+
doc = Kitchen::Document.new(
|
|
20
|
+
nokogiri_document: nokogiri_doc,
|
|
21
|
+
config: config
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
[recipes].flatten.each do |recipe|
|
|
25
|
+
recipe.document = doc
|
|
26
|
+
recipe.bake
|
|
27
|
+
end
|
|
28
|
+
profile.baked!
|
|
29
|
+
|
|
30
|
+
File.open(output_file, "w") do |f|
|
|
31
|
+
f.write doc.to_xhtml(indent:2)
|
|
32
|
+
end
|
|
33
|
+
profile.written!
|
|
34
|
+
|
|
35
|
+
profile
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class BakeProfile
|
|
39
|
+
def started!; @started_at = Time.now; end
|
|
40
|
+
def opened!; @opened_at = Time.now; end
|
|
41
|
+
def parsed!; @parsed_at = Time.now; end
|
|
42
|
+
def baked!; @baked_at = Time.now; end
|
|
43
|
+
def written!; @written_at = Time.now; end
|
|
44
|
+
|
|
45
|
+
def open_seconds; @opened_at - @started_at; end
|
|
46
|
+
def parse_seconds; @parsed_at - @opened_at; end
|
|
47
|
+
def bake_seconds; @baked_at - @parsed_at; end
|
|
48
|
+
def write_seconds; @written_at - @baked_at; end
|
|
49
|
+
|
|
50
|
+
def to_s
|
|
51
|
+
<<~STRING
|
|
52
|
+
Open: #{open_seconds} s
|
|
53
|
+
Parse: #{parse_seconds} s
|
|
54
|
+
Bake: #{bake_seconds} s
|
|
55
|
+
Write: #{write_seconds} s
|
|
56
|
+
STRING
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
end
|