ioposrw 0.4
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 +7 -0
- data/HISTORY.ja.md +21 -0
- data/QUICKREF.md +13 -0
- data/README.md +68 -0
- data/Rakefile +212 -0
- data/ext/extconf.rb +6 -0
- data/ext/ioposrw.c +582 -0
- data/gemstub.rb +35 -0
- data/lib/ioposrw.rb +35 -0
- data/lib/ioposrw/stringio.rb +6 -0
- data/lib/ioposrw/version.rb +3 -0
- data/test/test_ioposrw.rb +125 -0
- metadata +97 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: 4a73f9689f954e9a73d234ab71568e67f1ec97b8
         | 
| 4 | 
            +
              data.tar.gz: 85a997f0de8af73b8ed2d7d686da6e547987e240
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: 59e351ee0a4b470aabec3e2c3bc426e6db067839ed2ddb7e1a06df8ffff3b03a4e94965754b744dcafab0d7ef38541e8e9a52244808cfeb23547d3bb505abf65
         | 
| 7 | 
            +
              data.tar.gz: 13a4080d5465fd244647949bd7618f5238ee6bc80ead9e0d1ba804dd3d2a36139cea707d3a84868a7919363d31eef4cf81fbb68d21dc14f49e1ddee46586f3d4
         | 
    
        data/HISTORY.ja.md
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            # ioposrw 変更履歴
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            ## 0.4 (平成29年12月26日 火曜日)
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              * ``IO#pread``/``IO#pwrite`` を ``IO#readat``/``IO#writeat`` に変更
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                Ruby-2.5 で ``IO#pread``/``IO#pwrite`` が組み込まれましたが引数のとり方が異なるため、 ``IO#readat``/``IO#writeat`` として利用できるように変更しました。
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              * ファイル終端位置の指定を行えるようになった
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                ``readat``/``writeat`` の ``offset`` 引数に nil を与えることで、ファイルの終端位置を示すことが出来るように機能を変更しました。
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              * ``IOPositioningReadWrite`` モジュールを追加し、``IO`` クラスへ include するように変更
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                実装の実体を ``IOPositioningReadWrite`` モジュールに行い、IO クラスへ include するようにしました。
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              * "ioposrw/stringio" ライブラリの追加
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                ``IO.ioposrw_enable_stringio_extend`` メソッドは廃止する予定です。
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                代わりに ``require "ioposrw/stringio"`` を使って下さい。
         | 
    
        data/QUICKREF.md
    ADDED
    
    | @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            # QUICK REFERENCE FOR IOPOSRW
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            ## LIBRARIES
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            *   ``require "ioposrw"`` - main library
         | 
| 6 | 
            +
            *   ``require "ioposrw/stringio"`` - need when use ``StringIO#pread/pwrite``
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            ## METHODS
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            *   ``IO#pread`` - Similar functionality to the pread(2) system call. Implemented as ``IOPositioningReadWrite::IO#pread``.
         | 
| 11 | 
            +
            *   ``IO#pwrite`` - Similar functionality to the pwrite(2) system call. Implemented as ``IOPositioningReadWrite::IO#pwrite``.
         | 
| 12 | 
            +
            *   ``StringIO#pread`` - StringIO version pread(2). Implemented as ``IOPositioningReadWrite::StringIO#pread``.
         | 
| 13 | 
            +
            *   ``StringIO#pwrite`` - StringIO version pwrite(2). Implemented as ``IOPositioningReadWrite::StringIO#pwrite``.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,68 @@ | |
| 1 | 
            +
            # ioposrw
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            位置指定IO読み書きメソッド追加拡張ライブラリ
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              * Product Name: ioposrw
         | 
| 6 | 
            +
              * Version: 0.4
         | 
| 7 | 
            +
              * Project Page: https://github.com/dearblue/ruby-ioposrw
         | 
| 8 | 
            +
              * License: 2-clause BSD License (二条項 BSD ライセンス)
         | 
| 9 | 
            +
              * Author: dearblue <dearblue@users.noreply.github.com>
         | 
| 10 | 
            +
             | 
| 11 | 
            +
             | 
| 12 | 
            +
            ## これは何?
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            ioposrw は IO インスタンスに位置指定読み書き機能を追加する拡張ライブラリです。
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            この機能は ``IO#readat`` / ``IO#writeat`` によって提供されます。
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            これらのメソッドは、元々ある ``IO#read`` / ``IO#write`` によって更新されるファイル位置ポインタに影響されません。
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            マルチスレッド動作中に同じファイルインスタンスの別の領域を読み書きしても、想定通りの動作が期待できます。
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            追加の機能として、``StringIO#readat`` / ``StringIO#writeat`` も実装してあります (実際に利用する場合は、``require "ioposrw/stringio"`` を行って下さい)。
         | 
| 23 | 
            +
             | 
| 24 | 
            +
             | 
| 25 | 
            +
            ## どう使うのか?
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            ライブラリの読み込み:
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            ```ruby:ruby
         | 
| 30 | 
            +
            require "ioposrw"
         | 
| 31 | 
            +
            ```
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            ファイルの読み込み:
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            ```ruby:ruby
         | 
| 36 | 
            +
            File.open("sample.txt") do |file|
         | 
| 37 | 
            +
              buf = ""
         | 
| 38 | 
            +
              file.readat(200, 100, buf) # sample.txt の先頭 200 バイト位置から
         | 
| 39 | 
            +
                                         # 100 バイトを buf に読み込む
         | 
| 40 | 
            +
            end
         | 
| 41 | 
            +
            ```
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            ファイルの書き込み:
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            ```ruby:ruby
         | 
| 46 | 
            +
            File.open("sample.txt", "w") do |file|
         | 
| 47 | 
            +
              buf = "ごにょごにょ"
         | 
| 48 | 
            +
              file.writeat(200, buf) # sample.txt の先頭 200 バイト位置に buf を書き込む
         | 
| 49 | 
            +
            end
         | 
| 50 | 
            +
            ```
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            ## 注意事項について
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            - Windows 上では、``IO#readat / IO#writeat`` を呼び出すとファイルポインタ (IO#pos) が指定位置の次に変更されます (このことは丁度 ``IO#pos=`` と ``IO#read`` を呼び出した後の状態と考えてください)。
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                これは Windows 自身に伴う仕様となります。
         | 
| 57 | 
            +
             | 
| 58 | 
            +
             | 
| 59 | 
            +
            ## 内部の実装方法
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            POSIX システムコールの ``pread(2)`` / ``pwrite(2)`` を中心に実装しました。
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            Windows 環境では ``OVERRAPPED`` 構造体を与えた ``ReadFile`` / ``WriteFile`` をそれぞれ用いて ``pread`` / ``pwrite`` 関数を構築し、あとは同じです。
         | 
| 64 | 
            +
             | 
| 65 | 
            +
             | 
| 66 | 
            +
            ## ライセンスについて
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            二条項BSDライセンスの下で取り扱うことができます。
         | 
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1,212 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            require "pathname"
         | 
| 3 | 
            +
            require "rake/clean"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            docnames = "{README,LICENSE,CHANGELOG,Changelog,HISTORY}"
         | 
| 6 | 
            +
            doctypes = "{,.txt,.rd,.rdoc,.md,.markdown}"
         | 
| 7 | 
            +
            cexttypes = "{c,C,cc,cxx,cpp,h,H,hh}"
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            DOC = FileList["#{docnames}{,.ja}#{doctypes}"] +
         | 
| 10 | 
            +
                  FileList["{contrib,ext}/**/#{docnames}{,.ja}#{doctypes}"] +
         | 
| 11 | 
            +
                  FileList["ext/**/*.#{cexttypes}"]
         | 
| 12 | 
            +
            EXT = FileList["ext/**/*"]
         | 
| 13 | 
            +
            BIN = FileList["bin/*"]
         | 
| 14 | 
            +
            LIB = FileList["lib/**/*.rb"]
         | 
| 15 | 
            +
            SPEC = FileList["spec/**/*"]
         | 
| 16 | 
            +
            TEST = FileList["test/**/*"]
         | 
| 17 | 
            +
            EXAMPLE = FileList["examples/**/*"]
         | 
| 18 | 
            +
            GEMSTUB_SRC = "gemstub.rb"
         | 
| 19 | 
            +
            RAKEFILE = [File.basename(__FILE__), GEMSTUB_SRC]
         | 
| 20 | 
            +
            EXTRA = []
         | 
| 21 | 
            +
            EXTCONF = FileList["ext/**/extconf.rb"]
         | 
| 22 | 
            +
            EXTCONF.reject! { |n| !File.file?(n) }
         | 
| 23 | 
            +
            EXTMAP = {}
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            load GEMSTUB_SRC
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            EXTMAP.dup.each_pair do |dir, name|
         | 
| 28 | 
            +
              EXTMAP[Pathname.new(dir).cleanpath.to_s] = Pathname.new(name).cleanpath.to_s
         | 
| 29 | 
            +
            end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            GEMSTUB.extensions += EXTCONF
         | 
| 32 | 
            +
            GEMSTUB.executables += FileList["bin/*"].map { |n| File.basename n }
         | 
| 33 | 
            +
            GEMSTUB.executables.sort!
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            PACKAGENAME = "#{GEMSTUB.name}-#{GEMSTUB.version}"
         | 
| 36 | 
            +
            GEMFILE = "#{PACKAGENAME}.gem"
         | 
