harbor 0.16.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.
- data/Rakefile +76 -0
- data/bin/harbor +7 -0
- data/lib/harbor.rb +17 -0
- data/lib/harbor/accessor_injector.rb +30 -0
- data/lib/harbor/application.rb +172 -0
- data/lib/harbor/auth/basic.rb +51 -0
- data/lib/harbor/block_io.rb +63 -0
- data/lib/harbor/cache.rb +90 -0
- data/lib/harbor/cache/disk.rb +99 -0
- data/lib/harbor/cache/item.rb +48 -0
- data/lib/harbor/cache/memory.rb +35 -0
- data/lib/harbor/cascade.rb +75 -0
- data/lib/harbor/console.rb +34 -0
- data/lib/harbor/container.rb +134 -0
- data/lib/harbor/contrib/debug.rb +236 -0
- data/lib/harbor/contrib/session/data_mapper.rb +74 -0
- data/lib/harbor/daemon.rb +105 -0
- data/lib/harbor/errors.rb +49 -0
- data/lib/harbor/events.rb +45 -0
- data/lib/harbor/exception_notifier.rb +59 -0
- data/lib/harbor/file.rb +66 -0
- data/lib/harbor/file_store.rb +69 -0
- data/lib/harbor/file_store/file.rb +100 -0
- data/lib/harbor/file_store/local.rb +71 -0
- data/lib/harbor/file_store/mosso.rb +154 -0
- data/lib/harbor/file_store/mosso/private.rb +8 -0
- data/lib/harbor/generator.rb +56 -0
- data/lib/harbor/generator/help.rb +34 -0
- data/lib/harbor/generator/setup.rb +82 -0
- data/lib/harbor/generator/skeletons/basic/config.ru.skel +21 -0
- data/lib/harbor/generator/skeletons/basic/lib/appname.rb.skel +49 -0
- data/lib/harbor/generator/skeletons/basic/lib/appname/controllers/home.rb +9 -0
- data/lib/harbor/generator/skeletons/basic/lib/appname/views/home/index.html.erb.skel +23 -0
- data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/application.html.erb.skel +48 -0
- data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/exception.html.erb.skel +13 -0
- data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/login.html.erb.skel +11 -0
- data/lib/harbor/generator/skeletons/basic/log/development.log +0 -0
- data/lib/harbor/hooks.rb +105 -0
- data/lib/harbor/json_cookies.rb +37 -0
- data/lib/harbor/layouts.rb +61 -0
- data/lib/harbor/locale.rb +50 -0
- data/lib/harbor/locales.txt +22 -0
- data/lib/harbor/logging.rb +39 -0
- data/lib/harbor/logging/appenders/email.rb +84 -0
- data/lib/harbor/logging/request_logger.rb +34 -0
- data/lib/harbor/mail_servers/abstract.rb +12 -0
- data/lib/harbor/mail_servers/sendmail.rb +19 -0
- data/lib/harbor/mail_servers/smtp.rb +25 -0
- data/lib/harbor/mail_servers/test.rb +17 -0
- data/lib/harbor/mailer.rb +96 -0
- data/lib/harbor/messages.rb +17 -0
- data/lib/harbor/mime.rb +206 -0
- data/lib/harbor/plugin.rb +52 -0
- data/lib/harbor/plugin_list.rb +38 -0
- data/lib/harbor/request.rb +138 -0
- data/lib/harbor/response.rb +281 -0
- data/lib/harbor/router.rb +112 -0
- data/lib/harbor/script.rb +155 -0
- data/lib/harbor/session.rb +75 -0
- data/lib/harbor/session/abstract.rb +27 -0
- data/lib/harbor/session/cookie.rb +17 -0
- data/lib/harbor/shellwords.rb +26 -0
- data/lib/harbor/test/mailer.rb +10 -0
- data/lib/harbor/test/request.rb +28 -0
- data/lib/harbor/test/response.rb +17 -0
- data/lib/harbor/test/session.rb +11 -0
- data/lib/harbor/test/test.rb +22 -0
- data/lib/harbor/version.rb +3 -0
- data/lib/harbor/view.rb +89 -0
- data/lib/harbor/view_context.rb +134 -0
- data/lib/harbor/view_context/helpers.rb +7 -0
- data/lib/harbor/view_context/helpers/cache.rb +77 -0
- data/lib/harbor/view_context/helpers/form.rb +34 -0
- data/lib/harbor/view_context/helpers/html.rb +26 -0
- data/lib/harbor/view_context/helpers/text.rb +120 -0
- data/lib/harbor/view_context/helpers/url.rb +11 -0
- data/lib/harbor/xml_view.rb +57 -0
- data/lib/harbor/zipped_io.rb +203 -0
- data/lib/vendor/cloudfiles-1.3.0/cloudfiles.rb +77 -0
- data/lib/vendor/cloudfiles-1.3.0/cloudfiles/authentication.rb +46 -0
- data/lib/vendor/cloudfiles-1.3.0/cloudfiles/connection.rb +280 -0
- data/lib/vendor/cloudfiles-1.3.0/cloudfiles/container.rb +260 -0
- data/lib/vendor/cloudfiles-1.3.0/cloudfiles/storage_object.rb +253 -0
- metadata +155 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
require Pathname(__FILE__).dirname.parent + "cache"
|
2
|
+
|
3
|
+
class Harbor::Cache::Disk
|
4
|
+
|
5
|
+
def initialize(path)
|
6
|
+
@path = path.is_a?(Pathname) ? path : Pathname(path)
|
7
|
+
|
8
|
+
FileUtils.mkdir_p(@path) unless ::File.directory?(@path.to_s)
|
9
|
+
end
|
10
|
+
|
11
|
+
def get(key)
|
12
|
+
if (path = filename_for_key(key))
|
13
|
+
item_for_path(path)
|
14
|
+
else
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
alias [] get
|
20
|
+
|
21
|
+
def put(key, ttl, maximum_age, content, cached_at)
|
22
|
+
item = Harbor::Cache::Item.new(key, ttl, maximum_age, content, cached_at)
|
23
|
+
|
24
|
+
FileUtils.rm(filenames_for_key(key))
|
25
|
+
|
26
|
+
::File.open(path_for_item(item), 'w') do |file|
|
27
|
+
file.write(content)
|
28
|
+
end
|
29
|
+
|
30
|
+
item
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete(key)
|
34
|
+
if (path = filename_for_key(key))
|
35
|
+
FileUtils.rm(path) rescue nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete_matching(key)
|
40
|
+
filenames_for_key(key).each do |path|
|
41
|
+
FileUtils.rm(path) rescue nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def bump(key)
|
46
|
+
if item = get(key)
|
47
|
+
current_path = path_for_item(item)
|
48
|
+
|
49
|
+
item.bump
|
50
|
+
|
51
|
+
new_path = path_for_item(item)
|
52
|
+
FileUtils.mv(current_path, new_path) unless current_path == new_path
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def filenames_for_key(key)
|
59
|
+
if key.is_a?(Regexp)
|
60
|
+
Dir[@path + "*"].select { |path| path[/.*?__INFO__/] =~ key }
|
61
|
+
else
|
62
|
+
Dir[@path + "c_#{key}.*"]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def filename_for_key(key)
|
67
|
+
filenames_for_key(key).first
|
68
|
+
end
|
69
|
+
|
70
|
+
def item_for_path(path)
|
71
|
+
components = ::File.basename(path).split('.')
|
72
|
+
|
73
|
+
return nil if components.size < 6
|
74
|
+
|
75
|
+
expires_at = Time.parse(components.pop)
|
76
|
+
cached_at = Time.parse(components.pop)
|
77
|
+
|
78
|
+
if (maximum_age = components.pop).size > 0
|
79
|
+
maximum_age = maximum_age.to_i
|
80
|
+
else
|
81
|
+
maximum_age = nil
|
82
|
+
end
|
83
|
+
|
84
|
+
if (ttl = components.pop).size > 0
|
85
|
+
ttl = ttl.to_i
|
86
|
+
else
|
87
|
+
ttl = nil
|
88
|
+
end
|
89
|
+
|
90
|
+
key = components.reject { |c| c == "__INFO__" }.join('.').sub(/^c\_/, '')
|
91
|
+
|
92
|
+
Harbor::Cache::Item.new(key, ttl, maximum_age, Pathname(path), cached_at, expires_at)
|
93
|
+
end
|
94
|
+
|
95
|
+
def path_for_item(item)
|
96
|
+
@path + "c_#{item.key}.__INFO__.#{item.ttl}.#{item.maximum_age}.#{item.cached_at.strftime('%Y%m%dT%H%M%S%Z')}.#{item.expires_at.strftime('%Y%m%dT%H%M%S%Z')}"
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class Harbor::Cache
|
2
|
+
|
3
|
+
class Item
|
4
|
+
|
5
|
+
attr_reader :key, :ttl, :expires_at, :cached_at, :content, :maximum_age, :ultimate_expiration_time
|
6
|
+
|
7
|
+
def initialize(key, ttl, maximum_age, string_or_io, cached_at, expires_at = nil)
|
8
|
+
raise ArgumentError.new("Harbor::Cache::Item#initialize expects a String value for 'key', got #{key}") unless key.is_a?(String)
|
9
|
+
raise ArgumentError.new("Harbor::Cache::Item#initialize expects a Fixnum value greater than 0 for 'ttl', got #{ttl}") unless ttl.is_a?(Fixnum) && ttl > 0
|
10
|
+
raise ArgumentError.new("Harbor::Cache::Item#initialize expects nil, or a Fixnum value greater than 0 for 'maximum_age', got #{maximum_age}") unless maximum_age.nil? || (maximum_age.is_a?(Fixnum) && maximum_age > 0)
|
11
|
+
raise ArgumentError.new("Harbor::Cache::Item#initialize expects a Time value for 'cached_at', got #{cached_at}") unless cached_at.is_a?(Time)
|
12
|
+
raise ArgumentError.new("Harbor::Cache::Item#initialize expects a maximum_age greater than the ttl, got ttl: #{ttl}, maximum_age: #{maximum_age}") if maximum_age && ttl && (maximum_age <= ttl)
|
13
|
+
|
14
|
+
@key = key
|
15
|
+
@ttl = ttl
|
16
|
+
@maximum_age = maximum_age
|
17
|
+
@cached_at = cached_at
|
18
|
+
@expires_at = expires_at || (cached_at + ttl)
|
19
|
+
@ultimate_expiration_time = (maximum_age ? cached_at + maximum_age : nil)
|
20
|
+
|
21
|
+
if string_or_io.respond_to?(:read)
|
22
|
+
@io = string_or_io
|
23
|
+
else
|
24
|
+
@content = string_or_io
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def content
|
29
|
+
@content ||= @io.read
|
30
|
+
end
|
31
|
+
|
32
|
+
def fresh?
|
33
|
+
Time.now < expires_at
|
34
|
+
end
|
35
|
+
|
36
|
+
def expired?
|
37
|
+
Time.now >= expires_at
|
38
|
+
end
|
39
|
+
|
40
|
+
def bump
|
41
|
+
if @ultimate_expiration_time
|
42
|
+
@expires_at = [Time.now + ttl, @ultimate_expiration_time].min
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require Pathname(__FILE__).dirname.parent + "cache"
|
2
|
+
|
3
|
+
class Harbor::Cache::Memory
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@cache = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def put(key, ttl, maximum_age, content, cached_at)
|
10
|
+
@cache[key] = Harbor::Cache::Item.new(key, ttl, maximum_age, content, cached_at)
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(key)
|
14
|
+
@cache[key]
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](key)
|
18
|
+
@cache[key]
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete(key)
|
22
|
+
@cache.delete(key)
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete_matching(key_regex)
|
26
|
+
@cache.reject! { |key, value| key =~ key_regex }
|
27
|
+
end
|
28
|
+
|
29
|
+
def bump(key)
|
30
|
+
if item = @cache[key]
|
31
|
+
item.bump
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Harbor
|
2
|
+
class Cascade
|
3
|
+
|
4
|
+
def initialize(environment, services, application, *ports)
|
5
|
+
unless services.is_a?(Harbor::Container)
|
6
|
+
raise ArgumentError.new("Harbor::Cascade#initialize[services] must be a Harbor::Container")
|
7
|
+
end
|
8
|
+
|
9
|
+
@services = services
|
10
|
+
@applications = []
|
11
|
+
@public_paths = []
|
12
|
+
|
13
|
+
begin
|
14
|
+
@applications << application.new(services, environment)
|
15
|
+
rescue ArgumentError => e
|
16
|
+
raise ArgumentError.new("#{application}: #{e.message}")
|
17
|
+
end
|
18
|
+
|
19
|
+
@public_paths << Pathname(application.public_path) if application.respond_to?(:public_path)
|
20
|
+
|
21
|
+
ports.each do |port|
|
22
|
+
begin
|
23
|
+
@applications << port.new(services, environment)
|
24
|
+
rescue ArgumentError => e
|
25
|
+
raise ArgumentError.new("#{port}: #{e.message}")
|
26
|
+
end
|
27
|
+
|
28
|
+
@public_paths << Pathname(port.public_path) if port.respond_to?(:public_path)
|
29
|
+
end
|
30
|
+
|
31
|
+
@public_paths << Pathname("public")
|
32
|
+
end
|
33
|
+
|
34
|
+
def call(env)
|
35
|
+
request = Request.new(self, env)
|
36
|
+
response = Response.new(request)
|
37
|
+
|
38
|
+
catch(:abort_request) do
|
39
|
+
if file = find_public_file(request.path_info[1..-1])
|
40
|
+
response.cache(nil, ::File.mtime(file), 86400) do
|
41
|
+
response.stream_file(file)
|
42
|
+
end
|
43
|
+
return response.to_a
|
44
|
+
end
|
45
|
+
|
46
|
+
application, handler = nil
|
47
|
+
|
48
|
+
@applications.each do |application|
|
49
|
+
break if handler = application.router.match(request)
|
50
|
+
end
|
51
|
+
|
52
|
+
application = @applications.first unless handler
|
53
|
+
request.application = application
|
54
|
+
|
55
|
+
application.dispatch_request(handler, request, response)
|
56
|
+
end
|
57
|
+
|
58
|
+
response.to_a
|
59
|
+
end
|
60
|
+
|
61
|
+
def find_public_file(file)
|
62
|
+
result = nil
|
63
|
+
|
64
|
+
@public_paths.each do |public_path|
|
65
|
+
if (path = public_path + file).file?
|
66
|
+
result = path
|
67
|
+
break
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
result
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Harbor
|
2
|
+
##
|
3
|
+
# Heplful to include in your config.ru for getting an interative IRB
|
4
|
+
# session.
|
5
|
+
#
|
6
|
+
# #!/usr/bin/env ruby
|
7
|
+
# # require app, set up ORM, etc.
|
8
|
+
#
|
9
|
+
# if $0 == __FILE__
|
10
|
+
# Harbor::Console.start
|
11
|
+
# else
|
12
|
+
# run MyApplication
|
13
|
+
# end
|
14
|
+
##
|
15
|
+
class Console
|
16
|
+
|
17
|
+
def self.start
|
18
|
+
require 'irb'
|
19
|
+
|
20
|
+
begin
|
21
|
+
require 'irb/completion'
|
22
|
+
rescue Exception
|
23
|
+
# No readline available, proceed anyway.
|
24
|
+
end
|
25
|
+
|
26
|
+
if ::File.exists? ".irbrc"
|
27
|
+
ENV['IRBRC'] = ".irbrc"
|
28
|
+
end
|
29
|
+
|
30
|
+
IRB.start
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Harbor
|
4
|
+
##
|
5
|
+
# Harbor::Container is an inversion of control container for simple
|
6
|
+
# dependency injection. For more information on dependency injection, see
|
7
|
+
# http://martinfowler.com/articles/injection.html.
|
8
|
+
#
|
9
|
+
# Simple Example:
|
10
|
+
#
|
11
|
+
# services = Harbor::Container.new
|
12
|
+
# services.register("mailer", Harbor::Mailer)
|
13
|
+
#
|
14
|
+
# class Controller
|
15
|
+
# attr_accessor :mailer
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# services.register("Controller", Controller)
|
19
|
+
#
|
20
|
+
# services.get("Controller") # => #<Controller: @mailer=#<Mailer>>
|
21
|
+
##
|
22
|
+
class Container
|
23
|
+
|
24
|
+
class RegistrationTypeMismatchError < StandardError
|
25
|
+
def initialize(registration_name, previously_registered_type, mismatched_type)
|
26
|
+
super(<<-EOS.split($/).join(' '))
|
27
|
+
"#{registration_name}" has already been registered as a #{previously_registered_type.inspect}
|
28
|
+
but a modification of Type to #{mismatched_type.inspect} was attempted.
|
29
|
+
EOS
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class ServiceRegistration
|
34
|
+
|
35
|
+
attr_reader :name, :service, :initializers
|
36
|
+
|
37
|
+
def initialize(name, service)
|
38
|
+
@name, @service = name, service
|
39
|
+
@initializers = Set.new
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize #:nodoc:
|
45
|
+
@services = {}
|
46
|
+
@dependencies = {}
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Retrieve a service by name from the set of registered services, initializing
|
51
|
+
# any dependencies from the container, and optionally setting any additional
|
52
|
+
# properties on the service.
|
53
|
+
#
|
54
|
+
# class Controller
|
55
|
+
# attr_accessor :request, :response, :mailer
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# services.get("Controller", :request => Request.new(env), :response => Response.new(request))
|
59
|
+
##
|
60
|
+
def get(name, optional_properties = {})
|
61
|
+
raise ArgumentError.new("#{name} is not a registered service name") unless registered?(name)
|
62
|
+
service_registration = @services[name]
|
63
|
+
service = service_registration.service.is_a?(Class) ? service_registration.service.new : service_registration.service
|
64
|
+
|
65
|
+
dependencies(name).each do |dependency|
|
66
|
+
service.send("#{dependency}=", get(dependency, optional_properties))
|
67
|
+
end
|
68
|
+
|
69
|
+
optional_instances = {}
|
70
|
+
optional_properties.each_pair do |k,v|
|
71
|
+
instance = v.is_a?(Class) ? v.new : v
|
72
|
+
optional_instances[k] = instance
|
73
|
+
end
|
74
|
+
|
75
|
+
optional_instances.each_pair do |k, v|
|
76
|
+
writer = "#{k}="
|
77
|
+
service.send(writer, v) if service.respond_to?(writer)
|
78
|
+
optional_instances.each_pair do |k2,v2|
|
79
|
+
next if k2 == k || !v2.respond_to?(writer)
|
80
|
+
v2.send(writer, v)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
service_registration.initializers.each do |initializer|
|
85
|
+
initializer.call(service)
|
86
|
+
end
|
87
|
+
|
88
|
+
service
|
89
|
+
end
|
90
|
+
|
91
|
+
##
|
92
|
+
# Register a service by name, with an optional initializer block.
|
93
|
+
#
|
94
|
+
# services.register("mail_server", Harbor::SendmailServer.new(:sendmail => "/sbin/sendmail"))
|
95
|
+
# services.register("mailer", Harbor::Mailer)
|
96
|
+
# services.get("mailer") # => #<Harbor::Mailer @from=nil @mail_server=#<SendmailServer...>>
|
97
|
+
#
|
98
|
+
# services.register("mailer", Harbor::Mailer) { |mailer| mailer.from = "admin@example.com" }
|
99
|
+
# services.get("mailer") # => #<Harbor::Mailer @from="admin@example.com" @mail_server=#<SendmailServer...>>
|
100
|
+
##
|
101
|
+
def register(name, service, &setup)
|
102
|
+
if (existing_registration = @services[name]) && existing_registration.service != service
|
103
|
+
raise RegistrationTypeMismatchError.new(name, existing_registration.service, service)
|
104
|
+
end
|
105
|
+
|
106
|
+
type_dependencies = dependencies(name)
|
107
|
+
type_methods = service.is_a?(Class) ? service.instance_methods.grep(/\=$/) : []
|
108
|
+
|
109
|
+
@services.values.each do |service_registration|
|
110
|
+
if service_registration.service.is_a?(Class) && service_registration.service.instance_methods.include?("#{name}=")
|
111
|
+
dependencies(service_registration.name) << name
|
112
|
+
end
|
113
|
+
|
114
|
+
if type_methods.include?("#{service_registration.name}=")
|
115
|
+
type_dependencies << service_registration.name
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
@services[name] ||= ServiceRegistration.new(name, service)
|
120
|
+
@services[name].initializers << setup if setup
|
121
|
+
|
122
|
+
self
|
123
|
+
end
|
124
|
+
|
125
|
+
def registered?(name)
|
126
|
+
@services.key?(name)
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
def dependencies(service)
|
131
|
+
@dependencies[service] ||= Set.new
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,236 @@
|
|
1
|
+
module Harbor
|
2
|
+
module Contrib
|
3
|
+
##
|
4
|
+
# USAGE:
|
5
|
+
# Add the following code to your config.ru, just before 'run'
|
6
|
+
# if ENV['ENVIRONMENT'] == 'development'
|
7
|
+
# require "harbor/contrib/debug"
|
8
|
+
# DataObjects::Postgres.logger = Logging::Logger.root
|
9
|
+
# use Harbor::Contrib::Debug
|
10
|
+
# end
|
11
|
+
##
|
12
|
+
class Debug
|
13
|
+
def initialize(app)
|
14
|
+
@app = app
|
15
|
+
@levels = %w(DEBUG INFO WARN ERROR FATAL)
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
start_time = Time.now
|
20
|
+
status, headers, body = @app.call(env)
|
21
|
+
load_time = Time.now - start_time
|
22
|
+
|
23
|
+
appenders = Logging::Logger.root.instance_variable_get(:@appenders)
|
24
|
+
logger = appenders.find { |appender| appender.name == "harbor_debug_messages" }
|
25
|
+
messages = logger.messages.dup
|
26
|
+
logger.messages.clear
|
27
|
+
|
28
|
+
return [status, headers, body] unless (headers["Content-Type"] =~ /html/) && body.is_a?(String)
|
29
|
+
|
30
|
+
debugger = @@template.dup
|
31
|
+
|
32
|
+
if body["jquery"]
|
33
|
+
debugger.gsub!("{{jquery}}", "")
|
34
|
+
else
|
35
|
+
debugger.gsub!("{{jquery}}", '<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>')
|
36
|
+
end
|
37
|
+
|
38
|
+
debugger.gsub!("{{load_time}}", "%2.2f" % load_time)
|
39
|
+
|
40
|
+
queries = messages.map do |level, message|
|
41
|
+
("<p>[#{@levels[level]}] " + message.gsub("<", "<") + "</p>") if message =~ /^\(/
|
42
|
+
end.compact
|
43
|
+
|
44
|
+
messages = messages.map do |level, message|
|
45
|
+
("<p>[#{@levels[level]}] " + message.gsub("<", "<") + "</p>") if message !~ /^\(/
|
46
|
+
end.compact
|
47
|
+
|
48
|
+
debugger.gsub!("{{query_count}}", "%s" % queries.size)
|
49
|
+
debugger.gsub!("{{message_count}}", "%s" % messages.size)
|
50
|
+
|
51
|
+
if messages.any?
|
52
|
+
debugger.gsub!("{{messages}}", '<div class="messages" style="display: none">' + messages.join("\n") + '</div>')
|
53
|
+
else
|
54
|
+
debugger.gsub!("{{messages}}", "")
|
55
|
+
end
|
56
|
+
|
57
|
+
if queries.any?
|
58
|
+
debugger.gsub!("{{queries}}", '<div class="queries" style="display: none">' + queries.join("\n") + '</div>')
|
59
|
+
else
|
60
|
+
debugger.gsub!("{{queries}}", "")
|
61
|
+
end
|
62
|
+
|
63
|
+
body.gsub!("</body>", debugger + "</body>")
|
64
|
+
headers["Content-Length"] = body.length.to_s
|
65
|
+
|
66
|
+
[status, headers, body]
|
67
|
+
end
|
68
|
+
|
69
|
+
class LogAppender < Logging::Appender
|
70
|
+
def initialize
|
71
|
+
@messages = []
|
72
|
+
|
73
|
+
super("harbor_debug_messages", :level => :debug)
|
74
|
+
end
|
75
|
+
|
76
|
+
def messages
|
77
|
+
@messages
|
78
|
+
end
|
79
|
+
|
80
|
+
def write(event)
|
81
|
+
messages << [event.level, event.data]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
@@template = <<-HTML
|
86
|
+
{{jquery}}
|
87
|
+
<style type="text/css" media="screen">
|
88
|
+
|
89
|
+
body:last-child { margin-bottom: 60px; }
|
90
|
+
|
91
|
+
#logger {
|
92
|
+
position: fixed; bottom: 0; font-family: "Lucida Grande"; z-index: 99000;
|
93
|
+
width: 100%;
|
94
|
+
}
|
95
|
+
|
96
|
+
#logger ul {
|
97
|
+
margin: 0; padding: 0; list-style: none; overflow: auto;
|
98
|
+
width: 293px;
|
99
|
+
margin: 0 auto;
|
100
|
+
-webkit-border-top-right-radius: 5px;
|
101
|
+
-webkit-border-top-left-radius: 5px;
|
102
|
+
-moz-border-radius-topright: 5px;
|
103
|
+
-moz-border-radius-topleft: 5px;
|
104
|
+
-webkit-box-shadow: 0 0 10px #222;
|
105
|
+
-moz-box-shadow: 0 0 10px #222;
|
106
|
+
}
|
107
|
+
|
108
|
+
#logger > div {
|
109
|
+
-webkit-border-top-right-radius: 5px;
|
110
|
+
-webkit-border-top-left-radius: 5px;
|
111
|
+
-moz-border-radius-topright: 5px;
|
112
|
+
-moz-border-radius-topleft: 5px;
|
113
|
+
background-color: #222;
|
114
|
+
padding: 10px;
|
115
|
+
height: 200px;
|
116
|
+
margin: 0 20px;
|
117
|
+
}
|
118
|
+
|
119
|
+
#logger > div div {
|
120
|
+
overflow: auto;
|
121
|
+
height: 200px;
|
122
|
+
}
|
123
|
+
|
124
|
+
#logger p { margin: 0; color: #eee; padding: 10px; line-height: 14px; font-size: 12px; font-family: monaco; border-bottom: 1px solid #555; }
|
125
|
+
|
126
|
+
#logger ul li:first-of-type a {
|
127
|
+
-webkit-border-top-left-radius: 5px;
|
128
|
+
-moz-border-radius-topleft: 5px;
|
129
|
+
}
|
130
|
+
#logger ul li:last-of-type a {
|
131
|
+
-webkit-border-top-right-radius: 5px;
|
132
|
+
-moz-border-radius-topright: 5px;
|
133
|
+
}
|
134
|
+
|
135
|
+
#logger ul li { float: left; border-right: 1px solid #111; }
|
136
|
+
#logger ul li:last-of-type { border: 0; }
|
137
|
+
#logger ul li a {
|
138
|
+
padding: 6px 10px; display: block; text-decoration: none; width: 77px; text-align: center; font-weight: bold;
|
139
|
+
text-shadow: 1px 1px 1px #222; font-size: 14px; font-family: "Lucida Grande";
|
140
|
+
line-height: 14px; border: 0;
|
141
|
+
}
|
142
|
+
#logger ul li a:hover, #logger ul li a.selected { text-shadow: 1px 1px 1px #444; }
|
143
|
+
#logger ul li a span { display: block; font-size: 9px; line-height: 14px; text-transform: uppercase; text-align: center; }
|
144
|
+
#logger .load_time a {
|
145
|
+
color: #fff;
|
146
|
+
background-color: #c62;
|
147
|
+
background-image: -webkit-gradient(
|
148
|
+
linear,
|
149
|
+
left top,
|
150
|
+
left bottom,
|
151
|
+
color-stop(0.0, #e72),
|
152
|
+
color-stop(0.5, #a62),
|
153
|
+
color-stop(0.51, #a51)
|
154
|
+
);
|
155
|
+
}
|
156
|
+
#logger .queries a {
|
157
|
+
color: #fff;
|
158
|
+
background-color: #248;
|
159
|
+
background-image: -webkit-gradient(
|
160
|
+
linear,
|
161
|
+
left top,
|
162
|
+
left bottom,
|
163
|
+
color-stop(0.0, #47b),
|
164
|
+
color-stop(0.5, #349),
|
165
|
+
color-stop(0.51, #248)
|
166
|
+
);
|
167
|
+
}
|
168
|
+
#logger .messages a {
|
169
|
+
color: #fff;
|
170
|
+
background-color: #482;
|
171
|
+
background-image: -webkit-gradient(
|
172
|
+
linear,
|
173
|
+
left top,
|
174
|
+
left bottom,
|
175
|
+
color-stop(0.0, #7b4),
|
176
|
+
color-stop(0.5, #493),
|
177
|
+
color-stop(0.51, #482)
|
178
|
+
);
|
179
|
+
}
|
180
|
+
{}
|
181
|
+
</style>
|
182
|
+
|
183
|
+
<div id="logger">
|
184
|
+
<ul>
|
185
|
+
<li class="load_time"><a href="#">{{load_time}}<span>seconds</span></a></li>
|
186
|
+
<li class="queries"><a href="#">{{query_count}}<span>queries</span></a></li>
|
187
|
+
<li class="messages"><a href="#">{{message_count}}<span>messages</span></a></li>
|
188
|
+
</ul>
|
189
|
+
|
190
|
+
<div style="display: none">
|
191
|
+
{{queries}}
|
192
|
+
{{messages}}
|
193
|
+
</div>
|
194
|
+
|
195
|
+
</div>
|
196
|
+
<script type="text/javascript" charset="utf-8">
|
197
|
+
var _body = document.body;
|
198
|
+
var _html = $("html").get(0);
|
199
|
+
$(window).keyup(function(event) {
|
200
|
+
if ( event.which == 76 && (event.target == _body || event.target == _html) ) {
|
201
|
+
$("#logger").slideToggle();
|
202
|
+
}
|
203
|
+
});
|
204
|
+
$("#logger li a").click(function() {
|
205
|
+
var info = $("#logger div." + $(this).parent().attr("class"));
|
206
|
+
if ( info.get(0) ) {
|
207
|
+
if ( (siblings = info.siblings("div:visible")).get(0) ) {
|
208
|
+
siblings.hide();
|
209
|
+
info.toggle();
|
210
|
+
}
|
211
|
+
else {
|
212
|
+
if ( info.css("display") == "block" ) {
|
213
|
+
$("#logger > div").slideToggle(function() {
|
214
|
+
info.hide();
|
215
|
+
});
|
216
|
+
}
|
217
|
+
else {
|
218
|
+
info.show();
|
219
|
+
$("#logger > div").slideToggle();
|
220
|
+
}
|
221
|
+
}
|
222
|
+
}
|
223
|
+
else {
|
224
|
+
$("#logger > div:visible").slideToggle(function() {
|
225
|
+
$("#logger > div div").hide();
|
226
|
+
});
|
227
|
+
}
|
228
|
+
return false;
|
229
|
+
});
|
230
|
+
</script>
|
231
|
+
HTML
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
Logging::Logger.root.add_appenders(Harbor::Contrib::Debug::LogAppender.new)
|