asciidoctor 1.5.1 → 1.5.2

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +85 -0
  3. data/README.adoc +79 -6
  4. data/Rakefile +22 -1
  5. data/benchmark/benchmark.rb +3 -1
  6. data/compat/asciidoc.conf +2 -2
  7. data/data/stylesheets/asciidoctor-default.css +3 -3
  8. data/features/step_definitions.rb +12 -1
  9. data/lib/asciidoctor.rb +74 -42
  10. data/lib/asciidoctor/abstract_block.rb +5 -1
  11. data/lib/asciidoctor/abstract_node.rb +40 -23
  12. data/lib/asciidoctor/attribute_list.rb +14 -5
  13. data/lib/asciidoctor/block.rb +45 -12
  14. data/lib/asciidoctor/callouts.rb +1 -0
  15. data/lib/asciidoctor/cli/invoker.rb +6 -2
  16. data/lib/asciidoctor/cli/options.rb +23 -9
  17. data/lib/asciidoctor/converter.rb +9 -19
  18. data/lib/asciidoctor/converter/base.rb +11 -14
  19. data/lib/asciidoctor/converter/composite.rb +8 -19
  20. data/lib/asciidoctor/converter/docbook45.rb +1 -0
  21. data/lib/asciidoctor/converter/docbook5.rb +24 -2
  22. data/lib/asciidoctor/converter/factory.rb +1 -0
  23. data/lib/asciidoctor/converter/html5.rb +61 -25
  24. data/lib/asciidoctor/converter/template.rb +19 -26
  25. data/lib/asciidoctor/document.rb +73 -45
  26. data/lib/asciidoctor/extensions.rb +121 -9
  27. data/lib/asciidoctor/helpers.rb +1 -0
  28. data/lib/asciidoctor/inline.rb +1 -0
  29. data/lib/asciidoctor/list.rb +1 -0
  30. data/lib/asciidoctor/opal_ext.rb +22 -0
  31. data/lib/asciidoctor/opal_ext/file.rb +26 -13
  32. data/lib/asciidoctor/parser.rb +15 -18
  33. data/lib/asciidoctor/path_resolver.rb +18 -0
  34. data/lib/asciidoctor/reader.rb +8 -9
  35. data/lib/asciidoctor/section.rb +5 -8
  36. data/lib/asciidoctor/stylesheets.rb +1 -0
  37. data/lib/asciidoctor/substitutors.rb +18 -18
  38. data/lib/asciidoctor/table.rb +2 -1
  39. data/lib/asciidoctor/timings.rb +1 -0
  40. data/lib/asciidoctor/version.rb +1 -1
  41. data/man/asciidoctor.1 +10 -11
  42. data/man/asciidoctor.adoc +80 -99
  43. data/test/attributes_test.rb +42 -0
  44. data/test/blocks_test.rb +19 -7
  45. data/test/document_test.rb +114 -0
  46. data/test/extensions_test.rb +100 -0
  47. data/test/fixtures/custom-docinfodir/basic-docinfo.html +1 -0
  48. data/test/fixtures/custom-docinfodir/docinfo.html +1 -0
  49. data/test/fixtures/hello-asciidoctor.pdf +0 -0
  50. data/test/invoker_test.rb +4 -3
  51. data/test/lists_test.rb +31 -5
  52. data/test/options_test.rb +1 -1
  53. data/test/paths_test.rb +21 -0
  54. data/test/preamble_test.rb +33 -0
  55. data/test/reader_test.rb +13 -0
  56. data/test/sections_test.rb +22 -0
  57. data/test/substitutions_test.rb +49 -0
  58. data/test/tables_test.rb +76 -0
  59. data/test/test_helper.rb +4 -2
  60. metadata +7 -5
  61. data/lib/asciidoctor/debug.rb +0 -25
@@ -1,3 +1,4 @@
1
+ # encoding: UTF-8
1
2
  module Asciidoctor
2
3
  # Extensions provide a way to participate in the parsing and converting
3
4
  # phases of the AsciiDoc processor or extend the AsciiDoc syntax.
