js_spec 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/CHANGES +6 -0
  2. data/README +23 -1
  3. data/Rakefile +5 -3
  4. data/bin/js_spec +1 -5
  5. data/bin/js_spec_server +0 -0
  6. data/core/JSSpecExtensions.js +70 -25
  7. data/core/jquery.js +2992 -0
  8. data/init.rb +0 -0
  9. data/lib/js_spec/client.rb +50 -0
  10. data/lib/js_spec/rack/response.rb +5 -0
  11. data/lib/js_spec/rails_server.rb +3 -3
  12. data/lib/js_spec/resources/dir.rb +43 -43
  13. data/lib/js_spec/resources/file.rb +16 -16
  14. data/lib/js_spec/resources/runners/firefox1_runner.rb +8 -0
  15. data/lib/js_spec/resources/runners/firefox_runner.rb +49 -72
  16. data/lib/js_spec/resources/runners.rb +6 -7
  17. data/lib/js_spec/resources/spec_dir_runner.rb +7 -7
  18. data/lib/js_spec/resources/spec_file_runner.rb +7 -7
  19. data/lib/js_spec/resources/spec_runner.rb +16 -11
  20. data/lib/js_spec/resources/suite.rb +14 -14
  21. data/lib/js_spec/resources/suite_finish.rb +12 -12
  22. data/lib/js_spec/server.rb +29 -17
  23. data/lib/js_spec/thin/backends/js_spec_server.rb +9 -0
  24. data/lib/js_spec/thin/js_spec_connection.rb +43 -0
  25. data/lib/js_spec.rb +14 -2
  26. data/spec/integration/integration_spec.rb +14 -0
  27. data/spec/integration/integration_spec_helper.rb +52 -0
  28. data/spec/integration_suite.rb +10 -0
  29. data/spec/spec_suite.rb +1 -0
  30. data/spec/unit/js_spec/client_spec.rb +137 -0
  31. data/spec/unit/{rails_server_spec.rb → js_spec/rails_server_spec.rb} +8 -7
  32. data/spec/unit/{resources → js_spec/resources}/dir_spec.rb +1 -1
  33. data/spec/unit/{resources → js_spec/resources}/file_spec.rb +9 -6
  34. data/spec/unit/js_spec/resources/runner_spec.rb +24 -0
  35. data/spec/unit/js_spec/resources/runners/firefox_runner_spec.rb +152 -0
  36. data/spec/unit/js_spec/resources/spec_dir_runner_spec.rb +48 -0
  37. data/spec/unit/{resources → js_spec/resources}/spec_file_runner_spec.rb +1 -1
  38. data/spec/unit/{resources → js_spec/resources}/suite_finish_spec.rb +5 -5
  39. data/spec/unit/{resources → js_spec/resources}/suite_spec.rb +1 -1
  40. data/spec/unit/{resources → js_spec/resources}/web_root_spec.rb +1 -1
  41. data/spec/unit/{server_spec.rb → js_spec/server_spec.rb} +29 -92
  42. data/spec/unit/rack/response_spec.rb +30 -0
  43. data/spec/unit/thin/js_spec_connection_spec.rb +50 -0
  44. data/spec/unit/unit_spec_helper.rb +40 -5
  45. metadata +107 -60
  46. data/spec/unit/resources/runner_spec.rb +0 -24
  47. data/spec/unit/resources/runners/firefox_runner_spec.rb +0 -87
  48. data/spec/unit/resources/spec_dir_runner_spec.rb +0 -43
