| Class | Maveric |
| In: |
maveric.rb
maveric/sessions.rb |
| Parent: | Object |
| VERSION | = | '0.3.0' | Implementation details. | |
| EOL | = | "\r\n" | Standard end of line for HTTP | |
| MP_BOUND_REGEX | = | /\Amultipart\/form-data.*boundary=\"?([^\";, ]+)\"?/n | Group 1 wil contain a boundary for multipart/form-data bodies. |
| options | [R] | |
| router | [R] |
If no argument is given, the array of actions is returned.
If a Symbol or String is given with no block, in prepare_env, the corresponding method is called with the environment hash as an argument. If a block is given then, in prepare_env, that block will be called with the environment hash as the single argument.
If you are specifying other options you must explicitly state :name => <chosen label> as an argument. Additional arguments include :test, which should be a Proc that accepts a single argument. The argument will be the environment hash and the boolean result of the block will determine if the block will run.
# File maveric.rb, line 303
303: def adjust_env act={}, &block
304: ::Maveric.type_check :act, act, Hash, Symbol, String
305: return @adj_env if act.is_a? Hash and act.empty?
306: act = {:name => act} unless act.is_a? Hash
307: act[:do] = block
308: @adj_env << act
309: end
Performs URI escaping.
# File maveric.rb, line 176
176: def escape(s)
177: s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
178: '%'+$1.unpack('H2'*$1.size).join('%').upcase
179: }.tr(' ', '+')
180: end
Sets up a new Maveric with everything it needs for normal operation. Many things are either copied or included from it‘s parent The logger is copied and Models and Views are included by their respective modules.
# File maveric.rb, line 276
276: def inherited klass
277: ::Maveric.log.info "#{klass} inherits from #{self}."
278: super klass
279: parent = self
280: klass.class_eval do
281: const_set(:Models, Module.new).
282: module_eval { include parent::Models }
283: const_set(:Views, Module.new).
284: module_eval { include parent::Views }
285: @adj_env = parent.adjust_env.dup
286: end
287: end
Builds a logging object if there‘s not one yet, then returns it.
# File maveric.rb, line 164
164: def log
165: unless defined? @@maveric_logger
166: @@maveric_logger = Log4r::Logger.new 'mvc'
167: @@maveric_logger.outputters = Log4r::Outputter['stderr']
168: @@maveric_logger.level = Log4r::INFO
169: @@maveric_logger.info "#{self} #{::Maveric::VERSION}"+
170: " integrated at #{Time.now}"
171: end
172: @@maveric_logger
173: end
A recursive method to hunt out subclasses of Controller nested within a Maveric subclass. Used in the setting up of autoroutes.
# File maveric.rb, line 261
261: def nested_controllers realm=self, stk=[]
262: stk << realm #We don't need to visit the same thing twice.
263: realm.constants.map do |c|
264: next if stk.include?(c = realm.const_get(c)) or not c.is_a? Module
265: a = []
266: a << c if c < ::Maveric::Controller
267: a += nested_controllers c, stk
268: end.compact.flatten
269: end
When instantiated, the Maveric decends through its constants for nested Controllers and adds them by their routes.
# File maveric.rb, line 316
316: def initialize opts={}
317: ::Maveric.log.info "#{self.class} instantiated at #{Time.now}"
318: ::Maveric.type_check :opts, opts, Hash
319: @options = {:path_prefix => '/'}.merge opts
320: @router = ::Maveric::Router.new
321: self.class.nested_controllers.each {|c| router.add c.routes, c }
322: end
Parse a multipart/form-data entity. Adapated from cgi.rb. The body argument may either be a StringIO or a IO of subclass thereof.
Might need to be rehauled to match query_parse‘s behaviour.
# File maveric.rb, line 210
210: def parse_multipart boundary, body
211: ::Maveric.type_check :body, body, IO, StringIO
212: values = {}
213: bound = /(?:\r?\n|\A)#{Regexp::quote('--'+boundary)}(?:--)?\r$/
214: until body.eof?
215: fv = {}
216: until body.eof? or /^#{EOL}$/=~l
217: case l = body.readline
218: when /^Content-Type: (.+?)(\r$|\Z)/m
219: fv[:type] = $1
220: when /^Content-Disposition: form-data;/
221: $'.scan(/(?:\s(\w+)="([^"]+)")/) {|w| fv[w[0].intern] = w[1] }
222: end
223: end
224:
225: o = unless fv[:filename] then ''
226: else fv[:tempfile] = Tempfile.new('MVC').binmode end
227: body.inject do |buf,line|
228: o << buf.chomp and break if bound =~ line
229: o << buf
230: line
231: end
232:
233: fv[:tempfile].rewind if fv.key? :tempfile
234: values[fv[:name]] = fv.key?(:filename) ? fv : o
235: end
236: body.rewind
237: values
238: end
Parses a query string by breaking it up around the delimiting characters. You can also use this to parse cookies by changing the characters used in the second parameter (which defaults to ’;,’).
This will return a hash of parameters, values being contained in an array. As a warning, the default value is an empty array, not nil.
# File maveric.rb, line 197
197: def query_parse(qs, delim = '&;')
198: (qs||'').split(/[#{delim}] */n).inject(Hash.new([])) { |h,p|
199: k, v = unescape(p).split('=',2)
200: (h[k]||=[]) << v
201: h
202: }
203: end
I have a penchant for static typing, so as a general assertion and insurance that the correct types of data are being passed I created this little checking method. It will test value to be an instance of any of the trailing class, or if the block provided evalutates as true.
# File maveric.rb, line 245
245: def type_check name, value, *klasses, &test
246: return unless $DEBUG
247: # order of speed: #include?, #index, #any?
248: if i = klasses.any?{|k|value.is_a? k} then return i
249: elsif test and r = test[value] then return r
250: else
251: raise TypeError, "Expected #{klasses*' or '} for #{name},"+
252: " got #{value.class}:#{value.inspect}."
253: end
254: end
Unescapes a URI escaped string.
# File maveric.rb, line 183
183: def unescape(s)
184: s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
185: [$1.delete('%')].pack('H*')
186: }
187: end
The env argument should be a normal environment hash derived from HTTP.
# File maveric.rb, line 367
367: def prepare_environment env
368: ::Maveric.type_check :env, env, Hash
369: env.update :maveric => self
370: self.class.adjust_env.select do |act|
371: act[:test].nil? or act[:test][env]
372: end.each do |act|
373: ::Maveric.log.debug "#{self.class} prep_env #{act[:name]}"
374: if act[:do]
375: act[:do].call env
376: else
377: __send__ act[:name], env
378: end
379: end
380: end
Maveric‘s cue to start doing the heavy lifting. The env should be a hash with the typical assignments from an HTTP environment or crying and whining will ensue. The req_body argument should be a StringIO.
# File maveric.rb, line 330
330: def process req_body=$stdin, env=ENV, opts={}
331: ::Maveric.log.info "#{self.class}#process\n "+
332: "[#{Time.now}] #{env['REMOTE_ADDR']} => #{env['REQUEST_URI']}"
333: ::Maveric.type_check :req_body, req_body, StringIO
334: ::Maveric.type_check :env, env, Hash
335: begin
336: prepare_environment env unless env.key? :maveric
337: raise RuntimeError, [404, "Page not found."] unless env[:route]
338:
339: # If this is a POST request we need to load data from the body
340: if env['REQUEST_METHOD'] =~ /^post$/i
341: env[:params].update env.key?(:multipart_boundary) ?
342: parse_multipart(env[:multipart_boundary], req_body) :
343: ::Maveric.query_parse(req_body.read)
344: end
345:
346: unless env[:route][:controller] < ::Maveric::Controller
347: raise TypeError, "Route Controller of "+
348: "#{env[:route][:controller].class}. Expecting ::Maveric::Controller."
349: end
350:
351: env[:route][:controller].new req_body, env, opts
352: rescue # we catch exception.is_a? StandardError
353: ::Maveric.log.error "#{Time.now}:\n#{$!.inspect}\n#{$!.backtrace*"\n"}"
354: begin
355: raise $! # this makes sense in a certain context...
356: # Here is where we should have transformational stuffs for accessorizing
357: # or customizing error pages. As long as someone doesn't get stupid
358: # about it.
359: rescue
360: return $!
361: end
362: end
363: end