asciidoctor-epub3 1.5.0.alpha.15 → 1.5.0.alpha.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +8 -0
- data/README.adoc +9 -1
- data/asciidoctor-epub3.gemspec +2 -1
- data/data/styles/epub3.css +5 -0
- data/lib/asciidoctor-epub3/converter.rb +146 -36
- data/lib/asciidoctor-epub3/version.rb +1 -1
- metadata +18 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bbc1a47f9a011cc194ab8cd731ec3d6121079f0a49e32540329d8b30fb390ecc
|
4
|
+
data.tar.gz: c02ceac6c1c9259ff70a3b799ceb9ed70f5af5e5b233914882332b560f95259d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 409a0b3d28aa559a14f33fd042b739595b1be0625b18044c308b088f3396479fc3859eece4d81bf9439d9c10e815061143eaed29506266154b9c22e6b2fd8100
|
7
|
+
data.tar.gz: d1e8a6c56803400dc9181bbaacec99df81a522a24301e96adb40f19456066e16d27202da6ce8e48081ee177e51c166916ef951eefc3119aba3ea02235b1b3628
|
data/CHANGELOG.adoc
CHANGED
@@ -5,6 +5,14 @@
|
|
5
5
|
This document provides a high-level view of the changes to the {project-name} by release.
|
6
6
|
For a detailed view of what has changed, refer to the {uri-repo}/commits/master[commit history] on GitHub.
|
7
7
|
|
8
|
+
== 1.5.0.alpha.16 (2020-04-26) - @slonopotamus
|
9
|
+
|
10
|
+
* add basic audio and video support (#9)
|
11
|
+
* add support for `[horizontal]` definition list (#165)
|
12
|
+
* add proper handling of `:data-uri:` document attribute (#324)
|
13
|
+
* add support for customizable document splitting into chapters via `epub-chapter-level` attribute (#327)
|
14
|
+
* avoid outputting 'true' for unsupported blocks (#332)
|
15
|
+
|
8
16
|
== 1.5.0.alpha.15 (2020-03-11) - @slonopotamus
|
9
17
|
|
10
18
|
* support section numbering and captions (#20)
|
data/README.adoc
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
= {project-name}: A _native_ EPUB3 converter for AsciiDoc
|
2
2
|
Dan Allen <https://github.com/mojavelinux[@mojavelinux]>; Sarah White <https://github.com/graphitefriction[@graphitefriction]>
|
3
|
-
v1.5.0.alpha.
|
3
|
+
v1.5.0.alpha.16, 2020-04-26
|
4
4
|
// Settings:
|
5
5
|
:experimental:
|
6
6
|
:idprefix:
|
@@ -140,6 +140,7 @@ Like other converters, Asciidoctor EPUB3 handles this chunking task by automatic
|
|
140
140
|
|
141
141
|
When `doctype` attribute is set to `book`, each top-level section will become a separate ebook "chapter" file.
|
142
142
|
This includes preface, bibliography, appendix, etc.
|
143
|
+
This behavior can be configured via `epub-chapter-level` document attribute.
|
143
144
|
|
144
145
|
Otherwise, whole document is converted to a single ebook chapter.
|
145
146
|
|
@@ -298,6 +299,13 @@ The recommended practice is to identify the referenced resource by means of a st
|
|
298
299
|
|An optional override of the properties attribute for this document's item in the manifest.
|
299
300
|
_Only applies to a chapter document._
|
300
301
|
|
302
|
+
|epub-chapter-level
|
303
|
+
|Specify the section level at which to split the EPUB into separate "chapter" files.
|
304
|
+
This attribute only affects documents with `:doctype: book`.
|
305
|
+
The default is to split into chapters at level-1 sections.
|
306
|
+
This attribute only affects the internal composition of the EPUB, not the way chapters and sections are displayed to users.
|
307
|
+
Some readers may be slow if the chapter files are too large, so for large documents with few level-1 headings, one might want to use a chapter level of 2 or 3.
|
308
|
+
|
301
309
|
|series-name, series-volume, series-id
|
302
310
|
|Populates the series statements (`belongs-to-collection`) in the package metadata.
|
303
311
|
Volume is a number, ID probably a UUID that is constant for all volumes in the series.
|
data/asciidoctor-epub3.gemspec
CHANGED
@@ -36,9 +36,10 @@ An extension for Asciidoctor that converts AsciiDoc documents to EPUB3 and KF8/M
|
|
36
36
|
s.add_development_dependency 'rake', '~> 13.0.0'
|
37
37
|
s.add_development_dependency 'rouge', '~> 3.0'
|
38
38
|
s.add_development_dependency 'rspec', '~> 3.9.0'
|
39
|
-
s.add_development_dependency 'rubocop', '~> 0.
|
39
|
+
s.add_development_dependency 'rubocop', '~> 0.81.0'
|
40
40
|
s.add_development_dependency 'rubocop-rspec', '~> 1.38.0'
|
41
41
|
|
42
42
|
s.add_runtime_dependency 'asciidoctor', '>= 1.5.6', '< 3.0.0'
|
43
43
|
s.add_runtime_dependency 'gepub', '~> 1.0.0'
|
44
|
+
s.add_runtime_dependency 'mime-types', '~> 3.0'
|
44
45
|
end
|
data/data/styles/epub3.css
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'mime/types'
|
3
4
|
require 'open3'
|
4
5
|
require_relative 'font_icon_map'
|
5
6
|
|
@@ -29,7 +30,7 @@ module Asciidoctor
|
|
29
30
|
unless (entry_dir = ::File.dirname entry.name) == '.' || (::File.directory? entry_dir)
|
30
31
|
::FileUtils.mkdir_p entry_dir
|
31
32
|
end
|
32
|
-
entry.extract
|
33
|
+
entry.extract entry.name
|
33
34
|
end
|
34
35
|
end
|
35
36
|
end
|
@@ -104,7 +105,8 @@ module Asciidoctor
|
|
104
105
|
if respond_to? method_name
|
105
106
|
send method_name, node
|
106
107
|
else
|
107
|
-
logger.warn %(conversion missing in backend #{@backend} for #{name})
|
108
|
+
logger.warn %(#{::File.basename node.attr('docfile')}: conversion missing in backend #{@backend} for #{name})
|
109
|
+
nil
|
108
110
|
end
|
109
111
|
end
|
110
112
|
|
@@ -114,7 +116,8 @@ module Asciidoctor
|
|
114
116
|
return Asciidoctor::Document === node ? node.attr('docname') || node.id : nil
|
115
117
|
end
|
116
118
|
return (node.id || 'preamble') if node.context == :preamble && node.level == 0
|
117
|
-
|
119
|
+
chapter_level = [node.document.attr('epub-chapter-level', 1).to_i, 1].max
|
120
|
+
Asciidoctor::Section === node && node.level <= chapter_level ? node.id : nil
|
118
121
|
end
|
119
122
|
|
120
123
|
def get_numbered_title node
|
@@ -150,7 +153,7 @@ module Asciidoctor
|
|
150
153
|
@epubcheck_path = node.attr 'ebook-epubcheck-path'
|
151
154
|
@xrefs_seen = ::Set.new
|
152
155
|
@icon_names = []
|
153
|
-
@
|
156
|
+
@media_files = []
|
154
157
|
@footnotes = []
|
155
158
|
|
156
159
|
@book = GEPUB::Book.new 'EPUB/package.opf'
|
@@ -221,14 +224,7 @@ module Asciidoctor
|
|
221
224
|
add_front_matter_page node
|
222
225
|
|
223
226
|
if node.doctype == 'book'
|
224
|
-
toc_items =
|
225
|
-
node.sections.each do |section|
|
226
|
-
toc_items << section
|
227
|
-
section.sections.each do |subsection|
|
228
|
-
next if get_chapter_name(node).nil?
|
229
|
-
toc_items << subsection
|
230
|
-
end
|
231
|
-
end
|
227
|
+
toc_items = node.sections
|
232
228
|
node.content
|
233
229
|
else
|
234
230
|
toc_items = [node]
|
@@ -245,13 +241,16 @@ module Asciidoctor
|
|
245
241
|
docimagesdir = (node.attr 'imagesdir', '.').chomp '/'
|
246
242
|
docimagesdir = (docimagesdir == '.' ? nil : %(#{docimagesdir}/))
|
247
243
|
|
248
|
-
@
|
249
|
-
if
|
250
|
-
logger.warn %(
|
251
|
-
elsif ::File.readable?
|
252
|
-
|
244
|
+
@media_files.each do |file|
|
245
|
+
if file[:name].start_with? %(#{docimagesdir}jacket/cover.)
|
246
|
+
logger.warn %(path is reserved for cover artwork: #{file[:name]}; skipping file found in content)
|
247
|
+
elsif ::File.readable? file[:path]
|
248
|
+
mime_types = MIME::Types.type_for file[:name]
|
249
|
+
mime_types.delete_if {|x| x.media_type != file[:media_type] }
|
250
|
+
preferred_mime_type = mime_types.empty? ? nil : mime_types[0].content_type
|
251
|
+
@book.add_item file[:name], content: file[:path], media_type: preferred_mime_type
|
253
252
|
else
|
254
|
-
logger.error %(#{File.basename node.attr('docfile')}:
|
253
|
+
logger.error %(#{File.basename node.attr('docfile')}: media file not found or not readable: #{file[:path]})
|
255
254
|
end
|
256
255
|
end
|
257
256
|
|
@@ -565,7 +564,7 @@ document.addEventListener('DOMContentLoaded', function(event, reader) {
|
|
565
564
|
end
|
566
565
|
figure_classes = ['listing']
|
567
566
|
figure_classes << 'coalesce' if node.option? 'unbreakable'
|
568
|
-
title_div = node.title? ? %(<figcaption>#{
|
567
|
+
title_div = node.title? ? %(<figcaption>#{node.captioned_title}</figcaption>) : ''
|
569
568
|
%(<figure class="#{figure_classes * ' '}">#{title_div}
|
570
569
|
#{syntax_hl ? (syntax_hl.format node, lang, opts) : pre_open + (node.content || '') + pre_close}
|
571
570
|
</figure>)
|
@@ -757,16 +756,30 @@ document.addEventListener('DOMContentLoaded', function(event, reader) {
|
|
757
756
|
# TODO: add complex class if list has nested blocks
|
758
757
|
def convert_dlist node
|
759
758
|
lines = []
|
759
|
+
id_attribute = node.id ? %( id="#{node.id}") : ''
|
760
|
+
|
761
|
+
classes = case node.style
|
762
|
+
when 'horizontal'
|
763
|
+
['hdlist', node.role]
|
764
|
+
when 'itemized', 'ordered'
|
765
|
+
# QUESTION should we just use itemized-list and ordered-list as the class here? or just list?
|
766
|
+
['dlist', %(#{node.style}-list), node.role]
|
767
|
+
else
|
768
|
+
['description-list']
|
769
|
+
end.compact
|
770
|
+
|
771
|
+
class_attribute = %( class="#{classes.join ' '}")
|
772
|
+
|
773
|
+
lines << %(<div#{id_attribute}#{class_attribute}>)
|
774
|
+
lines << %(<div class="title">#{node.title}</div>) if node.title?
|
775
|
+
|
760
776
|
case (style = node.style)
|
761
777
|
when 'itemized', 'ordered'
|
762
778
|
list_tag_name = style == 'itemized' ? 'ul' : 'ol'
|
763
779
|
role = node.role
|
764
780
|
subject_stop = node.attr 'subject-stop', (role && (node.has_role? 'stack') ? nil : ':')
|
765
|
-
# QUESTION should we just use itemized-list and ordered-list as the class here? or just list?
|
766
|
-
div_classes = [%(#{style}-list), role].compact
|
767
781
|
list_class_attr = (node.option? 'brief') ? ' class="brief"' : ''
|
768
|
-
lines << %(
|
769
|
-
<#{list_tag_name}#{list_class_attr}#{list_tag_name == 'ol' && (node.option? 'reversed') ? ' reversed="reversed"' : ''}>)
|
782
|
+
lines << %(<#{list_tag_name}#{list_class_attr}#{list_tag_name == 'ol' && (node.option? 'reversed') ? ' reversed="reversed"' : ''}>)
|
770
783
|
node.items.each do |subjects, dd|
|
771
784
|
# consists of one term (a subject) and supporting content
|
772
785
|
subject = [*subjects].first.text
|
@@ -782,11 +795,40 @@ document.addEventListener('DOMContentLoaded', function(event, reader) {
|
|
782
795
|
end
|
783
796
|
lines << '</li>'
|
784
797
|
end
|
785
|
-
lines << %(</#{list_tag_name}>
|
786
|
-
|
798
|
+
lines << %(</#{list_tag_name}>)
|
799
|
+
when 'horizontal'
|
800
|
+
lines << '<table>'
|
801
|
+
if (node.attr? 'labelwidth') || (node.attr? 'itemwidth')
|
802
|
+
lines << '<colgroup>'
|
803
|
+
col_style_attribute = (node.attr? 'labelwidth') ? %( style="width: #{(node.attr 'labelwidth').chomp '%'}%;") : ''
|
804
|
+
lines << %(<col#{col_style_attribute} />)
|
805
|
+
col_style_attribute = (node.attr? 'itemwidth') ? %( style="width: #{(node.attr 'itemwidth').chomp '%'}%;") : ''
|
806
|
+
lines << %(<col#{col_style_attribute} />)
|
807
|
+
lines << '</colgroup>'
|
808
|
+
end
|
809
|
+
node.items.each do |terms, dd|
|
810
|
+
lines << '<tr>'
|
811
|
+
lines << %(<td class="hdlist1#{(node.option? 'strong') ? ' strong' : ''}">)
|
812
|
+
first_term = true
|
813
|
+
terms.each do |dt|
|
814
|
+
lines << %(<br />) unless first_term
|
815
|
+
lines << '<p>'
|
816
|
+
lines << dt.text
|
817
|
+
lines << '</p>'
|
818
|
+
first_term = nil
|
819
|
+
end
|
820
|
+
lines << '</td>'
|
821
|
+
lines << '<td class="hdlist2">'
|
822
|
+
if dd
|
823
|
+
lines << %(<p>#{dd.text}</p>) if dd.text?
|
824
|
+
lines << dd.content if dd.blocks?
|
825
|
+
end
|
826
|
+
lines << '</td>'
|
827
|
+
lines << '</tr>'
|
828
|
+
end
|
829
|
+
lines << '</table>'
|
787
830
|
else
|
788
|
-
lines << '<
|
789
|
-
<dl>'
|
831
|
+
lines << '<dl>'
|
790
832
|
node.items.each do |terms, dd|
|
791
833
|
[*terms].each do |dt|
|
792
834
|
lines << %(<dt>
|
@@ -803,9 +845,10 @@ document.addEventListener('DOMContentLoaded', function(event, reader) {
|
|
803
845
|
end
|
804
846
|
lines << '</dd>'
|
805
847
|
end
|
806
|
-
lines << '</dl>
|
807
|
-
</div>'
|
848
|
+
lines << '</dl>'
|
808
849
|
end
|
850
|
+
|
851
|
+
lines << '</div>'
|
809
852
|
lines * LF
|
810
853
|
end
|
811
854
|
|
@@ -879,14 +922,16 @@ document.addEventListener('DOMContentLoaded', function(event, reader) {
|
|
879
922
|
document
|
880
923
|
end
|
881
924
|
|
882
|
-
def
|
883
|
-
if target.end_with?
|
925
|
+
def register_media_file node, target, media_type
|
926
|
+
if target.end_with?('.svg') || target.start_with?('data:image/svg+xml')
|
884
927
|
chapter = get_enclosing_chapter node
|
885
928
|
chapter.set_attr 'epub-properties', [] unless chapter.attr? 'epub-properties'
|
886
929
|
epub_properties = chapter.attr 'epub-properties'
|
887
930
|
epub_properties << 'svg' unless epub_properties.include? 'svg'
|
888
931
|
end
|
889
932
|
|
933
|
+
return if target.start_with? 'data:'
|
934
|
+
|
890
935
|
out_dir = node.attr('outdir', nil, true) || doc_option(node.document, :to_dir)
|
891
936
|
fs_path = (::File.join out_dir, target)
|
892
937
|
unless ::File.exist? fs_path
|
@@ -894,7 +939,7 @@ document.addEventListener('DOMContentLoaded', function(event, reader) {
|
|
894
939
|
fs_path = ::File.join base_dir, target
|
895
940
|
end
|
896
941
|
# We need *both* virtual and physical image paths. Unfortunately, references[:images] only has one of them.
|
897
|
-
@
|
942
|
+
@media_files << { name: target, path: fs_path, media_type: media_type }
|
898
943
|
end
|
899
944
|
|
900
945
|
def resolve_image_attrs node
|
@@ -911,16 +956,81 @@ document.addEventListener('DOMContentLoaded', function(event, reader) {
|
|
911
956
|
img_attrs
|
912
957
|
end
|
913
958
|
|
959
|
+
def convert_audio node
|
960
|
+
id_attr = node.id ? %( id="#{node.id}") : ''
|
961
|
+
target = node.media_uri node.attr 'target'
|
962
|
+
register_media_file node, target, 'audio'
|
963
|
+
title_element = node.title? ? %(\n<figcaption>#{node.captioned_title}</figcaption>) : ''
|
964
|
+
|
965
|
+
autoplay_attr = (node.option? 'autoplay') ? ' autoplay="autoplay"' : ''
|
966
|
+
controls_attr = (node.option? 'nocontrols') ? '' : ' controls="controls"'
|
967
|
+
loop_attr = (node.option? 'loop') ? ' loop="loop"' : ''
|
968
|
+
|
969
|
+
start_t = node.attr 'start'
|
970
|
+
end_t = node.attr 'end'
|
971
|
+
if start_t || end_t
|
972
|
+
time_anchor = %(#t=#{start_t || ''}#{end_t ? ",#{end_t}" : ''})
|
973
|
+
else
|
974
|
+
time_anchor = ''
|
975
|
+
end
|
976
|
+
|
977
|
+
%(<figure#{id_attr} class="audioblock#{prepend_space node.role}">#{title_element}
|
978
|
+
<div class="content">
|
979
|
+
<audio src="#{target}#{time_anchor}"#{autoplay_attr}#{controls_attr}#{loop_attr}>
|
980
|
+
<div>Your Reading System does not support (this) audio.</div>
|
981
|
+
</audio>
|
982
|
+
</div>
|
983
|
+
</figure>)
|
984
|
+
end
|
985
|
+
|
986
|
+
# TODO: Support multiple video files in different formats for a single video
|
987
|
+
def convert_video node
|
988
|
+
id_attr = node.id ? %( id="#{node.id}") : ''
|
989
|
+
target = node.media_uri node.attr 'target'
|
990
|
+
register_media_file node, target, 'video'
|
991
|
+
title_element = node.title? ? %(\n<figcaption>#{node.captioned_title}</figcaption>) : ''
|
992
|
+
|
993
|
+
width_attr = (node.attr? 'width') ? %( width="#{node.attr 'width'}") : ''
|
994
|
+
height_attr = (node.attr? 'height') ? %( height="#{node.attr 'height'}") : ''
|
995
|
+
autoplay_attr = (node.option? 'autoplay') ? ' autoplay="autoplay"' : ''
|
996
|
+
controls_attr = (node.option? 'nocontrols') ? '' : ' controls="controls"'
|
997
|
+
loop_attr = (node.option? 'loop') ? ' loop="loop"' : ''
|
998
|
+
|
999
|
+
start_t = node.attr 'start'
|
1000
|
+
end_t = node.attr 'end'
|
1001
|
+
if start_t || end_t
|
1002
|
+
time_anchor = %(#t=#{start_t || ''}#{end_t ? ",#{end_t}" : ''})
|
1003
|
+
else
|
1004
|
+
time_anchor = ''
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
if (poster = node.attr 'poster').nil_or_empty?
|
1008
|
+
poster_attr = ''
|
1009
|
+
else
|
1010
|
+
poster = node.media_uri poster
|
1011
|
+
register_media_file node, poster, 'image'
|
1012
|
+
poster_attr = %( poster="#{poster}")
|
1013
|
+
end
|
1014
|
+
|
1015
|
+
%(<figure#{id_attr} class="video#{prepend_space node.role}">#{title_element}
|
1016
|
+
<div class="content">
|
1017
|
+
<video src="#{target}#{time_anchor}"#{width_attr}#{height_attr}#{autoplay_attr}#{poster_attr}#{controls_attr}#{loop_attr}>
|
1018
|
+
<div>Your Reading System does not support (this) video.</div>
|
1019
|
+
</video>
|
1020
|
+
</div>
|
1021
|
+
</figure>)
|
1022
|
+
end
|
1023
|
+
|
914
1024
|
def convert_image node
|
915
1025
|
target = node.image_uri node.attr 'target'
|
916
|
-
|
1026
|
+
register_media_file node, target, 'image'
|
917
1027
|
id_attr = node.id ? %( id="#{node.id}") : ''
|
1028
|
+
title_element = node.title? ? %(\n<figcaption>#{node.captioned_title}</figcaption>) : ''
|
918
1029
|
img_attrs = resolve_image_attrs node
|
919
1030
|
%(<figure#{id_attr} class="image#{prepend_space node.role}">
|
920
1031
|
<div class="content">
|
921
1032
|
<img src="#{target}"#{prepend_space img_attrs * ' '} />
|
922
|
-
</div>#{
|
923
|
-
<figcaption>#{node.captioned_title}</figcaption>) : ''}
|
1033
|
+
</div>#{title_element}
|
924
1034
|
</figure>)
|
925
1035
|
end
|
926
1036
|
|
@@ -1025,7 +1135,7 @@ document.addEventListener('DOMContentLoaded', function(event, reader) {
|
|
1025
1135
|
%(<i class="#{i_classes * ' '}"></i>)
|
1026
1136
|
else
|
1027
1137
|
target = node.image_uri node.target
|
1028
|
-
|
1138
|
+
register_media_file node, target, 'image'
|
1029
1139
|
|
1030
1140
|
img_attrs = resolve_image_attrs node
|
1031
1141
|
img_attrs << %(class="inline#{prepend_space node.role}")
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: asciidoctor-epub3
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.5.0.alpha.
|
4
|
+
version: 1.5.0.alpha.16
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Allen
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2020-
|
12
|
+
date: 2020-04-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: asciidoctor-diagram
|
@@ -107,14 +107,14 @@ dependencies:
|
|
107
107
|
requirements:
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: 0.
|
110
|
+
version: 0.81.0
|
111
111
|
type: :development
|
112
112
|
prerelease: false
|
113
113
|
version_requirements: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
115
|
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: 0.
|
117
|
+
version: 0.81.0
|
118
118
|
- !ruby/object:Gem::Dependency
|
119
119
|
name: rubocop-rspec
|
120
120
|
requirement: !ruby/object:Gem::Requirement
|
@@ -163,6 +163,20 @@ dependencies:
|
|
163
163
|
- - "~>"
|
164
164
|
- !ruby/object:Gem::Version
|
165
165
|
version: 1.0.0
|
166
|
+
- !ruby/object:Gem::Dependency
|
167
|
+
name: mime-types
|
168
|
+
requirement: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - "~>"
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '3.0'
|
173
|
+
type: :runtime
|
174
|
+
prerelease: false
|
175
|
+
version_requirements: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - "~>"
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '3.0'
|
166
180
|
description: 'An extension for Asciidoctor that converts AsciiDoc documents to EPUB3
|
167
181
|
and KF8/MOBI (Kindle) e-book archives.
|
168
182
|
|