data/init.rb ADDED
File without changes
@@ -0,0 +1,50 @@
1
+ module JsSpec
2
+ class Client
3
+ class << self
4
+ def run(params={})
5
+ data = []
6
+ data << "selenium_host=#{CGI.escape(params[:selenium_host] || 'localhost')}"
7
+ data << "selenium_port=#{CGI.escape((params[:selenium_port] || 4444).to_s)}"
8
+ data << "spec_url=#{CGI.escape(params[:spec_url])}" if params[:spec_url]
9
+ response = Net::HTTP.start(DEFAULT_HOST, DEFAULT_PORT) do |http|
10
+ http.post('/runners/firefox', data.join("&"))
11
+ end
12
+
13
+ body = response.body
14
+ if body.empty?
15
+ puts "SUCCESS"
16
+ return true
17
+ else
18
+ puts "FAILURE"
19
+ puts body
20
+ return false
21
+ end
22
+ end
23
+
24
+ def run_argv(argv)
25
+ params = {}
26
+ parser = OptionParser.new do |o|
27
+ o.banner = "JsSpec Runner"
28
+ o.banner << "\nUsage: #{$0} [options] [-- untouched arguments]"
29
+
30
+ o.on
31
+ o.on('-h', '--selenium_host=SELENIUM_HOST', "The host name of the Selenium Server relative to where this file is executed") do |host|
32
+ params[:selenium_host] = host
33
+ end
34
+
35
+ o.on('-p', '--selenium_port=SELENIUM_PORT', "The port of the Selenium Server relative to where this file is executed") do |port|
36
+ params[:selenium_port] = port
37
+ end
38
+
39
+ o.on('-u', '--spec_url=SPEC_URL', "The url of the js spec server, relative to the browsers running via the Selenium Server") do |spec_url|
40
+ params[:spec_url] = spec_url
41
+ end
42
+
43
+ o.on_tail
44
+ end
45
+ parser.order!(argv)
46
+ run params
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,5 @@
1
+ Rack::Response.class_eval do
2
+ def ready?
3
+ status ? true : false
4
+ end
5
+ end
@@ -2,10 +2,10 @@ module JsSpec
2
2
  class RailsServer < Server
3
3
  class << self
4
4
  def run(rails_root, server_options = {})
5
- server_options[:Host] ||= Server::DEFAULT_HOST
6
- server_options[:Port] ||= Server::DEFAULT_PORT
5
+ server_options[:Host] ||= DEFAULT_HOST
6
+ server_options[:Port] ||= DEFAULT_PORT
7
7
  Server.instance = new(rails_root, server_options[:Host], server_options[:Port])
8
- Rack::Handler::Mongrel.run(Server.instance, server_options)
8
+ Server.instance.run server_options
9
9
  end
10
10
  end
11
11
 
@@ -1,60 +1,60 @@
1
1
  module JsSpec
2
2
  module Resources
3
3
  class Dir < File
4
- def locate(name)
5
- if file = file(name)
6
- file
7
- else
8
- locate_spec_runner(name)
4
+ def locate(name)
5
+ if file = file(name)
6
+ file
7
+ else
8
+ locate_spec_runner(name)
9
+ end
9
10
  end
10
- end
11
11
 
12
- def get
13
- SpecDirRunner.new(self).get
14
- end
12
+ def get(request, response)
13
+ SpecDirRunner.new(self).get(request, response)
14
+ end
15
15
 
16
- def glob(pattern)
17
- expanded_pattern = absolute_path + pattern
18
- ::Dir.glob(expanded_pattern).map do |absolute_globbed_path|
19
- relative_globbed_path = absolute_globbed_path.gsub(absolute_path, relative_path)
20
- File.new(absolute_globbed_path, relative_globbed_path)
16
+ def glob(pattern)
17
+ expanded_pattern = absolute_path + pattern
18
+ ::Dir.glob(expanded_pattern).map do |absolute_globbed_path|
19
+ relative_globbed_path = absolute_globbed_path.gsub(absolute_path, relative_path)
20
+ File.new(absolute_globbed_path, relative_globbed_path)
21
+ end
21
22
  end
22
- end
23
23
 
24
- protected
25
- def determine_child_paths(name)
26
- absolute_child_path = "#{absolute_path}/#{name}"
27
- relative_child_path = "#{relative_path}/#{name}"
28
- [absolute_child_path, relative_child_path]
29
- end
24
+ protected
25
+ def determine_child_paths(name)
26
+ absolute_child_path = "#{absolute_path}/#{name}"
27
+ relative_child_path = "#{relative_path}/#{name}"
28
+ [absolute_child_path, relative_child_path]
29
+ end
30
30
 
31
- def locate_spec_runner(name)
32
- if subdir = subdir(name)
33
- subdir
34
- elsif file = file(name + '.js')
35
- SpecFileRunner.new(file)
36
- else
37
- raise "No specs found at #{relative_path}/#{name}."
31
+ def locate_spec_runner(name)
32
+ if subdir = subdir(name)
33
+ subdir
34
+ elsif file = file(name + '.js')
35
+ SpecFileRunner.new(file)
36
+ else
37
+ raise "No specs found at #{relative_path}/#{name}."
38
+ end
38
39
  end
