asciidoctor 1.5.5 → 1.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of asciidoctor might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +216 -1
- data/CONTRIBUTING.adoc +2 -2
- data/Gemfile +20 -1
- data/LICENSE.adoc +1 -1
- data/README-fr.adoc +4 -3
- data/README-jp.adoc +11 -10
- data/README-zh_CN.adoc +4 -3
- data/README.adoc +17 -202
- data/Rakefile +41 -25
- data/asciidoctor.gemspec +9 -10
- data/data/locale/attributes.adoc +216 -34
- data/data/stylesheets/asciidoctor-default.css +23 -16
- data/features/step_definitions.rb +15 -19
- data/features/xref.feature +584 -20
- data/lib/asciidoctor.rb +292 -278
- data/lib/asciidoctor/abstract_block.rb +155 -94
- data/lib/asciidoctor/abstract_node.rb +108 -94
- data/lib/asciidoctor/attribute_list.rb +30 -22
- data/lib/asciidoctor/block.rb +7 -7
- data/lib/asciidoctor/cli/invoker.rb +47 -34
- data/lib/asciidoctor/cli/options.rb +22 -11
- data/lib/asciidoctor/converter.rb +3 -3
- data/lib/asciidoctor/converter/base.rb +2 -2
- data/lib/asciidoctor/converter/composite.rb +1 -1
- data/lib/asciidoctor/converter/docbook45.rb +2 -2
- data/lib/asciidoctor/converter/docbook5.rb +132 -87
- data/lib/asciidoctor/converter/factory.rb +0 -1
- data/lib/asciidoctor/converter/html5.rb +116 -98
- data/lib/asciidoctor/converter/manpage.rb +51 -52
- data/lib/asciidoctor/converter/template.rb +47 -36
- data/lib/asciidoctor/core_ext.rb +8 -2
- data/lib/asciidoctor/core_ext/1.8.7/hash/key.rb +4 -0
- data/lib/asciidoctor/core_ext/1.8.7/io/binread.rb +6 -0
- data/lib/asciidoctor/core_ext/1.8.7/io/write.rb +5 -0
- data/lib/asciidoctor/core_ext/1.8.7/string/chr.rb +1 -1
- data/lib/asciidoctor/core_ext/1.8.7/string/{limit.rb → limit_bytesize.rb} +7 -6
- data/lib/asciidoctor/core_ext/1.8.7/symbol/empty.rb +6 -0
- data/lib/asciidoctor/core_ext/1.8.7/symbol/length.rb +1 -1
- data/lib/asciidoctor/core_ext/nil_or_empty.rb +5 -5
- data/lib/asciidoctor/core_ext/regexp/is_match.rb +3 -0
- data/lib/asciidoctor/core_ext/string/{limit.rb → limit_bytesize.rb} +2 -2
- data/lib/asciidoctor/document.rb +216 -213
- data/lib/asciidoctor/extensions.rb +318 -185
- data/lib/asciidoctor/helpers.rb +35 -35
- data/lib/asciidoctor/inline.rb +32 -1
- data/lib/asciidoctor/list.rb +22 -6
- data/lib/asciidoctor/parser.rb +1008 -1038
- data/lib/asciidoctor/path_resolver.rb +46 -50
- data/lib/asciidoctor/reader.rb +275 -251
- data/lib/asciidoctor/section.rb +86 -58
- data/lib/asciidoctor/stylesheets.rb +6 -6
- data/lib/asciidoctor/substitutors.rb +567 -649
- data/lib/asciidoctor/table.rb +163 -108
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +18 -16
- data/man/asciidoctor.adoc +15 -13
- data/test/attributes_test.rb +138 -22
- data/test/blocks_test.rb +377 -97
- data/test/converter_test.rb +13 -0
- data/test/document_test.rb +244 -34
- data/test/extensions_test.rb +409 -42
- data/test/fixtures/asciidoc_index.txt +521 -0
- data/test/fixtures/basic-docinfo-footer.html +6 -0
- data/test/fixtures/basic-docinfo-footer.xml +8 -0
- data/test/fixtures/basic-docinfo.html +1 -0
- data/test/fixtures/basic-docinfo.xml +4 -0
- data/test/fixtures/basic.asciidoc +5 -0
- data/test/fixtures/chapter-a.adoc +3 -0
- data/test/fixtures/child-include.adoc +5 -0
- data/test/fixtures/circle.svg +9 -0
- data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +6 -0
- data/test/fixtures/custom-backends/haml/docbook45/block_paragraph.xml.haml +6 -0
- data/test/fixtures/custom-backends/haml/html5-tweaks/block_paragraph.html.haml +1 -0
- data/test/fixtures/custom-backends/haml/html5/block_paragraph.html.haml +3 -0
- data/test/fixtures/custom-backends/haml/html5/block_sidebar.html.haml +5 -0
- data/test/fixtures/custom-backends/slim/docbook45/block_paragraph.xml.slim +6 -0
- data/test/fixtures/custom-backends/slim/html5/block_paragraph.html.slim +3 -0
- data/test/fixtures/custom-backends/slim/html5/block_sidebar.html.slim +5 -0
- data/test/fixtures/custom-docinfodir/basic-docinfo.html +1 -0
- data/test/fixtures/custom-docinfodir/docinfo.html +1 -0
- data/test/fixtures/docinfo-footer.html +1 -0
- data/test/fixtures/docinfo-footer.xml +9 -0
- data/test/fixtures/docinfo.html +1 -0
- data/test/fixtures/docinfo.xml +3 -0
- data/test/fixtures/dot.gif +0 -0
- data/test/fixtures/encoding.asciidoc +13 -0
- data/test/fixtures/grandchild-include.adoc +3 -0
- data/test/fixtures/hello-asciidoctor.pdf +69 -0
- data/test/fixtures/include-file.asciidoc +24 -0
- data/test/fixtures/include-file.ml +3 -0
- data/test/fixtures/include-file.xml +5 -0
- data/test/fixtures/master.adoc +5 -0
- data/test/fixtures/mismatched-end-tag.adoc +7 -0
- data/test/fixtures/parent-include-restricted.adoc +5 -0
- data/test/fixtures/parent-include.adoc +5 -0
- data/test/fixtures/sample.asciidoc +26 -0
- data/test/fixtures/stylesheets/custom.css +3 -0
- data/test/fixtures/subs-docinfo.html +2 -0
- data/test/fixtures/subs.adoc +7 -0
- data/test/fixtures/tagged-class-enclosed.rb +26 -0
- data/test/fixtures/tagged-class.rb +23 -0
- data/test/fixtures/tip.gif +0 -0
- data/test/invoker_test.rb +82 -4
- data/test/links_test.rb +312 -37
- data/test/lists_test.rb +204 -25
- data/test/manpage_test.rb +191 -4
- data/test/options_test.rb +18 -1
- data/test/paragraphs_test.rb +32 -7
- data/test/parser_test.rb +150 -30
- data/test/paths_test.rb +47 -13
- data/test/preamble_test.rb +1 -1
- data/test/reader_test.rb +366 -126
- data/test/sections_test.rb +203 -56
- data/test/substitutions_test.rb +339 -131
- data/test/tables_test.rb +315 -15
- data/test/test_helper.rb +400 -0
- data/test/text_test.rb +5 -5
- metadata +110 -22
| @@ -128,28 +128,27 @@ class PathResolver | |
| 128 128 | 
             
                else
         | 
| 129 129 | 
             
                  @working_dir = ::File.expand_path ::Dir.pwd
         | 
| 130 130 | 
             
                end
         | 
| 131 | 
            -
                @_partition_path_sys = {}
         | 
| 132 | 
            -
                @_partition_path_web = {}
         | 
| 131 | 
            +
                @_partition_path_sys, @_partition_path_web = {}, {}
         | 
| 133 132 | 
             
              end
         | 
| 134 133 |  | 
| 135 | 
            -
              # Public: Check if the specified path is an absolute root path
         | 
| 136 | 
            -
              # This operation  | 
| 134 | 
            +
              # Public: Check if the specified path is an absolute root path.
         | 
| 135 | 
            +
              # This operation considers posix paths, windows paths, and file URLs.
         | 
| 136 | 
            +
              #
         | 
| 137 | 
            +
              # Unix absolute paths and UNC paths start with slash. Windows roots can start
         | 
| 138 | 
            +
              # with a drive letter. Absolute paths in the browser start with file://.
         | 
| 137 139 | 
             
              #
         | 
| 138 140 | 
             
              # path - the String path to check
         | 
| 139 141 | 
             
              #
         | 
| 140 142 | 
             
              # returns a Boolean indicating whether the path is an absolute root path
         | 
| 141 | 
            -
               | 
| 142 | 
            -
                 | 
| 143 | 
            -
             | 
| 144 | 
            -
             | 
| 145 | 
            -
             | 
| 146 | 
            -
                 | 
| 147 | 
            -
             | 
| 148 | 
            -
                 | 
| 149 | 
            -
             | 
| 150 | 
            -
                  true
         | 
| 151 | 
            -
                else
         | 
| 152 | 
            -
                  false
         | 
| 143 | 
            +
              if RUBY_ENGINE == 'opal'
         | 
| 144 | 
            +
                def is_root? path
         | 
| 145 | 
            +
                  (path.start_with? SLASH) ||
         | 
| 146 | 
            +
                      (::JAVASCRIPT_IO_MODULE == 'xmlhttprequest' && (path.start_with? 'file://')) ||
         | 
| 147 | 
            +
                      (@file_separator == BACKSLASH && (WindowsRootRx.match? path))
         | 
| 148 | 
            +
                end
         | 
| 149 | 
            +
              else
         | 
| 150 | 
            +
                def is_root? path
         | 
| 151 | 
            +
                  (path.start_with? SLASH) || (@file_separator == BACKSLASH && (WindowsRootRx.match? path))
         | 
| 153 152 | 
             
                end
         | 
| 154 153 | 
             
              end
         | 
| 155 154 |  | 
| @@ -176,7 +175,7 @@ class PathResolver | |
| 176 175 | 
             
              # path - the String path to normalize
         | 
| 177 176 | 
             
              #
         | 
| 178 177 | 
             
              # returns a String path with any backslashes replaced with forward slashes
         | 
| 179 | 
            -
              def  | 
| 178 | 
            +
              def posixify path
         | 
