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.
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