39
- end
40
40
 
41
- def file(name)
42
- absolute_file_path, relative_file_path = determine_child_paths(name)
43
- if ::File.exists?(absolute_file_path) && !::File.directory?(absolute_file_path)
44
- Resources::File.new(absolute_file_path, relative_file_path)
45
- else
46
- nil
41
+ def file(name)
42
+ absolute_file_path, relative_file_path = determine_child_paths(name)
43
+ if ::File.exists?(absolute_file_path) && !::File.directory?(absolute_file_path)
44
+ Resources::File.new(absolute_file_path, relative_file_path)
45
+ else
46
+ nil
47
+ end
47
48
  end
48
- end
49
49
 
50
- def subdir(name)
51
- absolute_dir_path, relative_dir_path = determine_child_paths(name)
52
- if ::File.directory?(absolute_dir_path)
53
- Resources::Dir.new(absolute_dir_path, relative_dir_path)
54
- else
55
- nil
50
+ def subdir(name)
51
+ absolute_dir_path, relative_dir_path = determine_child_paths(name)
52
+ if ::File.directory?(absolute_dir_path)
53
+ Resources::Dir.new(absolute_dir_path, relative_dir_path)
54
+ else
55
+ nil
56
+ end
56
57
  end
57
58
  end
58
59
  end
59
- end
60
60
  end
@@ -1,32 +1,32 @@
1
1
  module JsSpec
2
2
  module Resources
3
3
  class File
4
- MIME_TYPES = {
4
+ MIME_TYPES = {
5
5
  '.js' => 'text/javascript',
6
6
  '.css' => 'text/css',
7
7
  '.png' => 'image/png',
8
8
  '.jpg' => 'image/jpeg',
9
9
  '.jpeg' => 'image/jpeg',
10
10
  '.gif' => 'image/gif',
11
- }
11
+ }
12
12
 
13
- attr_reader :absolute_path, :relative_path
13
+ attr_reader :absolute_path, :relative_path
14
14
 
15
- def initialize(absolute_path, relative_path)
16
- @absolute_path = absolute_path
17
- @relative_path = relative_path
18
- end
15
+ def initialize(absolute_path, relative_path)
16
+ @absolute_path = absolute_path
17
+ @relative_path = relative_path
18
+ end
19
19
 
20
- def get
21
- extension = ::File.extname(absolute_path)
22
- Server.response.headers['Content-Type'] = MIME_TYPES[extension] || 'text/html'
23
- ::File.read(absolute_path)
24
- end
20
+ def get(request, response)
21
+ extension = ::File.extname(absolute_path)
22
+ response.headers['Content-Type'] = MIME_TYPES[extension] || 'text/html'
23
+ response.body = ::File.read(absolute_path)
24
+ end
25
25
 
26
- def ==(other)
27
- return false unless other.class == self.class
28
- absolute_path == other.absolute_path && relative_path == other.relative_path
26
+ def ==(other)
27
+ return false unless other.class == self.class
28
+ absolute_path == other.absolute_path && relative_path == other.relative_path
29
+ end
29
30
  end
30
31
  end
31
- end
32
32
  end
@@ -0,0 +1,8 @@
1
+ module JsSpec
2
+ module Resources
3
+ class Runners
4
+ class Firefox1Runner < FirefoxRunner
5
+ end
6
+ end
7
+ end
8
+ end
@@ -1,90 +1,67 @@
1
1
  module JsSpec
2
2
  module Resources
3
3
  class Runners
4
- class FirefoxRunner
5
- class << self
6
- def resume(guid, text)
7
- responses[guid] = text
8
- threads[guid].kill
9
- end
10
-
11
- def threads
12
- @threads ||= {}
13
- end
4
+ class FirefoxRunner
5
+ class << self
6
+ def resume(guid, text)
7
+ runner = instances.delete(guid)
8
+ runner.finalize(text)
9
+ end
14
10
 
15
- def response_value(guid)
16
- responses[guid]
17
- ensure
18
- responses.delete guid
19
- end
11
+ def register_instance(runner)
12
+ instances[runner.guid] = runner
13
+ end
20
14
 
