Class: Gopher::Application

Inherits:
Object
  • Object
show all
Defined in:
lib/gopher2000/base.rb

Overview

main application class for a gopher server. holds all the methods/data required to interact with clients.

Constant Summary collapse

ACCESS_LOG_PATTERN =

The output pattern we will use to generate access logs

"%d\t%m\n"
@@access_log =
nil
@@debug_log =
nil

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#configObject

Returns the value of attribute config.



20
21
22
# File 'lib/gopher2000/base.rb', line 20

def config
  @config
end

#last_reloadObject

Returns the value of attribute last_reload.



20
21
22
# File 'lib/gopher2000/base.rb', line 20

def last_reload
  @last_reload
end

Returns the value of attribute menus.



20
21
22
# File 'lib/gopher2000/base.rb', line 20

def menus
  @menus
end

#paramsObject

Returns the value of attribute params.



20
21
22
# File 'lib/gopher2000/base.rb', line 20

def params
  @params
end

#requestObject

Returns the value of attribute request.



20
21
22
# File 'lib/gopher2000/base.rb', line 20

def request
  @request
end

#routesObject

Returns the value of attribute routes.



20
21
22
# File 'lib/gopher2000/base.rb', line 20

def routes
  @routes
end

#scriptsObject

Returns the value of attribute scripts.



20
21
22
# File 'lib/gopher2000/base.rb', line 20

def scripts
  @scripts
end

#text_templatesObject

Returns the value of attribute text_templates.



20
21
22
# File 'lib/gopher2000/base.rb', line 20

def text_templates
  @text_templates
end

Class Method Details

.generate_method(method_name) { ... } ⇒ Object

generate a method which we will use to run routes. this is based on #generate_method as used by sinatra.

Parameters:

  • method_name (String)

    name to use for the method

Yields:

  • block to use for the method

See Also:



453
454
455
456
457
458
# File 'lib/gopher2000/base.rb', line 453

def generate_method(method_name, &block)
  define_method(method_name, &block)
  method = instance_method method_name
  remove_method method_name
  method
end

.sanitize_selector(raw) ⇒ Object

Sanitizes a gopher selector



437
438
439
440
441
442
443
444
445
# File 'lib/gopher2000/base.rb', line 437

