prepor-beefcake 1.0.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c84028ca26869888306b5bf5c48b48dfb79909b2
4
+ data.tar.gz: 7396b01b5432a3452ec5d59b3f19e3b140c66e19
5
+ SHA512:
6
+ metadata.gz: ffea398a17e0efa2bf8e6278c895caea789a086aff759ebb06bd57f25fff28da763fce39ac618c2cc13a6b814897079257322d6973bf330bc8e1ade0953a980a
7
+ data.tar.gz: 719b223605e7c5acaa0b795df704caf16576478cea4dd870225f7118d31613ac171f9ca62601fc242d0ba91d33942e9c919451d486038a6059f80defc8db4e67
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ *.gem
2
+ Gemfile.lock
3
+ bench/beefcake.prof.*
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.0
6
+ - 2.1.1
7
+ - 2.1.2
8
+ - jruby-19mode
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in beefcake.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2011 Blake Mizerany
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # Beefcake
2
+
3
+ A sane Google Protocol Buffers library for Ruby. It's all about being Buf;
4
+ ProtoBuf.
5
+
6
+ ## Installation
7
+
8
+ ```shell
9
+ gem install beefcake
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ```ruby
15
+ require 'beefcake'
16
+
17
+ class Variety
18
+ include Beefcake::Message
19
+
20
+ # Required
21
+ required :x, :int32, 1
22
+ required :y, :int32, 2
23
+
24
+ # Optional
25
+ optional :tag, :string, 3
26
+
27
+ # Repeated
28
+ repeated :ary, :fixed64, 4
29
+ repeated :pary, :fixed64, 5, :packed => true
30
+
31
+ # Enums - Simply use a Module (NOTE: defaults are optional)
32
+ module Foonum
33
+ A = 1
34
+ B = 2
35
+ end
36
+
37
+ # As per the spec, defaults are only set at the end
38
+ # of decoding a message, not on object creation.
39
+ optional :foo, Foonum, 6, :default => Foonum::B
40
+ end
41
+
42
+ # You can create a new message with hash arguments:
43
+ x = Variety.new(:x => 1, :y => 2)
44
+
45
+ # You can set fields individually using accessor methods:
46
+ x = Variety.new
47
+ x.x = 1
48
+ x.y = 2
49
+
50
+ # And you can access fields using Hash syntax:
51
+ x[:x] # => 1
52
+ x[:y] = 4
53
+ x # => <Variety x: 1, y: 4>
54
+ ```
55
+
56
+ ### Encoding
57
+
58
+ Any object responding to `<<` can accept encoding
59
+
60
+ ```ruby
61
+ # see code example above for the definition of Variety
62
+ x = Variety.new(:x => 1, :y => 2)
63
+
64
+ # For example, you can encode into a String:
65
+ s = ""
66
+ x.encode(s)
67
+ s # => "\b\x01\x10\x02)\0"
68
+
69
+ # If you don't encode into anything, a new Beefcake::Buffer will be returned:
70
+ x.encode # => #<Beefcake::Buffer:0x007fbfe1867ab0 @buf="\b\x01\x10\x02)\0">
71
+
72
+ # And that buffer can be converted to a String:
73
+ x.encode.to_s # => "\b\x01\x10\x02)\0"
74
+ ```
75
+
76
+ ### Decoding
77
+
78
+ ```ruby
79
+ # see code example above for the definition of Variety
80
+ x = Variety.new(:x => 1, :y => 2)
81
+
82
+ # You can decode from a Beefcake::Buffer
83
+ encoded = x.encode
84
+ Variety.decode(encoded) # => <Variety x: 1, y: 2, pary: [], foo: B(2)>
85
+
86
+ # Decoding from a String works the same way:
87
+ Variety.decode(encoded.to_s) # => <Variety x: 1, y: 2, pary: [], foo: B(2)>
88
+
89
+ # You can update a Beefcake::Message instance with new data too:
90
+ new_data = Variety.new(x: 12345, y: 2).encode
91
+ Variety.decoded(new_data, x)
92
+ x # => <Variety x: 12345, y: 2, pary: [], foo: B(2)>
93
+ ```
94
+
95
+ ### Generate code from `.proto` file
96
+
97
+ ```shell
98
+ protoc --beefcake_out output/path -I path/to/proto/files/dir path/to/file.proto
99
+ ```
100
+
101
+ You can set the `BEEFCAKE_NAMESPACE` variable to generate the classes under a
102
+ desired namespace. (i.e. App::Foo::Bar)
103
+
104
+ ## About
105
+
106
+ Ruby deserves and needs first-class ProtoBuf support. Other libs didn't feel
107
+ very "Ruby" to me and were hard to parse.
108
+
109
+ This library was built with EventMachine in mind. Not just blocking-IO.
110
+
111
+ Source: https://github.com/protobuf-ruby/beefcake
112
+
113
+ ### Support Features
114
+
115
+ * Optional fields
116
+ * Required fields
117
+ * Repeated fields
118
+ * Packed Repeated Fields
119
+ * Varint fields
120
+ * 32-bit fields
121
+ * 64-bit fields
122
+ * Length-delimited fields
123
+ * Embedded Messages
124
+ * Unknown fields are ignored (as per spec)
125
+ * Enums
126
+ * Defaults (i.e. `optional :foo, :string, :default => "bar"`)
127
+ * Varint-encoded length-delimited message streams
128
+
129
+ ### Future
130
+
131
+ * Imports
132
+ * Use package in generation
133
+ * Groups (would be nice for accessing older protos)
134
+
135
+ ### Further Reading
136
+
137
+ http://code.google.com/apis/protocolbuffers/docs/encoding.html
138
+
139
+ ## Testing
140
+
141
+ rake test
142
+
143
+ Beefcake conducts continuous integration on [Travis CI](http://travis-ci.org).
144
+ The current build status for HEAD is [![Build Status](https://travis-ci.org/protobuf-ruby/beefcake.png?branch=master)](https://travis-ci.org/protobuf-ruby/beefcake).
145
+
146
+ All pull requests automatically trigger a build request. Please ensure that
147
+ tests succeed.
148
+
149
+ Currently Beefcake is tested and working on:
150
+
151
+ * Ruby 1.9.3
152
+ * Ruby 2.0.0
153
+ * Ruby 2.1.0
154
+ * Ruby 2.1.1
155
+ * Ruby 2.1.2
156
+ * JRuby in 1.9 mode
157
+
158
+ ## Thank You
159
+
160
+ * Keith Rarick (kr) for help with encoding/decoding.
161
+ * Aman Gupta (tmm1) for help with cross VM support and performance enhancements.
data/RELEASE_NOTES.md ADDED
@@ -0,0 +1,38 @@
1
+ # Beefcake Release Notes
2
+
3
+ # 0.1.0 - 2014-09-05
4
+
5
+ Release 1.0.0 includes changes and improvements.
6
+
7
+ * Version numbering now properly semantic.
8
+ * Ruby 1.8 is no longer supported.
9
+ * Field number re-use raises a `DuplicateFieldNumber` error.
10
+ * Checking to see if a type is encodable is much faster.
11
+ * Fields named `fields` are now supported.
12
+ * String read and decoding are benchmarked during testing.
13
+ * `string` fields now decode with a `UTF-8` encoding.
14
+
15
+ # 0.5.0 - 2013-12-20
16
+
17
+ Release 0.5.0 corrects a few behaviors.
18
+
19
+ * Drastically revised README, written by Tobias "grobie" Schmidt
20
+ * Output fewer newlines in generated files, fixed by Tobias "grobie" Schmidt
21
+ * Don't crash when attempting to reencode frozen strings,
22
+ found thanks to Kyle "Aphyr" Kingsbury
23
+ * Return `nil` instead of raising a generic Ruby error when trying to
24
+ decode a zero-length buffer, fixed by Tobias "grobie" Schmidt
25
+
26
+ # 0.4.0 - 2013-10-10
27
+
28
+ Release 0.4.0 is the first with new maintainers.
29
+
30
+ * Modernize tests
31
+ * Add Travis CI monitoring
32
+ * Support varint-encoded length-delimited buffers
33
+ * Support generation with recursive definitions
34
+ * Support Ruby 2.0
35
+ * Support encoded buffers
36
+ * Support false but non-nil values
37
+ * Support top-level enums, added by Kim Altintop:
38
+ https://github.com/protobuf-ruby/beefcake/pull/23
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'bundler/gem_tasks'
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << 'minitest'
8
+ t.test_files = FileList['test/*_test.rb']
9
+ t.verbose = true
10
+ end
11
+
12
+ task :default => :test
data/beefcake.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "beefcake/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "prepor-beefcake"
7
+ s.version = Beefcake::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Blake Mizerany", "Matt Proud", "Bryce Kerley"]
10
+ s.email = ["blake.mizerany@gmail.com", "matt.proud@gmail.com", "bkerley@brycekerley.net"]
11
+ s.homepage = "https://github.com/protobuf-ruby/beefcake"
12
+ s.summary = %q{A sane protobuf library for Ruby}
13
+ s.description = %q{A sane protobuf library for Ruby}
14
+ s.license = 'MIT'
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.required_ruby_version = '>= 1.9.3'
22
+
23
+ s.add_development_dependency('rake', '~> 10.1.0')
24
+ s.add_development_dependency('minitest', '~> 5.3')
25
+ end
data/bench/simple.rb ADDED
@@ -0,0 +1,116 @@
1
+ $:.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'beefcake'
3
+
4
+ class MyMessage
5
+ include Beefcake::Message
6
+ required :number, :int32, 1
7
+ required :chars, :string, 2
8
+ required :raw, :bytes, 3
9
+ required :bool, :bool, 4
10
+ required :float, :float, 5
11
+ end
12
+
13
+ ITERS = 100_000
14
+
15
+ case ARGV[0]
16
+ when 'pprof'
17
+ # profile message creation/encoding/decoding w/ perftools.rb
18
+ # works on 1.8 and 1.9
19
+ # ruby bench/simple.rb pprof
20
+ # open bench/beefcake.prof.gif
21
+
22
+ ENV['CPUPROFILE_FREQUENCY'] = '4000'
23
+ require 'rubygems'
24
+ require 'perftools'
25
+ PerfTools::CpuProfiler.start(File.expand_path("../beefcake.prof", __FILE__)) do
26
+ ITERS.times do
27
+ str = MyMessage.new(
28
+ :number => 12345,
29
+ :chars => 'hello',
30
+ :raw => 'world',
31
+ :bool => true,
32
+ :float => 1.2345
33
+ ).encode
34
+ MyMessage.decode(str)
35
+ end
36
+ end
37
+ Dir.chdir(File.dirname(__FILE__)) do
38
+ `pprof.rb beefcake.prof --gif > beefcake.prof.gif`
39
+ end
40
+
41
+ when 'ruby-prof'
42
+ # profile message creation/encoding/decoding w/ ruby-prof
43
+ # works on 1.8 and 1.9
44
+ # ruby bench/simple.rb ruby-prof
45
+ # open bench/beefcake.prof.html
46
+
47
+ require 'ruby-prof'
48
+ result = RubyProf.profile do
49
+ ITERS.times do
50
+ str = MyMessage.new(
51
+ :number => 12345,
52
+ :chars => 'hello',
53
+ :raw => 'world',
54
+ :bool => true,
55
+ :float => 1.2345
56
+ ).encode
57
+ MyMessage.decode(str)
58
+ end
59
+ end
60
+
61
+ filename = File.expand_path('beefcake.prof.html', File.dirname(__FILE__))
62
+ File.open(filename, 'w') do |file|
63
+ RubyProf::GraphHtmlPrinter.new(result).print(file)
64
+ end
65
+
66
+ else
67
+ # benchmark message creation/encoding/decoding
68
+ # rvm install 1.8.7 1.9.2 jruby rbx
69
+ # rvm 1.8.7,1.9.2,jruby,rbx ruby bench/simple.rb
70
+
71
+ require 'benchmark'
72
+
73
+ Benchmark.bmbm do |x|
74
+ x.report 'object creation' do
75
+ ITERS.times do
76
+ Object.new
77
+ end
78
+ end
79
+ x.report 'message creation' do
80
+ ITERS.times do
81
+ MyMessage.new(
82
+ :number => 12345,
83
+ :chars => 'hello',
84
+ :raw => 'world',
85
+ :bool => true,
86
+ :float => 1.2345
87
+ )
88
+ end
89
+ end
90
+ x.report 'message encoding' do
91
+ m = MyMessage.new(
92
+ :number => 12345,
93
+ :chars => 'hello',
94
+ :raw => 'world',
95
+ :bool => true,
96
+ :float => 1.2345
97
+ )
98
+ ITERS.times do
99
+ m.encode
100
+ end
101
+ end
102
+ x.report 'message decoding' do
103
+ str = MyMessage.new(
104
+ :number => 12345,
105
+ :chars => 'hello',
106
+ :raw => 'world',
107
+ :bool => true,
108
+ :float => 1.2345
109
+ ).encode.to_s
110
+ ITERS.times do
111
+ MyMessage.decode(str.dup)
112
+ end
113
+ end
114
+ end
115
+
116
+ end
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'beefcake/generator'
4
+
5
+ req = CodeGeneratorRequest.decode(STDIN.read)
6
+ res = Beefcake::Generator.compile(req)
7
+
8
+ # Send it out!
9
+ STDOUT.print(res.encode)
Binary file
data/lib/beefcake.rb ADDED
@@ -0,0 +1,283 @@
1
+ require 'beefcake/buffer'
2
+
3
+ module Beefcake
4
+ module Message
5
+
6
+ class WrongTypeError < StandardError
7
+ def initialize(name, exp, got)
8
+ super("Wrong type `#{got}` given for (#{name}). Expected #{exp}")
9
+ end
10
+ end
11
+
12
+
13
+ class InvalidValueError < StandardError
14
+ def initialize(name, val)
15
+ super("Invalid Value given for `#{name}`: #{val.inspect}")
16
+ end
17
+ end
18
+
19
+
20
+ class RequiredFieldNotSetError < StandardError
21
+ def initialize(name)
22
+ super("Field #{name} is required but nil")
23
+ end
24
+ end
25
+
26
+ class DuplicateFieldNumber < StandardError
27
+ def initialize(num, name)
28
+ super("Field number #{num} (#{name}) was already used")
29
+ end
30
+ end
31
+
32
+ class Field < Struct.new(:rule, :name, :type, :fn, :opts)
33
+ def <=>(o)
34
+ fn <=> o.fn
35
+ end
36
+ end
37
+
38
+
39
+ module Dsl
40
+ def required(name, type, fn, opts={})
41
+ field(:required, name, type, fn, opts)
42
+ end
43
+
44
+ def repeated(name, type, fn, opts={})
45
+ field(:repeated, name, type, fn, opts)
46
+ end
47
+
48
+ def optional(name, type, fn, opts={})
49
+ field(:optional, name, type, fn, opts)
50
+ end
51
+
52
+ def field(rule, name, type, fn, opts)
53
+ if fields.include?(fn)
54
+ raise DuplicateFieldNumber.new(fn, name)
55
+ end
56
+ fields[fn] = Field.new(rule, name, type, fn, opts)
57
+ attr_accessor name
58
+ end
59
+
60
+ def fields
61
+ @fields ||= {}
62
+ end
63
+ end
64
+
65
+ module Encode
66
+
67
+ def encode(buf = Buffer.new)
68
+ validate!
69
+
70
+ if ! buf.respond_to?(:<<)
71
+ raise ArgumentError, "buf doesn't respond to `<<`"
72
+ end
73
+
74
+ if ! buf.is_a?(Buffer)
75
+ buf = Buffer.new(buf)
76
+ end
77
+
78
+ # TODO: Error if any required fields at nil
79
+
80
+ __beefcake_fields__.values.sort.each do |fld|
81
+ if fld.opts[:packed]
82
+ bytes = encode!(Buffer.new, fld, 0)
83
+ buf.append_info(fld.fn, Buffer.wire_for(fld.type))
84
+ buf.append_uint64(bytes.length)
85
+ buf << bytes
86
+ else
87
+ encode!(buf, fld, fld.fn)
88
+ end
89
+ end
90
+
91
+ buf
92
+ end
93
+
94
+ def encode!(buf, fld, fn)
95
+ v = self[fld.name]
96
+ v = v.is_a?(Array) ? v : [v]
97
+
98
+ v.compact.each do |val|
99
+ case fld.type
100
+ when Class # encodable
101
+ # TODO: raise error if type != val.class
102
+ buf.append(:string, val.encode, fn)
103
+ when Module # enum
104
+ if ! valid_enum?(fld.type, val)
105
+ raise InvalidValueError.new(fld.name, val)
106
+ end
107
+
108
+ buf.append(:int32, val, fn)
109
+ else
110
+ buf.append(fld.type, val, fn)
111
+ end
112
+ end
113
+
114
+ buf
115
+ end
116
+
117
+ def write_delimited(buf = Buffer.new)
118
+ if ! buf.respond_to?(:<<)
119
+ raise ArgumentError, "buf doesn't respond to `<<`"
120
+ end
121
+
122
+ if ! buf.is_a?(Buffer)
123
+ buf = Buffer.new(buf)
124
+ end
125
+
126
+ buf.append_bytes(encode)
127
+
128
+ buf
129
+ end
130
+
131
+ def valid_enum?(mod, val)
132
+ !!name_for(mod, val)
133
+ end
134
+
135
+ def name_for(mod, val)
136
+ mod.constants.each do |name|
137
+ if mod.const_get(name) == val
138
+ return name
139
+ end
140
+ end
141
+ nil
142
+ end
143
+
144
+ def validate!
145
+ __beefcake_fields__.values.each do |fld|
146
+ if fld.rule == :required && self[fld.name].nil?
147
+ raise RequiredFieldNotSetError, fld.name
148
+ end
149
+ end
150
+ end
151
+
152
+ end
153
+
154
+
155
+ module Decode
156
+ def decode(buf, o=self.new)
157
+ if ! buf.is_a?(Buffer)
158
+ buf = Buffer.new(buf)
159
+ end
160
+
161
+ # TODO: test for incomplete buffer
162
+ while buf.length > 0
163
+ fn, wire = buf.read_info
164
+
165
+ fld = fields[fn]
166
+
167
+ # We don't have a field for with index fn.
168
+ # Ignore this data and move on.
169
+ if fld.nil?
170
+ buf.skip(wire)
171
+ next
172
+ end
173
+
174
+ exp = Buffer.wire_for(fld.type)
175
+ if wire != exp
176
+ raise WrongTypeError.new(fld.name, exp, wire)
177
+ end
178
+
179
+ if fld.rule == :repeated && fld.opts[:packed]
180
+ len = buf.read_uint64
181
+ tmp = Buffer.new(buf.read(len))
182
+ o[fld.name] ||= []
183
+ while tmp.length > 0
184
+ o[fld.name] << tmp.read(fld.type)
185
+ end
186
+ elsif fld.rule == :repeated
187
+ val = buf.read(fld.type)
188
+ (o[fld.name] ||= []) << val
189
+ else
190
+ val = buf.read(fld.type)
191
+ o[fld.name] = val
192
+ end
193
+ end
194
+
195
+ # Set defaults
196
+ fields.values.each do |f|
197
+ next if o[f.name] == false
198
+ o[f.name] ||= f.opts[:default]
199
+ end
200
+
201
+ o.validate!
202
+
203
+ o
204
+ end
205
+
206
+ def read_delimited(buf, o=self.new)
207
+ if ! buf.is_a?(Buffer)
208
+ buf = Buffer.new(buf)
209
+ end
210
+
211
+ return if buf.length == 0
212
+
213
+ n = buf.read_int64
214
+ tmp = Buffer.new(buf.read(n))
215
+
216
+ decode(tmp, o)
217
+ end
218
+ end
219
+
220
+
221
+ def self.included(o)
222
+ o.extend Dsl
223
+ o.extend Decode
224
+ o.send(:include, Encode)
225
+ end
226
+
227
+ def initialize(attrs={})
228
+ __beefcake_fields__.values.each do |fld|
229
+ self[fld.name] = attrs[fld.name]
230
+ end
231
+ end
232
+
233
+ def __beefcake_fields__
234
+ self.class.fields
235
+ end
236
+
237
+ def [](k)
238
+ __send__(k)
239
+ end
240
+
241
+ def []=(k, v)
242
+ __send__("#{k}=", v)
243
+ end
244
+
245
+ def ==(o)
246
+ return false if (o == nil) || (o == false)
247
+ return false unless o.is_a? self.class
248
+ __beefcake_fields__.values.all? {|fld| self[fld.name] == o[fld.name] }
249
+ end
250
+
251
+ def inspect
252
+ set = __beefcake_fields__.values.select {|fld| self[fld.name] != nil }
253
+
254
+ flds = set.map do |fld|
255
+ val = self[fld.name]
256
+
257
+ case fld.type
258
+ when Class
259
+ "#{fld.name}: #{val.inspect}"
260
+ when Module
261
+ title = name_for(fld.type, val) || "-NA-"
262
+ "#{fld.name}: #{title}(#{val.inspect})"
263
+ else
264
+ "#{fld.name}: #{val.inspect}"
265
+ end
266
+ end
267
+
268
+ "<#{self.class.name} #{flds.join(", ")}>"
269
+ end
270
+
271
+ def to_hash
272
+ __beefcake_fields__.values.inject({}) do |h, fld|
273
+ value = self[fld.name]
274
+ unless value.nil?
275
+ h[fld.name] = value
276
+ end
277
+ h
278
+ end
279
+ end
280
+
281
+ end
282
+
283
+ end