Class: Otto

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

Defined Under Namespace

Modules: RequestHelpers, ResponseHelpers, Static, VERSION Classes: Route

Constant Summary collapse

LIB_HOME =
File.expand_path File.dirname(__FILE__)

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path = nil, opts = {}) ⇒ Otto

Returns a new instance of Otto.



30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/otto.rb', line 30

def initialize path=nil, opts={}
  @routes_static =  { :GET => {} }
  @routes =         { :GET => [] }
  @routes_literal = { :GET => {} }
  @route_definitions = {}
  @option = opts.merge({
    :public => nil,
    :locale => 'en'
  })
  load(path) unless path.nil?
  super()
end

Class Attribute Details

.debugObject

Returns the value of attribute debug.



325
326
327
# File 'lib/otto.rb', line 325

def debug
  @debug
end

Instance Attribute Details

#not_foundObject

Returns the value of attribute not_found.



29
30
31
# File 'lib/otto.rb', line 29

def not_found
  @not_found
end

#optionObject (readonly) Also known as: options

Returns the value of attribute option.



28
29
30
# File 'lib/otto.rb', line 28

def option
  @option
end

#route_definitionsObject (readonly)

Returns the value of attribute route_definitions.



27
28
29
# File 'lib/otto.rb', line 27

def route_definitions
  @route_definitions
end

#routesObject (readonly)

Returns the value of attribute routes.



27
28
29
# File 'lib/otto.rb', line 27

def routes
  @routes
end

#routes_literalObject (readonly)

Returns the value of attribute routes_literal.



27
28
29
# File 'lib/otto.rb', line 27

def routes_literal
  @routes_literal
end

#routes_staticObject (readonly)

Returns the value of attribute routes_static.



27
28
29
# File 'lib/otto.rb', line 27

def routes_static
  @routes_static
end

#server_errorObject

Returns the value of attribute server_error.



29
30
31
# File 'lib/otto.rb', line 29

def server_error
  @server_error
end

#static_routeObject (readonly)

Returns the value of attribute static_route.



28
29
30
# File 'lib/otto.rb', line 28

def static_route
  @static_route
end

Class Method Details

.defaultObject



326
327
328
329
# File 'lib/otto.rb', line 326

def default
  @default ||= Otto.new
  @default
end

.env?(*guesses) ⇒ Boolean

Returns:

  • (Boolean)


339
340
341
# File 'lib/otto.rb', line 339

def env? *guesses
  !guesses.flatten.select { |n| ENV['RACK_ENV'].to_s == n.to_s }.empty?
end

.load(path) ⇒ Object



330
331
332
# File 'lib/otto.rb', line 330

def load path
  default.load path
end

.path(definition, params = {}) ⇒ Object



333
334
335
# File 'lib/otto.rb', line 333

def path definition, params={}
  default.path definition, params
end

.routesObject



336
337
338
# File 'lib/otto.rb', line 336

def routes
  default.routes
end

Instance Method Details

#add_static_path(path) ⇒ Object



76
77
78
79
80
81
82
83
84
85
# File 'lib/otto.rb', line 76

def add_static_path path
  if safe_file?(path)
    base_path = File.split(path).first
    # Files in the root directory can refer to themselves
    base_path = path if base_path == '/'
    static_path = File.join(option[:public], base_path)
    STDERR.puts "new static route: #{base_path} (#{path})" if Otto.debug
    routes_static[:GET][base_path] = base_path
  end
end

#call(env) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/otto.rb', line 87

