webmoney 0.0.4.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.
- data/ChangeLog +35 -0
- data/ext/wmsigner/base64.cpp +144 -0
- data/ext/wmsigner/base64.h +48 -0
- data/ext/wmsigner/cmdbase.cpp +417 -0
- data/ext/wmsigner/cmdbase.h +111 -0
- data/ext/wmsigner/crypto.cpp +155 -0
- data/ext/wmsigner/crypto.h +17 -0
- data/ext/wmsigner/extconf.rb +16 -0
- data/ext/wmsigner/md4.cpp +192 -0
- data/ext/wmsigner/md4.h +41 -0
- data/ext/wmsigner/rsalib1.cpp +838 -0
- data/ext/wmsigner/rsalib1.h +81 -0
- data/ext/wmsigner/signer.cpp +315 -0
- data/ext/wmsigner/signer.h +63 -0
- data/ext/wmsigner/stdafx.cpp +8 -0
- data/ext/wmsigner/stdafx.h +33 -0
- data/ext/wmsigner/wmsigner.cpp +119 -0
- data/lib/WebMoneyCA.crt +90 -0
- data/lib/messenger.rb +37 -0
- data/lib/passport.rb +44 -0
- data/lib/webmoney.rb +237 -0
- data/lib/wmid.rb +14 -0
- data/rakefile +63 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/unit/messenger_spec.rb +23 -0
- data/spec/unit/passport_spec.rb +47 -0
- data/spec/unit/signer_spec.rb +35 -0
- data/spec/unit/time_spec.rb +14 -0
- data/spec/unit/webmoney_spec.rb +111 -0
- data/spec/unit/wmid_spec.rb +30 -0
- data/tools/rakehelp.rb +117 -0
- metadata +102 -0
data/lib/wmid.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# Support class
|
2
|
+
class Webmoney
|
3
|
+
class Wmid < String
|
4
|
+
|
5
|
+
def initialize(str)
|
6
|
+
str = str.to_s unless str.kind_of?(String)
|
7
|
+
raise IncorrectWmidError, ': ' + str.to_s unless str =~ /^\d{12}$/
|
8
|
+
super(str)
|
9
|
+
end
|
10
|
+
|
11
|
+
def wmid; self end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
data/rakefile
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'tools/rakehelp'
|
6
|
+
require 'spec/rake/spectask'
|
7
|
+
|
8
|
+
GEM_VERSION="0.0.4.2"
|
9
|
+
|
10
|
+
setup_extension('wmsigner', 'wmsigner')
|
11
|
+
|
12
|
+
desc "Compiles native extensions"
|
13
|
+
task :compile => [:wmsigner]
|
14
|
+
|
15
|
+
task :default => [:compile, :spec]
|
16
|
+
|
17
|
+
Spec::Rake::SpecTask.new do |task|
|
18
|
+
task.libs << 'spec'
|
19
|
+
task.spec_files = Dir.glob( 'spec/**/*_spec.rb' )
|
20
|
+
task.verbose = true
|
21
|
+
end
|
22
|
+
|
23
|
+
gemspec = Gem::Specification.new do |gemspec|
|
24
|
+
gemspec.name = "webmoney"
|
25
|
+
gemspec.version = GEM_VERSION
|
26
|
+
gemspec.author = "Alexander Oryol"
|
27
|
+
gemspec.email = "eagle.alex@gmail.com"
|
28
|
+
gemspec.summary = "Webmoney interfaces and native wmsigner"
|
29
|
+
gemspec.files = %w( rakefile ChangeLog lib/WebMoneyCA.crt ) +
|
30
|
+
Dir.glob( 'lib/*.rb' ) +
|
31
|
+
Dir.glob( 'spec/*.rb' ) +
|
32
|
+
Dir.glob( 'spec/unit/*.rb' ) +
|
33
|
+
Dir.glob( 'ext/**/*.{h,cpp,rb}' ) +
|
34
|
+
Dir.glob( 'tools/*.rb' )
|
35
|
+
gemspec.autorequire = 'webmoney'
|
36
|
+
gemspec.require_path = 'lib'
|
37
|
+
gemspec.add_dependency('hpricot')
|
38
|
+
gemspec.add_dependency('builder')
|
39
|
+
|
40
|
+
if RUBY_PLATFORM.match("win32")
|
41
|
+
gemspec.platform = Gem::Platform::WIN32
|
42
|
+
gemspec.files += []
|
43
|
+
else
|
44
|
+
gemspec.platform = Gem::Platform::RUBY
|
45
|
+
gemspec.extensions = Dir.glob( 'ext/**/extconf.rb' )
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
task :package => [:clean, :compile, :spec]
|
50
|
+
Rake::GemPackageTask.new( gemspec ) do |task|
|
51
|
+
task.gem_spec = gemspec
|
52
|
+
task.need_tar = true
|
53
|
+
end
|
54
|
+
|
55
|
+
setup_clean ["ext/wmsigner/*.{so,o}", "ext/wmsigner/Makefile", "lib/wmsigner.so", "pkg", "*.gem"]
|
56
|
+
|
57
|
+
task :install => [:default, :package] do
|
58
|
+
sh %{ sudo gem install pkg/webmoney-#{GEM_VERSION}.gem }
|
59
|
+
end
|
60
|
+
|
61
|
+
task :uninstall do
|
62
|
+
sh %{ sudo gem uninstall webmoney }
|
63
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'spec'
|
4
|
+
require 'hpricot'
|
5
|
+
require 'ostruct'
|
6
|
+
require 'yaml'
|
7
|
+
require 'time'
|
8
|
+
require File.dirname(__FILE__) + '/../lib/webmoney'
|
9
|
+
|
10
|
+
# Variables may be access, for example WmConfig.wmid
|
11
|
+
config = OpenStruct.new(YAML.load_file("#{ENV['HOME']}/.wm/config.yml"))
|
12
|
+
if ENV['WM_ENV']
|
13
|
+
env_config = config.send(ENV['WM_ENV'])
|
14
|
+
config.common.update(env_config) unless env_config.nil?
|
15
|
+
end
|
16
|
+
::WmConfig = OpenStruct.new(config.common)
|
17
|
+
|
18
|
+
def webmoney
|
19
|
+
Webmoney.new :wmid => WmConfig.wmid, :password => WmConfig.password, :key => WmConfig.key
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
class Webmoney
|
4
|
+
|
5
|
+
describe Messenger, "class" do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@wm = webmoney()
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should create instance and send messages" do
|
12
|
+
@wm.messenger.should be_nil
|
13
|
+
@wm.send_message(:wmid => @wm.wmid, :subj => 'FIRST', :text => 'BODY')
|
14
|
+
@wm.messenger.should be_instance_of(Messenger)
|
15
|
+
@wm.send_message(:wmid => @wm.wmid, :subj => 'SECOND', :text => 'SECOUND')
|
16
|
+
sleep(3)
|
17
|
+
end
|
18
|
+
|
19
|
+
# TODO HOW TEST IT FUNCTIONALITY???
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
class Webmoney
|
4
|
+
|
5
|
+
describe Passport, "class" do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@wm = webmoney()
|
9
|
+
end
|
10
|
+
|
11
|
+
def make_request(wmid=nil)
|
12
|
+
test_wmid = wmid || @wm.wmid
|
13
|
+
x = Builder::XmlMarkup.new
|
14
|
+
x.request do
|
15
|
+
x.wmid @wm.wmid
|
16
|
+
x.passportwmid test_wmid
|
17
|
+
x.sign @wm.send(:sign, @wm.wmid + test_wmid)
|
18
|
+
x.params { x.dict 0; x.info 1; x.mode 0 }
|
19
|
+
end
|
20
|
+
x.target!
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should return empty on incorrect xml" do
|
24
|
+
Passport.new('<response/>').should == ''
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should return Passport instance" do
|
28
|
+
Passport.new(@wm.send(:https_request, :get_passport, make_request)).should be_instance_of(Passport)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should return correct data" do
|
32
|
+
wmid = '000000000007'
|
33
|
+
p = Passport.new(@wm.send(:https_request, :get_passport, make_request(wmid)))
|
34
|
+
p.wmid.should == wmid
|
35
|
+
p.attestat.should == Webmoney::Passport::REGISTRATOR
|
36
|
+
p.created_at.strftime('%Y-%m-%d %H:%M:%S').should == '2004-02-25 21:54:01'
|
37
|
+
|
38
|
+
wmid = '370860915669'
|
39
|
+
p = Passport.new(@wm.send(:https_request, :get_passport, make_request(wmid)))
|
40
|
+
p.wmid.should == wmid
|
41
|
+
p.attestat.should == Webmoney::Passport::ALIAS
|
42
|
+
p.created_at.strftime('%Y-%m-%d %H:%M:%S').should == '2006-04-19 10:16:30'
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe Signer, "class" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@s = Signer.new( WmConfig.wmid, WmConfig.password, WmConfig.key)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should be Signer class" do
|
10
|
+
@s.should be_kind_of(Signer)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should signing string" do
|
14
|
+
@s.sign('Test123').should_not be_nil
|
15
|
+
@s.sign('Test123').should match(/[0-9a-f]{132}/)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should signing empty string" do
|
19
|
+
@s.sign('').should match(/[0-9a-f]{132}/)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should raise error on nil string" do
|
23
|
+
lambda{@s.sign(nil)}.should raise_error(ArgumentError)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should raise error on nil pass" do
|
27
|
+
lambda{Signer.new('405424574082', nil, '')}.should raise_error(ArgumentError)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should raise error on blank key" do
|
31
|
+
lambda{Signer.new('405424574082', 'test', nil)}.should raise_error(ArgumentError)
|
32
|
+
lambda{Signer.new('405424574082', 'test', '')}.should raise_error(ArgumentError)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe Time, "class" do
|
4
|
+
it "should test time from_ms" do
|
5
|
+
time = Time.xmlschema("2006-10-10T17:00:45.383")
|
6
|
+
time.strftime('%Y-%m-%d %H:%M:%S').should == '2006-10-10 17:00:45'
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should test time from_ms without ending zeros" do
|
10
|
+
time = Time.xmlschema("2006-10-10T17:00:45")
|
11
|
+
time.strftime('%Y-%m-%d %H:%M:%S').should == '2006-10-10 17:00:45'
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
class Webmoney
|
4
|
+
|
5
|
+
describe Webmoney, "class" do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@wm = webmoney()
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should be instance of Webmoney" do
|
12
|
+
@wm.should be_instance_of(Webmoney)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should be classic" do
|
16
|
+
@wm.classic?.should be_true
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return reqn" do
|
20
|
+
t1 = @wm.send(:reqn)
|
21
|
+
sleep(0.1)
|
22
|
+
t2 = @wm.send(:reqn)
|
23
|
+
t1.should match(/^\d{16}$/)
|
24
|
+
(t2 > t1).should be_true
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should raise error on incorrect arg" do
|
28
|
+
lambda { @wm.send(:request, :check_sign, 1) }.
|
29
|
+
should raise_error(ArgumentError)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should send request" do
|
33
|
+
r = @wm.send(:https_request, :check_sign, '<w3s.request/>')
|
34
|
+
doc = Hpricot.XML(r.gsub(/w3s\.response/,'w3s_response'))
|
35
|
+
doc.at('w3s_response').should_not be_nil
|
36
|
+
end
|
37
|
+
|
38
|
+
it"should raise error on bad response" do
|
39
|
+
lambda { @wm.send(:https_request,
|
40
|
+
'https://w3s.wmtransfer.com/asp/XMLUnexistantIface.asp', '<w3s.request/>')}.
|
41
|
+
should raise_error(RequestError)
|
42
|
+
@wm.error.should == '404'
|
43
|
+
@wm.errormsg.should match(/^<!DOCTYPE HTML PUBLIC/)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should parse retval and raise error" do
|
47
|
+
lambda { @wm.request(:create_transaction)}.should raise_error(ResultError)
|
48
|
+
@wm.error.should == -3
|
49
|
+
@wm.errormsg.should match(%r{incorrect value of w3s.request/trans/tranid is incorrect})
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should sign string" do
|
53
|
+
@wm.send(:sign, 'Test123').should match(/^[0-9a-f]{132}$/)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should return nil on sign empty string" do
|
57
|
+
@wm.send(:sign, '').should be_nil
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should check_sign" do
|
61
|
+
plan = 'test123'
|
62
|
+
@wm.request(:check_sign,
|
63
|
+
:wmid => @wm.wmid, :plan => plan, :sign => @wm.send(:sign, plan)).
|
64
|
+
should be_true
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should check_sign broken" do
|
68
|
+
plan = 'test123'
|
69
|
+
@wm.request(:check_sign,
|
70
|
+
:wmid => @wm.wmid, :plan => plan, :sign => 'abcd').
|
71
|
+
should be_false
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should check_sign with specials" do
|
75
|
+
plan = '<test>текст</test>'
|
76
|
+
real_plan = Iconv.conv('CP1251', 'UTF-8', plan)
|
77
|
+
@wm.request(:check_sign,
|
78
|
+
:wmid => @wm.wmid, :plan => plan, :sign => @wm.send(:sign, real_plan )).
|
79
|
+
should be_true
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should parse retval and raise error on broken get_passport" do
|
83
|
+
lambda { @wm.request(:get_passport, :wmid => '') }.should raise_error(ResultError)
|
84
|
+
@wm.error.should == 2
|
85
|
+
@wm.errormsg.should match(%r{неверно указан проверяемый WMID})
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should get_passport" do
|
89
|
+
@wm.request(:get_passport, :wmid => @wm.wmid).should be_instance_of(Passport)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should return correct BL" do
|
93
|
+
wmid = '370860915669'
|
94
|
+
@wm.request(:bussines_level, :wmid => wmid).should == 0
|
95
|
+
|
96
|
+
wmid = Wmid.new '000000000007'
|
97
|
+
bl = @wm.request(:bussines_level, :wmid => wmid)
|
98
|
+
(bl > 1000).should be_true
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should send message" do
|
102
|
+
result = @wm.request(:send_message,
|
103
|
+
:wmid => @wm.wmid, :subj => 'Текст', :text => 'Тело <b>сообщения</b>')
|
104
|
+
result.should be_kind_of(Hash)
|
105
|
+
result[:id].should match(/^\d*$/)
|
106
|
+
((result[:date] + 60) > Time.now).should be_true
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
class Webmoney
|
4
|
+
|
5
|
+
describe Wmid, "class" do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@t = Wmid.new('123456789012')
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should be kind of Wmid" do
|
12
|
+
@t.should be_kind_of(Wmid)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should be string" do
|
16
|
+
@t.should == '123456789012'
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should permit initialize by integer" do
|
20
|
+
Wmid.new(123456789012).should == '123456789012'
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should raise error on incorrect wmid" do
|
24
|
+
lambda{Wmid.new('abc')}.
|
25
|
+
should raise_error(IncorrectWmidError)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
data/tools/rakehelp.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
def make(makedir)
|
2
|
+
Dir.chdir(makedir) do
|
3
|
+
sh(PLATFORM =~ /win32/ ? 'nmake' : 'make')
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
|
8
|
+
def extconf(dir)
|
9
|
+
Dir.chdir(dir) do ruby "extconf.rb" end
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def setup_tests
|
14
|
+
Rake::TestTask.new do |t|
|
15
|
+
t.libs << "test"
|
16
|
+
t.test_files = FileList['test/test*.rb']
|
17
|
+
t.verbose = true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def setup_clean otherfiles
|
23
|
+
files = ['build/*', '**/*.o', '**/*.so', '**/*.a', 'lib/*-*', '**/*.log'] + otherfiles
|
24
|
+
CLEAN.include(files)
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def setup_rdoc files
|
29
|
+
Rake::RDocTask.new do |rdoc|
|
30
|
+
rdoc.rdoc_dir = 'doc/rdoc'
|
31
|
+
rdoc.options << '--line-numbers'
|
32
|
+
rdoc.rdoc_files.add(files)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def setup_extension(dir, extension)
|
38
|
+
ext = "ext/#{dir}"
|
39
|
+
ext_so = "#{ext}/#{extension}.#{Config::CONFIG['DLEXT']}"
|
40
|
+
ext_files = FileList[
|
41
|
+
"#{ext}/*.cpp",
|
42
|
+
"#{ext}/*.h",
|
43
|
+
"#{ext}/extconf.rb",
|
44
|
+
"#{ext}/Makefile",
|
45
|
+
"lib"
|
46
|
+
]
|
47
|
+
|
48
|
+
task "lib" do
|
49
|
+
directory "lib"
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "Builds just the #{extension} extension"
|
53
|
+
task extension.to_sym => ["#{ext}/Makefile", ext_so ]
|
54
|
+
|
55
|
+
file "#{ext}/Makefile" => ["#{ext}/extconf.rb"] do
|
56
|
+
extconf "#{ext}"
|
57
|
+
end
|
58
|
+
|
59
|
+
file ext_so => ext_files do
|
60
|
+
make "#{ext}"
|
61
|
+
cp ext_so, "lib"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def base_gem_spec(pkg_name, pkg_version)
|
67
|
+
rm_rf "test/coverage"
|
68
|
+
pkg_version = pkg_version
|
69
|
+
pkg_name = pkg_name
|
70
|
+
pkg_file_name = "#{pkg_name}-#{pkg_version}"
|
71
|
+
Gem::Specification.new do |s|
|
72
|
+
s.name = pkg_name
|
73
|
+
s.version = pkg_version
|
74
|
+
s.platform = Gem::Platform::RUBY
|
75
|
+
s.has_rdoc = true
|
76
|
+
s.extra_rdoc_files = [ "README" ]
|
77
|
+
|
78
|
+
s.files = %w(COPYING LICENSE README Rakefile) +
|
79
|
+
Dir.glob("{bin,doc/rdoc,test}/**/*") +
|
80
|
+
Dir.glob("ext/**/*.{h,cpp,rb,rl}") +
|
81
|
+
Dir.glob("{examples,tools,lib}/**/*.rb")
|
82
|
+
|
83
|
+
s.require_path = "lib"
|
84
|
+
s.extensions = FileList["ext/**/extconf.rb"].to_a
|
85
|
+
s.bindir = "bin"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def setup_gem(pkg_name, pkg_version)
|
90
|
+
spec = base_gem_spec(pkg_name, pkg_version)
|
91
|
+
yield spec if block_given?
|
92
|
+
|
93
|
+
Rake::GemPackageTask.new(spec) do |p|
|
94
|
+
p.gem_spec = spec
|
95
|
+
p.need_tar = true if RUBY_PLATFORM !~ /mswin/
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def sub_project(project, *targets)
|
100
|
+
targets.each do |target|
|
101
|
+
Dir.chdir "projects/#{project}" do
|
102
|
+
sh %{rake --trace #{target.to_s} }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Conditional require rcov/rcovtask if present
|
108
|
+
begin
|
109
|
+
require 'rcov/rcovtask'
|
110
|
+
|
111
|
+
Rcov::RcovTask.new do |t|
|
112
|
+
t.test_files = FileList['test/test*.rb']
|
113
|
+
t.rcov_opts << "-x /usr"
|
114
|
+
t.output_dir = "test/coverage"
|
115
|
+
end
|
116
|
+
rescue Object
|
117
|
+
end
|