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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +10 -2
  3. data/LICENSE.txt +1 -1
  4. data/README.md +4 -3
  5. data/epuber.gemspec +11 -16
  6. data/lib/epuber/book/contributor.rb +10 -7
  7. data/lib/epuber/book/file_request.rb +3 -3
  8. data/lib/epuber/book/target.rb +30 -33
  9. data/lib/epuber/book/toc_item.rb +2 -4
  10. data/lib/epuber/book.rb +21 -21
  11. data/lib/epuber/checker/bookspec_checker.rb +26 -0
  12. data/lib/epuber/checker/text_checker.rb +16 -7
  13. data/lib/epuber/checker.rb +16 -2
  14. data/lib/epuber/checker_transformer_base.rb +3 -6
  15. data/lib/epuber/command/build.rb +40 -25
  16. data/lib/epuber/command/from_file.rb +39 -0
  17. data/lib/epuber/command/init.rb +34 -32
  18. data/lib/epuber/command/server.rb +3 -3
  19. data/lib/epuber/command.rb +18 -20
  20. data/lib/epuber/compiler/compilation_context.rb +10 -8
  21. data/lib/epuber/compiler/file_database.rb +2 -4
  22. data/lib/epuber/compiler/file_finders/abstract.rb +36 -26
  23. data/lib/epuber/compiler/file_finders/imaginary.rb +40 -35
  24. data/lib/epuber/compiler/file_resolver.rb +79 -89
  25. data/lib/epuber/compiler/file_stat.rb +4 -4
  26. data/lib/epuber/compiler/file_types/abstract_file.rb +4 -7
  27. data/lib/epuber/compiler/file_types/bade_file.rb +20 -15
  28. data/lib/epuber/compiler/file_types/coffee_script_file.rb +1 -1
  29. data/lib/epuber/compiler/file_types/css_file.rb +103 -0
  30. data/lib/epuber/compiler/file_types/generated_file.rb +1 -1
  31. data/lib/epuber/compiler/file_types/image_file.rb +4 -2
  32. data/lib/epuber/compiler/file_types/nav_file.rb +0 -1
  33. data/lib/epuber/compiler/file_types/opf_file.rb +0 -1
  34. data/lib/epuber/compiler/file_types/source_file.rb +8 -3
  35. data/lib/epuber/compiler/file_types/stylus_file.rb +4 -3
  36. data/lib/epuber/compiler/file_types/xhtml_file.rb +67 -13
  37. data/lib/epuber/compiler/generator.rb +1 -2
  38. data/lib/epuber/compiler/meta_inf_generator.rb +1 -1
  39. data/lib/epuber/compiler/nav_generator.rb +10 -11
  40. data/lib/epuber/compiler/opf_generator.rb +26 -27
  41. data/lib/epuber/compiler/problem.rb +12 -21
  42. data/lib/epuber/compiler/xhtml_processor.rb +63 -32
  43. data/lib/epuber/compiler.rb +77 -25
  44. data/lib/epuber/config.rb +16 -10
  45. data/lib/epuber/dsl/attribute.rb +17 -18
  46. data/lib/epuber/dsl/attribute_support.rb +7 -7
  47. data/lib/epuber/dsl/object.rb +19 -17
  48. data/lib/epuber/dsl/tree_object.rb +2 -3
  49. data/lib/epuber/epubcheck.rb +15 -0
  50. data/lib/epuber/from_file/bookspec_generator.rb +371 -0
  51. data/lib/epuber/from_file/encryption_handler.rb +146 -0
  52. data/lib/epuber/from_file/from_file_executor.rb +140 -0
  53. data/lib/epuber/from_file/nav_file.rb +163 -0
  54. data/lib/epuber/from_file/opf_file.rb +219 -0
  55. data/lib/epuber/helper.rb +0 -1
  56. data/lib/epuber/lockfile.rb +7 -9
  57. data/lib/epuber/plugin.rb +2 -3
  58. data/lib/epuber/ruby_extensions/match_data.rb +1 -1
  59. data/lib/epuber/ruby_extensions/thread.rb +1 -0
  60. data/lib/epuber/server/base.styl +0 -1
  61. data/lib/epuber/server/basic.styl +1 -30
  62. data/lib/epuber/server/handlers.rb +1 -1
  63. data/lib/epuber/server.rb +81 -80
  64. data/lib/epuber/third_party/bower.rb +5 -5
  65. data/lib/epuber/transformer/book_transformer.rb +108 -0
  66. data/lib/epuber/transformer/text_transformer.rb +4 -2
  67. data/lib/epuber/transformer.rb +4 -2
  68. data/lib/epuber/user_interface.rb +49 -38
  69. data/lib/epuber/vendor/hash_binding.rb +9 -2
  70. data/lib/epuber/vendor/ruby_templater.rb +4 -8
  71. data/lib/epuber/vendor/version.rb +12 -12
  72. data/lib/epuber/version.rb +1 -1
  73. metadata +79 -100
  74. data/lib/epuber/server/fonts/AvenirNext/AvenirNext-Bold.ttf +0 -0
  75. data/lib/epuber/server/fonts/AvenirNext/AvenirNext-BoldItalic.ttf +0 -0
  76. data/lib/epuber/server/fonts/AvenirNext/AvenirNext-Italic.ttf +0 -0
  77. 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
@@ -2,7 +2,6 @@
2
2
 
3
3
  module Epuber
4
4
  module Helper
5
-
6
5
  # @param [Book::TocItem] toc_item
7
6
  # @param [Compiler::FileResolver] file_resolver
8
7
  # @param [String] context_path
@@ -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
- hash = YAML.safe_load(File.read(file_path))
23
- else
24
- hash = {}
25
- end
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 = self.new(hash)
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 path [String]
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 == 0 # it can't be zero, this happens only when the match is at the beginning of file or string
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
 
@@ -6,6 +6,7 @@ class Thread
6
6
  def hash
7
7
  to_s.hash
8
8
  end
9
+
9
10
  def ==(other)
10
11
  to_s == other.to_s
11
12
  end
@@ -1,3 +1,2 @@
1
1
 
2
- main_font_family = AvenirNext
3
2
  text_color = #B87B4A
@@ -9,7 +9,7 @@ body, div, html, p
9
9
 
10
10
 
11
11
  body
12
- font-family main_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)
@@ -49,7 +49,7 @@ module Epuber
49
49
  #
50
50
  def handle_bade(file_path)
51
51
  [200, self.class.render_bade(file_path)]
52
- rescue => e
52
+ rescue StandardError => e
53
53
  env['sinatra.error'] = e
54
54
  ShowExceptions.new(self).call(env)
55
55
  end