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.
Files changed (84) hide show
  1. data/Rakefile +76 -0
  2. data/bin/harbor +7 -0
  3. data/lib/harbor.rb +17 -0
  4. data/lib/harbor/accessor_injector.rb +30 -0
  5. data/lib/harbor/application.rb +172 -0
  6. data/lib/harbor/auth/basic.rb +51 -0
  7. data/lib/harbor/block_io.rb +63 -0
  8. data/lib/harbor/cache.rb +90 -0
  9. data/lib/harbor/cache/disk.rb +99 -0
  10. data/lib/harbor/cache/item.rb +48 -0
  11. data/lib/harbor/cache/memory.rb +35 -0
  12. data/lib/harbor/cascade.rb +75 -0
  13. data/lib/harbor/console.rb +34 -0
  14. data/lib/harbor/container.rb +134 -0
  15. data/lib/harbor/contrib/debug.rb +236 -0
  16. data/lib/harbor/contrib/session/data_mapper.rb +74 -0
  17. data/lib/harbor/daemon.rb +105 -0
  18. data/lib/harbor/errors.rb +49 -0
  19. data/lib/harbor/events.rb +45 -0
  20. data/lib/harbor/exception_notifier.rb +59 -0
  21. data/lib/harbor/file.rb +66 -0
  22. data/lib/harbor/file_store.rb +69 -0
  23. data/lib/harbor/file_store/file.rb +100 -0
  24. data/lib/harbor/file_store/local.rb +71 -0
  25. data/lib/harbor/file_store/mosso.rb +154 -0
  26. data/lib/harbor/file_store/mosso/private.rb +8 -0
  27. data/lib/harbor/generator.rb +56 -0
  28. data/lib/harbor/generator/help.rb +34 -0
  29. data/lib/harbor/generator/setup.rb +82 -0
  30. data/lib/harbor/generator/skeletons/basic/config.ru.skel +21 -0
  31. data/lib/harbor/generator/skeletons/basic/lib/appname.rb.skel +49 -0
  32. data/lib/harbor/generator/skeletons/basic/lib/appname/controllers/home.rb +9 -0
  33. data/lib/harbor/generator/skeletons/basic/lib/appname/views/home/index.html.erb.skel +23 -0
  34. data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/application.html.erb.skel +48 -0
  35. data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/exception.html.erb.skel +13 -0
  36. data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/login.html.erb.skel +11 -0
  37. data/lib/harbor/generator/skeletons/basic/log/development.log +0 -0
  38. data/lib/harbor/hooks.rb +105 -0
  39. data/lib/harbor/json_cookies.rb +37 -0
  40. data/lib/harbor/layouts.rb +61 -0
  41. data/lib/harbor/locale.rb +50 -0
  42. data/lib/harbor/locales.txt +22 -0
  43. data/lib/harbor/logging.rb +39 -0
  44. data/lib/harbor/logging/appenders/email.rb +84 -0
  45. data/lib/harbor/logging/request_logger.rb +34 -0
  46. data/lib/harbor/mail_servers/abstract.rb +12 -0
  47. data/lib/harbor/mail_servers/sendmail.rb +19 -0
  48. data/lib/harbor/mail_servers/smtp.rb +25 -0
  49. data/lib/harbor/mail_servers/test.rb +17 -0
  50. data/lib/harbor/mailer.rb +96 -0
  51. data/lib/harbor/messages.rb +17 -0
  52. data/lib/harbor/mime.rb +206 -0
  53. data/lib/harbor/plugin.rb +52 -0
  54. data/lib/harbor/plugin_list.rb +38 -0
  55. data/lib/harbor/request.rb +138 -0
  56. data/lib/harbor/response.rb +281 -0
  57. data/lib/harbor/router.rb +112 -0
  58. data/lib/harbor/script.rb +155 -0
  59. data/lib/harbor/session.rb +75 -0
  60. data/lib/harbor/session/abstract.rb +27 -0
  61. data/lib/harbor/session/cookie.rb +17 -0
  62. data/lib/harbor/shellwords.rb +26 -0
  63. data/lib/harbor/test/mailer.rb +10 -0
  64. data/lib/harbor/test/request.rb +28 -0
  65. data/lib/harbor/test/response.rb +17 -0
  66. data/lib/harbor/test/session.rb +11 -0
  67. data/lib/harbor/test/test.rb +22 -0
  68. data/lib/harbor/version.rb +3 -0
  69. data/lib/harbor/view.rb +89 -0
  70. data/lib/harbor/view_context.rb +134 -0
  71. data/lib/harbor/view_context/helpers.rb +7 -0
  72. data/lib/harbor/view_context/helpers/cache.rb +77 -0
  73. data/lib/harbor/view_context/helpers/form.rb +34 -0
  74. data/lib/harbor/view_context/helpers/html.rb +26 -0
  75. data/lib/harbor/view_context/helpers/text.rb +120 -0
  76. data/lib/harbor/view_context/helpers/url.rb +11 -0
  77. data/lib/harbor/xml_view.rb +57 -0
  78. data/lib/harbor/zipped_io.rb +203 -0
  79. data/lib/vendor/cloudfiles-1.3.0/cloudfiles.rb +77 -0
  80. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/authentication.rb +46 -0
  81. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/connection.rb +280 -0
  82. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/container.rb +260 -0
  83. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/storage_object.rb +253 -0
  84. 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("<", "&lt;") + "</p>") if message =~ /^\(/
42
+ end.compact
43
+
44
+ messages = messages.map do |level, message|
45
+ ("<p>[#{@levels[level]}] " + message.gsub("<", "&lt;") + "</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)