asciidoctor-html5s 0.1.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.adoc +62 -0
  4. data/asciidoctor-html5s.gemspec +34 -0
  5. data/data/templates/_attribution.html.slim +4 -0
  6. data/data/templates/_footer.html.slim +8 -0
  7. data/data/templates/_footnotes.html.slim +11 -0
  8. data/data/templates/_hdlist.html.slim +20 -0
  9. data/data/templates/_header.html.slim +27 -0
  10. data/data/templates/_qanda.html.slim +12 -0
  11. data/data/templates/_toc.html.slim +4 -0
  12. data/data/templates/admonition.html.slim +10 -0
  13. data/data/templates/audio.html.slim +7 -0
  14. data/data/templates/colist.html.slim +4 -0
  15. data/data/templates/dlist.html.slim +13 -0
  16. data/data/templates/document.html.slim +30 -0
  17. data/data/templates/embedded.html.slim +5 -0
  18. data/data/templates/example.html.slim +2 -0
  19. data/data/templates/floating_title.html.slim +2 -0
  20. data/data/templates/helpers.rb +665 -0
  21. data/data/templates/image.html.slim +3 -0
  22. data/data/templates/inline_anchor.html.slim +12 -0
  23. data/data/templates/inline_break.html.slim +2 -0
  24. data/data/templates/inline_button.html.slim +1 -0
  25. data/data/templates/inline_callout.html.slim +1 -0
  26. data/data/templates/inline_footnote.html.slim +9 -0
  27. data/data/templates/inline_image.html.slim +10 -0
  28. data/data/templates/inline_indexterm.html.slim +2 -0
  29. data/data/templates/inline_kbd.html.slim +7 -0
  30. data/data/templates/inline_menu.html.slim +18 -0
  31. data/data/templates/inline_quoted.html.slim +26 -0
  32. data/data/templates/listing.html.slim +16 -0
  33. data/data/templates/literal.html.slim +2 -0
  34. data/data/templates/olist.html.slim +4 -0
  35. data/data/templates/open.html.slim +7 -0
  36. data/data/templates/outline.html.slim +9 -0
  37. data/data/templates/page_break.html.slim +1 -0
  38. data/data/templates/paragraph.html.slim +6 -0
  39. data/data/templates/pass.html.slim +1 -0
  40. data/data/templates/preamble.html.slim +4 -0
  41. data/data/templates/quote.html.slim +6 -0
  42. data/data/templates/section.html.slim +13 -0
  43. data/data/templates/sidebar.html.slim +4 -0
  44. data/data/templates/stem.html.slim +2 -0
  45. data/data/templates/table.html.slim +39 -0
  46. data/data/templates/thematic_break.html.slim +1 -0
  47. data/data/templates/toc.html.slim +9 -0
  48. data/data/templates/ulist.html.slim +11 -0
  49. data/data/templates/verse.html.slim +7 -0
  50. data/data/templates/video.html.slim +18 -0
  51. data/lib/asciidoctor-html5s.rb +1 -0
  52. data/lib/asciidoctor/html5s.rb +8 -0
  53. data/lib/asciidoctor/html5s/attached_colist_treeprocessor.rb +24 -0
  54. data/lib/asciidoctor/html5s/converter.rb +3009 -0
  55. data/lib/asciidoctor/html5s/version.rb +5 -0
  56. metadata +226 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c16c90fa30631f549c6e9a6f9677919571dc2d69
