epuber 0.7.4 → 0.9.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 +4 -4
- data/Gemfile +10 -2
- data/LICENSE.txt +1 -1
- data/README.md +4 -3
- data/epuber.gemspec +11 -16
- data/lib/epuber/book/contributor.rb +10 -7
- data/lib/epuber/book/file_request.rb +3 -3
- data/lib/epuber/book/target.rb +30 -33
- data/lib/epuber/book/toc_item.rb +2 -4
- data/lib/epuber/book.rb +21 -21
- data/lib/epuber/checker/bookspec_checker.rb +26 -0
- data/lib/epuber/checker/text_checker.rb +16 -7
- data/lib/epuber/checker.rb +16 -2
- data/lib/epuber/checker_transformer_base.rb +3 -6
- data/lib/epuber/command/build.rb +40 -25
- data/lib/epuber/command/from_file.rb +39 -0
- data/lib/epuber/command/init.rb +34 -32
- data/lib/epuber/command/server.rb +3 -3
- data/lib/epuber/command.rb +18 -20
- data/lib/epuber/compiler/compilation_context.rb +10 -8
- data/lib/epuber/compiler/file_database.rb +2 -4
- data/lib/epuber/compiler/file_finders/abstract.rb +36 -26
- data/lib/epuber/compiler/file_finders/imaginary.rb +40 -35
- data/lib/epuber/compiler/file_resolver.rb +79 -89
- data/lib/epuber/compiler/file_stat.rb +4 -4
- data/lib/epuber/compiler/file_types/abstract_file.rb +4 -7
- data/lib/epuber/compiler/file_types/bade_file.rb +20 -15
- data/lib/epuber/compiler/file_types/coffee_script_file.rb +1 -1
- data/lib/epuber/compiler/file_types/css_file.rb +103 -0
- data/lib/epuber/compiler/file_types/generated_file.rb +1 -1
- data/lib/epuber/compiler/file_types/image_file.rb +4 -2
- data/lib/epuber/compiler/file_types/nav_file.rb +0 -1
- data/lib/epuber/compiler/file_types/opf_file.rb +0 -1
- data/lib/epuber/compiler/file_types/source_file.rb +8 -3
- data/lib/epuber/compiler/file_types/stylus_file.rb +4 -3
- data/lib/epuber/compiler/file_types/xhtml_file.rb +67 -13
- data/lib/epuber/compiler/generator.rb +1 -2
- data/lib/epuber/compiler/meta_inf_generator.rb +1 -1
- data/lib/epuber/compiler/nav_generator.rb +10 -11
- data/lib/epuber/compiler/opf_generator.rb +26 -27
- data/lib/epuber/compiler/problem.rb +12 -21
- data/lib/epuber/compiler/xhtml_processor.rb +63 -32
- data/lib/epuber/compiler.rb +77 -25
- data/lib/epuber/config.rb +16 -10
- data/lib/epuber/dsl/attribute.rb +17 -18
- data/lib/epuber/dsl/attribute_support.rb +7 -7
- data/lib/epuber/dsl/object.rb +19 -17
- data/lib/epuber/dsl/tree_object.rb +2 -3
- data/lib/epuber/epubcheck.rb +15 -0
- data/lib/epuber/from_file/bookspec_generator.rb +371 -0
- data/lib/epuber/from_file/encryption_handler.rb +146 -0
- data/lib/epuber/from_file/from_file_executor.rb +140 -0
- data/lib/epuber/from_file/nav_file.rb +163 -0
- data/lib/epuber/from_file/opf_file.rb +219 -0
- data/lib/epuber/helper.rb +0 -1
- data/lib/epuber/lockfile.rb +7 -9
- data/lib/epuber/plugin.rb +2 -3
- data/lib/epuber/ruby_extensions/match_data.rb +1 -1
- data/lib/epuber/ruby_extensions/thread.rb +1 -0
- data/lib/epuber/server/base.styl +0 -1
- data/lib/epuber/server/basic.styl +1 -30
- data/lib/epuber/server/handlers.rb +1 -1
- data/lib/epuber/server.rb +81 -80
- data/lib/epuber/third_party/bower.rb +5 -5
- data/lib/epuber/transformer/book_transformer.rb +108 -0
- data/lib/epuber/transformer/text_transformer.rb +4 -2
- data/lib/epuber/transformer.rb +4 -2
- data/lib/epuber/user_interface.rb +49 -38
- data/lib/epuber/vendor/hash_binding.rb +9 -2
- data/lib/epuber/vendor/ruby_templater.rb +4 -8
- data/lib/epuber/vendor/version.rb +12 -12
- data/lib/epuber/version.rb +1 -1
- metadata +79 -100
- data/lib/epuber/server/fonts/AvenirNext/AvenirNext-Bold.ttf +0 -0
- data/lib/epuber/server/fonts/AvenirNext/AvenirNext-BoldItalic.ttf +0 -0
- data/lib/epuber/server/fonts/AvenirNext/AvenirNext-Italic.ttf +0 -0
- data/lib/epuber/server/fonts/AvenirNext/AvenirNext-Regular.ttf +0 -0
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Epuber
|
4
|
+
class NavFile
|
5
|
+
class NavItem
|
6
|
+
# @return [String]
|
7
|
+
attr_accessor :href
|
8
|
+
# @return [String]
|
9
|
+
attr_accessor :title
|
10
|
+
# @return [Array<NavItem>]
|
11
|
+
attr_accessor :children
|
12
|
+
|
13
|
+
# @param [String] href
|
14
|
+
# @param [String] title
|
15
|
+
#
|
16
|
+
def initialize(href, title)
|
17
|
+
@href = href
|
18
|
+
@title = title
|
19
|
+
@children = []
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param [String] other_href
|
23
|
+
# @param [Boolean] ignore_fragment
|
24
|
+
#
|
25
|
+
# @return [NavItem, nil]
|
26
|
+
#
|
27
|
+
def find_by_href(other_href, ignore_fragment: false)
|
28
|
+
if ignore_fragment
|
29
|
+
other_href = other_href.split('#').first
|
30
|
+
self_href = @href.split('#').first
|
31
|
+
return self if self_href == other_href
|
32
|
+
elsif @href == other_href
|
33
|
+
return self
|
34
|
+
end
|
35
|
+
|
36
|
+
@children.find { |item| item.find_by_href(other_href) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class LandmarkItem
|
41
|
+
# @return [String]
|
42
|
+
#
|
43
|
+
attr_accessor :href
|
44
|
+
|
45
|
+
# @return [String]
|
46
|
+
#
|
47
|
+
attr_accessor :type
|
48
|
+
|
49
|
+
# @param [String] href
|
50
|
+
# @param [String] type
|
51
|
+
#
|
52
|
+
def initialize(href, type)
|
53
|
+
@href = href
|
54
|
+
@type = type
|
55
|
+
end
|
56
|
+
|
57
|
+
# @param [Nokogiri::XML::Node] node
|
58
|
+
#
|
59
|
+
# @return [LandmarkItem]
|
60
|
+
#
|
61
|
+
def self.from_node(node)
|
62
|
+
new(node['href'], node['type'])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
LANDMARKS_MAP = {
|
67
|
+
'cover' => :landmark_cover,
|
68
|
+
'bodymatter' => :landmark_start_page,
|
69
|
+
'copyright-page' => :landmark_copyright,
|
70
|
+
'toc' => :landmark_toc,
|
71
|
+
}.freeze
|
72
|
+
|
73
|
+
MODE_NCX = :ncx
|
74
|
+
MODE_XHTML = :xhtml
|
75
|
+
|
76
|
+
# @return [Nokogiri::XML::Document]
|
77
|
+
#
|
78
|
+
attr_reader :document
|
79
|
+
|
80
|
+
# @return [:ncx, :xhtml]
|
81
|
+
#
|
82
|
+
attr_reader :mode
|
83
|
+
|
84
|
+
# @return [Array<NavItem>]
|
85
|
+
#
|
86
|
+
attr_reader :items
|
87
|
+
|
88
|
+
# @return [Array<LandmarkItem>, nil]
|
89
|
+
#
|
90
|
+
attr_reader :landmarks
|
91
|
+
|
92
|
+
# @param [string] document
|
93
|
+
# @param [:ncx, :xhtml] mode
|
94
|
+
#
|
95
|
+
def initialize(document, mode)
|
96
|
+
raise ArgumentError, 'mode must be :ncx or :xhtml' unless [MODE_NCX, MODE_XHTML].include?(mode)
|
97
|
+
|
98
|
+
@document = Nokogiri::XML(document)
|
99
|
+
@document.remove_namespaces!
|
100
|
+
|
101
|
+
@mode = mode
|
102
|
+
@items = _parse
|
103
|
+
|
104
|
+
@landmarks = _parse_landmarks
|
105
|
+
end
|
106
|
+
|
107
|
+
# @param [String] href
|
108
|
+
# @param [Boolean] ignore_fragment
|
109
|
+
#
|
110
|
+
# @return [NavItem, nil]
|
111
|
+
#
|
112
|
+
def find_by_href(href, ignore_fragment: false)
|
113
|
+
@items.find { |item| item.find_by_href(href, ignore_fragment: ignore_fragment) }
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
# @param [Nokogiri::XML::Element] li_node
|
119
|
+
#
|
120
|
+
# @return [NavItem]
|
121
|
+
#
|
122
|
+
def _parse_nav_xhtml_item(li_node)
|
123
|
+
href = li_node.at_css('a')['href'].strip
|
124
|
+
title = li_node.at_css('a').text.strip
|
125
|
+
|
126
|
+
item = NavItem.new(href, title)
|
127
|
+
item.children = li_node.css('ol > li').map { |p| _parse_nav_xhtml_item(p) }
|
128
|
+
item
|
129
|
+
end
|
130
|
+
|
131
|
+
# @param [Nokogiri::XML::Element] point_node
|
132
|
+
#
|
133
|
+
# @return [NavItem]
|
134
|
+
#
|
135
|
+
def _parse_nav_ncx_item(point_node)
|
136
|
+
href = point_node.at_css('content')['src'].strip
|
137
|
+
title = point_node.at_css('navLabel text').text.strip
|
138
|
+
|
139
|
+
item = NavItem.new(href, title)
|
140
|
+
item.children = point_node.css('> navPoint').map { |p| _parse_nav_ncx_item(p) }
|
141
|
+
item
|
142
|
+
end
|
143
|
+
|
144
|
+
# @return [Array<NavItem>]
|
145
|
+
#
|
146
|
+
def _parse
|
147
|
+
if @mode == MODE_XHTML
|
148
|
+
@document.css('nav[type="toc"] > ol > li')
|
149
|
+
.map { |point| _parse_nav_xhtml_item(point) }
|
150
|
+
elsif @mode == MODE_NCX
|
151
|
+
@document.css('navMap > navPoint')
|
152
|
+
.map { |point| _parse_nav_ncx_item(point) }
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def _parse_landmarks
|
157
|
+
return nil if @mode != MODE_XHTML
|
158
|
+
|
159
|
+
@document.css('nav[type="landmarks"] > ol > li > a')
|
160
|
+
.map { |node| LandmarkItem.from_node(node) }
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Epuber
|
4
|
+
class OpfFile
|
5
|
+
class ManifestItem
|
6
|
+
# @return [String]
|
7
|
+
#
|
8
|
+
attr_accessor :id
|
9
|
+
|
10
|
+
# @return [String]
|
11
|
+
#
|
12
|
+
attr_accessor :href
|
13
|
+
|
14
|
+
# @return [String]
|
15
|
+
#
|
16
|
+
attr_accessor :media_type
|
17
|
+
|
18
|
+
# @return [String, nil]
|
19
|
+
#
|
20
|
+
attr_accessor :properties
|
21
|
+
|
22
|
+
def initialize(id, href, media_type, properties)
|
23
|
+
@id = id
|
24
|
+
@href = href
|
25
|
+
@media_type = media_type
|
26
|
+
@properties = properties
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param [ManifestItem] other
|
30
|
+
#
|
31
|
+
# @return [Boolean]
|
32
|
+
#
|
33
|
+
def ==(other)
|
34
|
+
@id == other.id
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param [Nokogiri::XML::Node] node
|
38
|
+
#
|
39
|
+
# @return [ManifestItem]
|
40
|
+
#
|
41
|
+
def self.from_node(node)
|
42
|
+
new(node['id'], node['href'], node['media-type'], node['properties'])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class SpineItem
|
47
|
+
# @return [String]
|
48
|
+
#
|
49
|
+
attr_accessor :idref
|
50
|
+
|
51
|
+
# @return [String]
|
52
|
+
#
|
53
|
+
attr_accessor :linear
|
54
|
+
|
55
|
+
def initialize(idref, linear)
|
56
|
+
@idref = idref
|
57
|
+
@linear = linear
|
58
|
+
end
|
59
|
+
|
60
|
+
# @param [SpineItem] other
|
61
|
+
#
|
62
|
+
# @return [Boolean]
|
63
|
+
#
|
64
|
+
def ==(other)
|
65
|
+
@idref == other.idref
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param [Nokogiri::XML::Node] node
|
69
|
+
#
|
70
|
+
# @return [SpineItem]
|
71
|
+
#
|
72
|
+
def self.from_node(node)
|
73
|
+
new(node['idref'], node['linear'])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class GuideItem
|
78
|
+
# @return [String]
|
79
|
+
#
|
80
|
+
attr_accessor :type
|
81
|
+
|
82
|
+
# @return [String]
|
83
|
+
#
|
84
|
+
attr_accessor :href
|
85
|
+
|
86
|
+
def initialize(type, href)
|
87
|
+
@type = type
|
88
|
+
@href = href
|
89
|
+
end
|
90
|
+
|
91
|
+
# @param [Nokogiri::XML::Node] node
|
92
|
+
#
|
93
|
+
# @return [GuideItem]
|
94
|
+
#
|
95
|
+
def self.from_node(node)
|
96
|
+
new(node['type'], node['href'])
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# reversed map of generator's map
|
101
|
+
LANDMARKS_MAP = Compiler::OPFGenerator::LANDMARKS_MAP.map { |k, v| [v, k] }
|
102
|
+
.to_h
|
103
|
+
.freeze
|
104
|
+
|
105
|
+
# @return [Nokogiri::XML::Document]
|
106
|
+
#
|
107
|
+
attr_reader :document
|
108
|
+
|
109
|
+
# @return [Nokogiri::XML::Node, nil]
|
110
|
+
#
|
111
|
+
attr_reader :package, :metadata, :manifest, :spine
|
112
|
+
|
113
|
+
# @return [Hash<String, ManifestItem>]
|
114
|
+
#
|
115
|
+
attr_reader :manifest_items
|
116
|
+
|
117
|
+
# @return [Array<SpineItem>]
|
118
|
+
#
|
119
|
+
attr_reader :spine_items
|
120
|
+
|
121
|
+
# @return [Array<GuideItem>]
|
122
|
+
#
|
123
|
+
attr_reader :guide_items
|
124
|
+
|
125
|
+
# @param [String] document
|
126
|
+
def initialize(xml)
|
127
|
+
@document = Nokogiri::XML(xml)
|
128
|
+
@document.remove_namespaces!
|
129
|
+
|
130
|
+
@package = @document.at_css('package')
|
131
|
+
@metadata = @document.at_css('package metadata')
|
132
|
+
@manifest = @document.at_css('package manifest')
|
133
|
+
@spine = @document.at_css('package spine')
|
134
|
+
|
135
|
+
@manifest_items = @document.css('package manifest item')
|
136
|
+
.map { |node| ManifestItem.from_node(node) }
|
137
|
+
.map { |item| [item.id, item] }
|
138
|
+
.to_h
|
139
|
+
@spine_items = @document.css('package spine itemref')
|
140
|
+
.map { |node| SpineItem.from_node(node) }
|
141
|
+
@guide_items = @document.css('package guide reference')
|
142
|
+
.map { |node| GuideItem.from_node(node) }
|
143
|
+
end
|
144
|
+
|
145
|
+
# Find nav file in EPUB (both EPUB 2 and EPUB 3). Returns array with nav and type of nav (:xhtml or :ncx).
|
146
|
+
#
|
147
|
+
# @return [Array<ManifestItem, [:ncx, :xhtml]>, nil] nav, ncx
|
148
|
+
#
|
149
|
+
def find_nav
|
150
|
+
nav = @manifest_items.find { |_, item| item.properties == 'nav' }&.last
|
151
|
+
return [nav, NavFile::MODE_XHTML] if nav
|
152
|
+
|
153
|
+
ncx_id = @spine['toc'] if @spine
|
154
|
+
ncx = manifest_file_by_id(ncx_id) if ncx_id
|
155
|
+
return [ncx, NavFile::MODE_NCX] if ncx
|
156
|
+
|
157
|
+
nil
|
158
|
+
end
|
159
|
+
|
160
|
+
# Returns main unique identifier of this EPUB
|
161
|
+
#
|
162
|
+
# @return [String, nil]
|
163
|
+
#
|
164
|
+
def raw_unique_identifier
|
165
|
+
id = @package['unique-identifier']
|
166
|
+
return unless id
|
167
|
+
|
168
|
+
@metadata.at_css(%(identifier[id="#{id}"]))&.text
|
169
|
+
end
|
170
|
+
|
171
|
+
# Return all identifiers from EPUB metadata
|
172
|
+
#
|
173
|
+
# @return [Array<Nokogiri::XML::Node>]
|
174
|
+
#
|
175
|
+
def identifiers
|
176
|
+
@metadata.css('identifier')
|
177
|
+
end
|
178
|
+
|
179
|
+
# Find meta refines in EPUB 3 metadata
|
180
|
+
#
|
181
|
+
# @param [String] id
|
182
|
+
# @param [String] property
|
183
|
+
#
|
184
|
+
# @return [String, nil]
|
185
|
+
#
|
186
|
+
def find_refines(id, property)
|
187
|
+
@metadata.at_css(%(meta[refines="##{id}"][property="#{property}"]))&.text
|
188
|
+
end
|
189
|
+
|
190
|
+
# Find file in <manifest> by id. Throws exception when not found.
|
191
|
+
#
|
192
|
+
# @param [String] id
|
193
|
+
#
|
194
|
+
# @return [ManifestItem]
|
195
|
+
#
|
196
|
+
def manifest_file_by_id(id)
|
197
|
+
item = @manifest_items[id]
|
198
|
+
raise "Manifest item with id #{id.inspect} not found" unless item
|
199
|
+
|
200
|
+
item
|
201
|
+
end
|
202
|
+
|
203
|
+
# Find file in <manifest> by href. Throws exception when not found.
|
204
|
+
#
|
205
|
+
# @param [String] href
|
206
|
+
#
|
207
|
+
# @return [ManifestItem]
|
208
|
+
#
|
209
|
+
def manifest_file_by_href(href)
|
210
|
+
# remove anchor
|
211
|
+
href = href.sub(/#.*$/, '')
|
212
|
+
|
213
|
+
item = @manifest_items.find { |_, i| i.href == href }&.last
|
214
|
+
raise "Manifest item with href #{href.inspect} not found" unless item
|
215
|
+
|
216
|
+
item
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
data/lib/epuber/helper.rb
CHANGED
data/lib/epuber/lockfile.rb
CHANGED
@@ -18,18 +18,16 @@ module Epuber
|
|
18
18
|
# @return [self]
|
19
19
|
#
|
20
20
|
def self.from_file(file_path)
|
21
|
-
if File.exist?(file_path)
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
hash = if File.exist?(file_path)
|
22
|
+
YAML.safe_load(File.read(file_path))
|
23
|
+
else
|
24
|
+
{}
|
25
|
+
end
|
26
26
|
|
27
27
|
# backward compatibility for version 0.5 and older
|
28
|
-
if hash.include?('version')
|
29
|
-
hash['epuber_version'] = hash.delete('version')
|
30
|
-
end
|
28
|
+
hash['epuber_version'] = hash.delete('version') if hash.include?('version')
|
31
29
|
|
32
|
-
inst =
|
30
|
+
inst = new(hash)
|
33
31
|
inst.defined_from_file = file_path
|
34
32
|
yield inst if hash.empty? && block_given?
|
35
33
|
inst
|
data/lib/epuber/plugin.rb
CHANGED
@@ -18,7 +18,7 @@ module Epuber
|
|
18
18
|
super(file_path)
|
19
19
|
@instances = []
|
20
20
|
|
21
|
-
eval(::File.read(file_path), binding, file_path)
|
21
|
+
eval(::File.read(file_path), binding, file_path) # rubocop:disable Security/Eval
|
22
22
|
end
|
23
23
|
|
24
24
|
# @param [Symbol] name name of the plugin function
|
@@ -66,7 +66,7 @@ module Epuber
|
|
66
66
|
#
|
67
67
|
attr_reader :files
|
68
68
|
|
69
|
-
# @param
|
69
|
+
# @param [String] path
|
70
70
|
#
|
71
71
|
def initialize(path)
|
72
72
|
@path = path
|
@@ -97,7 +97,6 @@ module Epuber
|
|
97
97
|
end.flatten
|
98
98
|
end
|
99
99
|
|
100
|
-
|
101
100
|
# @return [Array<Checker>]
|
102
101
|
#
|
103
102
|
def checkers
|
@@ -17,7 +17,7 @@ class MatchData
|
|
17
17
|
#
|
18
18
|
def line_number
|
19
19
|
n = pre_match_lines.length
|
20
|
-
n += 1 if n
|
20
|
+
n += 1 if n.zero? # it can't be zero, this happens only when the match is at the beginning of file or string
|
21
21
|
n
|
22
22
|
end
|
23
23
|
|
data/lib/epuber/server/base.styl
CHANGED
@@ -9,7 +9,7 @@ body, div, html, p
|
|
9
9
|
|
10
10
|
|
11
11
|
body
|
12
|
-
font-family
|
12
|
+
font-family system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
13
13
|
font-weight normal
|
14
14
|
font-size 20px
|
15
15
|
|
@@ -184,32 +184,3 @@ p.file
|
|
184
184
|
span.arrow
|
185
185
|
font-family monospace
|
186
186
|
color text_color
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
@font-face
|
194
|
-
font-family AvenirNext
|
195
|
-
font-style normal
|
196
|
-
font-weight bold
|
197
|
-
src url(fonts/AvenirNext/AvenirNext-Bold.ttf)
|
198
|
-
|
199
|
-
@font-face
|
200
|
-
font-family AvenirNext
|
201
|
-
font-style italic
|
202
|
-
font-weight bold
|
203
|
-
src url(fonts/AvenirNext/AvenirNext-BoldItalic.ttf)
|
204
|
-
|
205
|
-
@font-face
|
206
|
-
font-family AvenirNext
|
207
|
-
font-style italic
|
208
|
-
font-weight normal
|
209
|
-
src url(fonts/AvenirNext/AvenirNext-Italic.ttf)
|
210
|
-
|
211
|
-
@font-face
|
212
|
-
font-family AvenirNext
|
213
|
-
font-style normal
|
214
|
-
font-weight normal
|
215
|
-
src url(fonts/AvenirNext/AvenirNext-Regular.ttf)
|