grpc_kit 0.1.8 → 0.1.9

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/examples/helloworld_client.rb +1 -1
  3. data/examples/routeguide_client.rb +33 -0
  4. data/examples/routeguide_server.rb +17 -0
  5. data/lib/grpc.rb +2 -1
  6. data/lib/grpc_kit.rb +4 -1
  7. data/lib/grpc_kit/calls.rb +17 -3
  8. data/lib/grpc_kit/calls/client_bidi_streamer.rb +63 -0
  9. data/lib/grpc_kit/calls/client_client_streamer.rb +8 -4
  10. data/lib/grpc_kit/calls/client_request_response.rb +7 -4
  11. data/lib/grpc_kit/calls/client_server_streamer.rb +7 -4
  12. data/lib/grpc_kit/calls/server_bidi_streamer.rb +43 -0
  13. data/lib/grpc_kit/calls/server_client_streamer.rb +5 -1
  14. data/lib/grpc_kit/calls/server_request_response.rb +5 -1
  15. data/lib/grpc_kit/calls/server_server_streamer.rb +5 -1
  16. data/lib/grpc_kit/client.rb +22 -4
  17. data/lib/grpc_kit/errors.rb +25 -11
  18. data/lib/grpc_kit/grpc/dsl.rb +17 -2
  19. data/lib/grpc_kit/grpc/generic_service.rb +0 -13
  20. data/lib/grpc_kit/grpc/interceptor.rb +27 -0
  21. data/lib/grpc_kit/grpc/logger.rb +4 -1
  22. data/lib/grpc_kit/grpc_time.rb +3 -1
  23. data/lib/grpc_kit/interceptors.rb +3 -0
  24. data/lib/grpc_kit/interceptors/client_bidi_streamer.rb +20 -0
  25. data/lib/grpc_kit/interceptors/client_client_streamer.rb +3 -0
  26. data/lib/grpc_kit/interceptors/client_request_response.rb +3 -0
  27. data/lib/grpc_kit/interceptors/client_server_streamer.rb +3 -0
  28. data/lib/grpc_kit/interceptors/server_bidi_streamer.rb +17 -0
  29. data/lib/grpc_kit/interceptors/server_client_streamer.rb +2 -0
  30. data/lib/grpc_kit/interceptors/server_request_response.rb +2 -0
  31. data/lib/grpc_kit/interceptors/server_server_streamer.rb +2 -0
  32. data/lib/grpc_kit/protobuffer.rb +8 -0
  33. data/lib/grpc_kit/rpc_desc.rb +21 -4
  34. data/lib/grpc_kit/rpcs.rb +4 -0
  35. data/lib/grpc_kit/rpcs/client_bidi_streamer.rb +20 -1
  36. data/lib/grpc_kit/rpcs/client_client_streamer.rb +5 -0
  37. data/lib/grpc_kit/rpcs/client_request_response.rb +11 -17
  38. data/lib/grpc_kit/rpcs/client_server_streamer.rb +5 -0
  39. data/lib/grpc_kit/rpcs/server_bidi_streamer.rb +17 -0
  40. data/lib/grpc_kit/rpcs/server_client_streamer.rb +3 -0
  41. data/lib/grpc_kit/rpcs/server_request_response.rb +3 -0
  42. data/lib/grpc_kit/rpcs/server_server_streamer.rb +3 -0
  43. data/lib/grpc_kit/server.rb +12 -4
  44. data/lib/grpc_kit/session/client_session.rb +7 -2
  45. data/lib/grpc_kit/session/drain_controller.rb +2 -0
  46. data/lib/grpc_kit/session/headers.rb +11 -0
  47. data/lib/grpc_kit/session/io.rb +9 -0
  48. data/lib/grpc_kit/session/recv_buffer.rb +7 -0
  49. data/lib/grpc_kit/session/send_buffer.rb +8 -0
  50. data/lib/grpc_kit/session/server_session.rb +8 -5
  51. data/lib/grpc_kit/session/stream.rb +17 -3
  52. data/lib/grpc_kit/session/stream_status.rb +7 -1
  53. data/lib/grpc_kit/stream/client_stream.rb +55 -9
  54. data/lib/grpc_kit/stream/server_stream.rb +19 -1
  55. data/lib/grpc_kit/transport/client_transport.rb +39 -3
  56. data/lib/grpc_kit/transport/packable.rb +9 -3
  57. data/lib/grpc_kit/transport/server_transport.rb +15 -2
  58. data/lib/grpc_kit/version.rb +1 -1
  59. metadata +6 -3
  60. data/TODO.md +0 -71
