ethon 0.8.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7a8e3e2daabb0f28d13cfec209027f4a3dfb588f
4
- data.tar.gz: b5dc230d462c0972fa15bb811906e70ca4b6d8a7
3
+ metadata.gz: 6fabe1040444c048c297b35b8785962b405cf22b
4
+ data.tar.gz: 024c7e7ed7c3ef61dbbae1375b25626e1e5bbdf4
5
5
  SHA512:
6
- metadata.gz: ba31326c9c9dd60a8635a32edde9b4936307e602097b2ca696333410aea60746fbda37b7c2d179ac2be01a46ccee66debfdd014deb073f87ffeb1fdeae00540b
7
- data.tar.gz: 9f2b01584a1f175f9032febbd0fbc1b80a56dcda094a2825746e6e7ccca8254f07fc3ae7bf5c5249e7b75f52914a9229201dfb1a0e26fb17af457a6cfae5efe9
6
+ metadata.gz: 9e5e32e36f44d8f909d5d90e5a337959ced86643e16792e346fac504e6c53e34b548fae11a5f8334ac36b9594cbdeacc6c9fa2014a6ed73ed3e57a89a19e2801
7
+ data.tar.gz: f350cc872574938c24a4080dde53bd6fc97dbc78b30399bac1a9b5e68702f64920bc722ed53fa77dca41a4a3298b53955e151e1d19826be0d7a9a75c1733a316
@@ -1,19 +1,26 @@
1
- script: "bundle exec rake"
1
+ language: ruby
2
+ cache: bundler
2
3
  sudo: false
4
+
3
5
  bundler_args: --without perf
6
+ script: bundle exec rake
7
+
4
8
  rvm:
5
9
  - 1.8.7
6
10
  - 1.9.2
7
11
  - 1.9.3
8
- - 2.0.0
12
+ - 2.0.0-p648
13
+ - 2.1.8
14
+ - 2.2.4
15
+ - 2.3.0
9
16
  - ruby-head
10
17
  - ree
11
- - jruby-head
12
18
  - jruby-18mode
13
19
  - jruby-19mode
20
+ - jruby-head
21
+ - rbx-2
22
+
14
23
  matrix:
15
24
  allow_failures:
25
+ - rvm: ree
16
26
  - rvm: ruby-head
17
- - rvm: jruby-head
18
- - rvm: rbx-18mode
19
- - rvm: rbx-19mode
@@ -2,10 +2,16 @@
2
2
 
3
3
  ## Master
4
4
 
5
- [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.8.1...master)
5
+ [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.9.0...master)
6
+
7
+ ## 0.9.0
8
+
9
+ [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.8.1...v0.9.0)
6
10
 
7
11
  ## 0.8.1
8
12
 
13
+ [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.8.0...v0.8.1)
14
+
9
15
  * Support optional escaping of params.