| 180 179 | 
             
                if path.nil_or_empty?
         | 
| 181 180 | 
             
                  ''
         | 
| 182 181 | 
             
                elsif path.include? BACKSLASH
         | 
| @@ -185,6 +184,7 @@ class PathResolver | |
| 185 184 | 
             
                  path
         | 
| 186 185 | 
             
                end
         | 
| 187 186 | 
             
              end
         | 
| 187 | 
            +
              alias posixfy posixify
         | 
| 188 188 |  | 
| 189 189 | 
             
              # Public: Expand the path by resolving any parent references (..)
         | 
| 190 190 | 
             
              # and cleaning self references (.).
         | 
| @@ -206,23 +206,23 @@ class PathResolver | |
| 206 206 | 
             
              # or segments that are self references (.). The path is converted to a posix
         | 
| 207 207 | 
             
              # path before being partitioned.
         | 
| 208 208 | 
             
              #
         | 
| 209 | 
            -
              # path | 
| 210 | 
            -
              #  | 
| 211 | 
            -
              # | 
| 209 | 
            +
              # path - the String path to partition
         | 
| 210 | 
            +
              # web  - a Boolean indicating whether the path should be handled
         | 
| 211 | 
            +
              #        as a web path (optional, default: false)
         | 
| 212 212 | 
             
              #
         | 
| 213 213 | 
             
              # Returns a 3-item Array containing the Array of String path segments, the
         | 
| 214 214 | 
             
              # path root (e.g., '/', './', 'c:/') if the path is absolute and the posix
         | 
| 215 215 | 
             
              # version of the path.
         | 
| 216 216 | 
             
              #--
         | 
| 217 217 | 
             
              # QUESTION is it worth it to normalize slashes? it doubles the time elapsed
         | 
| 218 | 
            -
              def partition_path path,  | 
