right_scraper 3.2.6 → 5.0.1

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.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/lib/right_scraper.rb +16 -34
  3. data/lib/right_scraper/builders.rb +32 -0
  4. data/lib/right_scraper/builders/base.rb +19 -20
  5. data/lib/right_scraper/builders/filesystem.rb +8 -6
  6. data/lib/right_scraper/builders/union.rb +4 -1
  7. data/lib/right_scraper/loggers.rb +31 -0
  8. data/lib/right_scraper/loggers/base.rb +113 -0
  9. data/lib/right_scraper/loggers/default.rb +98 -0
  10. data/lib/right_scraper/{scraper.rb → main.rb} +53 -9
  11. data/lib/right_scraper/processes.rb +33 -0
  12. data/lib/right_scraper/processes/shell.rb +227 -0
  13. data/lib/right_scraper/processes/{ssh.rb → ssh_agent.rb} +4 -0
  14. data/lib/right_scraper/processes/svn_client.rb +117 -0
  15. data/lib/right_scraper/processes/warden.rb +358 -0
  16. data/lib/right_scraper/registered_base.rb +154 -0
  17. data/lib/right_scraper/repositories.rb +33 -0
  18. data/lib/right_scraper/repositories/base.rb +271 -232
  19. data/lib/right_scraper/repositories/download.rb +8 -6
  20. data/lib/right_scraper/repositories/git.rb +8 -9
  21. data/lib/right_scraper/repositories/svn.rb +8 -8
  22. data/lib/right_scraper/resources.rb +32 -0
  23. data/lib/right_scraper/resources/base.rb +5 -1
  24. data/lib/right_scraper/resources/cookbook.rb +34 -27
  25. data/lib/right_scraper/resources/workflow.rb +27 -28
  26. data/lib/right_scraper/retrievers.rb +34 -0
  27. data/lib/right_scraper/retrievers/base.rb +80 -84
  28. data/lib/right_scraper/retrievers/checkout_base.rb +178 -0
  29. data/lib/right_scraper/retrievers/download.rb +125 -117
  30. data/lib/right_scraper/retrievers/git.rb +377 -223
  31. data/lib/right_scraper/retrievers/svn.rb +102 -62
  32. data/lib/right_scraper/scanners.rb +37 -0
  33. data/lib/right_scraper/scanners/base.rb +77 -80
  34. data/lib/right_scraper/scanners/cookbook_manifest.rb +31 -30
  35. data/lib/right_scraper/scanners/cookbook_metadata.rb +380 -35
  36. data/lib/right_scraper/scanners/cookbook_s3_upload.rb +56 -53
  37. data/lib/right_scraper/scanners/union.rb +61 -58
  38. data/lib/right_scraper/scanners/workflow_manifest.rb +55 -54
  39. data/lib/right_scraper/scanners/workflow_metadata.rb +41 -39
  40. data/lib/right_scraper/scanners/workflow_s3_upload.rb +59 -55
  41. data/lib/right_scraper/scrapers.rb +32 -0
  42. data/lib/right_scraper/scrapers/base.rb +217 -205
  43. data/lib/right_scraper/scrapers/cookbook.rb +42 -40
  44. data/lib/right_scraper/scrapers/workflow.rb +57 -58
  45. data/lib/right_scraper/version.rb +3 -0
  46. data/right_scraper.gemspec +12 -16
  47. metadata +57 -163
  48. data/Gemfile +0 -15
  49. data/Rakefile +0 -89
  50. data/lib/right_scraper/logger.rb +0 -107
  51. data/lib/right_scraper/loggers/noisy.rb +0 -85
  52. data/lib/right_scraper/repositories/mock.rb +0 -70
  53. data/lib/right_scraper/retrievers/checkout.rb +0 -79
  54. data/lib/right_scraper/scraper_logger.rb +0 -66
  55. data/lib/right_scraper/svn_client.rb +0 -164
  56. data/right_scraper.rconf +0 -13
  57. data/spec/builder_spec.rb +0 -50
  58. data/spec/cookbook_helper.rb +0 -73
  59. data/spec/cookbook_manifest_spec.rb +0 -93
  60. data/spec/cookbook_s3_upload_spec.rb +0 -159
  61. data/spec/download/download_retriever_spec.rb +0 -118
  62. data/spec/download/download_retriever_spec_helper.rb +0 -72
  63. data/spec/download/download_spec.rb +0 -128
  64. data/spec/download/multi_dir_spec.rb +0 -106
  65. data/spec/download/multi_dir_spec_helper.rb +0 -40
  66. data/spec/git/cookbook_spec.rb +0 -165
  67. data/spec/git/demokey +0 -27
  68. data/spec/git/demokey.pub +0 -1
  69. data/spec/git/password_key +0 -30
  70. data/spec/git/password_key.pub +0 -1
  71. data/spec/git/repository_spec.rb +0 -110
  72. data/spec/git/retriever_spec.rb +0 -553
  73. data/spec/git/retriever_spec_helper.rb +0 -112
  74. data/spec/git/scraper_spec.rb +0 -151
  75. data/spec/git/ssh_spec.rb +0 -174
  76. data/spec/git/url_spec.rb +0 -103
  77. data/spec/logger_spec.rb +0 -185
  78. data/spec/repository_spec.rb +0 -111
  79. data/spec/retriever_spec_helper.rb +0 -146
  80. data/spec/scanner_spec.rb +0 -61
  81. data/spec/scraper_helper.rb +0 -88
  82. data/spec/scraper_spec.rb +0 -147
  83. data/spec/spec_helper.rb +0 -185
  84. data/spec/svn/cookbook_spec.rb +0 -96
  85. data/spec/svn/multi_svn_spec.rb +0 -64
  86. data/spec/svn/multi_svn_spec_helper.rb +0 -40
  87. data/spec/svn/repository_spec.rb +0 -72
  88. data/spec/svn/retriever_spec.rb +0 -266
  89. data/spec/svn/scraper_spec.rb +0 -90
  90. data/spec/svn/svn_retriever_spec_helper.rb +0 -90
  91. data/spec/svn/url_spec.rb +0 -47
  92. data/spec/url_spec.rb +0 -164