@@ -2,6 +2,10 @@
2
2
 
3
3
  module GrpcKit
4
4
  class ProtoBuffer
5
+ # @param encoder [Class, GrpcKit::GRPC::Stream]
6
+ # @param decoder [Class, GrpcKit::GRPC::Stream]
7
+ # @param encode_method [Symbol]
8
+ # @param decode_method [Symbol]
5
9
  def initialize(encoder:, decoder:, encode_method:, decode_method:)
6
10
  @encoder = encoder
7
11
  @decoder = decoder
@@ -9,10 +13,14 @@ module GrpcKit
9
13
  @decode_method = decode_method
10
14
  end
11
15
 
16
+ # @param data [String]
17
+ # @return [void]
12
18
  def encode(data)
13
19
  @encoder.send(@encode_method, data)
14
20
  end
15
21
 
22
+ # @param data [String]
23
+ # @return [void]
16
24
  def decode(data)
17
25
  @decoder.send(@decode_method, data)
18
26
  end
@@ -6,7 +6,7 @@ require 'grpc_kit/protobuffer'
6
6
  require 'grpc_kit/interceptors/client_request_response'
7
7
  require 'grpc_kit/interceptors/client_client_streamer'
8
8
  require 'grpc_kit/interceptors/client_server_streamer'
9
- # require 'grpc_kit/client_interceptors/bidi_streamer'
9
+ require 'grpc_kit/interceptors/client_bidi_streamer'
10
10
  require 'grpc_kit/rpcs/client_request_response'
11
11
  require 'grpc_kit/rpcs/client_client_streamer'
12
12
  require 'grpc_kit/rpcs/client_server_streamer'
@@ -15,7 +15,7 @@ require 'grpc_kit/rpcs/client_bidi_streamer'
15
15
  require 'grpc_kit/interceptors/server_request_response'
16
16
  require 'grpc_kit/interceptors/server_client_streamer'
17
17
  require 'grpc_kit/interceptors/server_server_streamer'
18
- # require 'grpc_kit/server_interceptors/bidi_streamer'
18
+ require 'grpc_kit/interceptors/server_bidi_streamer'
19
19
  require 'grpc_kit/rpcs/server_request_response'
20
20
  require 'grpc_kit/rpcs/server_client_streamer'
21
21
  require 'grpc_kit/rpcs/server_server_streamer'
@@ -23,6 +23,12 @@ require 'grpc_kit/rpcs/server_bidi_streamer'
23
23
 
24
24
  module GrpcKit
25
25
  class RpcDesc
26
+ # @param name [Symbol] path name
27
+ # @param marshal [Class, GrpcKit::GRPC::Stream] marshaling object
28
+ # @param unmarshal [Class, GrpcKit::GRPC::Stream] unmarshaling object
29
+ # @param marshal_method [Symbol] method name of marshaling which marshal is called this method
30
+ # @param unmarshal_method [Symbol] method name of unmarshaling which unmarshal is called this method
31
+ # @param service_name [String]
26
32
  def initialize(name:, marshal:, unmarshal:, marshal_method:, unmarshal_method:, service_name:)
27
33
  @name = name
28
34
  @marshal = marshal
@@ -32,6 +38,9 @@ module GrpcKit
32
38
  @service_name = service_name
33
39
  end
34
40
 
41
+ # @param handler [GrpcKit::GRPC::GenericService]
42
+ # @param interceptors [Array<GrpcKit::GRPC::ServerInterceptor>]
43
+ # @return [#invoke] Server version of rpc class
35
44
  def build_server(handler, interceptors: [])
36
45
  inter = interceptors.empty? ? nil : server_interceptor.new(interceptors)
37
46
 
@@ -46,6 +55,8 @@ module GrpcKit
46
55
  server.new(handler, config)
47
56
  end
48
57
 
