asciidoctor-bespoke 1.0.0.alpha.1
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 +7 -0
- data/LICENSE.adoc +22 -0
- data/README.adoc +807 -0
- data/Rakefile +7 -0
- data/bin/asciidoctor-bespoke +20 -0
- data/lib/asciidoctor-bespoke.rb +2 -0
- data/lib/asciidoctor-bespoke/converter.rb +48 -0
- data/lib/asciidoctor-bespoke/version.rb +5 -0
- data/templates/slim/admonition.html.slim +4 -0
- data/templates/slim/audio.html.slim +3 -0
- data/templates/slim/document.html.slim +69 -0
- data/templates/slim/helpers.rb +222 -0
- data/templates/slim/image.html.slim +24 -0
- data/templates/slim/listing.html.slim +15 -0
- data/templates/slim/literal.html.slim +10 -0
- data/templates/slim/notes.html.slim +2 -0
- data/templates/slim/olist.html.slim +14 -0
- data/templates/slim/open.html.slim +4 -0
- data/templates/slim/paragraph.html.slim +9 -0
- data/templates/slim/quote.html.slim +14 -0
- data/templates/slim/section.html.slim +32 -0
- data/templates/slim/sidebar.html.slim +7 -0
- data/templates/slim/slide_speaker.html.slim +13 -0
- data/templates/slim/slide_title.html.slim +54 -0
- data/templates/slim/ulist.html.slim +14 -0
- data/templates/slim/video.html.slim +36 -0
- metadata +129 -0
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
if File.file?(asciidoctor_bespoke = (File.expand_path '../../lib/asciidoctor-bespoke.rb', __FILE__))
|
4
|
+
require asciidoctor_bespoke
|
5
|
+
else
|
6
|
+
require 'asciidoctor-bespoke'
|
7
|
+
end
|
8
|
+
require 'asciidoctor/cli'
|
9
|
+
|
10
|
+
options = Asciidoctor::Cli::Options.new backend: 'bespoke'
|
11
|
+
|
12
|
+
# FIXME This is a really bizarre API. Please make me simpler.
|
13
|
+
if Integer === (result = options.parse! ARGV)
|
14
|
+
exit result
|
15
|
+
else
|
16
|
+
invoker = Asciidoctor::Cli::Invoker.new options
|
17
|
+
GC.start
|
18
|
+
invoker.invoke!
|
19
|
+
exit invoker.code
|
20
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'asciidoctor/converter/html5'
|
2
|
+
require 'asciidoctor/converter/composite'
|
3
|
+
require 'asciidoctor/converter/template'
|
4
|
+
|
5
|
+
# Asciidoctor < 1.5.5 doesn't recognize svg tag name if followed immediately by newline
|
6
|
+
Asciidoctor::Converter::Html5Converter.tap do |klass|
|
7
|
+
klass.send :remove_const, :SvgPreambleRx
|
8
|
+
klass.const_set :SvgPreambleRx, /\A.*?(?=<svg\b)/m
|
9
|
+
end if Asciidoctor::VERSION == '1.5.3' || Asciidoctor::VERSION == '1.5.4'
|
10
|
+
|
11
|
+
module Asciidoctor
|
12
|
+
module Bespoke
|
13
|
+
class Converter < ::Asciidoctor::Converter::CompositeConverter
|
14
|
+
ProvidedTemplatesDir = ::File.expand_path '../../../templates', __FILE__
|
15
|
+
SlimPrettyOpts = { pretty: true, indent: false }.freeze
|
16
|
+
register_for 'bespoke'
|
17
|
+
|
18
|
+
def initialize backend, opts = {}
|
19
|
+
template_dirs = [ProvidedTemplatesDir] # last dir wins
|
20
|
+
if (custom_template_dirs = opts[:template_dirs])
|
21
|
+
template_dirs += custom_template_dirs.map {|d| ::File.expand_path d }
|
22
|
+
end
|
23
|
+
include_dirs = template_dirs.reverse.tap {|c| c << (::File.join c.pop, 'slim') } # first dir wins
|
24
|
+
engine_opts = (opts[:template_engine_options] || {}).dup
|
25
|
+
extra_slim_opts = { include_dirs: include_dirs }
|
26
|
+
extra_slim_opts.update SlimPrettyOpts if Set.new(%w(1 true)).include?(ENV['SLIM_PRETTY'].to_s)
|
27
|
+
engine_opts[:slim] = (engine_opts.key? :slim) ? (extra_slim_opts.merge engine_opts[:slim]) : extra_slim_opts
|
28
|
+
template_opts = opts.merge htmlsyntax: 'html', template_engine: 'slim', template_engine_options: engine_opts
|
29
|
+
template_converter = ::Asciidoctor::Converter::TemplateConverter.new backend, template_dirs, template_opts
|
30
|
+
html5_converter = ::Asciidoctor::Converter::Html5Converter.new backend, opts
|
31
|
+
super backend, template_converter, html5_converter
|
32
|
+
basebackend 'html'
|
33
|
+
htmlsyntax 'html'
|
34
|
+
end
|
35
|
+
|
36
|
+
def convert node, transform = nil, opts = {}
|
37
|
+
if (node.attributes.delete 'skip-option') || node.context == :preamble
|
38
|
+
''
|
39
|
+
# FIXME mixin slide? method to AbstractBlock (or AbstractNode)
|
40
|
+
elsif node.context == :section && node.level == 1 && (node.attr? 'transform')
|
41
|
+
super node, %(slide_#{node.attr 'transform'}), opts
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
doctype 5
|
2
|
+
html lang=(attr :lang, 'en' unless attr? :nolang)
|
3
|
+
head
|
4
|
+
meta charset=(attr :encoding, 'utf-8')
|
5
|
+
/ QUESTION should we add maximum-scale=1 to viewport setting?
|
6
|
+
meta name='viewport' content='width=device-width, initial-scale=1'
|
7
|
+
title=(doctitle sanitize: true, use_fallback: true).tr_s(' ', ' ')
|
8
|
+
- [:description, :keywords, :author, :copyright].each do |_key|
|
9
|
+
- if attr? _key
|
10
|
+
meta name=_key content=(attr _key)
|
11
|
+
meta name='generator' content="Asciidoctor #{attr 'asciidoctor-version'} (Bespoke.js converter)"
|
12
|
+
- if attr? :favicon
|
13
|
+
- if (_favicon_href = attr :favicon).empty?
|
14
|
+
- _favicon_href = 'favicon.ico'
|
15
|
+
- if (_favicon_image_type = File.extname(_favicon_href)[1..-1]) == 'ico'
|
16
|
+
- _favicon_image_type = 'x-icon'
|
17
|
+
link rel='shortcut icon' href=_favicon_href type="image/#{_favicon_image_type}"
|
18
|
+
meta name='mobile-web-app-capable' content='yes'
|
19
|
+
- if attr? :appname
|
20
|
+
meta name='application-name' content=(attr :appname)
|
21
|
+
link rel='stylesheet' href='build/build.css'
|
22
|
+
- if attr? :icons, 'font'
|
23
|
+
- if attr? 'iconfont-remote'
|
24
|
+
link rel='stylesheet' href=(attr 'iconfont-cdn', (cdn_uri 'font-awesome', '4.5.0', 'css/font-awesome.min.css'))
|
25
|
+
- else
|
26
|
+
link rel='stylesheet' href=(normalize_web_path 'font-awesome.css', (attr :stylesdir, ''))
|
27
|
+
- case (_source_highlighter = attr 'source-highlighter')
|
28
|
+
- when 'highlight.js'
|
29
|
+
link rel='stylesheet' href="#{_highlightjs_path = attr 'highlightjsdir', (cdn_uri 'highlight.js', '9.1.0')}/styles/#{attr 'highlightjs-theme', 'github'}.min.css"
|
30
|
+
- when 'prettify'
|
31
|
+
link rel='stylesheet' href="#{_prettify_path = attr 'prettifydir', (cdn_uri 'prettify', 'r298')}/#{attr 'prettify-theme', 'prettify'}.min.css"
|
32
|
+
- unless (_docinfo = docinfo :head, '-bespoke.html').empty?
|
33
|
+
=_docinfo
|
34
|
+
body id=id
|
35
|
+
article.deck
|
36
|
+
- unless noheader
|
37
|
+
/ TODO enable :header docinfo once Asciidoctor 1.5.5 is available
|
38
|
+
/- unless (_docinfo = docinfo :header, '-bespoke.html').empty?
|
39
|
+
/ =_docinfo
|
40
|
+
- if header?
|
41
|
+
include slide_title.html.slim
|
42
|
+
=content
|
43
|
+
- unless (_docinfo = docinfo :footer, '-bespoke.html').empty?
|
44
|
+
=_docinfo
|
45
|
+
script src='build/build.js'
|
46
|
+
- case _source_highlighter
|
47
|
+
- when 'highlight.js'
|
48
|
+
script src="#{_highlightjs_path}/highlight.min.js"
|
49
|
+
|<script>Array.prototype.forEach.call(document.querySelectorAll('i.conum+b'),function(n){n.parentNode.removeChild(n)});</script>
|
50
|
+
|<script>hljs.initHighlighting()</script>
|
51
|
+
- when 'prettify'
|
52
|
+
script src="#{_prettify_path}/prettify.js"
|
53
|
+
|<script>Array.prototype.forEach.call(document.querySelectorAll('i.conum+b'),function(n){n.parentNode.removeChild(n)})</script>
|
54
|
+
|<script>prettyPrint()</script>
|
55
|
+
- if attr? :stem
|
56
|
+
script type='text/x-mathjax-config'
|
57
|
+
|MathJax.Hub.Config({
|
58
|
+
messageStyle: "none",
|
59
|
+
tex2jax: {
|
60
|
+
inlineMath: [#{Asciidoctor::INLINE_MATH_DELIMITERS[:latexmath].inspect}],
|
61
|
+
displayMath: [#{Asciidoctor::BLOCK_MATH_DELIMITERS[:latexmath].inspect}],
|
62
|
+
ignoreClass: "nostem|nolatexmath"
|
63
|
+
},
|
64
|
+
asciimath2jax: {
|
65
|
+
delimiters: [#{Asciidoctor::BLOCK_MATH_DELIMITERS[:asciimath].inspect}],
|
66
|
+
ignoreClass: "nostem|noasciimath"
|
67
|
+
}
|
68
|
+
})
|
69
|
+
script src="#{cdn_uri 'mathjax', '2.6.0', 'MathJax.js?config=TeX-MML-AM_HTMLorMML'}"
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# This module gets mixed in to every node (the context of the template) at the
|
2
|
+
# time the node is being converted. The properties and methods in this module
|
3
|
+
# effectively become direct members of the template.
|
4
|
+
module Slim::Helpers
|
5
|
+
CDN_BASE = '//cdnjs.cloudflare.com/ajax/libs'
|
6
|
+
EOL = %(\n)
|
7
|
+
BUILD_ROLE_BY_TYPE = {
|
8
|
+
'self' => 'build',
|
9
|
+
'items' => 'build-items'
|
10
|
+
}
|
11
|
+
|
12
|
+
SvgStartTagRx = /\A<svg[^>]*>/
|
13
|
+
ViewBoxAttributeRx = /\sview[bB]ox="[^"]+"/
|
14
|
+
WidthAttributeRx = /\swidth="([^"]+)"/
|
15
|
+
HeightAttributeRx = /\sheight="([^"]+)"/
|
16
|
+
SliceHintRx = / +/
|
17
|
+
|
18
|
+
# Capture nested template content and register it with the specified key, to
|
19
|
+
# be executed at a later time.
|
20
|
+
#
|
21
|
+
# This method must be invoked using the control code directive (i.e., -). By
|
22
|
+
# using a control code directive, the block is set up to append the result
|
23
|
+
# directly to the output buffer. (Integrations often hide the distinction
|
24
|
+
# between a control code directive and an output directive in this context).
|
25
|
+
#
|
26
|
+
# key - The Symbol under which to save the template block.
|
27
|
+
# opts - A Hash of options to control processing (default: {}):
|
28
|
+
# * :append - A Boolean that indicates whether to append this block
|
29
|
+
# to others registered with this key (default: false).
|
30
|
+
# * :content - String content to be used if template content is not
|
31
|
+
# provided (optional).
|
32
|
+
# block - The template content (in Slim template syntax).
|
33
|
+
#
|
34
|
+
# Examples
|
35
|
+
#
|
36
|
+
# - content_for :body
|
37
|
+
# p content
|
38
|
+
# - content_for :body, append: true
|
39
|
+
# p more content
|
40
|
+
#
|
41
|
+
# Returns nothing.
|
42
|
+
def content_for key, opts = {}, &block
|
43
|
+
@content = {} unless defined? @content
|
44
|
+
(opts[:append] ? (@content[key] ||= []) : (@content[key] = [])) << (block_given? ? block : lambda { opts[:content] })
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# Checks whether deferred template content has been registered for the specified key.
|
49
|
+
#
|
50
|
+
# key - The Symbol under which to look for saved template blocks.
|
51
|
+
#
|
52
|
+
# Returns a Boolean indicating whether content has been registered for this key.
|
53
|
+
def content_for? key
|
54
|
+
(defined? @content) && (@content.key? key)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Evaluates the deferred template content registered with the specified key.
|
58
|
+
#
|
59
|
+
# When the corresponding content_for method is invoked using a control code
|
60
|
+
# directive, the block is set up to append the result to the output buffer
|
61
|
+
# directly.
|
62
|
+
#
|
63
|
+
# key - The Symbol under which to look for template blocks to yield.
|
64
|
+
# opts - A Hash of options to control processing (default: {}):
|
65
|
+
# * :drain - A Boolean indicating whether to drain the key of blocks
|
66
|
+
# after calling them (default: true).
|
67
|
+
#
|
68
|
+
# Examples
|
69
|
+
#
|
70
|
+
# - yield_content :body
|
71
|
+
#
|
72
|
+
# Returns nothing (assuming the content has been captured in the context of control code).
|
73
|
+
def yield_content key, opts = {}
|
74
|
+
if (defined? @content) && (blks = (opts.fetch :drain, true) ? (@content.delete key) : @content[key])
|
75
|
+
blks.map {|b| b.call }.join
|
76
|
+
end
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def asset_uri_scheme
|
81
|
+
if instance_variable_defined? :@asset_uri_scheme
|
82
|
+
@asset_uri_scheme
|
83
|
+
else
|
84
|
+
@asset_uri_scheme = (scheme = @document.attr 'asset-uri-scheme', 'https').nil_or_empty? ? nil : %(#{scheme}:)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def cdn_uri name, version, path = nil
|
89
|
+
[%(#{asset_uri_scheme}#{CDN_BASE}), name, version, path].compact * '/'
|
90
|
+
end
|
91
|
+
|
92
|
+
#--
|
93
|
+
#TODO mix directly into AbstractNode
|
94
|
+
def local_attr name, default_val = nil
|
95
|
+
attr name, default_val, false
|
96
|
+
end
|
97
|
+
|
98
|
+
#--
|
99
|
+
#TODO mix directly into AbstractNode
|
100
|
+
def local_attr? name, default_val = nil
|
101
|
+
attr? name, default_val, false
|
102
|
+
end
|
103
|
+
|
104
|
+
# Retrieve the converted content, wrap it in a `<p>` element if
|
105
|
+
# the content_model equals :simple and return the result.
|
106
|
+
#
|
107
|
+
# Returns the block content as a String, wrapped inside a `<p>` element if
|
108
|
+
# the content_model equals `:simple`.
|
109
|
+
def resolve_content
|
110
|
+
@content_model == :simple ? %(<p>#{content}</p>) : content
|
111
|
+
end
|
112
|
+
|
113
|
+
#--
|
114
|
+
#TODO mix into AbstractBlock directly?
|
115
|
+
def pluck selector = {}, &block
|
116
|
+
quantity = (selector.delete :quantity).to_i
|
117
|
+
if blocks?
|
118
|
+
unless (result = find_by selector, &block).empty?
|
119
|
+
result = result[0..(quantity - 1)] if quantity > 0
|
120
|
+
result.each {|b| b.set_attr 'skip-option', '' }
|
121
|
+
end
|
122
|
+
else
|
123
|
+
result = []
|
124
|
+
end
|
125
|
+
quantity == 1 ? result[0] : result
|
126
|
+
end
|
127
|
+
|
128
|
+
def pluck_first selector = {}, &block
|
129
|
+
pluck selector.merge(quantity: 1), &block
|
130
|
+
end
|
131
|
+
|
132
|
+
def partition_title str
|
133
|
+
::Asciidoctor::Document::Title.new str, separator: (@document.attr 'title-separator')
|
134
|
+
end
|
135
|
+
|
136
|
+
# Retrieves the level-1 section node for the current slide.
|
137
|
+
#
|
138
|
+
# Returns the Asciidoctor::Section for the current slide.
|
139
|
+
def slide
|
140
|
+
node = self
|
141
|
+
until node.context == :section && node.level == 1
|
142
|
+
node = node.parent
|
143
|
+
end
|
144
|
+
node
|
145
|
+
end
|
146
|
+
|
147
|
+
# Resolves the list of build-related roles for this block.
|
148
|
+
#
|
149
|
+
# Consults the build attribute first, then the build option if the build
|
150
|
+
# attribute is not set.
|
151
|
+
#
|
152
|
+
# Also sets the build-initiated attribute on the slide if not previously set.
|
153
|
+
#
|
154
|
+
# Returns an Array of build-related roles or an empty Array if builds are not
|
155
|
+
# enabled on this node.
|
156
|
+
def build_roles
|
157
|
+
if local_attr? :build
|
158
|
+
slide.set_attr 'build-initiated', ''
|
159
|
+
(local_attr :build).split('+').map {|type| BUILD_ROLE_BY_TYPE[type] }
|
160
|
+
elsif option? :build
|
161
|
+
if (_slide = slide).local_attr? 'build-initiated'
|
162
|
+
['build-items']
|
163
|
+
else
|
164
|
+
_slide.set_attr 'build-initiated', ''
|
165
|
+
['build', 'build-items']
|
166
|
+
end
|
167
|
+
else
|
168
|
+
[]
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# QUESTION should we wrap in span.line if active but delimiter is not present?
|
173
|
+
# TODO alternate terms for "slice" - part(ition), chunk, segment, split, break
|
174
|
+
def slice_text str, active = nil
|
175
|
+
if (active || (active.nil? && (option? :slice))) && (str.include? ' ')
|
176
|
+
(str.split SliceHintRx).map {|line| %(<span class="line">#{line}</span>) }.join EOL
|
177
|
+
else
|
178
|
+
str
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Retrieves the built-in html5 converter.
|
183
|
+
#
|
184
|
+
# Returns the instance of the Asciidoctor::Converter::Html5Converter
|
185
|
+
# associated with this node.
|
186
|
+
def html5_converter
|
187
|
+
converter.converters[-1]
|
188
|
+
end
|
189
|
+
|
190
|
+
def delegate
|
191
|
+
html5_converter.convert self
|
192
|
+
end
|
193
|
+
|
194
|
+
def include_svg target
|
195
|
+
if (svg = html5_converter.read_svg_contents self, target)
|
196
|
+
# add viewBox attribute if missing
|
197
|
+
unless ViewBoxAttributeRx =~ (start_tag = SvgStartTagRx.match(svg)[0])
|
198
|
+
if (width = start_tag.match WidthAttributeRx) && (width = width[1].to_f) >= 0 &&
|
199
|
+
(height = start_tag.match HeightAttributeRx) && (height = height[1].to_f) >= 0
|
200
|
+
width = width.to_i if width == width.to_i
|
201
|
+
height = height.to_i if height == height.to_i
|
202
|
+
svg = %(<svg viewBox="0 0 #{width} #{height}"#{start_tag[4..-1]}#{svg[start_tag.length..-1]})
|
203
|
+
end
|
204
|
+
end
|
205
|
+
svg
|
206
|
+
else
|
207
|
+
%(<span class="alt">#{local_attr 'alt'}</span>)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def spacer
|
212
|
+
' '
|
213
|
+
end
|
214
|
+
|
215
|
+
def newline
|
216
|
+
if defined? @pretty
|
217
|
+
@pretty ? EOL : nil
|
218
|
+
elsif (@pretty = ::Thread.current[:tilt_current_template].options[:pretty])
|
219
|
+
EOL
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
- _target, _alt, _width, _height, _format, _reload = (local_attr :target), (local_attr :alt), (local_attr :width), (local_attr :height), (local_attr :format), ('true' if option? :reload)
|
2
|
+
/ QUESTION support default image role?
|
3
|
+
figure.image id=id class=[role, ('flux' if option? :flux)]
|
4
|
+
/ TODO support link on image
|
5
|
+
/ TODO append an index/counter to the alt attribute (since it is otherwise shared)
|
6
|
+
- _target.split(/[#,]/).each do |_target|
|
7
|
+
=newline
|
8
|
+
- if _format == 'svg' || (_target.include? '.svg') && ((_svg = option? :inline) || (_obj = option? :interactive))
|
9
|
+
- case
|
10
|
+
- when _svg
|
11
|
+
=include_svg _target
|
12
|
+
- when _obj
|
13
|
+
object type='image/svg+xml' data=(image_uri _target) width=_width height=_height data-reload=_reload
|
14
|
+
/ TODO support fallback option
|
15
|
+
span.alt=_alt
|
16
|
+
- else
|
17
|
+
img src=(image_uri _target) alt=_alt width=_width height=_height data-reload=_reload
|
18
|
+
- if title?
|
19
|
+
=newline
|
20
|
+
figcaption=title
|
21
|
+
- if local_attr? :credit
|
22
|
+
=newline
|
23
|
+
/ QUESTION sould we use data-credit on <figure> element instead?
|
24
|
+
small.credit=local_attr :credit
|
@@ -0,0 +1,15 @@
|
|
1
|
+
- _has_title = title?
|
2
|
+
- content_for :pre
|
3
|
+
- if style == 'source' && (_lang = local_attr :language)
|
4
|
+
/ NOTE class attribute required for highlight.js and prettify
|
5
|
+
/ QUESTION should we add a "highlight" class to pre?
|
6
|
+
pre.source id=(id unless _has_title) : code data-lang=_lang class="language-#{_lang} prettyprint" =content
|
7
|
+
- else
|
8
|
+
/ QUESTION should we add a class to pre?
|
9
|
+
pre id=(id unless _has_title) : code =content
|
10
|
+
- if _has_title
|
11
|
+
figure.listing id=id
|
12
|
+
figcaption=title
|
13
|
+
- yield_content :pre
|
14
|
+
- else
|
15
|
+
- yield_content :pre
|
@@ -0,0 +1,10 @@
|
|
1
|
+
// NOTE if style is not set, then it's a literal paragraph (not a literal block)
|
2
|
+
- _has_title = title?
|
3
|
+
- content_for :pre
|
4
|
+
pre.screen id=(id unless _has_title) class=role : samp =content
|
5
|
+
- if _has_title
|
6
|
+
figure.listing id=id
|
7
|
+
figcaption=title
|
8
|
+
- yield_content :pre
|
9
|
+
- else
|
10
|
+
- yield_content :pre
|