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.

Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +209 -25
  3. data/{LICENSE → LICENSE.adoc} +4 -3
  4. data/README.adoc +392 -395
  5. data/Rakefile +94 -137
  6. data/benchmark/benchmark.rb +127 -0
  7. data/benchmark/sample-data/mdbasics.adoc +334 -0
  8. data/bin/asciidoctor +5 -8
  9. data/bin/asciidoctor-safe +4 -8
  10. data/compat/asciidoc.conf +78 -11
  11. data/compat/font-awesome-3-compat.css +397 -0
  12. data/data/stylesheets/asciidoctor-default.css +399 -0
  13. data/data/stylesheets/coderay-asciidoctor.css +89 -0
  14. data/features/open_block.feature +92 -0
  15. data/features/pass_block.feature +66 -0
  16. data/features/step_definitions.rb +42 -0
  17. data/features/text_formatting.feature +55 -0
  18. data/features/xref.feature +116 -0
  19. data/lib/asciidoctor.rb +1155 -605
  20. data/lib/asciidoctor/abstract_block.rb +157 -71
  21. data/lib/asciidoctor/abstract_node.rb +150 -93
  22. data/lib/asciidoctor/attribute_list.rb +85 -90
  23. data/lib/asciidoctor/block.rb +51 -24
  24. data/lib/asciidoctor/callouts.rb +4 -7
  25. data/lib/asciidoctor/cli.rb +3 -0
  26. data/lib/asciidoctor/cli/invoker.rb +86 -76
  27. data/lib/asciidoctor/cli/options.rb +111 -61
  28. data/lib/asciidoctor/converter.rb +232 -0
  29. data/lib/asciidoctor/converter/base.rb +58 -0
  30. data/lib/asciidoctor/converter/composite.rb +66 -0
  31. data/lib/asciidoctor/converter/docbook45.rb +94 -0
  32. data/lib/asciidoctor/converter/docbook5.rb +684 -0
  33. data/lib/asciidoctor/converter/factory.rb +225 -0
  34. data/lib/asciidoctor/converter/html5.rb +1081 -0
  35. data/lib/asciidoctor/converter/template.rb +296 -0
  36. data/lib/asciidoctor/core_ext.rb +7 -0
  37. data/lib/asciidoctor/core_ext/object/nil_or_empty.rb +23 -0
  38. data/lib/asciidoctor/core_ext/string/chr.rb +6 -0
  39. data/lib/asciidoctor/core_ext/symbol/length.rb +6 -0
  40. data/lib/asciidoctor/document.rb +590 -304
  41. data/lib/asciidoctor/extensions.rb +1100 -308
  42. data/lib/asciidoctor/helpers.rb +109 -46
  43. data/lib/asciidoctor/inline.rb +16 -9
  44. data/lib/asciidoctor/list.rb +23 -15
  45. data/lib/asciidoctor/opal_ext.rb +4 -0
  46. data/lib/asciidoctor/opal_ext/comparable.rb +38 -0
  47. data/lib/asciidoctor/opal_ext/dir.rb +13 -0
  48. data/lib/asciidoctor/opal_ext/error.rb +2 -0
  49. data/lib/asciidoctor/opal_ext/file.rb +125 -0
  50. data/lib/asciidoctor/{lexer.rb → parser.rb} +646 -455
  51. data/lib/asciidoctor/path_resolver.rb +141 -77
  52. data/lib/asciidoctor/reader.rb +257 -187
  53. data/lib/asciidoctor/section.rb +12 -16
  54. data/lib/asciidoctor/stylesheets.rb +91 -0
  55. data/lib/asciidoctor/substitutors.rb +1548 -0
  56. data/lib/asciidoctor/table.rb +73 -57
  57. data/lib/asciidoctor/timings.rb +39 -0
  58. data/lib/asciidoctor/version.rb +1 -1
  59. data/man/asciidoctor.1 +22 -14
  60. data/man/asciidoctor.adoc +18 -10
  61. data/test/attributes_test.rb +314 -14
  62. data/test/blocks_test.rb +763 -118
  63. data/test/converter_test.rb +352 -0
  64. data/test/document_test.rb +518 -199
  65. data/test/extensions_test.rb +273 -103
  66. data/test/fixtures/asciidoc_index.txt +27 -13
  67. data/test/fixtures/basic-docinfo.xml +1 -1
  68. data/test/fixtures/chapter-a.adoc +3 -0
  69. data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +6 -0
  70. data/test/fixtures/docinfo.xml +1 -1
  71. data/test/fixtures/include-file.asciidoc +2 -0
  72. data/test/fixtures/master.adoc +5 -0
  73. data/test/invoker_test.rb +173 -61
  74. data/test/links_test.rb +97 -21
  75. data/test/lists_test.rb +181 -22
  76. data/test/options_test.rb +86 -2
  77. data/test/paragraphs_test.rb +47 -5
  78. data/test/{lexer_test.rb → parser_test.rb} +128 -57
  79. data/test/paths_test.rb +36 -1
  80. data/test/preamble_test.rb +25 -17
  81. data/test/reader_test.rb +404 -249
  82. data/test/sections_test.rb +623 -58
  83. data/test/substitutions_test.rb +609 -132
  84. data/test/tables_test.rb +198 -24
  85. data/test/test_helper.rb +101 -31
  86. data/test/text_test.rb +88 -31
  87. metadata +160 -64
  88. data/Gemfile +0 -12
  89. data/Guardfile +0 -18
  90. data/asciidoctor.gemspec +0 -143
  91. data/lib/asciidoctor/backends/_stylesheets.rb +0 -466
  92. data/lib/asciidoctor/backends/base_template.rb +0 -114
  93. data/lib/asciidoctor/backends/docbook45.rb +0 -774
  94. data/lib/asciidoctor/backends/docbook5.rb +0 -103
  95. data/lib/asciidoctor/backends/html5.rb +0 -1214
  96. data/lib/asciidoctor/renderer.rb +0 -259
  97. data/lib/asciidoctor/substituters.rb +0 -1083
  98. data/test/fixtures/asciidoc.txt +0 -105
  99. data/test/fixtures/ascshort.txt +0 -32
  100. data/test/fixtures/list_elements.asciidoc +0 -10
  101. data/test/renderer_test.rb +0 -162