| 37 | 
            +
            GEMSPEC = "#{PACKAGENAME}.gemspec"
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            GEMSTUB.files += DOC + EXT + EXTCONF + BIN + LIB + SPEC + TEST + EXAMPLE + RAKEFILE + EXTRA
         | 
| 40 | 
            +
            GEMSTUB.files.sort!
         | 
| 41 | 
            +
            if GEMSTUB.rdoc_options.nil? || GEMSTUB.rdoc_options.empty?
         | 
| 42 | 
            +
              readme = %W(.md .markdown .rd .rdoc .txt #{""}).map { |ext| "README#{ext}" }.find { |m| DOC.find { |n| n == m } }
         | 
| 43 | 
            +
              GEMSTUB.rdoc_options = %w(--charset UTF-8) + (readme ? %W(-m #{readme}) : [])
         | 
| 44 | 
            +
            end
         | 
| 45 | 
            +
            GEMSTUB.extra_rdoc_files += DOC + LIB + EXT.reject { |n| n.include?("/externals/") || !%w(.h .hh .c .cc .cpp .cxx).include?(File.extname(n)) }
         | 
| 46 | 
            +
            GEMSTUB.extra_rdoc_files.sort!
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            GEMSTUB_TRYOUT = GEMSTUB.dup
         | 
| 49 | 
            +
            GEMSTUB_TRYOUT.version = "#{GEMSTUB.version}#{Time.now.strftime(".TRYOUT.%Y%m%d.%H%M%S")}"
         | 
| 50 | 
            +
            PACKAGENAME_TRYOUT = "#{GEMSTUB.name}-#{GEMSTUB_TRYOUT.version}"
         | 
| 51 | 
            +
            GEMFILE_TRYOUT = "#{PACKAGENAME_TRYOUT}.gem"
         | 
| 52 | 
            +
            GEMSPEC_TRYOUT = "#{PACKAGENAME_TRYOUT}.gemspec"
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            CLEAN << GEMSPEC << GEMSPEC_TRYOUT
         | 
| 55 | 
            +
            CLOBBER << GEMFILE
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            task :default => :tryout do
         | 
| 58 | 
            +
              $stderr.puts <<-EOS
         | 
| 59 | 
            +
            #{__FILE__}:#{__LINE__}:
         | 
| 60 | 
            +
            \ttype ``rake release'' to build release package.
         | 
| 61 | 
            +
              EOS
         | 
| 62 | 
            +
            end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            desc "build tryout package"
         | 
| 65 | 
            +
            task :tryout
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            desc "build release package"
         | 
| 68 | 
            +
            task :release => :all
         | 
| 69 | 
            +
             | 
| 70 | 
            +
            unless EXTCONF.empty?
         | 
| 71 | 
            +
              RUBYSET ||= (ENV["RUBYSET"] || "").split(",")
         | 
| 72 | 
            +
             | 
| 73 | 
            +
              if RUBYSET.nil? || RUBYSET.empty?
         | 
| 74 | 
            +
                $stderr.puts <<-EOS
         | 
| 75 | 
            +
            #{__FILE__}:
         | 
| 76 | 
            +
            |
         | 
| 77 | 
            +
            | If you want binary gem package, launch rake with ``RUBYSET`` enviroment
         | 
| 78 | 
            +
            | variable for set ruby interpreters by comma separated.
         | 
| 79 | 
            +
            |
         | 
| 80 | 
            +
            |   e.g.) $ rake RUBYSET=ruby
         | 
| 81 | 
            +
            |     or) $ rake RUBYSET=ruby21,ruby22,ruby23
         | 
| 82 | 
            +
            |
         | 
| 83 | 
            +
                EOS
         | 
| 84 | 
            +
              else
         | 
| 85 | 
            +
                platforms = RUBYSET.map { |ruby| `#{ruby} --disable-gems -e "puts RUBY_PLATFORM"`.chomp }
         | 
| 86 | 
            +
                platforms1 = platforms.uniq
         | 
| 87 | 
            +
                unless platforms1.size == 1 && !platforms1[0].empty?
         | 
| 88 | 
            +
                  abort <<-EOS
         | 
| 89 | 
            +
            #{__FILE__}:#{__LINE__}: different platforms:
         | 
| 90 | 
            +
            #{RUBYSET.zip(platforms).map { |ruby, platform| "%24s => %s" % [ruby, platform] }.join("\n")}
         | 
| 91 | 
            +
            ABORTED.
         | 
| 92 | 
            +
                  EOS
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
                PLATFORM = platforms1[0]
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                RUBY_VERSIONS = RUBYSET.map do |ruby|
         | 
| 97 | 
            +
                  ver = `#{ruby} --disable-gems -e "puts RUBY_VERSION"`.slice(/\d+\.\d+/)
         | 
| 98 | 
            +
                  raise "failed ruby checking - ``#{ruby}''" unless $?.success?
         | 
| 99 | 
            +
                  [ver, ruby]
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                SOFILES_SET = RUBY_VERSIONS.map { |(ver, ruby)|
         | 
| 103 | 
            +
                  EXTCONF.map { |extconf|
         | 
| 104 | 
            +
                    extdir = Pathname.new(extconf).cleanpath.dirname.to_s
         | 
| 105 | 
            +
                    case
         | 
| 106 | 
            +
                    when soname = EXTMAP[extdir.sub(/^ext\//i, "")]
         | 
| 107 | 
            +
                      soname = soname.sub(/\.so$/i, "")
         | 
| 108 | 
            +
                    when extdir == "ext" || extdir == "."
         | 
| 109 | 
            +
                      soname = GEMSTUB.name
         | 
| 110 | 
            +
                    else
         | 
| 111 | 
            +
                      soname = File.basename(extdir)
         | 
| 112 | 
            +
                    end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                    [ruby, File.join("lib", "#{soname.sub(/(?<=\/)|^(?!.*\/)/, "#{ver}/")}.so"), extconf]
         | 
| 115 | 
            +
                  }
         | 
| 116 | 
            +
                }.flatten(1)
         | 
| 117 | 
            +
                SOFILES = SOFILES_SET.map { |(ruby, sopath, extconf)| sopath }
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                GEMSTUB_NATIVE = GEMSTUB.dup
         | 
| 120 | 
            +
                GEMSTUB_NATIVE.files += SOFILES
         | 
| 121 | 
            +
                GEMSTUB_NATIVE.platform = Gem::Platform.new(PLATFORM).to_s
         | 
| 122 | 
            +
                GEMSTUB_NATIVE.extensions.clear
         | 
| 123 | 
            +
                GEMFILE_NATIVE = "#{GEMSTUB_NATIVE.name}-#{GEMSTUB_NATIVE.version}-#{GEMSTUB_NATIVE.platform}.gem"
         | 
| 124 | 
            +
                GEMSPEC_NATIVE = "#{GEMSTUB_NATIVE.name}-#{GEMSTUB_NATIVE.platform}.gemspec"
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                task :all => ["native-gem", GEMFILE]
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                desc "build binary gem package"
         | 
| 129 | 
            +
                task "native-gem" => GEMFILE_NATIVE
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                desc "generate binary gemspec"
         | 
| 132 | 
            +
                task "native-gemspec" => GEMSPEC_NATIVE
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                file GEMFILE_NATIVE => DOC + EXT + EXTCONF + BIN + LIB + SPEC + TEST + EXAMPLE + SOFILES + RAKEFILE + [GEMSPEC_NATIVE] do
         | 
| 135 | 
            +
                  sh "gem build #{GEMSPEC_NATIVE}"
         | 
| 136 | 
            +
                end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                file GEMSPEC_NATIVE => RAKEFILE do
         | 
| 139 | 
            +
                  File.write(GEMSPEC_NATIVE, GEMSTUB_NATIVE.to_ruby, mode: "wb")
         | 
| 140 | 
            +
                end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                desc "build c-extension libraries"
         | 
| 143 | 
            +
                task "sofiles" => SOFILES
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                SOFILES_SET.each do |(ruby, soname, extconf)|
         | 
| 146 | 
            +
                  sodir = File.dirname(soname)
         | 
| 147 | 
            +
                  makefile = File.join(sodir, "Makefile")
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                  CLEAN << GEMSPEC_NATIVE << sodir
         | 
| 150 | 
            +
                  CLOBBER << GEMFILE_NATIVE
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                  directory sodir
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                  desc "generate Makefile for binary extension library"
         | 
| 155 | 
            +
                  file makefile => [sodir, extconf] do
         | 
| 156 | 
            +
                    rel_extconf = Pathname.new(extconf).relative_path_from(Pathname.new(sodir)).to_s
         | 
| 157 | 
            +
                    cd sodir do
         | 
| 158 | 
            +
                      sh *%W"#{ruby} #{rel_extconf} --ruby=#{ruby} #{ENV["EXTCONF"]}"
         | 
| 159 | 
            +
                    end
         | 
| 160 | 
            +
                  end
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                  desc "build binary extension library"
         | 
| 163 | 
            +
                  file soname => [makefile] + EXT do
         | 
| 164 | 
            +
                    cd sodir do
         | 
| 165 | 
            +
                      sh "make"
         | 
| 166 | 
            +
                    end
         | 
| 167 | 
            +
                  end
         | 
| 168 | 
            +
                end
         | 
| 169 | 
            +
              end
         | 
| 170 | 
            +
            end
         | 
| 171 | 
            +
             | 
| 172 | 
            +
             | 
| 173 | 
            +
            task :all => GEMFILE
         | 
| 174 | 
            +
            task :tryout => GEMFILE_TRYOUT
         | 
| 175 | 
            +
             | 
| 176 | 
            +
            desc "generate local rdoc"
         | 
| 177 | 
            +
            task :rdoc => DOC + LIB do
         | 
| 178 | 
            +
              sh *(%w(rdoc) + GEMSTUB.rdoc_options + DOC + LIB)
         | 
| 179 | 
            +
            end
         | 
| 180 | 
            +
             | 
| 181 | 
            +
            desc "launch rspec"
         | 
| 182 | 
            +
            task rspec: :all do
         | 
| 183 | 
            +
              sh "rspec"
         | 
| 184 | 
            +
            end
         | 
| 185 | 
            +
             | 
| 186 | 
            +
            desc "build gem package"
         | 
| 187 | 
            +
            task gem: GEMFILE
         | 
| 188 | 
            +
             | 
| 189 | 
            +
            desc "generate gemspec"
         | 
| 190 | 
            +
            task gemspec: GEMSPEC
         | 
| 191 | 
            +
             | 
| 192 | 
            +
            desc "print package name"
         | 
| 193 | 
            +
            task "package-name" do
         | 
| 194 | 
            +
              puts PACKAGENAME
         | 
| 195 | 
            +
            end
         | 
| 196 | 
            +
             | 
| 197 | 
            +
            file GEMFILE => DOC + EXT + EXTCONF + BIN + LIB + SPEC + TEST + EXAMPLE + RAKEFILE + [GEMSPEC] do
         | 
| 198 | 
            +
              sh "gem build #{GEMSPEC}"
         | 
| 199 | 
            +
            end
         | 
| 200 | 
            +
             | 
| 201 | 
            +
            file GEMFILE_TRYOUT => DOC + EXT + EXTCONF + BIN + LIB + SPEC + TEST + EXAMPLE + RAKEFILE + [GEMSPEC_TRYOUT] do
         | 
| 202 | 
            +
            #file GEMFILE_TRYOUT do
         | 
| 203 | 
            +
              sh "gem build #{GEMSPEC_TRYOUT}"
         | 
| 204 | 
            +
            end
         | 
| 205 | 
            +
             | 
| 206 | 
            +
            file GEMSPEC => RAKEFILE do
         | 
| 207 | 
            +
              File.write(GEMSPEC, GEMSTUB.to_ruby, mode: "wb")
         | 
| 208 | 
            +
            end
         | 
| 209 | 
            +
             | 
| 210 | 
            +
            file GEMSPEC_TRYOUT => RAKEFILE do
         | 
| 211 | 
            +
              File.write(GEMSPEC_TRYOUT, GEMSTUB_TRYOUT.to_ruby, mode: "wb")
         | 
| 212 | 
            +
            end
         | 
    
        data/ext/extconf.rb
    ADDED
    
    
    
        data/ext/ioposrw.c
    ADDED
    
    | @@ -0,0 +1,582 @@ | |
| 1 | 
            +
            /* encoding:utf-8 */
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            /*
         | 
| 4 | 
            +
             * ioposrw.c -
         | 
| 5 | 
            +
             *
         | 
| 6 | 
            +
             * - Author: dearblue <dearblue@users.sourceforge.jp>
         | 
| 7 | 
            +
             * - License: 2-clause BSD License
         | 
| 8 | 
            +
             * - Project Page: http://sourceforge.jp/projects/rutsubo/
         | 
| 9 | 
            +
             */
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            #include <ruby.h>
         | 
| 12 | 
            +
            #include <ruby/io.h>
         | 
| 13 | 
            +
            #include <ruby/intern.h>
         | 
| 14 | 
            +
            #include <ruby/thread.h>
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            // rdoc 用に認識させるために使う
         | 
| 17 | 
            +
            #define RDOC(...)
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            #define NOGVL_FUNC(FUNC) ((void *(*)(void *))(FUNC))
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            #define FUNCALL(RECV, METHOD, ...)                                  \
         | 
| 22 | 
            +
                ({                                                              \
         | 
| 23 | 
            +
                    VALUE _params[] = { __VA_ARGS__ };                          \
         | 
| 24 | 
            +
                    rb_funcall3((RECV), (METHOD),                               \
         | 
| 25 | 
            +
                                sizeof(_params) / sizeof(_params[0]), _params); \
         | 
| 26 | 
            +
                })                                                              \
         | 
| 27 | 
            +
             | 
| 28 | 
            +
             | 
| 29 | 
            +
            #ifndef RUBY_WIN32_H
         | 
| 30 | 
            +
            #   include <unistd.h>
         | 
| 31 | 
            +
            #   include <sys/types.h>
         | 
| 32 | 
            +
            #   include <sys/stat.h>
         | 
| 33 | 
            +
            #else
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            #include <windows.h>
         | 
| 36 | 
            +
            #include <io.h>
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            static ssize_t
         | 
| 39 | 
            +
            pread(int d, void *buf, size_t nbytes, off_t offset)
         | 
| 40 | 
            +
            {
         | 
| 41 | 
            +
                HANDLE file = (HANDLE)_get_osfhandle(d);
         | 
| 42 | 
            +
                OVERLAPPED ol;
         | 
| 43 | 
            +
                memset(&ol, 0, sizeof(ol));
         | 
| 44 | 
            +
                ol.Offset = offset;
         | 
| 45 | 
            +
                ol.OffsetHigh = offset >> 32;
         | 
| 46 | 
            +
                DWORD read;
         | 
| 47 | 
            +
                DWORD status = ReadFile(file, buf, nbytes, &read, &ol);
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                if (status == 0) {
         | 
| 50 | 
            +
                    DWORD err = GetLastError();
         | 
| 51 | 
            +
                    if (err == ERROR_HANDLE_EOF) { return 0; }
         | 
| 52 | 
            +
                    errno = rb_w32_map_errno(err);
         | 
| 53 | 
            +
                    return -1;
         | 
| 54 | 
            +
                }
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                return read;
         | 
| 57 | 
            +
            }
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            static ssize_t
         | 
| 60 | 
            +
            pwrite(int d, const void *buf, size_t nbytes, off_t offset)
         | 
| 61 | 
            +
            {
         | 
| 62 | 
            +
                HANDLE file = (HANDLE)_get_osfhandle(d);
         | 
| 63 | 
            +
                OVERLAPPED ol;
         | 
| 64 | 
            +
                memset(&ol, 0, sizeof(ol));
         | 
| 65 | 
            +
                ol.Offset = offset;
         | 
| 66 | 
            +
                ol.OffsetHigh = offset >> 32;
         | 
| 67 | 
            +
                DWORD wrote;
         | 
| 68 | 
            +
                DWORD status = WriteFile(file, buf, nbytes, &wrote, &ol);
         | 
| 69 | 
            +
                if (status == 0) {
         | 
| 70 | 
            +
                    errno = rb_w32_map_errno(GetLastError());
         | 
| 71 | 
            +
                    return -1;
         | 
| 72 | 
            +
                }
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                return wrote;
         | 
| 75 | 
            +
            }
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            #endif /* ! RUBY_WIN32_H */
         | 
| 78 | 
            +
             | 
| 79 | 
            +
             | 
| 80 | 
            +
            /* ---- */
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            static VALUE mIOPosRW, mExtIO, mExtStrIO;
         | 
| 83 | 
            +
             | 
| 84 | 
            +
             | 
| 85 | 
            +
            static void
         | 
| 86 | 
            +
            ext_getoffset(VALUE io, int d, VALUE offset, off_t *offp)
         | 
| 87 | 
            +
            {
         | 
| 88 | 
            +
                struct stat st;
         | 
| 89 | 
            +
                if (NIL_P(offset)) {
         | 
| 90 | 
            +
                    if (fstat(d, &st) != 0) {
         | 
| 91 | 
            +
                        VALUE p = rb_inspect(io);
         | 
| 92 | 
            +
                        rb_raise(rb_eArgError,
         | 
| 93 | 
            +
                                 "unable to give a nil to `offset' for this IO object - %s",
         | 
| 94 | 
            +
                                 StringValueCStr(p));
         | 
| 95 | 
            +
                    }
         | 
| 96 | 
            +
                    *offp = st.st_size;
         | 
| 97 | 
            +
                } else {
         | 
| 98 | 
            +
                    *offp = NUM2OFFT(offset);
         | 
| 99 | 
            +
                    if (*offp < 0) {
         | 
| 100 | 
            +
                        if (fstat(d, &st) != 0) {
         | 
| 101 | 
            +
                            VALUE p = rb_inspect(io);
         | 
| 102 | 
            +
                            rb_raise(rb_eArgError,
         | 
| 103 | 
            +
                                     "unable to give a negative to `offset' for this IO object - %s",
         | 
| 104 | 
            +
                                     StringValueCStr(p));
         | 
| 105 | 
            +
                        }
         | 
| 106 | 
            +
                        *offp += st.st_size;
         | 
| 107 | 
            +
                    }
         | 
| 108 | 
            +
                    if (*offp < 0) {
         | 
| 109 | 
            +
                        VALUE p = rb_inspect(io);
         | 
| 110 | 
            +
                        rb_raise(rb_eArgError,
         | 
| 111 | 
            +
                                 "file offset too small at %"PRId64" - %s",
         | 
| 112 | 
            +
                                 (int64_t)NUM2OFFT(offset),
         | 
| 113 | 
            +
                                 StringValueCStr(p));
         | 
| 114 | 
            +
                    }
         | 
| 115 | 
            +
                }
         | 
| 116 | 
            +
            }
         | 
| 117 | 
            +
             | 
| 118 | 
            +
            struct readat_args
         | 
| 119 | 
            +
            {
         | 
| 120 | 
            +
                rb_io_t *fptr;
         | 
| 121 | 
            +
                void *buf;
         | 
| 122 | 
            +
                size_t nbytes;
         | 
| 123 | 
            +
                off_t offset;
         | 
| 124 | 
            +
            };
         | 
| 125 | 
            +
             | 
| 126 | 
            +
            static ssize_t
         | 
| 127 | 
            +
            io_readat_pread(struct readat_args *args)
         | 
| 128 | 
            +
            {
         | 
| 129 | 
            +
                return pread(args->fptr->fd, args->buf, args->nbytes, args->offset);
         | 
| 130 | 
            +
            }
         | 
| 131 | 
            +
             | 
| 132 | 
            +
            static ssize_t
         | 
| 133 | 
            +
            io_readat_try(struct readat_args *args)
         | 
| 134 | 
            +
            {
         | 
| 135 | 
            +
                return (ssize_t)rb_thread_call_without_gvl(NOGVL_FUNC(io_readat_pread),
         | 
| 136 | 
            +
                                                           args, RUBY_UBF_IO, 0);
         | 
| 137 | 
            +
            }
         | 
| 138 | 
            +
             | 
| 139 | 
            +
            enum {
         | 
| 140 | 
            +
                READ_BLOCK_SIZE = 1024 * 1024,
         | 
| 141 | 
            +
            };
         | 
| 142 | 
            +
             | 
| 143 | 
            +
            static inline VALUE
         | 
| 144 | 
            +
            io_readat_all(struct readat_args *args, VALUE buffer)
         | 
| 145 | 
            +
            {
         | 
| 146 | 
            +
                VALUE tmp = rb_str_buf_new(READ_BLOCK_SIZE);
         | 
| 147 | 
            +
                rb_str_set_len(tmp, 0);
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                if (NIL_P(buffer)) {
         | 
| 150 | 
            +
                    buffer = rb_str_buf_new(0);
         | 
| 151 | 
            +
                } else {
         | 
| 152 | 
            +
                    StringValue(buffer);
         | 
| 153 | 
            +
                }
         | 
| 154 | 
            +
                rb_str_set_len(buffer, 0);
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                rb_str_locktmp(buffer);
         | 
| 157 | 
            +
                args->buf = RSTRING_PTR(tmp);
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                for (;;) {
         | 
| 160 | 
            +
                    rb_str_resize(tmp, READ_BLOCK_SIZE);
         | 
| 161 | 
            +
                    args->nbytes = RSTRING_LEN(tmp);
         | 
| 162 | 
            +
                    ssize_t n = (ssize_t)rb_ensure(RUBY_METHOD_FUNC(io_readat_try), (VALUE)args, rb_str_unlocktmp, buffer);
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                    if (n == 0) {
         | 
| 165 | 
            +
                        break;
         | 
| 166 | 
            +
                    } else if (n < 0) {
         | 
| 167 | 
            +
                        rb_str_resize(tmp, 0);
         | 
| 168 | 
            +
                        rb_str_set_len(tmp, 0);
         | 
| 169 | 
            +
                        rb_sys_fail(NIL_P(args->fptr->pathv) ? NULL : RSTRING_PTR(args->fptr->pathv));
         | 
| 170 | 
            +
                    } else {
         | 
| 171 | 
            +
                        args->offset += n;
         | 
| 172 | 
            +
                        rb_str_set_len(tmp, n);
         | 
| 173 | 
            +
                        rb_str_concat(buffer, tmp);
         | 
| 174 | 
            +
                        rb_str_locktmp(buffer);
         | 
| 175 | 
            +
                    }
         | 
| 176 | 
            +
                }
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                rb_str_resize(tmp, 0);
         | 
| 179 | 
            +
                rb_str_set_len(tmp, 0);
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                if (RSTRING_LEN(buffer) == 0) {
         | 
| 182 | 
            +
                    return Qnil;
         | 
| 183 | 
            +
                } else {
         | 
| 184 | 
            +
                    return buffer;
         | 
| 185 | 
            +
                }
         | 
| 186 | 
            +
            }
         | 
| 187 | 
            +
             | 
| 188 | 
            +
            static inline VALUE
         | 
| 189 | 
            +
            io_readat_partial(struct readat_args *args, VALUE buffer)
         | 
| 190 | 
            +
            {
         | 
| 191 | 
            +
                if (NIL_P(buffer)) {
         | 
| 192 | 
            +
                    buffer = rb_str_buf_new(args->nbytes);
         | 
| 193 | 
            +
                } else {
         | 
| 194 | 
            +
                    StringValue(buffer);
         | 
| 195 | 
            +
                }
         | 
| 196 | 
            +
                rb_str_resize(buffer, args->nbytes);
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                rb_str_locktmp(buffer);
         | 
| 199 | 
            +
                args->buf = RSTRING_PTR(buffer);
         | 
| 200 | 
            +
                ssize_t n = (ssize_t)rb_ensure(RUBY_METHOD_FUNC(io_readat_try), (VALUE)args, rb_str_unlocktmp, buffer);
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                if (n == 0) {
         | 
| 203 | 
            +
                    return Qnil;
         | 
| 204 | 
            +
                } else if (n < 0) {
         | 
| 205 | 
            +
                    rb_sys_fail(NIL_P(args->fptr->pathv) ? NULL : RSTRING_PTR(args->fptr->pathv));
         | 
| 206 | 
            +
                }
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                rb_str_set_len(buffer, n);
         | 
| 209 | 
            +
                rb_str_resize(buffer, n);
         | 
| 210 | 
            +
             | 
| 211 | 
            +
                return buffer;
         | 
| 212 | 
            +
            }
         | 
| 213 | 
            +
             | 
| 214 | 
            +
            static void
         | 
| 215 | 
            +
            io_readat_security(VALUE io, VALUE offset, VALUE length, VALUE buffer)
         | 
| 216 | 
            +
            {
         | 
| 217 | 
            +
                if (rb_safe_level() < 3) {
         | 
| 218 | 
            +
                    return;
         | 
| 219 | 
            +
                }
         | 
| 220 | 
            +
             | 
| 221 | 
            +
                if (!OBJ_TAINTED(io)) {
         | 
| 222 | 
            +
                    rb_secure(4);
         | 
| 223 | 
            +
                    if (!OBJ_TAINTED(offset) && !OBJ_TAINTED(length) && !OBJ_TAINTED(buffer)) {
         | 
| 224 | 
            +
                        return;
         | 
| 225 | 
            +
                    }
         | 
| 226 | 
            +
                } else {
         | 
| 227 | 
            +
                    if (OBJ_TAINTED(offset) &&
         | 
| 228 | 
            +
                        (NIL_P(length) || OBJ_TAINTED(length)) &&
         | 
| 229 | 
            +
                        (NIL_P(buffer) || OBJ_TAINTED(buffer))) {
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                        return;
         | 
| 232 | 
            +
                    }
         | 
| 233 | 
            +
                }
         | 
| 234 | 
            +
             | 
| 235 | 
            +
                rb_secure(3);
         | 
| 236 | 
            +
            }
         | 
| 237 | 
            +
             | 
| 238 | 
            +
            /*
         | 
| 239 | 
            +
             * call-seq:
         | 
| 240 | 
            +
             *  readat(offset) -> string or nil
         | 
| 241 | 
            +
             *  readat(offset, length) -> string or nil
         | 
| 242 | 
            +
             *  readat(offset, length, buffer) -> buffer or nil
         | 
| 243 | 
            +
             *
         | 
| 244 | 
            +
             * 指定位置から読み込むことが出来ます。
         | 
| 245 | 
            +
             *
         | 
| 246 | 
            +
             * IO#read との違いは、IO#pos= と IO#read との間でスレッドスイッチが発生することによる、
         | 
| 247 | 
            +
             * 意図したストリーム位置から #read できないという問題がないことです。
         | 
| 248 | 
            +
             *
         | 
| 249 | 
            +
             * [RETURN]
         | 
| 250 | 
            +
             *      正常に読み込んだ場合、データが格納された buffer、もしくは String インスタンスを返します。
         | 
| 251 | 
            +
             *
         | 
| 252 | 
            +
             *      ファイルサイズを越えた位置を offset に与えた場合、nil を返します。
         | 
| 253 | 
            +
             *
         | 
| 254 | 
            +
             * [offset]
         | 
| 255 | 
            +
             *      読み込み位置をバイト値で指定します。
         | 
| 256 | 
            +
             *
         | 
| 257 | 
            +
             *      これはストリーム先端からの絶対位置となります。
         | 
| 258 | 
            +
             *
         | 
| 259 | 
            +
             *      負の整数を指定した場合、ファイル終端からの相対位置になります。
         | 
| 260 | 
            +
             *      File#seek に SEEK_END で負の値を指定した場合と同じです。
         | 
| 261 | 
            +
             *
         | 
| 262 | 
            +
             *      nil を指定した場合、<tt>seek(0, SEEK_END)</tt> と同等となります。
         | 
| 263 | 
            +
             *
         | 
| 264 | 
            +
             * [length (省略可能)]
         | 
| 265 | 
            +
             *      読み込むデータ長をバイト値で指定します。
         | 
| 266 | 
            +
             *
         | 
| 267 | 
            +
             *      省略もしくは nil を指定すると、ファイルの最後まで読み込みます。
         | 
| 268 | 
            +
             *
         | 
| 269 | 
            +
             * [buffer (省略可能)]
         | 
| 270 | 
            +
             *      読み込み先バッファとして指定します。
         | 
| 271 | 
            +
             *
         | 
| 272 | 
            +
             *      省略もしくは nil を指定すると、IO#readat 内部で勝手に用意します。
         | 
| 273 | 
            +
             *
         | 
| 274 | 
            +
             *      buffer は変更可能 (frozen ではない String) なインスタンスである必要があります。
         | 
| 275 | 
            +
             *
         | 
| 276 | 
            +
             * [EXCEPTIONS]
         | 
| 277 | 
            +
             *      いろいろ。
         | 
| 278 | 
            +
             *
         | 
| 279 | 
            +
             *      Errno::EXXX だったり、SEGV だったり、これらにとどまりません。
         | 
| 280 | 
            +
             *
         | 
| 281 | 
            +
             * 処理中は呼び出しスレッドのみが停止します (GVL を開放します)。
         | 
| 282 | 
            +
             * その間別スレッドから buffer オブジェクトの変更はできなくなります
         | 
| 283 | 
            +
             * (厳密に言うと、内部バッファの伸縮を伴う操作が出来なくなるだけです)。
         | 
| 284 | 
            +
             *
         | 
| 285 | 
            +
             *
         | 
| 286 | 
            +
             * ==== 注意点
         | 
| 287 | 
            +
             *
         | 
| 288 | 
            +
             * Windows においては、#readat 後に IO 位置が更新されます (IO#pos= と IO#read した後と同じ位置)。
         | 
| 289 | 
            +
             * これは Windows 上の制限となります。
         | 
| 290 | 
            +
             */
         | 
| 291 | 
            +
            static VALUE
         | 
| 292 | 
            +
            io_readat(int argc, VALUE argv[], VALUE io)
         | 
| 293 | 
            +
            {
         | 
| 294 | 
            +
                VALUE offset, length = Qnil, buffer = Qnil;
         | 
| 295 | 
            +
                struct readat_args args;
         | 
| 296 | 
            +
             | 
| 297 | 
            +
                rb_scan_args(argc, argv, "12", &offset, &length, &buffer);
         | 
| 298 | 
            +
                io_readat_security(io, offset, length, buffer);
         | 
| 299 | 
            +
                rb_check_type(io, RUBY_T_FILE);
         | 
| 300 | 
            +
             | 
| 301 | 
            +
                GetOpenFile(io, args.fptr);
         | 
| 302 | 
            +
                rb_io_check_char_readable(args.fptr);
         | 
| 303 | 
            +
                ext_getoffset(io, args.fptr->fd, offset, &args.offset);
         | 
| 304 | 
            +
             | 
| 305 | 
            +
                if (NIL_P(length)) {
         | 
| 306 | 
            +
                    buffer = io_readat_all(&args, buffer);
         | 
| 307 | 
            +
                } else {
         | 
| 308 | 
            +
                    args.nbytes = NUM2SIZET(length);
         | 
| 309 | 
            +
                    if (args.nbytes > 0) {
         | 
| 310 | 
            +
                        buffer = io_readat_partial(&args, buffer);
         | 
| 311 | 
            +
                    } else {
         | 
| 312 | 
            +
                        if (NIL_P(buffer)) {
         | 
| 313 | 
            +
                            buffer = rb_str_new(0, 0);
         | 
| 314 | 
            +
                        } else {
         | 
| 315 | 
            +
                            rb_str_set_len(buffer, 0);
         | 
| 316 | 
            +
                        }
         | 
| 317 | 
            +
                    }
         | 
| 318 | 
            +
                }
         | 
| 319 | 
            +
             | 
| 320 | 
            +
                if (!NIL_P(buffer)) {
         | 
| 321 | 
            +
                    OBJ_TAINT(buffer);
         | 
| 322 | 
            +
                }
         | 
| 323 | 
            +
             | 
| 324 | 
            +
                return buffer;
         | 
| 325 | 
            +
            }
         | 
| 326 | 
            +
             | 
| 327 | 
            +
             | 
| 328 | 
            +
            struct writeat_args
         | 
| 329 | 
            +
            {
         | 
| 330 | 
            +
                rb_io_t *fptr;
         | 
| 331 | 
            +
                const void *buf;
         | 
| 332 | 
            +
                size_t nbytes;
         | 
| 333 | 
            +
                off_t offset;
         | 
| 334 | 
            +
            };
         | 
| 335 | 
            +
             | 
| 336 | 
            +
            static ssize_t
         | 
| 337 | 
            +
            io_writeat_pwrite(struct writeat_args *args)
         | 
| 338 | 
            +
            {
         | 
| 339 | 
            +
                return pwrite(args->fptr->fd, args->buf, args->nbytes, args->offset);
         | 
| 340 | 
            +
            }
         | 
| 341 | 
            +
             | 
| 342 | 
            +
            static ssize_t
         | 
| 343 | 
            +
            io_writeat_try(struct writeat_args *args)
         | 
| 344 | 
            +
            {
         | 
| 345 | 
            +
                return (ssize_t)rb_thread_call_without_gvl(NOGVL_FUNC(io_writeat_pwrite), args, RUBY_UBF_IO, 0);
         | 
| 346 | 
            +
            }
         | 
| 347 | 
            +
             | 
| 348 | 
            +
            /*
         | 
| 349 | 
            +
             * call-seq:
         | 
| 350 | 
            +
             *  writeat(offset, buffer) -> wrote size
         | 
| 351 | 
            +
             *
         | 
| 352 | 
            +
             * 指定位置への書き込みを行います。
         | 
| 353 | 
            +
             *
         | 
| 354 | 
            +
             * [RETURN]
         | 
| 355 | 
            +
             *      書き込んだデータ量がバイト値で返ります。
         | 
| 356 | 
            +
             *
         | 
| 357 | 
            +
             * [offset]
         | 
| 358 | 
            +
             *      書き込み位置をバイト値で指定します。これはストリーム先端からの絶対位置となります。
         | 
| 359 | 
            +
             *
         | 
| 360 | 
            +
             * [buffer]
         | 
| 361 | 
            +
             *      書き込みたいデータが格納されたStringインスタンスを指定します。
         | 
| 362 | 
            +
             *
         | 
| 363 | 
            +
             * [EXCEPTIONS]
         | 
| 364 | 
            +
             *      Errno::EXXX や、SEGV など。
         | 
| 365 | 
            +
             *
         | 
| 366 | 
            +
             * 処理中は呼び出しスレッドのみが停止します (GVL を開放します)。
         | 
| 367 | 
            +
             * また、別スレッドから buffer オブジェクトの変更はできなくなります
         | 
| 368 | 
            +
             * (厳密に言うと、内部バッファの伸縮を伴う操作が出来なくなるだけです)。
         | 
| 369 | 
            +
             */
         | 
| 370 | 
            +
            static VALUE
         | 
| 371 | 
            +
            io_writeat(VALUE io, VALUE offset, VALUE buffer)
         | 
| 372 | 
            +
            {
         | 
| 373 | 
            +
                rb_secure(4);
         | 
| 374 | 
            +
                rb_check_type(io, RUBY_T_FILE);
         | 
| 375 | 
            +
             | 
| 376 | 
            +
                struct writeat_args args;
         | 
| 377 | 
            +
             | 
| 378 | 
            +
                StringValue(buffer);
         | 
| 379 | 
            +
                args.nbytes = RSTRING_LEN(buffer);
         | 
| 380 | 
            +
                GetOpenFile(io, args.fptr);
         | 
| 381 | 
            +
                rb_io_check_closed(args.fptr);
         | 
| 382 | 
            +
                ext_getoffset(io, args.fptr->fd, offset, &args.offset);
         | 
| 383 | 
            +
             | 
| 384 | 
            +
                rb_str_locktmp(buffer);
         | 
| 385 | 
            +
                args.buf = RSTRING_PTR(buffer);
         | 
| 386 | 
            +
                ssize_t n = (ssize_t)rb_ensure(RUBY_METHOD_FUNC(io_writeat_try), (VALUE)&args, rb_str_unlocktmp, buffer);
         | 
| 387 | 
            +
             | 
| 388 | 
            +
                if (n < 0) {
         | 
| 389 | 
            +
                    rb_sys_fail(NIL_P(args.fptr->pathv) ? NULL : RSTRING_PTR(args.fptr->pathv));
         | 
| 390 | 
            +
                }
         | 
| 391 | 
            +
             | 
| 392 | 
            +
                return SIZET2NUM(n);
         | 
| 393 | 
            +
            }
         | 
| 394 | 
            +
             | 
| 395 | 
            +
             | 
| 396 | 
            +
            /* ---- */
         | 
| 397 | 
            +
             | 
| 398 | 
            +
             | 
| 399 | 
            +
            static VALUE cStringIO;
         | 
| 400 | 
            +
            static ID IDstring, IDis_closed_read, IDis_closed_write;
         | 
| 401 | 
            +
             | 
| 402 | 
            +
             | 
| 403 | 
            +
            static inline VALUE
         | 
| 404 | 
            +
            strio_getstr(VALUE io)
         | 
| 405 | 
            +
            {
         | 
| 406 | 
            +
                VALUE str = FUNCALL(io, IDstring);
         | 
| 407 | 
            +
                rb_check_type(str, RUBY_T_STRING);
         | 
| 408 | 
            +
                return str;
         | 
| 409 | 
            +
            }
         | 
| 410 | 
            +
             | 
| 411 | 
            +
            static inline VALUE
         | 
| 412 | 
            +
            strio_getread(VALUE io)
         | 
| 413 | 
            +
            {
         | 
| 414 | 
            +
                if (RTEST(FUNCALL(io, IDis_closed_read))) {
         | 
| 415 | 
            +
                    rb_raise(rb_eIOError, "not opened for reading");
         | 
| 416 | 
            +
                }
         | 
| 417 | 
            +
             | 
| 418 | 
            +
                return strio_getstr(io);
         | 
| 419 | 
            +
            }
         | 
| 420 | 
            +
             | 
| 421 | 
            +
            static inline VALUE
         | 
| 422 | 
            +
            strio_getwrite(VALUE io)
         | 
| 423 | 
            +
            {
         | 
| 424 | 
            +
                if (RTEST(FUNCALL(io, IDis_closed_write))) {
         | 
| 425 | 
            +
                    rb_raise(rb_eIOError, "not opened for writing");
         | 
| 426 | 
            +
                }
         | 
| 427 | 
            +
             | 
| 428 | 
            +
                return strio_getstr(io);
         | 
| 429 | 
            +
            }
         | 
| 430 | 
            +
             | 
| 431 | 
            +
             | 
| 432 | 
            +
            /*
         | 
| 433 | 
            +
             * call-seq:
         | 
| 434 | 
            +
             *  readat(offset) -> nil or string
         | 
| 435 | 
            +
             *  readat(offset, length) -> nil or string
         | 
| 436 | 
            +
             *  readat(offset, length, buffer) -> nil or buffer
         | 
| 437 | 
            +
             *
         | 
| 438 | 
            +
             * IO#readat と同じ。
         | 
| 439 | 
            +
             *
         | 
| 440 | 
            +
             * ただし、例外の搬出は挙動が異なります。このあたりは将来的に (200年後くらいには) 改善される見込みです。
         | 
| 441 | 
            +
             *
         | 
| 442 | 
            +
             * 処理中は Ruby のすべてのスレッドが停止します (GVL を開放しないため)。
         | 
| 443 | 
            +
             */
         | 
| 444 | 
            +
            static VALUE
         | 
| 445 | 
            +
            strio_readat(int argc, VALUE argv[], VALUE io)
         | 
| 446 | 
            +
            {
         | 
| 447 | 
            +
                VALUE offset, length = Qnil, buffer = Qnil;
         | 
| 448 | 
            +
             | 
| 449 | 
            +
                rb_scan_args(argc, argv, "12", &offset, &length, &buffer);
         | 
| 450 | 
            +
             | 
| 451 | 
            +
                VALUE str = strio_getread(io);
         | 
| 452 | 
            +
             | 
| 453 | 
            +
                VALUE tmp = Qnil;
         | 
| 454 | 
            +
                if (NIL_P(buffer)) {
         | 
| 455 | 
            +
                    tmp = buffer = rb_str_buf_new(0);
         | 
| 456 | 
            +
                }
         | 
| 457 | 
            +
             | 
| 458 | 
            +
                ssize_t lenmax = RSTRING_LEN(str);
         | 
| 459 | 
            +
                ssize_t len = NIL_P(length) ? lenmax : NUM2SSIZET(length);
         | 
| 460 | 
            +
             | 
| 461 | 
            +
                ssize_t off = NUM2SSIZET(offset);
         | 
| 462 | 
            +
                if (off < 0) {
         | 
| 463 | 
            +
                    off += lenmax;
         | 
| 464 | 
            +
                }
         | 
| 465 | 
            +
             | 
| 466 | 
            +
                if (len == 0) {
         | 
| 467 | 
            +
                    if (NIL_P(buffer)) {
         | 
| 468 | 
            +
                        buffer = rb_str_new(0, 0);
         | 
| 469 | 
            +
                    } else {
         | 
| 470 | 
            +
                        rb_str_modify(buffer);
         | 
| 471 | 
            +
                        rb_str_set_len(buffer, 0);
         | 
| 472 | 
            +
                    }
         | 
| 473 | 
            +
                    OBJ_INFECT(buffer, io);
         | 
| 474 | 
            +
                    return buffer;
         | 
| 475 | 
            +
                }
         | 
| 476 | 
            +
             | 
| 477 | 
            +
                if (off >= lenmax) { return Qnil; }
         | 
| 478 | 
            +
             | 
| 479 | 
            +
                if (off + len < 0) {
         | 
| 480 | 
            +
                    rb_raise(rb_eRangeError, "offset and length are too small or too large");
         | 
| 481 | 
            +
                }
         | 
| 482 | 
            +
             | 
| 483 | 
            +
                if (off + len > lenmax) { len = lenmax - off; }
         | 
| 484 | 
            +
             | 
| 485 | 
            +
                StringValue(buffer);
         | 
| 486 | 
            +
                rb_str_modify(buffer);
         | 
| 487 | 
            +
                rb_str_resize(buffer, len);
         | 
| 488 | 
            +
                rb_str_set_len(buffer, len);
         | 
| 489 | 
            +
             | 
| 490 | 
            +
                memcpy(RSTRING_PTR(buffer), RSTRING_PTR(str) + off, len);
         | 
| 491 | 
            +
             | 
| 492 | 
            +
                OBJ_INFECT(buffer, str);
         | 
| 493 | 
            +
             | 
| 494 | 
            +
                return buffer;
         | 
| 495 | 
            +
            }
         | 
| 496 | 
            +
             | 
| 497 | 
            +
            /*
         | 
| 498 | 
            +
             * call-seq:
         | 
| 499 | 
            +
             *  writeat(offset, buffer) -> integer
         | 
| 500 | 
            +
             *
         | 
| 501 | 
            +
             * IO#writeat と同じ。
         | 
| 502 | 
            +
             *
         | 
| 503 | 
            +
             * ただし、例外の搬出は挙動が異なります。このあたりは将来的に (200年後くらいには) 改善される見込みです。
         | 
| 504 | 
            +
             *
         | 
| 505 | 
            +
             * 処理中は Ruby のすべてのスレッドが停止します (GVL を開放しないため)。
         | 
| 506 | 
            +
             */
         | 
| 507 | 
            +
            static VALUE
         | 
| 508 | 
            +
            strio_writeat(VALUE io, VALUE offset, VALUE buffer)
         | 
| 509 | 
            +
            {
         | 
| 510 | 
            +
                rb_secure(4);
         | 
| 511 | 
            +
             | 
| 512 | 
            +
                VALUE str = strio_getwrite(io);
         | 
| 513 | 
            +
             | 
| 514 | 
            +
                ssize_t off = NUM2SSIZET(offset);
         | 
| 515 | 
            +
                if (off < 0) { NUM2SSIZET(SIZET2NUM(off)); } // EXCEPTION!
         | 
| 516 | 
            +
                StringValue(buffer);
         | 
| 517 | 
            +
                ssize_t len = RSTRING_LEN(buffer);
         | 
| 518 | 
            +
                if (len < 0) { NUM2SSIZET(SIZET2NUM(len)); } // EXCEPTION!
         | 
| 519 | 
            +
                if (off + len < 0) {
         | 
| 520 | 
            +
                    rb_raise(rb_eRangeError,
         | 
| 521 | 
            +
                             "offset and buffer size are too large");
         | 
| 522 | 
            +
                }
         | 
| 523 | 
            +
             | 
| 524 | 
            +
                if (off + len > RSTRING_LEN(str)) {
         | 
| 525 | 
            +
                    ssize_t p = RSTRING_LEN(str);
         | 
| 526 | 
            +
                    rb_str_modify(buffer);
         | 
| 527 | 
            +
                    rb_str_resize(str, off + len);
         | 
| 528 | 
            +
                    rb_str_set_len(str, off + len);
         | 
| 529 | 
            +
                    if (off > p) {
         | 
| 530 | 
            +
                        memset(RSTRING_PTR(str) + p, 0, off - p);
         | 
| 531 | 
            +
                    }
         | 
| 532 | 
            +
                } else {
         | 
| 533 | 
            +
                    rb_str_modify(str);
         | 
| 534 | 
            +
                }
         | 
| 535 | 
            +
             | 
| 536 | 
            +
                memcpy(RSTRING_PTR(str) + off, RSTRING_PTR(buffer), len);
         | 
| 537 | 
            +
             | 
| 538 | 
            +
                return SSIZET2NUM(len);
         | 
| 539 | 
            +
            }
         | 
| 540 | 
            +
             | 
| 541 | 
            +
             | 
| 542 | 
            +
            /*
         | 
| 543 | 
            +
             * Document-class: IO
         | 
| 544 | 
            +
             *
         | 
| 545 | 
            +
             * <b>現段階の実装状況においてセーフレベルをまともに扱っていないため、セキュリティリスクがあります。</b>
         | 
| 546 | 
            +
             *
         | 
| 547 | 
            +
             * この拡張ライブラリは IO#readat / IO#writeat を提供します。
         | 
| 548 | 
            +
             *
         | 
| 549 | 
            +
             * <tt>require "ioposrw"</tt> によって利用できるようになります。
         | 
| 550 | 
            +
             */
         | 
| 551 | 
            +
             | 
| 552 | 
            +
            /*
         | 
| 553 | 
            +
             * Document-class: StringIO
         | 
| 554 | 
            +
             *
         | 
| 555 | 
            +
             * <b>現段階の実装状況においてセーフレベルをまともに扱っていないため、セキュリティリスクがあります。</b>
         | 
| 556 | 
            +
             *
         | 
| 557 | 
            +
             * StringIO#readat / StringIO#writeat を提供します。
         | 
| 558 | 
            +
             *
         | 
| 559 | 
            +
             * StringIO#readat / StringIO#writeat は <tt>require "ioposrw"</tt> しただけでは有効になりません。
         | 
| 560 | 
            +
             *
         | 
| 561 | 
            +
             * <tt>require "ioposrw/stringio"</tt> をして下さい。
         | 
| 562 | 
            +
             *
         | 
| 563 | 
            +
             * まだ <tt>require "stringio"</tt> されていなければ勝手に読み込みます。
         | 
| 564 | 
            +
             */
         | 
| 565 | 
            +
             | 
| 566 | 
            +
            void
         | 
| 567 | 
            +
            Init_ioposrw(void)
         | 
| 568 | 
            +
            {
         | 
| 569 | 
            +
                IDstring          = rb_intern_const("string");
         | 
| 570 | 
            +
                IDis_closed_read  = rb_intern_const("closed_read?");
         | 
| 571 | 
            +
                IDis_closed_write = rb_intern_const("closed_write?");
         | 
| 572 | 
            +
             | 
| 573 | 
            +
                mIOPosRW = rb_define_module("IOPositioningReadWrite");
         | 
| 574 | 
            +
                mExtIO = rb_define_module_under(mIOPosRW, "IO");
         | 
| 575 | 
            +
                mExtStrIO = rb_define_module_under(mIOPosRW, "StringIO");
         | 
| 576 | 
            +
             | 
| 577 | 
            +
                rb_define_method(mExtIO, "readat", RUBY_METHOD_FUNC(io_readat), -1);
         | 
| 578 | 
            +
                rb_define_method(mExtIO, "writeat", RUBY_METHOD_FUNC(io_writeat), 2);
         | 
| 579 | 
            +
             | 
| 580 | 
            +
                rb_define_method(mExtStrIO, "readat", RUBY_METHOD_FUNC(strio_readat), -1);
         | 
| 581 | 
            +
                rb_define_method(mExtStrIO, "writeat", RUBY_METHOD_FUNC(strio_writeat), 2);
         | 
| 582 | 
            +
            }
         | 
    
        data/gemstub.rb
    ADDED
    
    | @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            unless File.read("README.md", 4096) =~ /^\s*\*\s*version:{1,2}\s*(.+)/i
         | 
| 2 | 
            +
              raise "バージョン情報が README.md に見つかりません"
         | 
| 3 | 
            +
            end
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ver = $1
         | 
| 6 | 
            +
            verfile = "lib/ioposrw/version.rb"
         | 
| 7 | 
            +
            LIB << verfile
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            file verfile => "README.md" do |*args|
         | 
| 10 | 
            +
              File.binwrite args[0].name, <<-VERSION_FILE
         | 
| 11 | 
            +
            module IOPositioningReadWrite
         | 
| 12 | 
            +
              VERSION = "#{ver}"
         | 
| 13 | 
            +
            end
         | 
| 14 | 
            +
              VERSION_FILE
         | 
| 15 | 
            +
            end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
             | 
| 18 | 
            +
            GEMSTUB = Gem::Specification.new do |s|
         | 
| 19 | 
            +
              s.name = "ioposrw"
         | 
| 20 | 
            +
              s.version = ver
         | 
| 21 | 
            +
              s.summary = "Append IO#readat/writeat methods. These provide similar functionality to the POSIX pread/pwrite."
         | 
| 22 | 
            +
              s.description = <<EOS
         | 
| 23 | 
            +
            Append IO#readat/writeat methods. These provide similar functionality to the POSIX pread/pwrite.
         | 
| 24 | 
            +
            This library works on windows and unix. But tested on windows and FreeBSD only.
         | 
| 25 | 
            +
            EOS
         | 
| 26 | 
            +
              s.license = "BSD-2-Clause License"
         | 
| 27 | 
            +
              s.author = "dearblue"
         | 
| 28 | 
            +
              s.email = "dearblue@users.noreply.github.com"
         | 
| 29 | 
            +
              s.homepage = "https://github.com/dearblue/ruby-ioposrw"
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              s.add_development_dependency "rake"
         | 
| 32 | 
            +
              s.add_development_dependency "test-unit"
         | 
| 33 | 
            +
            end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            DOC << "QUICKREF.md"
         | 
    
        data/lib/ioposrw.rb
    ADDED
    
    | @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            ver = RUBY_VERSION.slice(/\d+\.\d+/)
         | 
| 2 | 
            +
            soname = File.basename(__FILE__, ".rb") << ".so"
         | 
| 3 | 
            +
            lib = File.join(File.dirname(__FILE__), ver, soname)
         | 
| 4 | 
            +
            if File.file?(lib)
         | 
| 5 | 
            +
              require_relative File.join(ver, soname)
         | 
| 6 | 
            +
            else
         | 
| 7 | 
            +
              require_relative soname
         | 
| 8 | 
            +
            end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            require_relative "ioposrw/version"
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            module IOPositioningReadWrite
         | 
| 13 | 
            +
              module IOClass
         | 
| 14 | 
            +
                #
         | 
| 15 | 
            +
                # call-seq:
         | 
| 16 | 
            +
                #  IO.ioposrw_enable_stringio_extend -> nil
         | 
| 17 | 
            +
                #
         | 
| 18 | 
            +
                # This is a feature that is FUTURE OBSOLUTE.
         | 
| 19 | 
            +
                #
         | 
| 20 | 
            +
                # Please use <tt>require "ioposrw/stringio"</tt> insted.
         | 
| 21 | 
            +
                #
         | 
| 22 | 
            +
                def ioposrw_enable_stringio_extend
         | 
| 23 | 
            +
                  warn <<-EOM
         | 
| 24 | 
            +
            (warning) #{caller(1, 1)[0]}: IO.ioposrw_enable_stringio_extend is FUTURE OBSOLUTE. please use ``require \"ioposrw/stringio\"'' insted.
         | 
| 25 | 
            +
                  EOM
         | 
| 26 | 
            +
                  require_relative "ioposrw/stringio"
         | 
| 27 | 
            +
                  nil
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
            end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            class IO
         | 
| 33 | 
            +
              extend IOPositioningReadWrite::IOClass
         | 
| 34 | 
            +
              include IOPositioningReadWrite::IO
         | 
| 35 | 
            +
            end
         | 
| @@ -0,0 +1,125 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "test/unit"
         | 
| 4 | 
            +
            require "ioposrw"
         | 
| 5 | 
            +
            require "tempfile"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            class TC_ioposrw < Test::Unit::TestCase
         | 
| 8 | 
            +
              attr_reader :file
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              SOURCE = ((0...256).to_a * 257).shuffle!.pack("C*")
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def setup
         | 
| 13 | 
            +
                @file = Tempfile.open("")
         | 
| 14 | 
            +
                file.binmode
         | 
| 15 | 
            +
                file << SOURCE
         | 
| 16 | 
            +
                file.flush
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              def try_readat(file)
         | 
| 20 | 
            +
                [
         | 
| 21 | 
            +
                  [0, 0, false, "", "空文字列になるべき"],
         | 
| 22 | 
            +
                  [SOURCE.length, 0, false, "", "空文字列になるべき"],
         | 
| 23 | 
            +
                  [SOURCE.length, nil, false, nil, "不適切な EOF"],
         | 
| 24 | 
            +
                  [file.size, nil, false, nil, "不適切な EOF"],
         | 
| 25 | 
            +
                  [file.size, 99999999, false, nil, "不適切な EOF"],
         | 
| 26 | 
            +
                  [0, nil, false, SOURCE, "先頭からの全読み込みが不一致"],
         | 
| 27 | 
            +
                  [0, SOURCE.length, false, SOURCE, "先頭からの全読み込みが不一致"],
         | 
| 28 | 
            +
                  [SOURCE.length / 2, SOURCE.length / 2, false,
         | 
| 29 | 
            +
                   SOURCE.slice(SOURCE.length / 2, SOURCE.length / 2),
         | 
| 30 | 
            +
                   "途中位置からの固定長分の読み込みが不一致"],
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                   [0, 0, true, "", "空文字列になるべき"],
         | 
| 33 | 
            +
                   [SOURCE.length, 0, true, "", "空文字列になるべき"],
         | 
| 34 | 
            +
                   [SOURCE.length, nil, true, nil, "不適切な EOF"],
         | 
| 35 | 
            +
                   [file.size, nil, true, nil, "不適切な EOF"],
         | 
| 36 | 
            +
                   [file.size, 99999999, true, nil, "不適切な EOF"],
         | 
| 37 | 
            +
                   [0, nil, true, SOURCE, "先頭からの全読み込みが不一致"],
         | 
| 38 | 
            +
                   [0, SOURCE.length, true, SOURCE, "先頭からの全読み込みが不一致"],
         | 
| 39 | 
            +
                   [SOURCE.length / 2, SOURCE.length / 2, true,
         | 
| 40 | 
            +
                    SOURCE.slice(SOURCE.length / 2, SOURCE.length / 2),
         | 
| 41 | 
            +
                    "途中位置からの固定長分の読み込みが不一致"],
         | 
| 42 | 
            +
                ].each do |pos, size, buf, ref, mesg|
         | 
| 43 | 
            +
                  if buf
         | 
| 44 | 
            +
                    buf = 200.times.reduce("") { |a, *| a << rand(256).chr(Encoding::BINARY) }
         | 
| 45 | 
            +
                  else
         | 
| 46 | 
            +
                    buf = nil
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                  assert(file.readat(pos, size, buf) == ref,
         | 
| 49 | 
            +
                         "%s (%s)" % [mesg, pos: pos, size: size, buf_len: (buf ? buf.length : nil)])
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                256.times do
         | 
| 53 | 
            +
                  size = rand(file.size * 2)
         | 
| 54 | 
            +
                  pos = file.pos = rand(file.size)
         | 
| 55 | 
            +
                  assert(file.read(size) == (buf = file.readat(pos, size)),
         | 
| 56 | 
            +
                         "IO#pos + IO#read との不一致 (pos=#{pos}, size=#{size})")
         | 
| 57 | 
            +
                  buf1 = "".force_encoding(Encoding::BINARY)
         | 
| 58 | 
            +
                  file.pos = pos
         | 
| 59 | 
            +
                  assert(file.read(size, buf1) == (buf = file.readat(pos, size, buf1)),
         | 
| 60 | 
            +
                         "IO#pos + IO#read with buffer との不一致 (pos=#{pos}, size=#{size})")
         | 
| 61 | 
            +
                  assert(buf == SOURCE.byteslice(pos, size),
         | 
| 62 | 
            +
                         "SOURCE.byteslice との不一致 (pos=#{pos}, size=#{size})")
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
              def try_writeat(file)
         | 
| 67 | 
            +
                test = proc do |pos, buf|
         | 
| 68 | 
            +
                  assert(file.writeat(pos, buf) == buf.bytesize, "書き込み量の不一致")
         | 
| 69 | 
            +
                  if SOURCE.length < pos
         | 
| 70 | 
            +
                    SOURCE << "\0" * (pos - SOURCE.length)
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
                  SOURCE[pos, buf.bytesize] = buf
         | 
| 73 | 
            +
                  assert(file.size == SOURCE.length, "ファイルサイズが不正 (file.size=#{file.size})")
         | 
| 74 | 
            +
                  buf = file.readat(0)
         | 
| 75 | 
            +
                  assert(buf.length == SOURCE.length, "ファイルデータ量の不一致")
         | 
| 76 | 
            +
                  assert(buf == SOURCE, "書き込みデータの不一致")
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
                [
         | 
| 79 | 
            +
                  [0, "HERGFDGFHMFDNGF"],
         | 
| 80 | 
            +
                  [10, "fsdghjljlkjhtgrfegvhjmg"],
         | 
| 81 | 
            +
                  [SOURCE.length + 100, "guiuydrtfv nm,j\;lkjchxdgxehtrjyukli,mnbvgdshbjt\n\r\r\n\0liu;79pfy7kt7lofvol"],
         | 
| 82 | 
            +
                ].each(&test)
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                256.times do
         | 
| 85 | 
            +
                  pos = rand(SOURCE.length + 16384)
         | 
| 86 | 
            +
                  buf = (0...256).to_a.shuffle!.pack("C*")
         | 
| 87 | 
            +
                  test.(pos, buf)
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
              end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
              def test_io_readat
         | 
| 92 | 
            +
                try_readat(file)
         | 
| 93 | 
            +
              end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
              def test_io_writeat
         | 
| 96 | 
            +
                try_writeat(file)
         | 
| 97 | 
            +
              end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
              def test_stringio_enable
         | 
| 100 | 
            +
                if RUBY_VERSION < "2.2"
         | 
| 101 | 
            +
                  th = Thread.fork do
         | 
| 102 | 
            +
                    $SAFE = 2
         | 
| 103 | 
            +
                    IO.ioposrw_enable_stringio_extend
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
                  assert_raise(SecurityError) { th.join }
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                require "ioposrw/stringio"
         | 
| 109 | 
            +
                assert($".select{|x| x=~/stringio\.so$/}.length == 1, 'require "stringio" されない')
         | 
| 110 | 
            +
                assert(StringIO.instance_methods.select{|x| x==:readat}.length == 1, "StringIO#readat が未定義")
         | 
| 111 | 
            +
                assert(StringIO.instance_methods.select{|x| x==:writeat}.length == 1, "StringIO#writeat が未定義")
         | 
| 112 | 
            +
                @@str = StringIO.new(SOURCE.dup)
         | 
| 113 | 
            +
                assert_not_nil(@@str)
         | 
| 114 | 
            +
              end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
              def test_stringio_readat
         | 
| 117 | 
            +
                assert(@@str)
         | 
| 118 | 
            +
                try_readat(@@str)
         | 
| 119 | 
            +
              end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
              def test_stringio_writeat
         | 
| 122 | 
            +
                assert(@@str)
         | 
| 123 | 
            +
                try_writeat(@@str)
         | 
| 124 | 
            +
              end
         | 
| 125 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,97 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: ioposrw
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: '0.4'
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - dearblue
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: bin
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2017-12-26 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: rake
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - ">="
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: '0'
         | 
| 20 | 
            +
              type: :development
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - ">="
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: '0'
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: test-unit
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - ">="
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: '0'
         | 
| 34 | 
            +
              type: :development
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - ">="
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: '0'
         | 
| 41 | 
            +
            description: |
         | 
| 42 | 
            +
              Append IO#readat/writeat methods. These provide similar functionality to the POSIX pread/pwrite.
         | 
| 43 | 
            +
              This library works on windows and unix. But tested on windows and FreeBSD only.
         | 
| 44 | 
            +
            email: dearblue@users.noreply.github.com
         | 
| 45 | 
            +
            executables: []
         | 
| 46 | 
            +
            extensions:
         | 
| 47 | 
            +
            - ext/extconf.rb
         | 
| 48 | 
            +
            extra_rdoc_files:
         | 
| 49 | 
            +
            - HISTORY.ja.md
         | 
| 50 | 
            +
            - QUICKREF.md
         | 
| 51 | 
            +
            - README.md
         | 
| 52 | 
            +
            - ext/ioposrw.c
         | 
| 53 | 
            +
            - lib/ioposrw.rb
         | 
| 54 | 
            +
            - lib/ioposrw/stringio.rb
         | 
| 55 | 
            +
            - lib/ioposrw/version.rb
         | 
| 56 | 
            +
            files:
         | 
| 57 | 
            +
            - HISTORY.ja.md
         | 
| 58 | 
            +
            - QUICKREF.md
         | 
| 59 | 
            +
            - README.md
         | 
| 60 | 
            +
            - Rakefile
         | 
| 61 | 
            +
            - ext/extconf.rb
         | 
| 62 | 
            +
            - ext/ioposrw.c
         | 
| 63 | 
            +
            - gemstub.rb
         | 
| 64 | 
            +
            - lib/ioposrw.rb
         | 
| 65 | 
            +
            - lib/ioposrw/stringio.rb
         | 
| 66 | 
            +
            - lib/ioposrw/version.rb
         | 
| 67 | 
            +
            - test/test_ioposrw.rb
         | 
| 68 | 
            +
            homepage: https://github.com/dearblue/ruby-ioposrw
         | 
| 69 | 
            +
            licenses:
         | 
| 70 | 
            +
            - BSD-2-Clause License
         | 
| 71 | 
            +
            metadata: {}
         | 
| 72 | 
            +
            post_install_message: 
         | 
| 73 | 
            +
            rdoc_options:
         | 
| 74 | 
            +
            - "--charset"
         | 
| 75 | 
            +
            - UTF-8
         | 
| 76 | 
            +
            - "-m"
         | 
| 77 | 
            +
            - README.md
         | 
| 78 | 
            +
            require_paths:
         | 
| 79 | 
            +
            - lib
         | 
| 80 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 81 | 
            +
              requirements:
         | 
| 82 | 
            +
              - - ">="
         | 
| 83 | 
            +
                - !ruby/object:Gem::Version
         | 
| 84 | 
            +
                  version: '0'
         | 
| 85 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 86 | 
            +
              requirements:
         | 
| 87 | 
            +
              - - ">="
         | 
| 88 | 
            +
                - !ruby/object:Gem::Version
         | 
| 89 | 
            +
                  version: '0'
         | 
| 90 | 
            +
            requirements: []
         | 
| 91 | 
            +
            rubyforge_project: 
         | 
| 92 | 
            +
            rubygems_version: 2.6.14
         | 
| 93 | 
            +
            signing_key: 
         | 
| 94 | 
            +
            specification_version: 4
         | 
| 95 | 
            +
            summary: Append IO#readat/writeat methods. These provide similar functionality to
         | 
| 96 | 
            +
              the POSIX pread/pwrite.
         | 
| 97 | 
            +
            test_files: []
         |