lib/angelo/base.rb in angelo-0.3.3 vs lib/angelo/base.rb in angelo-0.4.0
- old
+ new
@@ -1,26 +1,34 @@
module Angelo
class Base
+ extend Forwardable
include ParamsParser
include Celluloid::Logger
+ include Tilt::ERB
+ include Mustermann
- extend Forwardable
- def_delegators :@responder, :content_type, :headers, :redirect, :request, :transfer_encoding
+ def_delegators :@responder, :content_type, :headers, :mustermann, :redirect, :request, :transfer_encoding
def_delegators :@klass, :public_dir, :report_errors?, :sse_event, :sse_message, :sses, :websockets
attr_accessor :responder
class << self
attr_accessor :app_file, :server
def inherited subclass
- # set app_file from caller stack
+ # Set app_file by groveling up the caller stack until we find
+ # the first caller from a directory different from __FILE__.
+ # This allows base.rb to be required from an arbitrarily deep
+ # nesting of require "angelo/<whatever>" and still set
+ # app_file correctly.
#
- subclass.app_file = caller(1).map {|l| l.split(/:(?=|in )/, 3)[0,1]}.flatten[0]
+ subclass.app_file = caller_locations.map(&:absolute_path).find do |f|
+ !f.start_with?(File.dirname(__FILE__) + File::SEPARATOR)
+ end
# bring RequestError into this namespace
#
subclass.class_eval 'class RequestError < Angelo::RequestError; end'
@@ -28,20 +36,31 @@
subclass.port DEFAULT_PORT
subclass.ping_time DEFAULT_PING_TIME
subclass.log_level DEFAULT_LOG_LEVEL
+ # Parse command line options if angelo/main has been required.
+ # They could also be parsed in run, but this makes them
+ # available to and overridable by the DSL.
+ #
+ subclass.parse_options(ARGV.dup) if @angelo_main
+
class << subclass
def root
@root ||= File.expand_path '..', app_file
end
end
end
+ end
+ # Methods defined in module DSL will be available in the DSL both
+ # in Angelo::Base subclasses and in the top level DSL.
+
+ module DSL
def addr a = nil
@addr = a if a
@addr
end
@@ -74,78 +93,82 @@
def report_errors!
@report_errors = true
end
- def report_errors?
- !!@report_errors
+ HTTPABLE.each do |m|
+ define_method m do |path, opts = {}, &block|
+ path = ::Mustermann.new path, opts
+ routes[m][path] = Responder.new &block
+ end
end
- def compile! name, &block
- define_method name, &block
- method = instance_method name
- remove_method name
- method
+ def websocket path, &block
+ path = ::Mustermann.new path
+ routes[:websocket][path] = Responder::Websocket.new &block
end
- def routes
- @routes ||= Hash.new{|h,k| h[k] = {}}
+ def eventsource path, headers = nil, &block
+ path = ::Mustermann.new path
+ routes[:get][path] = Responder::Eventsource.new headers, &block
end
- def filters
- @filters ||= {before: {default: []}, after: {default: []}}
+ def task name, &block
+ Angelo::Server.define_task name, &block
end
- def filter which, opts = {}, &block
- f = compile! :filter, &block
- case opts
- when String
- filter_by which, opts, f
- when Hash
- if opts[:path]
- filter_by which, opts[:path], f
- else
- filters[which][:default] << f
- end
- end
- end
-
- def filter_by which, path, meth
- filters[which][path] ||= []
- filters[which][path] << meth
- end
-
def before opts = {}, &block
filter :before, opts, &block
end
def after opts = {}, &block
filter :after, opts, &block
end
- HTTPABLE.each do |m|
- define_method m do |path, &block|
- routes[m][path] = Responder.new &block
- end
+ def on_pong &block
+ Responder::Websocket.on_pong = block
end
- def websocket path, &block
- routes[:websocket][path] = Responder::Websocket.new &block
+ end
+
+ # Make the DSL methods available to subclass-level code.
+ # main.rb makes them available to the top level.
+
+ extend DSL
+
+ class << self
+ def report_errors?
+ !!@report_errors
end
- def eventsource path, headers = nil, &block
- routes[:get][path] = Responder::Eventsource.new headers, &block
+ def routes
+ @routes ||= Hash.new{|h,k| h[k] = RouteMap.new}
end
- def on_pong &block
- Responder::Websocket.on_pong = block
+ def filters
+ @filters ||= {before: {default: []}, after: {default: []}}
end
- def task name, &block
- Angelo::Server.define_task name, &block
+ def filter which, opts = {}, &block
+ case opts
+ when String
+ filter_by which, opts, block
+ when Hash
+ if opts[:path]
+ filter_by which, opts[:path], block
+ else
+ filters[which][:default] << block
+ end
+ end
end
+ def filter_by which, path, block
+ pattern = ::Mustermann.new path
+ filters[which][pattern] ||= []
+ filters[which][pattern] << block
+ end
+
def websockets reject = true
@websockets ||= Stash::Websocket.new server
@websockets.reject! &:closed? if reject
@websockets
end
@@ -213,12 +236,12 @@
def params
@params ||= case request.method
when GET, DELETE, OPTIONS
parse_query_string
when POST, PUT
- parse_post_body
- end
+ parse_query_string_and_post_body
+ end.merge mustermann.params(request.path)
end
def request_headers
@request_headers ||= Hash.new do |hash, key|
if Symbol === key
@@ -317,18 +340,77 @@
def sleep time
Celluloid.sleep time
end
- def filter which
- fs = self.class.filters[which][:default]
- fs += self.class.filters[which][request.path] if self.class.filters[which][request.path]
- fs.each {|f| f.bind(self).call}
- end
-
def chunked_response &block
transfer_encoding :chunked
ChunkedResponse.new &block
+ end
+
+ def filter which
+ self.class.filters[which].each do |pattern, filters|
+ case pattern
+ when :default
+ filters.each {|filter| instance_eval &filter}
+ when ::Mustermann
+ if pattern.match request.path
+ @pre_filter_params = params
+ @params = @pre_filter_params.merge pattern.params(request.path)
+ filters.each {|filter| instance_eval &filter}
+ @params = @pre_filter_params
+ end
+ end
+ end
+ end
+
+ # It seems more sensible to put this in main.rb since it's used
+ # only if angelo/main is required, but it's here so it can be
+ # tested, since requiring angelo/main doesn't play well with the
+ # test code.
+
+ def self.parse_options(argv)
+ require "optparse"
+
+ optparse = OptionParser.new do |op|
+ op.banner = "Usage: #{$0} [options]"
+
+ op.on('-p port', OptionParser::DecimalInteger, "set the port (default is #{port})") {|val| port val}
+ op.on('-o addr', "set the host (default is #{addr})") {|val| addr val}
+ op.on('-h', '--help', "Show this help") do
+ puts op
+ exit
+ end
+ end
+
+ begin
+ optparse.parse(argv)
+ rescue OptionParser::ParseError => ex
+ $stderr.puts ex
+ $stderr.puts optparse
+ exit 1
+ end
+ end
+
+ class RouteMap
+
+ def initialize
+ @hash = Hash.new
+ end
+
+ def []= route, responder
+ @hash[route] = responder
+ end
+
+ def [] route
+ responder = nil
+ if mustermann = @hash.keys.select {|k| k.match(route)}.first
+ responder = @hash.fetch mustermann
+ responder.mustermann = mustermann
+ end
+ responder
+ end
+
end
class EventSource
extend Forwardable