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
# 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
  })
  load(path) unless path.nil?
  super()
end

Class Attribute Details

.debugObject

Returns the value of attribute debug.



304
305
306
# File 'lib/otto.rb', line 304

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



305
306
307
308
# File 'lib/otto.rb', line 305

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

.env?(*guesses) ⇒ Boolean

Returns:

  • (Boolean)


318
319
320
# File 'lib/otto.rb', line 318

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

.load(path) ⇒ Object



309
310
311
# File 'lib/otto.rb', line 309

def load path
  default.load path
end

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



312
313
314
# File 'lib/otto.rb', line 312

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

.routesObject



315
316
317
# File 'lib/otto.rb', line 315

def routes
  default.routes
end

Instance Method Details

#add_static_path(path) ⇒ Object



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

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



86
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
# File 'lib/otto.rb', line 86

def call env
  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

#load(path) ⇒ Object

Raises:

  • (ArgumentError)


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

def load path
  path = File.expand_path(path)
  raise ArgumentError, "Bad path: #{path}" unless File.exists?(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)


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

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

#safe_file?(path) ⇒ Boolean

Returns:

  • (Boolean)


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

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


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

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