httpkit 0.6.0.pre.3 → 0.6.0.pre.5

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.
Files changed (59) hide show
  1. checksums.yaml +15 -0
  2. data/.rspec +0 -1
  3. data/.travis.yml +5 -4
  4. data/Gemfile +0 -2
  5. data/Gemfile.devtools +27 -22
  6. data/README.md +11 -13
  7. data/config/flay.yml +2 -2
  8. data/config/flog.yml +1 -1
  9. data/config/reek.yml +11 -5
  10. data/config/rubocop.yml +47 -5
  11. data/examples/echo_server.rb +2 -2
  12. data/examples/getting_started.rb +16 -10
  13. data/httpkit.gemspec +5 -4
  14. data/lib/httpkit.rb +15 -5
  15. data/lib/httpkit/body.rb +44 -33
  16. data/lib/httpkit/client.rb +52 -23
  17. data/lib/httpkit/client/body_handler.rb +21 -0
  18. data/lib/httpkit/client/{keep_alive.rb → keep_alive_handler.rb} +1 -1
  19. data/lib/httpkit/client/mandatory_handler.rb +29 -0
  20. data/lib/httpkit/client/{timeouts.rb → timeouts_handler.rb} +1 -1
  21. data/lib/httpkit/connection/eventmachine.rb +4 -4
  22. data/lib/httpkit/request.rb +46 -10
  23. data/lib/httpkit/response.rb +37 -5
  24. data/lib/httpkit/serializer.rb +33 -25
  25. data/lib/httpkit/server.rb +14 -19
  26. data/lib/httpkit/server/body_handler.rb +25 -0
  27. data/lib/httpkit/server/{keep_alive.rb → keep_alive_handler.rb} +22 -13
  28. data/lib/httpkit/server/mandatory_handler.rb +23 -0
  29. data/lib/httpkit/server/{timeouts.rb → timeouts_handler.rb} +1 -1
  30. data/lib/httpkit/support/handler_manager.rb +8 -5
  31. data/lib/httpkit/support/message.rb +28 -15
  32. data/lib/httpkit/version.rb +1 -1
  33. data/spec/integration/keep_alive_spec.rb +6 -7
  34. data/spec/integration/smoke_spec.rb +4 -4
  35. data/spec/integration/streaming_spec.rb +2 -3
  36. data/spec/integration/timeouts_spec.rb +6 -6
  37. data/spec/shared/integration/server_client_pair.rb +1 -1
  38. data/spec/spec_helper.rb +3 -2
  39. data/spec/support/handler.rb +1 -1
  40. data/spec/support/helper.rb +6 -4
  41. data/spec/unit/body_spec.rb +6 -0
  42. data/spec/unit/client/keep_alive_handler_spec.rb +6 -0
  43. data/spec/unit/client/mandatory_handler_spec.rb +31 -0
  44. data/spec/unit/client/timeouts_handler_spec.rb +6 -0
  45. data/spec/unit/client_spec.rb +83 -34
  46. data/spec/unit/connection/eventmachine_spec.rb +12 -13
  47. data/spec/unit/httpkit_spec.rb +65 -24
  48. data/spec/unit/promise_spec.rb +1 -1
  49. data/spec/unit/request_spec.rb +2 -10
  50. data/spec/unit/response_spec.rb +7 -15
  51. data/spec/unit/serializer_spec.rb +83 -0
  52. data/spec/unit/server/{keep_alive_spec.rb → keep_alive_handler_spec.rb} +5 -2
  53. data/spec/unit/server/mandatory_handler_spec.rb +30 -0
  54. data/spec/unit/server/timeouts_handler_spec.rb +6 -0
  55. data/spec/unit/server_spec.rb +26 -32
  56. data/spec/unit/support/handler_manager_spec.rb +38 -7
  57. data/spec/unit/support/message_spec.rb +45 -20
  58. metadata +57 -36
  59. data/lib/httpkit/serializer/encoding.rb +0 -43
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YjQ5NTA0NGVmZTllMzYyZDQ2NmFlMGFkMTRiNzZhNWIwMjY3MzQ2Mw==
5
+ data.tar.gz: !binary |-
6
+ OWU0MTU3ZGZmYmE3OWVjZjRlOTZjZGYzMTU2MWFiNmQ5OWUxMTczNQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZTYyYTczNzczNDA4YWFiMGIwZDE0ZTBkYzcxY2E3NWEwMGM5OTE1Yzg4ZDY2
10
+ YzI5NWMxY2IxMDA5MjEzNDI0MTU4NDA4NTFlMjljNGYxZDg1YzM3ZWQ1MjE3
11
+ YWQzYmViMmJhM2QxYmE1ZDI5YTFiOTYwYTI4YjAxMjhlMDkxYjQ=
12
+ data.tar.gz: !binary |-
13
+ Y2NiZjliN2Q1NDI1NmMwYWJiMmJmNTA3YTc1Mjc3M2MwNDgyMzc3NGIwMjk4
14
+ MDNkY2ExNzkyMTVhZWUxOTAwNGEwMjZhZTQxMjlkMWMyYzYxY2M0NTQ3ODQ5
15
+ Yzg5YjM1OTEyNjEzM2U2MmVmMzBmNmNjY2NmYWY5OTJlOWNjMjI=
data/.rspec CHANGED
@@ -1,3 +1,2 @@
1
1
  --order rand
