amqp-client 1.0.1 → 1.1.2

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.
@@ -5,44 +5,111 @@ require_relative "./table"
5
5
  module AMQP
6
6
  class Client
7
7
  # Encode/decode AMQP Properties
8
- # @!attribute content_type
9
- # @return [String] Content type of the message body
10
- # @!attribute content_encoding
11
- # @return [String] Content encoding of the body
12
- # @!attribute headers
13
- # @return [Hash<String, Object>] Custom headers
14
- # @!attribute delivery_mode
15
- # @return [Integer] 2 for persisted message, transient messages for all other values
16
- # @!attribute priority
17
- # @return [Integer] A priority of the message (between 0 and 255)
18
- # @!attribute correlation_id
19
- # @return [Integer] A correlation id, most often used used for RPC communication
20
- # @!attribute reply_to
21
- # @return [String] Queue to reply RPC responses to
22
- # @!attribute expiration
23
- # @return [Integer, String] Number of seconds the message will stay in the queue
24
- # @!attribute message_id
25
- # @return [String]
26
- # @!attribute timestamp
27
- # @return [Date] User-definable, but often used for the time the message was originally generated
28
- # @!attribute type
29
- # @return [String] User-definable, but can can indicate what kind of message this is
30
- # @!attribute user_id
31
- # @return [String] User-definable, but can be used to verify that this is the user that published the message
32
- # @!attribute app_id
33
- # @return [String] User-definable, but often indicates which app that generated the message
34
- Properties = Struct.new(:content_type, :content_encoding, :headers, :delivery_mode, :priority, :correlation_id,
35
- :reply_to, :expiration, :message_id, :timestamp, :type, :user_id, :app_id,
36
- keyword_init: true) do
8
+ class Properties
9
+ # rubocop:disable Metrics/ParameterLists
10
+ def initialize(content_type: nil, content_encoding: nil, headers: nil, delivery_mode: nil,
11
+ priority: nil, correlation_id: nil, reply_to: nil, expiration: nil,
12
+ message_id: nil, timestamp: nil, type: nil, user_id: nil, app_id: nil)
13
+ @content_type = content_type
14
+ @content_encoding = content_encoding
15
+ @headers = headers
16
+ @delivery_mode = delivery_mode
17
+ @priority = priority
18
+ @correlation_id = correlation_id
19
+ @reply_to = reply_to
20
+ @expiration = expiration
21
+ @message_id = message_id
22
+ @timestamp = timestamp
23
+ @type = type
24
+ @user_id = user_id
25
+ @app_id = app_id
26
+ end
27
+ # rubocop:enable Metrics/ParameterLists
28
+
29
+ # Properties as a Hash
30
+ # @return [Hash] Properties
31
+ def to_h
32
+ {
33
+ content_type: content_type,
34
+ content_encoding: content_encoding,
35
+ headers: headers,
36
+ delivery_mode: delivery_mode,
37
+ priority: priority,
38
+ correlation_id: correlation_id,
39
+ reply_to: reply_to,
40
+ expiration: expiration,
41
+ message_id: message_id,
42
+ timestamp: timestamp,
43
+ type: type,
44
+ user_id: user_id,
45
+ app_id: app_id
46
+ }
47
+ end
48
+
49
+ alias to_hash to_h
50
+
51
+ # Returns true if two Property objects holds the same information
52
+ # @return [Boolean]
53
+ def ==(other)
54
+ return false unless other.is_a? self.class
55
+
56
+ instance_variables.all? { |v| instance_variable_get(v) == other.instance_variable_get(v) }
57
+ end
58
+
59
+ # Content type of the message body
60
+ # @return [String, nil]
61
+ attr_accessor :content_type
62
+ # Content encoding of the body
63
+ # @return [String, nil]
64
+ attr_accessor :content_encoding
65
+ # Headers, for applications and header exchange routing
66
+ # @return [Hash<String, Object>, nil]
67
+ attr_accessor :headers
68
+ # Message persistent level
69
+ # @note The exchange and queue have to durable as well for the message to be persistent
70
+ # @return [1] Transient message
71
+ # @return [2] Persistent message
72
+ # @return [nil] Not specified (implicitly transient)
73
+ attr_accessor :delivery_mode
74
+ # A priority of the message (between 0 and 255)
75
+ # @return [Integer, nil]
76
+ attr_accessor :priority
77
+ # Message correlation id, commonly used to correlate RPC requests and responses
78
+ # @return [String, nil]
79
+ attr_accessor :correlation_id
80
+ # Queue to reply RPC responses to
81
+ # @return [String, nil]
82
+ attr_accessor :reply_to
83
+ # Number of seconds the message will stay in the queue
84
+ # @return [String, nil]
85
+ attr_accessor :expiration
86
+ # Application message identifier
87
+ # @return [String, nil]
88
+ attr_accessor :message_id
89
+ # Message timestamp, often indicates when the message was originally generated
90
+ # @return [Date, nil]
91
+ attr_accessor :timestamp
92
+ # Message type name
93
+ # @return [String, nil]
94
+ attr_accessor :type
95
+ # The user that published the message
96
+ # @return [String, nil]
97
+ attr_accessor :user_id
98
+ # Name of application that generated the message
99
+ # @return [String, nil]
100
+ attr_accessor :app_id
101
+
37
102
  # Encode properties into a byte array
