wycats-merb-core 0.9.8 → 0.9.9

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 (58) hide show
  1. data/CHANGELOG +136 -2
  2. data/CONTRIBUTORS +6 -0
  3. data/PUBLIC_CHANGELOG +15 -0
  4. data/Rakefile +12 -14
  5. data/lib/merb-core.rb +82 -43
  6. data/lib/merb-core/bootloader.rb +268 -60
  7. data/lib/merb-core/config.rb +119 -34
  8. data/lib/merb-core/controller/abstract_controller.rb +58 -18
  9. data/lib/merb-core/controller/exceptions.rb +2 -15
  10. data/lib/merb-core/controller/merb_controller.rb +28 -1
  11. data/lib/merb-core/controller/mime.rb +4 -0
  12. data/lib/merb-core/controller/mixins/controller.rb +14 -17
  13. data/lib/merb-core/controller/mixins/render.rb +23 -28
  14. data/lib/merb-core/controller/mixins/responder.rb +0 -1
  15. data/lib/merb-core/controller/template.rb +44 -20
  16. data/lib/merb-core/core_ext/kernel.rb +8 -3
  17. data/lib/merb-core/dispatch/default_exception/default_exception.rb +1 -1
  18. data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +3 -1
  19. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +71 -67
  20. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +6 -2
  21. data/lib/merb-core/dispatch/dispatcher.rb +5 -9
  22. data/lib/merb-core/dispatch/request.rb +46 -57
  23. data/lib/merb-core/dispatch/router.rb +83 -6
  24. data/lib/merb-core/dispatch/router/behavior.rb +87 -27
  25. data/lib/merb-core/dispatch/router/resources.rb +281 -167
  26. data/lib/merb-core/dispatch/router/route.rb +141 -27
  27. data/lib/merb-core/logger.rb +213 -202
  28. data/lib/merb-core/rack.rb +3 -1
  29. data/lib/merb-core/rack/adapter.rb +7 -4
  30. data/lib/merb-core/rack/adapter/ebb.rb +12 -13
  31. data/lib/merb-core/rack/adapter/evented_mongrel.rb +2 -15
  32. data/lib/merb-core/rack/adapter/irb.rb +3 -2
  33. data/lib/merb-core/rack/adapter/mongrel.rb +22 -15
  34. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +4 -16
  35. data/lib/merb-core/rack/adapter/thin.rb +21 -22
  36. data/lib/merb-core/rack/adapter/thin_turbo.rb +4 -11
  37. data/lib/merb-core/rack/adapter/webrick.rb +54 -18
  38. data/lib/merb-core/rack/handler/mongrel.rb +12 -13
  39. data/lib/merb-core/rack/middleware/csrf.rb +1 -1
  40. data/lib/merb-core/server.rb +135 -98
  41. data/lib/merb-core/tasks/gem_management.rb +50 -12
  42. data/lib/merb-core/tasks/merb.rb +1 -0
  43. data/lib/merb-core/tasks/merb_rake_helper.rb +9 -38
  44. data/lib/merb-core/tasks/stats.rake +2 -2
  45. data/lib/merb-core/test.rb +9 -3
  46. data/lib/merb-core/test/helpers.rb +1 -0
  47. data/lib/merb-core/test/helpers/multipart_request_helper.rb +3 -2
  48. data/lib/merb-core/test/helpers/request_helper.rb +40 -372
  49. data/lib/merb-core/test/helpers/route_helper.rb +15 -7
  50. data/lib/merb-core/test/matchers.rb +1 -0
  51. data/lib/merb-core/test/matchers/controller_matchers.rb +4 -247
  52. data/lib/merb-core/test/matchers/view_matchers.rb +22 -4
  53. data/lib/merb-core/test/run_specs.rb +117 -25
  54. data/lib/merb-core/version.rb +1 -1
  55. metadata +1 -1
  56. data/lib/merb-core/vendor/facets.rb +0 -2
  57. data/lib/merb-core/vendor/facets/dictionary.rb +0 -433
  58. data/lib/merb-core/vendor/facets/inflect.rb +0 -342
@@ -12,10 +12,9 @@ module Merb
12
12
 
13
13
  attr_reader :conditions, :params, :segments
14
14
  attr_reader :index, :variables, :name
15
- attr_reader :redirect_status, :redirect_url
16
15
  attr_accessor :fixation
