asciidoctor-bespoke 1.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ begin
2
+ require 'bundler/gem_tasks'
3
+ rescue LoadError => e
4
+ warn e.message
5
+ end
6
+
7
+ task default: ['build']
@@ -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,2 @@
1
+ require 'asciidoctor' unless defined? Asciidoctor::Converter
2
+ require_relative 'asciidoctor-bespoke/converter'
@@ -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,5 @@
1
+ module Asciidoctor
2
+ module Bespoke
3
+ VERSION = '1.0.0.alpha.1'
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ - if has_role? 'cue'
2
+ include notes.html.slim
3
+ - else
4
+ =delegate
@@ -0,0 +1,3 @@
1
+ audio(id=id src=(media_uri local_attr :target) loop=('true' if option? :loop) preload='auto'
2
+ data-rewind=('true' if option? :rewind) data-volume=((option? :muted) ? 0 : (attr :volume)))
3
+ span.alt=local_attr :alt, 'Your browser does not support the audio element.'
@@ -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