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

Sign up to get free protection for your applications and to get access to all the features.
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