103
+ # @param properties [Hash]
38
104
  # @return [String] byte array
39
- def encode
105
+ def self.encode(properties)
106
+ return "\x00\x00" if properties.empty?
107
+
40
108
  flags = 0
41
109
  arr = [flags]
42
- fmt = StringIO.new(String.new("S>", capacity: 35))
43
- fmt.pos = 2
110
+ fmt = String.new("S>", capacity: 37)
44
111
 
45
- if content_type
112
+ if (content_type = properties[:content_type])
46
113
  content_type.is_a?(String) || raise(ArgumentError, "content_type must be a string")
47
114
 
48
115
  flags |= (1 << 15)
@@ -50,7 +117,7 @@ module AMQP
50
117
  fmt << "Ca*"
51
118
  end
52
119
 
53
- if content_encoding
120
+ if (content_encoding = properties[:content_encoding])
54
121
  content_encoding.is_a?(String) || raise(ArgumentError, "content_encoding must be a string")
55
122
 
56
123
  flags |= (1 << 14)
@@ -58,7 +125,7 @@ module AMQP
58
125
  fmt << "Ca*"
59
126
  end
60
127
 
61
- if headers
128
+ if (headers = properties[:headers])
62
129
  headers.is_a?(Hash) || raise(ArgumentError, "headers must be a hash")
63
130
 
64
131
  flags |= (1 << 13)
@@ -67,31 +134,31 @@ module AMQP
67
134
  fmt << "L>a*"
68
135
  end
69
136
 
70
- if delivery_mode
137
+ if (delivery_mode = properties[:delivery_mode])
71
138
  delivery_mode.is_a?(Integer) || raise(ArgumentError, "delivery_mode must be an int")
72
- delivery_mode.between?(0, 2) || raise(ArgumentError, "delivery_mode must be be between 0 and 2")
73
139
 
74
140
  flags |= (1 << 12)
75
141
  arr << delivery_mode
76
142
  fmt << "C"
77
143
  end
78
144
 
79
- if priority
145
+ if (priority = properties[:priority])
80
146
  priority.is_a?(Integer) || raise(ArgumentError, "priority must be an int")
147
+
81
148
  flags |= (1 << 11)
82
149
  arr << priority
83
150
  fmt << "C"
84
151
  end
85
152
 
86
- if correlation_id
87
- priority.is_a?(String) || raise(ArgumentError, "correlation_id must be a string")
153
+ if (correlation_id = properties[:correlation_id])
154
+ correlation_id.is_a?(String) || raise(ArgumentError, "correlation_id must be a string")
88
155
 
89
156
  flags |= (1 << 10)
90
157
  arr << correlation_id.bytesize << correlation_id
91
158
  fmt << "Ca*"
92
159
  end
93
160
 
94
- if reply_to
161
+ if (reply_to = properties[:reply_to])
95
162
  reply_to.is_a?(String) || raise(ArgumentError, "reply_to must be a string")
96
163
 
97
164
  flags |= (1 << 9)
