epuber 0.3.5

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 (206) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +6 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +46 -0
  5. data/bin/epuber +10 -0
  6. data/epuber.gemspec +51 -0
  7. data/lib/epuber.rb +14 -0
  8. data/lib/epuber/book.rb +267 -0
  9. data/lib/epuber/book/contributor.rb +90 -0
  10. data/lib/epuber/book/file_request.rb +68 -0
  11. data/lib/epuber/book/target.rb +284 -0
  12. data/lib/epuber/book/toc_item.rb +130 -0
  13. data/lib/epuber/checker.rb +19 -0
  14. data/lib/epuber/checker/text_checker.rb +129 -0
  15. data/lib/epuber/checker_transformer_base.rb +71 -0
  16. data/lib/epuber/command.rb +65 -0
  17. data/lib/epuber/command/compile.rb +142 -0
  18. data/lib/epuber/command/init.rb +145 -0
  19. data/lib/epuber/command/server.rb +59 -0
  20. data/lib/epuber/compiler.rb +255 -0
  21. data/lib/epuber/compiler/compilation_context.rb +86 -0
  22. data/lib/epuber/compiler/file_finders/abstract.rb +270 -0
  23. data/lib/epuber/compiler/file_finders/imaginary.rb +167 -0
  24. data/lib/epuber/compiler/file_finders/normal.rb +22 -0
  25. data/lib/epuber/compiler/file_resolver.rb +316 -0
  26. data/lib/epuber/compiler/file_types/abstract_file.rb +119 -0
  27. data/lib/epuber/compiler/file_types/bade_file.rb +39 -0
  28. data/lib/epuber/compiler/file_types/container_xml_file.rb +26 -0
  29. data/lib/epuber/compiler/file_types/generated_file.rb +29 -0
  30. data/lib/epuber/compiler/file_types/ibooks_display_options_file.rb +31 -0
  31. data/lib/epuber/compiler/file_types/image_file.rb +42 -0
  32. data/lib/epuber/compiler/file_types/mime_type_file.rb +20 -0
  33. data/lib/epuber/compiler/file_types/nav_file.rb +42 -0
  34. data/lib/epuber/compiler/file_types/opf_file.rb +27 -0
  35. data/lib/epuber/compiler/file_types/source_file.rb +52 -0
  36. data/lib/epuber/compiler/file_types/static_file.rb +18 -0
  37. data/lib/epuber/compiler/file_types/stylus_file.rb +20 -0
  38. data/lib/epuber/compiler/file_types/xhtml_file.rb +102 -0
  39. data/lib/epuber/compiler/generator.rb +67 -0
  40. data/lib/epuber/compiler/meta_inf_generator.rb +41 -0
  41. data/lib/epuber/compiler/nav_generator.rb +201 -0
  42. data/lib/epuber/compiler/opf_generator.rb +284 -0
  43. data/lib/epuber/compiler/xhtml_processor.rb +254 -0
  44. data/lib/epuber/config.rb +133 -0
  45. data/lib/epuber/dsl/attribute.rb +248 -0
  46. data/lib/epuber/dsl/attribute_support.rb +130 -0
  47. data/lib/epuber/dsl/object.rb +145 -0
  48. data/lib/epuber/dsl/tree_object.rb +101 -0
  49. data/lib/epuber/helper.rb +19 -0
  50. data/lib/epuber/lockfile.rb +55 -0
  51. data/lib/epuber/plugin.rb +108 -0
  52. data/lib/epuber/ruby_extensions/match_data.rb +40 -0
  53. data/lib/epuber/ruby_extensions/thread.rb +13 -0
  54. data/lib/epuber/server.rb +614 -0
  55. data/lib/epuber/server/auto_refresh/auto_refresh.coffee +97 -0
  56. data/lib/epuber/server/auto_refresh/connector.coffee +125 -0
  57. data/lib/epuber/server/auto_refresh/protocol.coffee +41 -0
  58. data/lib/epuber/server/auto_refresh/reloader.coffee +261 -0
  59. data/lib/epuber/server/base.styl +3 -0
  60. data/lib/epuber/server/basic.styl +211 -0
  61. data/lib/epuber/server/book_content.styl +39 -0
  62. data/lib/epuber/server/default_cover.png +0 -0
  63. data/lib/epuber/server/fonts/AvenirNext/AvenirNext-Bold.ttf +0 -0
  64. data/lib/epuber/server/fonts/AvenirNext/AvenirNext-BoldItalic.ttf +0 -0
  65. data/lib/epuber/server/fonts/AvenirNext/AvenirNext-Italic.ttf +0 -0
  66. data/lib/epuber/server/fonts/AvenirNext/AvenirNext-Regular.ttf +0 -0
  67. data/lib/epuber/server/handlers.rb +67 -0
  68. data/lib/epuber/server/keyboard_control.coffee +6 -0
  69. data/lib/epuber/server/pages/book.bade +75 -0
  70. data/lib/epuber/server/pages/common.bade +59 -0
  71. data/lib/epuber/server/pages/files.bade +17 -0
  72. data/lib/epuber/server/pages/toc.bade +29 -0
  73. data/lib/epuber/server/support.coffee +10 -0
  74. data/lib/epuber/templates/template.bookspec +143 -0
  75. data/lib/epuber/third_party/bower.rb +22 -0
  76. data/lib/epuber/third_party/bower/bower.json +10 -0
  77. data/lib/epuber/third_party/bower/bower_components/cookies-js/bower.json +10 -0
  78. data/lib/epuber/third_party/bower/bower_components/cookies-js/dist/cookies.d.ts +33 -0
  79. data/lib/epuber/third_party/bower/bower_components/cookies-js/dist/cookies.js +173 -0
  80. data/lib/epuber/third_party/bower/bower_components/cookies-js/dist/cookies.min.js +6 -0
  81. data/lib/epuber/third_party/bower/bower_components/jquery/MIT-LICENSE.txt +21 -0
  82. data/lib/epuber/third_party/bower/bower_components/jquery/bower.json +28 -0
  83. data/lib/epuber/third_party/bower/bower_components/jquery/dist/jquery.js +9210 -0
  84. data/lib/epuber/third_party/bower/bower_components/jquery/dist/jquery.min.js +5 -0
  85. data/lib/epuber/third_party/bower/bower_components/jquery/dist/jquery.min.map +1 -0
  86. data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax.js +786 -0
  87. data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/jsonp.js +89 -0
  88. data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/load.js +75 -0
  89. data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/parseJSON.js +13 -0
  90. data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/parseXML.js +28 -0
  91. data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/script.js +64 -0
  92. data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/var/nonce.js +5 -0
  93. data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/var/rquery.js +3 -0
  94. data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/xhr.js +136 -0
  95. data/lib/epuber/third_party/bower/bower_components/jquery/src/attributes.js +11 -0
  96. data/lib/epuber/third_party/bower/bower_components/jquery/src/attributes/attr.js +141 -0
  97. data/lib/epuber/third_party/bower/bower_components/jquery/src/attributes/classes.js +158 -0
  98. data/lib/epuber/third_party/bower/bower_components/jquery/src/attributes/prop.js +94 -0
  99. data/lib/epuber/third_party/bower/bower_components/jquery/src/attributes/support.js +35 -0
  100. data/lib/epuber/third_party/bower/bower_components/jquery/src/attributes/val.js +161 -0
  101. data/lib/epuber/third_party/bower/bower_components/jquery/src/callbacks.js +205 -0
  102. data/lib/epuber/third_party/bower/bower_components/jquery/src/core.js +502 -0
  103. data/lib/epuber/third_party/bower/bower_components/jquery/src/core/access.js +60 -0
  104. data/lib/epuber/third_party/bower/bower_components/jquery/src/core/init.js +123 -0
  105. data/lib/epuber/third_party/bower/bower_components/jquery/src/core/parseHTML.js +39 -0
  106. data/lib/epuber/third_party/bower/bower_components/jquery/src/core/ready.js +97 -0
  107. data/lib/epuber/third_party/bower/bower_components/jquery/src/core/var/rsingleTag.js +4 -0
  108. data/lib/epuber/third_party/bower/bower_components/jquery/src/css.js +450 -0
  109. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/addGetHookIf.js +22 -0
  110. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/curCSS.js +57 -0
  111. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/defaultDisplay.js +70 -0
  112. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/hiddenVisibleSelectors.js +15 -0
  113. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/support.js +96 -0
  114. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/swap.js +28 -0
  115. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/var/cssExpand.js +3 -0
  116. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/var/getStyles.js +12 -0
  117. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/var/isHidden.js +13 -0
  118. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/var/rmargin.js +3 -0
  119. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/var/rnumnonpx.js +5 -0
  120. data/lib/epuber/third_party/bower/bower_components/jquery/src/data.js +178 -0
  121. data/lib/epuber/third_party/bower/bower_components/jquery/src/data/Data.js +181 -0
  122. data/lib/epuber/third_party/bower/bower_components/jquery/src/data/accepts.js +20 -0
  123. data/lib/epuber/third_party/bower/bower_components/jquery/src/data/var/data_priv.js +5 -0
  124. data/lib/epuber/third_party/bower/bower_components/jquery/src/data/var/data_user.js +5 -0
  125. data/lib/epuber/third_party/bower/bower_components/jquery/src/deferred.js +149 -0
  126. data/lib/epuber/third_party/bower/bower_components/jquery/src/deprecated.js +13 -0
  127. data/lib/epuber/third_party/bower/bower_components/jquery/src/dimensions.js +50 -0
  128. data/lib/epuber/third_party/bower/bower_components/jquery/src/effects.js +648 -0
  129. data/lib/epuber/third_party/bower/bower_components/jquery/src/effects/Tween.js +114 -0
  130. data/lib/epuber/third_party/bower/bower_components/jquery/src/effects/animatedSelector.js +13 -0
  131. data/lib/epuber/third_party/bower/bower_components/jquery/src/event.js +868 -0
  132. data/lib/epuber/third_party/bower/bower_components/jquery/src/event/ajax.js +13 -0
  133. data/lib/epuber/third_party/bower/bower_components/jquery/src/event/alias.js +39 -0
  134. data/lib/epuber/third_party/bower/bower_components/jquery/src/event/support.js +9 -0
  135. data/lib/epuber/third_party/bower/bower_components/jquery/src/exports/amd.js +24 -0
  136. data/lib/epuber/third_party/bower/bower_components/jquery/src/exports/global.js +32 -0
  137. data/lib/epuber/third_party/bower/bower_components/jquery/src/intro.js +44 -0
  138. data/lib/epuber/third_party/bower/bower_components/jquery/src/jquery.js +37 -0
  139. data/lib/epuber/third_party/bower/bower_components/jquery/src/manipulation.js +580 -0
  140. data/lib/epuber/third_party/bower/bower_components/jquery/src/manipulation/_evalUrl.js +18 -0
  141. data/lib/epuber/third_party/bower/bower_components/jquery/src/manipulation/support.js +32 -0
  142. data/lib/epuber/third_party/bower/bower_components/jquery/src/manipulation/var/rcheckableType.js +3 -0
  143. data/lib/epuber/third_party/bower/bower_components/jquery/src/offset.js +207 -0
  144. data/lib/epuber/third_party/bower/bower_components/jquery/src/outro.js +1 -0
  145. data/lib/epuber/third_party/bower/bower_components/jquery/src/queue.js +142 -0
  146. data/lib/epuber/third_party/bower/bower_components/jquery/src/queue/delay.js +22 -0
  147. data/lib/epuber/third_party/bower/bower_components/jquery/src/selector-native.js +172 -0
  148. data/lib/epuber/third_party/bower/bower_components/jquery/src/selector-sizzle.js +14 -0
  149. data/lib/epuber/third_party/bower/bower_components/jquery/src/selector.js +1 -0
  150. data/lib/epuber/third_party/bower/bower_components/jquery/src/serialize.js +111 -0
  151. data/lib/epuber/third_party/bower/bower_components/jquery/src/sizzle/dist/sizzle.js +2067 -0
  152. data/lib/epuber/third_party/bower/bower_components/jquery/src/sizzle/dist/sizzle.min.js +3 -0
  153. data/lib/epuber/third_party/bower/bower_components/jquery/src/sizzle/dist/sizzle.min.map +1 -0
  154. data/lib/epuber/third_party/bower/bower_components/jquery/src/traversing.js +199 -0
  155. data/lib/epuber/third_party/bower/bower_components/jquery/src/traversing/findFilter.js +100 -0
  156. data/lib/epuber/third_party/bower/bower_components/jquery/src/traversing/var/rneedsContext.js +6 -0
  157. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/arr.js +3 -0
  158. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/class2type.js +4 -0
  159. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/concat.js +5 -0
  160. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/hasOwn.js +5 -0
  161. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/indexOf.js +5 -0
  162. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/pnum.js +3 -0
  163. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/push.js +5 -0
  164. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/rnotwhite.js +3 -0
  165. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/slice.js +5 -0
  166. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/strundefined.js +3 -0
  167. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/support.js +4 -0
  168. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/toString.js +5 -0
  169. data/lib/epuber/third_party/bower/bower_components/jquery/src/wrap.js +79 -0
  170. data/lib/epuber/third_party/bower/bower_components/keymaster/MIT-LICENSE +20 -0
  171. data/lib/epuber/third_party/bower/bower_components/keymaster/Makefile +4 -0
  172. data/lib/epuber/third_party/bower/bower_components/keymaster/README.markdown +212 -0
  173. data/lib/epuber/third_party/bower/bower_components/keymaster/bower.json +27 -0
  174. data/lib/epuber/third_party/bower/bower_components/keymaster/keymaster.js +296 -0
  175. data/lib/epuber/third_party/bower/bower_components/keymaster/package.json +11 -0
  176. data/lib/epuber/third_party/bower/bower_components/keymaster/test.html +93 -0
  177. data/lib/epuber/third_party/bower/bower_components/spin.js/LICENSE.txt +21 -0
  178. data/lib/epuber/third_party/bower/bower_components/spin.js/README.md +21 -0
  179. data/lib/epuber/third_party/bower/bower_components/spin.js/bower.json +18 -0
  180. data/lib/epuber/third_party/bower/bower_components/spin.js/jquery.spin.js +80 -0
  181. data/lib/epuber/third_party/bower/bower_components/spin.js/spin.js +337 -0
  182. data/lib/epuber/third_party/bower/bower_components/uri.js/LICENSE.txt +21 -0
  183. data/lib/epuber/third_party/bower/bower_components/uri.js/README.md +534 -0
  184. data/lib/epuber/third_party/bower/bower_components/uri.js/bower.json +16 -0
  185. data/lib/epuber/third_party/bower/bower_components/uri.js/contributing.md +19 -0
  186. data/lib/epuber/third_party/bower/bower_components/uri.js/src/IPv6.js +188 -0
  187. data/lib/epuber/third_party/bower/bower_components/uri.js/src/SecondLevelDomains.js +241 -0
  188. data/lib/epuber/third_party/bower/bower_components/uri.js/src/URI.fragmentQuery.js +104 -0
  189. data/lib/epuber/third_party/bower/bower_components/uri.js/src/URI.fragmentURI.js +97 -0
  190. data/lib/epuber/third_party/bower/bower_components/uri.js/src/URI.js +2115 -0
  191. data/lib/epuber/third_party/bower/bower_components/uri.js/src/URI.min.js +86 -0
  192. data/lib/epuber/third_party/bower/bower_components/uri.js/src/URITemplate.js +499 -0
  193. data/lib/epuber/third_party/bower/bower_components/uri.js/src/jquery.URI.js +235 -0
  194. data/lib/epuber/third_party/bower/bower_components/uri.js/src/jquery.URI.min.js +7 -0
  195. data/lib/epuber/third_party/bower/bower_components/uri.js/src/punycode.js +508 -0
  196. data/lib/epuber/transformer.rb +19 -0
  197. data/lib/epuber/transformer/text_transformer.rb +60 -0
  198. data/lib/epuber/user_interface.rb +186 -0
  199. data/lib/epuber/vendor/globals_context.rb +26 -0
  200. data/lib/epuber/vendor/hash_binding.rb +26 -0
  201. data/lib/epuber/vendor/nokogiri_extensions.rb +30 -0
  202. data/lib/epuber/vendor/ruby_templater.rb +71 -0
  203. data/lib/epuber/vendor/size.rb +20 -0
  204. data/lib/epuber/vendor/version.rb +83 -0
  205. data/lib/epuber/version.rb +4 -0
  206. metadata +556 -0