@@ -58,6 +59,8 @@ module Extensions
58
59
  # This method automatically detects whether to use the include or extend keyword
59
60
  # based on what is appropriate.
60
61
  #
62
+ # NOTE Inspiration for this DSL design comes from https://corcoran.io/2013/09/04/simple-pattern-ruby-dsl/
63
+ #
61
64
  # Returns nothing
62
65
  def use_dsl
63
66
  if self.name.nil_or_empty?
@@ -109,7 +112,7 @@ module Extensions
109
112
  def parse_content parent, content, attributes = {}
110
113
  reader = (content.is_a? Reader) ? reader : (Reader.new content)
111
114
  while reader.has_more_lines?
112
- block = Parser.next_block(reader, parent, attributes)
115
+ block = Parser.next_block reader, parent, attributes
113
116
  parent << block if block
114
117
  end
115
118
  nil
@@ -242,6 +245,36 @@ module Extensions
242
245
  end
243
246
  IncludeProcessor::DSL = ProcessorDsl
244
247
 
248
+ # Public: DocinfoProcessors are used to add additional content to
249
+ # the header and/or footer of the generated document.
250
+ #
251
+ # The placement of docinfo content is controlled by the converter.
252
+ #
253
+ # DocinfoProcessors implementations must extend DocinfoProcessor.
254
+ # If a location is not specified, the DocinfoProcessor is assumed
255
+ # to add content to the header.
256
+ class DocinfoProcessor < Processor
257
+ attr_accessor :location
258
+
259
+ def initialize config = {}
260
+ super config
261
+ @config[:location] ||= :header
262
+ end
263
+
264
+ def process document
265
+ raise ::NotImplementedError
266
+ end
267
+ end
268
+
269
+ module DocinfoProcessorDsl
270
+ include ProcessorDsl
271
+
272
+ def at_location value
273
+ option :location, value
274
+ end
275
+ end
276
+ DocinfoProcessor::DSL = DocinfoProcessorDsl
277
+
245
278
  # Public: BlockProcessors are used to handle delimited blocks and paragraphs
246
279
  # that have a custom name.
247
280
  #
@@ -448,7 +481,7 @@ module Extensions
448
481
 
449
482
  def initialize kind, instance, process_method = nil
450
483
  super kind, instance, instance.config
451
- @process_method = process_method || instance.method(:process)
484
+ @process_method = process_method || (instance.method :process)
452
485
  end
453
486
  end
454
487
 
@@ -484,7 +517,7 @@ module Extensions
484
517
 
485
518
  def initialize groups = {}
486
519
  @groups = groups
487
- @preprocessor_extensions = @treeprocessor_extensions = @postprocessor_extensions = @include_processor_extensions = nil
520
+ @preprocessor_extensions = @treeprocessor_extensions = @postprocessor_extensions = @include_processor_extensions = @docinfo_processor_extensions =nil
488
521
  @block_extensions = @block_macro_extensions = @inline_macro_extensions = nil
489
522
  @document = nil
490
523
  end
@@ -723,6 +756,79 @@ module Extensions
723
756
  @include_processor_extensions
724
757
  end
725
758
 