@@ -99,8 +166,8 @@ module AMQP
99
166
  fmt << "Ca*"
100
167
  end
101
168
 
102
- if expiration
103
- self.expiration = expiration.to_s if expiration.is_a?(Integer)
169
+ if (expiration = properties[:expiration])
170
+ expiration = expiration.to_s if expiration.is_a?(Integer)
104
171
  expiration.is_a?(String) || raise(ArgumentError, "expiration must be a string or integer")
105
172
 
106
173
  flags |= (1 << 8)
@@ -108,7 +175,7 @@ module AMQP
108
175
  fmt << "Ca*"
109
176
  end
110
177
 
111
- if message_id
178
+ if (message_id = properties[:message_id])
112
179
  message_id.is_a?(String) || raise(ArgumentError, "message_id must be a string")
113
180
 
114
181
  flags |= (1 << 7)
@@ -116,7 +183,7 @@ module AMQP
116
183
  fmt << "Ca*"
117
184
  end
118
185
 
119
- if timestamp
186
+ if (timestamp = properties[:timestamp])
120
187
  timestamp.is_a?(Integer) || timestamp.is_a?(Time) || raise(ArgumentError, "timestamp must be an Integer or a Time")
121
188
 
122
189
  flags |= (1 << 6)
@@ -124,7 +191,7 @@ module AMQP
124
191
  fmt << "Q>"
125
192
  end
126
193
 
127
- if type
194
+ if (type = properties[:type])
128
195
  type.is_a?(String) || raise(ArgumentError, "type must be a string")
129
196
 
130
197
  flags |= (1 << 5)
@@ -132,7 +199,7 @@ module AMQP
132
199
  fmt << "Ca*"
133
200
  end
134
201
 
135
- if user_id
202
+ if (user_id = properties[:user_id])
136
203
  user_id.is_a?(String) || raise(ArgumentError, "user_id must be a string")
137
204
 
138
205
  flags |= (1 << 4)
@@ -140,7 +207,7 @@ module AMQP
140
207
  fmt << "Ca*"
141
208
  end
142
209
 
143
- if app_id
210
+ if (app_id = properties[:app_id])
144
211
  app_id.is_a?(String) || raise(ArgumentError, "app_id must be a string")
145
212
 
146
213
  flags |= (1 << 3)
@@ -149,87 +216,87 @@ module AMQP
149
216
  end
150
217
 
151
218
  arr[0] = flags
152
- arr.pack(fmt.string)
219
+ arr.pack(fmt)
153
220
  end
154
221
 
155
222
  # Decode a byte array
156
223
  # @return [Properties]
157
- def self.decode(bytes)
158
- h = new
159
- flags = bytes.unpack1("S>")
160
- pos = 2
224
+ def self.decode(bytes, pos = 0)
225
+ p = new
226
+ flags = bytes.byteslice(pos, 2).unpack1("S>")
227
+ pos += 2
161
228
  if (flags & 0x8000).positive?
162
- len = bytes[pos].ord
229
+ len = bytes.getbyte(pos)
163
230
  pos += 1
164
- h[:content_type] = bytes.byteslice(pos, len).force_encoding("utf-8")
231
+ p.content_type = bytes.byteslice(pos, len).force_encoding("utf-8")
165
232
  pos += len
166
233
  end
167
234
  if (flags & 0x4000).positive?
168
- len = bytes[pos].ord
235
+ len = bytes.getbyte(pos)
169
236
  pos += 1
170
- h[:content_encoding] = bytes.byteslice(pos, len).force_encoding("utf-8")
237
+ p.content_encoding = bytes.byteslice(pos, len).force_encoding("utf-8")
171
238
  pos += len
172
239
  end
173
240
  if (flags & 0x2000).positive?
174
241
  len = bytes.byteslice(pos, 4).unpack1("L>")
175
242
  pos += 4
176
- h[:headers] = Table.decode(bytes.byteslice(pos, len))
243
+ p.headers = Table.decode(bytes.byteslice(pos, len))
177
244
  pos += len
178
245
  end
179
246
  if (flags & 0x1000).positive?
180
- h[:delivery_mode] = bytes[pos].ord
247
+ p.delivery_mode = bytes.getbyte(pos)
181
248
  pos += 1
182
249
  end
183
250
  if (flags & 0x0800).positive?
184
- h[:priority] = bytes[pos].ord
251
+ p.priority = bytes.getbyte(pos)
185
252
  pos += 1
186
253
  end
187
254
  if (flags & 0x0400).positive?
188
- len = bytes[pos].ord
255
+ len = bytes.getbyte(pos)
189
256
  pos += 1
190
- h[:correlation_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
257
+ p.correlation_id = bytes.byteslice(pos, len).force_encoding("utf-8")
191
258
  pos += len
192
259
  end
193
260
  if (flags & 0x0200).positive?
194
- len = bytes[pos].ord
261
+ len = bytes.getbyte(pos)
195
262
  pos += 1
196
- h[:reply_to] = bytes.byteslice(pos, len).force_encoding("utf-8")
263
+ p.reply_to = bytes.byteslice(pos, len).force_encoding("utf-8")
197
264
  pos += len
198
265
  end
199
266
  if (flags & 0x0100).positive?
200
- len = bytes[pos].ord
267
+ len = bytes.getbyte(pos)
201
268
  pos += 1
202
- h[:expiration] = bytes.byteslice(pos, len).force_encoding("utf-8")
269
+ p.expiration = bytes.byteslice(pos, len).force_encoding("utf-8")
203
270
  pos += len
204
271
  end
205
272
  if (flags & 0x0080).positive?
206
- len = bytes[pos].ord
273
+ len = bytes.getbyte(pos)
207
274
  pos += 1
208
- h[:message_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
275
+ p.message_id = bytes.byteslice(pos, len).force_encoding("utf-8")
209
276
  pos += len
210
277
  end
211
278
  if (flags & 0x0040).positive?
212
- h[:timestamp] = Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))
279
+ p.timestamp = Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))
213
280
  pos += 8
214
281
  end
215
282
  if (flags & 0x0020).positive?
216
- len = bytes[pos].ord
283
+ len = bytes.getbyte(pos)
217
284
  pos += 1
218
- h[:type] = bytes.byteslice(pos, len).force_encoding("utf-8")
285
+ p.type = bytes.byteslice(pos, len).force_encoding("utf-8")
219
286
  pos += len
220
287
  end
221
288
  if (flags & 0x0010).positive?
222
- len = bytes[pos].ord
289
+ len = bytes.getbyte(pos)
223
290
  pos += 1
