Class: Maveric

Inherits:
Object
  • Object
show all
Defined in:
lib/maveric.rb,
lib/maveric/sessions.rb

Overview

The Maveric: Yeargh.

The Maveric may be used alone or may be used in a cadre of loosely aligned Maveric instances. The Maveric stands tall and proud, relying on it’s fine family and it’s inventory of goods to get things done with little magic or trickery.

Defined Under Namespace

Modules: Models, Views Classes: Controller, FCGI, MongrelHandler, Route, Router, Sessions, WEBrickServlet

Constant Summary collapse

VERSION =

Implementation details.

'0.3.0'
EOL =

Standard end of line for HTTP

"\r\n"
MP_BOUND_REGEX =

Group 1 wil contain a boundary for multipart/form-data bodies.

/\Amultipart\/form-data.*boundary=\"?([^\";, ]+)\"?/n

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Maveric

When instantiated, the Maveric decends through its constants for nested Controllers and adds them by their routes.



316
317
318
319
320
321
322
# File 'lib/maveric.rb', line 316

def initialize opts={}
  ::Maveric.log.info "#{self.class} instantiated at #{Time.now}"
  ::Maveric.type_check :opts, opts, Hash
  @options = {:path_prefix => '/'}.merge opts
  @router = ::Maveric::Router.new
  self.class.nested_controllers.each {|c| router.add c.routes, c }
end

Instance Attribute Details

#optionsObject (readonly)

Returns the value of attribute options.



324
325
326
# File 'lib/maveric.rb', line 324

def options
  @options
end

#routerObject (readonly)

Returns the value of attribute router.



324
325
326
# File 'lib/maveric.rb', line 324

def router
  @router
end

Class Method Details

.adjust_env(act = {}, &block) ⇒ Object

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.



303
304
305
306
307
308
309
# File 'lib/maveric.rb', line 303

def adjust_env act={}, &block
  ::Maveric.type_check :act, act, Hash, Symbol, String
  return @adj_env if act.is_a? Hash and act.empty?
  act = {:name => act} unless act.is_a? Hash
  act[:do] = block
  @adj_env << act
end

.escape(s) ⇒ Object

Performs URI escaping.



176
177
178
179
180
# File 'lib/maveric.rb', line 176

def escape(s)
  s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
    '%'+$1.unpack('H2'*$1.size).join('%').upcase
  }.tr(' ', '+')
end

.inherited(klass) ⇒ Object

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.



276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/maveric.rb', line 276

def inherited klass
  ::Maveric.log.info "#{klass} inherits from #{self}."
  super klass
  parent = self
  klass.class_eval do
    const_set(:Models, Module.new).
      module_eval { include parent::Models }
    const_set(:Views, Module.new).
      module_eval { include parent::Views }
    @adj_env = parent.adjust_env.dup
  end
end

.logObject

Builds a logging object if there’s not one yet, then returns it.



164
165
166
167
168
169
170
171
172
173
# File 'lib/maveric.rb', line 164

def log
  unless defined? @@maveric_logger
    @@maveric_logger = Log4r::Logger.new 'mvc'
    @@maveric_logger.outputters = Log4r::Outputter['stderr']
    @@maveric_logger.level = Log4r::INFO
    @@maveric_logger.info "#{self} #{::Maveric::VERSION}"+
      " integrated at #{Time.now}"
  end
  @@maveric_logger
end

.nested_controllers(realm = self, stk = []) ⇒ Object

A recursive method to hunt out subclasses of Controller nested within a Maveric subclass. Used in the setting up of autoroutes.



261
262
263
264
265
266
267
268
269
# File 'lib/maveric.rb', line 261

def nested_controllers realm=self, stk=[]
  stk << realm #We don't need to visit the same thing twice.
  realm.constants.map do |c|
    next if stk.include?(c = realm.const_get(c)) or not c.is_a? Module
    a = []
    a << c if c < ::Maveric::Controller
    a += nested_controllers c, stk
  end.compact.flatten
end

.parse_multipart(boundary, body) ⇒ Object

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.



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/maveric.rb', line 210

