nwrfc-aphid 0.1.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.
data/lib/nwrfc.rb ADDED
@@ -0,0 +1,495 @@
1
+ # Author:: Martin Ceronio martin.ceronio@infosize.co.za
2
+ # Copyright:: Copyright (c) 2012 Martin Ceronio
3
+ # License:: MIT and/or Creative Commons Attribution-ShareAlike
4
+ # SAP, Netweaver, RFC and other names referred to in this code
5
+ # are, or may be registered trademarks and the property of SAP, AG
6
+ # No ownership over any of these is asserted by Martin Ceronio
7
+
8
+ require File.dirname(__FILE__)+'/nwrfc/nwrfclib'
9
+ require File.dirname(__FILE__)+'/nwrfc/datacontainer'
10
+ require File.dirname(__FILE__)+'/nwrfc/server'
11
+ require File.dirname(__FILE__)+'/nwrfc/nwerror'
12
+
13
+ require 'date'
14
+ require 'time'
15
+
16
+ # This library provides a way to call the functions of the SAP Netweaver RFC
17
+ # SDK, i.e. opening a connection to an ABAP system, calling functions etc., as
18
+ # well as running an RFC service
19
+
20
+ module NWRFC
21
+
22
+ # ABAP Time Format ("HHMMSS")
23
+ NW_TIME_FORMAT = "%H%M%S"
24
+ # ABAP Date Format ("YYYYMMDD")
25
+ NW_DATE_FORMAT = "%Y%m%d"
26
+
27
+ def inspect
28
+ self.to_s
29
+ end
30
+
31
+ # Return the version of the NW RFC SDK library
32
+ def NWRFC.get_version
33
+ # See custom method FFI::Pointer#read_string_dn in nwrfclib.rb
34
+ # http://stackoverflow.com/questions/9293307/ruby-ffi-ruby-1-8-reading-utf-16le-encoded-strings
35
+ major = FFI::MemoryPointer.new(:uint)
36
+ minor = FFI::MemoryPointer.new(:uint)
37
+ patch = FFI::MemoryPointer.new(:uint)
38
+ version = NWRFCLib.get_version(major, minor, patch)
39
+ [version.read_string_dn.uC, major.read_uint, minor.read_uint, patch.read_uint]
40
+ end
41
+
42
+ # Take Hash of connection parameters and returns FFI pointer to an array
43
+ # for setting up a connection
44
+ def NWRFC.make_conn_params(params) #https://github.com/ffi/ffi/wiki/Structs
45
+ par = FFI::MemoryPointer.new(NWRFCLib::RFCConnParam, params.length)
46
+ pars = params.length.times.collect do |i|
47
+ NWRFCLib::RFCConnParam.new(par + i * NWRFCLib::RFCConnParam.size)
48
+ end
49
+ tpar = params.to_a
50
+ params.length.times do |n|
51
+ pars[n][:name] = FFI::MemoryPointer.from_string(tpar[n][0].to_s.cU)
52
+ pars[n][:value] = FFI::MemoryPointer.from_string(tpar[n][1].to_s.cU)
53
+ end
54
+ par
55
+ end
56
+
57
+ # Check for an error using error handle (used internally)
58
+ def NWRFC.check_error(error_handle)
59
+ raise NWError, error_handle \
60
+ if error_handle[:code] > 0
61
+ #raise "Error code #{error_handle[:code]} group #{error_handle[:group]} message #{error_handle[:message].get_str}" \
62
+
63
+ end
64
+
65
+ # Represents a client connection to a SAP system that can be used to invoke
66
+ # remote-enabled functions
67
+ class Connection
68
+ attr_reader :handle
69
+
70
+ # Opens a connection to the SAP system with the given connection parameters
71
+ # (described in the NW RFC SDK document), passed in the form of a Hash, e.g.
72
+ # Connection.new { 'ashost' :=> 'ajax.domain.com', ... }
73
+ def initialize(conn_params)
74
+ Rails.logger.info "NWRFC#open_connection" if defined?(Rails)
75
+ raise "Connection parameters must be a Hash" unless conn_params.instance_of? Hash
76
+ @cparams = NWRFC.make_conn_params(conn_params)
77
+ raise "Could not create valid pointer from parameters" unless @cparams.instance_of? FFI::MemoryPointer
78
+ @error = NWRFCLib::RFCError.new
79
+ @handle = NWRFCLib.open_connection(@cparams, conn_params.length, @error.to_ptr)
80
+ NWRFC.check_error(@error)
81
+ self
82
+ end
83
+
84
+ # Close the current connection and clear its handle after successful cleanup.
85
+ def disconnect
86
+ return unless @handle
87
+ rc = NWRFCLib.close_connection(@handle, @error.to_ptr)
88
+ NWRFC.check_error(@error) if rc > 0
89
+ @handle = nil
90
+ end
91
+
92
+ # Get the description of a given function module from the system to which we are connected
93
+ # @return [Function] function module description
94
+ def get_function(function_name)
95
+ Rails.logger.info "NWRFC#get_function(#{function_name})" if defined?(Rails)
96
+ Function.new(self, function_name)
97
+ end
98
+
99
+ # Return details about the current connection and the system
100
+ # @return [Hash] information about the current connection
101
+ def connection_info
102
+ return @get_connection_attributes if @get_connection_attributes
103
+ conn_info = NWRFCLib::RFCConnection.new
104
+ rc = NWRFCLib.get_connection_attributes(@handle, conn_info.to_ptr, @error)
105
+ NWRFC.check_error(@error) if rc > 0
106
+ @get_connection_attributes = conn_info.members.inject({}) {|hash, member|
107
+ hash[member] = conn_info[member].get_str #get_str, own definition in nwrfclib.rb, FFI::StructLayout::CharArray#get_str
108
+ hash
109
+ }
110
+ end
111
+
112
+ alias :close :disconnect
113
+
114
+ def start_transaction(queue_name = nil)
115
+ @tid = FFI::MemoryPointer.new(:char, 50)
116
+ rc = NWRFCLib.get_transaction_id(@handle, @tid, @error)
117
+ NWRFC.check_error(@error) if rc > 0
118
+ queue_name = FFI::MemoryPointer.from_string(queue_name.to_s.cU) if queue_name
119
+ transaction_handle = NWRFCLib.create_transaction(@handle, @tid, queue_name, @error)
120
+ NWRFC.check_error(@error)
121
+ Transaction.new(transaction_handle)
122
+ end
123
+
124
+ end
125
+
126
+ class Transaction
127
+ attr_reader :handle
128
+
129
+ def initialize(handle)
130
+ @handle = handle
131
+ @error = NWRFCLib::RFCError.new
132
+ @submitted = false
133
+ @confirmed = false
134
+ end
135
+
136
+ def commit
137
+ unless @submitted
138
+ rc = NWRFCLib.submit_transaction(@handle, @error)
139
+ NWRFC.check_error(@error) if rc > 0
140
+ @submitted = true
141
+ end
142
+ unless @confirmed
143
+ rc = NWRFCLib.confirm_transaction(@handle, @error)
144
+ NWRFC.check_error(@error) if rc > 0
145
+ @confirmed = true
146
+ end
147
+ close
148
+ end
149
+
150
+ def close
151
+ return unless @handle
152
+ rc = NWRFCLib.destroy_transaction(@handle, @error)
153
+ NWRFC.check_error(@error) if rc > 0
154
+ @handle = nil
155
+ end
156
+
157
+ alias :submit :commit
158
+ alias :destroy :close
159
+
160
+ end
161
+
162
+ # Converts ABAP true/false into Ruby true/false
163
+ # @return True for 'X', False for ' ' or nil otherwise
164
+ def NWRFC.abap_bool(value)
165
+ return true if value == 'X'
166
+ return false if value == ' '
167
+ nil
168
+ end
169
+
170
+ # Converts Ruby true/false into ABAP true/false
171
+ # @return 'X' for true,, ' ' for false or nil otherwise
172
+ def NWRFC.bool_abap(value)
173
+ return 'X' if value == true
174
+ return ' ' if value == false
175
+ nil
176
+ end
177
+
178
+ # Represents the metadata of a function parameter
179
+ class Parameter
180
+
181
+ attr_accessor :handle
182
+
183
+ # Create a parameter by setting parameter attributes
184
+ #@todo For certain types, e.g. :RFCTYPE_BCD, a length specification is
185
+ # required, otherwise a segfault is the result later down the line.
186
+ # Find and implement all the types where this is required
187
+ def initialize(*args)
188
+
189
+ attr = args[0]
190
+
191
+
192
+
193
+ raise "RFCTYPE_BCD requires a length" if attr[:type] == :RFCTYPE_BCD && !(attr[:length])
194
+
195
+ @handle = NWRFCLib::RFCFuncParam.new
196
+ @handle[:name] = attr[:name].cU if attr[:name]
197
+ @handle[:direction] = NWRFCLib::RFC_DIRECTION[attr[:direction]] if attr[:direction]
198
+ @handle[:type] = NWRFCLib::RFC_TYPE[attr[:type]] if attr[:type]
199
+ @handle[:ucLength] = attr[:length] * 2 if attr[:length]
200
+ @handle[:nucLength] = attr[:length] if attr[:length]
201
+ @handle[:decimals] = attr[:decimals] if attr[:decimals]
202
+ # TODO: Add support for type description
203
+ #@handle[:typeDescHandle]
204
+ @handle[:defaultValue] = attr[:defaultValue].cU if attr[:defaultValue]
205
+ @handle[:parameterText] = attr[:parameterText].cU if attr[:parameterText]
206
+ @handle[:optional] = abap_bool(attr[:optional]) if attr[:optional]
207
+ end
208
+ end
209
+
210
+ class Type
211
+
212
+ end
213
+
214
+ # Represents a remote-enabled function module for RFC, can be instantiated either by the caller
215
+ # or by calling Connection#get_function. This only represents the description of the function;
216
+ # to call a function, an instance of a function call must be obtained with #get_function_call
217
+ class Function
218
+ attr_reader :desc, :function_name
219
+ attr_accessor :connection
220
+
221
+ # Get a function module instance; can also be obtained by calling Connection#get_function
222
+ # Takes either: (connection, function_name) or (function_name)
223
+ # When passed only `function_name`, creates a new function description locally, instead of
224
+ # fetching it form the server pointed to by connection
225
+ #@overload new(connection, function_name)
226
+ # Fetches a function definition from the server pointed to by the connection
227
+ # @param [Connection] connection Connection to SAP ABAP system
228
+ # @param [String] function_name Name of the function module on the connected system
229
+ #
230
+ #@overload new(function_name)
231
+ # Returns a new function descriptor. This is ideally used in the case of establishing a
232
+ # server function. In this case, the function cannot be used to make a remote function call.
233
+ # @param [String] function_name Name of the new function module
234
+ def initialize(*args)#(connection, function_name)
235
+ raise("Must initialize function with 1 or 2 arguments") if args.size != 1 && args.size != 2
236
+ @error = NWRFCLib::RFCError.new
237
+ @active_function_calls = 0
238
+ if args.size == 2
239
+ @function_name = args[1] #function_name
240
+ @desc = NWRFCLib.get_function_desc(args[0].handle, args[1].cU, @error.to_ptr)
241
+ NWRFC.check_error(@error)
242
+ @connection = args[0]
243
+ @owns_desc = false
244
+ else
245
+ @function_name = args[0] #function_name
246
+ @desc = NWRFCLib::create_function_desc(args[0].cU, @error)
247
+ NWRFC.check_error(@error)
248
+ @connection = nil
249
+ @owns_desc = true
250
+ end
251
+ end
252
+
253
+ # Add a parameter to a function module. Ideally to be used in the case where a function definition is built
254
+ # up in the client code, rather than fetching it from the server for a remote call
255
+ # @param [Parameter] Definition of a function module parameter
256
+ def add_parameter(parameter)
257
+ rc = NWRFCLib.add_parameter(@desc, parameter.handle, @error)
258
+ NWRFC.check_error(@error) if rc > 0
259
+ end
260
+
261
+ # Create and return a callable instance of this function module
262
+ def get_function_call
263
+ FunctionCall.new(self)
264
+ end
265
+
266
+ # Yield a function call and release its container, including nested data,
267
+ # even when the block raises.
268
+ def with_function_call
269
+ function_call = get_function_call
270
+ begin
271
+ yield function_call
272
+ ensure
273
+ function_call.close
274
+ end
275
+ end
276
+
277
+ def close
278
+ return unless @desc
279
+ if @owns_desc
280
+ raise "Cannot close function description while function calls are active" if @active_function_calls > 0
281
+ rc = NWRFCLib.destroy_function_desc(@desc, @error)
282
+ NWRFC.check_error(@error) if rc > 0
283
+ end
284
+ @desc = nil
285
+ end
286
+
287
+ alias :destroy :close
288
+
289
+ # Get the number of parameters this function has
290
+ def parameter_count
291
+ pcount = FFI::MemoryPointer.new(:uint)
292
+ rc = NWRFCLib.get_parameter_count(@desc, pcount, @error)
293
+ NWRFC.check_error(@error) if rc > 0
294
+ pcount.read_uint
295
+ end
296
+
297
+ # Return the description of parameters associated with this Function
298
+ def parameters
299
+ parameter_count.times.inject({}) do |params, index|
300
+ param = NWRFCLib::RFCFuncParam.new
301
+ NWRFCLib.get_parameter_desc_by_index(@desc, index, param.to_ptr, @error.to_ptr)
302
+ params[param[:name].get_str] = {
303
+ :type => NWRFCLib::RFC_TYPE[param[:type]],
304
+ :direction => NWRFCLib::RFC_DIRECTION[param[:direction]],
305
+ :nucLength => param[:nucLength],
306
+ :ucLength => param[:ucLength],
307
+ :decimals => param[:decimals],
308
+ :typeDescHandle => param[:typeDescHandle],
309
+ :defaultValue => param[:defaultValue].get_str,
310
+ :parameterText => param[:parameterText].get_str,
311
+ :optional => param[:optional]
312
+ }
313
+ params
314
+ end
315
+ end
316
+
317
+ protected
318
+
319
+ def function_call_opened
320
+ @active_function_calls += 1
321
+ end
322
+
323
+ def function_call_closed
324
+ @active_function_calls -= 1
325
+ end
326
+
327
+ end
328
+
329
+ # Represents a callable instance of a function
330
+ class FunctionCall < DataContainer
331
+ attr_reader :handle, :desc, :connection, :function
332
+
333
+ # Call with either Function or Connection and Function Call instance (handle)
334
+ #@overload new(function)
335
+ # Get a function call instance from the function description
336
+ # @param [Function] function Function Description
337
+ #@overload new(function_handle)
338
+ # Used in the case of server functions; instantiate a function call instance from the connection
339
+ # and function description handles received when function is invoked on our side from a remote
340
+ # system; in this case, there is no handle to the connection, and we take advantage only of the
341
+ # data container capabilities
342
+ # @param [FFI::Pointer] function_handle Pointer to the function handle (RFC_FUNCTION_HANDLE)
343
+ def initialize(*args)
344
+ @error = NWRFCLib::RFCError.new
345
+ if args[0].kind_of?(FFI::Pointer)
346
+ @handle = args[0]
347
+ @owns_handle = false
348
+ @connection = nil
349
+ @function = nil
350
+ # @connection = args[0].connection
351
+ @desc = NWRFCLib.describe_function(@handle, @error)
352
+ #@todo Investigate having a referenced Function object as well in the server case; does it have practical applications?
353
+ # Doing this would require an extra way of handling the constructor of Function
354
+ # @function = Function.new
355
+ elsif args[0].kind_of?(Function)
356
+ @function = args[0] #function
357
+ @owns_handle = true
358
+ @connection = args[0].connection
359
+ @handle = NWRFCLib.create_function(@function.desc, @error.to_ptr)
360
+ @desc = args[0].desc
361
+ end
362
+ NWRFC.check_error(@error)
363
+ @function.send(:function_call_opened) if @function
364
+ end
365
+
366
+ def close
367
+ return unless @handle
368
+ if @owns_handle
369
+ rc = NWRFCLib.destroy_function(@handle, @error.to_ptr)
370
+ NWRFC.check_error(@error) if rc > 0
371
+ end
372
+ @handle = nil
373
+ @desc = nil
374
+ @function.send(:function_call_closed) if @function
375
+ @function = nil
376
+ end
377
+
378
+ alias :destroy :close
379
+
380
+ # Execute the function on the connected ABAP system
381
+ #@raise NWRFC::NWError
382
+ def invoke(tx = nil)
383
+ Rails.logger.info "NWRFC#invoke" if defined?(Rails)
384
+ raise "Not a callable function" unless @connection
385
+ if tx
386
+ rc = NWRFCLib.invoke_in_transaction(tx.handle, @handle, @error.to_ptr)
387
+ NWRFC.check_error(@error) if rc > 0
388
+ else
389
+ rc = NWRFCLib.invoke(@connection.handle, @handle, @error.to_ptr)
390
+ #@todo Handle function exceptions by checking for :RFC_ABAP_EXCEPTION (5)
391
+ NWRFC.check_error(@error) if rc > 0
392
+ end
393
+ end
394
+
395
+ # Returns whether or not a given parameter is active, i.e. whether it will be sent to the server during the RFC
396
+ # call with FunctionCall#invoke. This is helpful for functions that set default values on parameters or otherwise
397
+ # check whether parameters are passed in cases where this may have an impact on performance or otherwise
398
+ # @param[String, Symbol] parameter Name of the parameter
399
+ def active?(parameter)
400
+ is_active = FFI::MemoryPointer.new :int
401
+ rc = NWRFCLib.is_parameter_active(@handle, parameter.to_s.cU, is_active, @error)
402
+ NWRFC.check_error(@error) if rc > 0
403
+ is_active.read_int == 1
404
+ end
405
+
406
+ # Set a named parameter to active or inactive
407
+ def set_active(parameter, active=true)
408
+ (active ? active_flag = 1 : active_flag = 0)
409
+ rc = NWRFCLib.set_parameter_active(@handle, parameter.to_s.cU, active_flag, @error)
410
+ NWRFC.check_error(@error) if rc > 0
411
+ active
412
+ end
413
+
414
+ # Set a named parameter to inactive
415
+ def deactivate(parameter)
416
+ set_active(parameter, false)
417
+ end
418
+
419
+ end
420
+
421
+
422
+ class Table < DataContainer
423
+
424
+ include Enumerable
425
+
426
+ # Iterate over the rows in a table. Each row is yielded as a structure
427
+ def each(&block) #:yields row
428
+ return [] if size == 0
429
+ rc = NWRFCLib.move_to_first_row(@handle, @error)
430
+ NWRFC.check_error(@error) if rc > 0
431
+ size.times do |row|
432
+ struct_handle = NWRFCLib.get_current_row(@handle, @error)
433
+ NWRFC.check_error(@error)
434
+ NWRFCLib.move_to_next_row(@handle, @error)
435
+ # CAVEAT: Other calls using the handle require "handle" field
436
+ # of the RFC_DATA_CONTAINER struct
437
+ yield Structure.new(struct_handle)
438
+ end
439
+ end
440
+
441
+ # Return the number of rows in the table
442
+ def size
443
+ rows = FFI::MemoryPointer.new(:uint)
444
+ rc = NWRFCLib.get_row_count(@handle, rows, @error)
445
+ rows.read_uint
446
+ end
447
+
448
+ # Delete all rows from (empty) the table
449
+ def clear
450
+ rc = NWRFCLib.delete_all_rows(@handle, @error)
451
+ NWRFC.check_error(@error) if rc > 0
452
+ end
453
+
454
+ # Retrieve the row at the given index
455
+ def [](index)
456
+ rc = NWRFCLib.move_to(@handle, index, @error)
457
+ NWRFC.check_error(@error) if rc > 0
458
+ struct_handle = NWRFCLib.get_current_row(@handle, @error)
459
+ NWRFC.check_error(@error)
460
+ Structure.new(struct_handle)
461
+ end
462
+
463
+ # Append a row (structure) to the table
464
+ def append(row)
465
+ raise "Must append a structure" unless row.class == NWRFC::Structure
466
+ rc = NWRFCLib.append_row(@handle, row.handle, @error)
467
+ NWRFC.check_error(@error) if rc > 0
468
+ end
469
+
470
+ # Add new (empty) row and return the structure handle
471
+ # or yield it to a passed block
472
+ # @return Structure
473
+ def new_row
474
+ s_handle = NWRFCLib.append_new_row(@handle, @error)
475
+ NWRFC.check_error(@error)
476
+ s = Structure.new(s_handle)
477
+ if block_given?
478
+ yield s
479
+ else
480
+ s
481
+ end
482
+ end
483
+
484
+ end #class Table
485
+
486
+ # Represents a structure. An instance is obtained internally by passing the
487
+ # handle of a structure. A user can obtain an instance by invoking sub-field
488
+ # access of a structure or a function
489
+ class Structure < DataContainer
490
+
491
+
492
+
493
+ end # class Structure
494
+
495
+ end #module NWRFC
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nwrfc-aphid
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Martin Ceronio
8
+ - Charles Dale
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 1980-01-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bigdecimal
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ffi
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.17.3
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 1.17.3
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: test-unit
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: A maintained fork of nwrfc, an SAP NetWeaver RFC Library wrapper using
70
+ Ruby-FFI
71
+ email:
72
+ - mydoghasworms@gmail.com
73
+ - rubygems@aphid.net
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files:
77
+ - README.rdoc
78
+ files:
79
+ - README.rdoc
80
+ - Rakefile
81
+ - lib/nwrfc.rb
82
+ - lib/nwrfc/datacontainer.rb
83
+ - lib/nwrfc/nwerror.rb
84
+ - lib/nwrfc/nwrfclib.rb
85
+ - lib/nwrfc/server.rb
86
+ homepage: https://github.com/chuckd/nwrfc
87
+ licenses:
88
+ - MIT
89
+ metadata:
90
+ source_code_uri: https://github.com/chuckd/nwrfc
91
+ rubygems_mfa_required: 'true'
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '2.5'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubygems_version: 4.0.16
107
+ specification_version: 4
108
+ summary: Maintained SAP NetWeaver RFC Library wrapper
109
+ test_files: []