2
2
  --color
3
- --format Fuubar
data/.travis.yml CHANGED
@@ -1,9 +1,10 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
4
- - 2.0.0
3
+ - 1.9
4
+ - 2.0
5
+ - 2.1
5
6
  - jruby
6
- - rbx
7
+ - rbx-2
7
8
  - ruby-head
8
9
  - jruby-head
9
10
  matrix:
@@ -12,4 +13,4 @@ matrix:
12
13
  - rvm: ruby-head
13
14
  - rvm: jruby-head
14
15
  fast_finish: true
15
- script: bundle exec rake -t ci:metrics spec:integration
16
+ script: "bundle exec rake -t ci:metrics spec:integration && (bundle exec rake -t metrics:mutant || true)"
data/Gemfile CHANGED
@@ -5,8 +5,6 @@ source 'https://rubygems.org'
5
5
  gemspec
6
6
 
7
7
  gem 'devtools', git: 'https://github.com/rom-rb/devtools.git'
8
- gem 'fuubar', git: 'https://github.com/lgierth/fuubar.git',
9
- ref: 'static-percentage'
10
8
  gem 'awesome_print'
11
9
 
12
10
  platform :rbx do
data/Gemfile.devtools CHANGED
@@ -1,9 +1,11 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  group :development do
4
- gem 'rake', '~> 10.1.0'
5
- gem 'rspec', '~> 2.14.1'
6
- gem 'yard', '~> 0.8.7'
4
+ gem 'rake', '~> 10.3.2'
5
+ gem 'rspec', '~> 2.99.0'
6
+ gem 'rspec-core', '~> 2.99.0'
7
+ gem 'rspec-its', '~> 1.0.1'
8
+ gem 'yard', '~> 0.8.7.4'
7
9
 
8
10
  platform :rbx do
9
11
  gem 'rubysl-singleton', '~> 2.0.0'
@@ -11,48 +13,51 @@ group :development do
11
13
  end
12
14
 
13
15
  group :yard do
14
- gem 'kramdown', '~> 1.2.0'
16
+ gem 'kramdown', '~> 1.3.3'
15
17
  end
16
18
 
17
19
  group :guard do
18
- gem 'guard', '~> 2.2.4'
20
+ gem 'guard', '~> 2.6.1'
19
21
  gem 'guard-bundler', '~> 2.0.0'
20
- gem 'guard-rspec', '~> 4.0.4'
21
- gem 'guard-rubocop', '~> 1.0.0'
22
+ gem 'guard-rspec', '~> 4.2.9'
23
+ gem 'guard-rubocop', '~> 1.1.0'
22
24
 
23
25
  # file system change event handling
24
- gem 'listen', '~> 2.2.0'
26
+ gem 'listen', '~> 2.7.7'
25
27
  gem 'rb-fchange', '~> 0.0.6', require: false
26
- gem 'rb-fsevent', '~> 0.9.3', require: false
27
- gem 'rb-inotify', '~> 0.9.0', require: false
28
+ gem 'rb-fsevent', '~> 0.9.4', require: false
29
+ gem 'rb-inotify', '~> 0.9.5', require: false
28
30
 
29
31
  # notification handling
30
- gem 'libnotify', '~> 0.8.0', require: false
32
+ gem 'libnotify', '~> 0.8.3', require: false
31
33
  gem 'rb-notifu', '~> 0.0.4', require: false
32
34
  gem 'terminal-notifier-guard', '~> 1.5.3', require: false
33
35
  end
34
36
 
35
37
  group :metrics do
36
38
  gem 'coveralls', '~> 0.7.0'
