asciidoctor 0.1.4 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of asciidoctor might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +209 -25
- data/{LICENSE → LICENSE.adoc} +4 -3
- data/README.adoc +392 -395
- data/Rakefile +94 -137
- data/benchmark/benchmark.rb +127 -0
- data/benchmark/sample-data/mdbasics.adoc +334 -0
- data/bin/asciidoctor +5 -8
- data/bin/asciidoctor-safe +4 -8
- data/compat/asciidoc.conf +78 -11
- data/compat/font-awesome-3-compat.css +397 -0
- data/data/stylesheets/asciidoctor-default.css +399 -0
- data/data/stylesheets/coderay-asciidoctor.css +89 -0
- data/features/open_block.feature +92 -0
- data/features/pass_block.feature +66 -0
- data/features/step_definitions.rb +42 -0
- data/features/text_formatting.feature +55 -0
- data/features/xref.feature +116 -0
- data/lib/asciidoctor.rb +1155 -605
- data/lib/asciidoctor/abstract_block.rb +157 -71
- data/lib/asciidoctor/abstract_node.rb +150 -93
- data/lib/asciidoctor/attribute_list.rb +85 -90
- data/lib/asciidoctor/block.rb +51 -24
- data/lib/asciidoctor/callouts.rb +4 -7
- data/lib/asciidoctor/cli.rb +3 -0
- data/lib/asciidoctor/cli/invoker.rb +86 -76
- data/lib/asciidoctor/cli/options.rb +111 -61
- data/lib/asciidoctor/converter.rb +232 -0
- data/lib/asciidoctor/converter/base.rb +58 -0
- data/lib/asciidoctor/converter/composite.rb +66 -0
- data/lib/asciidoctor/converter/docbook45.rb +94 -0
- data/lib/asciidoctor/converter/docbook5.rb +684 -0
- data/lib/asciidoctor/converter/factory.rb +225 -0
- data/lib/asciidoctor/converter/html5.rb +1081 -0
- data/lib/asciidoctor/converter/template.rb +296 -0
- data/lib/asciidoctor/core_ext.rb +7 -0
- data/lib/asciidoctor/core_ext/object/nil_or_empty.rb +23 -0
- data/lib/asciidoctor/core_ext/string/chr.rb +6 -0
- data/lib/asciidoctor/core_ext/symbol/length.rb +6 -0
- data/lib/asciidoctor/document.rb +590 -304
- data/lib/asciidoctor/extensions.rb +1100 -308
- data/lib/asciidoctor/helpers.rb +109 -46
- data/lib/asciidoctor/inline.rb +16 -9
- data/lib/asciidoctor/list.rb +23 -15
- data/lib/asciidoctor/opal_ext.rb +4 -0
- data/lib/asciidoctor/opal_ext/comparable.rb +38 -0
- data/lib/asciidoctor/opal_ext/dir.rb +13 -0
- data/lib/asciidoctor/opal_ext/error.rb +2 -0
- data/lib/asciidoctor/opal_ext/file.rb +125 -0
- data/lib/asciidoctor/{lexer.rb → parser.rb} +646 -455
- data/lib/asciidoctor/path_resolver.rb +141 -77
- data/lib/asciidoctor/reader.rb +257 -187
- data/lib/asciidoctor/section.rb +12 -16
- data/lib/asciidoctor/stylesheets.rb +91 -0
- data/lib/asciidoctor/substitutors.rb +1548 -0
- data/lib/asciidoctor/table.rb +73 -57
- data/lib/asciidoctor/timings.rb +39 -0
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +22 -14
- data/man/asciidoctor.adoc +18 -10
- data/test/attributes_test.rb +314 -14
- data/test/blocks_test.rb +763 -118
- data/test/converter_test.rb +352 -0
- data/test/document_test.rb +518 -199
- data/test/extensions_test.rb +273 -103
- data/test/fixtures/asciidoc_index.txt +27 -13
- data/test/fixtures/basic-docinfo.xml +1 -1
- data/test/fixtures/chapter-a.adoc +3 -0
- data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +6 -0
- data/test/fixtures/docinfo.xml +1 -1
- data/test/fixtures/include-file.asciidoc +2 -0
- data/test/fixtures/master.adoc +5 -0
- data/test/invoker_test.rb +173 -61
- data/test/links_test.rb +97 -21
- data/test/lists_test.rb +181 -22
- data/test/options_test.rb +86 -2
- data/test/paragraphs_test.rb +47 -5
- data/test/{lexer_test.rb → parser_test.rb} +128 -57
- data/test/paths_test.rb +36 -1
- data/test/preamble_test.rb +25 -17
- data/test/reader_test.rb +404 -249
- data/test/sections_test.rb +623 -58
- data/test/substitutions_test.rb +609 -132
- data/test/tables_test.rb +198 -24
- data/test/test_helper.rb +101 -31
- data/test/text_test.rb +88 -31
- metadata +160 -64
- data/Gemfile +0 -12
- data/Guardfile +0 -18
- data/asciidoctor.gemspec +0 -143
- data/lib/asciidoctor/backends/_stylesheets.rb +0 -466
- data/lib/asciidoctor/backends/base_template.rb +0 -114
- data/lib/asciidoctor/backends/docbook45.rb +0 -774
- data/lib/asciidoctor/backends/docbook5.rb +0 -103
- data/lib/asciidoctor/backends/html5.rb +0 -1214
- data/lib/asciidoctor/renderer.rb +0 -259
- data/lib/asciidoctor/substituters.rb +0 -1083
- data/test/fixtures/asciidoc.txt +0 -105
- data/test/fixtures/ascshort.txt +0 -32
- data/test/fixtures/list_elements.asciidoc +0 -10
- data/test/renderer_test.rb +0 -162
data/lib/asciidoctor/renderer.rb
DELETED
@@ -1,259 +0,0 @@
|
|
1
|
-
module Asciidoctor
|
2
|
-
# Public: Methods for rendering Asciidoc Documents, Sections, and Blocks
|
3
|
-
# using eRuby templates.
|
4
|
-
class Renderer
|
5
|
-
RE_ASCIIDOCTOR_NAMESPACE = /^Asciidoctor::/
|
6
|
-
RE_TEMPLATE_CLASS_SUFFIX = /Template$/
|
7
|
-
RE_CAMELCASE_BOUNDARY_1 = /([[:upper:]]+)([[:upper:]][[:alpha:]])/
|
8
|
-
RE_CAMELCASE_BOUNDARY_2 = /([[:lower:]])([[:upper:]])/
|
9
|
-
|
10
|
-
attr_reader :compact
|
11
|
-
attr_reader :cache
|
12
|
-
|
13
|
-
@@global_cache = nil
|
14
|
-
|
15
|
-
# Public: Initialize an Asciidoctor::Renderer object.
|
16
|
-
#
|
17
|
-
def initialize(options={})
|
18
|
-
@debug = !!options[:debug]
|
19
|
-
|
20
|
-
@views = {}
|
21
|
-
@compact = options[:compact]
|
22
|
-
@cache = nil
|
23
|
-
|
24
|
-
backend = options[:backend]
|
25
|
-
case backend
|
26
|
-
when 'html5', 'docbook45', 'docbook5'
|
27
|
-
eruby = load_eruby options[:eruby]
|
28
|
-
#Helpers.require_library 'asciidoctor/backends/' + backend
|
29
|
-
require 'asciidoctor/backends/' + backend
|
30
|
-
# Load up all the template classes that we know how to render for this backend
|
31
|
-
BaseTemplate.template_classes.each do |tc|
|
32
|
-
if tc.to_s.downcase.include?('::' + backend + '::') # optimization
|
33
|
-
view_name, view_backend = self.class.extract_view_mapping(tc)
|
34
|
-
if view_backend == backend
|
35
|
-
@views[view_name] = tc.new(view_name, backend, eruby)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
else
|
40
|
-
Debug.debug { "No built-in templates for backend: #{backend}" }
|
41
|
-
end
|
42
|
-
|
43
|
-
# If user passed in a template dir, let them override our base templates
|
44
|
-
if (template_dirs = options.delete(:template_dirs))
|
45
|
-
Helpers.require_library 'tilt', true
|
46
|
-
|
47
|
-
if (template_cache = options[:template_cache]) === true
|
48
|
-
# FIXME probably want to use our own cache object for more control
|
49
|
-
@cache = (@@global_cache ||= TemplateCache.new)
|
50
|
-
elsif template_cache
|
51
|
-
@cache = template_cache
|
52
|
-
end
|
53
|
-
|
54
|
-
view_opts = {
|
55
|
-
:erb => { :trim => '<>' },
|
56
|
-
:haml => { :format => :xhtml, :attr_wrapper => '"', :ugly => true, :escape_attrs => false },
|
57
|
-
:slim => { :disable_escape => true, :sort_attrs => false, :pretty => false }
|
58
|
-
}
|
59
|
-
|
60
|
-
# workaround until we have a proper way to configure
|
61
|
-
if {'html5' => true, 'dzslides' => true, 'deckjs' => true, 'revealjs' => true}.has_key? backend
|
62
|
-
view_opts[:haml][:format] = view_opts[:slim][:format] = :html5
|
63
|
-
end
|
64
|
-
|
65
|
-
slim_loaded = false
|
66
|
-
path_resolver = PathResolver.new
|
67
|
-
engine = options[:template_engine]
|
68
|
-
|
69
|
-
template_dirs.each do |template_dir|
|
70
|
-
# TODO need to think about safe mode restrictions here
|
71
|
-
template_dir = path_resolver.system_path template_dir, nil
|
72
|
-
template_glob = '*'
|
73
|
-
if engine
|
74
|
-
template_glob = "*.#{engine}"
|
75
|
-
# example: templates/haml
|
76
|
-
if File.directory? File.join(template_dir, engine)
|
77
|
-
template_dir = File.join template_dir, engine
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
# example: templates/html5 or templates/haml/html5
|
82
|
-
if File.directory? File.join(template_dir, backend)
|
83
|
-
template_dir = File.join template_dir, backend
|
84
|
-
end
|
85
|
-
|
86
|
-
# skip scanning folder if we've already done it for same backend/engine
|
87
|
-
if @cache && @cache.cached?(:scan, template_dir, template_glob)
|
88
|
-
@views.update(@cache.fetch :scan, template_dir, template_glob)
|
89
|
-
next
|
90
|
-
end
|
91
|
-
|
92
|
-
helpers = nil
|
93
|
-
scan_result = {}
|
94
|
-
# Grab the files in the top level of the directory (we're not traversing)
|
95
|
-
Dir.glob(File.join(template_dir, template_glob)).
|
96
|
-
select{|f| File.file? f }.each do |template|
|
97
|
-
basename = File.basename(template)
|
98
|
-
if basename == 'helpers.rb'
|
99
|
-
helpers = template
|
100
|
-
next
|
101
|
-
end
|
102
|
-
name_parts = basename.split('.')
|
103
|
-
next if name_parts.size < 2
|
104
|
-
view_name = name_parts.first
|
105
|
-
ext_name = name_parts.last
|
106
|
-
if ext_name == 'slim' && !slim_loaded
|
107
|
-
# slim doesn't get loaded by Tilt
|
108
|
-
Helpers.require_library 'slim', true
|
109
|
-
end
|
110
|
-
next unless Tilt.registered? ext_name
|
111
|
-
opts = view_opts[ext_name.to_sym]
|
112
|
-
if @cache
|
113
|
-
@views[view_name] = scan_result[view_name] = @cache.fetch(:view, template) {
|
114
|
-
Tilt.new(template, nil, opts)
|
115
|
-
}
|
116
|
-
else
|
117
|
-
@views[view_name] = Tilt.new template, nil, opts
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
require helpers unless helpers.nil?
|
122
|
-
@cache.store(scan_result, :scan, template_dir, template_glob) if @cache
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
# Public: Render an Asciidoc object with a specified view template.
|
128
|
-
#
|
129
|
-
# view - the String view template name.
|
130
|
-
# object - the Object to be used as an evaluation scope.
|
131
|
-
# locals - the optional Hash of locals to be passed to Tilt (default {}) (also ignored, really)
|
132
|
-
def render(view, object, locals = {})
|
133
|
-
if !@views.has_key? view
|
134
|
-
raise "Couldn't find a view in @views for #{view}"
|
135
|
-
end
|
136
|
-
|
137
|
-
@views[view].render(object, locals)
|
138
|
-
end
|
139
|
-
|
140
|
-
def views
|
141
|
-
readonly_views = @views.dup
|
142
|
-
readonly_views.freeze
|
143
|
-
readonly_views
|
144
|
-
end
|
145
|
-
|
146
|
-
def register_view(view_name, tilt_template)
|
147
|
-
# TODO need to figure out how to cache this
|
148
|
-
@views[view_name] = tilt_template
|
149
|
-
end
|
150
|
-
|
151
|
-
# Internal: Load the eRuby implementation
|
152
|
-
#
|
153
|
-
# name - the String name of the eRuby implementation (default: 'erb')
|
154
|
-
#
|
155
|
-
# returns the eRuby implementation class
|
156
|
-
def load_eruby(name)
|
157
|
-
if name.nil? || !['erb', 'erubis'].include?(name)
|
158
|
-
name = 'erb'
|
159
|
-
end
|
160
|
-
|
161
|
-
if name == 'erb'
|
162
|
-
Helpers.require_library 'erb'
|
163
|
-
::ERB
|
164
|
-
elsif name == 'erubis'
|
165
|
-
Helpers.require_library 'erubis', true
|
166
|
-
::Erubis::FastEruby
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
# TODO better name for this method (and/or field)
|
171
|
-
def self.global_cache
|
172
|
-
@@global_cache
|
173
|
-
end
|
174
|
-
|
175
|
-
# TODO better name for this method (and/or field)
|
176
|
-
def self.reset_global_cache
|
177
|
-
@@global_cache.clear if @@global_cache
|
178
|
-
end
|
179
|
-
|
180
|
-
# Internal: Extracts the view name and backend from a qualified Ruby class
|
181
|
-
#
|
182
|
-
# The purpose of this method is to determine the view name and backend to
|
183
|
-
# which a built-in template class maps. We can make certain assumption since
|
184
|
-
# we have control over these class names. The Asciidoctor:: prefix and
|
185
|
-
# Template suffix are stripped as the first step in the conversion.
|
186
|
-
#
|
187
|
-
# qualified_class - The Class or String qualified class name from which to extract the view name and backend
|
188
|
-
#
|
189
|
-
# Examples
|
190
|
-
#
|
191
|
-
# Renderer.extract_view_mapping(Asciidoctor::HTML5::DocumentTemplate)
|
192
|
-
# # => ['document', 'html5']
|
193
|
-
#
|
194
|
-
# Renderer.extract_view_mapping(Asciidoctor::DocBook45::BlockSidebarTemplate)
|
195
|
-
# # => ['block_sidebar', 'docbook45']
|
196
|
-
#
|
197
|
-
# Returns A two-element String Array mapped as [view_name, backend], where backend may be nil
|
198
|
-
def self.extract_view_mapping(qualified_class)
|
199
|
-
view_name, backend = qualified_class.to_s.
|
200
|
-
sub(RE_ASCIIDOCTOR_NAMESPACE, '').
|
201
|
-
sub(RE_TEMPLATE_CLASS_SUFFIX, '').
|
202
|
-
split('::').reverse
|
203
|
-
view_name = camelcase_to_underscore(view_name)
|
204
|
-
backend = backend.downcase unless backend.nil?
|
205
|
-
[view_name, backend]
|
206
|
-
end
|
207
|
-
|
208
|
-
# Internal: Convert a CamelCase word to an underscore-delimited word
|
209
|
-
#
|
210
|
-
# Examples
|
211
|
-
#
|
212
|
-
# Renderer.camelcase_to_underscore('BlockSidebar')
|
213
|
-
# # => 'block_sidebar'
|
214
|
-
#
|
215
|
-
# Renderer.camelcase_to_underscore('BlockUlist')
|
216
|
-
# # => 'block_ulist'
|
217
|
-
#
|
218
|
-
# Returns the String converted from CamelCase to underscore-delimited
|
219
|
-
def self.camelcase_to_underscore(str)
|
220
|
-
str.gsub(RE_CAMELCASE_BOUNDARY_1, '\1_\2').
|
221
|
-
gsub(RE_CAMELCASE_BOUNDARY_2, '\1_\2').downcase
|
222
|
-
end
|
223
|
-
|
224
|
-
end
|
225
|
-
|
226
|
-
class TemplateCache
|
227
|
-
attr_reader :cache
|
228
|
-
|
229
|
-
def initialize
|
230
|
-
@cache = {}
|
231
|
-
end
|
232
|
-
|
233
|
-
# check if a key is available in the cache
|
234
|
-
def cached? *key
|
235
|
-
@cache.has_key? key
|
236
|
-
end
|
237
|
-
|
238
|
-
# retrieves an item from the cache stored in the cache key
|
239
|
-
# if a block is given, the block is called and the return
|
240
|
-
# value stored in the cache under the specified key
|
241
|
-
def fetch(*key)
|
242
|
-
if block_given?
|
243
|
-
@cache[key] ||= yield
|
244
|
-
else
|
245
|
-
@cache[key]
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
# stores an item in the cache under the specified key
|
250
|
-
def store(value, *key)
|
251
|
-
@cache[key] = value
|
252
|
-
end
|
253
|
-
|
254
|
-
# Clears the cache
|
255
|
-
def clear
|
256
|
-
@cache = {}
|
257
|
-
end
|
258
|
-
end
|
259
|
-
end
|
@@ -1,1083 +0,0 @@
|
|
1
|
-
module Asciidoctor
|
2
|
-
# Public: Methods to perform substitutions on lines of AsciiDoc text. This module
|
3
|
-
# is intented to be mixed-in to Section and Block to provide operations for performing
|
4
|
-
# the necessary substitutions.
|
5
|
-
module Substituters
|
6
|
-
|
7
|
-
SUBS = {
|
8
|
-
:basic => [:specialcharacters],
|
9
|
-
:normal => [:specialcharacters, :quotes, :attributes, :replacements, :macros, :post_replacements],
|
10
|
-
:verbatim => [:specialcharacters, :callouts],
|
11
|
-
:title => [:specialcharacters, :quotes, :replacements, :macros, :attributes, :post_replacements],
|
12
|
-
:header => [:specialcharacters, :attributes],
|
13
|
-
:pass => [:attributes, :macros]
|
14
|
-
}
|
15
|
-
|
16
|
-
COMPOSITE_SUBS = {
|
17
|
-
:none => [],
|
18
|
-
:normal => SUBS[:normal],
|
19
|
-
:verbatim => SUBS[:verbatim]
|
20
|
-
}
|
21
|
-
|
22
|
-
SUB_OPTIONS = {
|
23
|
-
:block => COMPOSITE_SUBS.keys + SUBS[:normal] + [:callouts],
|
24
|
-
:inline => COMPOSITE_SUBS.keys + SUBS[:normal]
|
25
|
-
}
|
26
|
-
|
27
|
-
# Internal: A String Array of passthough (unprocessed) text captured from this block
|
28
|
-
attr_reader :passthroughs
|
29
|
-
|
30
|
-
# Public: Apply the specified substitutions to the lines of text
|
31
|
-
#
|
32
|
-
# source - The String or String Array of text to process
|
33
|
-
# subs - The substitutions to perform. Can be a Symbol or a Symbol Array (default: :normal)
|
34
|
-
# expand - A Boolean to control whether sub aliases are expanded (default: true)
|
35
|
-
#
|
36
|
-
# returns Either a String or String Array, whichever matches the type of the first argument
|
37
|
-
def apply_subs source, subs = :normal, expand = false
|
38
|
-
if subs == :normal
|
39
|
-
subs = SUBS[:normal]
|
40
|
-
elsif subs.nil?
|
41
|
-
return source
|
42
|
-
elsif expand
|
43
|
-
if subs.is_a? Symbol
|
44
|
-
subs = COMPOSITE_SUBS[subs] || [subs]
|
45
|
-
else
|
46
|
-
effective_subs = []
|
47
|
-
subs.each do |key|
|
48
|
-
if COMPOSITE_SUBS.has_key? key
|
49
|
-
effective_subs.push(*COMPOSITE_SUBS[key])
|
50
|
-
else
|
51
|
-
effective_subs << key
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
subs = effective_subs
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
return source if subs.empty?
|
60
|
-
|
61
|
-
multiline = source.is_a?(Array)
|
62
|
-
text = multiline ? source.join : source
|
63
|
-
|
64
|
-
if (has_passthroughs = subs.include?(:macros))
|
65
|
-
text = extract_passthroughs(text)
|
66
|
-
end
|
67
|
-
|
68
|
-
subs.each do |type|
|
69
|
-
case type
|
70
|
-
when :specialcharacters
|
71
|
-
text = sub_specialcharacters(text)
|
72
|
-
when :quotes
|
73
|
-
text = sub_quotes(text)
|
74
|
-
when :attributes
|
75
|
-
text = sub_attributes(text.lines.entries).join
|
76
|
-
when :replacements
|
77
|
-
text = sub_replacements(text)
|
78
|
-
when :macros
|
79
|
-
text = sub_macros(text)
|
80
|
-
when :highlight
|
81
|
-
text = highlight_source(text, subs.include?(:callouts))
|
82
|
-
when :callouts
|
83
|
-
text = sub_callouts(text) unless subs.include?(:highlight)
|
84
|
-
when :post_replacements
|
85
|
-
text = sub_post_replacements(text)
|
86
|
-
else
|
87
|
-
warn "asciidoctor: WARNING: unknown substitution type #{type}"
|
88
|
-
end
|
89
|
-
end
|
90
|
-
text = restore_passthroughs(text) if has_passthroughs
|
91
|
-
|
92
|
-
multiline ? text.lines.entries : text
|
93
|
-
end
|
94
|
-
|
95
|
-
# Public: Apply normal substitutions.
|
96
|
-
#
|
97
|
-
# lines - The lines of text to process. Can be a String or a String Array
|
98
|
-
#
|
99
|
-
# returns - A String with normal substitutions performed
|
100
|
-
def apply_normal_subs(lines)
|
101
|
-
apply_subs lines.is_a?(Array) ? lines.join : lines
|
102
|
-
end
|
103
|
-
|
104
|
-
# Public: Apply substitutions for titles.
|
105
|
-
#
|
106
|
-
# title - The String title to process
|
107
|
-
#
|
108
|
-
# returns - A String with title substitutions performed
|
109
|
-
def apply_title_subs(title)
|
110
|
-
apply_subs title, SUBS[:title]
|
111
|
-
end
|
112
|
-
|
113
|
-
# Public: Apply substitutions for header metadata and attribute assignments
|
114
|
-
#
|
115
|
-
# text - String containing the text process
|
116
|
-
#
|
117
|
-
# returns - A String with header substitutions performed
|
118
|
-
def apply_header_subs(text)
|
119
|
-
apply_subs text, SUBS[:header]
|
120
|
-
end
|
121
|
-
|
122
|
-
=begin
|
123
|
-
# Public: Apply explicit substitutions, if specified, otherwise normal substitutions.
|
124
|
-
#
|
125
|
-
# lines - The lines of text to process. Can be a String or a String Array
|
126
|
-
#
|
127
|
-
# returns - A String with substitutions applied
|
128
|
-
def apply_para_subs(lines)
|
129
|
-
if (subs = attr('subs', nil, false))
|
130
|
-
apply_subs lines.join, resolve_subs(subs)
|
131
|
-
else
|
132
|
-
apply_subs lines.join
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
# Public: Apply substitutions for titles
|
137
|
-
#
|
138
|
-
# lines - A String Array containing the lines of text process
|
139
|
-
#
|
140
|
-
# returns - A String with literal (verbatim) substitutions performed
|
141
|
-
def apply_literal_subs(lines)
|
142
|
-
if (subs = attr('subs', nil, false))
|
143
|
-
apply_subs lines.join, resolve_subs(subs)
|
144
|
-
elsif @style == 'source' && @document.attributes['basebackend'] == 'html' &&
|
145
|
-
((highlighter = @document.attributes['source-highlighter']) == 'coderay' ||
|
146
|
-
highlighter == 'pygments') && attr?('language')
|
147
|
-
highlight_source lines.join, highlighter, callouts
|
148
|
-
else
|
149
|
-
apply_subs lines.join, SUBS[:verbatim]
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
# Public: Apply substitutions for passthrough text
|
154
|
-
#
|
155
|
-
# lines - A String Array containing the lines of text process
|
156
|
-
#
|
157
|
-
# returns - A String with passthrough substitutions performed
|
158
|
-
def apply_passthrough_subs(lines)
|
159
|
-
if (subs = attr('subs', nil, false))
|
160
|
-
subs = resolve_subs(subs)
|
161
|
-
else
|
162
|
-
subs = SUBS[:pass]
|
163
|
-
end
|
164
|
-
apply_subs lines.join, subs
|
165
|
-
end
|
166
|
-
=end
|
167
|
-
|
168
|
-
# Internal: Extract the passthrough text from the document for reinsertion after processing.
|
169
|
-
#
|
170
|
-
# text - The String from which to extract passthrough fragements
|
171
|
-
#
|
172
|
-
# returns - The text with the passthrough region substituted with placeholders
|
173
|
-
def extract_passthroughs(text)
|
174
|
-
result = text.dup
|
175
|
-
|
176
|
-
result.gsub!(REGEXP[:pass_macro]) {
|
177
|
-
# alias match for Ruby 1.8.7 compat
|
178
|
-
m = $~
|
179
|
-
# honor the escape
|
180
|
-
if m[0].start_with? '\\'
|
181
|
-
next m[0][1..-1]
|
182
|
-
end
|
183
|
-
|
184
|
-
if m[1] == '$$'
|
185
|
-
subs = [:specialcharacters]
|
186
|
-
elsif m[3].nil? || m[3].empty?
|
187
|
-
subs = []
|
188
|
-
else
|
189
|
-
subs = resolve_pass_subs m[3]
|
190
|
-
end
|
191
|
-
|
192
|
-
# TODO move unescaping closing square bracket to an operation
|
193
|
-
@passthroughs << {:text => m[2] || m[4].gsub('\]', ']'), :subs => subs}
|
194
|
-
index = @passthroughs.size - 1
|
195
|
-
"\e#{index}\e"
|
196
|
-
} unless !(result.include?('+++') || result.include?('$$') || result.include?('pass:'))
|
197
|
-
|
198
|
-
result.gsub!(REGEXP[:pass_lit]) {
|
199
|
-
# alias match for Ruby 1.8.7 compat
|
200
|
-
m = $~
|
201
|
-
|
202
|
-
unescaped_attrs = nil
|
203
|
-
# honor the escape
|
204
|
-
if m[3].start_with? '\\'
|
205
|
-
next m[2].nil? ? "#{m[1]}#{m[3][1..-1]}" : "#{m[1]}[#{m[2]}]#{m[3][1..-1]}"
|
206
|
-
elsif m[1] == '\\' && !m[2].nil?
|
207
|
-
unescaped_attrs = "[#{m[2]}]"
|
208
|
-
end
|
209
|
-
|
210
|
-
if unescaped_attrs.nil? && !m[2].nil?
|
211
|
-
attributes = parse_attributes(m[2])
|
212
|
-
else
|
213
|
-
attributes = {}
|
214
|
-
end
|
215
|
-
|
216
|
-
@passthroughs << {:text => m[4], :subs => [:specialcharacters], :attributes => attributes, :literal => true}
|
217
|
-
index = @passthroughs.size - 1
|
218
|
-
"#{unescaped_attrs || m[1]}\e#{index}\e"
|
219
|
-
} unless !result.include?('`')
|
220
|
-
|
221
|
-
result
|
222
|
-
end
|
223
|
-
|
224
|
-
# Internal: Restore the passthrough text by reinserting into the placeholder positions
|
225
|
-
#
|
226
|
-
# text - The String text into which to restore the passthrough text
|
227
|
-
#
|
228
|
-
# returns The String text with the passthrough text restored
|
229
|
-
def restore_passthroughs(text)
|
230
|
-
return text if @passthroughs.nil? || @passthroughs.empty? || !text.include?("\e")
|
231
|
-
|
232
|
-
text.gsub(REGEXP[:pass_placeholder]) {
|
233
|
-
pass = @passthroughs[$1.to_i];
|
234
|
-
text = apply_subs(pass[:text], pass.fetch(:subs, []))
|
235
|
-
pass[:literal] ? Inline.new(self, :quoted, text, :type => :monospaced, :attributes => pass.fetch(:attributes, {})).render : text
|
236
|
-
}
|
237
|
-
end
|
238
|
-
|
239
|
-
# Public: Substitute special characters (i.e., encode XML)
|
240
|
-
#
|
241
|
-
# Special characters are defined in the Asciidoctor::SPECIAL_CHARS Array constant
|
242
|
-
#
|
243
|
-
# text - The String text to process
|
244
|
-
#
|
245
|
-
# returns The String text with special characters replaced
|
246
|
-
def sub_specialcharacters(text)
|
247
|
-
# this syntax only available in Ruby 1.9
|
248
|
-
#text.gsub(SPECIAL_CHARS_PATTERN, SPECIAL_CHARS)
|
249
|
-
|
250
|
-
text.gsub(SPECIAL_CHARS_PATTERN) { SPECIAL_CHARS[$&] }
|
251
|
-
end
|
252
|
-
alias :sub_specialchars :sub_specialcharacters
|
253
|
-
|
254
|
-
# Public: Substitute quoted text (includes emphasis, strong, monospaced, etc)
|
255
|
-
#
|
256
|
-
# text - The String text to process
|
257
|
-
#
|
258
|
-
# returns The String text with quoted text rendered using the backend templates
|
259
|
-
def sub_quotes(text)
|
260
|
-
result = text.dup
|
261
|
-
|
262
|
-
QUOTE_SUBS.each {|type, scope, pattern|
|
263
|
-
result.gsub!(pattern) { transform_quoted_text($~, type, scope) }
|
264
|
-
}
|
265
|
-
|
266
|
-
result
|
267
|
-
end
|
268
|
-
|
269
|
-
# Public: Substitute replacement characters (e.g., copyright, trademark, etc)
|
270
|
-
#
|
271
|
-
# text - The String text to process
|
272
|
-
#
|
273
|
-
# returns The String text with the replacement characters substituted
|
274
|
-
def sub_replacements(text)
|
275
|
-
result = text.dup
|
276
|
-
|
277
|
-
REPLACEMENTS.each {|pattern, replacement, restore|
|
278
|
-
result.gsub!(pattern) {
|
279
|
-
matched = $&
|
280
|
-
head = $1
|
281
|
-
tail = $2
|
282
|
-
if matched.include?('\\')
|
283
|
-
matched.tr('\\', '')
|
284
|
-
else
|
285
|
-
case restore
|
286
|
-
when :none
|
287
|
-
replacement
|
288
|
-
when :leading
|
289
|
-
"#{head}#{replacement}"
|
290
|
-
when :bounding
|
291
|
-
"#{head}#{replacement}#{tail}"
|
292
|
-
end
|
293
|
-
end
|
294
|
-
}
|
295
|
-
}
|
296
|
-
|
297
|
-
result
|
298
|
-
end
|
299
|
-
|
300
|
-
# Public: Substitute attribute references
|
301
|
-
#
|
302
|
-
# Attribute references are in the format {name}.
|
303
|
-
#
|
304
|
-
# If an attribute referenced in the line is missing, the line is dropped.
|
305
|
-
#
|
306
|
-
# text - The String text to process
|
307
|
-
#
|
308
|
-
# returns The String text with the attribute references replaced with attribute values
|
309
|
-
#--
|
310
|
-
# NOTE it's necessary to perform this substitution line-by-line
|
311
|
-
# so that a missing key doesn't wipe out the whole block of data
|
312
|
-
def sub_attributes(data, opts = {})
|
313
|
-
return data if data.nil? || data.empty?
|
314
|
-
|
315
|
-
string_data = data.is_a? String
|
316
|
-
# normalizes data type to an array (string becomes single-element array)
|
317
|
-
lines = string_data ? [data] : data
|
318
|
-
|
319
|
-
result = []
|
320
|
-
lines.each {|line|
|
321
|
-
reject = false
|
322
|
-
line = line.gsub(REGEXP[:attr_ref]) {
|
323
|
-
# alias match for Ruby 1.8.7 compat
|
324
|
-
m = $~
|
325
|
-
# escaped attribute, return unescaped
|
326
|
-
if !m[1].nil? || !m[4].nil?
|
327
|
-
"{#{m[2]}}"
|
328
|
-
elsif (directive = m[3])
|
329
|
-
offset = directive.length + 1
|
330
|
-
expr = m[2][offset..-1]
|
331
|
-
case directive
|
332
|
-
when 'set'
|
333
|
-
args = expr.split(':')
|
334
|
-
_, value = Lexer::store_attribute(args[0], args[1] || '', @document)
|
335
|
-
if value.nil?
|
336
|
-
# since this is an assignment, only drop-line applies here (skip and drop imply the same result)
|
337
|
-
if @document.attributes.fetch('attribute-undefined', COMPLIANCE[:attribute_undefined]) == 'drop-line'
|
338
|
-
Debug.debug { "Undefining attribute: #{key}, line marked for removal" }
|
339
|
-
break ''
|
340
|
-
end
|
341
|
-
end
|
342
|
-
''
|
343
|
-
when 'counter', 'counter2'
|
344
|
-
args = expr.split(':')
|
345
|
-
val = @document.counter(args[0], args[1])
|
346
|
-
directive == 'counter2' ? '' : val
|
347
|
-
else
|
348
|
-
# if we get here, our attr_ref regex is too loose
|
349
|
-
warn "asciidoctor: WARNING: illegal attribute directive: #{m[2]}"
|
350
|
-
''
|
351
|
-
end
|
352
|
-
elsif (key = m[2].downcase) && @document.attributes.has_key?(key)
|
353
|
-
@document.attributes[key]
|
354
|
-
elsif INTRINSICS.has_key? key
|
355
|
-
INTRINSICS[key]
|
356
|
-
else
|
357
|
-
case (opts[:attribute_missing] || @document.attributes.fetch('attribute-missing', COMPLIANCE[:attribute_missing]))
|
358
|
-
when 'skip'
|
359
|
-
"{#{key}}"
|
360
|
-
when 'drop-line'
|
361
|
-
Debug.debug { "Missing attribute: #{key}, line marked for removal" }
|
362
|
-
break ''
|
363
|
-
else # 'drop'
|
364
|
-
''
|
365
|
-
end
|
366
|
-
end
|
367
|
-
} if line.include? '{'
|
368
|
-
|
369
|
-
result << line unless reject
|
370
|
-
}
|
371
|
-
|
372
|
-
string_data ? result.join : result
|
373
|
-
end
|
374
|
-
|
375
|
-
# Public: Substitute inline macros (e.g., links, images, etc)
|
376
|
-
#
|
377
|
-
# Replace inline macros, which may span multiple lines, in the provided text
|
378
|
-
#
|
379
|
-
# text - The String text to process
|
380
|
-
#
|
381
|
-
# returns The String with the inline macros rendered using the backend templates
|
382
|
-
def sub_macros(text)
|
383
|
-
return text if text.nil? || text.empty?
|
384
|
-
|
385
|
-
result = text.dup
|
386
|
-
|
387
|
-
# some look ahead assertions to cut unnecessary regex calls
|
388
|
-
found = {}
|
389
|
-
found[:square_bracket] = result.include?('[')
|
390
|
-
found[:round_bracket] = result.include?('(')
|
391
|
-
found[:colon] = result.include?(':')
|
392
|
-
found[:at] = result.include?('@')
|
393
|
-
found[:macroish] = (found[:square_bracket] && found[:colon])
|
394
|
-
found[:macroish_short_form] = (found[:square_bracket] && found[:colon] && result.include?(':['))
|
395
|
-
found[:uri] = (found[:colon] && result.include?('://'))
|
396
|
-
use_link_attrs = @document.attributes.has_key?('linkattrs')
|
397
|
-
experimental = @document.attributes.has_key?('experimental')
|
398
|
-
|
399
|
-
if experimental
|
400
|
-
if found[:macroish_short_form] && (result.include?('kbd:') || result.include?('btn:'))
|
401
|
-
result.gsub!(REGEXP[:kbd_btn_macro]) {
|
402
|
-
# alias match for Ruby 1.8.7 compat
|
403
|
-
m = $~
|
404
|
-
# honor the escape
|
405
|
-
if (captured = m[0]).start_with? '\\'
|
406
|
-
next captured[1..-1]
|
407
|
-
end
|
408
|
-
|
409
|
-
if captured.start_with?('kbd')
|
410
|
-
keys = unescape_bracketed_text m[1]
|
411
|
-
|
412
|
-
if keys == '+'
|
413
|
-
keys = ['+']
|
414
|
-
else
|
415
|
-
# need to use closure to work around lack of negative lookbehind
|
416
|
-
keys = keys.split(REGEXP[:kbd_delim]).inject([]) {|c, key|
|
417
|
-
if key.end_with?('++')
|
418
|
-
c << key[0..-3].strip
|
419
|
-
c << '+'
|
420
|
-
else
|
421
|
-
c << key.strip
|
422
|
-
end
|
423
|
-
c
|
424
|
-
}
|
425
|
-
end
|
426
|
-
Inline.new(self, :kbd, nil, :attributes => {'keys' => keys}).render
|
427
|
-
elsif captured.start_with?('btn')
|
428
|
-
label = unescape_bracketed_text m[1]
|
429
|
-
Inline.new(self, :button, label).render
|
430
|
-
end
|
431
|
-
}
|
432
|
-
end
|
433
|
-
|
434
|
-
if found[:macroish] && result.include?('menu:')
|
435
|
-
result.gsub!(REGEXP[:menu_macro]) {
|
436
|
-
# alias match for Ruby 1.8.7 compat
|
437
|
-
m = $~
|
438
|
-
# honor the escape
|
439
|
-
if (captured = m[0]).start_with? '\\'
|
440
|
-
next captured[1..-1]
|
441
|
-
end
|
442
|
-
|
443
|
-
menu = m[1]
|
444
|
-
items = m[2]
|
445
|
-
|
446
|
-
if items.nil?
|
447
|
-
submenus = []
|
448
|
-
menuitem = nil
|
449
|
-
else
|
450
|
-
if (delim = items.include?('>') ? '>' : (items.include?(',') ? ',' : nil))
|
451
|
-
submenus = items.split(delim).map(&:strip)
|
452
|
-
menuitem = submenus.pop
|
453
|
-
else
|
454
|
-
submenus = []
|
455
|
-
menuitem = items.rstrip
|
456
|
-
end
|
457
|
-
end
|
458
|
-
|
459
|
-
Inline.new(self, :menu, nil, :attributes => {'menu' => menu, 'submenus' => submenus, 'menuitem' => menuitem}).render
|
460
|
-
}
|
461
|
-
end
|
462
|
-
|
463
|
-
if result.include?('"') && result.include?('>')
|
464
|
-
result.gsub!(REGEXP[:menu_inline_macro]) {
|
465
|
-
# alias match for Ruby 1.8.7 compat
|
466
|
-
m = $~
|
467
|
-
# honor the escape
|
468
|
-
if (captured = m[0]).start_with? '\\'
|
469
|
-
next captured[1..-1]
|
470
|
-
end
|
471
|
-
|
472
|
-
input = m[1]
|
473
|
-
|
474
|
-
menu, *submenus = input.split('>').map(&:strip)
|
475
|
-
menuitem = submenus.pop
|
476
|
-
Inline.new(self, :menu, nil, :attributes => {'menu' => menu, 'submenus' => submenus, 'menuitem' => menuitem}).render
|
477
|
-
}
|
478
|
-
end
|
479
|
-
end
|
480
|
-
|
481
|
-
# FIXME this location is somewhat arbitrary, probably need to be able to control ordering
|
482
|
-
# TODO this handling needs some cleanup
|
483
|
-
if (extensions = @document.extensions) && extensions.inline_macros? && found[:macroish]
|
484
|
-
extensions.load_inline_macro_processors(@document).each do |processor|
|
485
|
-
result.gsub!(processor.regexp) {
|
486
|
-
# alias match for Ruby 1.8.7 compat
|
487
|
-
m = $~
|
488
|
-
# honor the escape
|
489
|
-
if m[0].start_with? '\\'
|
490
|
-
next m[0][1..-1]
|
491
|
-
end
|
492
|
-
|
493
|
-
target = m[1]
|
494
|
-
if processor.options[:short_form]
|
495
|
-
attributes = {}
|
496
|
-
else
|
497
|
-
posattrs = processor.options.fetch(:pos_attrs, [])
|
498
|
-
attributes = parse_attributes(m[2], posattrs, :sub_input => true, :unescape_input => true)
|
499
|
-
end
|
500
|
-
processor.process self, target, attributes
|
501
|
-
}
|
502
|
-
end
|
503
|
-
end
|
504
|
-
|
505
|
-
if found[:macroish] && (result.include?('image:') || result.include?('icon:'))
|
506
|
-
# image:filename.png[Alt Text]
|
507
|
-
result.gsub!(REGEXP[:image_macro]) {
|
508
|
-
# alias match for Ruby 1.8.7 compat
|
509
|
-
m = $~
|
510
|
-
# honor the escape
|
511
|
-
if m[0].start_with? '\\'
|
512
|
-
next m[0][1..-1]
|
513
|
-
end
|
514
|
-
|
515
|
-
raw_attrs = unescape_bracketed_text m[2]
|
516
|
-
if m[0].start_with? 'icon:'
|
517
|
-
type = 'icon'
|
518
|
-
posattrs = ['size']
|
519
|
-
else
|
520
|
-
type = 'image'
|
521
|
-
posattrs = ['alt', 'width', 'height']
|
522
|
-
end
|
523
|
-
target = sub_attributes(m[1])
|
524
|
-
unless type == 'icon'
|
525
|
-
@document.register(:images, target)
|
526
|
-
end
|
527
|
-
attrs = parse_attributes(raw_attrs, posattrs)
|
528
|
-
if !attrs['alt']
|
529
|
-
attrs['alt'] = File.basename(target, File.extname(target))
|
530
|
-
end
|
531
|
-
Inline.new(self, :image, nil, :type => type, :target => target, :attributes => attrs).render
|
532
|
-
}
|
533
|
-
end
|
534
|
-
|
535
|
-
if found[:macroish_short_form] || found[:round_bracket]
|
536
|
-
# indexterm:[Tigers,Big cats]
|
537
|
-
# (((Tigers,Big cats)))
|
538
|
-
result.gsub!(REGEXP[:indexterm_macro]) {
|
539
|
-
# alias match for Ruby 1.8.7 compat
|
540
|
-
m = $~
|
541
|
-
# honor the escape
|
542
|
-
if m[0].start_with? '\\'
|
543
|
-
next m[0][1..-1]
|
544
|
-
end
|
545
|
-
|
546
|
-
terms = unescape_bracketed_text(m[1] || m[2]).split(',').map(&:strip)
|
547
|
-
@document.register(:indexterms, [*terms])
|
548
|
-
Inline.new(self, :indexterm, text, :attributes => {'terms' => terms}).render
|
549
|
-
}
|
550
|
-
|
551
|
-
# indexterm2:[Tigers]
|
552
|
-
# ((Tigers))
|
553
|
-
result.gsub!(REGEXP[:indexterm2_macro]) {
|
554
|
-
# alias match for Ruby 1.8.7 compat
|
555
|
-
m = $~
|
556
|
-
# honor the escape
|
557
|
-
if m[0].start_with? '\\'
|
558
|
-
next m[0][1..-1]
|
559
|
-
end
|
560
|
-
|
561
|
-
text = unescape_bracketed_text(m[1] || m[2])
|
562
|
-
@document.register(:indexterms, [text])
|
563
|
-
Inline.new(self, :indexterm, text, :type => :visible).render
|
564
|
-
}
|
565
|
-
end
|
566
|
-
|
567
|
-
if found[:uri]
|
568
|
-
# inline urls, target[text] (optionally prefixed with link: and optionally surrounded by <>)
|
569
|
-
result.gsub!(REGEXP[:link_inline]) {
|
570
|
-
# alias match for Ruby 1.8.7 compat
|
571
|
-
m = $~
|
572
|
-
# honor the escape
|
573
|
-
if m[2].start_with? '\\'
|
574
|
-
next "#{m[1]}#{m[2][1..-1]}#{m[3]}"
|
575
|
-
# not a valid macro syntax w/o trailing square brackets
|
576
|
-
# we probably shouldn't even get here...our regex is doing too much
|
577
|
-
elsif m[1] == 'link:' && m[3].nil?
|
578
|
-
next m[0]
|
579
|
-
end
|
580
|
-
prefix = (m[1] != 'link:' ? m[1] : '')
|
581
|
-
target = m[2]
|
582
|
-
suffix = ''
|
583
|
-
# strip the <> around the link
|
584
|
-
if prefix.start_with?('<') && target.end_with?('>')
|
585
|
-
prefix = prefix[4..-1]
|
586
|
-
target = target[0..-5]
|
587
|
-
elsif prefix.start_with?('(') && target.end_with?(')')
|
588
|
-
target = target[0..-2]
|
589
|
-
suffix = ')'
|
590
|
-
elsif target.end_with?('):')
|
591
|
-
target = target[0..-3]
|
592
|
-
suffix = '):'
|
593
|
-
end
|
594
|
-
@document.register(:links, target)
|
595
|
-
|
596
|
-
attrs = nil
|
597
|
-
#text = !m[3].nil? ? sub_attributes(m[3].gsub('\]', ']')) : ''
|
598
|
-
if !m[3].to_s.empty?
|
599
|
-
if use_link_attrs && (m[3].start_with?('"') || m[3].include?(','))
|
600
|
-
attrs = parse_attributes(sub_attributes(m[3].gsub('\]', ']')), [])
|
601
|
-
text = attrs[1]
|
602
|
-
else
|
603
|
-
text = sub_attributes(m[3].gsub('\]', ']'))
|
604
|
-
end
|
605
|
-
|
606
|
-
if text.end_with? '^'
|
607
|
-
text = text.chop
|
608
|
-
attrs ||= {}
|
609
|
-
attrs['window'] = '_blank' unless attrs.has_key?('window')
|
610
|
-
end
|
611
|
-
else
|
612
|
-
text = ''
|
613
|
-
end
|
614
|
-
|
615
|
-
"#{prefix}#{Inline.new(self, :anchor, (!text.empty? ? text : target), :type => :link, :target => target, :attributes => attrs).render}#{suffix}"
|
616
|
-
}
|
617
|
-
end
|
618
|
-
|
619
|
-
if found[:macroish] && (result.include?('link:') || result.include?('mailto:'))
|
620
|
-
# inline link macros, link:target[text]
|
621
|
-
result.gsub!(REGEXP[:link_macro]) {
|
622
|
-
# alias match for Ruby 1.8.7 compat
|
623
|
-
m = $~
|
624
|
-
# honor the escape
|
625
|
-
if m[0].start_with? '\\'
|
626
|
-
next m[0][1..-1]
|
627
|
-
end
|
628
|
-
raw_target = m[1]
|
629
|
-
mailto = m[0].start_with?('mailto:')
|
630
|
-
target = mailto ? "mailto:#{raw_target}" : raw_target
|
631
|
-
|
632
|
-
attrs = nil
|
633
|
-
#text = sub_attributes(m[2].gsub('\]', ']'))
|
634
|
-
if use_link_attrs && (m[2].start_with?('"') || m[2].include?(','))
|
635
|
-
attrs = parse_attributes(sub_attributes(m[2].gsub('\]', ']')), [])
|
636
|
-
text = attrs[1]
|
637
|
-
if mailto
|
638
|
-
if attrs.has_key? 2
|
639
|
-
target = "#{target}?subject=#{Helpers.encode_uri(attrs[2])}"
|
640
|
-
|
641
|
-
if attrs.has_key? 3
|
642
|
-
target = "#{target}&body=#{Helpers.encode_uri(attrs[3])}"
|
643
|
-
end
|
644
|
-
end
|
645
|
-
end
|
646
|
-
else
|
647
|
-
text = sub_attributes(m[2].gsub('\]', ']'))
|
648
|
-
end
|
649
|
-
|
650
|
-
if text.end_with? '^'
|
651
|
-
text = text.chop
|
652
|
-
attrs ||= {}
|
653
|
-
attrs['window'] = '_blank' unless attrs.has_key?('window')
|
654
|
-
end
|
655
|
-
|
656
|
-
# QUESTION should a mailto be registered as an e-mail address?
|
657
|
-
@document.register(:links, target)
|
658
|
-
|
659
|
-
Inline.new(self, :anchor, (!text.empty? ? text : raw_target), :type => :link, :target => target, :attributes => attrs).render
|
660
|
-
}
|
661
|
-
end
|
662
|
-
|
663
|
-
if found[:at]
|
664
|
-
result.gsub!(REGEXP[:email_inline]) {
|
665
|
-
# alias match for Ruby 1.8.7 compat
|
666
|
-
m = $~
|
667
|
-
address = m[0]
|
668
|
-
case address[0..0]
|
669
|
-
when '\\'
|
670
|
-
next address[1..-1]
|
671
|
-
when '>', ':'
|
672
|
-
next address
|
673
|
-
end
|
674
|
-
|
675
|
-
target = "mailto:#{address}"
|
676
|
-
# QUESTION should this be registered as an e-mail address?
|
677
|
-
@document.register(:links, target)
|
678
|
-
|
679
|
-
Inline.new(self, :anchor, address, :type => :link, :target => target).render
|
680
|
-
}
|
681
|
-
end
|
682
|
-
|
683
|
-
if found[:macroish_short_form] && result.include?('footnote')
|
684
|
-
result.gsub!(REGEXP[:footnote_macro]) {
|
685
|
-
# alias match for Ruby 1.8.7 compat
|
686
|
-
m = $~
|
687
|
-
# honor the escape
|
688
|
-
if m[0].start_with? '\\'
|
689
|
-
next m[0][1..-1]
|
690
|
-
end
|
691
|
-
if m[1] == 'footnote'
|
692
|
-
# hmmmm
|
693
|
-
text = restore_passthroughs(m[2])
|
694
|
-
id = nil
|
695
|
-
index = @document.counter('footnote-number')
|
696
|
-
@document.register(:footnotes, Document::Footnote.new(index, id, text))
|
697
|
-
type = nil
|
698
|
-
target = nil
|
699
|
-
else
|
700
|
-
id, text = m[2].split(',', 2).map(&:strip)
|
701
|
-
if !text.nil?
|
702
|
-
# hmmmm
|
703
|
-
text = restore_passthroughs(text)
|
704
|
-
index = @document.counter('footnote-number')
|
705
|
-
@document.register(:footnotes, Document::Footnote.new(index, id, text))
|
706
|
-
type = :ref
|
707
|
-
target = nil
|
708
|
-
else
|
709
|
-
footnote = @document.references[:footnotes].find {|fn| fn.id == id }
|
710
|
-
target = id
|
711
|
-
id = nil
|
712
|
-
index = footnote.index
|
713
|
-
text = footnote.text
|
714
|
-
type = :xref
|
715
|
-
end
|
716
|
-
end
|
717
|
-
Inline.new(self, :footnote, text, :attributes => {'index' => index}, :id => id, :target => target, :type => type).render
|
718
|
-
}
|
719
|
-
end
|
720
|
-
|
721
|
-
if found[:macroish] || result.include?('<<')
|
722
|
-
result.gsub!(REGEXP[:xref_macro]) {
|
723
|
-
# alias match for Ruby 1.8.7 compat
|
724
|
-
m = $~
|
725
|
-
# honor the escape
|
726
|
-
if m[0].start_with? '\\'
|
727
|
-
next m[0][1..-1]
|
728
|
-
end
|
729
|
-
if !m[1].nil?
|
730
|
-
id, reftext = m[1].split(',', 2).map(&:strip)
|
731
|
-
id.sub!(REGEXP[:dbl_quoted], '\2')
|
732
|
-
reftext.sub!(REGEXP[:m_dbl_quoted], '\2') unless reftext.nil?
|
733
|
-
else
|
734
|
-
id = m[2]
|
735
|
-
reftext = !m[3].empty? ? m[3] : nil
|
736
|
-
end
|
737
|
-
|
738
|
-
if id.include? '#'
|
739
|
-
path, fragment = id.split('#')
|
740
|
-
else
|
741
|
-
path = nil
|
742
|
-
fragment = id
|
743
|
-
end
|
744
|
-
|
745
|
-
# handles form: id
|
746
|
-
if path.nil?
|
747
|
-
refid = fragment
|
748
|
-
target = "##{fragment}"
|
749
|
-
# handles forms: doc#, doc.adoc#, doc#id and doc.adoc#id
|
750
|
-
else
|
751
|
-
path = Helpers.rootname(path)
|
752
|
-
# the referenced path is this document, or its contents has been included in this document
|
753
|
-
if @document.attr?('docname', path) || @document.references[:includes].include?(path)
|
754
|
-
refid = fragment
|
755
|
-
path = nil
|
756
|
-
target = "##{fragment}"
|
757
|
-
else
|
758
|
-
refid = fragment.nil? ? path : "#{path}##{fragment}"
|
759
|
-
path = "#{path}#{@document.attr 'outfilesuffix', '.html'}"
|
760
|
-
target = fragment.nil? ? path : "#{path}##{fragment}"
|
761
|
-
end
|
762
|
-
end
|
763
|
-
Inline.new(self, :anchor, reftext, :type => :xref, :target => target, :attributes => {'path' => path, 'fragment' => fragment, 'refid' => refid}).render
|
764
|
-
}
|
765
|
-
end
|
766
|
-
|
767
|
-
if found[:square_bracket] && result.include?('[[[')
|
768
|
-
result.gsub!(REGEXP[:biblio_macro]) {
|
769
|
-
# alias match for Ruby 1.8.7 compat
|
770
|
-
m = $~
|
771
|
-
# honor the escape
|
772
|
-
if m[0].start_with? '\\'
|
773
|
-
next m[0][1..-1]
|
774
|
-
end
|
775
|
-
id = reftext = m[1]
|
776
|
-
Inline.new(self, :anchor, reftext, :type => :bibref, :target => id).render
|
777
|
-
}
|
778
|
-
end
|
779
|
-
|
780
|
-
if found[:square_bracket] && result.include?('[[')
|
781
|
-
result.gsub!(REGEXP[:anchor_macro]) {
|
782
|
-
# alias match for Ruby 1.8.7 compat
|
783
|
-
m = $~
|
784
|
-
# honor the escape
|
785
|
-
if m[0].start_with? '\\'
|
786
|
-
next m[0][1..-1]
|
787
|
-
end
|
788
|
-
id, reftext = m[1].split(',').map(&:strip)
|
789
|
-
id.sub!(REGEXP[:dbl_quoted], '\2')
|
790
|
-
if reftext.nil?
|
791
|
-
reftext = "[#{id}]"
|
792
|
-
else
|
793
|
-
reftext.sub!(REGEXP[:m_dbl_quoted], '\2')
|
794
|
-
end
|
795
|
-
# NOTE the reftext should also match what's in our references dic
|
796
|
-
if !@document.references[:ids].has_key? id
|
797
|
-
Debug.debug { "Missing reference for anchor #{id}" }
|
798
|
-
end
|
799
|
-
Inline.new(self, :anchor, reftext, :type => :ref, :target => id).render
|
800
|
-
}
|
801
|
-
end
|
802
|
-
|
803
|
-
result
|
804
|
-
end
|
805
|
-
|
806
|
-
# Public: Substitute callout references
|
807
|
-
#
|
808
|
-
# text - The String text to process
|
809
|
-
#
|
810
|
-
# returns The String with the callout references rendered using the backend templates
|
811
|
-
def sub_callouts(text)
|
812
|
-
text.gsub(REGEXP[:callout_render]) {
|
813
|
-
# alias match for Ruby 1.8.7 compat
|
814
|
-
m = $~
|
815
|
-
# honor the escape
|
816
|
-
if m[1] == '\\'
|
817
|
-
# we have to do a sub since we aren't sure it's the first char
|
818
|
-
next m[0].sub('\\', '')
|
819
|
-
end
|
820
|
-
Inline.new(self, :callout, m[3], :id => @document.callouts.read_next_id).render
|
821
|
-
}
|
822
|
-
end
|
823
|
-
|
824
|
-
# Public: Substitute post replacements
|
825
|
-
#
|
826
|
-
# text - The String text to process
|
827
|
-
#
|
828
|
-
# returns The String with the post replacements rendered using the backend templates
|
829
|
-
def sub_post_replacements(text)
|
830
|
-
if @document.attributes['hardbreaks']
|
831
|
-
lines = text.lines.entries
|
832
|
-
return text if lines.size == 1
|
833
|
-
last = lines.pop
|
834
|
-
lines.map {|line| Inline.new(self, :break, line.rstrip.chomp(LINE_BREAK), :type => :line).render }.push(last) * EOL
|
835
|
-
else
|
836
|
-
text.gsub(REGEXP[:line_break]) { Inline.new(self, :break, $1, :type => :line).render }
|
837
|
-
end
|
838
|
-
end
|
839
|
-
|
840
|
-
# Internal: Transform (render) a quoted text region
|
841
|
-
#
|
842
|
-
# match - The MatchData for the quoted text region
|
843
|
-
# type - The quoting type (single, double, strong, emphasis, monospaced, etc)
|
844
|
-
# scope - The scope of the quoting (constrained or unconstrained)
|
845
|
-
#
|
846
|
-
# returns The rendered text for the quoted text region
|
847
|
-
def transform_quoted_text(match, type, scope)
|
848
|
-
unescaped_attrs = nil
|
849
|
-
if match[0].start_with? '\\'
|
850
|
-
if scope == :constrained && !match[2].nil?
|
851
|
-
unescaped_attrs = "[#{match[2]}]"
|
852
|
-
else
|
853
|
-
return match[0][1..-1]
|
854
|
-
end
|
855
|
-
end
|
856
|
-
|
857
|
-
if scope == :constrained
|
858
|
-
if unescaped_attrs.nil?
|
859
|
-
attributes = parse_quoted_text_attributes(match[2])
|
860
|
-
id = attributes.nil? ? nil : attributes.delete('id')
|
861
|
-
"#{match[1]}#{Inline.new(self, :quoted, match[3], :type => type, :id => id, :attributes => attributes).render}"
|
862
|
-
else
|
863
|
-
"#{unescaped_attrs}#{Inline.new(self, :quoted, match[3], :type => type, :attributes => {}).render}"
|
864
|
-
end
|
865
|
-
else
|
866
|
-
attributes = parse_quoted_text_attributes(match[1])
|
867
|
-
id = attributes.nil? ? nil : attributes.delete('id')
|
868
|
-
Inline.new(self, :quoted, match[2], :type => type, :id => id, :attributes => attributes).render
|
869
|
-
end
|
870
|
-
end
|
871
|
-
|
872
|
-
# Internal: Parse the attributes that are defined on quoted text
|
873
|
-
#
|
874
|
-
# str - A String of unprocessed attributes (space-separated roles or the id/role shorthand syntax)
|
875
|
-
#
|
876
|
-
# returns nil if str is nil, an empty Hash if str is empty, otherwise a Hash of attributes (role and id only)
|
877
|
-
def parse_quoted_text_attributes(str)
|
878
|
-
return nil if str.nil?
|
879
|
-
return {} if str.empty?
|
880
|
-
str = sub_attributes(str) if str.include?('{')
|
881
|
-
str = str.strip
|
882
|
-
# for compliance, only consider first positional attribute
|
883
|
-
str, _ = str.split(',', 2) if str.include?(',')
|
884
|
-
|
885
|
-
if str.empty?
|
886
|
-
{}
|
887
|
-
elsif str.start_with?('.') || str.start_with?('#')
|
888
|
-
segments = str.split('#', 2)
|
889
|
-
|
890
|
-
if segments.length > 1
|
891
|
-
id, *more_roles = segments[1].split('.')
|
892
|
-
else
|
893
|
-
id = nil
|
894
|
-
more_roles = []
|
895
|
-
end
|
896
|
-
|
897
|
-
roles = segments[0].empty? ? [] : segments[0].split('.')
|
898
|
-
if roles.length > 1
|
899
|
-
roles.shift
|
900
|
-
end
|
901
|
-
|
902
|
-
if more_roles.length > 0
|
903
|
-
roles.concat more_roles
|
904
|
-
end
|
905
|
-
|
906
|
-
attrs = {}
|
907
|
-
attrs['id'] = id unless id.nil?
|
908
|
-
attrs['role'] = roles.empty? ? nil : (roles * ' ')
|
909
|
-
attrs
|
910
|
-
else
|
911
|
-
{'role' => str}
|
912
|
-
end
|
913
|
-
end
|
914
|
-
|
915
|
-
# Internal: Parse the attributes in the attribute line
|
916
|
-
#
|
917
|
-
# attrline - A String of unprocessed attributes (key/value pairs)
|
918
|
-
# posattrs - The keys for positional attributes
|
919
|
-
#
|
920
|
-
# returns nil if attrline is nil, an empty Hash if attrline is empty, otherwise a Hash of parsed attributes
|
921
|
-
def parse_attributes(attrline, posattrs = ['role'], opts = {})
|
922
|
-
return nil if attrline.nil?
|
923
|
-
return {} if attrline.empty?
|
924
|
-
attrline = @document.sub_attributes(attrline) if opts[:sub_input]
|
925
|
-
attrline = unescape_bracketed_text(attrline) if opts[:unescape_input]
|
926
|
-
block = nil
|
927
|
-
if opts.fetch(:sub_result, true)
|
928
|
-
# substitutions are only performed on attribute values if block is not nil
|
929
|
-
block = self
|
930
|
-
end
|
931
|
-
|
932
|
-
if opts.has_key?(:into)
|
933
|
-
AttributeList.new(attrline, block).parse_into(opts[:into], posattrs)
|
934
|
-
else
|
935
|
-
AttributeList.new(attrline, block).parse(posattrs)
|
936
|
-
end
|
937
|
-
end
|
938
|
-
|
939
|
-
# Internal: Strip bounding whitespace, fold endlines and unescaped closing
|
940
|
-
# square brackets from text extracted from brackets
|
941
|
-
def unescape_bracketed_text(text)
|
942
|
-
return '' if text.empty?
|
943
|
-
text.strip.tr(EOL, ' ').gsub('\]', ']')
|
944
|
-
end
|
945
|
-
|
946
|
-
# Internal: Resolve the list of comma-delimited subs against the possible options.
|
947
|
-
#
|
948
|
-
# subs - A comma-delimited String of substitution aliases
|
949
|
-
#
|
950
|
-
# returns An Array of Symbols representing the substitution operation
|
951
|
-
def resolve_subs subs, type = :block, subject = nil
|
952
|
-
return [] if subs.nil? || subs.empty?
|
953
|
-
candidates = []
|
954
|
-
subs.split(',').each do |val|
|
955
|
-
key = val.strip.to_sym
|
956
|
-
# special case to disable callouts for inline subs
|
957
|
-
if key == :verbatim && type == :inline
|
958
|
-
candidates << :specialcharacters
|
959
|
-
elsif COMPOSITE_SUBS.has_key? key
|
960
|
-
candidates.push(*COMPOSITE_SUBS[key])
|
961
|
-
else
|
962
|
-
candidates << key
|
963
|
-
end
|
964
|
-
end
|
965
|
-
# weed out invalid options and remove duplicates (first wins)
|
966
|
-
resolved = candidates & SUB_OPTIONS[type]
|
967
|
-
if (invalid = candidates - resolved).size > 0
|
968
|
-
warn "asciidoctor: WARNING: invalid substitution type#{invalid.size > 1 ? 's' : ''}#{subject ? ' for ' : nil}#{subject}: #{invalid * ', '}"
|
969
|
-
end
|
970
|
-
resolved
|
971
|
-
end
|
972
|
-
|
973
|
-
def resolve_block_subs subs, subject
|
974
|
-
resolve_subs subs, :block, subject
|
975
|
-
end
|
976
|
-
|
977
|
-
def resolve_pass_subs subs
|
978
|
-
resolve_subs subs, :inline, 'passthrough macro'
|
979
|
-
end
|
980
|
-
|
981
|
-
# Public: Highlight the source code if a source highlighter is defined
|
982
|
-
# on the document, otherwise return the text unprocessed
|
983
|
-
#
|
984
|
-
# Callout marks are stripped from the source prior to passing it to the
|
985
|
-
# highlighter, then later restored in rendered form, so they are not
|
986
|
-
# incorrectly processed by the source highlighter.
|
987
|
-
#
|
988
|
-
# source - the source code String to highlight
|
989
|
-
# sub_callouts - a Boolean flag indicating whether callout marks should be substituted
|
990
|
-
#
|
991
|
-
# returns the highlighted source code, if a source highlighter is defined
|
992
|
-
# on the document, otherwise the unprocessed text
|
993
|
-
def highlight_source(source, sub_callouts, highlighter = nil)
|
994
|
-
highlighter ||= @document.attributes['source-highlighter']
|
995
|
-
Helpers.require_library highlighter, (highlighter == 'pygments' ? 'pygments.rb' : highlighter)
|
996
|
-
callout_marks = {}
|
997
|
-
lineno = 0
|
998
|
-
callout_on_last = false
|
999
|
-
if sub_callouts
|
1000
|
-
last = -1
|
1001
|
-
# extract callout marks, indexed by line number
|
1002
|
-
source = source.split(EOL).map {|line|
|
1003
|
-
lineno = lineno + 1
|
1004
|
-
line.gsub(REGEXP[:callout_scan]) {
|
1005
|
-
# alias match for Ruby 1.8.7 compat
|
1006
|
-
m = $~
|
1007
|
-
# honor the escape
|
1008
|
-
if m[1] == '\\'
|
1009
|
-
m[0].sub('\\', '')
|
1010
|
-
else
|
1011
|
-
(callout_marks[lineno] ||= []) << m[3]
|
1012
|
-
last = lineno
|
1013
|
-
nil
|
1014
|
-
end
|
1015
|
-
}
|
1016
|
-
} * EOL
|
1017
|
-
callout_on_last = (last == lineno)
|
1018
|
-
end
|
1019
|
-
|
1020
|
-
linenums_mode = nil
|
1021
|
-
|
1022
|
-
case highlighter
|
1023
|
-
when 'coderay'
|
1024
|
-
result = ::CodeRay::Duo[attr('language', 'text').to_sym, :html, {
|
1025
|
-
:css => @document.attributes.fetch('coderay-css', 'class').to_sym,
|
1026
|
-
:line_numbers => (linenums_mode = (attr?('linenums') ? @document.attributes.fetch('coderay-linenums-mode', 'table').to_sym : nil)),
|
1027
|
-
:line_number_anchors => false}].highlight(source)
|
1028
|
-
when 'pygments'
|
1029
|
-
lexer = ::Pygments::Lexer[attr('language')]
|
1030
|
-
if lexer
|
1031
|
-
opts = { :cssclass => 'pyhl', :classprefix => 'tok-', :nobackground => true }
|
1032
|
-
opts[:noclasses] = true unless @document.attributes.fetch('pygments-css', 'class') == 'class'
|
1033
|
-
if attr? 'linenums'
|
1034
|
-
opts[:linenos] = (linenums_mode = @document.attributes.fetch('pygments-linenums-mode', 'table').to_sym).to_s
|
1035
|
-
end
|
1036
|
-
|
1037
|
-
# FIXME stick these regexs into constants
|
1038
|
-
if linenums_mode == :table
|
1039
|
-
result = lexer.highlight(source, :options => opts).
|
1040
|
-
sub(/<div class="pyhl">(.*)<\/div>/m, '\1').
|
1041
|
-
gsub(/<pre[^>]*>(.*?)<\/pre>\s*/m, '\1')
|
1042
|
-
else
|
1043
|
-
result = lexer.highlight(source, :options => opts).
|
1044
|
-
sub(/<div class="pyhl"><pre[^>]*>(.*?)<\/pre><\/div>/m, '\1')
|
1045
|
-
end
|
1046
|
-
else
|
1047
|
-
result = source
|
1048
|
-
end
|
1049
|
-
end
|
1050
|
-
|
1051
|
-
if !sub_callouts || callout_marks.empty?
|
1052
|
-
result
|
1053
|
-
else
|
1054
|
-
lineno = 0
|
1055
|
-
reached_code = linenums_mode != :table
|
1056
|
-
result.split(EOL).map {|line|
|
1057
|
-
unless reached_code
|
1058
|
-
unless line.include?('<td class="code">')
|
1059
|
-
next line
|
1060
|
-
end
|
1061
|
-
reached_code = true
|
1062
|
-
end
|
1063
|
-
lineno = lineno + 1
|
1064
|
-
if (conums = callout_marks.delete(lineno))
|
1065
|
-
tail = nil
|
1066
|
-
if callout_on_last && callout_marks.empty? && (pos = line.index '</pre>')
|
1067
|
-
tail = line[pos..-1]
|
1068
|
-
line = line[0...pos]
|
1069
|
-
end
|
1070
|
-
if conums.size == 1
|
1071
|
-
%(#{line}#{Inline.new(self, :callout, conums.first, :id => @document.callouts.read_next_id).render }#{tail})
|
1072
|
-
else
|
1073
|
-
conums_markup = conums.map {|conum| Inline.new(self, :callout, conum, :id => @document.callouts.read_next_id).render } * ' '
|
1074
|
-
%(#{line}#{conums_markup}#{tail})
|
1075
|
-
end
|
1076
|
-
else
|
1077
|
-
line
|
1078
|
-
end
|
1079
|
-
} * EOL
|
1080
|
-
end
|
1081
|
-
end
|
1082
|
-
end
|
1083
|
-
end
|