759
+ # Public: Registers an {DocinfoProcessor} with the extension registry to
760
+ # add additionnal docinfo to the document.
761
+ #
762
+ # The DocinfoProcessor may be one of four types:
763
+ #
764
+ # * A DocinfoProcessor subclass
765
+ # * An instance of a DocinfoProcessor subclass
766
+ # * The String name of a DocinfoProcessor subclass
767
+ # * A method block (i.e., Proc) that conforms to the DocinfoProcessor contract
768
+ #
769
+ # Unless the DocinfoProcessor is passed as the method block, it must be the
770
+ # first argument to this method.
771
+ #
772
+ # Examples
773
+ #
774
+ # # as an DocinfoProcessor subclass
775
+ # docinfo_processor MetaRobotsDocinfoProcessor
776
+ #
777
+ # # as an instance of a DocinfoProcessor subclass with an explicit location
778
+ # docinfo_processor JQueryDocinfoProcessor.new, :location => :footer
779
+ #
780
+ # # as a name of a DocinfoProcessor subclass
781
+ # docinfo_processor 'MetaRobotsDocinfoProcessor'
782
+ #
783
+ # # as a method block
784
+ # docinfo_processor do
785
+ # process |doc|
786
+ # at_location :footer
787
+ # 'footer content'
788
+ # end
789
+ # end
790
+ #
791
+ # Returns the [Extension] stored in the registry that proxies the
792
+ # instance of this DocinfoProcessor.
793
+ def docinfo_processor *args, &block
794
+ add_document_processor :docinfo_processor, args, &block
795
+ end
796
+
797
+ # Public: Checks whether any {DocinfoProcessor} extensions have been registered.
798
+ #
799
+ # location - A Symbol for selecting docinfo extensions at a given location (:header or :footer) (default: nil)
800
+ #
801
+ # Returns a [Boolean] indicating whether any DocinfoProcessor extensions are registered.
802
+ def docinfo_processors? location = nil
803
+ if @docinfo_processor_extensions
804
+ if location
805
+ @docinfo_processor_extensions.find {|ext| ext.config[:location] == location }
806
+ else
807
+ true
808
+ end
809
+ else
810
+ false
811
+ end
812
+ end
813
+
814
+ # Public: Retrieves the {Extension} proxy objects for all the
815
+ # DocinfoProcessor instances stored in this registry.
816
+ #
817
+ # location - A Symbol for selecting docinfo extensions at a given location (:header or :footer) (default: nil)
818
+ #
819
+ # Returns an [Array] of Extension proxy objects.
820
+ def docinfo_processors location = nil
821
+ if @docinfo_processor_extensions
822
+ if location
823
+ @docinfo_processor_extensions.select {|ext| ext.config[:location] == location }
824
+ else
825
+ @docinfo_processor_extensions
826
+ end
827
+ else
828
+ nil
829
+ end
830
+ end
831
+
726
832
  # Public: Registers a {BlockProcessor} with the extension registry to
727
833
  # process the block content (i.e., delimited block or paragraph) in the
728
834
  # AsciiDoc source annotated with the specified block name (i.e., style).
@@ -1011,9 +1117,12 @@ module Extensions
1011
1117
  config = resolve_args args, 1
1012
1118
  # TODO if block arity is 0, assume block is process method
1013
1119
  processor = kind_class.new config
1014
- class << processor
1015
- include_dsl
1016
- end
1120
+ # NOTE class << processor idiom doesn't work in Opal
1121
+ #class << processor
1122
+ # include_dsl
1123
+ #end
1124
+ # NOTE kind_class.contants(false) doesn't exist in Ruby 1.8.7
1125
+ processor.extend kind_class.const_get :DSL if kind_class.constants.grep :DSL
1017
1126
  processor.instance_exec(&block)
1018
1127
  processor.freeze
1019
1128
  unless processor.process_block_given?
@@ -1058,9 +1167,12 @@ module Extensions
1058
1167
  if block_given?
1059
1168
  name, config = resolve_args args, 2
1060
1169
  processor = kind_class.new as_symbol(name), config
1061
- class << processor
1062
- include_dsl
1063
- end
1170
+ # NOTE class << processor idiom doesn't work in Opal
1171
+ #class << processor
1172
+ # include_dsl
1173
+ #end
1174
+ # NOTE kind_class.contants(false) doesn't exist in Ruby 1.8.7
1175
+ processor.extend kind_class.const_get :DSL if kind_class.constants.grep :DSL
1064
1176
  if block.arity == 1
1065
1177
  yield processor
1066
1178
  else
@@ -1,3 +1,4 @@
1
+ # encoding: UTF-8
1
2
  module Asciidoctor
2
3
  module Helpers
3
4
  # Internal: Require the specified library using Kernel#require.
@@ -1,3 +1,4 @@
1
+ # encoding: UTF-8
1
2
  module Asciidoctor
2
3
  # Public: Methods for managing inline elements in AsciiDoc block
3
4
  class Inline < AbstractNode
@@ -1,3 +1,4 @@
1
+ # encoding: UTF-8
1
2
  module Asciidoctor
