thrift 0.2.0.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/CHANGELOG +1 -13
  2. data/Manifest +11 -23
  3. data/Rakefile +8 -6
  4. data/ext/binary_protocol_accelerated.c +4 -53
  5. data/ext/compact_protocol.c +3 -53
  6. data/ext/extconf.rb +4 -18
  7. data/ext/struct.c +181 -130
  8. data/ext/struct.h +2 -44
  9. data/ext/thrift_native.c +3 -3
  10. data/lib/thrift.rb +6 -1
  11. data/lib/thrift/protocol/binary_protocol_accelerated.rb +5 -1
  12. data/lib/thrift/protocol/compact_protocol.rb +1 -0
  13. data/lib/thrift/server/nonblocking_server.rb +1 -2
  14. data/lib/thrift/struct.rb +46 -112
  15. data/lib/thrift/struct_union.rb +159 -0
  16. data/lib/thrift/transport/http_client_transport.rb +12 -6
  17. data/lib/thrift/transport/memory_buffer_transport.rb +2 -2
  18. data/lib/thrift/types.rb +1 -1
  19. data/lib/thrift/union.rb +179 -0
  20. data/spec/ThriftSpec.thrift +48 -0
  21. data/spec/binary_protocol_accelerated_spec.rb +18 -13
  22. data/spec/binary_protocol_spec_shared.rb +2 -2
  23. data/spec/compact_protocol_spec.rb +19 -3
  24. data/spec/http_client_spec.rb +17 -2
  25. data/spec/struct_spec.rb +34 -0
  26. data/spec/union_spec.rb +193 -0
  27. data/thrift.gemspec +11 -13
  28. metadata +36 -67
  29. data.tar.gz.sig +0 -2
  30. data/Makefile.am +0 -47
  31. data/benchmark/gen-rb/BenchmarkService.rb +0 -81
  32. data/benchmark/gen-rb/Benchmark_constants.rb +0 -11
  33. data/benchmark/gen-rb/Benchmark_types.rb +0 -10
  34. data/lib/thrift/protocol/binaryprotocol.rb +0 -213
  35. data/lib/thrift/protocol/binaryprotocolaccelerated.rb +0 -19
  36. data/lib/thrift/protocol/tbinaryprotocol.rb +0 -2
  37. data/lib/thrift/protocol/tprotocol.rb +0 -2
  38. data/lib/thrift/server/httpserver.rb +0 -44
  39. data/lib/thrift/server/nonblockingserver.rb +0 -278
  40. data/lib/thrift/server/thttpserver.rb +0 -2
  41. data/lib/thrift/server/tserver.rb +0 -2
  42. data/spec/gen-rb/NonblockingService.rb +0 -268
  43. data/spec/gen-rb/ThriftSpec_constants.rb +0 -11
  44. data/spec/gen-rb/ThriftSpec_types.rb +0 -134
  45. metadata.gz.sig +0 -0