37
- gem 'flay', '~> 2.4.0'
38
- gem 'flog', '~> 4.2.0'
39
- gem 'reek', '~> 1.3.2'
40
- gem 'rubocop', '~> 0.15.0'
41
- gem 'simplecov', '~> 0.8.2'
42
- gem 'yardstick', '~> 0.9.7', git: 'https://github.com/dkubb/yardstick.git'
39
+ gem 'flay', '~> 2.5.0'
40
+ gem 'flog', '~> 4.2.1'
41
+ gem 'reek', '~> 1.3.7'
42
+ gem 'rubocop', '~> 0.23.0'
43
+ gem 'simplecov', '~> 0.7.1'
44
+ gem 'yardstick', '~> 0.9.9'
45
+
46
+ platforms :mri do
47
+ gem 'mutant', '~> 0.5.23'
48
+ gem 'mutant-rspec', '~> 0.5.21'
49
+ end
43
50
 
44
51
  platforms :ruby_19, :ruby_20 do
45
- gem 'mutant', '~> 0.3.0.rc3', git: 'https://github.com/mbj/mutant.git'
46
- gem 'unparser', '~> 0.1.5', git: 'https://github.com/mbj/unparser.git'
47
52
  gem 'yard-spellcheck', '~> 0.1.5'
48
53
  end
49
54
 
50
55
  platform :rbx do
51
56
  gem 'json', '~> 1.8.1'
52
- gem 'racc', '~> 1.4.10'
57
+ gem 'racc', '~> 1.4.11'
53
58
  gem 'rubysl-logger', '~> 2.0.0'
54
59
  gem 'rubysl-open-uri', '~> 2.0.0'
55
- gem 'rubysl-prettyprint', '~> 2.0.2'
60
+ gem 'rubysl-prettyprint', '~> 2.0.3'
56
61
  end
57
62
  end
58
63
 
@@ -62,6 +67,6 @@ end
62
67
 
63
68
  platform :jruby do
64
69
  group :jruby do
65
- gem 'jruby-openssl', '~> 0.8.5'
70
+ gem 'jruby-openssl', '~> 0.9.4'
66
71
  end
67
72
  end
data/README.md CHANGED
@@ -1,26 +1,24 @@
1
- # The HTTP toolkit for Ruby [![Build Status](https://travis-ci.org/lgierth/httpkit.png?branch=master)](https://travis-ci.org/lgierth/httpkit) [![Code Climate](https://codeclimate.com/github/lgierth/httpkit.png)](https://codeclimate.com/github/lgierth/httpkit) [![Coverage Status](https://coveralls.io/repos/lgierth/httpkit/badge.png?branch=master)](https://coveralls.io/r/lgierth/httpkit?branch=master)
1
+ # The HTTP toolkit for Ruby
2
2
 
3
3
  HTTPkit is a Ruby toolkit for building HTTP clients and servers,
4
4
  as well as compositions of them.
5
5
 