2
3
  # Public: Methods for managing AsciiDoc lists (ordered, unordered and labeled lists)
3
4
  class List < AbstractBlock
@@ -1,3 +1,25 @@
1
+ %x(
2
+ var value;
3
+ if (typeof module !== 'undefined' && module.exports) {
4
+ value = 'node';
5
+ }
6
+ else if (typeof XMLHttpRequest !== 'undefined') {
7
+ // or we can check for document
8
+ //else if (typeof document !== 'undefined' && document.nodeType) {
9
+ value = 'browser';
10
+ }
11
+ else if (typeof Java !== 'undefined' && Java.type) {
12
+ value = 'java-nashorn';
13
+ }
14
+ else if (typeof java !== 'undefined') {
15
+ value = 'java-rhino';
16
+ }
17
+ else {
18
+ // standalone is likely SpiderMonkey
19
+ value = 'standalone';
20
+ }
21
+ )
22
+ JAVASCRIPT_PLATFORM = %x(value)
1
23
  require 'asciidoctor/opal_ext/comparable'
2
24
  require 'asciidoctor/opal_ext/dir'
3
25
  require 'asciidoctor/opal_ext/error'
@@ -44,7 +44,7 @@ class File
44
44
 
45
45
  if block_given?
46
46
  lines = File.read(@path)
47
- %x{
47
+ %x(
48
48
  self.eof = false;
49
49
  self.lineno = 0;
50
50
  var chomped = #{lines.chomp},
@@ -61,7 +61,7 @@ class File
61
61
  }
62
62
  }
63
63
  self.eof = true;
64
- }
64
+ )
65
65
  self
66
66
  else
67
67
  read.each_line
@@ -96,14 +96,22 @@ class File
96
96
  end
97
97
 
98
98
  def self.read(path)
