image_size 2.0.2 → 3.0.0
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.
- checksums.yaml +4 -4
- data/.github/workflows/check.yml +69 -0
- data/.rubocop.yml +27 -4
- data/.rubocop_todo.yml +11 -8
- data/CHANGELOG.markdown +19 -0
- data/GPL +340 -0
- data/Gemfile +1 -3
- data/LICENSE.txt +56 -0
- data/README.markdown +22 -18
- data/image_size.gemspec +6 -5
- data/lib/image_size/chunky_reader.rb +44 -0
- data/lib/image_size/reader.rb +64 -0
- data/lib/image_size/seekable_io_reader.rb +28 -0
- data/lib/image_size/stream_io_reader.rb +22 -0
- data/lib/image_size/string_reader.rb +21 -0
- data/lib/image_size/uri_reader.rb +88 -0
- data/lib/image_size.rb +159 -85
- data/spec/image_size/chunky_reader_spec.rb +69 -0
- data/spec/image_size_spec.rb +45 -2
- data/spec/images/.gitattributes +1 -0
- data/spec/images/cur/32x256.cur +0 -0
- data/spec/images/ico/32x256.ico +0 -0
- data/spec/images/jp2/163x402.jp2 +0 -0
- data/spec/images/jp2/176x373.jpx +0 -0
- data/spec/images/jp2/224x293.j2c +0 -0
- data/spec/images/jpeg/436x429.jpeg +0 -0
- data/spec/images/jpeg/extraneous-bytes.436x429.jpeg +0 -0
- data/spec/images/mng/61x42.mng +0 -0
- data/spec/images/{apng → png}/192x110.apng +0 -0
- data/spec/images/pnm/22x25.pam +8 -0
- data/spec/images/pnm/22x25.pbm +0 -0
- data/spec/images/pnm/22x25.pgm +4 -0
- data/spec/images/pnm/22x25.ppm +4 -0
- data/spec/images/pnm/ascii.22x25.pbm +27 -0
- data/spec/images/pnm/ascii.22x25.pgm +28 -0
- data/spec/images/pnm/ascii.22x25.ppm +28 -0
- data/spec/images/svg/crlf.72x100.svg +3 -0
- data/spec/images/svg/long.72x100.svg +20 -0
- data/spec/images/svg/long.crlf.72x100.svg +20 -0
- data/spec/images/tiff/big-endian.68x49.tiff +0 -0
- data/spec/images/tiff/little-endian.40x68.tiff +0 -0
- data/spec/images/xbm/crlf.16x32.xbm +11 -0
- data/spec/images/xpm/crlf.24x32.xpm +40 -0
- metadata +86 -32
- data/.travis.yml +0 -24
- data/spec/images/cur/50x256.cur +0 -0
- data/spec/images/ico/256x27.ico +0 -0
- data/spec/images/jpeg/320x240.jpeg +0 -0
- data/spec/images/jpeg/extraneous-bytes.320x240.jpeg +0 -0
- data/spec/images/mng/612x132.mng +0 -0
- data/spec/images/pbm/85x55.pbm +0 -0
- data/spec/images/pgm/90x55.pgm +0 -5
- data/spec/images/tiff/48x64.tiff +0 -0
    
        data/README.markdown
    CHANGED
    
    | @@ -1,10 +1,10 @@ | |
