# File lib/asciidoctor.rb, line 1269
  def load input, options = {}
    options = options.dup
    if (timings = options[:timings])
      timings.start :read
    end

    attributes = options[:attributes] = if !(attrs = options[:attributes])
      {}
    elsif ::Hash === attrs || (::RUBY_ENGINE_JRUBY && ::Java::JavaUtil::Map === attrs)
      attrs.dup
    elsif ::Array === attrs
      attrs.inject({}) do |accum, entry|
        k, v = entry.split '=', 2
        accum[k] = v || ''
        accum
      end
    elsif ::String === attrs
      # convert non-escaped spaces into null character, so we split on the
      # correct spaces chars, and restore escaped spaces
      capture_1 = '\1'
      attrs = attrs.gsub(SpaceDelimiterRx, %(#{capture_1}#{NULL})).gsub(EscapedSpaceRx, capture_1)
      attrs.split(NULL).inject({}) do |accum, entry|
        k, v = entry.split '=', 2
        accum[k] = v || ''
        accum
      end
    elsif (attrs.respond_to? :keys) && (attrs.respond_to? :[])
      # convert it to a Hash as we know it
      original_attrs = attrs
      attrs = {}
      original_attrs.keys.each do |key|
        attrs[key] = original_attrs[key]
      end
      attrs
    else
      raise ::ArgumentError, %(illegal type for attributes option: #{attrs.class.ancestors})
    end

    lines = nil
    if ::File === input
      # TODO cli checks if input path can be read and is file, but might want to add check to API
      input_path = ::File.expand_path input.path
      input_mtime = input.mtime
      lines = input.readlines
      # hold off on setting infile and indir until we get a better sense of their purpose
      attributes['docfile'] = input_path
      attributes['docdir'] = ::File.dirname input_path
      attributes['docname'] = Helpers.basename input_path, true
      docdate = (attributes['docdate'] ||= input_mtime.strftime('%Y-%m-%d'))
      doctime = (attributes['doctime'] ||= input_mtime.strftime('%H:%M:%S %Z'))
      attributes['docdatetime'] = %(#{docdate} #{doctime})
    elsif input.respond_to? :readlines
      # NOTE tty, pipes & sockets can't be rewound, but can't be sniffed easily either
      # just fail the rewind operation silently to handle all cases
      begin
        input.rewind
      rescue
      end
      lines = input.readlines
    elsif ::String === input
      lines = input.lines.entries
    elsif ::Array === input
      lines = input.dup
    else
      raise ::ArgumentError, %(unsupported input type: #{input.class})
    end

    if timings
      timings.record :read
      timings.start :parse
    end

    if options[:parse] == false
      doc = Document.new lines, options
    else
      doc = (Document.new lines, options).parse
    end

    timings.record :parse if timings
    doc
  rescue => ex
    begin
      context = %(asciidoctor: FAILED: #{attributes['docfile'] || '<stdin>'}: Failed to load AsciiDoc document)
      if ex.respond_to? :exception
        # The original message must be explicitely preserved when wrapping a Ruby exception
        wrapped_ex = ex.exception %(#{context} - #{ex.message})
        # JRuby automatically sets backtrace, but not MRI
        wrapped_ex.set_backtrace ex.backtrace
      else
        # Likely a Java exception class
        wrapped_ex = ex.class.new context, ex
        wrapped_ex.stack_trace = ex.stack_trace
      end
    rescue
      wrapped_ex = ex
    end
    raise wrapped_ex
  end