10
16
  ([Tasos Laskos](https://github.com/zapotek)
11
17
  * `Easy::Mirror`: Reduced object allocations and method calls during info handling.
@@ -13,6 +19,11 @@
13
19
 
14
20
  ## 0.8.0
15
21
 
22
+ [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.7.3...v0.7.4)
23
+
24
+ * `Easy::Mirror`: Reduced object allocations and method calls during info handling.
25
+ ([Tasos Laskos](https://github.com/zapotek)
26
+
16
27
  ## 0.7.4
17
28
 
18
29
  [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.7.3...v0.7.4)
data/Gemfile CHANGED
@@ -1,22 +1,27 @@
1
1
  source "https://rubygems.org"
2
2
  gemspec
3
3
 
4
- gem "rake"
4
+ if Gem.ruby_version < Gem::Version.new("1.9.3")
5
+ gem "rake", "< 11"
6
+ else
7
+ gem "rake"
8
+ end
5
9
 
6
10
  group :development, :test do
7
- gem "rspec", "~> 2.11"
11
+ gem "rspec", "~> 3.4"
8
12
 
9
- gem "sinatra", :git => "https://github.com/sinatra/sinatra.git"
13
+ gem "sinatra"
10
14
  gem "json"
11
15
  gem "mime-types", "~> 1.18"
12
16
 
13
17
  unless ENV["CI"]
14
18
  gem "guard-rspec", "~> 0.7"
15
- gem 'rb-fsevent', '~> 0.9.1'
19
+ gem "rb-fsevent", "~> 0.9.1"
16
20
  end
17
21
  end
18
22
 
19
23
  group :perf do
20
- gem "patron", "~> 0.4"
21
- gem "curb", "~> 0.8.0"
24
+ gem "benchmark-ips"
25
+ gem "patron"
26
+ gem "curb"
22
27
  end
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012-2014 Hans Hasselberg
1
+ Copyright (c) 2012-2016 Hans Hasselberg
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -71,7 +71,7 @@ everything up correctly.
71
71
 
72
72
  (The MIT License)
73
73
 
74
- Copyright © 2012-2014 [Hans Hasselberg](http://www.hans.io)
74
+ Copyright © 2012-2016 [Hans Hasselberg](http://www.hans.io)
75
75
 
76
76
  Permission is hereby granted, free of charge, to any person obtaining a
77
77
  copy of this software and associated documentation files (the "Software"),
@@ -25,6 +25,7 @@ module Ethon
25
25
  base.attach_function :easy_strerror, :curl_easy_strerror, [:easy_code], :string
26
26
  base.attach_function :easy_escape, :curl_easy_escape, [:pointer, :pointer, :int], :pointer
27
27
  base.attach_function :easy_reset, :curl_easy_reset, [:pointer], :void
28
+ base.attach_function :easy_duphandle, :curl_easy_duphandle, [:pointer], :pointer
28
29
 
29
30
  base.attach_function :formadd, :curl_formadd, [:pointer, :pointer, :varargs], :int
30
31
  base.attach_function :formfree, :curl_formfree, [:pointer], :void
@@ -5,10 +5,15 @@ module Ethon
5
5
  # easy or multi interface.
6
6
  module Options
7
7
 
8
+ OPTION_STRINGS = { :easy => 'easy_options', :multi => 'multi_options' }.freeze
9
+ FOPTION_STRINGS = { :easy => 'EASY_OPTIONS', :multi => 'MULTI_OPTIONS' }.freeze
10
+ FTYPES = [:long, :string, :ffipointer, :callback, :debug_callback, :off_t]
11
+ FUNCS = Hash[*[:easy, :multi].zip([:easy, :multi].map { |t| Hash[*FTYPES.zip(FTYPES.map { |ft| "#{t}_setopt_#{ft}" }).flatten] }).flatten]
8
12
  # Sets appropriate option for easy, depending on value type.
9
13
  def set_option(option, value, handle, type = :easy)
10
- raise NameError, "Ethon::Curls::Options unknown type #{type}." unless respond_to?("#{type.to_s.downcase}_options")
11
- opthash=send("#{type.to_s.downcase}_options")
14
+ type = type.to_sym unless type.is_a?(Symbol)
15
+ raise NameError, "Ethon::Curls::Options unknown type #{type}." unless respond_to?(OPTION_STRINGS[type])
16
+ opthash=send(OPTION_STRINGS[type], nil)
12
17
  raise Errors::InvalidOption.new(option) unless opthash.include?(option)
13
18
 
14
19
  case opthash[option][:type]
@@ -90,8 +95,7 @@ module Ethon
90
95
  tv=((value<0) ? value.abs-1 : value)
91
96
  raise Errors::InvalidValue.new(option,value) unless tv<(1<<bits)
92
97
  end
93
-
94
- send("#{type}_setopt_#{func}", handle, opthash[option][:opt], value)
98
+ send(FUNCS[type][func], handle, opthash[option][:opt], value)
95
99
  end
96
100
 
97
101
  OPTION_TYPE_BASE = {
@@ -174,25 +178,24 @@ module Ethon
174
178
  else
175
179
  raise ArgumentError, "Ethon::Curls::Options #{ftype} #{name} Expected no opts." unless opts.nil?
176
180
  end
177
-
178
- opthash=const_get("#{ftype.to_s.upcase}_OPTIONS")
179
- opthash[name]={:type=>type, :opt=>OPTION_TYPE_BASE[OPTION_TYPE_MAP[type]]+num, :opts=>opts}
181
+ opthash=const_get(FOPTION_STRINGS[ftype])
182
+ opthash[name] = { :type => type,
183
+ :opt => OPTION_TYPE_BASE[OPTION_TYPE_MAP[type]] + num,
184
+ :opts => opts }
180
185
  end
181
186
 
182
187
  def self.option_alias(ftype,name,*aliases)
183
- opthash=const_get("#{ftype.to_s.upcase}_OPTIONS")
188
+ opthash=const_get(FOPTION_STRINGS[ftype])
184
189
  aliases.each { |a| opthash[a]=opthash[name] }
185
190
  end
186
191
 
187
192
  def self.option_type(type)
188
- cname="#{type.to_s.upcase}_OPTIONS"
189
- c=const_set(cname,{})
190
- eval %Q<
191
- def #{type.to_s.downcase}_options(rt=nil)
192
- return #{cname}.map { |k,v| [k,v[:opt]] } if rt==:enum
193
- #{cname}
194
- end
195
- >
193
+ cname = FOPTION_STRINGS[type]
194
+ const_set(cname, {})
195
+ define_method(OPTION_STRINGS[type]) do |rt|
196
+ return Ethon::Curls::Options.const_get(cname).map { |k, v| [k, v[:opt]] } if rt == :enum
197
+ Ethon::Curls::Options.const_get(cname)
198
+ end
196
199
  end
197
200
 
198
201
  # Curl multi options, refer
@@ -415,7 +418,7 @@ module Ethon
415
418
  option_alias :easy, :keypasswd, :sslkeypasswd
416
419
  option :easy, :sslengine, :string, 89
417
420
  option :easy, :sslengine_default, :none, 90
418
- option :easy, :sslversion, :enum, 32, [:default, :tlsv1, :sslv2, :sslv3]
421
+ option :easy, :sslversion, :enum, 32, [:default, :tlsv1, :sslv2, :sslv3, :tlsv1_0, :tlsv1_1, :tlsv1_2]
419
422
  option :easy, :ssl_verifypeer, :bool, 64
420
423
  option :easy, :cainfo, :string, 65
421
424
  option :easy, :issuercert, :string, 170
@@ -259,6 +259,17 @@ module Ethon
259
259
  set_callbacks
260
260
  end
261
261
 
262
+ # Clones libcurl session handle. This means that all options that is set in
263
+ # the current handle will be set on duplicated handle.
264
+ def dup
265
+ e = super
266
+ e.handle = Curl.easy_duphandle(handle)
267
+ e.instance_variable_set(:@body_write_callback, nil)
268
+ e.instance_variable_set(:@header_write_callback, nil)
269
+ e.instance_variable_set(:@debug_callback, nil)
270
+ e.set_callbacks
271
+ e
272
+ end
262
273
  # Url escapes the value.
263
274
  #
264
275
  # @example Url escape.
@@ -13,6 +13,12 @@ module Ethon
13
13
  @handle ||= FFI::AutoPointer.new(Curl.easy_init, Curl.method(:easy_cleanup))
14
14
  end
15
15
 
16
+ # Sets a pointer to the curl easy handle.
17
+ # @param [ ::FFI::Pointer ] Easy handle that will be assigned.
18
+ def handle=(h)
19
+ @handle = h
20
+ end
21
+
16
22
  # Perform the easy request.
17
23
  #
18
24
  # @example Perform the request.
@@ -21,7 +27,9 @@ module Ethon
21
27
  # @return [ Integer ] The return code.
22
28
  def perform
23
29
  @return_code = Curl.easy_perform(handle)
24
- Ethon.logger.debug { "ETHON: performed #{self.log_inspect}" }
30
+ if Ethon.logger.level.zero?
31
+ Ethon.logger.debug { "ETHON: performed #{log_inspect}" }
32
+ end
25
33
  complete
26
34
  @return_code
27
35
  end
@@ -20,22 +20,20 @@ module Ethon
20
20
  @escape.nil? ? true : false
21
21
  end
22
22
 
23
- Curl.easy_options.each do |opt,props|
24
- eval %Q<
25
- def #{opt}=(value)
26
- Curl.set_option(:#{opt}, value, handle)
23
+ Curl.easy_options(nil).each do |opt, props|
24
+ method_name = "#{opt}=".freeze
25
+ unless method_defined? method_name
26
+ define_method(method_name) do |value|
27
+ Curl.set_option(opt, value, handle)
27
28
  value
28
29
  end
29
- > unless method_defined? opt.to_s+"="
30
- if props[:type]==:callback then
31
- eval %Q<
32
- def #{opt}(&block)
33
- @procs ||= {}
34
- @procs[:#{opt}]=block
35
- Curl.set_option(:#{opt}, block, handle)
36
- nil
37
- end
38
- > unless method_defined? opt.to_s
30
+ end
31
+ next if props[:type] != :callback || method_defined?(opt)
32
+ define_method(opt) do |&block|
33
+ @procs ||= {}
34
+ @procs[opt.to_sym] = block
35
+ Curl.set_option(opt, block, handle)
36
+ nil
39
37
  end
40
38
  end
41
39
  end
@@ -1,5 +1,5 @@
1
1
  module Ethon
2
2
 
3
3
  # Ethon version.
4
- VERSION = '0.8.1'
4
+ VERSION = '0.9.0'
5
5
  end
@@ -3,111 +3,79 @@ require 'ethon'
3
3
  require 'open-uri'
4
4
  require 'patron'
5
5
  require 'curb'
6
- require "net/http"
6
+ require 'net/http'
7
7
  require 'cgi'
8
- require 'benchmark'
9
-
10
- Benchmark.bm do |bm|
8
+ require 'benchmark/ips'
9
+
10
+ require_relative '../spec/support/server'
11
+ require_relative '../spec/support/localhost_server'
12
+
13
+ LocalhostServer.new(TESTSERVER.new, 3000)
14
+ LocalhostServer.new(TESTSERVER.new, 3001)
15
+ LocalhostServer.new(TESTSERVER.new, 3002)
16
+
17
+ url = 'http://localhost:3000/'.freeze
18
+ uri = URI.parse('http://localhost:3000/').freeze
19
+ ethon = Ethon::Easy.new(url: url)
20
+ patron = Patron::Session.new
21
+ patron_url = Patron::Session.new(base_url: url)
22
+ curb = Curl::Easy.new(url)
23
+
24
+ puts '[Creation]'
25
+ Benchmark.ips do |x|
26
+ x.report('String.new') { '' }
27
+ x.report('Easy.new') { Ethon::Easy.new }
28
+ end
11
29
 
12
- [100_000].each do |i|
13
- puts "[ #{i} Creations]"
30
+ puts '[Escape]'
31
+ Benchmark.ips do |x|
32
+ x.report('CGI.escape') { CGI.escape("まつもと") }
33
+ x.report('Easy.escape') { ethon.escape("まつもと") }
34
+ end
14
35
 
15
- bm.report("String.new ") do
16
- i.times { String.new }
17
- end
36
+ puts '[Requests]'
37
+ Benchmark.ips do |x|
38
+ x.report('net/http') { Net::HTTP.get_response(uri) }
39
+ x.report('open-uri') { open url }
18
40
 
19
- bm.report("Easy.new ") do
20
- i.times { Ethon::Easy.new }
21
- end
41
+ x.report('patron') do
42
+ patron.base_url = url
43
+ patron.get('/')
22
44
  end
23
45
 
24
- GC.start
25
-
26
- [100_000].each do |i|
27
- puts "[ #{i} Escapes]"
28
-
29
- bm.report("CGI::escape ") do
30
- i.times { CGI::escape("まつもと") }
31
- end
46
+ x.report('patron reuse') { patron_url.get('/') }
32
47
 
33
- bm.report("Easy.escape ") do
34
- e = Ethon::Easy.new
35
- i.times { e.escape("まつもと") }
36
- end
48
+ x.report('curb') do
49
+ curb.url = url
50
+ curb.perform
37
51
  end
38
52
 
39
- GC.start
40
-
41
- [1000].each do |i|
42
- puts "[ #{i} Requests]"
43
-
44
- bm.report("net/http ") do
45
- uri = URI.parse("http://localhost:3001/")
46
- i.times { Net::HTTP.get_response(uri) }
47
- end
48
-
49
- bm.report("open-uri ") do
50
- i.times { open "http://localhost:3001/" }
51
- end
53
+ x.report('curb reuse') { curb.perform }
52
54
 
53
- bm.report("patron ") do
54
- sess = Patron::Session.new
55
- i.times do
56
- sess.base_url = "http://localhost:3001"
57
- sess.get("/")
58
- end
59
- end
60
-
61
- bm.report("patron reuse ") do
62
- sess = Patron::Session.new
63
- sess.base_url = "http://localhost:3001"
64
- i.times do
65
- sess.get("/")
66
- end
67
- end
68
-
69
- bm.report("curb reuse ") do
70
- easy = Curl::Easy.new("http://localhost:3001")
71
- i.times do
72
- easy.perform
73
- end
74
- end
75
-
76
- bm.report("Easy.perform ") do
77
- easy = Ethon::Easy.new
78
- i.times do
79
- easy.url = "http://localhost:3001/"
80
- easy.prepare
81
- easy.perform
82
- end
83
- end
84
-
85
- bm.report("Easy.perform reuse") do
86
- easy = Ethon::Easy.new
87
- easy.url = "http://localhost:3001/"
88
- easy.prepare
89
- i.times { easy.perform }
90
- end
55
+ x.report('Easy.perform') do
56
+ ethon.url = url
57
+ ethon.perform
91
58
  end
92
59
 
93
- GC.start
94
-
95
- puts "[ 4 delayed Requests ]"
60
+ x.report('Easy.perform reuse') { ethon.perform }
61
+ end
96
62
 
97
- bm.report("net/http ") do
63
+ puts "[ 4 delayed Requests ]"
64
+ Benchmark.ips do |x|
65
+ x.report('net/http') do
98
66
  3.times do |i|
99
67
  uri = URI.parse("http://localhost:300#{i}/?delay=1")
100
68
  Net::HTTP.get_response(uri)
101
69
  end
102
70
  end
103
71
 
104
- bm.report("open-uri ") do
72
+ x.report("open-uri") do
105
73
  3.times do |i|
106
74
  open("http://localhost:300#{i}/?delay=1")
107
75
  end
108
76
  end
109
77
 
110
- bm.report("patron ") do
78
+ x.report("patron") do
111
79
  sess = Patron::Session.new
112
80
  3.times do |i|
113
81
  sess.base_url = "http://localhost:300#{i}/?delay=1"
@@ -115,21 +83,19 @@ Benchmark.bm do |bm|
115
83
  end
116
84
  end
117
85
 
118
- bm.report("Easy.perform ") do
86
+ x.report("Easy.perform") do
119
87
  easy = Ethon::Easy.new
120
88
  3.times do |i|
121
89
  easy.url = "http://localhost:300#{i}/?delay=1"
122
- easy.prepare
123
90
  easy.perform
124
91
  end
125
92
  end
126
93
 
127
- bm.report("Multi.perform ") do
94
+ x.report("Multi.perform") do
128
95
  multi = Ethon::Multi.new
129
96
  3.times do |i|
130
97
  easy = Ethon::Easy.new
131
98
  easy.url = "http://localhost:300#{i}/?delay=1"
132
- easy.prepare
133
99
  multi.add(easy)
134
100
  end
135
101
  multi.perform
@@ -88,7 +88,7 @@ describe Ethon::Easy::Informations do
88
88
 
89
89
  describe "#supports_zlib?" do
90
90
  it "returns true" do
91
- Kernel.should_receive(:warn) #deprecation warning
91
+ expect(Kernel).to receive(:warn)
92
92
  expect(easy.supports_zlib?).to be_truthy
93
93
  end
94
94
  end
@@ -25,6 +25,7 @@ describe Ethon::Easy::Operations do
25
25
  let(:password) { nil }
26
26
 
27
27
  before do
28
+ Ethon.logger.level = Logger::DEBUG
28
29
  easy.url = url
29
30
  easy.timeout = timeout
30
31
  easy.connecttimeout = connect_timeout
@@ -51,7 +52,7 @@ describe Ethon::Easy::Operations do
51
52
  end
52
53
 
53
54
  it "calls Curl.easy_cleanup" do
54
- FFI::AutoPointer.any_instance.should_receive(:free)
55
+ expect_any_instance_of(FFI::AutoPointer).to receive(:free)
55
56
  easy.cleanup
56
57
  end
57
58
 
@@ -99,6 +99,83 @@ describe Ethon::Easy do
99
99
  end
100
100
  end
101
101
 
102
+ describe "#dup" do
103
+ let!(:easy) do
104
+ easy = Ethon::Easy.new
105
+ easy.url = "http://localhost:3001/"
106
+ easy.on_complete { 'on_complete' }
107
+ easy.on_headers { 'on_headers' }
108
+ easy.response_body = 'test_body'
109
+ easy.response_headers = 'test_headers'
110
+ easy
111
+ end
112
+ let!(:e) { easy.dup }
113
+
114
+ it "sets a new handle" do
115
+ expect(e.handle).not_to eq(easy.handle)
116
+ end
117
+
118
+ it "preserves url" do
119
+ expect(e.url).to eq(easy.url)
120
+ end
121
+
122
+ it "preserves on_complete callback" do
123
+ expect(e.on_complete).to be(easy.on_complete)
124
+ end
125
+
126
+ it "preserves on_headers callback" do
127
+ expect(e.on_headers).to be(easy.on_headers)
128
+ end
129
+
130
+ it 'preserves body_write_callback of original handle' do
131
+ expect { easy.perform }.to change { easy.response_body }
132
+ expect { easy.perform }.not_to change { e.response_body }
133
+ end
134
+
135
+ it 'sets new body_write_callback of duplicated handle' do
136
+ expect { e.perform }.to change { e.response_body }
137
+ expect { e.perform }.not_to change { easy.response_body }
138
+ end
139
+
140
+ it 'preserves headers_write_callback of original handle' do
141
+ expect { easy.perform }.to change { easy.response_headers }
142
+ expect { easy.perform }.not_to change { e.response_headers }
143
+ end
144
+
145
+ it 'sets new headers_write_callback of duplicated handle' do
146
+ expect { e.perform }.to change { e.response_headers }
147
+ expect { e.perform }.not_to change { easy.response_headers }
148
+ end
149
+
150
+ it "resets response_body" do
151
+ expect(e.response_body).to be_empty
152
+ end
153
+
154
+ it "resets response_headers" do
155
+ expect(e.response_headers).to be_empty
156
+ end
157
+
158
+ it "sets response_body for duplicated Easy" do
159
+ e.perform
160
+ expect(e.response_body).not_to be_empty
161
+ end
162
+
163
+ it "sets response_headers for duplicated Easy" do
164
+ e.perform
165
+ expect(e.response_headers).not_to be_empty
166
+ end
167
+
168
+ it "preserves response_body for original Easy" do
169
+ e.perform
170
+ expect(easy.response_body).to eq('test_body')
171
+ end
172
+
173
+ it "preserves response_headers for original Easy" do
174
+ e.perform
175
+ expect(easy.response_headers).to eq('test_headers')
176
+ end
177
+ end
178
+
102
179
  describe "#mirror" do
103
180
  it "returns a Mirror" do
104
181
  expect(easy.mirror).to be_a(Ethon::Easy::Mirror)
@@ -4,7 +4,7 @@ require 'zlib'
4
4
  require 'sinatra/base'
5
5
 
6
6
  TESTSERVER = Sinatra.new do
7
- set :logging, false
7
+ set :logging, nil
8
8
 
9
9
  fail_count = 0
10
10
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ethon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hans Hasselberg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-10 00:00:00.000000000 Z
11
+ date: 2016-04-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi