persie 0.0.1.alpha1 → 0.0.1.alpha.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ea791aeacddc16c2a1f0b7e81bce072725656bce
4
- data.tar.gz: 4605919698cbe7adea1a47865e744c9709b59a51
3
+ metadata.gz: 41585c7758fb18afab82bae8efd74601ea83b192
4
+ data.tar.gz: 33669806f0da31cc015369764b1698ef63e54ec0
5
5
  SHA512:
6
- metadata.gz: 0de1f27767e07275e7c6a01ea9eeb731da2c70fa64222876692a339ff81ad0b67ad7af67e64396a2270765c2f232bf91c706aaba778a638f2e0083be007c51e4
7
- data.tar.gz: 2774549129372c32847f0137b9bea1e4ff8cf7b7f223640476efd169db905a499ff76a70f578427a138d4c0b1831f61d613c2a15aa6716b8ec7803092e59e8f7
6
+ metadata.gz: f3a40f880ba33b0212a01db6e136e99bf38caa9aeb0b1024e7474656c722261f6bab08f93b21a13bd3725d62238a33cc7479577adf400c277e9e9ee18a5d3e4d
7
+ data.tar.gz: 03dcbd66639256a4af1f7fd5b58f9306ad6c932d3589fac3198eeb4f42f2be43986f2e0d4e100ab8c4cbad7c17e20c08ac7bff8e2bcc0eae8f0ff6aa69f3ca1d
@@ -45,8 +45,8 @@ module Persie
45
45
  # In this method, node == node.document
46
46
  # In other methods, you should use node.document
47
47
  ebook_format = node.attr('ebook-format')
48
- result = []
49
48
 
49
+ result = []
50
50
  result << '<!DOCTYPE html>'
51
51
  lang_attr = %(lang="#{node.attr('lang', 'en')}")
52
52
  if EPUB_FORMATS.include? ebook_format
@@ -54,29 +54,25 @@ module Persie
54
54
  else