99
- %x{
100
- var data = ''
101
- if (typeof module !== 'undefined' && module.exports) {
102
- // Running under Node.js
103
- var fs = require("fs");
104
- data = fs.readFileSync(path, "utf8");
105
- } else {
106
- // Running under the browser
99
+ case JAVASCRIPT_PLATFORM
100
+ when 'node'
101
+ %x(return require('fs').readFileSync(path, 'utf8');)
102
+ when 'java-nashorn'
103
+ %x(
104
+ var Paths = Java.type('java.nio.file.Paths');
105
+ var Files = Java.type('java.nio.file.Files');
106
+ var lines = Files.readAllLines(Paths.get(path), Java.type('java.nio.charset.StandardCharsets').UTF_8);
107
+ var data = [];
108
+ lines.forEach(function(line) { data.push(line); });
109
+ return data.join("\n");
110
+ )
111
+ #when 'java-rhino'
112
+ when 'browser'
113
+ %x(
114
+ var data = '';
107
115
  var status = -1;
108
116
  try {
109
117
  var xhr = new XMLHttpRequest();
@@ -125,8 +133,13 @@ class File
125
133
  if (status == 404 || (status == 0 && data == '')) {
126
134
  throw #{IOError.new `'No such file or directory: ' + path`};
127
135
  }
128
- }
129
- }
130
- `data`
136
+ return data;
137
+ )
138
+ # NOTE we're assuming standalone is SpiderMonkey
139
+ when 'standalone'
140
+ %x(return read(path);)
141
+ else
142
+ ''
143
+ end
131
144
  end
132
145
  end
@@ -1,3 +1,4 @@
1
+ # encoding: UTF-8
1
2
  module Asciidoctor
2
3
  # Public: Methods to parse lines of AsciiDoc into an object hierarchy
3
4
  # representing the structure of the document. All methods are class methods and
@@ -52,19 +53,6 @@ class Parser
52
53
  new_section, block_attributes = next_section(reader, document, block_attributes)
53
54
  document << new_section if new_section
54
55
  end
55
- # NOTE we could try to avoid creating a preamble in the first place, though
56
- # that would require reworking assumptions in next_section since the preamble
57
- # is treated like an untitled section
58
- # NOTE logic relocated to end of next_section
59
- #if Compliance.unwrap_standalone_preamble &&
60
- # document.blocks.size == 1 && (first_block = document.blocks[0]).context == :preamble &&
61
- # first_block.blocks? && (document.doctype != 'book' || first_block.blocks[0].style != 'abstract')
62
- # preamble = document.blocks.shift
63
- # while (child_block = preamble.blocks.shift)
64
- # child_block.parent = document
65
- # document << child_block
66
- # end
67
- #end
68
56
  end
69
57
 
70
58
  document
@@ -230,6 +218,9 @@ class Parser
230
218
  doctype = parent.doctype
231
219
  if has_header || (doctype == 'book' && attributes[1] != 'abstract')
232
220
  preamble = intro = Block.new(parent, :preamble, :content_model => :compound)
221
+ if doctype == 'book' && (parent.attr? 'preface-title')
222
+ preamble.title = parent.attr 'preface-title'
223
+ end
233
224
  parent << preamble
234
225
  end
235
226
  section = parent
@@ -947,7 +938,7 @@ class Parser
947
938
 
948
939
  if block.sub? :callouts
949
940
  unless (catalog_callouts block.source, document)
950
- # No need to look for callouts if they aren't there
941
+ # No need to sub callouts if they aren't there
951
942
  block.remove_sub :callouts
952
943
  end
953
944
  end
@@ -1144,7 +1135,6 @@ class Parser
1144
1135
  else
1145
1136
  list_block.level = 1
1146
1137
  end
1147
- #Debug.debug { "Created #{list_type} block: #{list_block}" }
1148
1138
 
1149
1139
  while reader.has_more_lines? && (match = ListRxMap[list_type].match(reader.peek_line))
1150
1140
  marker = resolve_list_marker(list_type, match[1])
@@ -2121,6 +2111,8 @@ class Parser
2121
2111
  end
2122
2112
 
2123
2113
  if accessible && attrs
2114
+ # NOTE lookup resolved value (resolution occurs inside set_attribute)
2115
+ value = doc.attributes[name] if value
2124
2116
  Document::AttributeEntry.new(name, value).save_to(attrs)
2125
2117
  end
2126
2118
 
@@ -2324,7 +2316,11 @@ class Parser
2324
2316
  parser_ctx.buffer = %(#{parser_ctx.buffer}#{m.pre_match})
2325
2317
  end
2326
2318
 
2327
- line = m.post_match
2319
+ if (line = m.post_match) == ''
2320
+ # hack to prevent dropping empty cell found at end of line (see issue #1106)
2321
+ seen = false
2322
+ end
2323
+
2328
2324
  parser_ctx.close_cell
2329
2325
  else
2330
2326
  # no other delimiters to see here
@@ -2346,8 +2342,9 @@ class Parser
2346
2342
 
2347
2343
  skipped = table_reader.skip_blank_lines unless parser_ctx.cell_open?
2348
2344
 
2349
- if !table_reader.has_more_lines?
2350
- parser_ctx.close_cell true
2345
+ unless table_reader.has_more_lines?
2346
+ # NOTE may have already closed cell in csv or dsv table (see previous call to parser_ctx.close_cell(true))
2347
+ parser_ctx.close_cell true if parser_ctx.cell_open?
2351
2348
  end
2352
2349
  end
2353
2350
 
@@ -1,3 +1,4 @@
1
+ # encoding: UTF-8
1
2
  module Asciidoctor
2
3
  # Public: Handles all operations for resolving, cleaning and joining paths.
3
4
  # This class includes operations for handling both web paths (request URIs) and
@@ -394,6 +395,9 @@ class PathResolver
394
395
  # The main function of this operation is to resolve any parent
395
396
  # references and remove any self references.
396
397
  #
398
+ # The target is assumed to be a path, not a qualified URI.
399
+ # That check should happen before this method is invoked.
400
+ #
397
401
  # target - the String target path
398
402
  # start - the String start (i.e., parent) path
399
403
  #
@@ -413,6 +417,20 @@ class PathResolver
413
417
  end
414
418
  end
415
419
 
420
+ # use this logic instead if we want to normalize target if it contains a URI
421
+ #unless is_web_root? target
422
+ # if preserve_uri_target && (target.include? ':') && UriSniffRx =~ target
423
+ # uri_prefix = $~[0]
424
+ # target = target[uri_prefix.length..-1]
425
+ # elsif !start.nil_or_empty?
426
+ # target = %(#{start}#{SLASH}#{target})
427
+ # if (target.include? ':') && UriSniffRx =~ target
428
+ # uri_prefix = $~[0]
429
+ # target = target[uri_prefix.length..-1]
430
+ # end
431
+ # end
432
+ #end
433
+
416
434
  target_segments, target_root, _ = partition_path target, true
417
435
  resolved_segments = []
418
436
  target_segments.each do |segment|
@@ -1,3 +1,4 @@
1
+ # encoding: UTF-8
1
2
  module Asciidoctor
2
3
  # Public: Methods for retrieving lines from AsciiDoc source files
3
4
  class Reader
@@ -708,10 +709,10 @@ class PreprocessorReader < Reader
708
709
  @conditional_stack.pop
709
710
  @skipping = @conditional_stack.empty? ? false : @conditional_stack[-1][:skipping]
710
711
  else
711
- warn "asciidoctor: ERROR: #{line_info}: mismatched macro: endif::#{target}[], expected endif::#{pair[:target]}[]"
712
+ warn %(asciidoctor: ERROR: #{line_info}: mismatched macro: endif::#{target}[], expected endif::#{pair[:target]}[])
712
713
  end
713
714
  else
714
- warn "asciidoctor: ERROR: #{line_info}: unmatched macro: endif::#{target}[]"
715
+ warn %(asciidoctor: ERROR: #{line_info}: unmatched macro: endif::#{target}[])
715
716
  end
716
717
  return true
717
718
  end
@@ -858,7 +859,7 @@ class PreprocessorReader < Reader
858
859
  # include file is resolved relative to dir of current include, or base_dir if within original docfile
859
860
  include_file = @document.normalize_system_path(target, @dir, nil, :target_name => 'include file')
860
861
  unless ::File.file? include_file
861
- warn "asciidoctor: WARNING: #{line_info}: include file not found: #{include_file}"
862
+ warn %(asciidoctor: WARNING: #{line_info}: include file not found: #{include_file})
862
863
  replace_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
863
864
  return true
864
865
  end
@@ -891,7 +892,7 @@ class PreprocessorReader < Reader
891
892
  elsif attributes.has_key? 'tag'
892
893
  tags = [attributes['tag']].to_set
893
894
  elsif attributes.has_key? 'tags'
894
- tags = attributes['tags'].split(DataDelimiterRx).uniq.to_set
895
+ tags = attributes['tags'].split(DataDelimiterRx).to_set
895
896
  end
896
897
  end
897
898
  if inc_lines
@@ -966,7 +967,7 @@ class PreprocessorReader < Reader
966
967
  return true
967
968
  end
968
969
  unless (missing_tags = tags.to_a - tags_found.to_a).empty?
969
- warn "asciidoctor: WARNING: #{line_info}: tag#{missing_tags.size > 1 ? 's' : nil} '#{missing_tags * ','}' not found in include #{target_type}: #{include_file}"
970
+ warn %(asciidoctor: WARNING: #{line_info}: tag#{missing_tags.size > 1 ? 's' : nil} '#{missing_tags * ','}' not found in include #{target_type}: #{include_file})
970
971
  end
971
972
  advance
972
973
  # FIXME not accounting for skipped lines in reader line numbering
@@ -1187,13 +1188,11 @@ class PreprocessorReader < Reader
1187
1188
  end
1188
1189
 
1189
1190
  def include_processors?
1190
- if !@include_processor_extensions
1191
+ if @include_processor_extensions.nil?
1191
1192
  if @document.extensions? && @document.extensions.include_processors?
1192
- @include_processor_extensions = @document.extensions.include_processors
1193
- true
1193
+ !!(@include_processor_extensions = @document.extensions.include_processors)
1194
1194
  else
1195
1195
  @include_processor_extensions = false
1196
- false
1197
1196
  end
1198
1197
  else
1199
1198
  @include_processor_extensions != false