21
- protected
22
- def responses
23
- @responses ||= {}
15
+ protected
16
+ def instances
17
+ @instances ||= {}
18
+ end
24
19
  end
25
- end
26
20
 
27
- include FileUtils
28
- attr_reader :profile_dir
29
-
30
- def initialize
31
- profile_base = "#{::Dir.tmpdir}/js_spec/firefox"
32
- mkdir_p profile_base
33
- @profile_dir = "#{profile_base}/#{Time.now.to_i}"
34
- end
35
-
36
- def post
37
- copy_profile_files
38
- create_profile
39
- wait_for_full_profile_to_be_created
40
-
41
- guid = UUID.new
42
- start_browser guid
43
- thread = Thread.start {sleep}
44
- self.class.threads[guid] = thread
45
- thread.join
46
-
47
- self.class.response_value guid
48
- end
21
+ include FileUtils
22
+ attr_reader :guid, :profile_dir, :request, :response, :connection, :driver
49
23
 
50
- protected
51
- def copy_profile_files
52
- dir = ::File.dirname(__FILE__)
53
- firefox_profile_path = ::File.expand_path("#{dir}/../../../../resources/firefox")
54
- system("cp -R #{firefox_profile_path} #{profile_dir}") || raise("Copying Firefox profile failed")
55
- end
56
-
57
- def create_profile
58
- system("firefox -profile #{profile_dir} -chrome chrome://killff/content/kill.html") || raise("Initializing profile failed")
59
- end
60
-
61
- def wait_for_full_profile_to_be_created
62
- Timeout.timeout(5) do
63
- wait_for_file_lock_to_go_away
24
+ def initialize(request, response)
25
+ profile_base = "#{::Dir.tmpdir}/js_spec/firefox"
26
+ mkdir_p profile_base
27
+ @profile_dir = "#{profile_base}/#{Time.now.to_i}"
28
+ @guid = UUID.new
29
+ @request = request
30
+ @response = response
31
+ @connection = Server.connection
64
32
  end
65
- end
66
33
 
67
- def start_browser(guid)
68
- Thread.start do
69
- system("firefox -profile #{profile_dir} http://localhost:8080/specs?guid=#{guid}") || raise("Starting Firefox failed")
34
+ def post(request, response)
35
+ FirefoxRunner.register_instance self
36
+ spec_url = (request && request['spec_url']) ? request['spec_url'] : spec_suite_url
37
+ parsed_spec_url = URI.parse(spec_url)
38
+ @driver = Selenium::SeleniumDriver.new(
39
+ request['selenium_host'] || 'localhost',
40
+ (request['selenium_port'] || 4444).to_i,
41
+ '*firefox',
42
+ "#{parsed_spec_url.scheme}://#{parsed_spec_url.host}:#{parsed_spec_url.port}"
43
+ )
44
+ driver.start
45
+ Thread.start do
46
+ url = "#{spec_url}?guid=#{guid}"
47
+ driver.open(url)
48
+ end
49
+ response.status = nil
70
50
  end
71
- end
72
51
 
73
- def wait_for_file_lock_to_go_away
74
- loop do
75
- return if lock_is_gone? && lock_remains_gone?
52
+ def finalize(text)
53
+ driver.stop
54
+ response.status = 200
55
+ response.body = text
56
+ connection.send_response(*response.finish)
76
57
  end
77
- end
78
58
 
79
- def lock_is_gone?
80
- !::File.exists?("#{profile_dir}/parent.lock")
81
- end
59
+ protected
82
60
 
83
- def lock_remains_gone?
84
- sleep 0.5
85
- lock_is_gone?
61
+ def spec_suite_url
62
+ "#{Server.root_url}/specs"
63
+ end
86
64
  end
87
65
  end
88
66
  end
89
- end
90
- end
67
+ end
@@ -1,16 +1,15 @@
1
1
  dir = File.dirname(__FILE__)
2
- require File.expand_path("#{dir}/runners/firefox_runner")
3
2
 
4
3
  module JsSpec
5
4
  module Resources
6
5
  class Runners
7
- def locate(name)
8
- if name == 'firefox'
9
- FirefoxRunner.new
10
- else
11
- raise "Invalid path #{name}"
6
+ def locate(name)
7
+ if name == 'firefox'
8
+ FirefoxRunner.new(Server.request, Server.response)
9
+ else
10
+ raise "Invalid path #{name}"
11
+ end
12
12
  end
13
13
  end
14
14
  end
15
- end
16
15
  end
@@ -1,15 +1,15 @@
1
1
  module JsSpec
2
2
  module Resources
3
3
  class SpecDirRunner < SpecRunner
4
- attr_reader :dir
4
+ attr_reader :dir
5
5
 
6
- def initialize(dir)
7
- @dir = dir
8
- end
6
+ def initialize(dir)
7
+ @dir = dir
8
+ end
9
9
 
10
- def spec_files
11
- dir.glob("/**/*_spec.js")
10
+ def spec_files
11
+ dir.glob("/**/*_spec.js")
12
+ end
12
13
  end
13
14
  end
14
- end
15
15
  end
@@ -1,15 +1,15 @@
1
1
  module JsSpec
2
2
  module Resources
3
3
  class SpecFileRunner < SpecRunner
4
- attr_reader :file
4
+ attr_reader :file
5
5
 
6
- def initialize(file)
7
- @file = file
8
- end
6
+ def initialize(file)
7
+ @file = file
8
+ end
9
9
 
10
- def spec_files
11
- [file]
10
+ def spec_files
11
+ [file]
12
+ end
12
13
  end
13
14
  end
14
- end
15
15
  end
@@ -1,8 +1,9 @@
1
1
  module JsSpec
2
2
  module Resources
3
3
  class SpecRunner
4
- def get
5
- html = <<-HTML
4
+ def get(request, response)
5
+ guid = (request && request['guid']) || 'null';
6
+ html = <<-HTML
6
7
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
7
8
  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ko">
8
9
  <head>
@@ -11,19 +12,23 @@ module JsSpec
11
12
  <link rel="stylesheet" type="text/css" href="/core/JSSpec.css" />
12
13
  <script type="text/javascript" src="/core/diff_match_patch.js"></script>
13
14
  <script type="text/javascript" src="/core/JSSpec.js"></script>
15
+ <script type="text/javascript" src="/core/jquery.js"></script>
14
16
  <script type="text/javascript" src="/core/JSSpecExtensions.js"></script>
15
- HTML
16
- spec_files.each do |file|
17
- html << %{<script type="text/javascript" src="#{file.relative_path}"></script>\n}
18
- end
17
+ HTML
18
+ spec_files.each do |file|
19
+ html << %{<script type="text/javascript" src="#{file.relative_path}"></script>\n}
20
+ end
19
21
 
20
- html << <<-HTML
22
+ html << <<-HTML
23
+ <script type="text/javascript">
24
+ JSSpec.guid = '#{guid}';
25
+ </script>
21
26
  </head>
22
27
  <body></body>
23
28
  </html>
24
- HTML
25
- html.gsub(/^ /, "")
29
+ HTML
30
+ response.body = html.gsub(/^ /, "")
31
+ end
26
32
  end
27
33
  end
28
- end
29
- end
34
+ end
@@ -1,24 +1,24 @@
1
1
  module JsSpec
2
2
  module Resources
3
3
  class Suite
4
- class << self
5
- def locate(id)
6
- new id
4
+ class << self
5
+ def locate(id)
6
+ new id
7
+ end
7
8
  end
8
- end
9
9
 
10
- attr_reader :id
11
- def initialize(id)
12
- @id = id
13
- end
10
+ attr_reader :id
11
+ def initialize(id)
12
+ @id = id
13
+ end
14
14
 
15
- def locate(name)
16
- if name == 'finish'
17
- SuiteFinish.new self
18
- else
19
- raise ArgumentError, "Invalid path: #{name}"
15
+ def locate(name)
16
+ if name == 'finish'
17
+ SuiteFinish.new self
18
+ else
19
+ raise ArgumentError, "Invalid path: #{name}"
20
+ end
20
21
  end
21
22
  end
22
23
  end
23
- end
24
24
  end
@@ -1,20 +1,20 @@
1
1
  module JsSpec
2
2
  module Resources
3
3
  class SuiteFinish
4
- attr_reader :suite
5
- def initialize(suite)
6
- @suite = suite
7
- end
4
+ attr_reader :suite
5
+ def initialize(suite)
6
+ @suite = suite
7
+ end
8
8
 