def call env
  locale = determine_locale env
  env['rack.locale'] = locale
  if option[:public] && safe_dir?(option[:public])
    @static_route ||= Rack::File.new(option[:public])
  end
  path_info = Rack::Utils.unescape(env['PATH_INFO'])
  path_info = '/' if path_info.to_s.empty?
  path_info_clean = path_info.gsub /\/$/, ''
  base_path = File.split(path_info).first
  # Files in the root directory can refer to themselves
  base_path = path_info if base_path == '/'
  http_verb = env['REQUEST_METHOD'].upcase.to_sym
  literal_routes = routes_literal[http_verb] || {}
  literal_routes.merge! routes_literal[:GET] if http_verb == :HEAD
  if static_route && http_verb == :GET && routes_static[:GET].member?(base_path)
    #STDERR.puts " request: #{path_info} (static)"
    static_route.call(env)
  elsif literal_routes.has_key?(path_info_clean)
    route = literal_routes[path_info_clean]
    #STDERR.puts " request: #{http_verb} #{path_info} (literal route: #{route.verb} #{route.path})"
    route.call(env)
  elsif static_route && http_verb == :GET && safe_file?(path_info)
    static_path = File.join(option[:public], base_path)
    STDERR.puts " new static route: #{base_path} (#{path_info})"
    routes_static[:GET][base_path] = base_path
    static_route.call(env)
  else
    extra_params = {}
    found_route = nil
    valid_routes = routes[http_verb] || []
    valid_routes.push *routes[:GET] if http_verb == :HEAD
    valid_routes.each { |route|
      #STDERR.puts " request: #{http_verb} #{path_info} (trying route: #{route.verb} #{route.pattern})"
      if (match = route.pattern.match(path_info))
        values = match.captures.to_a
        # The first capture returned is the entire matched string b/c
        # we wrapped the entire regex in parens. We don't need it to
        # the full match.
        full_match = values.shift
        extra_params =
          if route.keys.any?
            route.keys.zip(values).inject({}) do |hash,(k,v)|
              if k == 'splat'
                (hash[k] ||= []) << v
              else
                hash[k] = v
              end
              hash
            end
          elsif values.any?
            {'captures' => values}
          else
            {}
          end
          found_route = route
          break
      end
    }
    found_route ||= literal_routes['/404']
    if found_route
      found_route.call env, extra_params
    else
      @not_found || Otto::Static.not_found
    end
  end
rescue => ex
  STDERR.puts ex.message, ex.backtrace
  if found_route = literal_routes['/500']
    found_route.call env
  else
    @server_error || Otto::Static.server_error
  end
end

#determine_locale(env) ⇒ Object



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/otto.rb', line 188

def determine_locale env
  accept_langs = env['HTTP_ACCEPT_LANGUAGE']
  accept_langs = self.option[:locale] if accept_langs.to_s.empty?
  locales = []
  unless accept_langs.empty?
    locales = accept_langs.split(',').map { |l|
      l += ';q=1.0' unless l =~ /;q=\d+(?:\.\d+)?$/
      l.split(';q=')
    }.sort_by { |locale, qvalue|
      qvalue.to_f
    }.collect { |locale, qvalue|
      locale
    }.reverse
  end
  STDERR.puts "locale: #{locales} (#{accept_langs})" if Otto.debug
  locales.empty? ? nil : locales
end

#load(path) ⇒ Object

Raises:

  • (ArgumentError)


43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/otto.rb', line 43

def load path
  path = File.expand_path(path)
  raise ArgumentError, "Bad path: #{path}" unless File.exist?(path)
  raw = File.readlines(path).select { |line| line =~ /^\w/ }.collect { |line| line.strip.split(/\s+/) }
  raw.each { |entry|
    begin
      verb, path, definition = *entry
      route = Otto::Route.new verb, path, definition
      route.otto = self
      path_clean = path.gsub /\/$/, ''
      @route_definitions[route.definition] = route
      STDERR.puts "route: #{route.pattern}" if Otto.debug
      @routes[route.verb] ||= []
      @routes[route.verb] << route
      @routes_literal[route.verb] ||= {}
      @routes_literal[route.verb][path_clean] = route
    rescue => ex
      STDERR.puts "Bad route in #{path}: #{entry}"
    end
  }
  self
end

#safe_dir?(path) ⇒ Boolean

Returns:

  • (Boolean)


72
73
74
# File 'lib/otto.rb', line 72

def safe_dir? path
  (File.owned?(path) || File.grpowned?(path)) && File.directory?(path)
end

#safe_file?(path) ⇒ Boolean

Returns:

  • (Boolean)


66
67
68
69
70
# File 'lib/otto.rb', line 66

def safe_file? path
  globstr = File.join(option[:public], '*')
  pathstr = File.join(option[:public], path)
  File.fnmatch?(globstr, pathstr) && (File.owned?(pathstr) || File.grpowned?(pathstr)) && File.readable?(pathstr) && !File.directory?(pathstr)
end

#uri(route_definition, params = {}) ⇒ Object

Return the URI path for the given route_definition e.g.

Otto.default.path 'YourClass.somemethod'  #=> /some/path


168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/otto.rb', line 168

def uri route_definition, params={}
  #raise RuntimeError, "Not working"
  route = @route_definitions[route_definition]
  unless route.nil?
    local_params = params.clone
    local_path = route.path.clone
    if objid = local_params.delete(:id) || local_params.delete('id')
      local_path.gsub! /\*/, objid
    end
    local_params.each_pair { |k,v|
      next unless local_path.match(":#{k}")
      local_path.gsub!(":#{k}", local_params.delete(k))
    }
    uri = Addressable::URI.new
    uri.path = local_path
    uri.query_values = local_params
    uri.to_s
  end
end