6
- - \#1 feature: readable, high-quality, extendable code with 66.81% mutation coverage (wip)
6
+ [![Build Status](https://travis-ci.org/lgierth/httpkit.png?branch=master)](https://travis-ci.org/lgierth/httpkit) [![Code Climate](https://codeclimate.com/github/lgierth/httpkit.png)](https://codeclimate.com/github/lgierth/httpkit) [![Coverage Status](https://coveralls.io/repos/lgierth/httpkit/badge.png?branch=master)](https://coveralls.io/r/lgierth/httpkit?branch=master)
7
+
8
+ - \#1 feature: readable, high-quality, extendable code with 71.88% mutation coverage (wip)
7
9
  - \#2 feature: sophisticated request and response streaming
8
- - \#3 feature: compatible with Rack, Faraday, and Webmachine for Ruby (todo)
9
- - \#4 feature: concurrenct or non-concurrent
10
- - Non-concurrent using one-off EventMachine reactor (clients only)
11
- - Evented using EventMachine
12
- - Synchronously evented using EventMachine and Fibers
13
- - Threaded using Celluloid (todo)
10
+ - \#3 feature: compatible with Rack, Faraday, Webmachine for Ruby, and VCR (all todo)
11
+ - \#4 feature: backed by Celluloid (wip) or Eventmachine
14
12
 
15
- *Note:* The `master` branch contains the latest development effort. Look at the
16
- `0.5.x` branch for stable, but very old releases. HTTPkit used to be called
17
- Hatetepe.
13
+ *Note:* The `master` branch contains the in-progress rewrite towards
14
+ HTTPkit 1.0. Look at the `0.5.x` branch for stable, but outdated and largely
15
+ unmaintained releases. HTTPkit used to be called Hatetepe.
18
16
 
19
17
  ## Installation
20
18
 
21
19
  Add this line to your application's Gemfile:
22
20
 
23
- gem 'httpkit'
21
+ gem 'httpkit', '0.6.0.pre.3'
24
22
 
25
23
  And then execute:
26
24
 
@@ -28,7 +26,7 @@ And then execute:
28
26
 
29
27
  Or install it yourself as:
30
28
 
31
- $ gem install httpkit
29
+ $ gem install httpkit --pre
32
30
 
33
31
  ## Usage
34
32
 
data/config/flay.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  ---
2
- threshold: 8
3
- total_score: 181
2
+ threshold: 10
3
+ total_score: 279
data/config/flog.yml CHANGED
@@ -1,2 +1,2 @@
1
1
  ---
2
- threshold: 10.0
2
+ threshold: 10.8
data/config/reek.yml CHANGED
@@ -31,7 +31,8 @@ IrresponsibleModule:
31
31
  exclude: []
32
32
  LongParameterList:
33
33
  enabled: true
34
- exclude: []
34
+ exclude:
35
+ - "HTTPkit::Request#initialize"
35
36
  max_params: 2
36
37
  overrides:
37
38
  initialize:
@@ -49,7 +50,8 @@ NestedIterators:
49
50
  ignore_iterators: []
50
51
  NilCheck:
51
52
  enabled: true
52
- exclude: []
53
+ exclude:
54
+ - "HTTPkit::Body#length_known?"
53
55
  RepeatedConditional:
54
56
  enabled: true
55
57
  exclude: []
@@ -58,15 +60,19 @@ TooManyInstanceVariables:
58
60
  enabled: true
59
61
  exclude:
60
62
  - HTTPkit::Connection::EventMachine
61
- max_instance_variables: 4
63
+ - HTTPkit::Request
64
+ - HTTPkit::Client
65
+ max_instance_variables: 5
62
66
  TooManyMethods:
63
67
  enabled: true
64
- exclude: []
65
- max_methods: 11
68
+ exclude:
69
+ - HTTPkit::Response
70
+ max_methods: 13
66
71
  TooManyStatements:
67
72
  enabled: true
68
73
  exclude:
69
74
  - each
75
+ - "HTTPkit::Request#initialize"
70
76
  max_statements: 5
71
77
  UncommunicativeMethodName:
72
78
  enabled: true
data/config/rubocop.yml CHANGED
@@ -1,16 +1,22 @@
1
1
  AllCops:
2
- Includes:
2
+ Include:
3
3
  - '**/*.rake'
4
4
  - 'Gemfile'
5
5
  - 'Gemfile.devtools'
6
- Excludes:
6
+ Exclude:
7
7
  - '**/vendor/**'
8
8
  - '**/benchmarks/**'
9
9
 
10
10
  # Avoid parameter lists longer than five parameters.
11
11
  ParameterLists:
12
- Max: 4
13
- CountKeywordArgs: true
12
+ Enabled: false
13
+
14
+ MethodLength:
15
+ CountComments: false
16
+ Max: 10
17
+
18
+ ClassLength:
19
+ Enabled: false
14
20
 
15
21
  # Avoid more than `Max` levels of nesting.
16
22
  BlockNesting:
@@ -25,7 +31,7 @@ CollectionMethods:
25
31
  find_all: 'select'
26
32
 
27
33
  AccessModifierIndentation:
28
- EnforcedStyle: indent
34
+ Enabled: false
29
35
 
30
36
  # Limit line length
31
37
  LineLength:
@@ -47,6 +53,9 @@ CaseEquality:
47
53
  ConstantName:
48
54
  Enabled: false
49
55
 
56
+ ClassAndModuleChildren:
57
+ Enabled: false
58
+
50
59
  # Not all trivial readers/writers can be defined with attr_* methods
51
60
  TrivialAccessors:
52
61
  Enabled: false
@@ -54,3 +63,36 @@ TrivialAccessors:
54
63
  SignalException:
55
64
  # Valid values are: semantic, only_raise and only_fail
56
65
  EnforcedStyle: only_raise
66
+
67
+ # Do not prefer do/end over {} for multiline blocks
68
+ Blocks:
69
+ Enabled: false
70
+
71
+ # Allow empty lines around body
72
+ EmptyLinesAroundBody:
73
+ Enabled: false
74
+
75
+ # Prefer String#% over Kernel#sprintf
76
+ FormatString:
77
+ Enabled: false
78
+
79
+ # Use square brackets for literal Array objects
80
+ PercentLiteralDelimiters:
81
+ PreferredDelimiters:
82
+ '%': ()
83
+ '%i': '[]'
84
+ '%q': ()
85
+ '%Q': ()
86
+ '%r': '{}'
87
+ '%s': ()
88
+ '%w': '[]'
89
+ '%W': '[]'
90
+ '%x': ()
91
+
92
+ GuardClause:
93
+ Enabled: false
94
+
95
+ # Disabled because reduce vs each_with_object is not a question of style, ffs.
96
+ # See lib/httpkit/support/handler_manager.rb
97
+ EachWithObject:
98
+ Enabled: false
@@ -19,7 +19,7 @@ class EchoServer
19
19
  end
20
20
 
21
21
  def serialize(request, response)
22
- writer = response.body.closed.method(:progress)
22
+ writer = response.body.method(:write)
23
23
  serializer = HTTPkit::Serializer.new(request, writer)
24
24
  serializer.serialize
25
25
  end
@@ -28,7 +28,7 @@ end
28
28
  HTTPkit.start do
29
29
  HTTPkit::Server.start(address: ENV.fetch('ADDRESS', '127.0.0.1'),
30
30
  port: ENV.fetch('PORT', 3000).to_i,
31
- handlers: [HTTPkit::Server::KeepAlive.new,
31
+ handlers: [HTTPkit::Server::KeepAliveHandler.new,
32
32
  EchoServer.new])
33
33
 
34
34
  Signal.trap(:INT) { HTTPkit.stop }
@@ -4,11 +4,14 @@ require 'httpkit'
4
4
 
5
5
  class HelloServer
6
6
  def serve(request, served)
7
- p request.http_method # => :get
8
- p request.uri # => "/"
9
- p request.headers # => {"Host"=>"127.0.0.1:3000",
10
- # "Content-Length"=>"0"}
11
- p request.body.to_s # => ""
7
+ p request.http_method
8
+ # => :get
9
+ p request.uri
10
+ # => "/"
11
+ p request.headers
12
+ # => {"Host"=>"127.0.0.1:3000", "Content-Length"=>"0"}
13
+ p request.body.to_s
14
+ # => ""
12
15
 
13
16
  served.fulfill(response)
14
17
  end
@@ -26,9 +29,12 @@ HTTPkit.run do
26
29
 
27
30
  response = client.request(:get, '/')
28
31
 
29
- p response.status # => 200
30
- p response.status_name # => "OK"
31
- p response.headers # => {"Content-Type"=>"text/plain",
32
- # "Content-Length"=>"5"}
33
- p response.body.to_s # => "hello"
32
+ p response.status
33
+ # => 200
34
+ p response.status_name
35
+ # => "OK"
36
+ p response.headers
37
+ # => {"Content-Type"=>"text/plain", "Content-Length"=>"5"}
38
+ p response.body.to_s
39
+ # => "hello"
34
40
  end
data/httpkit.gemspec CHANGED
@@ -19,9 +19,10 @@ Gem::Specification.new do |spec|
19
19
  spec.test_files = spec.files.grep(%r{^spec/})
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_dependency "eventmachine"
23
- spec.add_dependency "http_parser.rb"
24
- spec.add_dependency "promise.rb", "~> 0.6"
22
+ spec.add_dependency "eventmachine", "~> 1.0"
23
+ spec.add_dependency "http_parser.rb", "~> 0.6.0"
24
+ spec.add_dependency "promise.rb", "~> 0.6.0"
25
+ spec.add_dependency "adamantium", "~> 0.2.0"
25
26
 
26
- spec.add_development_dependency "rspec"
27
+ spec.add_development_dependency "rspec", "~> 3.0"
27
28
  end
data/lib/httpkit.rb CHANGED
@@ -5,6 +5,7 @@ require 'fiber'
5
5
  require 'promise'
6
6
  require 'http/parser'
7
7
  require 'time'
8
+ require 'adamantium'
8
9
 
9
10
  require 'httpkit/version'
10
11
 
@@ -18,15 +19,18 @@ require 'httpkit/response'
18
19
 
19
20
  require 'httpkit/connection/eventmachine'
20
21
  require 'httpkit/connection/status'
21
- require 'httpkit/serializer/encoding'
22
22
  require 'httpkit/serializer'
23
23
 
24
24
  require 'httpkit/client'
25
- require 'httpkit/client/keep_alive'
26
- require 'httpkit/client/timeouts'
25
+ require 'httpkit/client/body_handler'
26
+ require 'httpkit/client/mandatory_handler'
27
+ require 'httpkit/client/keep_alive_handler'
28
+ require 'httpkit/client/timeouts_handler'
27
29
  require 'httpkit/server'
28
- require 'httpkit/server/keep_alive'
29
- require 'httpkit/server/timeouts'
30
+ require 'httpkit/server/body_handler'
31
+ require 'httpkit/server/mandatory_handler'
32
+ require 'httpkit/server/keep_alive_handler'
33
+ require 'httpkit/server/timeouts_handler'
30
34
 
31
35
  module HTTPkit
32
36
  def self.run
@@ -46,4 +50,10 @@ module HTTPkit
46
50
  EM.stop
47
51
  EM.next_tick {}
48
52
  end
53
+
54
+ def self.sleep(duration)
55
+ promise = Promise.new
56
+ EM.add_timer(duration) { promise.fulfill }
57
+ promise.sync
58
+ end
49
59
  end
data/lib/httpkit/body.rb CHANGED
@@ -2,66 +2,77 @@
2
2
 
3
3
  module HTTPkit
4
4
  class Body
5
- def self.build(arg)
6
- if arg.is_a?(Body)
7
- arg
5
+ include Adamantium::Mutable
6
+
7
+ def self.build(source = nil, length = nil)
8
+ if source.is_a?(self)
9
+ source
8
10
  else
9
- body = new(arg.to_s)
10
- body.closed.fulfill
11
- body
11
+ new(source, length)
12
12
  end
13
13
  end
14
14
 
15
15
  attr_reader :closed
16
16
 
17
- # TODO: eliminate string param
18
- def initialize(string = '')
19
- @io = StringIO.new(string)
17
+ # @param [String,#each,nil] source
18
+ def initialize(source = nil, length = nil, closed = Promise.new)
19
+ @source, @length = source, length
20
+ @closed = closed
21
+ @chunks = []
20
22
 
21
- @closed = Promise.new
22
- closed.on_progress { |str| @io.write(str) }
23
+ apply_source
24
+ @closed.fulfill if source
23
25
  end
24
26
 
25
- # don't optimize the sync call, we don't wanna search in @io.string
26
- # because search time increases with its size.
27
- def gets(*args)
28
- closed.sync
29
- @io.gets(*args)
27
+ def length
28
+ @length || 0
30
29
  end
31
30
 
32
- def read(length = nil, buffer = nil)
33
- closed.sync unless optimized_read?(length)
34
- @io.read(length, buffer)
31
+ def length_known?
32
+ !@length.nil?
35
33
  end
36
34
 
37
- def rewind
38
- closed.sync
39
- @io.rewind
35
+ def write(chunk)
36
+ if @closed.pending? && !chunk.empty?
37
+ @chunks << chunk
38
+ @closed.progress(chunk)
39
+ end
40
40
  end
41
41
 
42
42
  def each(&block)
43
- yield_string(&block)
44
- closed.on_progress(&block)
45
- closed.sync
43
+ return to_enum(__method__) unless block
44
+
45
+ if @source.respond_to?(:each)
46
+ @source.each(&block)
47
+ else
48
+ each_chunk(&block)
49
+ end
46
50
  end
47
51
 
48
52
  def to_s
49
- closed.sync
50
- @io.string
53
+ each.to_a.join
51
54
  end
52
55
 
53
- def string
54
- @io.string
56
+ def inspect
57
+ len = length_known? ? length : 'unknown'
58
+ sprintf('#<%s:0x%d @length=%s>', self.class.name, object_id, len)
55
59
  end
56
60
 
57
61
  private
58
62
 
59
- def optimized_read?(length)
60
- length && length < (@io.length - pos)
63
+ def apply_source
64
+ if @source.respond_to?(:to_str) && (chunk = @source.to_str)
65
+ @length = chunk.bytesize
66
+ write(chunk)
67
+ elsif @source.respond_to?(:bytesize)
68
+ @length = @source.bytesize
69
+ end
61
70
  end
62
71
 
63
- def yield_string(&block)
64
- block.call(@io.string) unless @io.string.empty?
72
+ def each_chunk(&block)
73
+ @closed.on_progress(&block)
74
+ @chunks.dup.each(&block)
75
+ @closed.sync
65
76
  end
66
77
  end
67
78
  end