58
+ # @param interceptors [Array<GrpcKit::GRPC::ClientInterceptor>]
59
+ # @return [#invoke] Client version of rpc class
49
60
  def build_client(interceptors: [])
50
61
  inter = interceptors.empty? ? nil : client_interceptor.new(interceptors)
51
62
 
@@ -60,26 +71,32 @@ module GrpcKit
60
71
  client.new(config)
61
72
  end
62
73
 
74
+ # @return [Symbol] Snake case name
63
75
  def ruby_style_name
64
76
  @ruby_style_name ||= to_underscore(@name).to_sym
65
77
  end
66
78
 
79
+ # @return [String] Full name of path name
67
80
  def path
68
81
  @path ||= "/#{@service_name}/#{@name}"
69
82
  end
70
83
 
84
+ # @return [Boolean]
71
85
  def request_response?
72
86
  !@marshal.is_a?(GrpcKit::GRPC::Stream) && !@unmarshal.is_a?(GrpcKit::GRPC::Stream)
73
87
  end
74
88
 
89
+ # @return [Boolean]
75
90
  def client_streamer?
76
91
  @marshal.is_a?(GrpcKit::GRPC::Stream) && !@unmarshal.is_a?(GrpcKit::GRPC::Stream)
77
92
  end
78
93
 
94
+ # @return [Boolean]
79
95
  def server_streamer?
80
96
  !@marshal.is_a?(GrpcKit::GRPC::Stream) && @unmarshal.is_a?(GrpcKit::GRPC::Stream)
81
97
  end
82
98
 
99
+ # @return [Boolean]
83
100
  def bidi_streamer?
84
101
  @marshal.is_a?(GrpcKit::GRPC::Stream) && @unmarshal.is_a?(GrpcKit::GRPC::Stream)
85
102
  end
@@ -138,7 +155,7 @@ module GrpcKit
138
155
  elsif server_streamer?
139
156
  GrpcKit::Interceptors::Server::ServerStreamer
140
157
  elsif bidi_streamer?
141
- GrpcKit::Interceptors::Server::RequestResponse # TODO
158
+ GrpcKit::Interceptors::Server::BidiStreamer
142
159
  end
143
160
  end
144
161
 
@@ -150,7 +167,7 @@ module GrpcKit
150
167
  elsif server_streamer?
151
168
  GrpcKit::Interceptors::Client::ServerStreamer
152
169
  elsif bidi_streamer?
153
- GrpcKit::Interceptors::Client::RequestResponse # TODO
170
+ GrpcKit::Interceptors::Client::BidiStreamer
154
171
  end
155
172
  end
156
173
 
data/lib/grpc_kit/rpcs.rb CHANGED
@@ -7,8 +7,10 @@ require 'grpc_kit/status_codes'
7
7
  module GrpcKit
8
8
  module Rpcs
9
9
  class ClientRpc
10
+ # @return [GrpcKit::MethodConfig]
10
11
  attr_reader :config
11
12
 
13
+ # @param config [GrpcKit::MethodConfig]
12
14
  def initialize(config)
13
15
  @config = config
14
16
  end
@@ -17,6 +19,8 @@ module GrpcKit
17
19
  end
18
20
 
19
21
  class ServerRpc
22
+ # @param handler [GrpcKit::GRPC::GenericService]
23
+ # @param config [GrpcKit::MethodConfig]
20
24
  def initialize(handler, config)
21
25
  @handler = handler
22
26
  @config = config
@@ -1,11 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'grpc_kit/rpcs'
4
+ require 'grpc_kit/calls/client_bidi_streamer'
4
5
 
5
6
  module GrpcKit
6
7
  module Rpcs::Client
7
8
  class BidiStreamer < GrpcKit::Rpcs::ClientRpc
8
- def invoke(session, data, opts = {}); end
9
+ # @param stream [GrpcKit::Stream::ClientStream]
10
+ # @param _requests [Object] it's for compatibility, no use
11
+ # @param metadata [Hash<String, String>]
12
+ # @param timeout [GrpcKit::GrpcTime]
13
+ # @return [GrpcKit::Calls::Client::BidiStreamer]
14
+ def invoke(stream, _requests, metadata: {}, timeout: nil)
15
+ call = GrpcKit::Calls::Client::BidiStreamer.new(
16
+ metadata: metadata,
17
+ config: @config,
18
+ timeout: timeout,
19
+ stream: stream,
20
+ )
21
+
22
+ if @config.interceptor
23
+ @config.interceptor.intercept(call, metadata) { |s| s }
24
+ else
25
+ call
26
+ end
27
+ end
9
28
  end