@@ -1,10 +1,8 @@
1
- require 'optparse'
2
-
3
1
  module Asciidoctor
4
2
  module Cli
5
3
 
6
4
  # Public: List of options that can be specified on the command line
7
- class Options < Hash
5
+ class Options < ::Hash
8
6
 
9
7
  def initialize(options = {})
10
8
  self[:attributes] = options[:attributes] || {}
@@ -21,11 +19,13 @@ module Asciidoctor
21
19
  self[:attributes]['backend'] = options[:backend]
22
20
  end
23
21
  self[:eruby] = options[:eruby] || nil
24
- self[:compact] = options[:compact] || false
25
- self[:verbose] = options[:verbose] || false
22
+ self[:verbose] = options[:verbose] || 1
23
+ self[:load_paths] = options[:load_paths] || nil
24
+ self[:requires] = options[:requires] || nil
26
25
  self[:base_dir] = options[:base_dir]
27
26
  self[:destination_dir] = options[:destination_dir] || nil
28
27
  self[:trace] = false
28
+ self[:timings] = false
29
29
  end
30
30
 
31
31
  def self.parse!(args)
@@ -33,7 +33,7 @@ module Asciidoctor
33
33
  end
34
34
 
35
35
  def parse!(args)
36
- opts_parser = OptionParser.new do |opts|
36
+ opts_parser = ::OptionParser.new do |opts|
37
37
  opts.banner = <<-EOS
38
38
  Usage: asciidoctor [OPTION]... FILE...
39
39
  Translate the AsciiDoc source FILE or FILE(s) into the backend output format (e.g., HTML 5, DocBook 4.5, etc.)