| 1 | 
            -
            [](https://rubygems.org/gems/image_size)
         | 
| 2 | 
            +
            [](https://github.com/toy/image_size/actions/workflows/check.yml)
         | 
| 3 3 |  | 
| 4 4 | 
             
            # image_size
         | 
| 5 5 |  | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 6 | 
            +
            Measure image size using pure Ruby.
         | 
| 7 | 
            +
            Formats: `apng`, `bmp`, `cur`, `gif`, `ico`, `j2c`, `jp2`, `jpeg`, `jpx`, `mng`, `pam`, `pbm`, `pcx`, `pgm`, `png`, `ppm`, `psd`, `svg`, `swf`, `tiff`, `webp`, `xbm`, `xpm`.
         | 
| 8 8 |  | 
| 9 9 | 
             
            ## Installation
         | 
| 10 10 |  | 
| @@ -23,20 +23,26 @@ gem 'image_size', '~> 2.0' | |
| 23 23 | 
             
            ## Usage
         | 
| 24 24 |  | 
| 25 25 | 
             
            ```ruby
         | 
| 26 | 
            -
            image_size = ImageSize.path('spec/ | 
| 26 | 
            +
            image_size = ImageSize.path('spec/images/jpeg/436x429.jpeg')
         | 
| 27 27 |  | 
| 28 28 | 
             
            image_size.format       #=> :jpec
         | 
| 29 | 
            -
            image_size.width        #=>  | 
| 30 | 
            -
            image_size.height       #=>  | 
| 31 | 
            -
            image_size.w            #=>  | 
| 32 | 
            -
            image_size.h            #=>  | 
| 33 | 
            -
            image_size.size         #=> [ | 
| 29 | 
            +
            image_size.width        #=> 436
         | 
| 30 | 
            +
            image_size.height       #=> 429
         | 
| 31 | 
            +
            image_size.w            #=> 436
         | 
| 32 | 
            +
            image_size.h            #=> 429
         | 
| 33 | 
            +
            image_size.size         #=> [436, 429]
         | 
| 34 | 
            +
            image_size.size.to_s    #=> "436x429"
         | 
| 35 | 
            +
            "#{image_size.size}"    #=> "436x429"
         | 
| 36 | 
            +
            image_size.size.width   #=> 436
         | 
| 37 | 
            +
            image_size.size.height  #=> 429
         | 
| 38 | 
            +
            image_size.size.w       #=> 436
         | 
| 39 | 
            +
            image_size.size.h       #=> 429
         | 
| 34 40 | 
             
            ```
         | 
| 35 41 |  | 
| 36 42 | 
             
            Or using `IO` object:
         | 
| 37 43 |  | 
| 38 44 | 
             
            ```ruby
         | 
| 39 | 
            -
            image_size = File.open('spec/ | 
| 45 | 
            +
            image_size = File.open('spec/images/jpeg/436x429.jpeg', 'rb'){ |fh| ImageSize.new(fh) }
         | 
| 40 46 | 
             
            ```
         | 
| 41 47 |  | 
| 42 48 | 
             
            Any object responding to `read` and `eof?`:
         | 
| @@ -68,14 +74,14 @@ So rewind if needed before passing to `ImageSize` and/or rewind after passing to | |
| 68 74 | 
             
            ```ruby
         | 
| 69 75 | 
             
            require 'image_size'
         | 
| 70 76 |  | 
| 71 | 
            -
            File.open('spec/ | 
| 77 | 
            +
            File.open('spec/images/jpeg/436x429.jpeg', 'rb') do |fh|
         | 
| 72 78 | 
             
              image_size = ImageSize.new(fh)
         | 
| 73 79 |  | 
| 74 80 | 
             
              fh.rewind
         | 
| 75 81 | 
             
              data = fh.read
         | 
| 76 82 | 
             
            end
         | 
| 77 83 |  | 
| 78 | 
            -
            File.open('spec/ | 
| 84 | 
            +
            File.open('spec/images/jpeg/436x429.jpeg', 'rb') do |fh|
         | 
| 79 85 | 
             
              data = fh.read
         | 
| 80 86 | 
             
              fh.rewind
         | 
| 81 87 |  | 
| @@ -85,9 +91,7 @@ end | |
| 85 91 |  | 
| 86 92 | 
             
            ## Licence
         | 
| 87 93 |  | 
| 88 | 
            -
            This code is free to use under the terms of the Ruby's licence.
         | 
| 94 | 
            +
            This code is free to use under the terms of the [Ruby's licence](LICENSE.txt).
         | 
| 89 95 |  | 
| 90 | 
            -
             | 
| 91 | 
            -
             | 
| 92 | 
            -
            Original author: "Keisuke Minami": mailto:keisuke@rccn.com
         | 
| 93 | 
            -
            Further development by Ivan Kuchin https://github.com/toy/image_size
         | 
| 96 | 
            +
            Original author: Keisuke Minami <keisuke@rccn.com>.\
         | 
| 97 | 
            +
            Further development 2010-2021 Ivan Kuchin https://github.com/toy/image_size
         | 
    
        data/image_size.gemspec
    CHANGED
    
    | @@ -2,10 +2,10 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            Gem::Specification.new do |s|
         | 
| 4 4 | 
             
              s.name        = 'image_size'
         | 
| 5 | 
            -
              s.version     = ' | 
| 5 | 
            +
              s.version     = '3.0.0'
         | 
| 6 6 | 
             
              s.summary     = %q{Measure image size using pure Ruby}
         | 
| 7 | 
            -
              s.description = %q{Measure following file dimensions: apng, bmp, cur, gif, jpeg,  | 
| 8 | 
            -
              s.homepage    = " | 
| 7 | 
            +
              s.description = %q{Measure following file dimensions: apng, bmp, cur, gif, ico, j2c, jp2, jpeg, jpx, mng, pam, pbm, pcx, pgm, png, ppm, psd, svg, swf, tiff, webp, xbm, xpm}
         | 
| 8 | 
            +
              s.homepage    = "https://github.com/toy/#{s.name}"
         | 
| 9 9 | 
             
              s.authors     = ['Keisuke Minami', 'Ivan Kuchin']
         | 
| 10 10 | 
             
              s.license     = 'Ruby'
         | 
| 11 11 |  | 
| @@ -22,7 +22,8 @@ Gem::Specification.new do |s| | |
| 22 22 | 
             
              s.require_paths = %w[lib]
         | 
| 23 23 |  | 
| 24 24 | 
             
              s.add_development_dependency 'rspec', '~> 3.0'
         | 
| 25 | 
            -
              if RUBY_VERSION >= '2. | 
| 26 | 
            -
                s.add_development_dependency 'rubocop', '~> 0 | 
| 25 | 
            +
              if RUBY_VERSION >= '2.4'
         | 
| 26 | 
            +
                s.add_development_dependency 'rubocop', '~> 1.0'
         | 
| 27 | 
            +
                s.add_development_dependency 'rubocop-rspec', '~> 2.0'
         | 
| 27 28 | 
             
              end
         | 
| 28 29 | 
             
            end
         | 
| @@ -0,0 +1,44 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'image_size/reader'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class ImageSize
         | 
| 6 | 
            +
              module ChunkyReader # :nodoc:
         | 
| 7 | 
            +
                include Reader
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                # Size of a chunk in which to read
         | 
| 10 | 
            +
                def chunk_size
         | 
| 11 | 
            +
                  4096
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                # Including class should define method chunk that accepts the chunk number
         | 
| 15 | 
            +
                # and returns a string of chunk_size length or shorter for last chunk, or
         | 
| 16 | 
            +
                # nil for further chunks.
         | 
| 17 | 
            +
                # Determines required chunks, takes parts of them to construct desired
         | 
| 18 | 
            +
                # substring, behaves same as str[start, length] except start can't be
         | 
| 19 | 
            +
                # negative.
         | 
| 20 | 
            +
                def [](offset, length)
         | 
| 21 | 
            +
                  raise ArgumentError, "expected offset not to be negative, got #{offset}" if offset < 0
         | 
| 22 | 
            +
                  return if length < 0
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  first = offset / chunk_size
         | 
| 25 | 
            +
                  return unless (first_chunk = chunk(first))
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  last = (offset + length - 1) / chunk_size
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  if first >= last
         | 
| 30 | 
            +
                    first_chunk[offset - (first * chunk_size), length]
         | 
| 31 | 
            +
                  else
         | 
| 32 | 
            +
                    return unless (first_piece = first_chunk[offset - (first * chunk_size), chunk_size])
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    chunks = (first.succ...last).map{ |i| chunk(i) }.unshift(first_piece)
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    if (last_chunk = chunk(last))
         | 
| 37 | 
            +
                      chunks.push(last_chunk[0, offset + length - (last * chunk_size)])
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                    chunks.join
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
            end
         | 
| @@ -0,0 +1,64 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'pathname'
         | 
| 4 | 
            +
            require 'stringio'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            class ImageSize
         | 
| 7 | 
            +
              module Reader # :nodoc:
         | 
| 8 | 
            +
                class << self
         | 
| 9 | 
            +
                  def open(input)
         | 
| 10 | 
            +
                    case
         | 
| 11 | 
            +
                    when input.is_a?(String)
         | 
| 12 | 
            +
                      yield StringReader.new(input)
         | 
| 13 | 
            +
                    when input.is_a?(StringIO)
         | 
| 14 | 
            +
                      yield StringReader.new(input.string)
         | 
| 15 | 
            +
                    when input.respond_to?(:read) && input.respond_to?(:eof?)
         | 
| 16 | 
            +
                      yield for_io(input)
         | 
| 17 | 
            +
                    when input.is_a?(Pathname)
         | 
| 18 | 
            +
                      input.open('rb'){ |f| yield for_io(f) }
         | 
| 19 | 
            +
                    else
         | 
| 20 | 
            +
                      raise ArgumentError, "expected data as String or an object responding to read and eof?, got #{input.class}"
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                private
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def for_io(io)
         | 
| 27 | 
            +
                    if io.respond_to?(:stat) && !io.stat.file?
         | 
| 28 | 
            +
                      StreamIOReader.new(io)
         | 
| 29 | 
            +
                    else
         | 
| 30 | 
            +
                      begin
         | 
| 31 | 
            +
                        io.seek(0, IO::SEEK_CUR)
         | 
| 32 | 
            +
                        SeekableIOReader.new(io)
         | 
| 33 | 
            +
                      rescue Errno::ESPIPE, Errno::EINVAL
         | 
| 34 | 
            +
                        StreamIOReader.new(io)
         | 
| 35 | 
            +
                      end
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def fetch(offset, length)
         | 
| 41 | 
            +
                  chunk = self[offset, length]
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  unless chunk && chunk.length == length
         | 
| 44 | 
            +
                    raise FormatError, "Expected #{length} bytes at offset #{offset}, got #{chunk.inspect}"
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  chunk
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def unpack(offset, length, format)
         | 
| 51 | 
            +
                  fetch(offset, length).unpack(format)
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                if ''.respond_to?(:unpack1)
         | 
| 55 | 
            +
                  def unpack1(offset, length, format)
         | 
| 56 | 
            +
                    fetch(offset, length).unpack1(format)
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                else
         | 
| 59 | 
            +
                  def unpack1(offset, length, format)
         | 
| 60 | 
            +
                    fetch(offset, length).unpack(format)[0]
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
            end
         | 
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'image_size/chunky_reader'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class ImageSize
         | 
| 6 | 
            +
              class SeekableIOReader # :nodoc:
         | 
| 7 | 
            +
                include ChunkyReader
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def initialize(io)
         | 
| 10 | 
            +
                  @io = io
         | 
| 11 | 
            +
                  @pos = 0
         | 
| 12 | 
            +
                  @chunks = {}
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              private
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def chunk(i)
         | 
| 18 | 
            +
                  unless @chunks.key?(i)
         | 
| 19 | 
            +
                    @io.seek((chunk_size * i) - @pos, IO::SEEK_CUR)
         | 
| 20 | 
            +
                    data = @io.read(chunk_size)
         | 
| 21 | 
            +
                    @pos += data.length
         | 
| 22 | 
            +
                    @chunks[i] = data
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  @chunks[i]
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'image_size/chunky_reader'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class ImageSize
         | 
| 6 | 
            +
              class StreamIOReader # :nodoc:
         | 
| 7 | 
            +
                include ChunkyReader
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def initialize(io)
         | 
| 10 | 
            +
                  @io = io
         | 
| 11 | 
            +
                  @chunks = []
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              private
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def chunk(i)
         | 
| 17 | 
            +
                  @chunks << @io.read(chunk_size) while i >= @chunks.length && !@io.eof?
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  @chunks[i]
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'image_size/reader'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class ImageSize
         | 
| 6 | 
            +
              class StringReader # :nodoc:
         | 
| 7 | 
            +
                include Reader
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def initialize(string)
         | 
| 10 | 
            +
                  @string = if string.respond_to?(:encoding) && string.encoding.name != 'ASCII-8BIT'
         | 
| 11 | 
            +
                    string.dup.force_encoding('ASCII-8BIT')
         | 
| 12 | 
            +
                  else
         | 
| 13 | 
            +
                    string
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def [](offset, length)
         | 
| 18 | 
            +
                  @string[offset, length]
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| @@ -0,0 +1,88 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'image_size/reader'
         | 
| 4 | 
            +
            require 'image_size/chunky_reader'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            require 'net/https'
         | 
| 7 | 
            +
            require 'uri'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            # This is a hacky experiment and not part of public API
         | 
| 10 | 
            +
            #
         | 
| 11 | 
            +
            # It adds ability to fetch size of image from http server while downloading only
         | 
| 12 | 
            +
            # needed chunks if the server recognises Range header
         | 
| 13 | 
            +
            class ImageSize
         | 
| 14 | 
            +
              class URIReader # :nodoc:
         | 
| 15 | 
            +
                include ChunkyReader
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def initialize(uri, redirects = 5)
         | 
| 18 | 
            +
                  if !@http || @http.address != uri.host || @http.port != uri.port
         | 
| 19 | 
            +
                    @http.finish if @http
         | 
| 20 | 
            +
                    @http = Net::HTTP.new(uri.host, uri.port)
         | 
| 21 | 
            +
                    @http.use_ssl = true if uri.scheme == 'https'
         | 
| 22 | 
            +
                    @http.start
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  @request_uri = uri.request_uri
         | 
| 26 | 
            +
                  response = request_chunk(0)
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  case response
         | 
| 29 | 
            +
                  when Net::HTTPRedirection
         | 
| 30 | 
            +
                    raise "Too many redirects: #{response['location']}" unless redirects > 0
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    initialize(uri + response['location'], redirects - 1)
         | 
| 33 | 
            +
                  when Net::HTTPOK
         | 
| 34 | 
            +
                    @body = response.body
         | 
| 35 | 
            +
                  when Net::HTTPPartialContent
         | 
| 36 | 
            +
                    @chunks = { 0 => response.body }
         | 
| 37 | 
            +
                  else
         | 
| 38 | 
            +
                    raise "Unexpected response: #{response}"
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def [](offset, length)
         | 
| 43 | 
            +
                  if @body
         | 
| 44 | 
            +
                    @body[offset, length]
         | 
| 45 | 
            +
                  else
         | 
| 46 | 
            +
                    super
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def chunk(i)
         | 
| 51 | 
            +
                  unless @chunks.key?(i)
         | 
| 52 | 
            +
                    response = request_chunk(i)
         | 
| 53 | 
            +
                    case response
         | 
| 54 | 
            +
                    when Net::HTTPPartialContent
         | 
| 55 | 
            +
                      @chunks[i] = response.body
         | 
| 56 | 
            +
                    else
         | 
| 57 | 
            +
                      raise "Unexpected response: #{response}"
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  @chunks[i]
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
              private
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                def request_chunk(i)
         | 
| 67 | 
            +
                  @http.get(@request_uri, 'Range' => "bytes=#{chunk_size * i}-#{(chunk_size * (i + 1)) - 1}")
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
              end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
              module Reader # :nodoc:
         | 
| 72 | 
            +
                class << self
         | 
| 73 | 
            +
                  def open_with_uri(input, &block)
         | 
| 74 | 
            +
                    if input.is_a?(URI)
         | 
| 75 | 
            +
                      yield URIReader.new(input)
         | 
| 76 | 
            +
                    else
         | 
| 77 | 
            +
                      open_without_uri(input, &block)
         | 
| 78 | 
            +
                    end
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
                  alias_method :open_without_uri, :open
         | 
| 81 | 
            +
                  alias_method :open, :open_with_uri
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
              end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
              def self.url(url)
         | 
| 86 | 
            +
                new(url.is_a?(URI) ? url : URI(url))
         | 
| 87 | 
            +
              end
         | 
| 88 | 
            +
            end
         |