marketo-api-ruby 0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NjY4NjRlOGQ0YjY2MWNmY2QwNDQyNmM3YTA2NTJlNzcxNmRiZTNiOA==
5
+ data.tar.gz: !binary |-
6
+ YWNhZjY4NGU3ZTE5YTVhZTY2Y2IyOTZmMGI4MTUyZTJmODg0MzFmZg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MGYzYjBkODBjOTE1MDRmYmYxMmNhMjQyMDU0MmI2NGQwZTdhNGViM2Q1ODAx
10
+ NWU4NzJmN2YwOWEwYmRiM2EzMWQzZGRiNzRlNTZhMjNkZjY1ZmFkMTZiOGQz
11
+ YmVjYzJiYjMxZGY5ZTVlODRlODQwOGVmMTI1Y2RmNmQwMGNjYzM=
12
+ data.tar.gz: !binary |-
13
+ ZGNiMmEwYTU3NGE3MmE3NWE3NGI3Nzc5OWY3M2MxYWQwZDU1Njc3ZjRiZTc5
14
+ YTA0MDZiNTEyMTA2MjYzNjhjYTFiMWVlYjAzNmJjMzZhNmI0NGZhYTk3YTRh
15
+ N2NlY2Y3ZjM3ZDFmYWYzMTU1ZGI5NzNhMTZmY2I4YjcwMDNiNGE=
Binary file
@@ -0,0 +1,2 @@
1
+ ^ �޼p��'*h⻖�2xX��"8��Qb���a� �ܑa�mٺ�$�s���OfB�~�.q_�`Ԗ=���!�$b�bGރ��n,l B�ndD�O��E��������G�z]}��P@��LM���n��rO�e�j�3�)��^'���2o�ɤmra
2
+ �׆����C��>�C�nc*3�C~qs�j��7�平��TXg�!)B��Hb�b��������a_ڌI�[� ��8��s��iG����.�c<t����
File without changes
@@ -0,0 +1,65 @@
1
+ == Contributing
2
+
3
+ I value any contribution to marketo-api-ruby you can provide: a bug report, a
4
+ feature request, or code contributions. Here are a few guidelines:
5
+
6
+ * Code changes *will* *not* be accepted without tests. The test suite is
7
+ written with {Minitest}[https://github.com/seattlerb/minitest].
8
+ * Match my coding style.
9
+ * Use a thoughtfully-named topic branch that contains your change. Rebase your
10
+ commits into logical chunks as necessary.
11
+ * Use {quality commit messages}[http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html].
12
+ * Do not change the version number; when your patch is accepted and a release
13
+ is made, the version will be updated at that point.
14
+ * Submit a GitHub pull request with your changes.
15
+ * New or changed behaviours require new or updated documentation.
16
+
17
+ === Test Dependencies
18
+
19
+ marketo-api-ruby uses Ryan Davis’s {Hoe}[https://github.com/seattlerb/hoe] to
20
+ manage the release process, and it adds a number of rake tasks. You will mostly
21
+ be interested in:
22
+
23
+ $ rake
24
+
25
+ which runs the tests the same way that:
26
+
27
+ $ rake test
28
+ $ rake travis
29
+
30
+ will do.
31
+
32
+ To assist with the installation of the development dependencies for
33
+ marketo-api-ruby, I have provided the simplest possible Gemfile pointing to the
34
+ (generated) +marketo-api-ruby.gemspec+ file. This will permit you to do:
35
+
36
+ $ bundle install
37
+
38
+ to get the development dependencies. If you aleady have +hoe+ installed, you
39
+ can accomplish the same thing with:
40
+
41
+ $ rake newb
42
+
43
+ This task will install any missing dependencies, run the tests/specs, and
44
+ generate the RDoc.
45
+
46
+ === Workflow
47
+
48
+ Here's the most direct way to get your work merged into the project:
49
+
50
+ * Fork the project.
51
+ * Clone down your fork (<tt>git clone git://github.com/<username>/marketo-api-ruby.git</tt>).
52
+ * Create a topic branch to contain your change (<tt>git checkout -b my\_awesome\_feature</tt>).
53
+ * Hack away, add tests. Not necessarily in that order.
54
+ * Make sure everything still passes by running +rake+.
55
+ * If necessary, rebase your commits into logical chunks, without errors.
56
+ * Push the branch up (<tt>git push origin my\_awesome\_feature</tt>).
57
+ * Create a pull request against ClearFit/marketo-api-ruby and describe what
58
+ your change does and the why you think it should be merged.
59
+
60
+ === Contributors
61
+
62
+ * Austin Ziegler (@halostatue) created and maintains marketo-api-ruby for ClearFit.
63
+ * James O'Brien (@jeob32) created
64
+ {marketo_gem}[https://github.com/Rapleaf/marketo_gem] for Rapleaf.
65
+ * Eddie Siegel (@eddiesiegel)
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # -*- ruby -*-
2
+
3
+ # NOTE: This file is present to keep Travis CI happy. Edits to it will not
4
+ # be accepted.
5
+
6
+ source "https://rubygems.org/"
7
+ gemspec
8
+
9
+ # vim: syntax=ruby
@@ -0,0 +1,3 @@
1
+ === 0.8 / 2014-04-08
2
+
3
+ * First release as marketo-api-ruby. Features a substantially restructured API.
@@ -0,0 +1,24 @@
1
+ == Licence
2
+
3
+ This software is available under the terms of the MIT license.
4
+
5
+ * Copyright 2014 ClearFit, Inc.
6
+ * Copyright 2013 Rapleaft, Inc.
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
9
+ this software and associated documentation files (the "Software"), to deal in
10
+ the Software without restriction, including without limitation the rights to
11
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12
+ of the Software, and to permit persons to whom the Software is furnished to do
13
+ so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in all
16
+ copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ SOFTWARE.
@@ -0,0 +1,32 @@
1
+ .gemtest
2
+ Contributing.rdoc
3
+ Gemfile
4
+ History.rdoc
5
+ Licence.rdoc
6
+ Manifest.txt
7
+ README.rdoc
8
+ Rakefile
9
+ lib/marketo-api-ruby.rb
10
+ lib/marketo_api.rb
11
+ lib/marketo_api/campaigns.rb
12
+ lib/marketo_api/client.rb
13
+ lib/marketo_api/client_proxy.rb
14
+ lib/marketo_api/lead.rb
15
+ lib/marketo_api/leads.rb
16
+ lib/marketo_api/lists.rb
17
+ lib/marketo_api/mobject.rb
18
+ lib/marketo_api/mobjects.rb
19
+ spec/marketo/authentication_header_spec.rb
20
+ spec/marketo/client_spec.rb
21
+ spec/marketo/lead_key_spec.rb
22
+ spec/marketo/lead_record_spec.rb
23
+ spec/spec_helper.rb
24
+ test/marketo_api/test_campaigns.rb
25
+ test/marketo_api/test_client.rb
26
+ test/marketo_api/test_lead.rb
27
+ test/marketo_api/test_leads.rb
28
+ test/marketo_api/test_lists.rb
29
+ test/marketo_api/test_mobject.rb
30
+ test/marketo_api/test_mobjects.rb
31
+ test/minitest_helper.rb
32
+ test/test_marketo_api.rb
@@ -0,0 +1,105 @@
1
+ = marketo-api-ruby
2
+
3
+ home :: https://github.com/ClearFit/marketo-api-ruby
4
+ code :: https://github.com/ClearFit/marketo-api-ruby
5
+ bugs :: https://github.com/ClearFit/marketo-api-ruby/issues
6
+ rdoc :: https://rdoc.info/gems/marketo-api-ruby
7
+ continuous integration :: {<img src="https://travis-ci.org/ClearFit/marketo-api-ruby.png" />}[https://travis-ci.org/ClearFit/marketo-api-ruby]
8
+ test coverage :: {<img src="https://coveralls.io/repos/ClearFit/marketo-api-ruby/badge.png" alt="Coverage Status" />}[https://coveralls.io/r/ClearFit/marketo-api-ruby]
9
+
10
+ == Description
11
+
12
+ MarketoAPI (marketo-api-ruby) provides a native Ruby interface to the
13
+ {Marketo SOAP API}[http://developers.marketo.com/documentation/soap/], using
14
+ {savon}[https://github.com/savonrb/savon]. While understanding the Marketo SOAP
15
+ API is necessary for using marketo-api-ruby, it is an explicit goal that
16
+ working with MarketoAPI not feel like working with a hinky Java port.
17
+
18
+ This is release 0.8, targeting Marketo API version
19
+ {2.3}[http://app.marketo.com/soap/mktows/2_3?WSDL].
20
+
21
+ === Features/Problems
22
+
23
+ The marketo-api-ruby implementation of the Marketo SOAP API is incomplete, but
24
+ is scheduled to be complete against Marketo SOAP API 2.3 for the version 1.0
25
+ release; this will take some time.
26
+
27
+ - Marketo Object (MObject) operations are 80% implemented.
28
+ - Not implemented: +syncMObjects+
29
+ - Improvements will be made to the criteria-building interface and the
30
+ association-building interface.
31
+ - Marketo Custom Object operations are not yet implemented.
32
+ - Marketo Lead operations are 37% implemented.
33
+ - Not implemented: +getMultipleLeads+, +mergeLeads+, +getLeadActivity+,
34
+ +getLeadChanges+
35
+ - Marketo Campaign operations are 90% implemented.
36
+ - The +requestCampaign+ API is currently awkward around managing program
37
+ tokens (+programTokenList+ in the SOAP API).
38
+ - The +scheduleCampaign+ API is currently awkward around managing program
39
+ tokens (+programTokenList+ in the SOAP API).
40
+ - Marketo List opertions are 100% implemented.
41
+
42
+ == Synopsis
43
+
44
+ require 'marketo_api' # Only required if not loaded by Bundler
45
+ marketo = MarketoAPI.client(
46
+ api_subdomain: '123-ABC-456',
47
+ user_id: 'my-api-user-id',
48
+ encryption_key: 'my-api-encryption-key'
49
+ )
50
+
51
+ # Get a lead by email, and sync it back to Marketo.
52
+ lead = marketo.leads.get_by_email('mork@ork.com')
53
+ lead.email = 'mindy@ork.com'
54
+ lead.sync!
55
+
56
+ # Get campaigns for marketo (MKTOWS)
57
+ puts marketo.campaigns.for_marketo
58
+
59
+ # Delete Opportunity 75.
60
+ marketo.mobjects.delete MarketoAPI::MObject.opportunity(75)
61
+
62
+ # Get all Marketo programs
63
+
64
+ == Install
65
+
66
+ Add to your application's Gemfile:
67
+
68
+ gem 'marketo-api-ruby'
69
+
70
+ Or install manually:
71
+
72
+ gem install marketo-api-ruby
73
+
74
+ == Developers
75
+
76
+ After checking out the source, run:
77
+
78
+ $ rake newb
79
+
80
+ This task will install any missing dependencies, run the tests/specs,
81
+ and generate the RDoc.
82
+
83
+ == marketo-api-ruby Modified Semantic Versioning
84
+
85
+ The marketo-api-ruby library tracks an externally versioned API; this is not
86
+ fully compatible with all aspects of {Semantic Versioning}[http://semver.org/].
87
+
88
+ 1. MAJOR versions will be updated when targeting a new major version of the
89
+ Marketo API, if Marketo makes incompatible changes to its API in minor
90
+ versions, *or* when incompatible marketo-api-ruby API changes are made. The
91
+ release notes will identify the cause of the update.
92
+ 2. MINOR versions will be updated when targeting a new minor version of the
93
+ Marketo API (e.g., Marketo version 2.2 to 2.3).
94
+ 3. PATCH version when backwards-compatible bug fixes are made.
95
+
96
+ Additionally, each release will clearly identify which Marketo API it targets
97
+ as its baseline. If a release is made without a PATCH level, it can be assumed
98
+ to be zero.
99
+
100
+ In practical terms, MAJOR versions will only be updated if you cannot use an
101
+ earlier API version for an existing query (such as +Marketo::Lead#sync+).
102
+
103
+ :include: Contributing.rdoc
104
+
105
+ :include: Licence.rdoc
@@ -0,0 +1,65 @@
1
+ # -*- ruby -*-
2
+
3
+ require "rubygems"
4
+ require "hoe"
5
+
6
+ Hoe.plugin :doofus
7
+ Hoe.plugin :gemspec2
8
+ Hoe.plugin :git
9
+ Hoe.plugin :heroku
10
+ Hoe.plugin :minitest
11
+ Hoe.plugin :travis
12
+ Hoe.plugin :email unless ENV['CI'] or ENV['TRAVIS']
13
+
14
+ spec = Hoe.spec "marketo-api-ruby" do
15
+ developer('Austin Ziegler', 'halostatue@gmail.com')
16
+ self.need_tar = false
17
+ self.require_ruby_version '>= 1.9.2'
18
+
19
+ license 'MIT'
20
+
21
+ self.history_file = 'History.rdoc'
22
+ self.readme_file = 'README.rdoc'
23
+ self.extra_rdoc_files = FileList["*.rdoc"].to_a
24
+
25
+ self.extra_deps << ['savon', '~> 2.4']
26
+
27
+ self.extra_dev_deps << ['hoe-doofus', '~> 1.0']
28
+ self.extra_dev_deps << ['hoe-gemspec2', '~> 1.1']
29
+ self.extra_dev_deps << ['hoe-git', '~> 1.6']
30
+ self.extra_dev_deps << ['hoe-rubygems', '~> 1.0']
31
+ self.extra_dev_deps << ['hoe-travis', '~> 1.2']
32
+ self.extra_dev_deps << ['minitest', '~> 5.3']
33
+ self.extra_dev_deps << ['rake', '~> 10.0']
34
+ self.extra_dev_deps << ['simplecov', '~> 0.7']
35
+ self.extra_dev_deps << ['coveralls', '~> 0.7']
36
+ end
37
+
38
+ namespace :test do
39
+ desc "Submit test coverage to Coveralls"
40
+ task :coveralls do
41
+ spec.test_prelude = [
42
+ 'require "psych"',
43
+ 'require "simplecov"',
44
+ 'require "coveralls"',
45
+ 'SimpleCov.formatter = Coveralls::SimpleCov::Formatter',
46
+ 'SimpleCov.start("test_frameworks") { command_name "Minitest" }',
47
+ 'gem "minitest"'
48
+ ].join('; ')
49
+ Rake::Task['test'].execute
50
+ end
51
+
52
+ desc "Generate a test coverage report"
53
+ task :coverage do
54
+ spec.test_prelude = [
55
+ 'require "simplecov"',
56
+ 'SimpleCov.start("test_frameworks") { command_name "Minitest" }',
57
+ 'gem "minitest"'
58
+ ].join('; ')
59
+ Rake::Task['test'].execute
60
+ end
61
+ end
62
+
63
+ Rake::Task['travis'].prerequisites.replace(%w(test:coveralls))
64
+
65
+ # vim: syntax=ruby
@@ -0,0 +1 @@
1
+ require 'marketo_api'
@@ -0,0 +1,39 @@
1
+ # MarketoAPI (marketo-api-ruby) provides a native Ruby interface to the
2
+ # {Marketo SOAP API}[http://developers.marketo.com/documentation/soap/],
3
+ # using {savon}[https://github.com/savonrb/savon].
4
+ #
5
+ # == Synopsis
6
+ #
7
+ # require 'marketo_api'
8
+ module MarketoAPI
9
+ VERSION = "0.8"
10
+
11
+ MINIMIZE_HASH = ->(k, v) { #:nodoc:
12
+ v.nil? or (v.respond_to?(:empty?) and v.empty?)
13
+ }
14
+
15
+ class << self
16
+ # Create a new MarketoAPI::Client, essentially an alias for
17
+ # MarketoAPI::Client.new.
18
+ def client(config = {})
19
+ MarketoAPI::Client.new(config)
20
+ end
21
+
22
+ def array(object) # :nodoc:
23
+ case object
24
+ when Hash
25
+ [ object ]
26
+ when Array
27
+ object
28
+ else
29
+ Kernel.Array(object)
30
+ end
31
+ end
32
+
33
+ def freeze(*args) # :nodoc:
34
+ args.each(&:freeze).freeze
35
+ end
36
+ end
37
+ end
38
+
39
+ require 'marketo_api/client'
@@ -0,0 +1,194 @@
1
+ require_relative 'client_proxy'
2
+
3
+ # Implements Campaign-related calls for Marketo.
4
+ #
5
+ # === Sources
6
+ #
7
+ # Campaigns have a source, which the Marketo SOAP API provides as +MKTOWS+
8
+ # and +SALES+. MarketoAPI provides these values as the friendlier values
9
+ # +marketo+ and +sales+, but accepts the standard Maketo SOAP API
10
+ # enumeration values.
11
+ class MarketoAPI::Campaigns < MarketoAPI::ClientProxy
12
+ SOURCES = { #:nodoc:
13
+ marketo: :MKTOWS,
14
+ sales: :SALES,
15
+ }.freeze
16
+ private_constant :SOURCES
17
+
18
+ ENUMS = MarketoAPI.freeze(*SOURCES.values) #:nodoc:
19
+ private_constant :ENUMS
20
+
21
+ # Implements
22
+ # {+getCampaignsForSource+}[http://developers.marketo.com/documentation/soap/getcampaignsforsource/].
23
+ #
24
+ # If possible, prefer the generated methods #for_marketo and #for_sales.
25
+ #
26
+ # :call-seq:
27
+ # for_source(source)
28
+ # for_source(source, name)
29
+ # for_source(source, name, exact_name)
30
+ def for_source(source, name = nil, exact_name = nil)
31
+ call(
32
+ :get_campaigns_for_source,
33
+ {
34
+ source: resolve_source(source),
35
+ name: name,
36
+ exact_name: exact_name
37
+ }.delete_if(&MarketoAPI::MINIMIZE_HASH)
38
+ )
39
+ end
40
+
41
+ ##
42
+ # :method: for_marketo
43
+ #
44
+ # Implements +getCampaignsForSource+ for the +marketo+ source; a
45
+ # specialization of #for_source.
46
+ #
47
+ # :call-seq:
48
+ # for_marketo()
49
+ # for_marketo(name)
50
+ # for_marketo(name, exact_name)
51
+
52
+ ##
53
+ # :method: for_sales
54
+ #
55
+ # Implements +getCampaignsForSource+ for the +sales+ source; a
56
+ # specialization of #for_source.
57
+ #
58
+ # :call-seq:
59
+ # for_sales()
60
+ # for_sales(name)
61
+ # for_sales(name, exact_name)
62
+
63
+ # Implements
64
+ # {+requestCampaign+}[http://developers.marketo.com/documentation/soap/requestcampaign/].
65
+ #
66
+ # === Parameters
67
+ #
68
+ # +source+:: Required. The source of the campaign.
69
+ # +leads+:: Required. An array of Lead objects or lead keys. If
70
+ # both +leads+ and +lead+ are provided, they will be
71
+ # merged.
72
+ # +lead+:: An alias for +leads+.
73
+ # +campaign_id+:: The campaign ID to request for the +lead+ or +leads+.
74
+ # Required if +campaign_name+ or +program_name+ are not
75
+ # provided.
76
+ # +campaign_name+:: The campaign name to request for the +lead+ or
77
+ # +leads+. Required if +campaign_id+ or +program_name+
78
+ # are not provided.
79
+ # +program_name+:: The program name to request for the +lead+ or
80
+ # +leads+. Required if +campaign_id+ or +campaign_name+
81
+ # are not provided, or if +program_tokens+ are
82
+ # provided.
83
+ # +program_tokens+:: An array of program tokens in the form:
84
+ # <tt>{ attrib: { name: name, value: value } }</tt>
85
+ # This will be made easier to manage in the future.
86
+ #
87
+ # If possible, prefer #request_marketo and #request_sales.
88
+ #
89
+ # :call-seq:
90
+ # request(options)
91
+ def request(options = {})
92
+ source = options.fetch(:source) { :MKTOWS }
93
+ leads = MarketoAPI.array(options.delete(:leads)) +
94
+ MarketoAPI.array(options.delete(:lead))
95
+ if leads.empty?
96
+ raise ArgumentError, ':lead or :leads must be provided'
97
+ end
98
+
99
+ valid_id = options.has_key?(:campaign_id) ||
100
+ options.has_key?(:campaign_name) || options.has_key?(:program_name)
101
+ unless valid_id
102
+ raise ArgumentError,
103
+ ':campaign_id, :campaign_name, or :program_name must be provided'
104
+ end
105
+
106
+ if options.has_key?(:campaign_id) && options.has_key?(:campaign_name)
107
+ raise ArgumentError,
108
+ ':campaign_id and :campaign_name are mutually exclusive'
109
+ end
110
+
111
+ if (tokens = options.delete(:program_tokens)) && !tokens.empty?
112
+ if !options[:program_name]
113
+ raise KeyError,
114
+ ':program_name must be provided when using :program_tokens'
115
+ end
116
+ end
117
+
118
+ call(
119
+ :request_campaign,
120
+ options.merge(
121
+ source: resolve_source(source),
122
+ lead_list: transform_param_list(:get, leads),
123
+ program_token_list: tokens
124
+ ).delete_if(&MarketoAPI::MINIMIZE_HASH)
125
+ )
126
+ end
127
+
128
+ ##
129
+ # :method: request_marketo
130
+ #
131
+ # Implements +getCampaignsForSource+ for the +marketo+ source; a
132
+ # specialization of #request.
133
+ #
134
+ # :call-seq:
135
+ # request_marketo(options)
136
+
137
+ ##
138
+ # :method: request_sales
139
+ #
140
+ # Implements +getCampaignsForSource+ for the +sales+ source; a
141
+ # specialization of #request.
142
+ #
143
+ # :call-seq:
144
+ # request_sales(options)
145
+
146
+ # Implements
147
+ # {+scheduleCampaign+}[http://developers.marketo.com/documentation/soap/schedulecampaign/].
148
+ #
149
+ # === Optional Parameters
150
+ #
151
+ # +run_at+:: The time to run the scheduled campaign.
152
+ # +program_tokens+:: An array of program tokens in the form:
153
+ # <tt>{ attrib: { name: name, value: value } }</tt>
154
+ # This will be made easier to manage in the future.
155
+ #
156
+ # +source+ must be +marketo+ or +sales+ or the equivalent enumerated
157
+ # values from the SOAP environment (+MKTOWS+ or +SALES+).
158
+ #
159
+ # :call-seq:
160
+ # schedule(program_name, campaign_name, options = {})
161
+ def schedule(program_name, campaign_name, options = {})
162
+ call(
163
+ :schedule_campaign,
164
+ {
165
+ program_name: program_name,
166
+ campaign_name: campaign_name,
167
+ campaign_run_at: options[:run_at],
168
+ program_token_list: options[:program_tokens]
169
+ }.delete_if(&MarketoAPI::MINIMIZE_HASH)
170
+ )
171
+ end
172
+
173
+ SOURCES.each_pair { |source, enum|
174
+ define_method(:"for_#{source}") do |name = nil, exact_name = nil|
175
+ for_source(enum, name, exact_name)
176
+ end
177
+
178
+ define_method(:"request_#{source}") do |options = {}|
179
+ request(options.merge(source: enum))
180
+ end
181
+ }
182
+
183
+ private
184
+ def resolve_source(source)
185
+ source = source.to_sym
186
+ res = if ENUMS.include? source
187
+ source
188
+ else
189
+ SOURCES[source]
190
+ end
191
+ raise ArgumentError, "Invalid source #{source}" unless res
192
+ res
193
+ end
194
+ end