@@ -42,14 +42,11 @@ Example: asciidoctor -b html5 source.asciidoc
42
42
 
43
43
  EOS
44
44
 
45
- opts.on('-v', '--verbose', 'enable verbose mode (default: false)') do |verbose|
46
- self[:verbose] = true
47
- end
48
45
  opts.on('-b', '--backend BACKEND', 'set output format backend (default: html5)') do |backend|
49
46
  self[:attributes]['backend'] = backend
50
47
  end
51
48
  opts.on('-d', '--doctype DOCTYPE', ['article', 'book', 'manpage', 'inline'],
52
- 'document type to use when rendering output: [article, book, manpage, inline] (default: article)') do |doc_type|
49
+ 'document type to use when converting document: [article, book, manpage, inline] (default: article)') do |doc_type|
53
50
  self[:attributes]['doctype'] = doc_type
54
51
  end
55
52
  opts.on('-o', '--out-file FILE', 'output file (default: based on input file path); use - to output to STDOUT') do |output_file|
@@ -70,16 +67,15 @@ Example: asciidoctor -b html5 source.asciidoc
70
67
  self[:header_footer] = false
71
68
  end
72
69
  opts.on('-n', '--section-numbers', 'auto-number section titles in the HTML backend; disabled by default') do
73
- self[:attributes]['numbered'] = ''
70
+ self[:attributes]['sectnums'] = ''
74
71
  end
75
72
  opts.on('-e', '--eruby ERUBY', ['erb', 'erubis'],
76
- 'specify eRuby implementation to render built-in templates: [erb, erubis] (default: erb)') do |eruby|
73
+ 'specify eRuby implementation to use when rendering custom ERB templates: [erb, erubis] (default: erb)') do |eruby|
77
74
  self[:eruby] = eruby
78
75
  end
79
- opts.on('-C', '--compact', 'compact the output by removing blank lines (default: false)') do
80
- self[:compact] = true
76
+ opts.on('-C', '--compact', 'compact the output by removing blank lines. (No longer in use)') do
81
77
  end
82
- opts.on('-a', '--attribute key[=value],key2[=value2],...', Array,
78
+ opts.on('-a', '--attribute key[=value],key2[=value2],...', ::Array,
83
79
  'a list of document attributes to set in the form of key, key! or key=value pair',
84
80
  'unless @ is appended to the value, these attributes take precedence over attributes',
85
81
  'defined in the source document') do |attribs|
@@ -92,17 +88,17 @@ Example: asciidoctor -b html5 source.asciidoc
92
88
  self[:attributes][key] = val || ''
93
89
  end
94
90
  end
95
- opts.on('-T', '--template-dir DIR', 'a directory containing custom render templates that override the built-in set (requires tilt gem)',
91
+ opts.on('-T', '--template-dir DIR', 'a directory containing custom converter templates that override the built-in converter (requires tilt gem)',
96
92
  'may be specified multiple times') do |template_dir|
97
93
  if self[:template_dirs].nil?
98
94
  self[:template_dirs] = [template_dir]
99
- elsif self[:template_dirs].is_a? Array
95
+ elsif self[:template_dirs].is_a? ::Array
100
96
  self[:template_dirs].push template_dir
101
97
  else
102
98
  self[:template_dirs] = [self[:template_dirs], template_dir]
103
99
  end
104
100
  end
105
- opts.on('-E', '--template-engine NAME', 'template engine to use for the custom render templates (loads gem on demand)') do |template_engine|
101
+ opts.on('-E', '--template-engine NAME', 'template engine to use for the custom converter templates (loads gem on demand)') do |template_engine|
106
102
  self[:template_engine] = template_engine
107
103
  end
108
104
  opts.on('-B', '--base-dir DIR', 'base directory containing the document and resources (default: directory of source file)') do |base_dir|
@@ -111,83 +107,137 @@ Example: asciidoctor -b html5 source.asciidoc
111
107
  opts.on('-D', '--destination-dir DIR', 'destination output directory (default: directory of source file)') do |dest_dir|
112
108
  self[:destination_dir] = dest_dir
113
109
  end
110
+ opts.on('-IDIRECTORY', '--load-path LIBRARY', 'add a directory to the $LOAD_PATH',
111
+ 'may be specified more than once') do |path|
112
+ (self[:load_paths] ||= []).concat(path.split ::File::PATH_SEPARATOR)
113
+ end
114
+ opts.on('-rLIBRARY', '--require LIBRARY', 'require the specified library before executing the processor (using require)',
115
+ 'may be specified more than once') do |path|
116
+ (self[:requires] ||= []).concat(path.split ',')
117
+ end
118
+ opts.on('-q', '--quiet', 'suppress warnings (default: false)') do |verbose|
119
+ self[:verbose] = 0
120
+ end
114
121
  opts.on('--trace', 'include backtrace information on errors (default: false)') do |trace|
115
122
  self[:trace] = true
116
123
  end
124
+ opts.on('-v', '--verbose', 'enable verbose mode (default: false)') do |verbose|
125
+ self[:verbose] = 2
126
+ end
127
+ opts.on('-t', '--timings', 'enable timings mode (default: false)') do |timing|
128
+ self[:timings] = true
129
+ end
117
130
 
118
131
  opts.on_tail('-h', '--help', 'show this message') do
119
132
  $stdout.puts opts
120
133
  return 0
121
134
  end
122
135
 
123
- opts.on_tail('-V', '--version', 'display the version') do
124
- $stdout.puts "Asciidoctor #{Asciidoctor::VERSION} [http://asciidoctor.org]"
136
+ opts.on_tail('-V', '--version', 'display the version and runtime environment (or -v if no other flags or arguments)') do
137
+ $stdout.puts %(Asciidoctor #{::Asciidoctor::VERSION} [http://asciidoctor.org])
138
+ $stdout.puts %(Runtime Environment (#{RUBY_DESCRIPTION}))
125
139
  return 0
126
140
  end
127
141
 
128
142
  end
129
143
 
130
- begin
131
- infiles = []
132
- opts_parser.parse! args
133
-
134
- if args.empty?
144
+ infiles = []
145
+ opts_parser.parse! args
146
+
147
+ if args.empty?
148
+ if self[:verbose] == 2
149
+ $stdout.puts %(Asciidoctor #{::Asciidoctor::VERSION} [http://asciidoctor.org])
150
+ $stdout.puts %(Runtime Environment (#{RUBY_DESCRIPTION}))
151
+ return 0
152
+ else
135
153
  $stderr.puts opts_parser
136
154
  return 1
137
155
  end
156
+ end
138
157
 
139
- # shave off the file to process so that options errors appear correctly
140
- if args.size == 1 && args.first == '-'
141
- infiles.push args.pop
142
- elsif
143
- args.each do |file|
144
- if (file == '-' || file.start_with?('-'))
145
- # warn, but don't panic; we may have enough to proceed, so we won't force a failure
146
- $stderr.puts "asciidoctor: WARNING: extra arguments detected (unparsed arguments: #{args.map{|a| "'#{a}'"} * ', '}) or incorrect usage of stdin"
158
+ # shave off the file to process so that options errors appear correctly
159
+ if args.size == 1 && args[0] == '-'
160
+ infiles.push args.pop
161
+ elsif
162
+ args.each do |file|
163
+ if file == '-' || (file.start_with? '-')
164
+ # warn, but don't panic; we may have enough to proceed, so we won't force a failure
165
+ $stderr.puts "asciidoctor: WARNING: extra arguments detected (unparsed arguments: #{args.map{|a| "'#{a}'"} * ', '}) or incorrect usage of stdin"
166
+ else
167
+ if ::File.readable? file
168
+ matches = [file]
147
169
  else
148
- # TODO this glob may not be necessary as the shell should have already performed expansion
149
- matches = Dir.glob file
150
-
151
- if matches.empty?
152
- $stderr.puts "asciidoctor: FAILED: input file #{file} missing or cannot be read"
170
+ # Tilt backslashes in Windows paths the Ruby-friendly way
171
+ if ::File::ALT_SEPARATOR == '\\' && (file.include? '\\')
172
+ file = file.tr '\\', '/'
173
+ end
174
+ if (matches = ::Dir.glob file).empty?
175
+ $stderr.puts %(asciidoctor: FAILED: input file #{file} missing or cannot be read)
153
176
  return 1
154
177
  end
155
-
156
- infiles.concat matches
157
178
  end
179
+
180
+ infiles.concat matches
158
181
  end
159
182
  end
183
+ end
160
184
 
161
- infiles.each do |file|
162
- unless file == '-' || File.readable?(file)
163
- $stderr.puts "asciidoctor: FAILED: input file #{file} missing or cannot be read"
164
- return 1
165
- end
185
+ infiles.each do |file|
186
+ unless file == '-' || (::File.readable? file)
187
+ $stderr.puts %(asciidoctor: FAILED: input file #{file} missing or cannot be read)
188
+ return 1
166
189
  end
190
+ end
167
191
 
168
- self[:input_files] = infiles
192
+ self[:input_files] = infiles
169
193
 
170
- if !self[:template_dirs].nil?
194
+ self.delete(:attributes) if self[:attributes].empty?
195
+
196
+ if self[:template_dirs]
197
+ begin
198
+ require 'tilt' unless defined? ::Tilt
199
+ rescue ::LoadError
200
+ raise $! if self[:trace]
201
+ $stderr.puts 'asciidoctor: FAILED: \'tilt\' could not be loaded'
202
+ $stderr.puts ' You must have the tilt gem installed (gem install tilt) to use custom backend templates'
203
+ $stderr.puts ' Use --trace for backtrace'
204
+ return 1
205
+ rescue ::SystemExit
206
+ # not permitted here
207
+ end
208
+ end
209
+
210
+ if (load_paths = self[:load_paths])
211
+ (self[:load_paths] = load_paths.uniq).reverse_each do |path|
212
+ $:.unshift File.expand_path(path)
213
+ end
214
+ end
215
+
216
+ if (requires = self[:requires])
217
+ (self[:requires] = requires.uniq).each do |path|
171
218
  begin
172
- require 'tilt'
173
- rescue LoadError
174
- $stderr.puts 'asciidoctor: FAILED: tilt could not be loaded; to use a custom backend, you must have the tilt gem installed (gem install tilt)'
219
+ require path
220
+ rescue ::LoadError
221
+ raise $! if self[:trace]
222
+ $stderr.puts %(asciidoctor: FAILED: '#{path}' could not be loaded)
223
+ $stderr.puts ' Use --trace for backtrace'
175
224
  return 1
225
+ rescue ::SystemExit
226
+ # not permitted here
176
227
  end
177
228
  end
178
-
179
- rescue OptionParser::MissingArgument
180
- $stderr.puts "asciidoctor: option #{$!.message}"
181
- $stdout.puts opts_parser
182
- return 1
183
- rescue OptionParser::InvalidOption, OptionParser::InvalidArgument
184
- $stderr.puts "asciidoctor: #{$!.message}"
185
- $stdout.puts opts_parser
186
- return 1
187
229
  end
188
- self
189
- end # parse()
190
230
 
231
+ self
232
+ rescue ::OptionParser::MissingArgument
233
+ $stderr.puts %(asciidoctor: option #{$!.message})
234
+ $stdout.puts opts_parser
235
+ return 1
236
+ rescue ::OptionParser::InvalidOption, ::OptionParser::InvalidArgument
237
+ $stderr.puts %(asciidoctor: #{$!.message})
238
+ $stdout.puts opts_parser
239
+ return 1
240
+ end
191
241
  end
192
242
  end
193
243
  end
@@ -0,0 +1,232 @@
1
+ module Asciidoctor
2
+ # A base module for defining converters that can be used to convert {AbstractNode}
3
+ # objects in a parsed AsciiDoc document to a backend format such as HTML or
4
+ # DocBook.
5
+ #
6
+ # Implementing a converter involves:
7
+ #
8
+ # * including this module in a {Converter} implementation class
9
+ # * overriding the {Converter#convert} method
10
+ # * optionally associating the converter with one or more backends using
11
+ # the {#register_for} DSL method imported by the {Config Converter::Config} module
12
+ #
13
+ # Examples
14
+ #
15
+ # class TextConverter
16
+ # include Asciidoctor::Converter
17
+ # register_for 'text'
18
+ # def initialize backend, opts
19
+ # super
20
+ # outfilesuffix '.txt'
21
+ # end
22
+ # def convert node, transform = nil
23
+ # case (transform ||= node.node_name)
24
+ # when 'document'
25
+ # node.content
26
+ # when 'section'
27
+ # [node.title, node.content] * "\n\n"
28
+ # when 'paragraph'
29
+ # node.content.tr("\n", ' ') << "\n"
30
+ # else
31
+ # if transform.start_with? 'inline_'
32
+ # node.text
33
+ # else
34
+ # %(<#{transform}>\n)
35
+ # end
36
+ # end
37
+ # end
38
+ # end
39
+ #
40
+ # puts Asciidoctor.convert_file 'sample.adoc', backend: :text
41
+ module Converter
42
+ # A module that provides the {#register_for} method for statically
43
+ # registering a converter with the default {Factory Converter::Factory} instance.
44
+ module Config
45
+ # Public: Statically registers the current {Converter} class with the default
46
+ # {Factory Converter::Factory} to handle conversion to the specified backends.
47
+ #
48
+ # This method also defines the converts? method on the class which returns whether
49
+ # the class is registered to convert a specified backend.
50
+ #
51
+ # backends - A String Array of backends with which to associate this {Converter} class.
52
+ #
53
+ # Returns nothing
54
+ def register_for *backends
55
+ Factory.register self, backends
56
+ metaclass = class << self; self; end
57
+ if backends == ['*']
58
+ metaclass.send :define_method, :converts? do |name|
59
+ true
60
+ end
61
+ else
62
+ metaclass.send :define_method, :converts? do |name|
63
+ backends.include? name
64
+ end
65
+ end
66
+ nil
67
+ end
68
+ end
69
+
70
+ module BackendInfo
71
+ def backend_info
72
+ @backend_info ||= setup_backend_info
73
+ end
74
+
75
+ def setup_backend_info
76
+ raise ::ArgumentError, %(Cannot determine backend for converter: #{self.class}) unless @backend
77
+ base = @backend.sub TrailingDigitsRx, ''
78
+ if (ext = DEFAULT_EXTENSIONS[base])
79
+ type = ext[1..-1]
80
+ else
81
+ # QUESTION should we be forcing the basebackend to html if unknown?
82
+ base = 'html'
83
+ ext = '.html'
84
+ type = 'html'
85
+ syntax = 'html'
86
+ end
87
+ {
88
+ 'basebackend' => base,
89
+ 'outfilesuffix' => ext,
90
+ 'filetype' => type,
91
+ 'htmlsyntax' => syntax
92
+ }
93
+ end
94
+
95
+ def filetype value = nil
96
+ if value
97
+ backend_info['filetype'] = value
98
+ else
99
+ backend_info['filetype']
100
+ end
101
+ end
102
+
103
+ def basebackend value = nil
104
+ if value
105
+ backend_info['basebackend'] = value
106
+ else
107
+ backend_info['basebackend']
108
+ end
109
+ end
110
+
111
+ def outfilesuffix value = nil
112
+ if value
113
+ backend_info['outfilesuffix'] = value
114
+ else
115
+ backend_info['outfilesuffix']
116
+ end
117
+ end
118
+
119
+ def htmlsyntax value = nil
120
+ if value
121
+ backend_info['htmlsyntax'] = value
122
+ else
123
+ backend_info['htmlsyntax']
124
+ end
125
+ end
126
+ end
127
+
128
+ class << self
129
+ # Mixes the {Config Converter::Config} module into any class that includes the {Converter} module.
130
+ #
131
+ # converter - The Class that includes the {Converter} module
132
+ #
133
+ # Returns nothing
134
+ def included converter
135
+ converter.extend Config
136
+ end
137
+ end
138
+
139
+ include Config
140
+ include BackendInfo
141
+
142
+ # Public: Creates a new instance of Converter
143
+ #
144
+ # backend - The String backend format to which this converter converts.
145
+ # opts - An options Hash (optional, default: {})
146
+ #
147
+ # Returns a new instance of [Converter]
148
+ def initialize backend, opts = {}
149
+ @backend = backend
150
+ setup_backend_info
151
+ end
152
+
153
+ =begin
154
+ # Public: Invoked when this converter is added to the chain of converters in a {CompositeConverter}.
155
+ #
156
+ # owner - The CompositeConverter instance
157
+ #
158
+ # Returns nothing
159
+ def composed owner
160
+ end
161
+ =end
162
+
163
+ # Public: Converts an {AbstractNode} using the specified transform. If a
164
+ # transform is not specified, implementations typically derive one from the
165
+ # {AbstractNode#node_name} property.
166
+ #
167
+ # Implementations are free to decide how to carry out the conversion. In
168
+ # the case of the built-in converters, the tranform value is used to
169
+ # dispatch to a handler method. The {TemplateConverter} uses the value of
170
+ # the transform to select a template to render.
171
+ #
172
+ # node - The concrete instance of AbstractNode to convert
173
+ # transform - An optional String transform that hints at which transformation
174
+ # should be applied to this node. If a transform is not specified,
175
+ # the transform is typically derived from the value of the
176
+ # node's node_name property. (optional, default: nil)
177
+ #
178
+ # Returns the [String] result
179
+ def convert node, transform = nil
180
+ raise ::NotImplementedError
181
+ end
182
+
183
+ # Public: Converts an {AbstractNode} using the specified transform along
184
+ # with additional options. Delegates to {#convert} without options by default.
185
+ #
186
+ # node - The concrete instance of AbstractNode to convert
187
+ # transform - An optional String transform that hints at which transformation
188
+ # should be applied to this node. If a transform is not specified,
189
+ # the transform is typically derived from the value of the
190
+ # node's node_name property. (optional, default: nil)
191
+ # opts - An optional Hash of options that provide additional hints about
192
+ # how to convert the node.
193
+ #
194
+ # Returns the [String] result
195
+ def convert_with_options node, transform = nil, opts = {}
196
+ convert node, transform
197
+ end
198
+ end
199
+
200
+ # A module that can be used to mix the {#write} method into a {Converter}
201
+ # implementation to allow the converter to control how the output is written
202
+ # to disk.
203
+ module Writer
204
+ # Public: Writes the output to the specified target file name or stream.
205
+ #
206
+ # output - The output String to write
207
+ # target - The String file name or stream object to which the output should
208
+ # be written.
209
+ #
210
+ # Returns nothing
211
+ def write output, target
212
+ if target.respond_to? :write
213
+ target.write output.chomp
214
+ # ensure there's a trailing endline to be nice to terminals
215
+ target.write EOL
216
+ else
217
+ ::File.open(target, 'w') {|f| f.write output }
218
+ end
219
+ nil
220
+ end
221
+ end
222
+
223
+ module VoidWriter
224
+ include Writer
225
+ # Public: Does not write output
226
+ def write output, target
227
+ end
228
+ end
229
+ end
230
+
231
+ require 'asciidoctor/converter/base'
232
+ require 'asciidoctor/converter/factory'