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
@@ -10,7 +10,7 @@ module Asciidoctor
10
10
  # attrlist = Asciidoctor::AttributeList.new('astyle')
11
11
  #
12
12
  # attrlist.parse
13
- # => {0 => 'astyle'}
13
+ # => {0 => 'astyle'}
14
14
  #
15
15
  # attrlist.rekey(['style'])
16
16
  # => {'style' => 'astyle'}
@@ -18,55 +18,58 @@ module Asciidoctor
18
18
  # attrlist = Asciidoctor::AttributeList.new('quote, Famous Person, Famous Book (2001)')
19
19
  #
20
20
  # attrlist.parse(['style', 'attribution', 'citetitle'])
21
- # => {'style' => 'quote', 'attribution' => 'Famous Person', 'citetitle' => 'Famous Book (2001)'}
21
+ # => {'style' => 'quote', 'attribution' => 'Famous Person', 'citetitle' => 'Famous Book (2001)'}
22
22
  #
23
23
  class AttributeList
24
24
 
25
25
  # Public: Regular expressions for detecting the boundary of a value
26
- BOUNDARY_PATTERNS = {
26
+ BoundaryRxs = {
27
27
  '"' => /.*?[^\\](?=")/,
28
28
  '\'' => /.*?[^\\](?=')/,
29
29
  ',' => /.*?(?=[ \t]*(,|$))/
30
30
  }
31
31
 
32
32
  # Public: Regular expressions for unescaping quoted characters
33
- UNESCAPE_PATTERNS = {
34
- '\\"' => /\\"/,
35
- '\\\'' => /\\'/
33
+ EscapedQuoteRxs = {
34
+ '"' => /\\"/,
35
+ '\'' => /\\'/
36
36
  }
37
37
 
38
+ # Public: A regular expression for an attribute name
39
+ # TODO named attributes cannot contain dash characters
40
+ NameRx = /[A-Za-z:_][A-Za-z:_\-.]*/
41
+
42
+ BlankRx = /[ \t]+/
43
+
38
44
  # Public: Regular expressions for skipping blanks and delimiters
39
- SKIP_PATTERNS = {
40
- :blank => /[ \t]+/,
45
+ SkipRxs = {
46
+ :blank => BlankRx,
41
47
  ',' => /[ \t]*(,|$)/
42
48
  }
43
49
 
44
- # Public: A regular expression for an attribute name
45
- # TODO named attributes cannot contain dash characters
46
- NAME_PATTERN = /[A-Za-z:_][A-Za-z:_\-\.]*/
47
-
48
- def initialize(source, block = nil, quotes = ['\'', '"'], delimiter = ',', escape_char = '\\')
50
+ def initialize source, block = nil, delimiter = ','
49
51
  @scanner = ::StringScanner.new source
50
52
  @block = block
51
- @quotes = quotes
52
- @escape_char = escape_char
53
53
  @delimiter = delimiter
54
+ @delimiter_skip_pattern = SkipRxs[delimiter]
55
+ @delimiter_boundary_pattern = BoundaryRxs[delimiter]
54
56
  @attributes = nil
55
57
  end
56
58
 
57
- def parse_into(attributes, posattrs = [])
58
- attributes.update(parse(posattrs))
59
+ def parse_into attributes, posattrs = []
60
+ attributes.update(parse posattrs)
59
61
  end
60
62
 
61
- def parse(posattrs = [])
62
- return @attributes unless @attributes.nil?
63
+ def parse posattrs = []
64
+ # return if already parsed
65
+ return @attributes if @attributes
63
66
 
64
67
  @attributes = {}
65
- # not sure if I want this assignment or not
68
+ # QUESTION do we want to store the attribute list as the zero-index attribute?
66
69
  #attributes[0] = @scanner.string
67
70
  index = 0
68
71
 
69
- while parse_attribute(index, posattrs)
72
+ while parse_attribute index, posattrs
70
73
  break if @scanner.eos?
71
74
  skip_delimiter
72
75
  index += 1
@@ -75,144 +78,136 @@ class AttributeList
75
78
  @attributes
76
79
  end
77
80
 
78
- def rekey(posattrs)
79
- AttributeList.rekey(@attributes, posattrs)
81
+ def rekey posattrs
82
+ AttributeList.rekey @attributes, posattrs
80
83
  end
81
84
 
82
- def self.rekey(attributes, pos_attrs)
85
+ def self.rekey attributes, pos_attrs
83
86
  pos_attrs.each_with_index do |key, index|
84
- next if key.nil?
87
+ next unless key
85
88
  pos = index + 1
86
- unless (val = attributes[pos]).nil?
89
+ if (val = attributes[pos])
90
+ # QUESTION should we delete the positional key?
87
91
  attributes[key] = val
88
- #QUESTION should we delete the positional key?
89
- #attributes.delete pos
90
92
  end
91
93
  end
92
94
 
93
95
  attributes
94
96
  end
95
97
 
96
- def parse_attribute(index = 0, pos_attrs = [])
98
+ def parse_attribute index = 0, pos_attrs = []
97
99
  single_quoted_value = false
98
100
  skip_blank
99
- first = @scanner.peek(1)
100
- # example: "quote" || 'quote'
101
- if @quotes.include? first
101
+ # example: "quote"
102
+ if (first = @scanner.peek(1)) == '"'
103
+ name = parse_attribute_value @scanner.get_byte
102
104
  value = nil
105
+ # example: 'quote'
106
+ elsif first == '\''
103
107
  name = parse_attribute_value @scanner.get_byte
104
- if first == '\''
105
- single_quoted_value = true
106
- end
108
+ value = nil
109
+ single_quoted_value = true
107
110
  else
108
111
  name = scan_name
109
112
 
110
113
  skipped = 0
111
114
  c = nil
112
115
  if @scanner.eos?
113
- if name.nil?
114
- return false
115
- end
116
+ return false unless name
116
117
  else
117
118
  skipped = skip_blank || 0
118
119
  c = @scanner.get_byte
119
120
  end
120
121
 
121
122
  # example: quote
122
- if c.nil? || c == @delimiter
123
+ if !c || c == @delimiter
123
124
  value = nil
124
125
  # example: Sherlock Holmes || =foo=
125
- elsif c != '=' || name.nil?
126
- remainder = scan_to_delimiter
127
- name = '' if name.nil?
128
- name += ' ' * skipped + c
129
- name += remainder unless remainder.nil?
126
+ elsif c != '=' || !name
127
+ name = %(#{name}#{' ' * skipped}#{c}#{scan_to_delimiter})
130
128
  value = nil
131
129
  else
132
130
  skip_blank
133
- # example: foo=,
134
- if @scanner.peek(1) == @delimiter
135
- value = nil
136
- else
137
- c = @scanner.get_byte
138
-
139
- # example: foo="bar" || foo='bar' || foo="ba\"zaar" || foo='ba\'zaar' || foo='ba"zaar' (all spaces ignored)
140
- if @quotes.include? c
131
+ if @scanner.peek(1)
132
+ # example: foo="bar" || foo="ba\"zaar"
133
+ if (c = @scanner.get_byte) == '"'
141
134
  value = parse_attribute_value c
142
- if c == '\''
143
- single_quoted_value = true
144
- end
135
+ # example: foo='bar' || foo='ba\'zaar' || foo='ba"zaar'
136
+ elsif c == '\''
137
+ value = parse_attribute_value c
138
+ single_quoted_value = true
139
+ # example: foo=,
140
+ elsif c == @delimiter
141
+ value = nil
145
142
  # example: foo=bar (all spaces ignored)
146
- elsif !c.nil?
147
- value = c + scan_to_delimiter
143
+ else
144
+ value = %(#{c}#{scan_to_delimiter})
145
+ return true if value == 'None'
148
146
  end
149
147
  end
150
148
  end
151
149
  end
152
150
 
153
- if value.nil?
154
- resolved_name = single_quoted_value && !@block.nil? ? @block.apply_normal_subs(name) : name
155
- if !(pos_name = pos_attrs[index]).nil?
156
- @attributes[pos_name] = resolved_name
157
- else
158
- #@attributes[index + 1] = resolved_name
159
- end
160
- # not sure if we want to always assign the positional key
161
- @attributes[index + 1] = resolved_name
162
- # not sure if I want this assignment or not
163
- #@attributes[resolved_name] = nil
164
- else
165
- resolved_value = value
151
+ if value
166
152
  # example: options="opt1,opt2,opt3"
167
153
  # opts is an alias for options
168
- if name == 'options' || name == 'opts'
154
+ case name
155
+ when 'options', 'opts'
169
156
  name = 'options'
170
- resolved_value.split(',').each do |o|
171
- @attributes["#{o.strip}-option"] = ''
172
- end
173
- elsif single_quoted_value && !@block.nil?
174
- resolved_value = @block.apply_normal_subs(value)
157
+ value.split(',').each {|o| @attributes[%(#{o.strip}-option)] = '' }
158
+ @attributes[name] = value
159
+ when 'title'
160
+ @attributes[name] = value
161
+ else
162
+ @attributes[name] = single_quoted_value && !value.empty? && @block ? (@block.apply_normal_subs value) : value
163
+ end
164
+ else
165
+ resolved_name = single_quoted_value && !name.empty? && @block ? (@block.apply_normal_subs name) : name
166
+ if (pos_name = pos_attrs[index])
167
+ @attributes[pos_name] = resolved_name
175
168
  end
176
- @attributes[name] = resolved_value
169
+ # QUESTION should we always assign the positional key?
170
+ @attributes[index + 1] = resolved_name
171
+ # QUESTION should we assign the resolved name as an attribute?
172
+ #@attributes[resolved_name] = nil
177
173
  end
178
174
 
179
175
  true
180
176
  end
181
177
 
182
- def parse_attribute_value(quote)
178
+ def parse_attribute_value quote
183
179
  # empty quoted value
184
180
  if @scanner.peek(1) == quote
185
- @scanner.get_byte
181
+ @scanner.get_byte
186
182
  return ''
187
183
  end
188
184
 
189
- value = scan_to_quote quote
190
- if value.nil?
191
- quote + scan_to_delimiter
192
- else
185
+ if (value = scan_to_quote quote)
193
186
  @scanner.get_byte
194
- value.gsub(UNESCAPE_PATTERNS[@escape_char + quote], quote)
187
+ value.gsub EscapedQuoteRxs[quote], quote
188
+ else
189
+ %(#{quote}#{scan_to_delimiter})
195
190
  end
196
191
  end
197
192
 
198
193
  def skip_blank
199
- @scanner.skip SKIP_PATTERNS[:blank]
194
+ @scanner.skip BlankRx
200
195
  end
201
196
 
202
197
  def skip_delimiter
203
- @scanner.skip SKIP_PATTERNS[@delimiter]
198
+ @scanner.skip @delimiter_skip_pattern
204
199
  end
205
200
 
206
201
  def scan_name
207
- @scanner.scan NAME_PATTERN
202
+ @scanner.scan NameRx
208
203
  end
209
204
 
210
205
  def scan_to_delimiter
211
- @scanner.scan BOUNDARY_PATTERNS[@delimiter]
206
+ @scanner.scan @delimiter_boundary_pattern
212
207
  end
213
208
 
214
- def scan_to_quote(quote)
215
- @scanner.scan BOUNDARY_PATTERNS[quote]
209
+ def scan_to_quote quote
210
+ @scanner.scan BoundaryRxs[quote]
216
211
  end
217
212
 
218
213
  end
@@ -8,6 +8,20 @@ module Asciidoctor
8
8
  # => "<em>This</em> is a &lt;test&gt;"
9
9
  class Block < AbstractBlock
10
10
 
11
+ DEFAULT_CONTENT_MODEL = ::Hash.new(:simple).merge({
12
+ # TODO should probably fill in all known blocks
13
+ :audio => :empty,
14
+ :image => :empty,
15
+ :listing => :verbatim,
16
+ :literal => :verbatim,
17
+ :stem => :raw,
18
+ :open => :compound,
19
+ :page_break => :empty,
20
+ :pass => :raw,
21
+ :thematic_break => :empty,
22
+ :video => :empty
23
+ })
24
+
11
25
  # Public: Create alias for context to be consistent w/ AsciiDoc
12
26
  alias :blockname :context
13
27
 
@@ -25,31 +39,31 @@ class Block < AbstractBlock
25
39
  # * :source a String or Array of raw source for this Block. (default: nil)
26
40
  #--
27
41
  # QUESTION should we store source_data as lines for blocks that have compound content models?
28
- def initialize(parent, context, opts = {})
29
- super(parent, context)
30
- @content_model = opts.fetch(:content_model, nil) || :simple
31
- @attributes = opts.fetch(:attributes, nil) || {}
32
- @subs = opts[:subs] if opts.has_key? :subs
33
- raw_source = opts.fetch(:source, nil) || nil
34
- if raw_source.nil?
35
- @lines = []
36
- elsif raw_source.class == String
37
- # FIXME make line normalization a utility method since it's used multiple times in code base!!
38
- if ::Asciidoctor::FORCE_ENCODING
39
- @lines = raw_source.lines.map {|line| "#{line.rstrip.force_encoding(::Encoding::UTF_8)}\n" }
42
+ def initialize parent, context, opts = {}
43
+ super
44
+ @content_model = opts[:content_model] || DEFAULT_CONTENT_MODEL[context]
45
+ if opts.has_key? :subs
46
+ # FIXME this is a bit funky
47
+ # we have to be defensive to avoid lock_in_subs wiping out the override
48
+ if !(subs = opts[:subs]) || (subs.is_a? ::Array)
49
+ @subs = subs || []
50
+ @default_subs = @subs.dup
51
+ @attributes.delete('subs')
40
52
  else
41
- @lines = raw_source.lines.map {|line| "#{line.rstrip}\n" }
42
- end
43
- if (last = @lines.pop)
44
- @lines.push last.chomp
53
+ @attributes['subs'] = %(#{subs})
45
54
  end
55
+ end
56
+ if !(raw_source = opts[:source])
57
+ @lines = []
58
+ elsif raw_source.is_a? ::String
59
+ @lines = Helpers.normalize_lines_from_string raw_source
46
60
  else
47
61
  @lines = raw_source.dup
48
62
  end
49
63
  end
50
64
 
51
- # Public: Get an rendered version of the block content, performing
52
- # any substitutions on the content.
65
+ # Public: Get the converted result of the child blocks by converting the
66
+ # children appropriate to content model that this block supports.
53
67
  #
54
68
  # Examples
55
69
  #
@@ -62,10 +76,23 @@ class Block < AbstractBlock
62
76
  case @content_model
63
77
  when :compound
64
78
  super
65
- when :simple, :verbatim, :raw
66
- apply_subs @lines.join, @subs
79
+ when :simple
80
+ apply_subs(@lines * EOL, @subs)
81
+ when :verbatim, :raw
82
+ #((apply_subs @lines.join(EOL), @subs).sub StripLineWiseRx, '\1')
83
+
84
+ # QUESTION could we use strip here instead of popping empty lines?
85
+ # maybe apply_subs can know how to strip whitespace?
86
+ result = apply_subs @lines, @subs
87
+ if result.size < 2
88
+ result[0]
89
+ else
90
+ result.shift while (first = result[0]) && first.rstrip.empty?
91
+ result.pop while (last = result[-1]) && last.rstrip.empty?
92
+ result * EOL
93
+ end
67
94
  else
68
- warn "Unknown content model '#@content_model' for block: #{to_s}" unless @content_model == :empty
95
+ warn %(Unknown content model '#{@content_model}' for block: #{to_s}) unless @content_model == :empty
69
96
  nil
70
97
  end
71
98
  end
@@ -75,12 +102,12 @@ class Block < AbstractBlock
75
102
  # Returns the a String containing the lines joined together or nil if there
76
103
  # are no lines
77
104
  def source
78
- @lines.join
105
+ @lines * EOL
79
106
  end
80
107
 
81
108
  def to_s
82
- content_summary = @content_model == :compound ? %(# of blocks = #{@blocks.size}) : %(# of lines = #{@lines.size})
83
- %(Block[@context: :#@context, @content_model: :#@content_model, #{content_summary}])
109
+ content_summary = @content_model == :compound ? %(blocks: #{@blocks.size}) : %(lines: #{@lines.size})
110
+ %(#<#{self.class}@#{object_id} {context: #{@context.inspect}, content_model: #{@content_model.inspect}, style: #{@style.inspect}, #{content_summary}}>)
84
111
  end
85
112
  end
86
113
  end
@@ -36,8 +36,8 @@ class Callouts
36
36
  # Public: Get the next callout index in the document
37
37
  #
38
38
  # Reads the next callout index in the document and advances the pointer.
39
- # This method is used during rendering to retrieve the unique id of the
40
- # callout that was generated during lexing.
39
+ # This method is used during conversion to retrieve the unique id of the
40
+ # callout that was generated during parsing.
41
41
  #
42
42
  # Returns The unique String id of the next callout in the document
43
43
  def read_next_id
@@ -60,10 +60,7 @@ class Callouts
60
60
  #
61
61
  # Returns A space-separated String of callout ids associated with the specified list item
62
62
  def callout_ids(li_ordinal)
63
- current_list.inject([]) {|collector, element|
64
- collector << element[:id] if element[:ordinal] == li_ordinal
65
- collector
66
- } * ' '
63
+ current_list.map {|element| element[:ordinal] == li_ordinal ? %(#{element[:id]} ) : nil }.join.chop
67
64
  end
68
65
 
69
66
  # Public: The current list for which callouts are being collected
@@ -89,7 +86,7 @@ class Callouts
89
86
  end
90
87
 
91
88
  # Public: Rewind the list index pointer, intended to be used when switching
92
- # from the parsing to rendering phase.
89
+ # from the parsing to conversion phase.
93
90
  #
94
91
  # Returns nothing
95
92
  def rewind
@@ -0,0 +1,3 @@
1
+ require 'optparse'
2
+ require 'asciidoctor/cli/options'
3
+ require 'asciidoctor/cli/invoker'
@@ -12,100 +12,110 @@ module Asciidoctor
12
12
  @err = nil
13
13
  @code = 0
14
14
  options = options.flatten
15
- if !options.empty? && options.first.is_a?(Cli::Options)
16
- @options = options.first
17
- elsif options.first.is_a? Hash
15
+ if (first_option = options[0]).is_a?(Cli::Options)
16
+ @options = first_option
17
+ elsif first_option.is_a?(::Hash)
18
18
  @options = Cli::Options.new(options)
19
19
  else
20
- @options = Cli::Options.parse!(options)
21
- # hmmm
22
- if @options.is_a?(Integer)
23
- @code = @options
20
+ if (result = Cli::Options.parse! options).is_a? ::Integer
21
+ @code = result
24
22
  @options = nil
23
+ else
24
+ @options = result
25
25
  end
26
26
  end
27
27
  end
28
28
 
29
29
  def invoke!
30
- return if @options.nil?
31
-
32
- begin
33
- opts = {}
34
- profile = false
35
- infiles = []
36
- outfile = nil
37
- tofile = nil
38
- @options.map {|k, v|
39
- case k
40
- when :input_files
41
- infiles = v
42
- when :output_file
43
- outfile = v
44
- when :destination_dir
45
- #opts[:to_dir] = File.expand_path(v) unless v.nil?
46
- opts[:to_dir] = v unless v.nil?
47
- when :attributes
48
- opts[:attributes] = v.dup
49
- when :verbose
50
- profile = true if v
51
- when :trace
52
- # currently, nothing
53
- else
54
- opts[k] = v unless v.nil?
55
- end
56
- }
30
+ old_verbose = -1
31
+ return unless @options
57
32
 
58
- if infiles.size == 1 && infiles.first == '-'
59
- # allows use of block to supply stdin, particularly useful for tests
60
- inputs = [block_given? ? yield : STDIN]
61
- else
62
- inputs = infiles.map {|infile| File.new infile}
63
- end
33
+ old_verbose = $VERBOSE
34
+ case @options[:verbose]
35
+ when 0
36
+ $VERBOSE = nil
37
+ when 1
38
+ $VERBOSE = false
39
+ when 2
40
+ $VERBOSE = true
41
+ end
64
42
 
65
- # NOTE: if infile is stdin, default to outfile as stout
66
- if outfile == '-' || (infiles.size == 1 && infiles.first == '-' && outfile.to_s.empty?)
67
- tofile = (@out || $stdout)
68
- elsif !outfile.nil?
69
- tofile = outfile
70
- opts[:mkdirs] = true
43
+ opts = {}
44
+ infiles = []
45
+ outfile = nil
46
+ tofile = nil
47
+ @options.map do |key, val|
48
+ case key
49
+ when :input_files
50
+ infiles = val
51
+ when :output_file
52
+ outfile = val
53
+ when :destination_dir
54
+ opts[:to_dir] = val if val
55
+ when :attributes
56
+ # NOTE processor will dup attributes internally
57
+ opts[:attributes] = val
58
+ when :trace
59
+ # currently, nothing
71
60
  else
72
- tofile = nil
73
- # automatically calculate outfile based on infile
74
- opts[:in_place] = true unless opts.has_key? :to_dir
75
- opts[:mkdirs] = true
61
+ opts[key] = val unless val.nil?
76
62
  end
63
+ end
77
64
 
78
- original_opts = opts
79
- inputs.each do |input|
80
-
81
- opts = Helpers.clone_options(original_opts) if inputs.size > 1
82
- opts[:to_file] = tofile unless tofile.nil?
83
- opts[:monitor] = {} if profile
65
+ if infiles.size == 1 && infiles[0] == '-'
66
+ # allows use of block to supply stdin, particularly useful for tests
67
+ inputs = [block_given? ? yield : STDIN]
68
+ else
69
+ inputs = infiles.map {|infile| ::File.new infile, 'r'}
70
+ end
84
71
 
85
- @documents ||= []
86
- @documents.push Asciidoctor.render(input, opts)
72
+ # NOTE if infile is stdin, default to outfile as stout
73
+ if outfile == '-' || (!outfile && infiles.size == 1 && infiles[0] == '-')
74
+ tofile = (@out || $stdout)
75
+ elsif outfile
76
+ tofile = outfile
77
+ opts[:mkdirs] = true
78
+ else
79
+ # automatically calculate outfile based on infile unless to_dir is set
80
+ tofile = nil
81
+ opts[:mkdirs] = true
82
+ end
87
83
 
88
- if profile
89
- monitor = opts[:monitor]
90
- err = (@err || $stderr)
91
- err.puts "Input file: #{input.respond_to?(:path) ? input.path : '-'}"
92
- err.puts " Time to read and parse source: #{'%05.5f' % monitor[:parse]}"
93
- err.puts " Time to render document: #{monitor.has_key?(:render) ? '%05.5f' % monitor[:render] : 'n/a'}"
94
- err.puts " Total time to read, parse and render: #{'%05.5f' % (monitor[:load_render] || monitor[:parse])}"
95
- end
84
+ show_timings = @options[:timings]
85
+ inputs.each do |input|
86
+ # NOTE processor will dup options and attributes internally
87
+ input_opts = tofile.nil? ? opts : opts.merge(:to_file => tofile)
88
+ if show_timings
89
+ timings = Timings.new
90
+ @documents << ::Asciidoctor.convert(input, input_opts.merge(:timings => timings))
91
+ timings.print_report((@err || $stderr), ((input.respond_to? :path) ? input.path : '-'))
92
+ else
93
+ @documents << ::Asciidoctor.convert(input, input_opts)
94
+ end
95
+ end
96
+ rescue ::Exception => e
97
+ if ::SignalException === e
98
+ @code = e.signo
99
+ # add extra endline if Ctrl+C is used
100
+ (@err || $stderr).puts if ::Interrupt === e
101
+ else
102
+ @code = (e.respond_to? :status) ? e.status : 1
103
+ if @options[:trace]
104
+ raise e
105
+ else
106
+ err = (@err || $stderr)
107
+ err.print %(#{e.class}: ) if ::RuntimeError === e
108
+ err.puts e.message
109
+ err.puts ' Use --trace for backtrace'
96
110
  end
97
- rescue Exception => e
98
- raise e if @options[:trace] || SystemExit === e
99
- err = (@err || $stderr)
100
- err.print "#{e.class}: " if e.class != RuntimeError
101
- err.puts e.message
102
- err.puts ' Use --trace for backtrace'
103
- @code = 1
104
111
  end
112
+ nil
113
+ ensure
114
+ $VERBOSE = old_verbose unless old_verbose == -1
105
115
  end
106
116
 
107
117
  def document
108
- @documents.size > 0 ? @documents.first : nil
118
+ @documents[0]
109
119
  end
110
120
 
111
121
  def redirect_streams(out, err = nil)
@@ -114,11 +124,11 @@ module Asciidoctor
114
124
  end
115
125
 
116
126
  def read_output
117
- !@out.nil? ? @out.string : ''
127
+ @out ? @out.string : ''
118
128
  end
119
129
 
120
130
  def read_error
121
- !@err.nil? ? @err.string : ''
131
+ @err ? @err.string : ''
122
132
  end
123
133
 
124
134
  def reset_streams