| 219 | 
            -
                if (result =  | 
| 218 | 
            +
              def partition_path path, web = nil
         | 
| 219 | 
            +
                if (result = (cache = web ? @_partition_path_web : @_partition_path_sys)[path])
         | 
| 220 220 | 
             
                  return result
         | 
| 221 221 | 
             
                end
         | 
| 222 222 |  | 
| 223 | 
            -
                posix_path =  | 
| 223 | 
            +
                posix_path = posixify path
         | 
| 224 224 |  | 
| 225 | 
            -
                root = if  | 
| 225 | 
            +
                root = if web
         | 
| 226 226 | 
             
                  # ex. /sample/path
         | 
| 227 227 | 
             
                  if is_web_root? posix_path
         | 
| 228 228 | 
             
                    SLASH
         | 
| @@ -241,9 +241,9 @@ class PathResolver | |
| 241 241 | 
             
                    # ex. /sample/path
         | 
| 242 242 | 
             
                    elsif posix_path.start_with? SLASH
         | 
| 243 243 | 
             
                      SLASH
         | 
| 244 | 
            -
                    # ex.  | 
| 244 | 
            +
                    # ex. C:/sample/path (or file:///sample/path in browser environment)
         | 
| 245 245 | 
             
                    else
         | 
| 246 | 
            -
                      posix_path | 
| 246 | 
            +
                      posix_path.slice 0, (posix_path.index SLASH) + 1
         | 
| 247 247 | 
             
                    end
         | 
| 248 248 | 
             
                  # ex. ./sample/path
         | 
| 249 249 | 
             
                  elsif posix_path.start_with? DOT_SLASH
         | 
| @@ -260,7 +260,7 @@ class PathResolver | |
| 260 260 | 
             
                  path_segments = path_segments[2..-1]
         | 
| 261 261 | 
             
                # shift twice for a file:/// path and adjust root
         | 
| 262 262 | 
             
                # NOTE technically file:/// paths work without this adjustment
         | 
| 263 | 
            -
                #elsif ::RUBY_ENGINE_OPAL && :: | 
| 263 | 
            +
                #elsif ::RUBY_ENGINE_OPAL && ::JAVASCRIPT_IO_MODULE == 'xmlhttprequest' && root == 'file:/'
         | 
| 264 264 | 
             
                #  root = 'file://'
         | 
| 265 265 | 
             
                #  path_segments = path_segments[2..-1]
         | 
| 266 266 | 
             
                # shift once for any other root
         | 
| @@ -269,9 +269,9 @@ class PathResolver | |
| 269 269 | 
             
                end
         | 
| 270 270 | 
             
                # strip out all dot entries
         | 
| 271 271 | 
             
                path_segments.delete DOT
         | 
| 272 | 
            -
                # QUESTION should we  | 
| 273 | 
            -
                #posix_path = posix_path. | 
| 274 | 
            -
                 | 
| 272 | 
            +
                # QUESTION should we chop trailing /? (we pay a small fraction)
         | 
| 273 | 
            +
                #posix_path = posix_path.chop if posix_path.end_with? SLASH
         | 
| 274 | 
            +
                cache[path] = [path_segments, root, posix_path]
         | 
| 275 275 | 
             
              end
         | 
| 276 276 |  | 
| 277 277 | 
             
              # Public: Join the segments using the posix file separator (since Ruby knows
         | 
| @@ -285,11 +285,7 @@ class PathResolver | |
| 285 285 | 
             
              # returns a String path formed by joining the segments using the posix file
         | 
| 286 286 | 
             
              # separator and prepending the root, if specified
         | 
| 287 287 | 
             
              def join_path segments, root = nil
         | 
| 288 | 
            -
                 | 
| 289 | 
            -
                  %(#{root}#{segments * SLASH})
         | 
| 290 | 
            -
                else
         | 
| 291 | 
            -
                  segments * SLASH
         | 
| 292 | 
            -
                end
         | 
| 288 | 
            +
                root ? %(#{root}#{segments * SLASH}) : segments * SLASH
         | 
| 293 289 | 
             
              end
         | 
| 294 290 |  | 
| 295 291 | 
             
              # Public: Resolve a system path from the target and start paths. If a jail
         | 
| @@ -300,8 +296,8 @@ class PathResolver | |
| 300 296 | 
             
              # specified in the constructor.
         | 
| 301 297 | 
             
              #
         | 
| 302 298 | 
             
              # target - the String target path
         | 
| 303 | 
            -
              # start  - the String start (i.e., parent) path
         | 
| 304 | 
            -
              # jail   - the String jail path to confine the resolved path
         | 
| 299 | 
            +
              # start  - the String start (i.e., parent) path (default: nil)
         | 
| 300 | 
            +
              # jail   - the String jail path to confine the resolved path (default: nil)
         | 
| 305 301 | 
             
              # opts   - an optional Hash of options to control processing (default: {}):
         | 
| 306 302 | 
             
              #          * :recover is used to control whether the processor should auto-recover
         | 
| 307 303 | 
             
              #              when an illegal path is encountered
         | 
| @@ -310,12 +306,12 @@ class PathResolver | |
| 310 306 | 
             
              # returns a String path that joins the target path with the start path with
         | 
| 311 307 | 
             
              # any parent references resolved and self references removed and enforces
         | 
| 312 308 | 
             
              # that the resolved path be contained within the jail, if provided
         | 
| 313 | 
            -
              def system_path target, start, jail = nil, opts = {}
         | 
| 309 | 
            +
              def system_path target, start = nil, jail = nil, opts = {}
         | 
| 314 310 | 
             
                if jail
         | 
| 315 311 | 
             
                  unless is_root? jail
         | 
| 316 312 | 
             
                    raise ::SecurityError, %(Jail is not an absolute path: #{jail})
         | 
| 317 313 | 
             
                  end
         | 
| 318 | 
            -
                  jail =  | 
| 314 | 
            +
                  jail = posixify jail
         | 
| 319 315 | 
             
                end
         | 
| 320 316 |  | 
| 321 317 | 
             
                if target.nil_or_empty?
         | 
| @@ -348,7 +344,7 @@ class PathResolver | |
| 348 344 | 
             
                if start.nil_or_empty?
         | 
| 349 345 | 
             
                  start = jail ? jail : @working_dir
         | 
| 350 346 | 
             
                elsif is_root? start
         | 
| 351 | 
            -
                  start =  | 
| 347 | 
            +
                  start = posixify start
         | 
| 352 348 | 
             
                else
         | 
| 353 349 | 
             
                  start = system_path start, jail, jail, opts
         | 
| 354 350 | 
             
                end
         | 
| @@ -379,7 +375,7 @@ class PathResolver | |
| 379 375 | 
             
                target_segments.each do |segment|
         | 
| 380 376 | 
             
                  if segment == DOT_DOT
         | 
| 381 377 | 
             
                    if jail
         | 
| 382 | 
            -
                      if resolved_segments. | 
| 378 | 
            +
                      if resolved_segments.size > jail_segments.size
         | 
| 383 379 | 
             
                        resolved_segments.pop
         | 
| 384 380 | 
             
                      elsif !(recover ||= (opts.fetch :recover, true))
         | 
| 385 381 | 
             
                        raise ::SecurityError, %(#{opts[:target_name] || 'path'} #{target} refers to location outside jail: #{jail} (disallowed in safe mode))
         | 
| @@ -391,7 +387,7 @@ class PathResolver | |
| 391 387 | 
             
                      resolved_segments.pop
         | 
| 392 388 | 
             
                    end
         | 
| 393 389 | 
             
                  else
         | 
| 394 | 
            -
                    resolved_segments | 
| 390 | 
            +
                    resolved_segments << segment
         | 
| 395 391 | 
             
                  end
         | 
| 396 392 | 
             
                end
         | 
| 397 393 |  | 
| @@ -412,12 +408,12 @@ class PathResolver | |
| 412 408 | 
             
              # start path with any parent references resolved and self
         | 
| 413 409 | 
             
              # references removed
         | 
| 414 410 | 
             
              def web_path target, start = nil
         | 
| 415 | 
            -
                target =  | 
| 416 | 
            -
                start =  | 
| 411 | 
            +
                target = posixify target
         | 
| 412 | 
            +
                start = posixify start
         | 
| 417 413 | 
             
                uri_prefix = nil
         | 
| 418 414 |  | 
| 419 415 | 
             
                unless start.nil_or_empty? || (is_web_root? target)
         | 
| 420 | 
            -
                  target = %(#{start | 
| 416 | 
            +
                  target = (start.end_with? SLASH) ? %(#{start}#{target}) : %(#{start}#{SLASH}#{target})
         | 
| 421 417 | 
             
                  if (uri_prefix = Helpers.uri_prefix target)
         | 
| 422 418 | 
             
                    target = target[uri_prefix.length..-1]
         | 
| 423 419 | 
             
                  end
         | 
| @@ -453,11 +449,11 @@ class PathResolver | |
| 453 449 | 
             
                  end
         | 
| 454 450 | 
             
                end
         | 
| 455 451 |  | 
| 456 | 
            -
                if  | 
| 457 | 
            -
                   | 
| 458 | 
            -
                else
         | 
| 459 | 
            -
                  join_path resolved_segments, target_root
         | 
| 452 | 
            +
                if (resolved_path = join_path resolved_segments, target_root).include? ' '
         | 
| 453 | 
            +
                  resolved_path = resolved_path.gsub ' ', '%20'
         | 
| 460 454 | 
             
                end
         | 
| 455 | 
            +
             | 
| 456 | 
            +
                uri_prefix ? %(#{uri_prefix}#{resolved_path}) : resolved_path
         | 
| 461 457 | 
             
              end
         | 
| 462 458 |  | 
| 463 459 | 
             
              # Public: Calculate the relative path to this absolute filename from the specified base directory
         | 
| @@ -470,7 +466,7 @@ class PathResolver | |
| 470 466 | 
             
              # Return the relative path String of the filename calculated from the base directory
         | 
| 471 467 | 
             
              def relative_path filename, base_directory
         | 
| 472 468 | 
             
                if (is_root? filename) && (is_root? base_directory)
         | 
| 473 | 
            -
                  offset = base_directory. | 
| 469 | 
            +
                  offset = (base_directory.end_with? @file_separator) ? base_directory.length : base_directory.length + 1
         | 
| 474 470 | 
             
                  filename[offset..-1]
         | 
| 475 471 | 
             
                else
         | 
| 476 472 | 
             
                  filename
         | 
    
        data/lib/asciidoctor/reader.rb
    CHANGED
    
    | @@ -19,7 +19,7 @@ class Reader | |
| 19 19 | 
             
                  %(#{path}: line #{lineno})
         | 
| 20 20 | 
             
                end
         | 
| 21 21 |  | 
| 22 | 
            -
                alias  | 
| 22 | 
            +
                alias to_s line_info
         | 
| 23 23 | 
             
              end
         | 
| 24 24 |  | 
| 25 25 | 
             
              attr_reader :file
         | 
| @@ -36,7 +36,7 @@ class Reader | |
| 36 36 | 
             
              attr_accessor :process_lines
         | 
| 37 37 |  | 
| 38 38 | 
             
              # Public: Initialize the Reader object
         | 
| 39 | 
            -
              def initialize data = nil, cursor = nil, opts = { | 
| 39 | 
            +
              def initialize data = nil, cursor = nil, opts = {}
         | 
| 40 40 | 
             
                if !cursor
         | 
| 41 41 | 
             
                  @file = @dir = nil
         | 
| 42 42 | 
             
                  @path = '<stdin>'
         | 
| @@ -88,7 +88,7 @@ class Reader | |
| 88 88 | 
             
                  if opts[:normalize]
         | 
| 89 89 | 
             
                    Helpers.normalize_lines_from_string data
         | 
| 90 90 | 
             
                  else
         | 
| 91 | 
            -
                    data.split  | 
| 91 | 
            +
                    data.split LF, -1
         | 
| 92 92 | 
             
                  end
         | 
| 93 93 | 
             
                else
         | 
| 94 94 | 
             
                  if opts[:normalize]
         | 
| @@ -133,16 +133,19 @@ class Reader | |
| 133 133 | 
             
                peek_line.nil_or_empty?
         | 
| 134 134 | 
             
              end
         | 
| 135 135 |  | 
| 136 | 
            -
              # Public: Peek at the next line of source data. Processes the line | 
| 136 | 
            +
              # Public: Peek at the next line of source data. Processes the line if not
         | 
| 137 137 | 
             
              # already marked as processed, but does not consume it.
         | 
| 138 138 | 
             
              #
         | 
| 139 139 | 
             
              # This method will probe the reader for more lines. If there is a next line
         | 
| 140 140 | 
             
              # that has not previously been visited, the line is passed to the
         | 
| 141 141 | 
             
              # Reader#process_line method to be initialized. This call gives
         | 
| 142 | 
            -
              # sub- | 
| 142 | 
            +
              # sub-classes the opportunity to do preprocessing. If the return value of
         | 
| 143 143 | 
             
              # the Reader#process_line is nil, the data is assumed to be changed and
         | 
| 144 144 | 
             
              # Reader#peek_line is invoked again to perform further processing.
         | 
| 145 145 | 
             
              #
         | 
| 146 | 
            +
              # If has_more_lines? is called immediately before peek_line, the direct flag
         | 
| 147 | 
            +
              # is implicitly true (since the line is flagged as visited).
         | 
| 148 | 
            +
              #
         | 
| 146 149 | 
             
              # direct  - A Boolean flag to bypasses the check for more lines and immediately
         | 
| 147 150 | 
             
              #           returns the first element of the internal @lines Array. (default: false)
         | 
| 148 151 | 
             
              #
         | 
| @@ -167,7 +170,7 @@ class Reader | |
| 167 170 | 
             
                end
         | 
| 168 171 | 
             
              end
         | 
| 169 172 |  | 
| 170 | 
            -
              # Public: Peek at the next multiple lines of source data. Processes the lines | 
| 173 | 
            +
              # Public: Peek at the next multiple lines of source data. Processes the lines if not
         | 
| 171 174 | 
             
              # already marked as processed, but does not consume them.
         | 
| 172 175 | 
             
              #
         | 
| 173 176 | 
             
              # This method delegates to Reader#read_line to process and collect the line, then
         | 
| @@ -175,7 +178,7 @@ class Reader | |
| 175 178 | 
             
              # be processed and marked as such so that subsequent reads will not need to process
         | 
| 176 179 | 
             
              # the lines again.
         | 
| 177 180 | 
             
              #
         | 
| 178 | 
            -
              # num    - The Integer number of lines to peek.
         | 
| 181 | 
            +
              # num    - The positive Integer number of lines to peek (must be greater than 0).
         | 
| 179 182 | 
             
              # direct - A Boolean indicating whether processing should be disabled when reading lines
         | 
| 180 183 | 
             
              #
         | 
| 181 184 | 
             
              # Returns A String Array of the next multiple lines of source data, or an empty Array
         | 
| @@ -187,12 +190,13 @@ class Reader | |
| 187 190 | 
             
                  if (line = read_line direct)
         | 
| 188 191 | 
             
                    result << line
         | 
| 189 192 | 
             
                  else
         | 
| 193 | 
            +
                    @lineno -= 1 if direct
         | 
| 190 194 | 
             
                    break
         | 
| 191 195 | 
             
                  end
         | 
| 192 196 | 
             
                end
         | 
| 193 197 |  | 
| 194 198 | 
             
                unless result.empty?
         | 
| 195 | 
            -
                  result | 
| 199 | 
            +
                  unshift_all result
         | 
| 196 200 | 
             
                  @look_ahead = old_look_ahead if direct
         | 
| 197 201 | 
             
                end
         | 
| 198 202 |  | 
| @@ -229,7 +233,7 @@ class Reader | |
| 229 233 | 
             
                end
         | 
| 230 234 | 
             
                lines
         | 
| 231 235 | 
             
              end
         | 
| 232 | 
            -
              alias  | 
| 236 | 
            +
              alias readlines read_lines
         | 
| 233 237 |  | 
| 234 238 | 
             
              # Public: Get the remaining lines of source data joined as a String.
         | 
| 235 239 | 
             
              #
         | 
| @@ -237,7 +241,7 @@ class Reader | |
| 237 241 | 
             
              #
         | 
| 238 242 | 
             
              # Returns the lines read joined as a String
         | 
| 239 243 | 
             
              def read
         | 
| 240 | 
            -
                read_lines *  | 
| 244 | 
            +
                read_lines * LF
         | 
| 241 245 | 
             
              end
         | 
| 242 246 |  | 
| 243 247 | 
             
              # Public: Advance to the next line by discarding the line at the front of the stack
         | 
| @@ -252,8 +256,10 @@ class Reader | |
| 252 256 |  | 
| 253 257 | 
             
              # Public: Push the String line onto the beginning of the Array of source data.
         | 
| 254 258 | 
             
              #
         | 
| 255 | 
            -
              #  | 
| 256 | 
            -
              #  | 
| 259 | 
            +
              # A line pushed on the reader using this method is not processed again. The
         | 
| 260 | 
            +
              # method assumes the line was previously retrieved from the reader or does
         | 
| 261 | 
            +
              # not otherwise contain preprocessor directives. Therefore, it is marked as
         | 
| 262 | 
            +
              # processed immediately.
         | 
| 257 263 | 
             
              #
         | 
| 258 264 | 
             
              # line_to_restore - the line to restore onto the stack
         | 
| 259 265 | 
             
              #
         | 
| @@ -262,20 +268,21 @@ class Reader | |
| 262 268 | 
             
                unshift line_to_restore
         | 
| 263 269 | 
             
                nil
         | 
| 264 270 | 
             
              end
         | 
| 265 | 
            -
              alias  | 
| 271 | 
            +
              alias restore_line unshift_line
         | 
| 266 272 |  | 
| 267 273 | 
             
              # Public: Push an Array of lines onto the front of the Array of source data.
         | 
| 268 274 | 
             
              #
         | 
| 269 | 
            -
              #  | 
| 270 | 
            -
              #  | 
| 275 | 
            +
              # Lines pushed on the reader using this method are not processed again. The
         | 
| 276 | 
            +
              # method assumes the lines were previously retrieved from the reader or do
         | 
| 277 | 
            +
              # not otherwise contain preprocessor directives. Therefore, they are marked
         | 
| 278 | 
            +
              # as processed immediately.
         | 
| 271 279 | 
             
              #
         | 
| 272 280 | 
             
              # Returns nothing.
         | 
| 273 281 | 
             
              def unshift_lines lines_to_restore
         | 
| 274 | 
            -
                 | 
| 275 | 
            -
                lines_to_restore.reverse_each {|line| unshift line }
         | 
| 282 | 
            +
                unshift_all lines_to_restore
         | 
| 276 283 | 
             
                nil
         | 
| 277 284 | 
             
              end
         | 
| 278 | 
            -
              alias  | 
| 285 | 
            +
              alias restore_lines unshift_lines
         | 
| 279 286 |  | 
| 280 287 | 
             
              # Public: Replace the next line with the specified line.
         | 
| 281 288 | 
             
              #
         | 
| @@ -292,7 +299,7 @@ class Reader | |
| 292 299 | 
             
                nil
         | 
| 293 300 | 
             
              end
         | 
| 294 301 | 
             
              # deprecated
         | 
| 295 | 
            -
              alias  | 
| 302 | 
            +
              alias replace_line replace_next_line
         | 
| 296 303 |  | 
| 297 304 | 
             
              # Public: Strip off leading blank lines in the Array of lines.
         | 
| 298 305 | 
             
              #
         | 
| @@ -346,10 +353,10 @@ class Reader | |
| 346 353 | 
             
                while (next_line = peek_line)
         | 
| 347 354 | 
             
                  if include_blank_lines && next_line.empty?
         | 
| 348 355 | 
             
                    comment_lines << shift
         | 
| 349 | 
            -
                  elsif (commentish = next_line.start_with?('//')) && ( | 
| 356 | 
            +
                  elsif (commentish = next_line.start_with?('//')) && (CommentBlockRx.match? next_line)
         | 
| 350 357 | 
             
                    comment_lines << shift
         | 
| 351 | 
            -
                    comment_lines.push(*(read_lines_until(:terminator =>  | 
| 352 | 
            -
                  elsif commentish && CommentLineRx  | 
| 358 | 
            +
                    comment_lines.push(*(read_lines_until(:terminator => next_line, :read_last_line => true, :skip_processing => true)))
         | 
| 359 | 
            +
                  elsif commentish && (CommentLineRx.match? next_line)
         | 
| 353 360 | 
             
                    comment_lines << shift
         | 
| 354 361 | 
             
                  else
         | 
| 355 362 | 
             
                    break
         | 
| @@ -366,7 +373,7 @@ class Reader | |
| 366 373 | 
             
                comment_lines = []
         | 
| 367 374 | 
             
                # optimized code for shortest execution path
         | 
| 368 375 | 
             
                while (next_line = peek_line)
         | 
| 369 | 
            -
                  if CommentLineRx  | 
| 376 | 
            +
                  if CommentLineRx.match? next_line
         | 
| 370 377 | 
             
                    comment_lines << shift
         | 
| 371 378 | 
             
                  else
         | 
| 372 379 | 
             
                    break
         | 
| @@ -393,7 +400,7 @@ class Reader | |
| 393 400 | 
             
              def eof?
         | 
| 394 401 | 
             
                !has_more_lines?
         | 
| 395 402 | 
             
              end
         | 
| 396 | 
            -
              alias  | 
| 403 | 
            +
              alias empty? eof?
         | 
| 397 404 |  | 
| 398 405 | 
             
              # Public: Return all the lines from `@lines` until we (1) run out them,
         | 
| 399 406 | 
             
              #   (2) find a blank line with :break_on_blank_lines => true, or (3) find
         | 
| @@ -470,7 +477,7 @@ class Reader | |
| 470 477 | 
             
                      line_restored = true
         | 
| 471 478 | 
             
                    end
         | 
| 472 479 | 
             
                  else
         | 
| 473 | 
            -
                    unless skip_comments && line.start_with? | 
| 480 | 
            +
                    unless skip_comments && (line.start_with? '//') && (CommentLineRx.match? line)
         | 
| 474 481 | 
             
                      result << line
         | 
| 475 482 | 
             
                      line_read = true
         | 
| 476 483 | 
             
                    end
         | 
| @@ -504,6 +511,14 @@ class Reader | |
| 504 511 | 
             
                @lines.unshift line
         | 
| 505 512 | 
             
              end
         | 
| 506 513 |  | 
| 514 | 
            +
              # Internal: Restore the lines to the stack and decrement the lineno
         | 
| 515 | 
            +
              def unshift_all lines
         | 
| 516 | 
            +
                @lineno -= lines.size
         | 
| 517 | 
            +
                @look_ahead += lines.size
         | 
| 518 | 
            +
                @eof = false
         | 
| 519 | 
            +
                @lines.unshift(*lines)
         | 
| 520 | 
            +
              end
         | 
| 521 | 
            +
             | 
| 507 522 | 
             
              def cursor
         | 
| 508 523 | 
             
                Cursor.new @file, @dir, @path, @lineno
         | 
| 509 524 | 
             
              end
         | 
| @@ -514,7 +529,7 @@ class Reader | |
| 514 529 | 
             
              def line_info
         | 
| 515 530 | 
             
                %(#{@path}: line #{@lineno})
         | 
| 516 531 | 
             
              end
         | 
| 517 | 
            -
              alias  | 
| 532 | 
            +
              alias next_line_info line_info
         | 
| 518 533 |  | 
| 519 534 | 
             
              def prev_line_info
         | 
| 520 535 | 
             
                %(#{@path}: line #{@lineno - 1})
         | 
| @@ -529,12 +544,12 @@ class Reader | |
| 529 544 |  | 
| 530 545 | 
             
              # Public: Get a copy of the remaining lines managed by this Reader joined as a String
         | 
| 531 546 | 
             
              def string
         | 
| 532 | 
            -
                @lines *  | 
| 547 | 
            +
                @lines * LF
         | 
| 533 548 | 
             
              end
         | 
| 534 549 |  | 
| 535 550 | 
             
              # Public: Get the source lines for this Reader joined as a String
         | 
| 536 551 | 
             
              def source
         | 
| 537 | 
            -
                @source_lines *  | 
| 552 | 
            +
                @source_lines * LF
         | 
| 538 553 | 
             
              end
         | 
| 539 554 |  | 
| 540 555 | 
             
              # Public: Get a summary of this Reader.
         | 
| @@ -553,15 +568,15 @@ class PreprocessorReader < Reader | |
| 553 568 | 
             
              attr_reader :includes
         | 
| 554 569 |  | 
| 555 570 | 
             
              # Public: Initialize the PreprocessorReader object
         | 
| 556 | 
            -
              def initialize document, data = nil, cursor = nil
         | 
| 571 | 
            +
              def initialize document, data = nil, cursor = nil, opts = {}
         | 
| 557 572 | 
             
                @document = document
         | 
| 558 | 
            -
                super data, cursor,  | 
| 573 | 
            +
                super data, cursor, opts
         | 
| 559 574 | 
             
                include_depth_default = document.attributes.fetch('max-include-depth', 64).to_i
         | 
| 560 575 | 
             
                include_depth_default = 0 if include_depth_default < 0
         | 
| 561 576 | 
             
                # track both absolute depth for comparing to size of include stack and relative depth for reporting
         | 
| 562 577 | 
             
                @maxdepth = {:abs => include_depth_default, :rel => include_depth_default}
         | 
| 563 578 | 
             
                @include_stack = []
         | 
| 564 | 
            -
                @includes =  | 
| 579 | 
            +
                @includes = document.catalog[:includes]
         | 
| 565 580 | 
             
                @skipping = false
         | 
| 566 581 | 
             
                @conditional_stack = []
         | 
| 567 582 | 
             
                @include_processor_extensions = nil
         | 
| @@ -571,9 +586,9 @@ class PreprocessorReader < Reader | |
| 571 586 | 
             
                result = super
         | 
| 572 587 |  | 
| 573 588 | 
             
                # QUESTION should this work for AsciiDoc table cell content? Currently it does not.
         | 
| 574 | 
            -
                if @document && (@document.attributes. | 
| 589 | 
            +
                if @document && (@document.attributes.key? 'skip-front-matter')
         | 
| 575 590 | 
             
                  if (front_matter = skip_front_matter! result)
         | 
| 576 | 
            -
                    @document.attributes['front-matter'] = front_matter *  | 
| 591 | 
            +
                    @document.attributes['front-matter'] = front_matter * LF
         | 
| 577 592 | 
             
                  end
         | 
| 578 593 | 
             
                end
         | 
| 579 594 |  | 
| @@ -599,45 +614,41 @@ class PreprocessorReader < Reader | |
| 599 614 |  | 
| 600 615 | 
             
                # NOTE highly optimized
         | 
| 601 616 | 
             
                if line.end_with?(']') && !line.start_with?('[') && line.include?('::')
         | 
| 602 | 
            -
                  if line.include? | 
| 617 | 
            +
                  if (line.include? 'if') && ConditionalDirectiveRx =~ line
         | 
| 603 618 | 
             
                    # if escaped, mark as processed and return line unescaped
         | 
| 604 | 
            -
                    if  | 
| 619 | 
            +
                    if $1 == '\\'
         | 
| 605 620 | 
             
                      @unescape_next_line = true
         | 
| 606 621 | 
             
                      @look_ahead += 1
         | 
| 607 622 | 
             
                      line[1..-1]
         | 
| 623 | 
            +
                    elsif preprocess_conditional_directive $2, $3, $4, $5
         | 
| 624 | 
            +
                      # move the pointer past the conditional line
         | 
| 625 | 
            +
                      advance
         | 
| 626 | 
            +
                      # treat next line as uncharted territory
         | 
| 627 | 
            +
                      nil
         | 
| 608 628 | 
             
                    else
         | 
| 609 | 
            -
                       | 
| 610 | 
            -
             | 
| 611 | 
            -
             | 
| 612 | 
            -
             | 
| 613 | 
            -
                        nil
         | 
| 614 | 
            -
                      else
         | 
| 615 | 
            -
                        # the line was not a valid conditional line
         | 
| 616 | 
            -
                        # mark it as visited and return it
         | 
| 617 | 
            -
                        @look_ahead += 1
         | 
| 618 | 
            -
                        line
         | 
| 619 | 
            -
                      end
         | 
| 629 | 
            +
                      # the line was not a valid conditional line
         | 
| 630 | 
            +
                      # mark it as visited and return it
         | 
| 631 | 
            +
                      @look_ahead += 1
         | 
| 632 | 
            +
                      line
         | 
| 620 633 | 
             
                    end
         | 
| 621 634 | 
             
                  elsif @skipping
         | 
| 622 635 | 
             
                    advance
         | 
| 623 636 | 
             
                    nil
         | 
| 624 | 
            -
                  elsif ( | 
| 637 | 
            +
                  elsif (line.start_with? 'inc', '\\inc') && IncludeDirectiveRx =~ line
         | 
| 625 638 | 
             
                    # if escaped, mark as processed and return line unescaped
         | 
| 626 | 
            -
                    if  | 
| 639 | 
            +
                    if $1 == '\\'
         | 
| 627 640 | 
             
                      @unescape_next_line = true
         | 
| 628 641 | 
             
                      @look_ahead += 1
         | 
| 629 642 | 
             
                      line[1..-1]
         | 
| 643 | 
            +
                    # QUESTION should we strip whitespace from raw attributes in Substitutors#parse_attributes? (check perf)
         | 
| 644 | 
            +
                    elsif preprocess_include_directive $2, $3.strip
         | 
| 645 | 
            +
                      # peek again since the content has changed
         | 
| 646 | 
            +
                      nil
         | 
| 630 647 | 
             
                    else
         | 
| 631 | 
            -
                      #  | 
| 632 | 
            -
                       | 
| 633 | 
            -
             | 
| 634 | 
            -
             | 
| 635 | 
            -
                      else
         | 
| 636 | 
            -
                        # the line was not a valid include line and is unchanged
         | 
| 637 | 
            -
                        # mark it as visited and return it
         | 
| 638 | 
            -
                        @look_ahead += 1
         | 
| 639 | 
            -
                        line
         | 
| 640 | 
            -
                      end
         | 
| 648 | 
            +
                      # the line was not a valid include line and is unchanged
         | 
| 649 | 
            +
                      # mark it as visited and return it
         | 
| 650 | 
            +
                      @look_ahead += 1
         | 
| 651 | 
            +
                      line
         | 
| 641 652 | 
             
                    end
         | 
| 642 653 | 
             
                  else
         | 
| 643 654 | 
             
                    # NOTE optimization to inline super
         | 
| @@ -672,95 +683,89 @@ class PreprocessorReader < Reader | |
| 672 683 | 
             
                end
         | 
| 673 684 | 
             
              end
         | 
| 674 685 |  | 
| 675 | 
            -
              # Internal: Preprocess the directive  | 
| 676 | 
            -
              #
         | 
| 677 | 
            -
              # Preprocess the conditional  | 
| 678 | 
            -
              #  | 
| 679 | 
            -
              #  | 
| 680 | 
            -
              #  | 
| 681 | 
            -
              #  | 
| 682 | 
            -
              #  | 
| 683 | 
            -
              #
         | 
| 684 | 
            -
              #  | 
| 685 | 
            -
              # target | 
| 686 | 
            -
              # | 
| 687 | 
            -
              # delimiter | 
| 688 | 
            -
              # | 
| 689 | 
            -
              # | 
| 690 | 
            -
              # text | 
| 691 | 
            -
              # | 
| 692 | 
            -
              # | 
| 686 | 
            +
              # Internal: Preprocess the directive to conditionally include or exclude content.
         | 
| 687 | 
            +
              #
         | 
| 688 | 
            +
              # Preprocess the conditional directive (ifdef, ifndef, ifeval, endif) under
         | 
| 689 | 
            +
              # the cursor. If Reader is currently skipping content, then simply track the
         | 
| 690 | 
            +
              # open and close delimiters of any nested conditional blocks. If Reader is
         | 
| 691 | 
            +
              # not skipping, mark whether the condition is satisfied and continue
         | 
| 692 | 
            +
              # preprocessing recursively until the next line of available content is
         | 
| 693 | 
            +
              # found.
         | 
| 694 | 
            +
              #
         | 
| 695 | 
            +
              # keyword   - The conditional inclusion directive (ifdef, ifndef, ifeval, endif)
         | 
| 696 | 
            +
              # target    - The target, which is the name of one or more attributes that are
         | 
| 697 | 
            +
              #             used in the condition (blank in the case of the ifeval directive)
         | 
| 698 | 
            +
              # delimiter - The conditional delimiter for multiple attributes ('+' means all
         | 
| 699 | 
            +
              #             attributes must be defined or undefined, ',' means any of the attributes
         | 
| 700 | 
            +
              #             can be defined or undefined.
         | 
| 701 | 
            +
              # text      - The text associated with this directive (occurring between the square brackets)
         | 
| 702 | 
            +
              #             Used for a single-line conditional block in the case of the ifdef or
         | 
| 703 | 
            +
              #             ifndef directives, and for the conditional expression for the ifeval directive.
         | 
| 693 704 | 
             
              #
         | 
| 694 705 | 
             
              # Returns a Boolean indicating whether the cursor should be advanced
         | 
| 695 | 
            -
              def  | 
| 706 | 
            +
              def preprocess_conditional_directive keyword, target, delimiter, text
         | 
| 707 | 
            +
                # attributes are case insensitive
         | 
| 708 | 
            +
                target = target.downcase unless (no_target = target.empty?)
         | 
| 709 | 
            +
             | 
| 696 710 | 
             
                # must have a target before brackets if ifdef or ifndef
         | 
| 697 711 | 
             
                # must not have text between brackets if endif
         | 
| 698 | 
            -
                #  | 
| 712 | 
            +
                # skip line if it doesn't meet this criteria
         | 
| 699 713 | 
             
                # QUESTION should we warn for these bogus declarations?
         | 
| 700 | 
            -
                if (( | 
| 701 | 
            -
                    (directive == 'endif' && text)
         | 
| 702 | 
            -
                  return false
         | 
| 703 | 
            -
                end
         | 
| 714 | 
            +
                return false if (no_target && (keyword == 'ifdef' || keyword == 'ifndef')) || (text && keyword == 'endif')
         | 
| 704 715 |  | 
| 705 | 
            -
                 | 
| 706 | 
            -
             | 
| 707 | 
            -
             | 
| 708 | 
            -
                if directive == 'endif'
         | 
| 709 | 
            -
                  stack_size = @conditional_stack.size
         | 
| 710 | 
            -
                  if stack_size > 0
         | 
| 711 | 
            -
                    pair = @conditional_stack[-1]
         | 
| 712 | 
            -
                    if target.empty? || target == pair[:target]
         | 
| 713 | 
            -
                      @conditional_stack.pop
         | 
| 714 | 
            -
                      @skipping = @conditional_stack.empty? ? false : @conditional_stack[-1][:skipping]
         | 
| 715 | 
            -
                    else
         | 
| 716 | 
            -
                      warn %(asciidoctor: ERROR: #{line_info}: mismatched macro: endif::#{target}[], expected endif::#{pair[:target]}[])
         | 
| 717 | 
            -
                    end
         | 
| 718 | 
            -
                  else
         | 
| 716 | 
            +
                if keyword == 'endif'
         | 
| 717 | 
            +
                  if @conditional_stack.empty?
         | 
| 719 718 | 
             
                    warn %(asciidoctor: ERROR: #{line_info}: unmatched macro: endif::#{target}[])
         | 
| 719 | 
            +
                  elsif no_target || target == (pair = @conditional_stack[-1])[:target]
         | 
| 720 | 
            +
                    @conditional_stack.pop
         | 
| 721 | 
            +
                    @skipping = @conditional_stack.empty? ? false : @conditional_stack[-1][:skipping]
         | 
| 722 | 
            +
                  else
         | 
| 723 | 
            +
                    warn %(asciidoctor: ERROR: #{line_info}: mismatched macro: endif::#{target}[], expected endif::#{pair[:target]}[])
         | 
| 720 724 | 
             
                  end
         | 
| 721 725 | 
             
                  return true
         | 
| 722 726 | 
             
                end
         | 
| 723 727 |  | 
| 724 | 
            -
                 | 
| 725 | 
            -
             | 
| 728 | 
            +
                if @skipping
         | 
| 729 | 
            +
                  skip = false
         | 
| 730 | 
            +
                else
         | 
| 726 731 | 
             
                  # QUESTION any way to wrap ifdef & ifndef logic up together?
         | 
| 727 | 
            -
                  case  | 
| 732 | 
            +
                  case keyword
         | 
| 728 733 | 
             
                  when 'ifdef'
         | 
| 729 734 | 
             
                    case delimiter
         | 
| 730 | 
            -
                    when nil
         | 
| 731 | 
            -
                      # if the attribute is undefined, then skip
         | 
| 732 | 
            -
                      skip = !@document.attributes.has_key?(target)
         | 
| 733 735 | 
             
                    when ','
         | 
| 734 | 
            -
                      # if  | 
| 735 | 
            -
                      skip = target.split(',').none? {|name| @document.attributes. | 
| 736 | 
            +
                      # skip if no attribute is defined
         | 
| 737 | 
            +
                      skip = target.split(',', -1).none? {|name| @document.attributes.key? name }
         | 
| 736 738 | 
             
                    when '+'
         | 
| 737 | 
            -
                      # if any attribute is undefined | 
| 738 | 
            -
                      skip = target.split('+').any? {|name| !@document.attributes. | 
| 739 | 
            +
                      # skip if any attribute is undefined
         | 
| 740 | 
            +
                      skip = target.split('+', -1).any? {|name| !@document.attributes.key? name }
         | 
| 741 | 
            +
                    else
         | 
| 742 | 
            +
                      # if the attribute is undefined, then skip
         | 
| 743 | 
            +
                      skip = !@document.attributes.key?(target)
         | 
| 739 744 | 
             
                    end
         | 
| 740 745 | 
             
                  when 'ifndef'
         | 
| 741 746 | 
             
                    case delimiter
         | 
| 742 | 
            -
                    when nil
         | 
| 743 | 
            -
                      # if the attribute is defined, then skip
         | 
| 744 | 
            -
                      skip = @document.attributes.has_key?(target)
         | 
| 745 747 | 
             
                    when ','
         | 
| 746 | 
            -
                      # if any attribute is  | 
| 747 | 
            -
                      skip = target.split(','). | 
| 748 | 
            +
                      # skip if any attribute is defined
         | 
| 749 | 
            +
                      skip = target.split(',', -1).any? {|name| @document.attributes.key? name }
         | 
| 748 750 | 
             
                    when '+'
         | 
| 749 | 
            -
                      # if  | 
| 750 | 
            -
                      skip = target.split('+'). | 
| 751 | 
            +
                      # skip if all attributes are defined
         | 
| 752 | 
            +
                      skip = target.split('+', -1).all? {|name| @document.attributes.key? name }
         | 
| 753 | 
            +
                    else
         | 
| 754 | 
            +
                      # if the attribute is defined, then skip
         | 
| 755 | 
            +
                      skip = @document.attributes.key?(target)
         | 
| 751 756 | 
             
                    end
         | 
| 752 757 | 
             
                  when 'ifeval'
         | 
| 753 758 | 
             
                    # the text in brackets must match an expression
         | 
| 754 759 | 
             
                    # don't honor match if it doesn't meet this criteria
         | 
| 755 | 
            -
                     | 
| 756 | 
            -
                      return false
         | 
| 757 | 
            -
                    end
         | 
| 760 | 
            +
                    return false unless no_target && EvalExpressionRx =~ text.strip
         | 
| 758 761 |  | 
| 759 | 
            -
                     | 
| 760 | 
            -
                    rhs =  | 
| 762 | 
            +
                    # NOTE save values eagerly for Ruby 1.8.7 compat
         | 
| 763 | 
            +
                    lhs, op, rhs = $1, $2, $3
         | 
| 764 | 
            +
                    lhs = resolve_expr_val lhs
         | 
| 765 | 
            +
                    rhs = resolve_expr_val rhs
         | 
| 761 766 |  | 
| 762 767 | 
             
                    # regex enforces a restricted set of math-related operations
         | 
| 763 | 
            -
                    if  | 
| 768 | 
            +
                    if op == '!='
         | 
| 764 769 | 
             
                      skip = lhs.send :==, rhs
         | 
| 765 770 | 
             
                    else
         | 
| 766 771 | 
             
                      skip = !(lhs.send op.to_sym, rhs)
         | 
| @@ -769,26 +774,25 @@ class PreprocessorReader < Reader | |
| 769 774 | 
             
                end
         | 
| 770 775 |  | 
| 771 776 | 
             
                # conditional inclusion block
         | 
| 772 | 
            -
                if  | 
| 777 | 
            +
                if keyword == 'ifeval' || !text
         | 
| 773 778 | 
             
                  @skipping = true if skip
         | 
| 774 779 | 
             
                  @conditional_stack << {:target => target, :skip => skip, :skipping => @skipping}
         | 
| 775 780 | 
             
                # single line conditional inclusion
         | 
| 776 781 | 
             
                else
         | 
| 777 782 | 
             
                  unless @skipping || skip
         | 
| 778 | 
            -
                    # FIXME slight hack to skip past conditional line
         | 
| 779 | 
            -
                    # but keep our synthetic line marked as processed
         | 
| 780 | 
            -
                    # QUESTION can we use read_line true and unshift twice instead?
         | 
| 781 | 
            -
                    conditional_line = peek_line true
         | 
| 782 783 | 
             
                    replace_next_line text.rstrip
         | 
| 783 | 
            -
                     | 
| 784 | 
            -
                     | 
| 784 | 
            +
                    # HACK push dummy line to stand in for the opening conditional directive that's subsequently dropped
         | 
| 785 | 
            +
                    unshift ''
         | 
| 786 | 
            +
                    # NOTE force line to be processed again if it looks like an include directive
         | 
| 787 | 
            +
                    # QUESTION should we just call preprocess_include_directive here?
         | 
| 788 | 
            +
                    @look_ahead -= 1 if text.start_with? 'include::'
         | 
| 785 789 | 
             
                  end
         | 
| 786 790 | 
             
                end
         | 
| 787 791 |  | 
| 788 792 | 
             
                true
         | 
| 789 793 | 
             
              end
         | 
| 790 794 |  | 
| 791 | 
            -
              # Internal: Preprocess the directive  | 
| 795 | 
            +
              # Internal: Preprocess the directive to include lines from another document.
         | 
| 792 796 | 
             
              #
         | 
| 793 797 | 
             
              # Preprocess the directive to include the target document. The scenarios
         | 
| 794 798 | 
             
              # are as follows:
         | 
| @@ -806,23 +810,21 @@ class PreprocessorReader < Reader | |
| 806 810 | 
             
              # If none of the above apply, emit the include directive line verbatim.
         | 
| 807 811 | 
             
              #
         | 
| 808 812 | 
             
              # target - The name of the source document to include as specified in the
         | 
| 809 | 
            -
              #          target slot of the include::[]  | 
| 813 | 
            +
              #          target slot of the include::[] directive
         | 
| 810 814 | 
             
              #
         | 
| 811 815 | 
             
              # Returns a Boolean indicating whether the line under the cursor has changed.
         | 
| 812 | 
            -
              def  | 
| 813 | 
            -
                if (target =  | 
| 816 | 
            +
              def preprocess_include_directive raw_target, raw_attributes
         | 
| 817 | 
            +
                if ((target = raw_target).include? '{') &&
         | 
| 818 | 
            +
                    (target = @document.sub_attributes raw_target, :attribute_missing => 'drop-line').empty?
         | 
| 814 819 | 
             
                  advance
         | 
| 815 820 | 
             
                  if @document.attributes.fetch('attribute-missing', Compliance.attribute_missing) == 'skip'
         | 
| 816 821 | 
             
                    unshift %(Unresolved directive in #{@path} - include::#{raw_target}[#{raw_attributes}])
         | 
| 817 822 | 
             
                  end
         | 
| 818 823 | 
             
                  true
         | 
| 819 | 
            -
                 | 
| 820 | 
            -
                # to handle when and how to process the include
         | 
| 821 | 
            -
                elsif include_processors? &&
         | 
| 822 | 
            -
                    (extension = @include_processor_extensions.find {|candidate| candidate.instance.handles? target })
         | 
| 824 | 
            +
                elsif include_processors? && (ext = @include_processor_extensions.find {|candidate| candidate.instance.handles? target })
         | 
| 823 825 | 
             
                  advance
         | 
| 824 | 
            -
                  # FIXME parse attributes if requested by extension
         | 
| 825 | 
            -
                   | 
| 826 | 
            +
                  # FIXME parse attributes only if requested by extension
         | 
| 827 | 
            +
                  ext.process_method[@document, self, target, AttributeList.new(raw_attributes).parse]
         | 
| 826 828 | 
             
                  true
         | 
| 827 829 | 
             
                # if running in SafeMode::SECURE or greater, don't process this directive
         | 
| 828 830 | 
             
                # however, be friendly and at least make it a link to the source document
         | 
| @@ -835,25 +837,20 @@ class PreprocessorReader < Reader | |
| 835 837 | 
             
                    warn %(asciidoctor: ERROR: #{line_info}: maximum include depth of #{@maxdepth[:rel]} exceeded)
         | 
| 836 838 | 
             
                    return false
         | 
| 837 839 | 
             
                  end
         | 
| 838 | 
            -
                  if ::RUBY_ENGINE_OPAL
         | 
| 840 | 
            +
                  if ::RUBY_ENGINE_OPAL && ::JAVASCRIPT_IO_MODULE == 'xmlhttprequest'
         | 
| 839 841 | 
             
                    # NOTE resolves uri relative to currently loaded document
         | 
| 840 842 | 
             
                    # NOTE we defer checking if file exists and catch the 404 error if it does not
         | 
| 841 | 
            -
                    # TODO only use this logic if env-browser is set
         | 
| 842 843 | 
             
                    target_type = :file
         | 
| 843 | 
            -
                     | 
| 844 | 
            -
                      ::Dir.pwd == @document.base_dir ? target : (::File.join @dir, target)
         | 
| 845 | 
            -
                    else
         | 
| 846 | 
            -
                      ::File.join @dir, target
         | 
| 847 | 
            -
                    end
         | 
| 844 | 
            +
                    inc_path = relpath = @include_stack.empty? && ::Dir.pwd == @document.base_dir ? target : (::File.join @dir, target)
         | 
| 848 845 | 
             
                  elsif Helpers.uriish? target
         | 
| 849 | 
            -
                    unless @document.attributes. | 
| 846 | 
            +
                    unless @document.attributes.key? 'allow-uri-read'
         | 
| 850 847 | 
             
                      replace_next_line %(link:#{target}[])
         | 
| 851 848 | 
             
                      return true
         | 
| 852 849 | 
             
                    end
         | 
| 853 850 |  | 
| 854 851 | 
             
                    target_type = :uri
         | 
| 855 | 
            -
                     | 
| 856 | 
            -
                    if @document.attributes. | 
| 852 | 
            +
                    inc_path = relpath = target
         | 
| 853 | 
            +
                    if @document.attributes.key? 'cache-uri'
         | 
| 857 854 | 
             
                      # caching requires the open-uri-cached gem to be installed
         | 
| 858 855 | 
             
                      # processing will be automatically aborted if these libraries can't be opened
         | 
| 859 856 | 
             
                      Helpers.require_library 'open-uri/cached', 'open-uri-cached' unless defined? ::OpenURI::Cache
         | 
| @@ -864,130 +861,159 @@ class PreprocessorReader < Reader | |
| 864 861 | 
             
                  else
         | 
| 865 862 | 
             
                    target_type = :file
         | 
| 866 863 | 
             
                    # include file is resolved relative to dir of current include, or base_dir if within original docfile
         | 
| 867 | 
            -
                     | 
| 868 | 
            -
                    unless ::File.file?  | 
| 869 | 
            -
                      warn %(asciidoctor: WARNING: #{line_info}: include file not found: #{ | 
| 864 | 
            +
                    inc_path = @document.normalize_system_path target, @dir, nil, :target_name => 'include file'
         | 
| 865 | 
            +
                    unless ::File.file? inc_path
         | 
| 866 | 
            +
                      warn %(asciidoctor: WARNING: #{line_info}: include file not found: #{inc_path})
         | 
| 870 867 | 
             
                      replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
         | 
| 871 868 | 
             
                      return true
         | 
| 872 869 | 
             
                    end
         | 
| 873 | 
            -
                    #path  | 
| 874 | 
            -
                     | 
| 870 | 
            +
                    # NOTE relpath is the path relative to the outermost document (or base_dir, if set)
         | 
| 871 | 
            +
                    #relpath = @document.relative_path inc_path
         | 
| 872 | 
            +
                    relpath = PathResolver.new.relative_path inc_path, @document.base_dir
         | 
| 875 873 | 
             
                  end
         | 
| 876 874 |  | 
| 877 | 
            -
                   | 
| 878 | 
            -
                   | 
| 879 | 
            -
                  attributes = {}
         | 
| 880 | 
            -
                  if !raw_attributes.empty?
         | 
| 875 | 
            +
                  inc_linenos, inc_tags, attributes = nil, nil, {}
         | 
| 876 | 
            +
                  unless raw_attributes.empty?
         | 
| 881 877 | 
             
                    # QUESTION should we use @document.parse_attribues?
         | 
| 882 878 | 
             
                    attributes = AttributeList.new(raw_attributes).parse
         | 
| 883 | 
            -
                    if attributes. | 
| 884 | 
            -
                       | 
| 879 | 
            +
                    if attributes.key? 'lines'
         | 
| 880 | 
            +
                      inc_linenos = []
         | 
| 885 881 | 
             
                      attributes['lines'].split(DataDelimiterRx).each do |linedef|
         | 
| 886 882 | 
             
                        if linedef.include?('..')
         | 
| 887 | 
            -
                          from, to = linedef.split('..', 2).map | 
| 883 | 
            +
                          from, to = linedef.split('..', 2).map {|it| it.to_i }
         | 
| 888 884 | 
             
                          if to == -1
         | 
| 889 | 
            -
                             | 
| 890 | 
            -
                             | 
| 885 | 
            +
                            inc_linenos << from
         | 
| 886 | 
            +
                            inc_linenos << 1.0/0.0
         | 
| 891 887 | 
             
                          else
         | 
| 892 | 
            -
                             | 
| 888 | 
            +
                            inc_linenos.concat ::Range.new(from, to).to_a
         | 
| 893 889 | 
             
                          end
         | 
| 894 890 | 
             
                        else
         | 
| 895 | 
            -
                           | 
| 891 | 
            +
                          inc_linenos << linedef.to_i
         | 
| 896 892 | 
             
                        end
         | 
| 897 893 | 
             
                      end
         | 
| 898 | 
            -
                       | 
| 899 | 
            -
                    elsif attributes. | 
| 900 | 
            -
                       | 
| 901 | 
            -
             | 
| 902 | 
            -
             | 
| 894 | 
            +
                      inc_linenos = inc_linenos.empty? ? nil : inc_linenos.sort.uniq
         | 
| 895 | 
            +
                    elsif attributes.key? 'tag'
         | 
| 896 | 
            +
                      unless (tag = attributes['tag']).empty?
         | 
| 897 | 
            +
                        if tag.start_with? '!'
         | 
| 898 | 
            +
                          inc_tags = { (tag.slice 1, tag.length) => false } unless tag == '!'
         | 
| 899 | 
            +
                        else
         | 
| 900 | 
            +
                          inc_tags = { tag => true }
         | 
| 901 | 
            +
                        end
         | 
| 902 | 
            +
                      end
         | 
| 903 | 
            +
                    elsif attributes.key? 'tags'
         | 
| 904 | 
            +
                      inc_tags = {}
         | 
| 905 | 
            +
                      attributes['tags'].split(DataDelimiterRx).each do |tagdef|
         | 
| 906 | 
            +
                        if tagdef.start_with? '!'
         | 
| 907 | 
            +
                          inc_tags[tagdef.slice 1, tagdef.length] = false unless tagdef == '!'
         | 
| 908 | 
            +
                        else
         | 
| 909 | 
            +
                          inc_tags[tagdef] = true
         | 
| 910 | 
            +
                        end unless tagdef.empty?
         | 
| 911 | 
            +
                      end
         | 
| 912 | 
            +
                      inc_tags = nil if inc_tags.empty?
         | 
| 903 913 | 
             
                    end
         | 
| 904 914 | 
             
                  end
         | 
| 905 | 
            -
             | 
| 906 | 
            -
             | 
| 907 | 
            -
             | 
| 908 | 
            -
             | 
| 909 | 
            -
                       | 
| 910 | 
            -
             | 
| 911 | 
            -
             | 
| 912 | 
            -
                           | 
| 913 | 
            -
             | 
| 914 | 
            -
                             | 
| 915 | 
            -
                             | 
| 916 | 
            -
             | 
| 917 | 
            -
             | 
| 918 | 
            -
                             | 
| 919 | 
            -
                               | 
| 920 | 
            -
             | 
| 921 | 
            -
             | 
| 922 | 
            -
             | 
| 923 | 
            -
                              end
         | 
| 924 | 
            -
                              break if inc_lines.empty?
         | 
| 915 | 
            +
             | 
| 916 | 
            +
                  if inc_linenos
         | 
| 917 | 
            +
                    inc_lines, inc_offset, inc_lineno = [], nil, 0
         | 
| 918 | 
            +
                    begin
         | 
| 919 | 
            +
                      open(inc_path, 'r') do |f|
         | 
| 920 | 
            +
                        f.each_line do |l|
         | 
| 921 | 
            +
                          inc_lineno += 1
         | 
| 922 | 
            +
                          select = inc_linenos[0]
         | 
| 923 | 
            +
                          if ::Float === select && select.infinite?
         | 
| 924 | 
            +
                            # NOTE record line where we started selecting
         | 
| 925 | 
            +
                            inc_offset ||= inc_lineno
         | 
| 926 | 
            +
                            inc_lines << l
         | 
| 927 | 
            +
                          else
         | 
| 928 | 
            +
                            if inc_lineno == select
         | 
| 929 | 
            +
                              # NOTE record line where we started selecting
         | 
| 930 | 
            +
                              inc_offset ||= inc_lineno
         | 
| 931 | 
            +
                              inc_lines << l
         | 
| 932 | 
            +
                              inc_linenos.shift
         | 
| 925 933 | 
             
                            end
         | 
| 934 | 
            +
                            break if inc_linenos.empty?
         | 
| 926 935 | 
             
                          end
         | 
| 927 936 | 
             
                        end
         | 
| 928 | 
            -
                      rescue
         | 
| 929 | 
            -
                        warn %(asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{include_file})
         | 
| 930 | 
            -
                        replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
         | 
| 931 | 
            -
                        return true
         | 
| 932 937 | 
             
                      end
         | 
| 933 | 
            -
             | 
| 934 | 
            -
                       | 
| 935 | 
            -
                       | 
| 938 | 
            +
                    rescue
         | 
| 939 | 
            +
                      warn %(asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{inc_path})
         | 
| 940 | 
            +
                      replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
         | 
| 941 | 
            +
                      return true
         | 
| 936 942 | 
             
                    end
         | 
| 937 | 
            -
             | 
| 938 | 
            -
                     | 
| 939 | 
            -
             | 
| 940 | 
            -
             | 
| 941 | 
            -
             | 
| 942 | 
            -
             | 
| 943 | 
            -
                       | 
| 944 | 
            -
             | 
| 945 | 
            -
                         | 
| 946 | 
            -
             | 
| 947 | 
            -
             | 
| 948 | 
            -
             | 
| 949 | 
            -
             | 
| 950 | 
            -
             | 
| 951 | 
            -
             | 
| 952 | 
            -
             | 
| 953 | 
            -
             | 
| 954 | 
            -
             | 
| 955 | 
            -
             | 
| 956 | 
            -
             | 
| 957 | 
            -
             | 
| 958 | 
            -
             | 
| 959 | 
            -
             | 
| 960 | 
            -
             | 
| 961 | 
            -
             | 
| 962 | 
            -
             | 
| 963 | 
            -
             | 
| 964 | 
            -
             | 
| 965 | 
            -
             | 
| 943 | 
            +
                    advance
         | 
| 944 | 
            +
                    # FIXME not accounting for skipped lines in reader line numbering
         | 
| 945 | 
            +
                    push_include inc_lines, inc_path, relpath, inc_offset, attributes if inc_offset
         | 
| 946 | 
            +
                  elsif inc_tags
         | 
| 947 | 
            +
                    inc_lines, inc_offset, inc_lineno, tag_stack, tags_used, active_tag = [], nil, 0, [], ::Set.new, nil
         | 
| 948 | 
            +
                    if inc_tags.key? '**'
         | 
| 949 | 
            +
                      if inc_tags.key? '*'
         | 
| 950 | 
            +
                        select = base_select = (inc_tags.delete '**')
         | 
| 951 | 
            +
                        wildcard = inc_tags.delete '*'
         | 
| 952 | 
            +
                      else
         | 
| 953 | 
            +
                        select = base_select = wildcard = (inc_tags.delete '**')
         | 
| 954 | 
            +
                      end
         | 
| 955 | 
            +
                    else
         | 
| 956 | 
            +
                      select = base_select = !(inc_tags.value? true)
         | 
| 957 | 
            +
                      wildcard = inc_tags.delete '*'
         | 
| 958 | 
            +
                    end
         | 
| 959 | 
            +
                    if (ext_idx = inc_path.rindex '.') && (circ_cmt = CIRCUMFIX_COMMENTS[inc_path.slice ext_idx, inc_path.length])
         | 
| 960 | 
            +
                      cmt_suffix_len = (tag_suffix = %([] #{circ_cmt[:suffix]})).length - 2
         | 
| 961 | 
            +
                    end
         | 
| 962 | 
            +
                    begin
         | 
| 963 | 
            +
                      open(inc_path, 'r') do |f|
         | 
| 964 | 
            +
                        f.each_line do |l|
         | 
| 965 | 
            +
                          inc_lineno += 1
         | 
| 966 | 
            +
                          # must force encoding since we're performing String operations on line
         | 
| 967 | 
            +
                          l.force_encoding ::Encoding::UTF_8 if FORCE_ENCODING
         | 
| 968 | 
            +
                          if (((tl = l.chomp).end_with? '[]') ||
         | 
| 969 | 
            +
                              (tag_suffix && (tl.end_with? tag_suffix) && (tl = tl.slice 0, tl.length - cmt_suffix_len))) &&
         | 
| 970 | 
            +
                              TagDirectiveRx =~ tl
         | 
| 971 | 
            +
                            if $1 # end tag
         | 
| 972 | 
            +
                              if (this_tag = $2) == active_tag
         | 
| 973 | 
            +
                                tag_stack.pop
         | 
| 974 | 
            +
                                active_tag, select = tag_stack.empty? ? [nil, base_select] : tag_stack[-1]
         | 
| 975 | 
            +
                              elsif inc_tags.key? this_tag
         | 
| 976 | 
            +
                                if (idx = tag_stack.rindex {|key, _| key == this_tag })
         | 
| 977 | 
            +
                                  idx == 0 ? tag_stack.shift : (tag_stack.delete_at idx)
         | 
| 978 | 
            +
                                  warn %(asciidoctor: WARNING: #{target}: line #{inc_lineno}: mismatched end tag in include: expected #{active_tag}, found #{this_tag})
         | 
| 979 | 
            +
                                else
         | 
| 980 | 
            +
                                  warn %(asciidoctor: WARNING: #{target}: line #{inc_lineno}: unexpected end tag in include: #{this_tag})
         | 
| 966 981 | 
             
                                end
         | 
| 967 | 
            -
                              end | 
| 982 | 
            +
                              end
         | 
| 983 | 
            +
                            elsif inc_tags.key?(this_tag = $2)
         | 
| 984 | 
            +
                              tags_used << this_tag
         | 
| 985 | 
            +
                              # QUESTION should we prevent tag from being selected when enclosing tag is excluded?
         | 
| 986 | 
            +
                              tag_stack << [(active_tag = this_tag), (select = inc_tags[this_tag])]
         | 
| 987 | 
            +
                            elsif !wildcard.nil?
         | 
| 988 | 
            +
                              select = active_tag && !select ? false : wildcard
         | 
| 989 | 
            +
                              tag_stack << [(active_tag = this_tag), select]
         | 
| 968 990 | 
             
                            end
         | 
| 991 | 
            +
                          elsif select
         | 
| 992 | 
            +
                            # NOTE record the line where we started selecting
         | 
| 993 | 
            +
                            inc_offset ||= inc_lineno
         | 
| 994 | 
            +
                            inc_lines << l
         | 
| 969 995 | 
             
                          end
         | 
| 970 996 | 
             
                        end
         | 
| 971 | 
            -
                      rescue
         | 
| 972 | 
            -
                        warn %(asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{include_file})
         | 
| 973 | 
            -
                        replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
         | 
| 974 | 
            -
                        return true
         | 
| 975 | 
            -
                      end
         | 
| 976 | 
            -
                      unless (missing_tags = tags.to_a - tags_found.to_a).empty?
         | 
| 977 | 
            -
                        warn %(asciidoctor: WARNING: #{line_info}: tag#{missing_tags.size > 1 ? 's' : nil} '#{missing_tags * ','}' not found in include #{target_type}: #{include_file})
         | 
| 978 997 | 
             
                      end
         | 
| 979 | 
            -
             | 
| 980 | 
            -
                       | 
| 981 | 
            -
                       | 
| 998 | 
            +
                    rescue
         | 
| 999 | 
            +
                      warn %(asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{inc_path})
         | 
| 1000 | 
            +
                      replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
         | 
| 1001 | 
            +
                      return true
         | 
| 982 1002 | 
             
                    end
         | 
| 1003 | 
            +
                    unless (missing_tags = inc_tags.keys.to_a - tags_used.to_a).empty?
         | 
| 1004 | 
            +
                      warn %(asciidoctor: WARNING: #{line_info}: tag#{missing_tags.size > 1 ? 's' : nil} '#{missing_tags * ','}' not found in include #{target_type}: #{inc_path})
         | 
| 1005 | 
            +
                    end
         | 
| 1006 | 
            +
                    advance
         | 
| 1007 | 
            +
                    # FIXME not accounting for skipped lines in reader line numbering
         | 
| 1008 | 
            +
                    push_include inc_lines, inc_path, relpath, inc_offset, attributes if inc_offset
         | 
| 983 1009 | 
             
                  else
         | 
| 984 1010 | 
             
                    begin
         | 
| 985 1011 | 
             
                      # NOTE read content first so that we only advance cursor if IO operation succeeds
         | 
| 986 | 
            -
                       | 
| 1012 | 
            +
                      inc_content = target_type == :file ? (::IO.read inc_path) : open(inc_path, 'r') {|f| f.read }
         | 
| 987 1013 | 
             
                      advance
         | 
| 988 | 
            -
                      push_include  | 
| 1014 | 
            +
                      push_include inc_content, inc_path, relpath, 1, attributes
         | 
| 989 1015 | 
             
                    rescue
         | 
| 990 | 
            -
                      warn %(asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{ | 
| 1016 | 
            +
                      warn %(asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{inc_path})
         | 
| 991 1017 | 
             
                      replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
         | 
| 992 1018 | 
             
                      return true
         | 
| 993 1019 | 
             
                    end
         | 
| @@ -1018,7 +1044,7 @@ class PreprocessorReader < Reader | |
| 1018 1044 | 
             
                  @file = file
         | 
| 1019 1045 | 
             
                  @dir = File.dirname file
         | 
| 1020 1046 | 
             
                  # only process lines in AsciiDoc files
         | 
| 1021 | 
            -
                  @process_lines = ASCIIDOC_EXTENSIONS[::File.extname | 
| 1047 | 
            +
                  @process_lines = ASCIIDOC_EXTENSIONS[::File.extname file]
         | 
| 1022 1048 | 
             
                else
         | 
| 1023 1049 | 
             
                  @file = nil
         | 
| 1024 1050 | 
             
                  @dir = '.' # right?
         | 
| @@ -1026,16 +1052,16 @@ class PreprocessorReader < Reader | |
| 1026 1052 | 
             
                  @process_lines = true
         | 
| 1027 1053 | 
             
                end
         | 
| 1028 1054 |  | 
| 1029 | 
            -
                 | 
| 1055 | 
            +
                if path
         | 
| 1030 1056 | 
             
                  @includes << Helpers.rootname(path)
         | 
| 1031 | 
            -
                  path
         | 
| 1057 | 
            +
                  @path = path
         | 
| 1032 1058 | 
             
                else
         | 
| 1033 | 
            -
                  '<stdin>'
         | 
| 1059 | 
            +
                  @path = '<stdin>'
         | 
| 1034 1060 | 
             
                end
         | 
| 1035 1061 |  | 
| 1036 1062 | 
             
                @lineno = lineno
         | 
| 1037 1063 |  | 
| 1038 | 
            -
                if attributes. | 
| 1064 | 
            +
                if attributes.key? 'depth'
         | 
| 1039 1065 | 
             
                  depth = attributes['depth'].to_i
         | 
| 1040 1066 | 
             
                  depth = 1 if depth <= 0
         | 
| 1041 1067 | 
             
                  @maxdepth = {:abs => (@include_stack.size - 1) + depth, :rel => depth}
         | 
| @@ -1046,14 +1072,14 @@ class PreprocessorReader < Reader | |
| 1046 1072 | 
             
                  pop_include
         | 
| 1047 1073 | 
             
                else
         | 
| 1048 1074 | 
             
                  # FIXME we eventually want to handle leveloffset without affecting the lines
         | 
| 1049 | 
            -
                  if attributes. | 
| 1075 | 
            +
                  if attributes.key? 'leveloffset'
         | 
| 1050 1076 | 
             
                    @lines.unshift ''
         | 
| 1051 1077 | 
             
                    @lines.unshift %(:leveloffset: #{attributes['leveloffset']})
         | 
| 1052 | 
            -
                    @lines | 
| 1078 | 
            +
                    @lines << ''
         | 
| 1053 1079 | 
             
                    if (old_leveloffset = @document.attr 'leveloffset')
         | 
| 1054 | 
            -
                      @lines | 
| 1080 | 
            +
                      @lines << %(:leveloffset: #{old_leveloffset})
         | 
| 1055 1081 | 
             
                    else
         | 
| 1056 | 
            -
                      @lines | 
| 1082 | 
            +
                      @lines << ':leveloffset!:'
         | 
| 1057 1083 | 
             
                    end
         | 
| 1058 1084 | 
             
                    # compensate for these extra lines
         | 
| 1059 1085 | 
             
                    @lineno -= 2
         | 
| @@ -1113,7 +1139,7 @@ class PreprocessorReader < Reader | |
| 1113 1139 | 
             
                  data.shift
         | 
| 1114 1140 | 
             
                  @lineno += 1 if increment_linenos
         | 
| 1115 1141 | 
             
                  while !data.empty? && data[0] != '---'
         | 
| 1116 | 
            -
                    front_matter | 
| 1142 | 
            +
                    front_matter << data.shift
         | 
| 1117 1143 | 
             
                    @lineno += 1 if increment_linenos
         | 
| 1118 1144 | 
             
                  end
         | 
| 1119 1145 |  | 
| @@ -1171,9 +1197,7 @@ class PreprocessorReader < Reader | |
| 1171 1197 |  | 
| 1172 1198 | 
             
                # QUESTION should we substitute first?
         | 
| 1173 1199 | 
             
                # QUESTION should we also require string to be single quoted (like block attribute values?)
         | 
| 1174 | 
            -
                if val.include? '{'
         | 
| 1175 | 
            -
                  val = @document.sub_attributes val, :attribute_missing => 'drop'
         | 
| 1176 | 
            -
                end
         | 
| 1200 | 
            +
                val = @document.sub_attributes val, :attribute_missing => 'drop' if val.include? '{'
         | 
| 1177 1201 |  | 
| 1178 1202 | 
             
                if quoted
         | 
| 1179 1203 | 
             
                  val
         | 
| @@ -1209,7 +1233,7 @@ class PreprocessorReader < Reader | |
| 1209 1233 | 
             
              end
         | 
| 1210 1234 |  | 
| 1211 1235 | 
             
              def to_s
         | 
| 1212 | 
            -
                %(#<#{self.class}@#{object_id} {path: #{@path.inspect}, line #: #{@lineno}, include depth: #{@include_stack.size}, include stack: [#{@include_stack.map {|inc| inc.to_s} | 
| 1236 | 
            +
                %(#<#{self.class}@#{object_id} {path: #{@path.inspect}, line #: #{@lineno}, include depth: #{@include_stack.size}, include stack: [#{@include_stack.map {|inc| inc.to_s } * ', '}]}>)
         | 
| 1213 1237 | 
             
              end
         | 
| 1214 1238 | 
             
            end
         | 
| 1215 1239 | 
             
            end
         |