224
- h[:user_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
291
+ p.user_id = bytes.byteslice(pos, len).force_encoding("utf-8")
225
292
  pos += len
226
293
  end
227
294
  if (flags & 0x0008).positive?
228
- len = bytes[pos].ord
295
+ len = bytes.getbyte(pos)
229
296
  pos += 1
230
- h[:app_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
297
+ p.app_id = bytes.byteslice(pos, len).force_encoding("utf-8")
231
298
  end
232
- h
299
+ p
233
300
  end
234
301
  end
235
302
  end
@@ -11,30 +11,24 @@ module AMQP
11
11
  @name = name
12
12
  end
13
13
 
14
- # Publish to the queue
15
- # @param body [String] The message body
16
- # @param properties [Properties]
17
- # @option properties [String] content_type Content type of the message body
18
- # @option properties [String] content_encoding Content encoding of the body
19
- # @option properties [Hash<String, Object>] headers Custom headers
20
- # @option properties [Integer] delivery_mode 2 for persisted message, transient messages for all other values
21
- # @option properties [Integer] priority A priority of the message (between 0 and 255)
22
- # @option properties [Integer] correlation_id A correlation id, most often used used for RPC communication
23
- # @option properties [String] reply_to Queue to reply RPC responses to
24
- # @option properties [Integer, String] expiration Number of seconds the message will stay in the queue
25
- # @option properties [String] message_id Can be used to uniquely identify the message, e.g. for deduplication
26
- # @option properties [Date] timestamp Often used for the time the message was originally generated
27
- # @option properties [String] type Can indicate what kind of message this is
28
- # @option properties [String] user_id Can be used to verify that this is the user that published the message
29
- # @option properties [String] app_id Can be used to indicates which app that generated the message
30
- # @return [Queue] self
14
+ # Publish to the queue, wait for confirm
15
+ # @param (see Client#publish)
16
+ # @option (see Client#publish)
17
+ # @raise (see Client#publish)
18
+ # @return [self]
31
19
  def publish(body, **properties)
32
20
  @client.publish(body, "", @name, **properties)
33
21
  self
34
22
  end
35
23
 
36
24
  # Subscribe/consume from the queue
37
- # @return [Queue] self
25
+ # @param no_ack [Boolean] When false messages have to be manually acknowledged (or rejected)
26
+ # @param prefetch [Integer] Specify how many messages to prefetch for consumers with no_ack is false
27
+ # @param worker_threads [Integer] Number of threads processing messages,
28
+ # 0 means that the thread calling this method will be blocked
29
+ # @param arguments [Hash] Custom arguments to the consumer
30
+ # @yield [Message] Delivered message from the queue
31
+ # @return [self]
38
32
  def subscribe(no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
39
33
  @client.subscribe(@name, no_ack: no_ack, prefetch: prefetch, worker_threads: worker_threads, arguments: arguments, &blk)
40
34
  self
@@ -44,7 +38,7 @@ module AMQP
44
38
  # @param exchange [String] Name of the exchange to bind to
45
39
  # @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
46
40
  # @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
47
- # @return [Queue] self
41
+ # @return [self]
48
42
  def bind(exchange, binding_key, arguments: {})
49
43
  @client.bind(@name, exchange, binding_key, arguments: arguments)
50
44
  self
@@ -54,14 +48,14 @@ module AMQP
54
48
  # @param exchange [String] Name of the exchange to unbind from
55
49
  # @param binding_key [String] Binding key which the queue is bound to the exchange with
56
50
  # @param arguments [Hash] Arguments matching the binding that's being removed
57
- # @return [Queue] self
51
+ # @return [self]
58
52
  def unbind(exchange, binding_key, arguments: {})
59
53
  @client.unbind(@name, exchange, binding_key, arguments: arguments)
60
54
  self
61
55
  end
62
56
 
63
57
  # Purge/empty the queue
64
- # @return [Queue] self
58
+ # @return [self]
65
59
  def purge
66
60
  @client.purge(@name)
67
61
  self
@@ -5,32 +5,34 @@ module AMQP
5
5
  # Encode and decode an AMQP table to/from hash, only used internally
6
6
  # @api private
7
7
  module Table
8
- module_function
9
-
10
8
  # Encodes a hash into a byte array
9
+ # @param hash [Hash]
11
10
  # @return [String] Byte array
12
- def encode(hash)
13
- tbl = StringIO.new
14
- hash.each do |k, v|
11
+ def self.encode(hash)
12
+ return "" if hash.empty?
13
+
14
+ arr = []
15
+ fmt = String.new
16
+ hash.each do |k, value|
15
17
  key = k.to_s
16
- tbl.write [key.bytesize, key].pack("Ca*")
17
- tbl.write encode_field(v)
18
+ arr.push(key.bytesize, key)
19
+ fmt << "Ca*"
20
+ encode_field(value, arr, fmt)
18
21
  end
19
- tbl.string
22
+ arr.pack(fmt)
20
23
  end
21
24
 
22
25
  # Decodes an AMQP table into a hash
23
26
  # @return [Hash<String, Object>]
24
- def decode(bytes)
27
+ def self.decode(bytes)
25
28
  hash = {}
26
29
  pos = 0
27
30
  while pos < bytes.bytesize
28
- key_len = bytes[pos].ord
31
+ key_len = bytes.getbyte(pos)
29
32
  pos += 1
30
33
  key = bytes.byteslice(pos, key_len).force_encoding("utf-8")
31
34
  pos += key_len
32
- rest = bytes.byteslice(pos, bytes.bytesize - pos)
33
- len, value = decode_field(rest)
35
+ len, value = decode_field(bytes, pos)
34
36
  pos += len + 1
35
37
  hash[key] = value
36
38
  end
@@ -38,43 +40,58 @@ module AMQP
38
40
  end
39
41
 
40
42
  # Encoding a single value in a table
43
+ # @return [nil]
41
44
  # @api private
42
- def encode_field(value)
45
+ def self.encode_field(value, arr, fmt)
43
46
  case value
44
47
  when Integer
45
48
  if value > 2**31
46
- ["l", value].pack("a q>")
49
+ arr.push("l", value)
50
+ fmt << "aq>"
47
51
  else
48
- ["I", value].pack("a l>")
52
+ arr.push("I", value)
53
+ fmt << "al>"
49
54
  end
50
55
  when Float
51
- ["d", value].pack("a G")
56
+ arr.push("d", value)
57
+ fmt << "aG"
52
58
  when String
53
- ["S", value.bytesize, value].pack("a L> a*")
59
+ arr.push("S", value.bytesize, value)
60
+ fmt << "aL>a*"
54
61
  when Time
55
- ["T", value.to_i].pack("a Q>")
62
+ arr.push("T", value.to_i)
63
+ fmt << "aQ>"
56
64
  when Array
57
- bytes = value.map { |e| encode_field(e) }.join
58
- ["A", bytes.bytesize, bytes].pack("a L> a*")
65
+ value_arr = []
66
+ value_fmt = String.new
67
+ value.each { |e| encode_field(e, value_arr, value_fmt) }
68
+ bytes = value_arr.pack(value_fmt)
69
+ arr.push("A", bytes.bytesize, bytes)
70
+ fmt << "aL>a*"
59
71
  when Hash
60
72
  bytes = Table.encode(value)
61
- ["F", bytes.bytesize, bytes].pack("a L> a*")
73
+ arr.push("F", bytes.bytesize, bytes)
74
+ fmt << "aL>a*"
62
75
  when true
63
- ["t", 1].pack("a C")
76
+ arr.push("t", 1)
77
+ fmt << "aC"
64
78
  when false
65
- ["t", 0].pack("a C")
79
+ arr.push("t", 0)
80
+ fmt << "aC"
66
81
  when nil
67
- ["V"].pack("a")
82
+ arr << "V"
83
+ fmt << "a"
68
84
  else raise ArgumentError, "unsupported table field type: #{value.class}"
69
85
  end
86
+ nil
70
87
  end
71
88
 
72
89
  # Decodes a single value
73
- # @return [Array<Integer, Object>] Bytes read and the parsed object
90
+ # @return [Array<Integer, Object>] Bytes read and the parsed value
74
91
  # @api private
75
- def decode_field(bytes)
76
- type = bytes[0]
77
- pos = 1
92
+ def self.decode_field(bytes, pos)
93
+ type = bytes[pos]
94
+ pos += 1
78
95
  case type
79
96
  when "S"
80
97
  len = bytes.byteslice(pos, 4).unpack1("L>")
@@ -86,15 +103,17 @@ module AMQP
86
103
  [4 + len, decode(bytes.byteslice(pos, len))]
87
104
  when "A"
88
105
  len = bytes.byteslice(pos, 4).unpack1("L>")
106
+ pos += 4
107
+ array_end = pos + len
89
108
  a = []
90
- while pos < len
91
- length, value = decode_field(bytes.byteslice(pos, -1))
109
+ while pos < array_end
110
+ length, value = decode_field(bytes, pos)
92
111
  pos += length + 1
93
112
  a << value
94
113
  end
95
114
  [4 + len, a]
96
115
  when "t"
97
- [1, bytes[pos].ord == 1]
116
+ [1, bytes.getbyte(pos) == 1]
98
117
  when "b"
99
118
  [1, bytes.byteslice(pos, 1).unpack1("c")]
100
119
  when "B"
@@ -114,7 +133,7 @@ module AMQP
114
133
  when "d"
115
134
  [8, bytes.byteslice(pos, 8).unpack1("G")]
116
135
  when "D"
117
- scale = bytes[pos].ord
136
+ scale = bytes.getbyte(pos)
118
137
  pos += 1
119
138
  value = bytes.byteslice(pos, 4).unpack1("L>")
120
139
  d = value / 10**scale
@@ -3,6 +3,6 @@
3
3
  module AMQP
4
4
  class Client
5
5
  # Version of the client library
6
- VERSION = "1.0.1"
6
+ VERSION = "1.1.2"
7
7
  end
8
8
  end