17
16
 
18
- def initialize(conditions, params, options = {}, &conditional_block)
17
+ def initialize(conditions, params, deferred_procs, options = {})
19
18
  @conditions, @params = conditions, params
20
19
 
21
20
  if options[:redirects]
@@ -25,10 +24,12 @@ module Merb
25
24
  @defaults = {}
26
25
  else
27
26
  @defaults = options[:defaults] || {}
28
- @conditional_block = conditional_block
29
27
  end
28
+
29
+ # @conditional_block = conditional_block
30
30
 
31
31
  @identifiers = options[:identifiers]
32
+ @deferred_procs = deferred_procs
32
33
  @segments = []
33
34
  @symbol_conditions = {}
34
35
  @placeholders = {}
@@ -42,10 +43,6 @@ module Merb
42
43
  def allow_fixation?
43
44
  @fixation
44
45
  end
45
-
46
- def redirects?
47
- @redirects
48
- end
49
46
 
50
47
  def to_s
51
48
  regexp? ?
@@ -55,30 +52,75 @@ module Merb
55
52
 
56
53
  alias_method :inspect, :to_s
57
54
 
55
+ # Appends self to Merb::Router.routes
58
56
  def register
59
57
  @index = Merb::Router.routes.size
60
58
  Merb::Router.routes << self
61
59
  self
62
60
  end
63
61
 
62
+ # Inserts self to Merb::Router.routes at the specified index.
63
+ def register_at(index)
64
+ @index = index
65
+ Merb::Router.routes.insert(index, self)
66
+ self
67
+ end
68
+
69
+ # Sets the route as a resource route with the given key as the
70
+ # lookup key.
71
+ def resource=(key)
72
+ Router.resource_routes[key] = self
73
+ key
74
+ end
75
+
64
76
  def name=(name)
65
77
  @name = name.to_sym
66
78
  Router.named_routes[@name] = self
67
79
  @name
68
80
  end
69
81
 
70
- # === Compiled method ===
82
+ # Generates the URL for the route given the passed arguments. The
83
+ # method will first match the anonymous parameters to route params
84
+ # and will convert all the parameters according to the specifed
85
+ # object identifiers.
86
+ #
87
+ # Then the named parameters are passed to a compiled generation proc.
88
+ #
89
+ # ==== Parameters
90
+ # args<Array>::
91
+ # The arguments passed to the public #url method with the name
92
+ # of the route removed. This is an array of the anonymous parameters
93
+ # followed by a hash containing the named parameters.
94
+ #
95
+ # defaults<Hash>::
96
+ # A hash of parameters to use to generate the route if there are
97
+ # any missing required parameters. This is usually the parameters
98
+ # for the current request
99
+ #
100
+ # ==== Returns
101
+ # String:: The generated URL.
71
102
  def generate(args = [], defaults = {})
72
103
  raise GenerationError, "Cannot generate regexp Routes" if regexp?
73
104
 
74
105
  params = extract_options_from_args!(args) || { }
75
106
 
107
+ params.each do |k, v|
108
+ params[k] = identify(v, k)
109
+ end
110
+
76
111
  # Support for anonymous params
77
112
  unless args.empty?
78
- raise GenerationError, "The route has #{@variables.length} variables: #{@variables.inspect}" if args.length > @variables.length
113
+ # First, let's determine which variables are missing
114
+ variables = @variables - params.keys
79
115
 
80
- args.each_with_index do |param, i|
81
- params[@variables[i]] ||= param
116
+ args.each do |param|
117
+ raise GenerationError, "The route has #{@variables.length} variables: #{@variables.inspect}" if variables.empty?
118
+
119
+ if identifier = identifier_for(param) and identifier.is_a?(Array)
120
+ identifier.each { |ident| params[variables.shift] = param.send(ident) }
121
+ else
122
+ params[variables.shift] ||= identify(param)
123
+ end
82
124
  end
83
125
  end
84
126
 
@@ -86,13 +128,63 @@ module Merb
86
128
  uri = Merb::Config[:path_prefix] + uri if Merb::Config[:path_prefix]
87
129
  uri
88
130
  end