9
- def post
10
- guid = Server.request['guid']
11
- if guid
12
- Runners::FirefoxRunner.resume(guid, Server.request['text'])
13
- else
14
- STDOUT.puts Server.request['text']
9
+ def post(request, response)
10
+ guid = request['guid']
11
+ if guid
12
+ Runners::FirefoxRunner.resume(guid, request['text'])
13
+ else
14
+ STDOUT.puts request['text']
15
+ end
16
+ ""
15
17
  end
16
- ""
17
18
  end
18
19
  end
19
- end
20
20
  end
@@ -1,8 +1,5 @@
1
1
  module JsSpec
2
2
  class Server
3
- DEFAULT_HOST = "127.0.0.1"
4
- DEFAULT_PORT = 8080
5
-
6
3
  class << self
7
4
  attr_accessor :instance
8
5
 
@@ -10,19 +7,17 @@ module JsSpec
10
7
  server_options[:Host] ||= DEFAULT_HOST
11
8
  server_options[:Port] ||= DEFAULT_PORT
12
9
  @instance = new(spec_root_path, implementation_root_path, public_path, server_options[:Host], server_options[:Port])
13
- Rack::Handler::Mongrel.run(instance, server_options)
14
- end
15
-
16
- def default_url
17
- "http://#{DEFAULT_HOST}:#{DEFAULT_PORT}"
10
+ instance.run server_options
18
11
  end
19
12
 
20
13
  def spec_root_path; instance.spec_root_path; end
21
14
  def implementation_root_path; instance.implementation_root_path; end
22
15
  def public_path; instance.public_path; end
23
16
  def core_path; instance.core_path; end
17
+ def connection; instance.connection; end
24
18
  def request; instance.request; end
25
19
  def response; instance.response; end
20
+ def root_url; instance.root_url; end
26
21
  end
27
22
 
28
23
  attr_reader :host, :port, :spec_root_path, :implementation_root_path, :core_path, :public_path
@@ -36,21 +31,30 @@ module JsSpec
36
31
  @host = host
37
32
  @port = port
38
33
  end
39
-
34
+
35
+ def run(options)
36
+ ::Thin::Server.start(
37
+ ::Thin::Backends::JsSpecServer.new(options[:Host], options[:Port]),
38
+ options[:Port],
39
+ self
40
+ )
41
+ end
42
+
40
43
  def call(env)
44
+ self.connection = env['js_spec.connection']
41
45
  self.request = Rack::Request.new(env)
42
46
  self.response = Rack::Response.new
43
47
  method = request.request_method.downcase.to_sym
44
- response.body = get_resource.send(method)
48
+ get_resource(request).send(method, request, response)
45
49
  response.finish
46
50
  ensure
51
+ self.connection = nil
47
52
  self.request = nil
48
53
  self.response = nil
49
54
  end
50
-
51
- def run(path)
52
- system(%{firefox "http://#{host}:#{port}/#{path}"})
53
- Suite.new
55
+
56
+ def connection
57
+ Thread.current[:connection]
54
58
  end
55
59
 
56
60
  def request
@@ -60,8 +64,16 @@ module JsSpec
60
64
  def response
61
65
  Thread.current[:response]
62
66
  end
67
+
68
+ def root_url
69
+ "http://#{host}:#{port}"
70
+ end
63
71
 
64
72
  protected
73
+ def connection=(connection)
74
+ Thread.current[:connection] = connection
75
+ end
76
+
65
77
  def request=(request)
66
78
  Thread.current[:request] = request
67
79
  end
@@ -70,12 +82,12 @@ module JsSpec
70
82
  Thread.current[:response] = response
71
83
  end
72
84
 
73
- def path_parts
85
+ def path_parts(req)
74
86
  request.path_info.split('/').reject { |part| part == "" }
75
87
  end
76
88
 
77
- def get_resource
78
- path_parts.inject(Resources::WebRoot.new(public_path)) do |resource, child_resource_name|
89
+ def get_resource(request)
90
+ path_parts(request).inject(Resources::WebRoot.new(public_path)) do |resource, child_resource_name|
79
91
  resource.locate(child_resource_name)
80
92
  end
81
93
  rescue Exception => e