55
55
  result << %(<html xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.w3.org/1999/xhtml" #{lang_attr}>)
56
56
  end
57
- result << %(<head>)
58
- content_type = if EPUB_FORMATS.include? ebook_format
59
- 'application/xhtml+xml'
60
- else
61
- 'text/html'
62
- end
63
- result << %(<meta charset="#{node.attr 'encoding', 'UTF-8'}"/>)
57
+ result << '<head>'
58
+ result << %(<meta charset="#{node.attr('encoding', 'UTF-8')}"/>)
64
59
  result << %(<title>#{node.doctitle(:sanitize => true) || node.attr('untitled-label')}</title>)
65
- if ebook_format === 'site'
60
+ if ebook_format === 'html'
66
61
  result << %(<meta http-equiv="X-UA-Compatible" content="IE=edge"/>)
67
62
  result << %(<meta name="viewport" content="width=device-width, initial-scale=1.0"/>)
68
63
  end
69
- result << %(<meta name="generator" content="Persie #{node.attr 'persie-version'}"/>)
64
+ result << %(<meta name="generator" content="persie #{node.attr 'persie-version'}"/>)
70
65
  result << %(<meta name="date" content="#{Time.parse(node.revdate).iso8601}"/>) if node.attr? 'revdate'
71
66
  result << %(<meta name="description" content="#{node.attr 'description'}"/>) if node.attr? 'description'
72
67
  result << %(<meta name="keywords" content="#{node.attr 'keywords'}"/>) if node.attr? 'keywords'
73
68
  result << %(<meta name="author" content="#{node.attr 'author'}"/>) if node.attr? 'author'
69
+ result << %(<meta name="translator" content="#{node.attr 'translator'}"/>) if node.attr? 'translator'
74
70
  result << %(<meta name="copyright" content="#{node.attr 'copyright'}"/>) if node.attr? 'copyright'
75
71
 
76
72
  stylesheet_path = case ebook_format
77
73
  when 'pdf'
78
- File.join(node.attr('themes-dir'), ebook_format, 'pdf.css')
79
- when 'site'
74
+ File.join(node.attr('themes-dir'), 'pdf', 'pdf.css')
75
+ when 'html'
80
76
  'style.css'
81
77
  else
82
78
  "#{ebook_format}.css"
@@ -115,8 +111,8 @@ MathJax.Hub.Config({
115
111
  body_attrs << %(class="sample") if node.attr? 'is-sample'
116
112
  result << %(<body #{body_attrs * ' '}>)
117
113
 
118
- result << cover(node)
119
- result << titlepage(node)
114
+ result << cover(node) unless multiple_pages_html?(node)
115
+ result << titlepage(node) unless multiple_pages_html?(node)
120
116
  result << toc(node)
121
117
 
122
118
  if node.attr? 'is-sample'
@@ -125,10 +121,10 @@ MathJax.Hub.Config({
125
121
  result << node.content
126
122
  end
127
123
 
128
- # Display footnotes in single page site
129
- if single_page_site?(node)
124
+ # Display footnotes in single page html
125
+ if single_page_html?(node)
130
126
  if node.footnotes? && !(node.attr? 'nofootnotes')
131
- result << "<div class=\"footnotes\">\n<ol>"
127
+ result << %(<div class="footnotes">\n<ol>)
132
128
  node.footnotes.each do |fn|
133
129
  ref = %( <a href="#fn-ref-#{fn.index}">&#8617;</a>)
134
130
  result << %(<li data-type="footnote" id="fn-#{fn.index}">#{fn.text}#{ref}</li>)
@@ -143,7 +139,6 @@ MathJax.Hub.Config({
143
139
  result * "\n"
144
140
  end
145
141
 
146
- # FIXME: need review
147
142
  def embedded(node)
148
143
  result = []
149
144
  if !node.notitle && node.has_header?
@@ -154,15 +149,11 @@ MathJax.Hub.Config({
154
149
  result << node.content
155
150
 
156
151
  if node.footnotes? && !(node.attr? 'nofootnotes')
157
- result << %(<div id="footnotes">
158
- <hr/>)
152
+ result << %(<div class="footnotes">\n<ol>)
159
153
  node.footnotes.each do |footnote|
160
- result << %(<div class="footnote" id="_footnote_#{footnote.index}">
161
- <a href="#_footnoteref_#{footnote.index}">#{footnote.index}</a> #{footnote.text}
162
- </div>)
154
+ result << %(<li data-type="footnote" id="fn-#{footnote.index}">#{footnote.text}</li>)
163
155
  end
164
-
165
- result << '</div>'
156
+ result << "</ol>\n</div>"
166
157
  end
167
158
 
168
159
  result * "\n"
@@ -218,6 +209,7 @@ MathJax.Hub.Config({
218
209
  else
219
210
  slevel - 1
220
211
  end
212
+
221
213
  wrapper_tag = if data_type != 'part'
222
214
  'section'
223
215
  else
@@ -268,20 +260,26 @@ MathJax.Hub.Config({
268
260
  result * "\n"
269
261
  end
270
262
 
271
- # fixme: not touched, need cleanup
272
263
  def audio(node)
273
264
  xml = node.document.attr? 'htmlsyntax', 'xml'
274
265
  id_attribute = node.id ? %( id="#{node.id}") : nil
275
266
  classes = ['audioblock', node.style, node.role].compact
276
267
  class_attribute = %( class="#{classes * ' '}")
277
268
  title_element = node.title? ? %(<div class="title">#{node.captioned_title}</div>\n) : nil
278
- %(<div#{id_attribute}#{class_attribute}>
279
- #{title_element}<div class="content">
280
- <audio src="#{node.media_uri(node.attr 'target')}"#{(node.option? 'autoplay') ? (append_boolean_attribute 'autoplay', xml) : nil}#{(node.option? 'nocontrols') ? nil : (append_boolean_attribute 'controls', xml)}#{(node.option? 'loop') ? (append_boolean_attribute 'loop', xml) : nil}>
281
- Your browser does not support the audio tag.
282
- </audio>
283
- </div>
284
- </div>)
269
+
270
+ result = [%(<div#{id_attribute}#{class_attribute}>)]
271
+ result << title_element
272
+ result << '<div class="content">'
273
+
274
+ autoplay = node.option?('autoplay') ? append_boolean_attribute('autoplay', xml) : nil
275
+ controls = node.option?('nocontrols') ? nil : append_boolean_attribute('controls', xml)
276
+ looop = node.option?('loop') ? append_boolean_attribute('loop', xml) : nil
277
+
278
+ result << %(<audio src="#{node.media_uri(node.attr 'target')}"#{autoplay}#{controls}#{looop}>)
279
+ result << 'Your browser does not support the audio tag.'
280
+ result << %(</audio>\n</div>\n</div>)
281
+
282
+ result * "\n"
285
283
  end
286
284
 
287
285
  def colist(node)
@@ -293,13 +291,12 @@ Your browser does not support the audio tag.
293
291
  start_attr = node.attr?('start') ? %( start="#{node.attr('start')}") : nil
294
292
 
295
293
  result << %(<dl#{id_attr}#{class_attr}#{start_attr}>)
296
-
297
294
  node.items.each_with_index do |item, i|
298
295
  result << %(<dt>#{digits[i]}</dt>)
299
296
  result << %(<dd><p>#{item.text}</p>#{item.block? ? item.content : nil}</dd>)
300
297
  end
301
-
302
298
  result << '</dl>'
299
+
303
300
  result * "\n"
304
301
  end
305
302
 
@@ -393,7 +390,6 @@ Your browser does not support the audio tag.
393
390
  %(<div data-type="example"#{id_attr}#{class_attr}>#{title_element}#{node.content}</div>)
394
391
  end
395
392
 
396
- # FIXME: not touched, need cleanup
397
393
  def floating_title(node)
398
394
  tag_name = %(h#{node.level + 1})
399
395
  id_attribute = node.id ? %( id="#{node.id}") : nil
@@ -402,15 +398,15 @@ Your browser does not support the audio tag.
402
398
  end
403
399
 
404
400
  def image(node)
405
- align = (node.attr? 'align') ? (node.attr 'align') : nil
406
- float = (node.attr? 'float') ? (node.attr 'float') : nil
401
+ align = node.attr?('align') ? node.attr('align') : nil
402
+ float = node.attr?('float') ? node.attr('float') : nil
407
403
  style_attr = if align || float
408
404
  styles = [align ? %(text-align: #{align}) : nil, float ? %(float: #{float}) : nil].compact
409
405
  %( style="#{styles * ';'}")
410
406
  end
411
407
 
412
- width_attr = (node.attr? 'width') ? %( width="#{node.attr 'width'}") : nil
413
- height_attr = (node.attr? 'height') ? %( height="#{node.attr 'height'}") : nil
408
+ width_attr = node.attr?('width') ? %( width="#{node.attr 'width'}") : nil
409
+ height_attr = node.attr?('height') ? %( height="#{node.attr 'height'}") : nil
414
410
 
415
411
  img_element = %(<img src="#{node.image_uri node.attr('target')}" alt="#{node.attr 'alt'}"#{width_attr}#{height_attr}/>)
416
412
  if (link = node.attr 'link')
@@ -462,11 +458,11 @@ Your browser does not support the audio tag.
462
458
  def literal(node)
463
459
  id_attr = node.id ? %( id="#{node.id}") : nil
464
460
  cls = node.role ? " #{node.role}" : nil
465
- output = [%(<div#{id_attr} class="literal#{cls}">)]
466
- output << %(<pre>#{node.content}</pre>)
467
- output << '</div>'
461
+ result = [%(<div#{id_attr} class="literal#{cls}">)]
462
+ result << %(<pre>#{node.content}</pre>)
463
+ result << '</div>'
468
464
 
469
- output * "\n"
465
+ result * "\n"
470
466
  end
471
467
 
472
468
  def math(node)
@@ -474,7 +470,7 @@ Your browser does not support the audio tag.
474
470
  class_attr = node.role ? %( class="#{node.role}") : nil
475
471
  title_element = node.title? ? %(<h5>#{node.title}</h5>\n) : nil
476
472
  open, close = ::Asciidoctor::BLOCK_MATH_DELIMITERS[node.style.to_sym]
477
- # QUESTION should the content be stripped already?
473
+
478
474
  equation = node.content.strip
479
475
  if node.subs.nil_or_empty? && !(node.attr? 'subs')
480
476
  equation = node.sub_specialcharacters equation
@@ -741,15 +737,15 @@ Your browser does not support the audio tag.
741
737
  result * "\n"
742
738
  end
743
739
 
744
- # FIXME: not touched, need cleanup
745
740
  def video(node)
746
741
  xml = node.document.attr? 'htmlsyntax', 'xml'
747
742
  id_attribute = node.id ? %( id="#{node.id}") : nil
748
743
  classes = ['videoblock', node.style, node.role].compact
749
744
  class_attribute = %( class="#{classes * ' '}")
750
745
  title_element = node.title? ? %(\n<div class="title">#{node.captioned_title}</div>) : nil
751
- width_attribute = (node.attr? 'width') ? %( width="#{node.attr 'width'}") : nil
752
- height_attribute = (node.attr? 'height') ? %( height="#{node.attr 'height'}") : nil
746
+ width_attribute = node.attr?('width') ? %( width="#{node.attr 'width'}") : nil
747
+ height_attribute = node.attr?('height') ? %( height="#{node.attr 'height'}") : nil
748
+
753
749
  case node.attr 'poster'
754
750
  when 'vimeo'
755
751
  start_anchor = (node.attr? 'start') ? "#at=#{node.attr 'start'}" : nil
@@ -757,32 +753,36 @@ Your browser does not support the audio tag.
757
753
  autoplay_param = (node.option? 'autoplay') ? "#{delimiter}autoplay=1" : nil
758
754
  delimiter = '&amp;' if autoplay_param
759
755
  loop_param = (node.option? 'loop') ? "#{delimiter}loop=1" : nil
760
- %(<div#{id_attribute}#{class_attribute}>#{title_element}
761
- <div class="content">
762
- <iframe#{width_attribute}#{height_attribute} src="//player.vimeo.com/video/#{node.attr 'target'}#{start_anchor}#{autoplay_param}#{loop_param}" frameborder="0"#{append_boolean_attribute 'webkitAllowFullScreen', xml}#{append_boolean_attribute 'mozallowfullscreen', xml}#{append_boolean_attribute 'allowFullScreen', xml}></iframe>
763
- </div>
764
- </div>)
756
+
757
+ result = %(<div#{id_attribute}#{class_attribute}>)
758
+ result << title_element
759
+ result << '<div class="content">'
760
+ result << %(<iframe#{width_attribute}#{height_attribute} src="//player.vimeo.com/video/#{node.attr 'target'}#{start_anchor}#{autoplay_param}#{loop_param}" frameborder="0"#{append_boolean_attribute 'webkitAllowFullScreen', xml}#{append_boolean_attribute 'mozallowfullscreen', xml}#{append_boolean_attribute 'allowFullScreen', xml}></iframe>)
761
+ result << %(</div>\n</div>)
762
+ result * "\n"
765
763
  when 'youtube'
766
- start_param = (node.attr? 'start') ? "&amp;start=#{node.attr 'start'}" : nil
767
- end_param = (node.attr? 'end') ? "&amp;end=#{node.attr 'end'}" : nil
768
- autoplay_param = (node.option? 'autoplay') ? '&amp;autoplay=1' : nil
769
- loop_param = (node.option? 'loop') ? '&amp;loop=1' : nil
770
- controls_param = (node.option? 'nocontrols') ? '&amp;controls=0' : nil
771
- %(<div#{id_attribute}#{class_attribute}>#{title_element}
772
- <div class="content">
773
- <iframe#{width_attribute}#{height_attribute} src="//www.youtube.com/embed/#{node.attr 'target'}?rel=0#{start_param}#{end_param}#{autoplay_param}#{loop_param}#{controls_param}" frameborder="0"#{(node.option? 'nofullscreen') ? nil : (append_boolean_attribute 'allowfullscreen', xml)}></iframe>
774
- </div>
775
- </div>)
764
+ start_param = node.attr?('start') ? "&amp;start=#{node.attr 'start'}" : nil
765
+ end_param = node.attr?('end') ? "&amp;end=#{node.attr 'end'}" : nil
766
+ autoplay_param = node.option?('autoplay') ? '&amp;autoplay=1' : nil
767
+ loop_param = node.option?('loop') ? '&amp;loop=1' : nil
768
+ controls_param = node.option?('nocontrols') ? '&amp;controls=0' : nil
769
+
770
+ result = %(<div#{id_attribute}#{class_attribute}>)
771
+ result << title_element
772
+ result << '<div class="content">'
773
+ result << %(<iframe#{width_attribute}#{height_attribute} src="//www.youtube.com/embed/#{node.attr 'target'}?rel=0#{start_param}#{end_param}#{autoplay_param}#{loop_param}#{controls_param}" frameborder="0"#{(node.option? 'nofullscreen') ? nil : (append_boolean_attribute 'allowfullscreen', xml)}></iframe>)
774
+ result << %(</div>\n</div>)
775
+ result * "\n"
776
776
  else
777
777
  poster_attribute = %(#{poster = node.attr 'poster'}).empty? ? nil : %( poster="#{node.media_uri poster}")
778
778
  time_anchor = ((node.attr? 'start') || (node.attr? 'end')) ? %(#t=#{node.attr 'start'}#{(node.attr? 'end') ? ',' : nil}#{node.attr 'end'}) : nil
779
- %(<div#{id_attribute}#{class_attribute}>#{title_element}
780
- <div class="content">
781
- <video src="#{node.media_uri(node.attr 'target')}#{time_anchor}"#{width_attribute}#{height_attribute}#{poster_attribute}#{(node.option? 'autoplay') ? (append_boolean_attribute 'autoplay', xml) : nil}#{(node.option? 'nocontrols') ? nil : (append_boolean_attribute 'controls', xml)}#{(node.option? 'loop') ? (append_boolean_attribute 'loop', xml) : nil}>
782
- Your browser does not support the video tag.
783
- </video>
784
- </div>
785
- </div>)
779
+ result = %(<div#{id_attribute}#{class_attribute}>)
780
+ result << title_element
781
+ result << '<div class="content">'
782
+ result << %(<video src="#{node.media_uri(node.attr 'target')}#{time_anchor}"#{width_attribute}#{height_attribute}#{poster_attribute}#{(node.option? 'autoplay') ? (append_boolean_attribute 'autoplay', xml) : nil}#{(node.option? 'nocontrols') ? nil : (append_boolean_attribute 'controls', xml)}#{(node.option? 'loop') ? (append_boolean_attribute 'loop', xml) : nil}>)
783
+ result << 'Your browser does not support the video tag.'
784
+ result << %(</video>\m</div>\n</div>)
785
+ result * "\n"
786
786
  end
787
787
  end
788
788
 
@@ -792,10 +792,9 @@ Your browser does not support the video tag.
792
792
 
793
793
  case node.type
794
794
  when :xref
795
- refid = (node.attr 'refid') || target
796
- # FIXME seems like text should be prepared already
795
+ refid = node.attr('refid') || target
797
796
  text = node.text || (node.document.references[:ids][refid] || %([#{refid}]))
798
- if (ebook_format == 'pdf' || single_page_site?(node)) && !target.start_with?('#')
797
+ if (ebook_format == 'pdf' || single_page_html?(node)) && !target.start_with?('#')
799
798
  parts = target.split('#', 2)
800
799
  target = "##{parts.last}"
801
800
  end
@@ -839,7 +838,7 @@ Your browser does not support the video tag.
839
838
  %(<a data-type="footnoteref" href="##{node.target}">#{index}</a>)
840
839
  else
841
840
  id_attr = node.id ? %( id="#{node.id}") : nil
842
- if single_page_site?(node)
841
+ if single_page_html?(node)
843
842
  %(<sup><a href="#fn-#{index}" id="fn-ref-#{index}">#{index}</a></sup>)
844
843
  else
845
844
  %(<span data-type="footnote"#{id_attr}>#{node.text}</span>)
@@ -893,7 +892,7 @@ Your browser does not support the video tag.
893
892
  if (keys = node.attr 'keys').size == 1
894
893
  %(<kbd>#{keys[0]}</kbd>)
895
894
  else
896
- key_combo = keys.map {|key| %(<kbd>#{key}</kbd>+) }.join.chop
895
+ key_combo = keys.map { |key| %(<kbd>#{key}</kbd>-) }.join.chop
897
896
  %(<span class="keyseq">#{key_combo}</span>)
898
897
  end
899
898
  end
@@ -923,11 +922,16 @@ Your browser does not support the video tag.
923
922
 
924
923
  private
925
924
 
926
- # Generates single page site or not.
927
- def single_page_site?(node)
925
+ # Generates single page html or not.
926
+ def single_page_html?(node)
928
927
  node.document.attr('single-page', false)
929
928
  end
930
929
 
930
+ # Generates multiple pages html or not.
931
+ def multiple_pages_html?(node)
932
+ node.document.attr('multiple-pages', false)
933
+ end
934
+
931
935
  # Genarate cover page
932
936
  def cover(node)
933
937
  doc = node.document
@@ -966,7 +970,7 @@ Your browser does not support the video tag.
966
970
  result * "\n"
967
971
  end
968
972
 
969
- # Generate a title page for PDF format
973
+ # Generate a title page
970
974
  def titlepage(node)
971
975
  result = [%(<section data-type="titlepage">)]
972
976
  result << %(<h1>#{node.header.title}</h1>)
@@ -1020,7 +1024,6 @@ Your browser does not support the video tag.
1020
1024
  result * "\n"
1021
1025
  end
1022
1026
 
1023
- # FIXME: after cleanup, delete this
1024
1027
  def append_boolean_attribute(name, xml)
1025
1028
  xml ? %( #{name}="#{name}") : %( #{name})
1026
1029
  end
@@ -31,7 +31,10 @@ module Persie
31
31
  end
32
32
 
33
33
  def handles? target
34
- (@document.attr('ebook-format') == 'epub') && (::Asciidoctor::ASCIIDOC_EXTENSIONS.include? ::File.extname(target))
34
+ format = ['epub', 'html'].include? @document.attr('ebook-format')
35
+ ext = ::Asciidoctor::ASCIIDOC_EXTENSIONS.include? ::File.extname(target)
36
+
37
+ format && ext
35
38
  end
36
39
 
37
40
  def update_config config
@@ -5,7 +5,9 @@ require 'asciidoctor'
5
5
  require_relative 'builders/pdf'
6
6
  require_relative 'builders/epub'
7
7
  require_relative 'builders/mobi'
8
- require_relative 'builders/site'
8
+ require_relative 'builders/single_html'
9
+ require_relative 'builders/multiple_htmls'
10
+
9
11
 
10
12
  module Persie
11
13
  class Book
@@ -52,8 +54,12 @@ module Persie
52
54
  Mobi.new(self, options).build
53
55
  end
54
56
 
55
- def build_site(options = {})
56
- Site.new(self, options).build
57
+ def build_single_html(options = {})
58
+ SingleHTML.new(self, options).build
59
+ end
60
+
61
+ def build_multiple_htmls(options = {})
62
+ MultipleHTMLs.new(self, options).build
57
63
  end
58
64
 
59
65
  end
@@ -9,6 +9,7 @@ require_relative 'asciidoctor_ext/sample'
9
9
 
10
10
  module Persie
11
11
  class Builder
12
+ include UI
12
13
 
13
14
  END_LINE = '=' * 72
14
15
 
@@ -16,7 +17,6 @@ module Persie
16
17
  attr_reader :document
17
18
 
18
19
  def initialize(book, options = {})
19
- @ui = UI.new(options)
20
20
  @book = book
21
21
  @options = options
22
22
  @document = ::Asciidoctor.load_file(@book.master_file, adoc_options)
@@ -28,15 +28,25 @@ module Persie
28
28
  raise ::NotImplementedError
29
29
  end
30
30
 
31
+ # Do nothing here.
32
+ # Implement in subclass or plugin.
33
+ def before_build
34
+ end
35
+
36
+ # Do nothing here.
37
+ # Implement in subclass or plugin.
38
+ def after_build
39
+ end
40
+
31
41
  # If in sample mode, show an indicator in command line.
32
42
  def check_sample
33
43
  if sample?
34
44
  if @document.sample_sections.size == 0
35
- @ui.error 'Not setting sample, terminated!'
36
- @ui.info END_LINE
45
+ error 'Not setting sample, terminated!'
46
+ info END_LINE
37
47
  exit
38
48
  end
39
- @ui.warning "Sample only\n"
49
+ warning "Sample only", true
40
50
  end
41
51
  end
42
52
 
@@ -1,107 +1,14 @@
1
- require 'nokogiri'
2
-
3
1
  require 'time'
4
2
 
3
+ require_relative '../gepub_ext'
5
4
  require_relative '../builder'
5
+ require_relative '../chunkable'
6
6
 
7
7
  module Persie
8
- module GepubBuilderMixin
9
-
10
- FromHtmlSpecialCharsMap = {
11
- '&lt;' => '<',
12
- '&gt;' => '>',
13
- '&amp;' => '&'
14
- }
15
- FromHtmlSpecialCharsRx = /(?:#{FromHtmlSpecialCharsMap.keys * '|'})/
16
- WordJoinerRx = [65279].pack 'U*'
17
- CsvDelimiterRx = /\s*,\s*/
18
-
19
- def sanitized_title(title, target = :plain)
20
- return (@doc.attr 'untitled-label') unless @doc.header?
21
-
22
- builder = self
23
-
24
- title = case target
25
- when :attribute_cdata
26
- builder.sanitize(title).gsub('"', '&quot;')
27
- when :element_cdata
28
- builder.sanitize(title)
29
- when :pcdata
30
- title
31
- when :plain
32
- builder.sanitize(title).gsub(FromHtmlSpecialCharsRx, FromHtmlSpecialCharsMap)
33
- end
34
-
35
- title.gsub WordJoinerRx, ''
36
- end
37
-
38
- def sanitize(text)
39
- if text.include?('<')
40
- text.gsub(::Asciidoctor::XmlSanitizeRx, '').tr_s(' ', ' ').strip
41
- else
42
- text
43
- end
44
- end
45
-
46
- def authors
47
- if (auts = @doc.attr 'authors')
48
- auts.split(CsvDelimiterRx)
49
- else
50
- []
51
- end
52
- end
53
-
54
- def add_theme_assets
55
- resources(workdir: @theme_dir) do
56
- file 'epub.css' if File.exist?('epub.css')
57
- glob 'fonts/*.*'
58
- end
59
- end
60
-
61
- def add_cover_image
62
- image = @doc.attr('epub-cover-image', 'cover.png')
63
- image = File.basename(image) # incase you set this a path
64
-
65
- resources(workdir: @theme_dir) do
66
- cover_image image if File.exist? image
67
- end
68
-
69
- end
70
-
71
- def add_images
72
- resources(workdir: @base_dir) do
73
- glob 'images/*.*'
74
- end
75
- end
76
-
77
- def add_content
78
- builder = self
79
- spine_items = @spine_items
80
- spine_item_titles = @spine_item_titles
81
- resources(workdir: @tmp_dir) do
82
- nav 'nav.xhtml' if @has_toc
83
-
84
- ordered do
85
- spine_items.each_with_index do |item, i|
86
- file "#{item}.xhtml"
87
- heading builder.sanitized_title(spine_item_titles[i])
88
- end
89
- end
90
- end
91
- end
92
-
93
- end
94
8
 
95
9
  class Epub < Builder
96
10
 
97
- # these are not using `include' directive
98
- SPECIAL_SPINE_ITEMS = ['cover', 'titlepage', 'nav']
99
-
100
- # Gets/Sets spine items.
101
- attr_accessor :spine_items
102
-
103
- # Gets/Sets spine items's titles.
104
- attr_accessor :spine_item_titles
11
+ include Chunkable
105
12
 
106
13
  def initialize(book, options = {})
107
14
  super
@@ -114,97 +21,18 @@ module Persie
114
21
 
115
22
  # Builds ePub.
116
23
  def build
117
- @ui.info '=== Build ePub ' << '=' * 57
24
+ info '=== Build ePub ' << '=' * 57
118
25
 
26
+ self.before_build
119
27
  self.check_sample
120
- self.convert_to_single_xhtml
28
+ self.convert_to_single_html
121
29
  self.generate_spine_items
122
30
  self.chunk
123
31
  self.generate_epub
124
32
  self.validate
33
+ self.after_build
125
34
 
126
- @ui.info END_LINE
127
- end
128
-
129
- # Converts to single XHTML file.
130
- def convert_to_single_xhtml
131
- @ui.info 'Converting to XHTML...'
132
- xhtml = @document.convert
133
- prepare_directory(self.xhtml_path)
134
- File.write(self.xhtml_path, xhtml)
135
- @ui.confirm ' XHTMl file created'
136
- @ui.info " Location: #{self.xhtml_path(true)}\n"
137
- end
138
-
139
- # Generates spine items.
140
- def generate_spine_items
141
- register_spine_item_processor
142
-
143
- # Re-loading the master file
144
- doc = ::Asciidoctor.load_file(@book.master_file, adoc_options)
145
- @spine_items.concat SPECIAL_SPINE_ITEMS
146
- @spine_items.concat doc.references['spine_items']
147
-
148
- @spine_items
149
- end
150
-
151
- # Chucks single XHTML file to multiple XHTML files.
152
- def chunk
153
- @ui.info 'Chunking files...'
154
-
155
- content = File.read(self.xhtml_path)
156
- root = ::Nokogiri::HTML(content)
157
-
158
- # Adjust spint items
159
- @has_cover = root.css('div[data-type="cover"]').size > 0
160
- @has_toc = root.css('nav[data-type="toc"]').size > 0
161
- self.spine_items.delete('cover') unless @has_cover
162
- self.spine_items.delete('toc') unless @has_toc
163
-
164
- correct_nav_href(root)
165
-
166
- top_level_sections = resolve_top_level_sections(root)
167
-
168
- # stupid check, incase of something went wrong
169
- unless top_level_sections.count == self.spine_items.count
170
- @ui.error ' Count of sections DO NOT equal to spine items count.'
171
- @ui.error ' Terminated!'
172
- if @options.debug?
173
- @ui.info 'sections count: ' + top_level_sections.count
174
- @ui.info 'spine_items: ' + self.spine_items.inspect
175
- end
176
- @ui.info END_LINE
177
- exit 31
178
- end
179
-
180
- sep = '<body data-type="book">'
181
- tpl_before = content.split(sep).first
182
- tpl_after = %(</body>\n</html>)
183
-
184
- top_level_sections.each_with_index do |node, i|
185
- # Collect the first h1 heading
186
- title = if (i == 0 && @has_cover) # cover page don't have title
187
- @document.attr('cover-page-title', 'Cover')
188
- else
189
- node.css('h1:first-of-type').first.inner_text
190
- end
191
- @spine_item_titles << title
192
-
193
- # Footnotes
194
- footnotes_div = generate_footnotes(node)
195
-
196
- # Write to chunked file
197
- path = File.join(@tmp_dir, "#{self.spine_items[i]}.xhtml")
198
- File.open(path, 'w') do |f|
199
- f.puts tpl_before
200
- f.puts sep
201
- f.puts node.to_xhtml
202
- f.puts footnotes_div
203
- f.puts tpl_after
204
- end
205
- end
206
-
207
- @ui.confirm ' Done\n'
35
+ info END_LINE
208
36
  end
209
37
 
210
38
  # Generates ePub file.
@@ -216,7 +44,7 @@ module Persie
216
44
  spine_items = self.spine_items
217
45
  spine_item_titles = self.spine_item_titles
218
46
 
219
- @ui.info 'Building ePub...'
47
+ info 'Building ePub...'
220
48
 
221
49
  builder = ::GEPUB::Builder.new do
222
50
  extend GepubBuilderMixin
@@ -289,36 +117,27 @@ module Persie
289
117
 
290
118
  prepare_directory(self.epub_path)
291
119
  builder.generate_epub(self.epub_path)
292
- @ui.confirm ' ePub file created'
293
- @ui.info " Location: #{self.epub_path(true)}"
120
+ confirm ' ePub file created'
121
+ info " Location: #{self.epub_path(true)}"
294
122
  end
295
123
 
296
124
  # Validates ePub file, optionally.
297
125
  def validate
298
126
  if @options.validate?
299
- @ui.info "\nValidating..."
127
+ info "Validating..."
300
128
  if Dependency.epubcheck_installed?
301
129
  system "epubcheck #{epub_path}"
302
130
  if $?.to_i == 0
303
- @ui.confirm ' PASS'
131
+ confirm ' PASS'
304
132
  else
305
- @ui.error ' ERROR'
133
+ error ' ERROR'
306
134
  end
307
135
  else
308
- @ui.warning ' epubcheck not installed, skip validation'
136
+ warning ' epubcheck not installed, skip validation'
309
137
  end
310
138
  end
311
139
  end
312
140
 
313
- # Gets XHTML file path.
314
- def xhtml_path(relative = false)
315
- name = sample? ? "#{@book.slug}-sample" : @book.slug
316
- path = File.join('tmp', 'epub', "#{name}.html")
317
- return path if relative
318
-
319
- File.join(@book.base_dir, path)
320
- end
321
-
322
141
  # Gets ePub file path.
323
142
  def epub_path(relative = false)
324
143
  name = sample? ? "#{@book.slug}-sample" : @book.slug
@@ -338,98 +157,5 @@ module Persie
338
157
  }
339
158
  end
340
159
 
341
- # Corrects navigation items' href.
342
- #
343
- # Example:
344
- # href="#id" => href="path.xhtml#id"
345
- def correct_nav_href(node)
346
- return unless (ols = node.css('nav[data-type="toc"]> ol')).size > 0
347
-
348
- spine_items_dup = self.spine_items.dup
349
- SPECIAL_SPINE_ITEMS.each { |i| spine_items_dup.delete(i) }
350
-
351
- top_level_lis = ols.first.css('> li')
352
- j = 0
353
- top_level_lis.each do |li|
354
- if li['data-type'] == 'part'
355
- first_a = li.css('> a').first
356
- first_a_href = first_a['href']
357
- first_a['href'] = "#{spine_items_dup[j]}.xhtml#{first_a_href}"
358
- if (li_ols = li.css('> ol')).size > 0
359
- li_ol = li_ols.first
360
- li_ol.css('> li').each do |lli|
361
- j += 1
362
- lli.css('a').each do |a|
363
- old_href = a['href']
364
- a['href'] = "#{spine_items_dup[j]}.xhtml#{old_href}"
365
- end
366
- end
367
- j += 1
368
- end
369
- else
370
- li.css('a').each do |a|
371
- old_href = a['href']
372
- a['href'] = "#{spine_items_dup[j]}.xhtml#{old_href}"
373
- end
374
- j += 1
375
- end
376
- end
377
- end
378
-
379
-
380
- # Resolves top level sections.
381
- #
382
- # When there are parts, takes sections within each part out.
383
- def resolve_top_level_sections(node)
384
- if (parts = node.css('body > div[data-type="part"]')).size > 0
385
- parts.each do |part|
386
- sections = part.css('> section')
387
- sections.each do |sect|
388
- part.delete sect
389
- end
390
- part.add_next_sibling(sections)
391
- end
392
- end
393
-
394
- node.css('body > *')
395
-
396
- end
397
-
398
- # Generates footnotes for one node.
399
- def generate_footnotes(node)
400
- footnotes_div = nil
401
- footnotes = node.css('span[data-type="footnote"]')
402
- if footnotes.length > 0
403
- footnotes_div = generate_footnotes_div(footnotes)
404
- replace_footnote_with_sup(footnotes)
405
- end
406
-
407
- footnotes_div
408
- end
409
-
410
- # Generate a footnotes div element.
411
- def generate_footnotes_div(footnotes)
412
- result = ['<div class="footnotes">']
413
- result << '<ol>'
414
- footnotes.each_with_index do |fn, i|
415
- index = i + 1
416
- result << %(<li id="fn-#{index}" epub:type="footnote">#{fn.inner_text}</li>)
417
- end
418
- result << '</ol>'
419
- result << '</div>'
420
-
421
- result * "\n"
422
- end
423
-
424
- def replace_footnote_with_sup(footnotes)
425
- footnotes.each_with_index do |fn, i|
426
- index = i + 1
427
- fn.replace(%(<sup><a href="#fn-#{index}">#{index}</a></sup>))
428
- end
429
-
430
- nil
431
- end
432
-
433
160
  end
434
-
435
161
  end