131
+
132
+ # Identifies the object according to the identifiers set while building
133
+ # the routes. Identifying an object means picking an instance method to
134
+ # call on the object that will return a string representation of the
135
+ # object for the route being generated. If the identifier is an array,
136
+ # then a param_key must be present and match one of the elements of the
137
+ # identifier array.
138
+ #
139
+ # param_keys that end in _id are treated slightly differently in order
140
+ # to get nested resources to work correctly.
141
+ def identify(obj, param_key = nil)
142
+ identifier = identifier_for(obj)
143
+ if identifier.is_a?(Array)
144
+ # First check if the param_key exists as an identifier
145
+ return obj.send(param_key) if identifier.include?(param_key)
146
+ # If the param_key ends in _id, just return the object id
147
+ return obj.id if "#{param_key}" =~ /_id$/
148
+ # Otherwise, raise an error
149
+ raise GenerationError, "The object #{obj.inspect} cannot be identified with #{identifier.inspect} for #{param_key}"
150
+ else
151
+ identifier ? obj.send(identifier) : obj
152
+ end
153
+ end
154
+
155
+ # Returns the identifier for the passed object. Built in core ruby classes are
156
+ # always identified with to_s. The method will return nil in that case (since
157
+ # to_s is the default for objects that do not have identifiers.)
158
+ def identifier_for(obj)
159
+ return if obj.is_a?(String) || obj.is_a?(Symbol) || obj.is_a?(Numeric) ||
160
+ obj.is_a?(TrueClass) || obj.is_a?(FalseClass) || obj.is_a?(NilClass) ||
161
+ obj.is_a?(Array) || obj.is_a?(Hash)
162
+
163
+ @identifiers.each do |klass, identifier|
164
+ return identifier if obj.is_a?(klass)
165
+ end
166
+
167
+ return nil
168
+ end
89
169
 
170
+ # Returns the if statement and return value for for the main
171
+ # Router.match compiled method.
90
172
  def compiled_statement(first)
91
173
  els_if = first ? ' if ' : ' elsif '
92
174
 
93
175
  code = ""
94
176
  code << els_if << condition_statements.join(" && ") << "\n"
95
- if @conditional_block
177
+
178
+ # First, we need to always return the value of the
179
+ # deferred block if it explicitly matched the route
180
+ if @redirects && @deferred_procs.any?
181
+ code << " return [#{@index.inspect}, block_result] if request.matched?" << "\n"
182
+ code << " request.redirects!" << "\n"
183
+ code << " [#{@index.inspect}, { :url => #{@redirect_url.inspect}, :status => #{@redirect_status.inspect} }]" << "\n"
184
+ elsif @redirects
185
+ code << " request.redirects!" << "\n"
186
+ code << " [#{@index.inspect}, { :url => #{@redirect_url.inspect}, :status => #{@redirect_status.inspect} }]" << "\n"
187
+ elsif @deferred_procs.any?
96
188
  code << " [#{@index.inspect}, block_result]" << "\n"
97
189
  else
98
190
  code << " [#{@index.inspect}, #{params_as_string}]" << "\n"
@@ -233,7 +325,7 @@ module Merb
233
325
  segments.each_with_index do |segment, i|
234
326
  bits << case
235
327
  when segment.is_a?(String) then segment
236
- when segment.is_a?(Symbol) then '#{param_for_route(cached_' + segment.to_s + ')}'
328
+ when segment.is_a?(Symbol) then '#{cached_' + segment.to_s + '}'
237
329
  when segment.is_a?(Array) && segment.any? { |s| !s.is_a?(String) } then "\#{#{@opt_segment_stack.last.shift}}"
238
330
  else ""
239
331
  end
@@ -242,16 +334,6 @@ module Merb
242
334
  bits
243
335
  end
244
336
 
245
- def param_for_route(param)
246
- case param
247
- when String, Symbol, Numeric, TrueClass, FalseClass, NilClass
248
- param
249
- else
250
- _, identifier = @identifiers.find { |klass, _| param.is_a?(klass) }
251
- identifier ? param.send(identifier) : param
252
- end
253
- end
254
-
255
337
  end
256
338
 
257
339
  # === Conditions ===
@@ -300,7 +382,11 @@ module Merb
300
382
  end
301
383
  end
302
384
 