10
29
  end
11
30
  end
@@ -6,6 +6,11 @@ require 'grpc_kit/calls/client_client_streamer'
6
6
  module GrpcKit
7
7
  module Rpcs::Client
8
8
  class ClientStreamer < GrpcKit::Rpcs::ClientRpc
9
+ # @param stream [GrpcKit::Stream::ClientStream]
10
+ # @param _request [nil] No use
11
+ # @param metadata [Hash<String, String>]
12
+ # @param timeout [GrpcKit::GrpcTime]
13
+ # @return [GrpcKit::Calls::Client::ClientStreamer]
9
14
  def invoke(stream, _request, metadata: {}, timeout: nil)
10
15
  call = GrpcKit::Calls::Client::ClientStreamer.new(
11
16
  metadata: metadata,
@@ -6,7 +6,12 @@ require 'grpc_kit/calls/client_request_response'
6
6
  module GrpcKit
7
7
  module Rpcs::Client
8
8
  class RequestResponse < GrpcKit::Rpcs::ClientRpc
9
- def invoke(stream, request, metadata: {}, timeout: nil)
9
+ # @param stream [GrpcKit::Stream::ClientStream]
10
+ # @param request [Object] request message
11
+ # @param metadata [Hash<String, String>]
12
+ # @param timeout [GrpcKit::GrpcTime]
13
+ # @return [Object] response message
14
+ def invoke(stream, request, metadata: {}, timeout: nil)
10
15
  call = GrpcKit::Calls::Client::RequestResponse.new(
11
16
  metadata: metadata,
12
17
  config: @config,
@@ -14,27 +19,16 @@ module GrpcKit
14
19
  stream: stream,
15
20
  )
16
21
 
17
- # TODO: DRY
18
- if @config.interceptor && timeout
19
- @config.interceptor.intercept(request, call, call.metadata) do |r, c, _|
20
- Timeout.timeout(timeout.to_f, GrpcKit::Errors::DeadlineExceeded) do
21
- call.send_msg(request, timeout: timeout.to_s, last: true)
22
+ Timeout.timeout(timeout&.to_f, GrpcKit::Errors::DeadlineExceeded) do
23
+ if @config.interceptor
24
+ @config.interceptor.intercept(request, call, call.metadata) do |r, c, _|
25
+ call.send_msg(request, last: true)
22
26
  call.recv(last: true)
23
27
  end
24
- end
25
- elsif @config.interceptor && !timeout
26
- @config.interceptor.intercept(request, call, call.metadata) do |r, c, _|
28
+ else
27
29
  call.send_msg(request, last: true)
28
30
  call.recv(last: true)
29
31
  end
30
- elsif !@config.interceptor && timeout
31
- Timeout.timeout(timeout.to_f, GrpcKit::Errors::DeadlineExceeded) do
32
- call.send_msg(request, timeout: timeout.to_s, last: true)
33
- call.recv(last: true)
34
- end
35
- else
36
- call.send_msg(request, last: true)
37
- call.recv(last: true)
38
32
  end
39
33
  end
40
34
  end
@@ -6,6 +6,11 @@ require 'grpc_kit/calls/client_server_streamer'
6
6
  module GrpcKit
7
7
  module Rpcs::Client
8
8
  class ServerStreamer < GrpcKit::Rpcs::ClientRpc
9
+ # @param stream [GrpcKit::Stream::ClientStream]
10
+ # @param request [Object] reqeust message
11
+ # @param metadata [Hash<String, String>]
12
+ # @param timeout [GrpcKit::GrpcTime]
13
+ # @return [GrpcKit::Calls::Client::ServerStreamer]
9
14
  def invoke(stream, request, metadata: {}, timeout: nil)
10
15
  call = GrpcKit::Calls::Client::ServerStreamer.new(metadata: metadata, config: @config, timeout: timeout, stream: stream)
11
16
 
@@ -1,10 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'grpc_kit/rpcs'
4
+ require 'grpc_kit/calls/server_bidi_streamer'
4
5
 
5
6
  module GrpcKit
6
7
  module Rpcs::Server
7
8
  class BidiStreamer < GrpcKit::Rpcs::ServerRpc
9
+ # @param stream [GrpcKit::Stream::ServerStream]
10
+ # @param metadata [Hash<String, String>]
11
+ # @return [void]
12
+ def invoke(stream, metadata: {})
13
+ call = GrpcKit::Calls::Server::BidiStreamer.new(metadata: metadata, config: @config, stream: stream)
14
+
15
+ if @config.interceptor
16
+ @config.interceptor.intercept(call) do |c|
17
+ @handler.send(@config.ruby_style_method_name, c)
18
+ end
19
+ else
20
+ @handler.send(@config.ruby_style_method_name, call)
21
+ end
22
+
23
+ stream.send_status
24
+ end
8
25
  end
9
26
  end
10
27
  end
@@ -6,6 +6,9 @@ require 'grpc_kit/calls/server_client_streamer'
6
6
  module GrpcKit
7
7
  module Rpcs::Server
8
8
  class ClientStreamer < GrpcKit::Rpcs::ServerRpc
9
+ # @param stream [GrpcKit::Stream::ServerStream]
10
+ # @param metadata [Hash<String, String>]
11
+ # @return [void]
9
12
  def invoke(stream, metadata: {})
10
13
  call = GrpcKit::Calls::Server::ClientStreamer.new(metadata: metadata, config: @config, stream: stream)
11
14
 
@@ -6,6 +6,9 @@ require 'grpc_kit/calls/server_request_response'
6
6
  module GrpcKit
7
7
  module Rpcs::Server
8
8
  class RequestResponse < GrpcKit::Rpcs::ServerRpc
9
+ # @param stream [GrpcKit::Stream::ServerStream]
10
+ # @param metadata [Hash<String, String>]
11
+ # @return [void]
9
12
  def invoke(stream, metadata: {})
10
13
  call = GrpcKit::Calls::Server::RequestResponse.new(metadata: metadata, config: @config, stream: stream)
11
14
  request = call.recv(last: true)
@@ -6,6 +6,9 @@ require 'grpc_kit/calls/server_server_streamer'
6
6
  module GrpcKit
7
7
  module Rpcs::Server
8
8
  class ServerStreamer < GrpcKit::Rpcs::ServerRpc
9
+ # @param stream [GrpcKit::Stream::ServerStream]
10
+ # @param metadata [Hash<String, String>]
11
+ # @return [void]
9
12
  def invoke(stream, metadata: {})
10
13
  call = GrpcKit::Calls::Server::ServerStreamer.new(metadata: metadata, config: @config, stream: stream)
11
14
 
@@ -5,6 +5,7 @@ require 'grpc_kit/session/server_session'
5
5
 
6
6
  module GrpcKit
7
7
  class Server
8
+ # @param interceptors [Array<GrpcKit::GRPC::ServerInterceptor>] list of interceptors
8
9
  def initialize(interceptors: [])
9
10
  @sessions = []
10
11
  @rpc_descs = {}
@@ -16,6 +17,8 @@ module GrpcKit
16
17
  GrpcKit.logger.debug("Launched grpc_kit(v#{GrpcKit::VERSION})")
17
18
  end
18
19
 
20
+ # @param handler [GrpcKit::GRPC::GenericService] gRPC handler object or class
21
+ # @return [void]
19
22
  def handle(handler)
20
23
  klass = handler.is_a?(Class) ? handler : handler.class
21
24
  unless klass.include?(GrpcKit::GRPC::GenericService)
@@ -32,6 +35,8 @@ module GrpcKit
32
35
  end
33
36
  end
34
37
 
38
+ # @param conn [TCPSocket]
39
+ # @return [void]
35
40
  def run(conn)
36
41
  raise 'Stopping server' if @stopping
37
42
 
@@ -41,8 +46,9 @@ module GrpcKit
41
46
  end
42
47
  end
43
48
 
49
+ # This method is expected to be called in trap context
50
+ # @return [void]
44
51
  def force_shutdown
45
- # expected to be called in trap context
46
52
  Thread.new do
47
53
  @mutex.synchronize do
48
54
  GrpcKit.logger.debug('force shutdown')
@@ -52,8 +58,9 @@ module GrpcKit
52
58
  end
53
59
  end
54
60
 
61
+ # This method is expected to be called in trap context
62
+ # @return [void]
55
63
  def graceful_shutdown
56
- # expected to be called in trap context
57
64
  Thread.new do
58
65
  GrpcKit.logger.debug('graceful shutdown')
59
66
  @mutex.synchronize { @sessions.each(&:drain) }
@@ -77,8 +84,9 @@ module GrpcKit
77
84
  @mutex.synchronize { @sessions.size }
78
85
  end
79
86
 
80
- # @params path [String]
81
- # @params stream [GrpcKit::Streams::ServerStream]
87
+ # @param path [String] gRPC method path
88
+ # @param stream [GrpcKit::Streams::ServerStream]
89
+ # @return [void]
82
90
  def dispatch(path, stream)
83
91
  rpc = @rpc_descs[path]
84
92
  unless rpc
@@ -16,7 +16,8 @@ module GrpcKit
16
16
 
17
17
  delegate %i[send_event recv_event] => :@io
18
18
 
19
- # @params io [GrpcKit::Session::IO]
19
+ # @param io [GrpcKit::Session::IO]
20
+ # @param opts [Hash]
20
21
  def initialize(io, opts = {})
21
22
  super() # initialize DS9::Session
22
23
 
@@ -27,6 +28,8 @@ module GrpcKit
27
28
  @stop = false
28
29
  end
29
30
 
31
+ # @param headers [Hash<String,String>]
32
+ # @return [void]
30
33
  def send_request(headers)
31
34
  if @draining
32
35
  raise ConnectionClosing, "You can't send new request. becuase this connection will shuting down"
@@ -40,7 +43,8 @@ module GrpcKit
40
43
  stream
41
44
  end
42
45
 
43
- # @params stream_id [Integer]
46
+ # @param stream_id [Integer]
47
+ # @return [void]
44
48
  def start(stream_id)
45
49
  stream = @streams.fetch(stream_id)
46
50
 
@@ -56,6 +60,7 @@ module GrpcKit
56
60
  shutdown
57
61
  end
58
62
 
63
+ # @return [void]
59
64
  def run_once
60
65
  return if @stop
61
66
 
@@ -12,10 +12,12 @@ module GrpcKit
12
12
  # @sent_ping = false
13
13
  end
14
14
 
15
+ # @return [void]
15
16
  def recv_ping_ack
16
17
  @after_one_rtt = true
17
18
  end
18
19
 
20
+ # @return [void]
19
21
  def call(session)
20
22
  if @goaway_sent
21
23
  # session.shutdown
@@ -20,6 +20,7 @@ module GrpcKit
20
20
  @metadata = {}
21
21
  end
22
22
 
23
+ # @return [Hash<String,String>]
23
24
  def metadata
24
25
  @metadata =
25
26
  if @metadata.empty?
@@ -31,34 +32,44 @@ module GrpcKit
31
32
  end
32
33
  end
33
34
 
35
+ # @return [String,nil]
34
36
  def path
35
37
  @opts[':path']
36
38
  end
37
39
 
40
+ # @return [String,nil]
38
41
  def grpc_status
39
42
  @opts['grpc-status']
40
43
  end
41
44
 
45
+ # @return [String,nil]
42
46
  def grpc_encoding
43
47
  @opts['grpc-encoding']
44
48
  end
45
49
 
50
+ # @return [String,nil]
46
51
  def content_type
47
52
  @opts['content-type']
48
53
  end
49
54
 
55
+ # @return [String,nil]
50
56
  def status_message
51
57
  @opts['grpc-message']
52
58
  end
53
59
 
60
+ # @return [Time,nil]
54
61
  def timeout
55
62
  @timeout ||= @opts['grpc-timeout'] && GrpcTime.new(@opts['grpc-timeout'])
56
63
  end
57
64
 
65
+ # @return [String,nil]
58
66
  def http_status
59
67
  @opts[':status']
60
68
  end
61
69
 
70
+ # @param key [String]
71
+ # @param val [String]
72
+ # @return [void]
62
73
  def add(key, val)
63
74
  @opts[key] = val
64
75
  end