def sanitize_selector(raw)
		"/#{raw}".dup.
 strip. # Strip whitespace
 sub(/\/$/, ''). # Strip last rslash
 sub(/^\/*/, '/'). # Strip extra lslashes
 gsub(/\.+/, '.') # Don't want consecutive dots!

		#raw = "/#{raw}" if ! raw.match(/^\//)
end

Instance Method Details

#compile(path) ⇒ Object

turn a path string with optional keys (/foo/:bar/:boo) into a regexp which will be used when searching for a route

Parameters:

  • path (String)

    the path to compile



416
417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/gopher2000/base.rb', line 416

def compile(path)
  keys = []
  pattern = path.to_str.gsub(/[^\?\%\\\/\:\*\w]/) { |c| encoded(c) }
  pattern.gsub!(/((:\w+)|\*)/) do |match|
    if match == "*"
      keys << 'splat'
      "(.*?)"
    else
      keys << $2[1..-1]
      "([^/?#]+)"
    end
  end
  [/^#{pattern}$/, keys]
end

#compile!(path, &block) ⇒ Object

compile a route



402
403
404
405
406
407
408
# File 'lib/gopher2000/base.rb', line 402

def compile!(path, &block)
  method_name = path
  route_method = Application.generate_method(method_name, &block)
  pattern, keys = compile path

  [ pattern, keys, route_method ]
end

#debug_log(x) ⇒ Object

output a debugging message



464
465
466
467
# File 'lib/gopher2000/base.rb', line 464

def debug_log(x)
  @@debug_logger ||= ::Logging.logger(STDERR)
  @@debug_logger.debug x
end

#debug_mode?Boolean

are we in debugging mode?

Returns:

  • (Boolean)


60
61
62
# File 'lib/gopher2000/base.rb', line 60

def debug_mode?
  config[:debug] == true
end

#default_route { ... } ⇒ Object

specify a default route to handle requests if no other route exists

Examples:

render a template

default_route do
  render :template
end

Yields:

  • a block to handle the default route



154
155
156
# File 'lib/gopher2000/base.rb', line 154

def default_route(&block)
  @default_route = Application.generate_method("DEFAULT_ROUTE", &block)
end

#dispatch(req) ⇒ Object

find and run the first route which matches the incoming request

Parameters:

  • req (Request)

    Gopher::Request object



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/gopher2000/base.rb', line 195

def dispatch(req)
  debug_log(req)

  response = Response.new
  @request = req

  if ! @request.valid?
    response.body = handle_invalid_request
    response.code = :error
  else
    begin
      debug_log("do lookup for #{@request.selector}")
      @params, block = lookup(@request.selector)

      #
      # call the block that handles this lookup
      #
      response.body = block.bind(self).call
      response.code = :success
    rescue Gopher::NotFoundError => e
      debug_log("#{@request.selector} -- not found")
      response.body = handle_not_found
      response.code = :missing
    rescue Exception => e
      debug_log("#{@request.selector} -- error")
      debug_log(e.inspect)
      debug_log(e.backtrace)

      response.body = handle_error(e)
      response.code = :error
    end
  end

  access_log(req, response)
  response
end

#error_templateObject

get the id of the template that will be used when rendering an error

Returns:

  • name of error template



346
347
348
# File 'lib/gopher2000/base.rb', line 346

def error_template
  menus.include?(:error) ? :error : :'internal/error'
end

#find_template(t) ⇒ Object

find a template

Parameters:

  • t (String/Symbol)

    name of the template

Returns:

  • template block and the class context it should use



301
302
303
304
305
306
307
308
309
310
# File 'lib/gopher2000/base.rb', line 301

def find_template(t)
  x = menus[t]
  if x
    return x, Gopher::Rendering::Menu
  end
  x = text_templates[t]
  if x
    return x, Gopher::Rendering::Text
  end
end

#globify(p) ⇒ Object

add a glob to the end of this string, if there’s not one already



395
396
397
# File 'lib/gopher2000/base.rb', line 395

def globify(p)
  p =~ /\*/ ? p : "#{p}/?*".gsub("//", "/")
end

#helpers(target = Gopher::Application, &block) ⇒ Object

Add helpers to the Base renedering class, which allows them to be called when outputting the results of an action. Here’s the code in Sinatra for reference:

Makes the methods defined in the block and in the Modules given in ‘extensions` available to the handlers and templates

def helpers(*extensions, &block)
  class_eval(&block)   if block_given?
  include(*extensions) if extensions.any?
end

target - What class should receive the helpers – defaults to Gopher::Rendering::Base, which will make it available when rendering block – a block which declares the helpers you want. for example:

helpers do

def foo; "FOO"; end

end



377
378
379
# File 'lib/gopher2000/base.rb', line 377

def helpers(target = Gopher::Application, &block)
  target.class_eval(&block)
end

#hostObject

return the host we will use when outputting gopher menus



46
47
48
# File 'lib/gopher2000/base.rb', line 46

def host
  config[:host] ||= '0.0.0.0'
end

#invalid_request_templateObject

get the id of the template that will be used when rendering an invalid request

Returns:

  • name of invalid_request template



355
356
357
# File 'lib/gopher2000/base.rb', line 355

def invalid_request_template
  menus.include?(:invalid_request) ? :invalid_request : :'internal/invalid_request'
end

#lookup(selector) ⇒ Object

lookup an incoming path

Parameters:

  • selector (String)

    the selector path of the incoming request

Raises:



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/gopher2000/base.rb', line 163

def lookup(selector)
  unless routes.nil?
		routes.each do |pattern, keys, block|

      if match = pattern.match(selector)
        match = match.to_a
        url = match.shift

        params = to_params_hash(keys, match)

        #
        # @todo think about this
        #
        @params = params

        return params, block
      end
    end
  end

  unless @default_route.nil?
    return {}, @default_route
  end

  raise Gopher::NotFoundError
end

define a template which will be used to render a gopher-style menu.

Examples:

a simple menu:

menu :index do
  # output a text entry in the menu
  text 'simple gopher example'

  # use br(x) to add x space between lines
  br(2)

  # link somewhere
  link 'current time', '/time'
  br

  # another link
  link 'about', '/about'
  br

  # ask for some input
  input 'Hey, what is your name?', '/hello'
  br

  # mount some files
  menu 'filez', '/files'
end

Parameters:

  • name (String/Symbol)

    the name of the template. This is what identifies the template when making a call to render

Yields:

  • a block which will output the menu. This block is executed within an instance of Gopher::Rendering::Menu and will have access to all of its methods.



266
267
268
# File 'lib/gopher2000/base.rb', line 266

def menu(name, &block)
  menus[name.to_sym] = block
end

#mount(path, opts = {}, klass = Gopher::Handlers::DirectoryHandler) ⇒ Object

Examples:

mount the directory ‘/home/user/foo’ at the gopher path ‘/files’, and only show JPG files:

mount '/files' => '/home/user/foo', :filter => '*.jpg'


101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/gopher2000/base.rb', line 101

def mount(path, opts = {}, klass = Gopher::Handlers::DirectoryHandler)
  debug_log "MOUNT #{path} #{opts.inspect}"
  opts[:mount_point] = path

  handler = klass.new(opts)
  handler.application = self

  #
  # add a route for the mounted class
  #
  route(globify(path)) do
    # when we call, pass the params and request object for this
    # particular request
    handler.call(params, request)
  end
end

#non_blocking?Boolean

should we use non-blocking operations? for now, defaults to false if in debug mode, true if we’re not in debug mode (presumably, in some sort of production state. HAH! Gopher servers in production)

Returns:

  • (Boolean)


387
388
389
# File 'lib/gopher2000/base.rb', line 387

def non_blocking?
  config[:non_blocking] ||= ! debug_mode?
end

#not_found(&block) ⇒ Object

specify a template to be used for missing requests



292
293
294
# File 'lib/gopher2000/base.rb', line 292

def not_found(&block)
  menu :not_found, &block
end

#not_found_templateObject

get the id of the template that will be used when rendering a not found error

Returns:

  • name of not_found template



338
339
340
# File 'lib/gopher2000/base.rb', line 338

def not_found_template
  menus.include?(:not_found) ? :not_found : :'internal/not_found'
end

#portObject

return the port we will use when outputting gopher menus



53
54
55
# File 'lib/gopher2000/base.rb', line 53

def port
  config[:port] ||= 70
end

#reload_staleObject

reload scripts if needed



77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/gopher2000/base.rb', line 77

def reload_stale
  reload_check = should_reload?
  self.last_reload = Time.now

  return if ! reload_check
  reset!

  self.scripts.each do |f|
    debug_log "reload #{f}"
    load f
  end
end

#render(template, *arguments) ⇒ Object

Find the desired template and call it within the proper context

Parameters:

  • template (String/Symbol)

    name of the template to render

  • arguments (Array)

    optional arguments to be passed to template

Returns:

  • result of rendering

Raises:



318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/gopher2000/base.rb', line 318

def render(template, *arguments)
  #
  # find the right renderer we need
  #
  block, handler = find_template(template)

  raise TemplateNotFound if block.nil?

  ctx = handler.new(self)
  ctx.params = @params
  ctx.request = @request

  ctx.instance_exec(*arguments, &block)
end

#reset!Object

reset the app. clear out any routes, templates, config values, etc. this is used during the load process



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/gopher2000/base.rb', line 27

def reset!
  self.routes = []
  self.menus = {}
  self.text_templates = {}
  self.scripts ||= []
  self.config ||= {
    :debug => false,
    :host => "0.0.0.0",
    :port => 70
  }

  register_defaults

  self
end

#route(path) { ... } ⇒ Object

define a route.

Examples:

respond with a simple string

route '/path' do
  "hi, welcome to /path"
end

respond by rendering a template

route '/render' do
  render :template
end

Parameters:

  • path (String)

    the path your route will answer to. This is basically a URI path

Yields:

  • a block that handles your route



134
135
136
137
138
139
140
141
142
# File 'lib/gopher2000/base.rb', line 134

def route(path, &block)
  selector = Gopher::Application.sanitize_selector(path)
  sig = compile!(selector, &block)

  debug_log("Add route for #{selector}")

  self.routes ||= []
  self.routes << sig
end

#should_reload?Boolean

check if our script has been updated since the last reload

Returns:

  • (Boolean)


68
69
70
71
72
# File 'lib/gopher2000/base.rb', line 68

def should_reload?
  ! last_reload.nil? && self.scripts.any? do |f|
    File.mtime(f) > last_reload
  end
end

#text(name) { ... } ⇒ Object

Define a template which will be used for outputting text. This is not strictly required for outputting text, but it gives you access to the methods defined in Gopher::Rendering::Text for wrapping strings, adding simple headers, etc.

Examples:

simple example

text :hello do
  big_header "Hello There!"
  block "Really long text... ... the end"
  br
end

Parameters:

  • name (String/Symbol)

    the name of the template. This is what identifies the template when making a call to render

Yields:

  • a block which will output the menu. This block is executed within an instance of Gopher::Rendering::Text and will have access to all of its methods.



285
286
287
# File 'lib/gopher2000/base.rb', line 285

def text(name, &block)
  text_templates[name.to_sym] = block
end