def parse_multipart boundary, body
  ::Maveric.type_check :body, body, IO, StringIO
  values = {}
  bound = /(?:\r?\n|\A)#{Regexp::quote('--'+boundary)}(?:--)?\r$/
  until body.eof?
    fv = {}
    until body.eof? or /^#{EOL}$/=~l
      case l = body.readline
      when /^Content-Type: (.+?)(\r$|\Z)/m
        fv[:type] = $1
      when /^Content-Disposition: form-data;/
        $'.scan(/(?:\s(\w+)="([^"]+)")/) {|w| fv[w[0].intern] = w[1] }
      end
    end

    o = unless fv[:filename] then ''
      else fv[:tempfile] = Tempfile.new('MVC').binmode end
    body.inject do |buf,line|
      o << buf.chomp and break if bound =~ line
      o << buf
      line
    end

    fv[:tempfile].rewind if fv.key? :tempfile
    values[fv[:name]] = fv.key?(:filename) ? fv : o
  end
  body.rewind
  values
end

.query_parse(qs, delim = '&;') ⇒ Object

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.



197
198
199
200
201
202
203
# File 'lib/maveric.rb', line 197

def query_parse(qs, delim = '&;')
  (qs||'').split(/[#{delim}] */n).inject(Hash.new([])) { |h,p|
    k, v = unescape(p).split('=',2)
    (h[k]||=[]) << v
    h
  }
end

.session(id) ⇒ Object

Return a session via Session#session



58
# File 'lib/maveric/sessions.rb', line 58

def self.session id; @sessions.session id; end

.sessionsObject

Return the standard session set.



56
# File 'lib/maveric/sessions.rb', line 56

def self.sessions; @sessions; end

.type_check(name, value, *klasses, &test) ⇒ Object

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.



245
246
247
248
249
250
251
252
253
254
# File 'lib/maveric.rb', line 245

def type_check name, value, *klasses, &test
  return unless $DEBUG
  # order of speed: #include?, #index, #any?
  if i = klasses.any?{|k|value.is_a? k} then return i
  elsif test and r = test[value] then return r
  else
    raise TypeError, "Expected #{klasses*' or '} for #{name},"+
      " got #{value.class}:#{value.inspect}."
  end
end

.unescape(s) ⇒ Object

Unescapes a URI escaped string.



183
184
185
186
187
# File 'lib/maveric.rb', line 183

def unescape(s)
  s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
    [$1.delete('%')].pack('H*')
  }
end

Instance Method Details

#env_1_session(env) ⇒ Object

Uses env to find the session_id, and retrives the data to set the valuse or env.



62
63
64
65
# File 'lib/maveric/sessions.rb', line 62

def env_1_session env
  session_id = env[:cookies][::Maveric::Sessions::COOKIE_NAME].first
  env[:session] = ::Maveric.session session_id 
end

#prepare_environment(env) ⇒ Object

The env argument should be a normal environment hash derived from HTTP.



367
368
369
370
371
372
373
# File 'lib/maveric.rb', line 367

def prepare_environment env
  ::Maveric.type_check :env, env, Hash
  env.update :maveric => self
  self.class.adjust_env.
    select {|act| act[:test].nil? or act[:test][env] }.
    each {|act| act[:do] ? act[:do][env] : __send__(act[:name], env) }
end

#process(req_body = $stdin, env = ENV, opts = {}) ⇒ Object

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.



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/maveric.rb', line 330

def process req_body=$stdin, env=ENV, opts={}
  ::Maveric.log.info "#{self.class}#process\n  "+
    "[#{Time.now}] #{env['REMOTE_ADDR']} => #{env['REQUEST_URI']}"
  ::Maveric.type_check :req_body, req_body, StringIO
  ::Maveric.type_check :env, env, Hash
  begin
    prepare_environment env unless env.key? :maveric
    raise RuntimeError, [404, "Page not found."] unless env[:route]

    # If this is a POST request we need to load data from the body
    if env['REQUEST_METHOD'] =~ /^post$/i
      env[:params].update env.key?(:multipart_boundary) ?
        parse_multipart(env[:multipart_boundary], req_body) :
        ::Maveric.query_parse(req_body.read)
    end

    unless env[:route][:controller] < ::Maveric::Controller
      raise TypeError, "Route Controller of "+
        "#{env[:route][:controller].class}. Expecting ::Maveric::Controller."
    end

    env[:route][:controller].new req_body, env, opts
  rescue # we catch exception.is_a? StandardError
    ::Maveric.log.error "#{Time.now}:\n#{$!.inspect}\n#{$!.backtrace*"\n"}"
    begin
      raise $! # this makes sense in a certain context...
      # Here is where we should have transformational stuffs for accessorizing
      # or customizing error pages. As long as someone doesn't get stupid
      # about it.
    rescue
      return $!
    end
  end
end