@@ -0,0 +1,254 @@
1
+ # encoding: utf-8
2
+
3
+ require 'nokogiri'
4
+ require 'uri'
5
+
6
+
7
+ module Epuber
8
+ class Compiler
9
+ require_relative 'file_finders/normal'
10
+
11
+ class XHTMLProcessor
12
+ class UnparseableLinkError < StandardError; end
13
+
14
+ # Method for parsing incomplete XML, supports multiple root elements
15
+ #
16
+ # @warning Because of nature of XML, when input string don't contain root element, it will create own called `body`, since it will be used in next steps.
17
+ #
18
+ # @param [String] text input XHTML text
19
+ #
20
+ # @return [Nokogiri::XML::Document] parsed document
21
+ #
22
+ def self.xml_document_from_string(text, file_path = nil)
23
+ if /\A[\n\r ]+(<\?xml)/ =~ text
24
+ UI.warning('XML header must be at the beginning of document', location: UI::Location.new(file_path, 1))
25
+
26
+ text = text.lstrip
27
+ end
28
+
29
+ doc = Nokogiri::XML(text)
30
+ doc.encoding = 'UTF-8'
31
+ doc.file_path = file_path
32
+
33
+ fragment = Nokogiri::XML.fragment(text)
34
+ root_elements = fragment.children.select { |el| el.element? }
35
+
36
+ if root_elements.count == 1
37
+ doc.root = root_elements.first
38
+ elsif fragment.at_css('body').nil?
39
+ doc.root = doc.create_element('body')
40
+
41
+ fragment.children.select do |child|
42
+ child.element? || child.comment? || child.text?
43
+ end.each do |child|
44
+ doc.root.add_child(child)
45
+ end
46
+ end
47
+
48
+ doc
49
+ end
50
+
51
+
52
+ # Method to add all missing items in XML root
53
+ #
54
+ # Required items:
55
+ # - html (with all namespaces and other attributes)
56
+ # - body
57
+ # - head (with title)
58
+ #
59
+ # @param [Nokogiri::XML::Document] xhtml_doc input XML document to work with
60
+ # @param [String] title title of this document, since this is required by EPUB specification
61
+ # @param [Epuber::Version] epub_version version of result EPUB
62
+ #
63
+ # @return nil
64
+ #
65
+ def self.add_missing_root_elements(xhtml_doc, title, epub_version)
66
+ # add missing body element
67
+ if xhtml_doc.at_css('body').nil?
68
+ xhtml_doc.root.surround_with_element('body')
69
+ end
70
+
71
+ # add missing root html element
72
+ if xhtml_doc.at_css('html').nil?
73
+ attrs = {}
74
+ attrs['xmlns'] = 'http://www.w3.org/1999/xhtml'
75
+ attrs['xmlns:epub'] = 'http://www.idpf.org/2007/ops' if epub_version >= 3
76
+ xhtml_doc.root.surround_with_element('html', attrs)
77
+ end
78
+
79
+ # add missing head in html
80
+ if xhtml_doc.at_css('html > head').nil?
81
+ html = xhtml_doc.css('html').first
82
+ head = xhtml_doc.create_element('head')
83
+ head << xhtml_doc.create_element('title', title)
84
+ head << xhtml_doc.create_element('meta', charset: 'utf-8') if epub_version >= 3.0
85
+
86
+ html.children.first.before(head)
87
+ end
88
+ end
89
+
90
+ # Method for adding style sheets with links, method will not add duplicate items
91
+ #
92
+ # @param [Nokogiri::XML::Document] xhtml_doc input XML document to work with
93
+ # @param [Array<String>] styles links to files
94
+ #
95
+ # @return nil
96
+ #
97
+ def self.add_styles(xhtml_doc, styles)
98
+ head = xhtml_doc.at_css('html > head')
99
+ old_links = head.css('link[rel="stylesheet"]').map { |node| node['href'] }
100
+
101
+ links_to_add = styles - old_links
102
+
103
+ links_to_add.each do |path|
104
+ head << xhtml_doc.create_element('link', href: path, rel: 'stylesheet', type: 'text/css')
105
+ end
106
+ end
107
+
108
+ # Adds viewport meta tag to head of some document, but only if there is not some existing tag
109
+ #
110
+ # @param [Nokogiri::XML::Document] xhtml_doc
111
+ # @param [Epuber::Size] viewport_size
112
+ #
113
+ def self.add_viewport(xhtml_doc, viewport_size)
114
+ head = xhtml_doc.at_css('html > head')
115
+ return unless head.at_css("meta[name='viewport']").nil?
116
+
117
+ s = viewport_size
118
+ head << xhtml_doc.create_element('meta', name: 'viewport', content: "width=#{s.width},height=#{s.height}")
119
+ end
120
+
121
+ # Method which will resolve path to file from pattern
122
+ #
123
+ # @param [String] path pattern or path of the file
124
+ # @param [Symbol | Array<Symbol>] groups groups of the searching file, could be for example :image when searching for file from tag <img>
125
+ # @param [String] file_path path to file from which is searching for other file
126
+ # @param [Epuber::Compiler::FileFinder] file_finder finder for searching for files
127
+ #
128
+ # @raise UnparseableLinkError, FileFinder::FileNotFoundError, FileFinder::MultipleFilesFoundError
129
+ #
130
+ # @return [URI] resolved path to file or remote web page
131
+ #
132
+ def self.resolved_link_to_file(path, groups, file_path, file_finder)
133
+ raise FileFinders::FileNotFoundError.new(path, file_path) if path.empty?
134
+
135
+ begin
136
+ uri = URI(path)
137
+ rescue URI::InvalidURIError
138
+ begin
139
+ uri = URI(URI::encode(path))
140
+ rescue URI::InvalidURIError
141
+ # skip not valid uri
142
+ raise UnparseableLinkError, "Unparseable link `#{path}`"
143
+ end
144
+ end
145
+
146
+ # skip uri with scheme (links to web pages)
147
+ return uri unless uri.scheme.nil?
148
+
149
+ # skip empty path
150
+ return uri if uri.path.empty? && !uri.fragment.nil? && !uri.fragment.empty?
151
+
152
+ uri.path = file_finder.find_file(uri.path, groups: groups, context_path: file_path)
153
+
154
+ uri
155
+ end
156
+
157
+ # Resolves all links to files in XHTML document and returns the valid and resolved versions
158
+ #
159
+ # @param [Nokogiri::XML::Document] xhtml_doc input XML document to work with
160
+ # @param [String] tag_name CSS selector for tag
161
+ # @param [String] attribute_name name of attribute
162
+ # @param [Symbol | Array<Symbol>] groups groups of the searching file, could be for example :image when searching for file from tag <img>
163
+ # @param [String] file_path path to file from which is searching for other file
164
+ # @param [Epuber::Compiler::FileFinder] file_finder finder for searching for files
165
+ #
166
+ # @return [Array<URI>] resolved links
167
+ #
168
+ def self.resolve_links_for(xhtml_doc, tag_name, attribute_name, groups, file_path, file_finder)
169
+ founded_links = []
170
+
171
+ xhtml_doc.css("#{tag_name}[#{attribute_name}]").each do |node|
172
+ begin
173
+ src = node[attribute_name]
174
+ # @type [String] src
175
+
176
+ next if src.nil?
177
+
178
+ target_file = resolved_link_to_file(src, groups, file_path, file_finder)
179
+ founded_links << target_file
180
+
181
+ node[attribute_name] = target_file.to_s
182
+ rescue UnparseableLinkError, FileFinders::FileNotFoundError, FileFinders::MultipleFilesFoundError => e
183
+ UI.warning(e.to_s, location: node)
184
+
185
+ # skip not found files
186
+ next
187
+ end
188
+ end
189
+
190
+ founded_links
191
+ end
192
+
193
+ # Resolves all links to files in XHTML document and returns the valid and resolved versions
194
+ #
195
+ # @param [Nokogiri::XML::Document] xhtml_doc input XML document to work with
196
+ # @param [String] file_path path to file from which is searching for other file
197
+ # @param [Epuber::Compiler::FileFinder] file_finder finder for searching for files
198
+ #
199
+ # @return [Array<URI>] resolved links
200
+ #
201
+ def self.resolve_links(xhtml_doc, file_path, file_finder)
202
+ [
203
+ resolve_links_for(xhtml_doc, 'a', 'href', :text, file_path, file_finder),
204
+ resolve_links_for(xhtml_doc, 'map > area', 'href', :text, file_path, file_finder),
205
+ ].flatten
206
+ end
207
+
208
+ # @param [Nokogiri::XML::Document] xhtml_doc input XML document to work with
209
+ #
210
+ # @return [Bool]
211
+ #
212
+ def self.using_javascript?(xhtml_doc)
213
+ !xhtml_doc.at_css('script').nil?
214
+ end
215
+
216
+ # @param [Nokogiri::XML::Document] xhtml_doc
217
+ # @param [String] file_path path of referring file
218
+ # @param [FileResolver] file_resolver
219
+ #
220
+ # @return nil
221
+ #
222
+ def self.resolve_images(xhtml_doc, file_path, file_resolver)
223
+ dirname = File.dirname(file_path)
224
+
225
+ xhtml_doc.css('img').each do |img|
226
+ path = img['src']
227
+ next if path.nil?
228
+
229
+ begin
230
+ new_path = file_resolver.dest_finder.find_file(path, groups: :image, context_path: dirname)
231
+
232
+ rescue UnparseableLinkError, FileFinders::FileNotFoundError, FileFinders::MultipleFilesFoundError
233
+ begin
234
+ new_path = resolved_link_to_file(path, :image, dirname, file_resolver.source_finder).to_s
235
+ pkg_abs_path = File.expand_path(new_path, dirname).unicode_normalize
236
+ pkg_new_path = Pathname.new(pkg_abs_path).relative_path_from(Pathname.new(file_resolver.source_path)).to_s
237
+
238
+ file = FileTypes::ImageFile.new(pkg_new_path)
239
+ file.path_type = :manifest
240
+ file_resolver.add_file(file)
241
+
242
+ rescue UnparseableLinkError, FileFinders::FileNotFoundError, FileFinders::MultipleFilesFoundError => e
243
+ UI.warning(e.to_s, location: img)
244
+
245
+ next
246
+ end
247
+ end
248
+
249
+ img['src'] = new_path
250
+ end
251
+ end
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,133 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative 'lockfile'
4
+
5
+
6
+ module Epuber
7
+ class Config
8
+ WORKING_PATH = '.epuber'
9
+
10
+ # @return [String]
11
+ #
12
+ def project_path
13
+ @project_path ||= Dir.pwd.unicode_normalize
14
+ end
15
+
16
+ # @param of_file [String] absolute path to file
17
+ #
18
+ # @return [String] relative path to file from root of project
19
+ #
20
+ def pretty_path_from_project(of_file)
21
+ Pathname.new(of_file.unicode_normalize).relative_path_from(Pathname.new(project_path))
22
+ end
23
+
24
+ # @return [String]
25
+ #
26
+ def working_path
27
+ @working_path ||= File.join(project_path, WORKING_PATH)
28
+ end
29
+
30
+ # @return [String]
31
+ #
32
+ def bookspec_path
33
+ @bookspec_path ||= find_all_bookspecs.first
34
+ end
35
+
36
+ # @return [String]
37
+ #
38
+ def bookspec_lockfile_path
39
+ "#{bookspec_path}.lock"
40
+ end
41
+
42
+ # @return [Array<String>]
43
+ #
44
+ def find_all_bookspecs
45
+ Dir.chdir(project_path) do
46
+ Dir.glob('*.bookspec').map do |path|
47
+ File.expand_path(path)
48
+ end
49
+ end
50
+ end
51
+
52
+ # @return [Epuber::Book]
53
+ #
54
+ def bookspec
55
+ require_relative 'book'
56
+ @bookspec ||= (
57
+ book = Epuber::Book.from_file(bookspec_path)
58
+ book.finish_toc
59
+ book.validate
60
+ book.freeze
61
+ book
62
+ )
63
+ end
64
+
65
+ # @param [Epuber::Book] bookspec
66
+ #
67
+ # @return [Epuber::Book]
68
+ #
69
+ def bookspec=(bookspec)
70
+ @bookspec = bookspec
71
+ end
72
+
73
+ # @return [Epuber::Lockfile]
74
+ #
75
+ def bookspec_lockfile
76
+ @bookspec_lockfile ||= (
77
+ lockfile = Lockfile.from_file(bookspec_lockfile_path)
78
+ lockfile.version = Epuber::VERSION
79
+ lockfile
80
+ )
81
+ end
82
+
83
+ # @return nil
84
+ #
85
+ def save_lockfile
86
+ bookspec_lockfile.write_to_file
87
+ end
88
+
89
+ # @param target [Epuber::Book::Target]
90
+ #
91
+ # @return [String]
92
+ #
93
+ def build_path(target)
94
+ File.join(working_path, 'build', target.name.to_s)
95
+ end
96
+
97
+ # @param target [Epuber::Book::Target]
98
+ #
99
+ # @return [String]
100
+ #
101
+ def release_build_path(target)
102
+ File.join(working_path, 'release_build', target.name.to_s)
103
+ end
104
+
105
+ # ---------------------------------------------------------------------------------------------------------------- #
106
+
107
+ # Singleton
108
+ #
109
+ # @return [Epuber::Config]
110
+ #
111
+ def self.instance
112
+ @instance ||= new
113
+ end
114
+
115
+ class << self
116
+ attr_accessor :test
117
+
118
+ def test?
119
+ test
120
+ end
121
+ end
122
+
123
+ self.test = false
124
+ end
125
+ end
126
+
127
+ # HACK: this should be done with nicer way
128
+ #
129
+ at_exit do
130
+ unless Epuber::Config.test?
131
+ Epuber::Config.instance.save_lockfile
132
+ end
133
+ end
@@ -0,0 +1,248 @@
1
+ # encoding: utf-8
2
+
3
+ require 'date'
4
+
5
+ require 'active_support/core_ext/string/inflections'
6
+
7
+ module Epuber
8
+ module DSL
9
+ # Stores the information of an attribute. It also provides logic to implement any required logic.
10
+ #
11
+ class Attribute
12
+ # @return [Symbol] name of attribute
13
+ #
14
+ attr_reader :name
15
+
16
+ # rubocop:disable Metrics/ParameterLists
17
+
18
+ # Returns a new attribute initialized with the given options.
19
+ #
20
+ # @param name [Symbol]
21
+ #
22
+ # @see #name
23
+ #
24
+ # @raise If there are unrecognized options.
25
+ #
26
+ def initialize(name, inherited: false,
27
+ root_only: false,
28
+ required: false,
29
+ singularize: false,
30
+ file_patterns: false,
31
+ container: nil,
32
+ keys: nil,
33
+ default_value: nil,
34
+ auto_convert: {},
35
+ types: nil)
36
+
37
+ @name = name
38
+
39
+ @inherited = inherited
40
+ @root_only = root_only
41
+ @required = required
42
+ @singularize = singularize
43
+ @file_patterns = file_patterns
44
+ @container = container
45
+ @keys = keys
46
+ @default_value = default_value
47
+ @auto_convert = auto_convert
48
+ @types = if !types.nil?
49
+ types
50
+ elsif @default_value
51
+ [@default_value.class]
52
+ else
53
+ [String]
54
+ end
55
+ end
56
+
57
+ # rubocop:enable Metrics/ParameterLists
58
+
59
+ # @return [String] A string representation suitable for UI.
60
+ #
61
+ def to_s
62
+ "Attribute `#{name}`"
63
+ end
64
+
65
+ # @return [String] A string representation suitable for debugging.
66
+ #
67
+ def inspect
68
+ "<#{self.class} name=#{name} types=#{types}>"
69
+ end
70
+
71
+ #---------------------------------------------------------------------#
72
+
73
+ # @!group Options
74
+
75
+ # @return [Array<Class>] the list of the classes of the values supported by the attribute writer.
76
+ # If not specified defaults to [String].
77
+ #
78
+ attr_reader :types
79
+
80
+ # @return [Array<Class>] the list of the classes of the values supported by the attribute, including
81
+ # the container.
82
+ #
83
+ def supported_types
84
+ @supported_types ||= @types.dup.push(container).compact
85
+ end
86
+
87
+ # @return [Class] if defined it can be [Array] or [Hash]. It is used as default initialization value
88
+ # and to automatically wrap other values to arrays.
89
+ #
90
+ attr_reader :container
91
+
92
+ # @return [Array, Hash] the list of the accepted keys for an attribute wrapped by a Hash.
93
+ #
94
+ # @note A hash is accepted to group the keys associated only with certain keys (see the source
95
+ # attribute of a Book).
96
+ #
97
+ attr_reader :keys
98
+
99
+ # @return [Object] if the attribute follows configuration over convention it can specify a default value.
100
+ #
101
+ # @note The default value is not automatically wrapped and should be specified within the container
102
+ # if any.
103
+ #
104
+ attr_reader :default_value
105
+
106
+ # @return [Bool] whether the specification should be considered invalid if a value for the attribute
107
+ # is not specified.
108
+ #
109
+ attr_reader :required
110
+ alias_method :required?, :required
111
+
112
+ # @return [Bool] whether the attribute should be specified only on the root specification.
113
+ #
114
+ attr_reader :root_only
115
+ alias_method :root_only?, :root_only
116
+
117
+
118
+ # @return [Bool] whether there should be a singular alias for the attribute writer.
119
+ #
120
+ attr_reader :singularize
121
+ alias_method :singularize?, :singularize
122
+
123
+ # @return [Bool] whether the attribute describes file patterns.
124
+ #
125
+ # @note This is mostly used by the linter.
126
+ #
127
+ attr_reader :file_patterns
128
+ alias_method :file_patterns?, :file_patterns
129
+
130
+ # @return [Bool] defines whether the attribute reader should join the values with the parent.
131
+ #
132
+ # @note Attributes stored in wrappers are always inherited.
133
+ #
134
+ def inherited?
135
+ !root_only? && @inherited
136
+ end
137
+
138
+ #---------------------------------------------------------------------#
139
+
140
+ # @return [String] the name of the setter method for the attribute.
141
+ #
142
+ def writer_name
143
+ "#{name}="
144
+ end
145
+
146
+ # @return [String] an aliased attribute writer offered for convenience on the DSL.
147
+ #
148
+ def writer_singular_form
149
+ "#{name.to_s.singularize}=" if singularize?
150
+ end
151
+
152
+ #---------------------------------------------------------------------#
153
+
154
+ # @!group Values validation
155
+
156
+ # Validates the value for an attribute. This validation should be performed before the value is
157
+ # prepared or wrapped.
158
+ #
159
+ # @note The this is called before preparing the value.
160
+ #
161
+ # @raise If the type is not in the allowed list.
162
+ #
163
+ # @return [void]
164
+ #
165
+ def validate_type(value)
166
+ return if value.nil?
167
+ return if supported_types.any? { |klass| value.class == klass }
168
+
169
+ raise StandardError, "Non acceptable type `#{value.class}` for #{self}. Allowed types: `#{types.inspect}`"
170
+ end
171
+
172
+ # Validates a value before storing.
173
+ #
174
+ # @raise If a root only attribute is set in a subspec.
175
+ #
176
+ # @raise If a unknown key is added to a hash.
177
+ #
178
+ # @return [void]
179
+ #
180
+ def validate_for_writing(spec, value)
181
+ if root_only? && !spec.root?
182
+ raise StandardError, "Can't set `#{name}` attribute for subspecs (in `#{spec.name}`)."
183
+ end
184
+
185
+ if keys
186
+ value.keys.each do |key|
187
+ unless allowed_keys.include?(key)
188
+ raise StandardError, "Unknown key `#{key}` for #{self}. Allowed keys: `#{allowed_keys.inspect}`"
189
+ end
190
+ end
191
+ end
192
+
193
+ # @return [Array] the flattened list of the allowed keys for the hash of a given specification.
194
+ #
195
+ def allowed_keys
196
+ if keys.is_a?(Hash)
197
+ keys.keys.concat(keys.values.flatten.compact)
198
+ else
199
+ keys
200
+ end
201
+ end
202
+ end
203
+
204
+
205
+ #---------------------------------------------------------------------#
206
+
207
+ # @!group Automatic conversion
208
+
209
+ # Converts value to compatible type of attribute
210
+ #
211
+ # Can be configured with option :auto_convert
212
+ # Supports conversion from type to type, eg `{ String => Fixnum }`
213
+ # also from types to type eg `{ [String, Date] => Fixnum }`
214
+ # Supports custom conversion with Proc, eg `{ String => lambda { |value| value.to_s } }`
215
+ # also with multiple types
216
+ #
217
+ def converted_value(value)
218
+ begin
219
+ validate_type(value)
220
+ rescue StandardError
221
+ raise if @auto_convert.nil?
222
+ dest_class = @auto_convert[value.class]
223
+
224
+ if dest_class.nil?
225
+ array_keys = @auto_convert.select { |k, _v| k.is_a?(Array) }
226
+ array_keys_with_type = array_keys.select { |k, _v| k.include?(value.class) }
227
+
228
+ if array_keys_with_type.count > 0
229
+ dest_class = array_keys_with_type.values.first
230
+ end
231
+ end
232
+
233
+ if dest_class.respond_to?(:call)
234
+ return dest_class.call(value)
235
+ elsif dest_class.respond_to?(:parse)
236
+ return dest_class.parse(value)
237
+ elsif dest_class.respond_to?(:new)
238
+ return dest_class.new(value)
239
+ else
240
+ raise StandardError, "Object/class #{dest_class} doesn't support any convert method (#call, .parse or implicit .new)"
241
+ end
242
+ end
243
+
244
+ value
245
+ end
246
+ end
247
+ end
248
+ end