data.tar.gz.sig DELETED
@@ -1,2 +0,0 @@
1
- Ť�E��1�].)� ��3i��s+�U������hc=D�����<��6^`�V�c"��\<�c�􅵌B��+��SLV���gOKxiڬ���œ�K|oa���rk�g�d���� ����FBQ�!���;��w��|�W(�6"���Ƹ�h����3�p!��oH��qh�����Q]+A=kM��d�-��E*�������M((KJm����qC��<�Ex���s����@���
2
- z�18
@@ -1,47 +0,0 @@
1
- #
2
- # Licensed to the Apache Software Foundation (ASF) under one
3
- # or more contributor license agreements. See the NOTICE file
4
- # distributed with this work for additional information
5
- # regarding copyright ownership. The ASF licenses this file
6
- # to you under the Apache License, Version 2.0 (the
7
- # "License"); you may not use this file except in compliance
8
- # with the License. You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing,
13
- # software distributed under the License is distributed on an
14
- # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
- # KIND, either express or implied. See the License for the
16
- # specific language governing permissions and limitations
17
- # under the License.
18
- #
19
-
20
- EXTRA_DIST = \
21
- CHANGELOG \
22
- Rakefile \
23
- Manifest \
24
- setup.rb \
25
- lib \
26
- ext \
27
- benchmark \
28
- script \
29
- spec
30
-
31
- all-local:
32
- $(RUBY) setup.rb config
33
- $(RUBY) setup.rb setup
34
-
35
- install-exec-hook:
36
- $(RUBY) setup.rb install
37
-
38
- # Make sure this doesn't fail if Ruby is not configured.
39
- clean-local:
40
- RUBY=$(RUBY) ; if test -z "$$RUBY" ; then RUBY=: ; fi ; \
41
- $$RUBY setup.rb clean
42
-
43
- check-local: all
44
- if HAVE_RSPEC
45
- rake spec
46
- endif
47
-
@@ -1,81 +0,0 @@
1
- #
2
- # Autogenerated by Thrift
3
- #
4
- # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
5
- #
6
-
7
- require 'thrift'
8
- require 'thrift/protocol'
9
- require File.dirname(__FILE__) + '/Benchmark_types'
10
-
11
- module ThriftBenchmark
12
- module BenchmarkService
13
- class Client
14
- include ::Thrift::Client
15
-
16
- def fibonacci(n)
17
- send_fibonacci(n)
18
- return recv_fibonacci()
19
- end
20
-
21
- def send_fibonacci(n)
22
- send_message('fibonacci', Fibonacci_args, :n => n)
23
- end
24
-
25
- def recv_fibonacci()
26
- result = receive_message(Fibonacci_result)
27
- return result.success unless result.success.nil?
28
- raise Thrift::ApplicationException.new(Thrift::ApplicationException::MISSING_RESULT, 'fibonacci failed: unknown result')
29
- end
30
-
31
- end
32
-
33
- class Processor
34
- include ::Thrift::Processor
35
-
36
- def process_fibonacci(seqid, iprot, oprot)
37
- args = read_args(iprot, Fibonacci_args)
38
- result = Fibonacci_result.new()
39
- result.success = @handler.fibonacci(args.n)
40
- write_result(result, oprot, 'fibonacci', seqid)
41
- end
42
-
43
- end
44
-
45
- # HELPER FUNCTIONS AND STRUCTURES
46
-
47
- class Fibonacci_args
48
- include ::Thrift::Struct
49
- N = 1
50
-
51
- Thrift::Struct.field_accessor self, :n
52
- FIELDS = {
53
- N => {:type => Thrift::Types::BYTE, :name => 'n'}
54
- }
55
-
56
- def struct_fields; FIELDS; end
57
-
58
- def validate
59
- end
60
-
61
- end
62
-
63
- class Fibonacci_result
64
- include ::Thrift::Struct
65
- SUCCESS = 0
66
-
67
- Thrift::Struct.field_accessor self, :success
68
- FIELDS = {
69
- SUCCESS => {:type => Thrift::Types::I32, :name => 'success'}
70
- }
71
-
72
- def struct_fields; FIELDS; end
73
-
74
- def validate
75
- end
76
-
77
- end
78
-
79
- end
80
-
81
- end
@@ -1,11 +0,0 @@
1
- #
2
- # Autogenerated by Thrift
3
- #
4
- # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
5
- #
6
-
7
- require 'thrift/protocol'
8
- require File.dirname(__FILE__) + '/Benchmark_types'
9
-
10
- module ThriftBenchmark
11
- end
@@ -1,10 +0,0 @@
1
- #
2
- # Autogenerated by Thrift
3
- #
4
- # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
5
- #
6
-
7
- require 'thrift/protocol'
8
-
9
- module ThriftBenchmark
10
- end
@@ -1,213 +0,0 @@
1
- #
2
- # Copyright (c) 2006- Facebook
3
- # Distributed under the Apache Software License
4
- #
5
- # See accompanying file LICENSE or visit the Thrift site at:
6
- # http://developers.facebook.com/thrift/
7
- #
8
- # Author: Mark Slee <mcslee@facebook.com>
9
- #
10
- require 'thrift/protocol'
11
-
12
- module Thrift
13
- class BinaryProtocol < Protocol
14
- VERSION_MASK = 0xffff0000
15
- VERSION_1 = 0x80010000
16
- TYPE_MASK = 0x000000ff
17
-
18
- attr_reader :strict_read, :strict_write
19
-
20
- def initialize(trans, strict_read=true, strict_write=true)
21
- super(trans)
22
- @strict_read = strict_read
23
- @strict_write = strict_write
24
- end
25
-
26
- def write_message_begin(name, type, seqid)
27
- # this is necessary because we added (needed) bounds checking to
28
- # write_i32, and 0x80010000 is too big for that.
29
- if strict_write
30
- write_i16(VERSION_1 >> 16)
31
- write_i16(type)
32
- write_string(name)
33
- write_i32(seqid)
34
- else
35
- write_string(name)
36
- write_byte(type)
37
- write_i32(seqid)
38
- end
39
- end
40
-
41
- def write_field_begin(name, type, id)
42
- write_byte(type)
43
- write_i16(id)
44
- end
45
-
46
- def write_field_stop
47
- write_byte(Thrift::Types::STOP)
48
- end
49
-
50
- def write_map_begin(ktype, vtype, size)
51
- write_byte(ktype)
52
- write_byte(vtype)
53
- write_i32(size)
54
- end
55
-
56
- def write_list_begin(etype, size)
57
- write_byte(etype)
58
- write_i32(size)
59
- end
60
-
61
- def write_set_begin(etype, size)
62
- write_byte(etype)
63
- write_i32(size)
64
- end
65
-
66
- def write_bool(bool)
67
- write_byte(bool ? 1 : 0)
68
- end
69
-
70
- def write_byte(byte)
71
- trans.write([byte].pack('c'))
72
- end
73
-
74
- def write_i16(i16)
75
- trans.write([i16].pack('n'))
76
- end
77
-
78
- def write_i32(i32)
79
- raise RangeError if i32 < -2**31 || i32 >= 2**31
80
- trans.write([i32].pack('N'))
81
- end
82
-
83
- def write_i64(i64)
84
- hi = i64 >> 32
85
- lo = i64 & 0xffffffff
86
- trans.write([hi, lo].pack('N2'))
87
- end
88
-
89
- def write_double(dub)
90
- trans.write([dub].pack('G'))
91
- end
92
-
93
- def write_string(str)
94
- write_i32(str.length)
95
- trans.write(str)
96
- end
97
-
98
- def read_message_begin
99
- version = read_i32
100
- if version < 0
101
- if (version & VERSION_MASK != VERSION_1)
102
- raise ProtocolException.new(ProtocolException::BAD_VERSION, 'Missing version identifier')
103
- end
104
- type = version & TYPE_MASK
105
- name = read_string
106
- seqid = read_i32
107
- [name, type, seqid]
108
- else
109
- if strict_read
110
- raise ProtocolException.new(ProtocolException::BAD_VERSION, 'No version identifier, old protocol client?')
111
- end
112
- name = trans.read_all(version)
113
- type = read_byte
114
- seqid = read_i32
115
- [name, type, seqid]
116
- end
117
- end
118
-
119
- def read_field_begin
120
- type = read_byte
121
- if (type == Types::STOP)
122
- [nil, type, 0]
123
- else
124
- id = read_i16
125
- [nil, type, id]
126
- end
127
- end
128
-
129
- def read_map_begin
130
- ktype = read_byte
131
- vtype = read_byte
132
- size = read_i32
133
- [ktype, vtype, size]
134
- end
135
-
136
- def read_list_begin
137
- etype = read_byte
138
- size = read_i32
139
- [etype, size]
140
- end
141
-
142
- def read_set_begin
143
- etype = read_byte
144
- size = read_i32
145
- [etype, size]
146
- end
147
-
148
- def read_bool
149
- byte = read_byte
150
- byte != 0
151
- end
152
-
153
- def read_byte
154
- dat = trans.read_all(1)
155
- val = dat[0]
156
- if (val > 0x7f)
157
- val = 0 - ((val - 1) ^ 0xff)
158
- end
159
- val
160
- end
161
-
162
- def read_i16
163
- dat = trans.read_all(2)
164
- val, = dat.unpack('n')
165
- if (val > 0x7fff)
166
- val = 0 - ((val - 1) ^ 0xffff)
167
- end
168
- val
169
- end
170
-
171
- def read_i32
172
- dat = trans.read_all(4)
173
- val, = dat.unpack('N')
174
- if (val > 0x7fffffff)
175
- val = 0 - ((val - 1) ^ 0xffffffff)
176
- end
177
- val
178
- end
179
-
180
- def read_i64
181
- dat = trans.read_all(8)
182
- hi, lo = dat.unpack('N2')
183
- if (hi > 0x7fffffff)
184
- hi ^= 0xffffffff
185
- lo ^= 0xffffffff
186
- 0 - (hi << 32) - lo - 1
187
- else
188
- (hi << 32) + lo
189
- end
190
- end
191
-
192
- def read_double
193
- dat = trans.read_all(8)
194
- val = dat.unpack('G').first
195
- val
196
- end
197
-
198
- def read_string
199
- sz = read_i32
200
- dat = trans.read_all(sz)
201
- dat
202
- end
203
-
204
- end
205
- deprecate_class! :TBinaryProtocol => BinaryProtocol
206
-
207
- class BinaryProtocolFactory < ProtocolFactory
208
- def get_protocol(trans)
209
- return Thrift::BinaryProtocol.new(trans)
210
- end
211
- end
212
- deprecate_class! :TBinaryProtocolFactory => BinaryProtocolFactory
213
- end
@@ -1,19 +0,0 @@
1
- require 'thrift/protocol/binaryprotocol'
2
- require 'thrift_native'
3
-
4
- =begin
5
- The only change required for a transport to support TBinaryProtocolAccelerated is to implement 2 methods:
6
- * borrow(size), which takes an optional argument and returns atleast _size_ bytes from the transport,
7
- or the default buffer size if no argument is given
8
- * consume!(size), which removes size bytes from the front of the buffer
9
-
10
- See TMemoryBuffer and TBufferedTransport for examples.
11
- =end
12
-
13
- module Thrift
14
- class BinaryProtocolAcceleratedFactory < ProtocolFactory
15
- def get_protocol(trans)
16
- BinaryProtocolAccelerated.new(trans)
17
- end
18
- end
19
- end
@@ -1,2 +0,0 @@
1
- require 'thrift/deprecation'
2
- require 'thrift/protocol/binaryprotocol'
@@ -1,2 +0,0 @@
1
- require 'thrift/deprecation'
2
- require 'thrift/protocol'
@@ -1,44 +0,0 @@
1
- require 'thrift/protocol'
2
- require 'thrift/protocol/binaryprotocol'
3
- require 'thrift/transport'
4
-
5
- require 'mongrel'
6
-
7
- ## Sticks a service on a URL, using mongrel to do the HTTP work
8
- module Thrift
9
- class SimpleMongrelHTTPServer
10
- class Handler < Mongrel::HttpHandler
11
- def initialize(processor, protocol_factory)
12
- @processor = processor
13
- @protocol_factory = protocol_factory
14
- end
15
-
16
- def process(request, response)
17
- if request.params["REQUEST_METHOD"] == "POST"
18
- response.start(200) do |head, out|
19
- head["Content-Type"] = "application/x-thrift"
20
- transport = IOStreamTransport.new request.body, out
21
- protocol = @protocol_factory.get_protocol transport
22
- @processor.process protocol, protocol
23
- end
24
- else
25
- response.start(404) { }
26
- end
27
- end
28
- end
29
-
30
- def initialize(processor, opts={})
31
- port = opts[:port] || 80
32
- ip = opts[:ip] || "0.0.0.0"
33
- path = opts[:path] || ""
34
- protocol_factory = opts[:protocol_factory] || BinaryProtocolFactory.new
35
- @server = Mongrel::HttpServer.new ip, port
36
- @server.register "/#{path}", Handler.new(processor, protocol_factory)
37
- end
38
-
39
- def serve
40
- @server.run.join
41
- end
42
- end
43
- deprecate_class! :TSimpleMongrelHTTPServer => SimpleMongrelHTTPServer
44
- end
@@ -1,278 +0,0 @@
1
- require 'thrift/server'
2
- require 'logger'
3
- require 'thread'
4
-
5
- module Thrift
6
- # this class expects to always use a FramedTransport for reading messages
7
- class NonblockingServer < Server
8
- def initialize(processor, serverTransport, transportFactory=nil, protocolFactory=nil, num=20, logger = nil)
9
- super(processor, serverTransport, transportFactory, protocolFactory)
10
- @num_threads = num
11
- if logger.nil?
12
- @logger = Logger.new(STDERR)
13
- @logger.level = Logger::WARN
14
- else
15
- @logger = logger
16
- end
17
- @shutdown_semaphore = Mutex.new
18
- @transport_semaphore = Mutex.new
19
- end
20
-
21
- def serve
22
- @logger.info "Starting #{self}"
23
- @serverTransport.listen
24
- @io_manager = start_io_manager
25
-
26
- begin
27
- loop do
28
- break if @serverTransport.closed?
29
- rd, = select([@serverTransport], nil, nil, 0.1)
30
- next if rd.nil?
31
- socket = @serverTransport.accept
32
- @logger.debug "Accepted socket: #{socket.inspect}"
33
- @io_manager.add_connection socket
34
- end
35
- rescue IOError => e
36
- end
37
- # we must be shutting down
38
- @logger.info "#{self} is shutting down, goodbye"
39
- ensure
40
- @transport_semaphore.synchronize do
41
- @serverTransport.close
42
- end
43
- @io_manager.ensure_closed unless @io_manager.nil?
44
- end
45
-
46
- def shutdown(timeout = 0, block = true)
47
- @shutdown_semaphore.synchronize do
48
- return if @is_shutdown
49
- @is_shutdown = true
50
- end
51
- # nonblocking is intended for calling from within a Handler
52
- # but we can't change the order of operations here, so lets thread
53
- shutdown_proc = lambda do
54
- @io_manager.shutdown(timeout)
55
- @transport_semaphore.synchronize do
56
- @serverTransport.close # this will break the accept loop
57
- end
58
- end
59
- if block
60
- shutdown_proc.call
61
- else
62
- Thread.new &shutdown_proc
63
- end
64
- end
65
-
66
- private
67
-
68
- def start_io_manager
69
- iom = IOManager.new(@processor, @serverTransport, @transportFactory, @protocolFactory, @num_threads, @logger)
70
- iom.spawn
71
- iom
72
- end
73
-
74
- class IOManager # :nodoc:
75
- DEFAULT_BUFFER = 2**20
76
-
77
- def initialize(processor, serverTransport, transportFactory, protocolFactory, num, logger)
78
- @processor = processor
79
- @serverTransport = serverTransport
80
- @transportFactory = transportFactory
81
- @protocolFactory = protocolFactory
82
- @num_threads = num
83
- @logger = logger
84
- @connections = []
85
- @buffers = Hash.new { |h,k| h[k] = '' }
86
- @signal_queue = Queue.new
87
- @signal_pipes = IO.pipe
88
- @signal_pipes[1].sync = true
89
- @worker_queue = Queue.new
90
- @shutdown_queue = Queue.new
91
- end
92
-
93
- def add_connection(socket)
94
- signal [:connection, socket]
95
- end
96
-
97
- def spawn
98
- @iom_thread = Thread.new do
99
- @logger.debug "Starting #{self}"
100
- run
101
- end
102
- end
103
-
104
- def shutdown(timeout = 0)
105
- @logger.debug "#{self} is shutting down workers"
106
- @worker_queue.clear
107
- @num_threads.times { @worker_queue.push [:shutdown] }
108
- signal [:shutdown, timeout]
109
- @shutdown_queue.pop
110
- @signal_pipes[0].close
111
- @signal_pipes[1].close
112
- @logger.debug "#{self} is shutting down, goodbye"
113
- end
114
-
115
- def ensure_closed
116
- kill_worker_threads if @worker_threads
117
- @iom_thread.kill
118
- end
119
-
120
- private
121
-
122
- def run
123
- spin_worker_threads
124
-
125
- loop do
126
- rd, = select([@signal_pipes[0], *@connections])
127
- if rd.delete @signal_pipes[0]
128
- break if read_signals == :shutdown
129
- end
130
- rd.each do |fd|
131
- if fd.handle.eof?
132
- remove_connection fd
133
- else
134
- read_connection fd
135
- end
136
- end
137
- end
138
- join_worker_threads(@shutdown_timeout)
139
- ensure
140
- @shutdown_queue.push :shutdown
141
- end
142
-
143
- def read_connection(fd)
144
- @buffers[fd] << fd.read(DEFAULT_BUFFER)
145
- frame = slice_frame!(@buffers[fd])
146
- if frame
147
- @logger.debug "#{self} is processing a frame"
148
- @worker_queue.push [:frame, fd, frame]
149
- end
150
- end
151
-
152
- def spin_worker_threads
153
- @logger.debug "#{self} is spinning up worker threads"
154
- @worker_threads = []
155
- @num_threads.times do
156
- @worker_threads << spin_thread
157
- end
158
- end
159
-
160
- def spin_thread
161
- Worker.new(@processor, @transportFactory, @protocolFactory, @logger, @worker_queue).spawn
162
- end
163
-
164
- def signal(msg)
165
- @signal_queue << msg
166
- @signal_pipes[1].write " "
167
- end
168
-
169
- def read_signals
170
- # clear the signal pipe
171
- # note that since read_nonblock is broken in jruby,
172
- # we can only read up to a set number of signals at once
173
- sigstr = @signal_pipes[0].readpartial(1024)
174
- # now read the signals
175
- begin
176
- sigstr.length.times do
177
- signal, obj = @signal_queue.pop(true)
178
- case signal
179
- when :connection
180
- @connections << obj
181
- when :shutdown
182
- @shutdown_timeout = obj
183
- return :shutdown
184
- end
185
- end
186
- rescue ThreadError
187
- # out of signals
188
- # note that in a perfect world this would never happen, since we're
189
- # only reading the number of signals pushed on the pipe, but given the lack
190
- # of locks, in theory we could clear the pipe/queue while a new signal is being
191
- # placed on the pipe, at which point our next read_signals would hit this error
192
- end
193
- end
194
-
195
- def remove_connection(fd)
196
- # don't explicitly close it, a thread may still be writing to it
197
- @connections.delete fd
198
- @buffers.delete fd
199
- end
200
-
201
- def join_worker_threads(shutdown_timeout)
202
- start = Time.now
203
- @worker_threads.each do |t|
204
- if shutdown_timeout > 0
205
- timeout = (start + shutdown_timeout) - Time.now
206
- break if timeout <= 0
207
- t.join(timeout)
208
- else
209
- t.join
210
- end
211
- end
212
- kill_worker_threads
213
- end
214
-
215
- def kill_worker_threads
216
- @worker_threads.each do |t|
217
- t.kill if t.status
218
- end
219
- @worker_threads.clear
220
- end
221
-
222
- def slice_frame!(buf)
223
- if buf.length >= 4
224
- size = buf.unpack('N').first
225
- if buf.length >= size + 4
226
- buf.slice!(0, size + 4)
227
- else
228
- nil
229
- end
230
- else
231
- nil
232
- end
233
- end
234
-
235
- class Worker # :nodoc:
236
- def initialize(processor, transportFactory, protocolFactory, logger, queue)
237
- @processor = processor
238
- @transportFactory = transportFactory
239
- @protocolFactory = protocolFactory
240
- @logger = logger
241
- @queue = queue
242
- end
243
-
244
- def spawn
245
- Thread.new do
246
- @logger.debug "#{self} is spawning"
247
- run
248
- end
249
- end
250
-
251
- private
252
-
253
- def run
254
- loop do
255
- cmd, *args = @queue.pop
256
- case cmd
257
- when :shutdown
258
- @logger.debug "#{self} is shutting down, goodbye"
259
- break
260
- when :frame
261
- fd, frame = args
262
- begin
263
- otrans = @transportFactory.get_transport(fd)
264
- oprot = @protocolFactory.get_protocol(otrans)
265
- membuf = MemoryBuffer.new(frame)
266
- itrans = @transportFactory.get_transport(membuf)
267
- iprot = @protocolFactory.get_protocol(itrans)
268
- @processor.process(iprot, oprot)
269
- rescue => e
270
- @logger.error "#{Thread.current.inspect} raised error: #{e.inspect}\n#{e.backtrace.join("\n")}"
271
- end
272
- end
273
- end
274
- end
275
- end
276
- end
277
- end
278
- end