4
+ data.tar.gz: 73ea5fbfd2ee70c5d361e1c8b6d5ef3ef4b6d804
5
+ SHA512:
6
+ metadata.gz: 9fd08e3a180262d33d54d187fe373286306405b65d0a49e5a295e0b5c7116c790dcf678e4a6300d6a20c43ea4f6746463539b292f177e23a30fc4985e2c7a30e
7
+ data.tar.gz: 87bc0fba47221dd500b4ffe14c1f507aa608d9816ca809b18788c29a3cd8f27eefa49ce488b81122e0f93f3e97669d8faed8a89250d37613d5210f6d46b8ddc2
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright 2014-2017 Jakub Jirutka <jakub@jirutka.cz> and the Asciidoctor Project.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.adoc ADDED
@@ -0,0 +1,62 @@
1
+ = Semantic HTML5 Backend For Asciidoctor
2
+ // custom
3
+ :gem-name: asciidoctor-html5s
4
+ :gh-name: jirutka/{gem-name}
5
+ :gh-branch: master
6
+
7
+ ifdef::env-github[]
8
+ image:https://travis-ci.org/{gh-name}.svg?branch={gh-branch}[Build Status, link="https://travis-ci.org/{gh-name}"]
9
+ image:https://img.shields.io/gem/v/{gem-name}.svg?style=flat[Gem Version, link="https://rubygems.org/gems/{gem-name}"]
10
+ endif::env-github[]
11
+
12
+ This project provides alternative HTML5 converter (backend) for http://asciidoctor.org/[Asciidoctor] that focuses on correct semantics, accessibility and compatibility with common typographic CSS styles.
13
+
14
+
15
+ == Goals
16
+
17
+ * Clean markup with correct HTML5 semantics.
18
+ * Good accessibility for people with disabilities.
19
+ * Compatibility with common typographic CSS styles when possible and especially with GitHub and GitLab.
20
+ * Full standalone converter without fallback to the built-in Asciidoctor converters.
21
+ * Easy to use and integrate into third-party projects.
22
+ * Well readable and maintainable code – this should be never sacrificed for performance (I’m looking at you, Asciidoctor!).
23
+
24
+
25
+ == Non-goals
26
+
27
+ * Compatibility with existing Asciidoctor CSS styles.
28
+
29
+
30
+ == Requirements
31
+
32
+ * https://www.ruby-lang.org/[Ruby] 2.0+ or http://jruby.org/[JRuby] 9.1+
33
+ * https://rubygems.org/gems/asciidoctor/[Asciidoctor] 1.5.5+
34
+ * https://rubygems.org/gems/thread_safe/[thread_safe] (not required, but recommended)
35
+
36
+ Note: This converter consists of https://github.com/slim-template/slim/[Slim] templates, but they are precompiled into pure Ruby code using https://github.com/jirutka/asciidoctor-templates-compiler/[asciidoctor-templates-compiler], so you don’t need Slim to use it!
37
+
38
+
39
+ == Installation
40
+
41
+ Install {gem-name} from Rubygems:
42
+
43
+ [source, sh, subs="+attributes"]
44
+ gem install {gem-name}
45
+
46
+ or to get the latest development version:
47
+
48
+ [source, sh, subs="+attributes"]
49
+ gem install --pre {gem-name}
50
+
51
+
52
+ == Usage
53
+
54
+ [source, sh, subs="+attributes"]
55
+ asciidoctor -r {gem-name} -b html5s FILE...
56
+
57
+
58
+ == License
59
+
60
+ This project is licensed under http://opensource.org/licenses/MIT/[MIT License].
61
+ For the full text of the license, see the link:LICENSE[LICENSE] file.
62
+
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ require File.expand_path('lib/asciidoctor/html5s/version', __dir__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'asciidoctor-html5s'
6
+ s.version = Asciidoctor::Html5s::VERSION
7
+ s.author = 'Jakub Jirutka'
8
+ s.email = 'jakub@jirutka.cz'
9
+ s.homepage = 'https://github.com/jirutka/asciidoctor-html5s'
10
+ s.license = 'MIT'
11
+
12
+ s.summary = 'Semantic HTML5 converter (backend) for Asciidoctor'
13
+ s.description = <<-EOF
14
+ This project provides alternative HTML5 converter (backend) for Asciidoctor
15
+ that focuses on correct semantics, accessibility and compatibility with common
16
+ typographic CSS styles.
17
+ EOF
18
+
19
+ s.files = Dir['data/**/*', 'lib/**/*', '*.gemspec', 'LICENSE*', 'README*']
20
+ s.has_rdoc = false
21
+
22
+ s.required_ruby_version = '>= 2.0'
23
+
24
+ s.add_runtime_dependency 'asciidoctor', '~> 1.5.5'
25
+ s.add_runtime_dependency 'thread_safe', '~> 0.3.4'
26
+
27
+ s.add_development_dependency 'asciidoctor-doctest', '= 2.0.0.beta.4'
28
+ s.add_development_dependency 'asciidoctor-templates-compiler', '~> 0.1.2'
29
+ s.add_development_dependency 'bundler', '~> 1.6'
30
+ s.add_development_dependency 'coderay', '~> 1.1'
31
+ s.add_development_dependency 'rake', '~> 10.0'
32
+ s.add_development_dependency 'slim', '~> 3.0'
33
+ s.add_development_dependency 'slim-htag', '~> 0.1.0'
34
+ end
@@ -0,0 +1,4 @@
1
+ footer
2
+ '&#8212;
3
+ cite
4
+ =[(attr :attribution), (attr :citetitle)].compact.join(', ')
@@ -0,0 +1,8 @@
1
+ #footer-text
2
+ - if attr? :revnumber
3
+ | #{attr 'version-label'} #{attr :revnumber}
4
+ - if attr? 'last-update-label'
5
+ br
6
+ | #{attr 'last-update-label'} #{attr :docdatetime}
7
+ - unless (docinfo_content = (docinfo :footer)).empty?
8
+ =docinfo_content
@@ -0,0 +1,11 @@
1
+ section.footnotes aria-label='Footnotes' role='doc-endnotes'
2
+ hr
3
+ ol.footnotes
4
+ - footnotes.each do |fn|
5
+ li.footnote id=(footnote_id fn.index) role='doc-endnote'
6
+ ="#{fn.text} "
7
+ a.footnote-backref [
8
+ href="##{footnoteref_id fn.index}"
9
+ role='doc-backlink'
10
+ title='Jump to the first occurrence in the text' ]
11
+ | &#8617;
@@ -0,0 +1,20 @@
1
+ = block_with_title :class=>'hdlist'
2
+ table
3
+ - if (attr? :labelwidth) || (attr? :itemwidth)
4
+ colgroup
5
+ col style=style_value(width: [(attr :labelwidth), '%'])
6
+ col style=style_value(width: [(attr :itemwidth), '%'])
7
+ - items.each do |terms, dd|
8
+ tr
9
+ th.hdlist1 class=('strong' if option? 'strong')
10
+ - terms = [*terms]
11
+ - terms.each_with_index do |dt, idx|
12
+ =dt.text
13
+ - unless idx >= terms.count - 1
14
+ br
15
+ td.hdlist2
16
+ - unless dd.nil?
17
+ - if dd.text?
18
+ p =dd.text
19
+ - if dd.blocks?
20
+ =dd.content
@@ -0,0 +1,27 @@
1
+ - if has_header?
2
+ - unless notitle
3
+ h1 =header.title
4
+ - if [:author, :revnumber, :revdate, :revremark].any? {|a| attr? a }
5
+ .details
6
+ - if attr? :author
7
+ span.author#author =(attr :author)
8
+ br
9
+ - if attr? :email
10
+ span.email#email =sub_macros(attr :email)
11
+ br
12
+ - if (authorcount = (attr :authorcount).to_i) > 1
13
+ - (2..authorcount).each do |idx|
14
+ span.author id="author#{idx}" =(attr "author_#{idx}")
15
+ br
16
+ - if attr? "email_#{idx}"
17
+ span.email id="email#{idx}" =sub_macros(attr "email_#{idx}")
18
+ - if attr? :revnumber
19
+ span#revnumber #{((attr 'version-label') || '').downcase} #{attr :revnumber}#{',' if attr? :revdate}
20
+ '
21
+ - if attr? :revdate
22
+ time#revdate datetime=revdate_iso =(attr :revdate)
23
+ - if attr? :revremark
24
+ br
25
+ span#revremark =(attr :revremark)
26
+ - if (attr? :toc) && (attr? 'toc-placement', 'auto')
27
+ include _toc.html
@@ -0,0 +1,12 @@
1
+ = block_with_title :class=>'qlist qanda', :role=>'doc-qna'
2
+ dl.qanda
3
+ - items.each do |questions, answer|
4
+ - [*questions].each do |question|
5
+ dt.qanda-question =question.text
6
+ - unless answer.nil?
7
+ dd.qanda-answer
8
+ - if answer.text?
9
+ = html_tag_if answer.blocks?, :p
10
+ =answer.text
11
+ - if answer.blocks?
12
+ =answer.content
@@ -0,0 +1,4 @@
1
+ nav#toc class=(attr 'toc-class', 'toc') role='doc-toc'
2
+ h2#toctitle =(attr 'toc-title')
3
+ / Renders block_outline.html.
4
+ = converter.convert document, 'outline'
@@ -0,0 +1,10 @@
1
+ - capture
2
+ h6 =(title || caption)
3
+ = html_tag_if !blocks?, :p
4
+ =content
5
+ - if admonition_aside?
6
+ aside.admonitionblock id=id class=[(attr :name), role] role=admonition_aria
7
+ - yield_capture
8
+ - else
9
+ section.admonitionblock id=id class=[(attr :name), role] role=admonition_aria
10
+ - yield_capture
@@ -0,0 +1,7 @@
1
+ = block_with_caption :bottom, :class=>'audioblock'
2
+ audio [
3
+ src=media_uri(attr :target)
4
+ autoplay=(option? 'autoplay')
5
+ controls=!(option? 'nocontrols')
6
+ loop=(option? 'loop') ]
7
+ | Your browser does not support the audio tag.
@@ -0,0 +1,4 @@
1
+ / Note: We ignore title on callout list here.
2
+ ol.colist id=id class=[style, role]
3
+ - items.each do |item|
4
+ li =item.text
@@ -0,0 +1,13 @@
1
+ - case style
2
+ - when 'qanda'
3
+ include _qanda.html
4
+ - when 'horizontal'
5
+ include _hdlist.html
6
+ - else
7
+ = block_with_title :class=>['dlist', style]
8
+ dl
9
+ - items.each do |terms, dd|
10
+ - [*terms].each do |dt|
11
+ dt =dt.text
12
+ - unless dd.nil?
13
+ dd =(print_item_content dd)
@@ -0,0 +1,30 @@
1
+ doctype 5
2
+ html lang=(attr :lang, 'en' unless attr? :nolang)
3
+ head
4
+ meta charset=(attr :encoding, 'UTF-8')
5
+ /[if IE]
6
+ meta http-equiv="X-UA-Compatible" content="IE=edge"
7
+ meta name='viewport' content='width=device-width, initial-scale=1.0'
8
+ meta name='generator' content="Asciidoctor #{attr 'asciidoctor-version'}"
9
+ = html_meta_if 'application-name', (attr 'app-name')
10
+ = html_meta_if 'author', (attr :authors)
11
+ = html_meta_if 'copyright', (attr :copyright)
12
+ = html_meta_if 'description', (attr :description)
13
+ = html_meta_if 'keywords', (attr :keywords)
14
+ title=((doctitle sanitize: true) || (attr 'untitled-label'))
15
+ = styles_and_scripts
16
+ - unless (docinfo_content = docinfo).empty?
17
+ =docinfo_content
18
+ body [
19
+ id=id
20
+ class=[(attr :doctype), ("#{attr 'toc-class'} toc-#{attr 'toc-position', 'left'}" if (attr? 'toc-class') && (attr? :toc) && (attr? 'toc-placement', 'auto'))]
21
+ style=style_value(max_width: (attr 'max-width')) ]
22
+ - unless noheader
23
+ header
24
+ include _header.html
25
+ #content =content
26
+ - unless !footnotes? || (attr? :nofootnotes)
27
+ include _footnotes.html
28
+ - unless nofooter
29
+ footer
30
+ include _footer.html
@@ -0,0 +1,5 @@
1
+ - unless notitle || !has_header?
2
+ h1 id=id =header.title
3
+ =content
4
+ - unless !footnotes? || (attr? :nofootnotes)
5
+ include _footnotes.html
@@ -0,0 +1,2 @@
1
+ = block_with_caption :top, :class=>'exampleblock'
2
+ =content
@@ -0,0 +1,2 @@
1
+ h level=(level + 1) id=id class=[style, role]
2
+ =title
@@ -0,0 +1,665 @@
1
+ require 'asciidoctor'
2
+ require 'asciidoctor/html5s'
3
+ require 'date'
4
+ require 'json'
5
+
6
+ # Needed only in compile-time.
7
+ require 'slim-htag' if defined? Slim
8
+
9
+ if Gem::Version.new(Asciidoctor::VERSION) <= Gem::Version.new('1.5.1')
10
+ fail 'asciidoctor: FAILED: HTML5/Slim backend needs Asciidoctor >=1.5.2!'
11
+ end
12
+
13
+ # Add custom functions to this module that you want to use in your Slim
14
+ # templates. Within the template you can invoke them as top-level functions
15
+ # just like in Haml.
16
+ module Slim::Helpers
17
+
18
+ # URIs of external assets.
19
+ FONT_AWESOME_URI = '//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.1.0/css/font-awesome.min.css'
20
+ HIGHLIGHTJS_BASE_URI = '//cdnjs.cloudflare.com/ajax/libs/highlight.js/7.4'
21
+ MATHJAX_JS_URI = '//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-MML-AM_HTMLorMML'
22
+
23
+ # Defaults
24
+ DEFAULT_HIGHLIGHTJS_THEME = 'github'
25
+ DEFAULT_SECTNUMLEVELS = 3
26
+ DEFAULT_TOCLEVELS = 2
27
+
28
+ # The MathJax configuration.
29
+ MATHJAX_CONFIG = {
30
+ tex2jax: {
31
+ inlineMath: [::Asciidoctor::INLINE_MATH_DELIMITERS[:latexmath].inspect],
32
+ displayMath: [::Asciidoctor::BLOCK_MATH_DELIMITERS[:latexmath].inspect],
33
+ ignoreClass: 'nostem|nolatexmath',
34
+ },
35
+ asciimath2jax: {
36
+ delimiters: [::Asciidoctor::BLOCK_MATH_DELIMITERS[:asciimath].inspect],
37
+ ignoreClass: 'nostem|noasciimath',
38
+ }
39
+ }.to_json
40
+
41
+ VOID_ELEMENTS = %w(area base br col command embed hr img input keygen link
42
+ meta param source track wbr)
43
+
44
+
45
+ ##
46
+ # Captures the given block for later yield.
47
+ #
48
+ # @example Basic capture usage.
49
+ # - capture
50
+ # img src=image_uri
51
+ # - if title?
52
+ # figure.image
53
+ # - yield_capture
54
+ # figcaption =captioned_title
55
+ # - else
56
+ # - yield_capture
57
+ #
58
+ # @example Capture with passing parameters.
59
+ # - capture do |id|
60
+ # img src=image_uri
61
+ # - if title?
62
+ # figure id=@id
63
+ # - yield_capture
64
+ # figcaption =caption
65
+ # - else
66
+ # - yield_capture @id
67
+ #
68
+ # @see yield_capture
69
+ def capture(&block)
70
+ @_html5s_capture = block
71
+ nil
72
+ end
73
+
74
+ ##
75
+ # Yields the captured block (see {#capture}).
76
+ #
77
+ # @param *params parameters to pass to the block.
78
+ # @return A content of the captured block.
79
+ # @see capture
80
+ def yield_capture(*params)
81
+ @_html5s_capture.call(*params) if @_html5s_capture
82
+ end
83
+
84
+ ##
85
+ # Creates an HTML tag with the given name and optionally attributes. Can take
86
+ # a block that will run between the opening and closing tags.
87
+ #
88
+ # @param name [#to_s] the name of the tag.
89
+ # @param attributes [Hash] (default: {})
90
+ # @param content [#to_s] the content; +nil+ to call the block. (default: nil).
91
+ # @yield The block of Slim/HTML code within the tag (optional).
92
+ # @return [String] a rendered HTML element.
93
+ #
94
+ def html_tag(name, attributes = {}, content = nil)
95
+ attrs = attributes.inject([]) do |attrs, (k, v)|
96
+ next attrs if !v || v.nil_or_empty?
97
+ v = v.compact.join(' ') if v.is_a? Array
98
+ attrs << (v == true ? k : %(#{k}="#{v}"))
99
+ end
100
+ attrs_str = attrs.empty? ? '' : attrs.join(' ').prepend(' ')
101
+
102
+ if VOID_ELEMENTS.include? name.to_s
103
+ %(<#{name}#{attrs_str}>)
104
+ else
105
+ content ||= yield if block_given?
106
+ %(<#{name}#{attrs_str}>#{content}</#{name}>)
107
+ end
108
+ end
109
+
110
+ ##
111
+ # Conditionally wraps a block in an element. If condition is +true+ then it
112
+ # renders the specified tag with optional attributes and the given
113
+ # block inside, otherwise it just renders the block.
114
+ #
115
+ # For example:
116
+ #
117
+ # = html_tag_if link?, 'a', {class: 'image', href: (attr :link)}
118
+ # img src='./img/tux.png'
119
+ #
120
+ # will produce:
121
+ #
122
+ # <a href="http://example.org" class="image">
123
+ # <img src="./img/tux.png">
124
+ # </a>
125
+ #
126
+ # if +link?+ is truthy, and just
127
+ #
128
+ # <img src="./img/tux.png">
129
+ #
130
+ # otherwise.
131
+ #
132
+ # @param condition [Boolean] the condition to test to determine whether to
133
+ # render the enclosing tag.
134
+ # @param name (see #html_tag)
135
+ # @param attributes (see #html_tag)
136
+ # @param content (see #html_tag)
137
+ # @yield (see #html_tag)
138
+ # @return [String] a rendered HTML fragment.
139
+ #
140
+ def html_tag_if(condition, name, attributes = {}, content = nil, &block)
141
+ if condition
142
+ html_tag name, attributes, content, &block
143
+ else
144
+ content || yield
145
+ end
146
+ end
147
+
148
+ ##
149
+ # Wraps a block in a div element with the specified class and optionally
150
+ # the node's +id+ and +role+(s). If the node's +title+ is not empty, then a
151
+ # nested div with the class "title" and the title's content is added as well.
152
+ #
153
+ # @example When @id, @role and @title attributes are set.
154
+ # = block_with_title :class=>['quoteblock', 'center']
155
+ # blockquote =content
156
+ #
157
+ # <section id="myid" class="quoteblock center myrole1 myrole2">
158
+ # <h6>Block Title</h6>
159
+ # <blockquote>Lorem ipsum</blockquote>
160
+ # </section>
161
+ #
162
+ # @example When @id, @role and @title attributes are empty.
163
+ # = block_with_title :class=>'quoteblock center', :style=>style_value(float: 'left')
164
+ # blockquote =content
165
+ #
166
+ # <div class="quoteblock center" style="float: left;">
167
+ # <blockquote>Lorem ipsum</blockquote>
168
+ # </div>
169
+ #
170
+ # @example When shorthand style for class attribute is used.
171
+ # = block_with_title 'quoteblock center'
172
+ # blockquote =content
173
+ #
174
+ # <div class="quoteblock center">
175
+ # <blockquote>Lorem ipsum</blockquote>
176
+ # </div>
177
+ #
178
+ # @param attrs [Hash, String] the tag's attributes as Hash),
179
+ # or the tag's class if it's not a Hash.
180
+ # @param title [String, nil] the title.
181
+ # @yield The block of Slim/HTML code within the tag (optional).
182
+ # @return [String] a rendered HTML fragment.
183
+ #
184
+ def block_with_title(attrs = {}, title = @title, &block)
185
+ if (klass = attrs[:class]).is_a? String
186
+ klass = klass.split(' ')
187
+ end
188
+ attrs[:class] = [klass, role].flatten.uniq
189
+ attrs[:id] = id
190
+
191
+ if title.nil_or_empty?
192
+ # XXX quick hack
193
+ nested = is_a?(::Asciidoctor::List) &&
194
+ (parent.is_a?(::Asciidoctor::ListItem) || parent.is_a?(::Asciidoctor::List))
195
+ html_tag_if !nested, :div, attrs, yield
196
+ else
197
+ html_tag :section, attrs do
198
+ [html_tag(:h6, {}, title), yield].join("\n")
199
+ end
200
+ end
201
+ end
202
+
203
+ def block_with_caption(position = :bottom, attrs = {}, &block)
204
+ if (klass = attrs[:class]).is_a? String
205
+ klass = klass.split(' ')
206
+ end
207
+ attrs[:class] = [klass, role].flatten.uniq
208
+ attrs[:id] = id
209
+
210
+ if title.nil_or_empty?
211
+ html_tag :div, attrs, yield
212
+ else
213
+ html_tag :figure, attrs do
214
+ ary = [yield, html_tag(:figcaption) { captioned_title }]
215
+ ary.reverse! if position == :top
216
+ ary.compact.join("\n")
217
+ end
218
+ end
219
+ end
220
+
221
+ ##
222
+ # Delimite the given equation as a STEM of the specified type.
223
+ #
224
+ # @param equation [String] the equation to delimite.
225
+ # @param type [#to_sym] the type of the STEM renderer (latexmath, or asciimath).
226
+ # @return [String] the delimited equation.
227
+ #
228
+ def delimit_stem(equation, type)
229
+ if is_a? ::Asciidoctor::Block
230
+ open, close = ::Asciidoctor::BLOCK_MATH_DELIMITERS[type.to_sym]
231
+ else
232
+ open, close = ::Asciidoctor::INLINE_MATH_DELIMITERS[type.to_sym]
233
+ end
234
+
235
+ if !equation.start_with?(open) || !equation.end_with?(close)
236
+ equation = [open, equation, close].join
237
+ end
238
+ equation
239
+ end
240
+
241
+ ##
242
+ # Formats the given hash as CSS declarations for an inline style.
243
+ #
244
+ # @example
245
+ # style_value(text_align: 'right', float: 'left')
246
+ # => "text-align: right; float: left;"
247
+ #
248
+ # style_value(text_align: nil, float: 'left')
249
+ # => "float: left;"
250
+ #
251
+ # style_value(width: [90, '%'], height: '50px')
252
+ # => "width: 90%; height: 50px;"
253
+ #
254
+ # style_value(width: ['120px', 'px'])
255
+ # => "width: 90px;"
256
+ #
257
+ # style_value(width: [nil, 'px'])
258
+ # => nil
259
+ #
260
+ # @param declarations [Hash]
261
+ # @return [String, nil]
262
+ #
263
+ def style_value(declarations)
264
+ decls = []
265
+
266
+ declarations.each do |prop, value|
267
+ next if value.nil?
268
+
269
+ if value.is_a? Array
270
+ value, unit = value
271
+ next if value.nil?
272
+ value = value.to_s + unit unless value.end_with? unit
273
+ end
274
+ prop = prop.to_s.gsub('_', '-')
275
+ decls << "#{prop}: #{value}"
276
+ end
277
+
278
+ decls.empty? ? nil : decls.join('; ') + ';'
279
+ end
280
+
281
+ def urlize(*segments)
282
+ path = segments * '/'
283
+ if path.start_with? '//'
284
+ @_uri_scheme ||= document.attr('asset-uri-scheme', 'https')
285
+ path = "#{@_uri_scheme}:#{path}" unless @_uri_scheme.empty?
286
+ end
287
+ normalize_web_path path
288
+ end
289
+
290
+
291
+ ##
292
+ # Gets the value of the specified attribute in this node.
293
+ #
294
+ # This is just an alias for +attr+ method with disabled _inherit_ to make it
295
+ # more clear.
296
+ #
297
+ # @param name [String, Symbol] the name of the attribute to lookup.
298
+ # @param default_val the value to return if the attribute is not found.
299
+ # @return value of the attribute or +default_val+ if not found.
300
+ #
301
+ def local_attr(name, default_val = nil)
302
+ attr(name, default_val, false)
303
+ end
304
+
305
+ ##
306
+ # Checks if the attribute is defined on this node, optionally performing
307
+ # a comparison of its value if +expect_val+ is not nil.
308
+ #
309
+ # This is just an alias for +attr?+ method with disabled _inherit_ to make it
310
+ # more clear.
311
+ #
312
+ # @param name [String, Symbol] the name of the attribute to lookup.
313
+ # @param default_val the expected value of the attribute.
314
+ # @return [Boolean] whether the attribute exists and, if +expect_val+ is
315
+ # specified, whether the value of the attribute matches the +expect_val+.
316
+ #
317
+ def local_attr?(name, expect_val = nil)
318
+ attr?(name, expect_val, false)
319
+ end
320
+
321
+ ##
322
+ # @param index [Integer] the footnote's index.
323
+ # @return [String] footnote id to be used in a link.
324
+ def footnote_id(index = local_attr(:index))
325
+ "_footnote_#{index}"
326
+ end
327
+
328
+ ##
329
+ # @param index (see #footnote_id)
330
+ # @return [String] footnoteref id to be used in a link.
331
+ def footnoteref_id(index = local_attr(:index))
332
+ "_footnoteref_#{index}"
333
+ end
334
+
335
+ def icons?
336
+ document.attr? :icons
337
+ end
338
+
339
+ def font_icons?
340
+ document.attr? :icons, 'font'
341
+ end
342
+
343
+ def nowrap?
344
+ 'nowrap' if !document.attr?(:prewrap) || option?('nowrap')
345
+ end
346
+
347
+ def print_item_content(item)
348
+ wrap = item.blocks? && !item.blocks.all? { |b| b.is_a? ::Asciidoctor::List }
349
+ [ (html_tag_if(wrap, :p) { item.text } if item.text?), item.content ].join
350
+ end
351
+
352
+ ##
353
+ # Returns corrected section level.
354
+ #
355
+ # @param sec [Asciidoctor::Section] the section node (default: self).
356
+ # @return [Integer]
357
+ #
358
+ def section_level(sec = self)
359
+ @_section_level ||= (sec.level == 0 && sec.special) ? 1 : sec.level
360
+ end
361
+
362
+ ##
363
+ # Returns the captioned section's title, optionally numbered.
364
+ #
365
+ # @param sec [Asciidoctor::Section] the section node (default: self).
366
+ # @return [String]
367
+ #
368
+ def section_title(sec = self)
369
+ sectnumlevels = document.attr(:sectnumlevels, DEFAULT_SECTNUMLEVELS).to_i
370
+
371
+ if sec.numbered && !sec.caption && sec.level <= sectnumlevels
372
+ [sec.sectnum, sec.captioned_title].join(' ')
373
+ else
374
+ sec.captioned_title
375
+ end
376
+ end
377
+
378
+ def link_rel
379
+ 'noopener' if option?('noopener') || attr(:window) == '_blank'
380
+ end
381
+
382
+ #--------------------------------------------------------
383
+ # block_admonition
384
+ #
385
+
386
+ ##
387
+ # @return [Boolean] should be this admonition wrapped in aside element?
388
+ def admonition_aside?
389
+ %w[note tip].include? attr(:name)
390
+ end
391
+
392
+ ##
393
+ # @return [String, nil] WAI-ARIA role of this admonition.
394
+ def admonition_aria
395
+ case attr(:name)
396
+ when 'note'
397
+ 'note' # https://www.w3.org/TR/wai-aria/roles#note
398
+ when 'tip'
399
+ 'doc-tip' # https://www.w3.org/TR/dpub-aria-1.0/#doc-tip
400
+ when 'caution', 'important', 'warning'
401
+ 'doc-notice' # https://www.w3.org/TR/dpub-aria-1.0/#doc-notice
402
+ end
403
+ end
404
+
405
+ #--------------------------------------------------------
406
+ # block_listing
407
+ #
408
+
409
+ ##
410
+ # @return [String] a canonical name of the source-highlighter to be used as
411
+ # a style class.
412
+ def highlighter
413
+ @_highlighter ||=
414
+ case (highlighter = document.attr('source-highlighter'))
415
+ when 'coderay'; 'CodeRay'
416
+ when 'highlight.js'; 'highlightjs'
417
+ else highlighter
418
+ end
419
+ end
420
+
421
+ ##
422
+ # Returns the callout list attached to this listing node, or +nil+ if none.
423
+ #
424
+ # Note: This variable is set by extension
425
+ # {Asciidoctor::Html5s::AttachedColistTreeprocessor}.
426
+ #
427
+ # @return [Asciidoctor::List, nil]
428
+ def callout_list
429
+ @html5s_colist
430
+ end
431
+
432
+ def source_lang
433
+ local_attr :language, false
434
+ end
435
+
436
+ #--------------------------------------------------------
437
+ # block_open
438
+ #
439
+
440
+ ##
441
+ # Returns +true+ if an abstract block is allowed in this document type,
442
+ # otherwise prints warning and returns +false+.
443
+ def abstract_allowed?
444
+ if result = (parent == document && document.doctype == 'book')
445
+ puts 'asciidoctor: WARNING: abstract block cannot be used in a document
446
+ without a title when doctype is book. Excluding block content.'
447
+ end
448
+ !result
449
+ end
450
+
451
+ ##
452
+ # Returns +true+ if a partintro block is allowed in this context, otherwise
453
+ # prints warning and returns +false+.
454
+ def partintro_allowed?
455
+ if result = (level != 0 || parent.context != :section || document.doctype != 'book')
456
+ puts "asciidoctor: ERROR: partintro block can only be used when doctype
457
+ is book and it's a child of a book part. Excluding block content."
458
+ end
459
+ !result
460
+ end
461
+
462
+ #--------------------------------------------------------
463
+ # block_table
464
+ #
465
+
466
+ def autowidth?
467
+ option? :autowidth
468
+ end
469
+
470
+ def spread?
471
+ if !autowidth? || local_attr?('width')
472
+ 'spread' if attr? :tablepcwidth, 100
473
+ end
474
+ end
475
+
476
+ #--------------------------------------------------------
477
+ # block_video
478
+ #
479
+
480
+ # @return [Boolean] +true+ if the video should be embedded in an iframe.
481
+ def video_iframe?
482
+ ['vimeo', 'youtube'].include? attr(:poster)
483
+ end
484
+
485
+ def video_uri
486
+ case attr(:poster, '').to_sym
487
+ when :vimeo
488
+ params = {
489
+ autoplay: (1 if option? 'autoplay'),
490
+ loop: (1 if option? 'loop')
491
+ }
492
+ start_anchor = "#at=#{attr :start}" if attr? :start
493
+ "//player.vimeo.com/video/#{attr :target}#{start_anchor}#{url_query params}"
494
+
495
+ when :youtube
496
+ video_id, list_id = attr(:target).split('/', 2)
497
+ params = {
498
+ rel: 0,
499
+ start: (attr :start),
500
+ end: (attr :end),
501
+ list: (attr :list, list_id),
502
+ autoplay: (1 if option? 'autoplay'),
503
+ loop: (1 if option? 'loop'),
504
+ controls: (0 if option? 'nocontrols')
505
+ }
506
+ "//www.youtube.com/embed/#{video_id}#{url_query params}"
507
+ else
508
+ anchor = [attr(:start), attr(:end)].join(',').chomp(',')
509
+ anchor.prepend('#t=') unless anchor.empty?
510
+ media_uri "#{attr :target}#{anchor}"
511
+ end
512
+ end
513
+
514
+ # Formats URL query parameters.
515
+ def url_query(params)
516
+ str = params.map { |k, v|
517
+ next if v.nil? || v.to_s.empty?
518
+ [k, v] * '='
519
+ }.compact.join('&amp;')
520
+
521
+ str.prepend('?') unless str.empty?
522
+ end
523
+
524
+ #--------------------------------------------------------
525
+ # document
526
+ #
527
+
528
+ ##
529
+ # @return [String, nil] the revision date in ISO 8601, or nil if not
530
+ # available or in invalid format.
531
+ def revdate_iso
532
+ ::Date.parse(revdate).iso8601
533
+ rescue ArgumentError
534
+ nil
535
+ end
536
+
537
+ ##
538
+ # Returns HTML meta tag if the given +content+ is not +nil+.
539
+ #
540
+ # @param name [#to_s] the name for the metadata.
541
+ # @param content [#to_s, nil] the value of the metadata, or +nil+.
542
+ # @return [String, nil] the meta tag, or +nil+ if the +content+ is +nil+.
543
+ #
544
+ def html_meta_if(name, content)
545
+ %(<meta name="#{name}" content="#{content}">) if content
546
+ end
547
+
548
+ # Returns formatted style/link and script tags for header.
549
+ def styles_and_scripts
550
+ scripts = []
551
+ styles = []
552
+ tags = []
553
+
554
+ stylesheet = attr :stylesheet
555
+ stylesdir = attr :stylesdir, ''
556
+ default_style = ::Asciidoctor::DEFAULT_STYLESHEET_KEYS.include? stylesheet
557
+ linkcss = attr?(:linkcss) || safe >= ::Asciidoctor::SafeMode::SECURE
558
+ ss = ::Asciidoctor::Stylesheets.instance
559
+
560
+ if linkcss
561
+ path = default_style ? ::Asciidoctor::DEFAULT_STYLESHEET_NAME : stylesheet
562
+ styles << { href: [stylesdir, path] }
563
+ elsif default_style
564
+ styles << { text: ss.primary_stylesheet_data }
565
+ else
566
+ styles << { text: read_asset(normalize_system_path(stylesheet, stylesdir), true) }
567
+ end
568
+
569
+ if attr? :icons, 'font'
570
+ if attr? 'iconfont-remote'
571
+ styles << { href: attr('iconfont-cdn', FONT_AWESOME_URI) }
572
+ else
573
+ styles << { href: [stylesdir, "#{attr 'iconfont-name', 'font-awesome'}.css"] }
574
+ end
575
+ end
576
+
577
+ if attr? 'stem'
578
+ scripts << { src: MATHJAX_JS_URI }
579
+ scripts << { type: 'text/x-mathjax-config', text: "MathJax.Hub.Config(#{MATHJAX_CONFIG});" }
580
+ end
581
+
582
+ case attr 'source-highlighter'
583
+ when 'coderay'
584
+ if attr('coderay-css', 'class') == 'class'
585
+ if linkcss
586
+ styles << { href: [stylesdir, ss.coderay_stylesheet_name] }
587
+ else
588
+ styles << { text: ss.coderay_stylesheet_data }
589
+ end
590
+ end
591
+
592
+ when 'highlightjs'
593
+ hjs_base = attr :highlightjsdir, HIGHLIGHTJS_BASE_URI
594
+ hjs_theme = attr 'highlightjs-theme', DEFAULT_HIGHLIGHTJS_THEME
595
+
596
+ scripts << { src: [hjs_base, 'highlight.min.js'] }
597
+ scripts << { src: [hjs_base, 'lang/common.min.js'] }
598
+ scripts << { text: 'hljs.initHighlightingOnLoad()' }
599
+ styles << { href: [hjs_base, "styles/#{hjs_theme}.min.css"] }
600
+ end
601
+
602
+ styles.each do |item|
603
+ if item.key?(:text)
604
+ tags << html_tag(:style) { item[:text] }
605
+ else
606
+ tags << html_tag(:link, rel: 'stylesheet', href: urlize(*item[:href]))
607
+ end
608
+ end
609
+
610
+ scripts.each do |item|
611
+ if item.key? :text
612
+ tags << html_tag(:script, type: item[:type]) { item[:text] }
613
+ else
614
+ tags << html_tag(:script, type: item[:type], src: urlize(*item[:src]))
615
+ end
616
+ end
617
+
618
+ tags.join("\n")
619
+ end
620
+
621
+ #--------------------------------------------------------
622
+ # inline_anchor
623
+ #
624
+
625
+ # @return [String] text of the xref anchor.
626
+ def xref_text
627
+ str =
628
+ if text
629
+ text
630
+ elsif (path = local_attr :path)
631
+ path
632
+ elsif document.respond_to? :catalog # Asciidoctor >=1.5.6
633
+ ref = document.catalog[:refs][attr :refid]
634
+ if ref.kind_of? Asciidoctor::AbstractNode
635
+ ref.xreftext((@_xrefstyle ||= document.attributes['xrefstyle']))
636
+ end
637
+ else # Asciidoctor < 1.5.6
638
+ document.references[:ids][attr :refid || target]
639
+ end
640
+ (str || "[#{attr :refid}]").tr_s("\n", ' ')
641
+ end
642
+
643
+ # @return [String, nil] text of the bibref anchor, or +nil+ if not found.
644
+ def bibref_text
645
+ if document.respond_to? :catalog # Asciidoctor >=1.5.6
646
+ # NOTE: Technically it should be `reftext`, but subs have already been applied to text.
647
+ text
648
+ else
649
+ "[#{target}]"
650
+ end
651
+ end
652
+
653
+ #--------------------------------------------------------
654
+ # inline_image
655
+ #
656
+
657
+ # @return [Array] style classes for a Font Awesome icon.
658
+ def icon_fa_classes
659
+ [ "fa fa-#{target}",
660
+ ("fa-#{attr :size}" if attr? :size),
661
+ ("fa-rotate-#{attr :rotate}" if attr? :rotate),
662
+ ("fa-flip-#{attr :flip}" if attr? :flip)
663
+ ].compact
664
+ end
665
+ end