303
- @variables = @segments.flatten.select { |s| s.is_a?(Symbol) }
385
+ unless regexp?
386
+ @variables = @segments.flatten.select { |s| s.is_a?(Symbol) }
387
+ compiled.gsub!(%r[/+], '/')
388
+ compiled.gsub!(%r[(.+)/$], '\1')
389
+ end
304
390
 
305
391
  compiled
306
392
  end
@@ -441,6 +527,7 @@ module Merb
441
527
  def condition_statements
442
528
  statements = []
443
529
 
530
+ # First, let's build the conditions for the regular
444
531
  conditions.each_pair do |key, value|
445
532
  statements << case value
446
533
  when Regexp
@@ -460,13 +547,40 @@ module Merb
460
547
  %{(cached_#{key} == #{value.inspect})}
461
548
  end
462
549
  end
463
-
464
- if @conditional_block
465
- statements << "(block_result = #{CachedProc.new(@conditional_block)}.call(request, #{params_as_string}))"
550
+
551
+ # The first one is special, so let's extract it
552
+ if first = @deferred_procs.first
553
+ deferred = ""
554
+ deferred << "(block_result = "
555
+ deferred << "request._process_block_return("
556
+ deferred << "#{first}.call(request, #{params_as_string})"
557
+ deferred << ")"
558
+ deferred << ")"
559
+
560
+ # Let's build the rest of them now
561
+ if @deferred_procs.length > 1
562
+ deferred << deferred_condition_statement(@deferred_procs[1..-1])
563
+ end
564
+
565
+ statements << deferred
466
566
  end
467
567
 
468
568
  statements
469
569
  end
570
+
571
+ # (request.matched? || ((block_result = process(proc.call))))
572
+ def deferred_condition_statement(deferred)
573
+ if current = deferred.first
574
+ html = " && (request.matched? || ("
575
+ html << "(block_result = "
576
+ html << "request._process_block_return("
577
+ html << "#{current}.call(request, block_result)"
578
+ html << ")"
579
+ html << ")"
580
+ html << "#{deferred_condition_statement(deferred[1..-1])}"
581
+ html << "))"
582
+ end
583
+ end
470
584
 
471
585
  def params_as_string
472
586
  elements = params.keys.map do |k|
@@ -1,203 +1,214 @@
1
- Merb::Logger = Extlib::Logger
1
+ # Merb::Logger = Extlib::Logger
2
+
3
+ class Merb::Logger < Extlib::Logger
4
+ def verbose!(message, level = :warn)
5
+ send("#{level}!", message) if Merb::Config[:verbose]
6
+ end
7
+
8
+ def verbose(message, level = :warn)
9
+ send(level, message) if Merb::Config[:verbose]
10
+ end
11
+ end
12
+
2
13
  # require "time" # httpdate
3
- # # ==== Public Merb Logger API
4
- # #
5
- # # To replace an existing logger with a new one:
6
- # # Merb::Logger.set_log(log{String, IO},level{Symbol, String})
7
- # #
8
- # # Available logging levels are
9
- # # Merb::Logger::{ Fatal, Error, Warn, Info, Debug }
10
- # #
11
- # # Logging via:
12
- # # Merb.logger.fatal(message<String>,&block)
13
- # # Merb.logger.error(message<String>,&block)
14
- # # Merb.logger.warn(message<String>,&block)
15
- # # Merb.logger.info(message<String>,&block)
16
- # # Merb.logger.debug(message<String>,&block)
17
- # #
18
- # # Logging with autoflush:
19
- # # Merb.logger.fatal!(message<String>,&block)
20
- # # Merb.logger.error!(message<String>,&block)
21
- # # Merb.logger.warn!(message<String>,&block)
22
- # # Merb.logger.info!(message<String>,&block)
23
- # # Merb.logger.debug!(message<String>,&block)
24
- # #
25
- # # Flush the buffer to
26
- # # Merb.logger.flush
27
- # #
28
- # # Remove the current log object
29
- # # Merb.logger.close
30
- # #
31
- # # ==== Private Merb Logger API
32
- # #
33
- # # To initialize the logger you create a new object, proxies to set_log.
34
- # # Merb::Logger.new(log{String, IO},level{Symbol, String})
35
- # module Merb
36
- #
37
- # class << self
38
- # attr_accessor :logger
39
- # end
40
- #
41
- # class Logger
42
- #
43
- # attr_accessor :level
44
- # attr_accessor :delimiter
45
- # attr_accessor :auto_flush
46
- # attr_reader :buffer
47
- # attr_reader :log
48
- # attr_reader :init_args
49
- #
50
- # # ==== Notes
51
- # # Ruby (standard) logger levels:
52
- # # :fatal:: An unhandleable error that results in a program crash
53
- # # :error:: A handleable error condition
54
- # # :warn:: A warning
55
- # # :info:: generic (useful) information about system operation
56
- # # :debug:: low-level information for developers
57
- # Levels =
58
- # {
59
- # :fatal => 7,
60
- # :error => 6,
61
- # :warn => 4,
62
- # :info => 3,
63
- # :debug => 0
64
- # } unless const_defined?(:Levels)
65
- #
66
- # private
67
- #
68
- # # Readies a log for writing.
69
- # #
70
- # # ==== Parameters
71
- # # log<IO, String>:: Either an IO object or a name of a logfile.
72
- # def initialize_log(log)
73
- # close if @log # be sure that we don't leave open files laying around.
74
- #
75
- # if log.respond_to?(:write)
76
- # @log = log
77
- # elsif File.exist?(log)
78
- # @log = open(log, (File::WRONLY | File::APPEND))
79
- # @log.sync = true
80
- # else
81
- # FileUtils.mkdir_p(File.dirname(log)) unless File.directory?(File.dirname(log))
82
- # @log = open(log, (File::WRONLY | File::APPEND | File::CREAT))
83
- # @log.sync = true
84
- # @log.write("#{Time.now.httpdate} #{delimiter} info #{delimiter} Logfile created\n")
85
- # end
86
- # end
87
- #
88
- # public
89
- #
90
- # # To initialize the logger you create a new object, proxies to set_log.
91
- # #
92
- # # ==== Parameters
93
- # # *args:: Arguments to create the log from. See set_logs for specifics.
94
- # def initialize(*args)
95
- # @init_args = args
96
- # set_log(*args)
97
- # end
98
- #
99
- # # Replaces an existing logger with a new one.
100
- # #
101
- # # ==== Parameters
102
- # # log<IO, String>:: Either an IO object or a name of a logfile.
103
- # # log_level<~to_sym>::
104
- # # The log level from, e.g. :fatal or :info. Defaults to :error in the
105
- # # production environment and :debug otherwise.
106
- # # delimiter<String>::
107
- # # Delimiter to use between message sections. Defaults to " ~ ".
108
- # # auto_flush<Boolean>::
109
- # # Whether the log should automatically flush after new messages are
110
- # # added. Defaults to false.
111
- # def set_log(log, log_level = nil, delimiter = " ~ ", auto_flush = false)
112
- # if log_level && Levels[log_level.to_sym]
113
- # @level = Levels[log_level.to_sym]
114
- # elsif Merb.environment == "production"
115
- # @level = Levels[:warn]
116
- # else
117
- # @level = Levels[:debug]
118
- # end
119
- # @buffer = []
120
- # @delimiter = delimiter
121
- # @auto_flush = auto_flush
122
- #
123
- # initialize_log(log)
124
- #
125
- # Merb.logger = self
126
- # end
127
- #
128
- # # Flush the entire buffer to the log object.
129
- # def flush
130
- # return unless @buffer.size > 0
131
- # @log.write(@buffer.slice!(0..-1).to_s)
132
- # end
133
- #
134
- # # Close and remove the current log object.
135
- # def close
136
- # flush
137
- # @log.close if @log.respond_to?(:close) && !@log.tty?
138
- # @log = nil
139
- # end
140
- #
141
- # # Appends a message to the log. The methods yield to an optional block and
142
- # # the output of this block will be appended to the message.
143
- # #
144
- # # ==== Parameters
145
- # # string<String>:: The message to be logged. Defaults to nil.
146
- # #
147
- # # ==== Returns
148
- # # String:: The resulting message added to the log file.
149
- # def <<(string = nil)
150
- # message = ""
151
- # message << delimiter
152
- # message << string if string
153
- # message << "\n" unless message[-1] == ?\n
154
- # @buffer << message
155
- # flush if @auto_flush
156
- #
157
- # message
158
- # end
159
- # alias :push :<<
160
- #
161
- # # Generate the logging methods for Merb.logger for each log level.
162
- # Levels.each_pair do |name, number|
163
- # class_eval <<-LEVELMETHODS, __FILE__, __LINE__
164
- #
165
- # # Appends a message to the log if the log level is at least as high as
166
- # # the log level of the logger.
167
- # #
168
- # # ==== Parameters
169
- # # string<String>:: The message to be logged. Defaults to nil.
170
- # #
171
- # # ==== Returns
172
- # # self:: The logger object for chaining.
173
- # def #{name}(message = nil)
174
- # self << message if #{number} >= level
175
- # self
176
- # end
177
- #
178
- # # Appends a message to the log if the log level is at least as high as
179
- # # the log level of the logger. The bang! version of the method also auto
180
- # # flushes the log buffer to disk.
181
- # #
182
- # # ==== Parameters
183
- # # string<String>:: The message to be logged. Defaults to nil.
184
- # #
185
- # # ==== Returns
186
- # # self:: The logger object for chaining.
187
- # def #{name}!(message = nil)
188
- # self << message if #{number} >= level
189
- # flush if #{number} >= level
190
- # self
191
- # end
192
- #
193
- # # ==== Returns
194
- # # Boolean:: True if this level will be logged by this logger.
195
- # def #{name}?
196
- # #{number} >= level
197
- # end
198
- # LEVELMETHODS
199
- # end
200
- #
201
- # end
202
- #
203
- # end
14
+ # ==== Public Merb Logger API
15
+ #
16
+ # To replace an existing logger with a new one:
17
+ # Merb::Logger.set_log(log{String, IO},level{Symbol, String})
18
+ #
19
+ # Available logging levels are
20
+ # Merb::Logger::{ Fatal, Error, Warn, Info, Debug }
21
+ #
22
+ # Logging via:
23
+ # Merb.logger.fatal(message<String>,&block)
24
+ # Merb.logger.error(message<String>,&block)
25
+ # Merb.logger.warn(message<String>,&block)
26
+ # Merb.logger.info(message<String>,&block)
27
+ # Merb.logger.debug(message<String>,&block)
28
+ #
29
+ # Logging with autoflush:
30
+ # Merb.logger.fatal!(message<String>,&block)
31
+ # Merb.logger.error!(message<String>,&block)
32
+ # Merb.logger.warn!(message<String>,&block)
33
+ # Merb.logger.info!(message<String>,&block)
34
+ # Merb.logger.debug!(message<String>,&block)
35
+ #
36
+ # Flush the buffer to
37
+ # Merb.logger.flush
38
+ #
39
+ # Remove the current log object
40
+ # Merb.logger.close
41
+ #
42
+ # ==== Private Merb Logger API
43
+ #
44
+ # To initialize the logger you create a new object, proxies to set_log.
45
+ # Merb::Logger.new(log{String, IO},level{Symbol, String})
46
+ module Merb
47
+
48
+ class Logger
49
+
50
+ attr_accessor :level
51
+ attr_accessor :delimiter
52
+ attr_accessor :auto_flush
53
+ attr_reader :buffer
54
+ attr_reader :log
55
+ attr_reader :init_args
56
+
57
+ # ==== Notes
58
+ # Ruby (standard) logger levels:
59
+ # :fatal:: An unhandleable error that results in a program crash
60
+ # :error:: A handleable error condition
61
+ # :warn:: A warning
62
+ # :info:: generic (useful) information about system operation
63
+ # :debug:: low-level information for developers
64
+ Levels = Mash.new({
65
+ :fatal => 7,
66
+ :error => 6,
67
+ :warn => 4,
68
+ :info => 3,
69
+ :debug => 0
70
+ }) unless const_defined?(:Levels)
71
+
72
+ @@mutex = {}
73
+
74
+ private
75
+
76
+ # Readies a log for writing.
77
+ #
78
+ # ==== Parameters
79
+ # log<IO, String>:: Either an IO object or a name of a logfile.
80
+ def initialize_log(log)
81
+ close if @log # be sure that we don't leave open files laying around.
82
+
83
+ if log.respond_to?(:write)
84
+ @log = log
85
+ elsif File.exist?(log)
86
+ @log = open(log, (File::WRONLY | File::APPEND))
87
+ @log.sync = true
88
+ else
89
+ FileUtils.mkdir_p(File.dirname(log)) unless File.directory?(File.dirname(log))
90
+ @log = open(log, (File::WRONLY | File::APPEND | File::CREAT))
91
+ @log.sync = true
92
+ @log.write("#{Time.now.httpdate} #{delimiter} info #{delimiter} Logfile created\n")
93
+ end
94
+ end
95
+
96
+ public
97
+
98
+ # To initialize the logger you create a new object, proxies to set_log.
99
+ #
100
+ # ==== Parameters
101
+ # *args:: Arguments to create the log from. See set_logs for specifics.
102
+ def initialize(*args)
103
+ set_log(*args)
104
+ end
105
+
106
+ # Replaces an existing logger with a new one.
107
+ #
108
+ # ==== Parameters
109
+ # log<IO, String>:: Either an IO object or a name of a logfile.
110
+ # log_level<~to_sym>::
111
+ # The log level from, e.g. :fatal or :info. Defaults to :error in the
112
+ # production environment and :debug otherwise.
113
+ # delimiter<String>::
114
+ # Delimiter to use between message sections. Defaults to " ~ ".
115
+ # auto_flush<Boolean>::
116
+ # Whether the log should automatically flush after new messages are
117
+ # added. Defaults to false.
118
+ def set_log(stream = Merb::Config[:log_stream],
119
+ log_level = Merb::Config[:log_level],
120
+ delimiter = Merb::Config[:log_delimiter],
121
+ auto_flush = Merb::Config[:log_auto_flush])
122
+
123
+ @buffer = []
124
+ @delimiter = delimiter
125
+ @auto_flush = auto_flush
126
+
127
+ if Levels[log_level]
128
+ @level = Levels[log_level]
129
+ else
130
+ @level = log_level
131
+ end
132
+
133
+ @log = stream
134
+ @mutex = (@@mutex[@log] ||= Mutex.new)
135
+ end
136
+
137
+ # Flush the entire buffer to the log object.
138
+ def flush
139
+ return unless @buffer.size > 0
140
+ @mutex.synchronize do
141
+ @log.write(@buffer.slice!(0..-1).to_s)
142
+ end
143
+ end
144
+
145
+ # Close and remove the current log object.
146
+ def close
147
+ flush
148
+ @log.close if @log.respond_to?(:close) && !@log.tty?
149
+ @log = nil
150
+ end
151
+
152
+ # Appends a message to the log. The methods yield to an optional block and
153
+ # the output of this block will be appended to the message.
154
+ #
155
+ # ==== Parameters
156
+ # string<String>:: The message to be logged. Defaults to nil.
157
+ #
158
+ # ==== Returns
159
+ # String:: The resulting message added to the log file.
160
+ def <<(string = nil)
161
+ message = ""
162
+ message << delimiter
163
+ message << string if string
164
+ message << "\n" unless message[-1] == ?\n
165
+ @buffer << message
166
+ flush if @auto_flush
167
+
168
+ message
169
+ end
170
+ alias :push :<<
171
+
172
+ # Generate the logging methods for Merb.logger for each log level.
173
+ Levels.each_pair do |name, number|
174
+ class_eval <<-LEVELMETHODS, __FILE__, __LINE__
175
+
176
+ # Appends a message to the log if the log level is at least as high as
177
+ # the log level of the logger.
178
+ #
179
+ # ==== Parameters
180
+ # string<String>:: The message to be logged. Defaults to nil.
181
+ #
182
+ # ==== Returns
183
+ # self:: The logger object for chaining.
184
+ def #{name}(message = nil)
185
+ self << message if #{number} >= level
186
+ self
187
+ end
188
+
189
+ # Appends a message to the log if the log level is at least as high as
190
+ # the log level of the logger. The bang! version of the method also auto
191
+ # flushes the log buffer to disk.
192
+ #
193
+ # ==== Parameters
194
+ # string<String>:: The message to be logged. Defaults to nil.
195
+ #
196
+ # ==== Returns
197
+ # self:: The logger object for chaining.
198
+ def #{name}!(message = nil)
199
+ self << message if #{number} >= level
200
+ flush if #{number} >= level
201
+ self
202
+ end
203
+
204
+ # ==== Returns
205
+ # Boolean:: True if this level will be logged by this logger.
206
+ def #{name}?
207
+ #{number} >= level
208
+ end
209
+ LEVELMETHODS
210
+ end
211
+
212
+ end
213
+
214
+ end