@@ -0,0 +1,358 @@
1
+ #--
2
+ # Copyright: Copyright (c) 2013 RightScale, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ # ancestor
25
+ require 'right_scraper/processes'
26
+
27
+ require 'fileutils'
28
+ require 'right_popen'
29
+ require 'right_popen/safe_output_buffer'
30
+ require 'tmpdir'
31
+
32
+ module RightScraper
33
+ module Processes
34
+ class Warden
35
+
36
+ DEFAULT_RVM_HOME = '/usr/local/rvm'
37
+ DEFAULT_WARDEN_HOME = '/opt/warden'
38
+
39
+ RELATIVE_SCRIPTS_RVM_PATH = 'scripts/rvm'
40
+
41
+ # TEAL FIX: dynamically discover highest rvm-installed ruby 1.9 build?
42
+ DEFAULT_RVM_RUBY_VERSION = 'ruby-1.9.3-p448'
43
+
44
+ WARDEN_SERVICE_SUBDIR_NAME = 'warden'
45
+ RELATIVE_WARDEN_SCRIPT_PATH = 'bin/warden'
46
+
47
+ WARDEN_COMMAND_TIMEOUT = 60 # max seconds to spawn, link, etc.
48
+
49
+ DEFAULT_OPTIONS = {
50
+ :warden_home => DEFAULT_WARDEN_HOME,
51
+ :rvm_home => DEFAULT_RVM_HOME,
52
+ :rvm_ruby_version => DEFAULT_RVM_RUBY_VERSION
53
+ }
54
+
55
+ # marshalling
56
+ class LinkResult
57
+ attr_reader :exit_status, :stdout, :stderr
58
+
59
+ def initialize(link_result)
60
+ @exit_status = link_result['exit_status'].to_i rescue 1
61
+ @stdout = link_result['stdout'].to_s
62
+ @stderr = link_result['stderr'].to_s
63
+ end
64
+
65
+ def succeeded?
66
+ 0 == exit_status
67
+ end
68
+ end
69
+
70
+ # exceptions
71
+ class StateError < Exception; end
72
+ class WardenError < Exception; end
73
+
74
+ class LinkError < Exception
75
+ attr_reader :link_result
76
+
77
+ def initialize(message, link_result)
78
+ super(message)
79
+ @link_result = link_result
80
+ end
81
+ end
82
+
83
+ def initialize(options = {})
84
+ options = DEFAULT_OPTIONS.merge(options)
85
+ @warden_home = options[:warden_home]
86
+ @rvm_home = options[:rvm_home]
87
+ unless @rvm_ruby_version = options[:rvm_ruby_version]
88
+ raise ArgumentError.new('options[:rvm_ruby_version] is required')
89
+ end
90
+
91
+ # warden paths
92
+ unless @warden_home && ::File.directory?(@warden_home)
93
+ raise ArgumentError.new('options[:warden_home] is required')
94
+ end
95
+ unless @rvm_home && ::File.directory?(@rvm_home)
96
+ raise ArgumentError.new('options[:rvm_home] is required')
97
+ end
98
+ @warden_server_dir = ::File.join(@warden_home, WARDEN_SERVICE_SUBDIR_NAME)
99
+ @bin_warden_path = ::File.join(@warden_server_dir, RELATIVE_WARDEN_SCRIPT_PATH)
100
+ unless File.file?(@bin_warden_path)
101
+ raise StateError, "Warden CLI script cannot be found at #{@bin_warden_path.inspect}"
102
+ end
103
+
104
+ # rvm paths
105
+ @scripts_rvm_path = ::File.join(@rvm_home, RELATIVE_SCRIPTS_RVM_PATH)
106
+ unless File.file?(@scripts_rvm_path)
107
+ raise StateError, "RVM setup script cannot be found at #{@scripts_rvm_path.inspect}"
108
+ end
109
+
110
+ # build the jail.
111
+ @handle = send('create')['handle']
112
+ raise StateError, 'handle is invalid' unless @handle
113
+ end
114
+
115
+ # Runs the script given by container-relative path. Optionally copies
116
+ # files in/out before/after script execution.
117
+ #
118
+ # === Parameters
119
+ # @param [String|Array] cmds to execute
120
+ # @param [String|Array] copy_in file(s) to copy into jail (using same path on both sides) or nil or empty
121
+ # @param [Hash] copy_out files as map of jail source path to host destination path or empty or nil
122
+ #
123
+ # === Return
124
+ # @return [String] stdout text
125
+ #
126
+ # === Raise
127
+ # @raise [StateError] for invalid state
128
+ # @raise [LinkError] for link (to script output) failure
129
+ # @raise [WardenError] for warden failure
130
+ def run_command_in_jail(cmds, copy_in = nil, copy_out = nil)
131
+ cmds = Array(cmds)
132
+ raise ArgumentError, 'cmds is required' if cmds.empty?
133
+ raise StateError, 'handle is invalid' unless @handle
134
+
135
+ # copy any files in before running commands.
136
+ copy_in = Array(copy_in)
137
+ send_copy_in_cmds(copy_in) if !copy_in.empty?
138
+
139
+ # note that appending --privileged will run script as root, but we have
140
+ # no use case for running scripts as root at this time.
141
+ output = []
142
+ cmds.each do |cmd|
143
+ job_id = send("spawn --handle #{@handle} --script #{cmd.inspect}")['job_id']
144
+ link_result = LinkResult.new(send("link --handle #{@handle} --job_id #{job_id}"))
145
+ if link_result.succeeded?
146
+ output << link_result.stdout
147
+ else
148
+ raise LinkError.new('Script failed running in isolation.', link_result)
149
+ end
150
+ end
151
+
152
+ # copy any files out after command(s) succeeded.
153
+ if copy_out && !copy_out.empty?
154
+ copy_out_cmds = copy_out.inject([]) do |result, (src_path, dst_path)|
155
+ # create output directories because warden will only copy files.
156
+ parent_dir = ::File.dirname(dst_path)
157
+ ::FileUtils.mkdir_p(parent_dir)
158
+ result << "copy_out --handle #{@handle} --src_path #{src_path.inspect} --dst_path #{dst_path.inspect}"
159
+ result
160
+ end
161
+ send(copy_out_cmds)
162
+ end
163
+
164
+ return output.join("\n")
165
+ end
166
+
167
+ def cleanup
168
+ raise StateError, 'handle is invalid' unless @handle
169
+ lay_to_rest
170
+ send("destroy --handle #{@handle}")
171
+ ensure
172
+ @handle = nil
173
+ end
174
+
175
+ private
176
+
177
+ def create_uuid
178
+ (0..15).to_a.map{|a| rand(16).to_s(16)}.join
179
+ end
180
+
181
+ # warden doesn't create directories on copy_in (or _out) so we need to
182
+ # generate a script and execute it before invoking copy_in.
183
+ #
184
+ # @param [Array] copy_in as array of files to copy into jail
185
+ def send_copy_in_cmds(copy_in)
186
+ mkdir_cmds = copy_in.
187
+ map { |dst_path| ::File.dirname(dst_path) }.uniq.sort.
188
+ map { |parent_dir| "mkdir -p #{parent_dir}" }
189
+ shell_script = <<EOS
190
+ #!/bin/bash
191
+ rm $0 # this script will self-destruct
192
+ #{mkdir_cmds.join(" &&\n")}
193
+ EOS
194
+ mkdir_script_name = "mkdir_script_#{create_uuid}.sh"
195
+
196
+ job_id = nil
197
+ ::Dir.mktmpdir do |tmpdir|
198
+ mkdir_script_path = ::File.join(tmpdir, mkdir_script_name)
199
+ ::File.open(mkdir_script_path, 'w') { |f| f.puts shell_script }
200
+ create_parent_dir_cmds = [
201
+ "copy_in --handle #{@handle} --src_path #{mkdir_script_path} --dst_path /tmp/mkdirs.sh",
202
+ "spawn --handle #{@handle} --script '/bin/bash /tmp/mkdirs.sh'",
203
+ ]
204
+ job_id = send(create_parent_dir_cmds)['job_id']
205
+ end
206
+
207
+ link_result = LinkResult.new(send("link --handle #{@handle} --job_id #{job_id}"))
208
+ if link_result.succeeded?
209
+ copy_in_cmds = copy_in.inject([]) do |result, src_path|
210
+ result << "copy_in --handle #{@handle} --src_path #{src_path.inspect} --dst_path #{src_path.inspect}"
211
+ result
212
+ end
213
+ send(copy_in_cmds)
214
+ else
215
+ raise LinkError.new('Failed to create parent directories for files to be copied.', link_result)
216
+ end
217
+ true
218
+ end
219
+
220
+ # Sends one or more commands to warden and accumulates the stdout and
221
+ # stderr from those commands.
222
+ def send(warden_cmd)
223
+ # warden runs in a ruby 1.9.3 environment, for which we need rvm and a
224
+ # slew of fancy setup on the assumption that the current environemnt is
225
+ # not that. ideally this code would run in a standalone service where
226
+ # the warden-client gem could be used to simplify some of this.
227
+ warden_cmds = Array(warden_cmd).map do |line|
228
+ # execute bin/warden (Geronimo)
229
+ "bundle exec #{RELATIVE_WARDEN_SCRIPT_PATH} -- #{line}"
230
+ end
231
+
232
+ shell_script = <<EOS
233
+ #!/bin/bash
234
+ source #{@scripts_rvm_path} 1>/dev/null &&
235
+ rvm use #{@rvm_ruby_version}@global 1>/dev/null &&
236
+ cd #{@warden_server_dir} 1>/dev/null &&
237
+ #{warden_cmds.join(" &&\n")}
238
+ EOS
239
+
240
+ # ensure bundler env vars for current process don't interfere.
241
+ ::Bundler.with_clean_env do
242
+ ::Dir.mktmpdir do |tmpdir|
243
+ @process = nil
244
+ @interupted_to_close = false
245
+ @stdout_buffer = []
246
+ @stderr_buffer = ::RightScale::RightPopen::SafeOutputBuffer.new
247
+ warden_script_path = ::File.join(tmpdir, "run_warden_#{create_uuid}.sh")
248
+ ::File.open(warden_script_path, 'w') { |f| f.puts shell_script }
249
+ cmd = "/bin/bash #{warden_script_path}"
250
+ ::RightScale::RightPopen.popen3_sync(
251
+ cmd,
252
+ :target => self,
253
+ :inherit_io => true, # avoid killing any rails connection
254
+ :watch_handler => :watch_warden,
255
+ :stderr_handler => :stderr_warden,
256
+ :stdout_handler => :stdout_warden,
257
+ :timeout_handler => :timeout_warden,
258
+ :exit_handler => :exit_warden,
259
+ :timeout_seconds => WARDEN_COMMAND_TIMEOUT)
260
+ if @process
261
+ @process = nil
262
+ warden_output = @stdout_buffer.join
263
+ if warden_output.empty?
264
+ result = {}
265
+ else
266
+ result = parse_warden_output(warden_output)
267
+ end
268
+ return result
269
+ else
270
+ raise WardenError, 'Unable to execute warden.'
271
+ end
272
+ end
273
+ end
274
+ end
275
+
276
+ # Warden outputs something that looks like YAML but also somewhat like a
277
+ # Java configuration file. in any case, the output is ambiguous because it
278
+ # does not escape characters and it is possible to spawn a process that
279
+ # prints output text that appears to be the start of a new key. *sigh*
280
+ #
281
+ # all we can do here is attempt to parse the output by some simple rules
282
+ # and hope for the best.
283
+ #
284
+ # example:
285
+ # exit_status : 0
286
+ # stdout : a
287
+ # b
288
+ # c
289
+ #
290
+ # stderr :
291
+ # info.state : active
292
+ # ...
293
+ def parse_warden_output(warden_output)
294
+ parsed_lines = {}
295
+ current_key = nil
296
+ regex = /^([a-z._]+) \: (.*)$/
297
+ warden_output.lines.each do |line|
298
+ if parts = regex.match(line)
299
+ current_key = parts[1]
300
+ parsed_lines[current_key] = [parts[2]]
301
+ elsif current_key
302
+ parsed_lines[current_key] << line.chomp
303
+ else
304
+ raise WardenError, "Unable to parse warden output:\n#{warden_output.inspect}"
305
+ end
306
+ end
307
+ parsed_lines.inject({}) do |result, (key, value)|
308
+ result[key] = value.join("\n")
309
+ result
310
+ end
311
+ end
312
+
313
+ def lay_to_rest
314
+ if @process
315
+ if @process.interrupt
316
+ @interupted_to_close = true
317
+ @process.sync_exit_with_target
318
+ else
319
+ @process.safe_close_io
320
+ end
321
+ end
322
+ end
323
+
324
+ def stdout_warden(data)
325
+ @stdout_buffer << data
326
+ end
327
+
328
+ def stderr_warden(data)
329
+ @stderr_buffer.safe_buffer_data(data)
330
+ end
331
+
332
+ def watch_warden(process)
333
+ if @interupted_to_close
334
+ true
335
+ else
336
+ @process = process
337
+ end
338
+ end
339
+
340
+ def timeout_warden
341
+ unless @interupted_to_close
342
+ raise WardenError, 'Timed out waiting for warden to respond'
343
+ end
344
+ end
345
+
346
+ def exit_warden(status)
347
+ unless @interupted_to_close || status.success?
348
+ raise WardenError,
349
+ "Warden failed exit_status = #{status.exitstatus}:\n" +
350
+ "stdout = #{@stdout_buffer.join}\n" +
351
+ "stderr = #{@stderr_buffer.display_text}"
352
+ end
353
+ true
354
+ end
355
+
356
+ end # Warden
357
+ end # Processes
358
+ end # RightScraper
@@ -0,0 +1,154 @@
1
+ #--
2
+ # Copyright: Copyright (c) 2013 RightScale, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ # ancestor
25
+ require 'right_scraper'
26
+
27
+ module RightScraper
28
+
29
+ # Abstract base class for a registered type.
30
+ #
31
+ # Example:
32
+ #
33
+ # class Foo < RegisteredBase
34
+ # ...
35
+ #
36
+ # register_self(:foo)
37
+ # end
38
+ class RegisteredBase
39
+
40
+ # exceptions
41
+ class RegisteredTypeError < ::StandardError; end
42
+
43
+ # Provides a module from which a specific set of registered types is derived
44
+ # (for registration, autoloading, etc.). It is not necessary for all types
45
+ # of the set to be declared within the scope of that module, but doing so
46
+ # will simplify registration and query.
47
+ #
48
+ # @return [Module] module or base class in common
49
+ def self.registration_module
50
+ raise NotImplementedError
51
+ end
52
+
53
+ # @return [Hash] mapping of registered types to classes or empty
54
+ def self.registered_types
55
+ unless types = registration_module.instance_variable_get(:@registered_types)
56
+ types = {}
57
+ registration_module.instance_variable_set(:@registered_types, types)
58
+ end
59
+ types
60
+ end
61
+
62
+ # Registers self.
63
+ #
64
+ # @param [Symbol] type to register or nil
65
+ #
66
+ # @return [TrueClass] always true
67
+ def self.register_self(type = nil)
68
+ # automatically determine registered type from self, if necessary.
69
+ unless type
70
+ class_name = self.name
71
+ default_module_name = registration_module.name + '::'
72
+ if class_name.start_with?(default_module_name)
73
+ subname = class_name[default_module_name.length..-1]
74
+ class_name = subname unless subname.index('::')
75
+ end
76
+ type = class_name.
77
+ gsub(/::/, '/').
78
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
79
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
80
+ downcase
81
+ end
82
+ self.register_class(type, self)
83
+ true
84
+ end
85
+
86
+ # Registers given class.
87
+ #
88
+ # @param [Symbol|String] type to register
89
+ # @param [Class] clazz to register
90
+ #
91
+ # @return [TrueClass] always true
92
+ def self.register_class(type, clazz)
93
+ raise ::ArgumentError, 'clazz is required' unless clazz
94
+ raise ::ArgumentError, 'type is required' unless type
95
+ registered_types[type.to_s] = clazz
96
+ true
97
+ end
98
+
99
+ # Queries the implementation class for a registered type.
100
+ #
101
+ # @param [Symbol|String] type for query
102
+ #
103
+ # @return [RightScraper::Repositories::Base] repository created
104
+ def self.query_registered_type(type)
105
+ raise ::ArgumentError, 'type is required' unless type
106
+
107
+ # a quick-out when given a known registerd type. autoloading types makes
108
+ # things more interesting for unknown types.
109
+ type = type.to_s
110
+ unless clazz = registered_types[type]
111
+ # default module implementations may be auto-loading so try default
112
+ # namespace before giving up (assumes snake-case types). types
113
+ # declared in a different namespace can also be autoloaded if fully
114
+ # qualified using forward slashes (require-style).
115
+ class_path = type.split('/').map do |snake_case|
116
+ camel_case = snake_case.split('_').map{ |e| e.capitalize }.join
117
+ end
118
+
119
+ # assume no registered types at global scope and insert registration
120
+ # module before any simple name.
121
+ if class_path.size == 1
122
+ class_path = registration_module.name.split('::') + class_path
123
+ end
124
+
125
+ # walk class path from global scope because const_get doesn't understand
126
+ # the '::' notation. autoloading is usually setup to support walking
127
+ # from the base module.
128
+ last_item = nil
129
+ begin
130
+ parent_item = ::Object
131
+ class_path.each do |item|
132
+ last_item = parent_item.const_get(item)
133
+ parent_item = last_item
134
+ end
135
+ rescue ::NameError => e
136
+ if e.message =~ /uninitialized constant/
137
+ last_item = nil
138
+ else
139
+ raise
140
+ end
141
+ end
142
+ if last_item
143
+ # type still needs to successfully self-register upon definition.
144
+ unless clazz = registered_types[type]
145
+ raise RegisteredTypeError, "Discovered type did not register itself properly: #{type.inspect} => #{last_item.inspect}"
146
+ end
147
+ else
148
+ raise RegisteredTypeError, "Unknown registered type: #{type.inspect}"
149
+ end
150
+ end
151
+ clazz
152
+ end
153
+ end
154
+ end