Skip to content

Commit

Permalink
merged
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason Rush committed Jan 6, 2009
2 parents 2518fa0 + f4313a2 commit 982d16c
Show file tree
Hide file tree
Showing 18 changed files with 590 additions and 22 deletions.
10 changes: 10 additions & 0 deletions lib/ext/object.rb
Expand Up @@ -14,6 +14,16 @@ class << self; self end.class_eval{ undef_method(mname) } rescue nil
end
ret
end

def cache_method_missing(name, method_body, *args, &block)
self.class.module_eval <<-METHOD
def #{name}(*args, &block)
#{method_body}
end
METHOD
self.send(name, *args, &block)
end

end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/matchers/path.rb
Expand Up @@ -54,7 +54,7 @@ def call( request )
def extract_path( request )
path = request.traits.waves.path
return path if path
path = request.path.split('/')
path = request.path.split('/').map { |e| Rack::Utils.unescape(e) }
path.shift unless path.empty?
request.traits.waves.path = path
end
Expand Down
4 changes: 3 additions & 1 deletion lib/resources/mixin.rb
Expand Up @@ -98,7 +98,9 @@ def to( resource )
when Symbol, String
begin
Waves.main::Resources[ resource ]
rescue NameError
rescue NameError => e
Waves::Logger.debug e.to_s
e.backtrace.each { |t| Waves::Logger.debug " #{t}" }
raise Waves::Dispatchers::NotFoundError
end
Waves.main::Resources[ resource ]
Expand Down
118 changes: 108 additions & 10 deletions lib/resources/paths.rb
Expand Up @@ -4,30 +4,128 @@ module Resources

class Paths

attr_accessor :request
def self.compiled; @compiled ||= {} ; end

include Waves::ResponseMixin

def initialize( request ) ; @request = request ; end
def compiled_paths; self.class.compiled ; end

def generate( template, args )
return "/#{ args * '/' }" unless template.is_a?( Array ) and not template.empty?
return "/" if template.empty?
if template.is_a? Array
if args.size == 1 and args.first.is_a? Hash
process_hash( template, args.first )
else
process_array( template, args)
end
else
"/#{ args * '/' }"
end
end

def process_array( template, args )
template_key = template
compiled = compiled_paths[template_key]
if compiled
return ( compiled % args ) rescue raise [template, args].inspect
end
compilable = true
cpath, interpolations = "", []
result = ( cpath % interpolations ) if template.all? do | want |
case want
when Symbol
cpath << "/%s" ; interpolations << args.shift
when String
cpath << "/#{want}"
when true
compilable = false
cpath += "/#{args.join("/")}"; args = []
when Hash
compilable = false
key, value = want.to_a.first
case value
when true
cpath += "/#{args.join("/")}"; args = []
when String, Symbol
compilable = true
component = args.shift
cpath << "/%s"
component ? interpolations << component : interpolations << value
when Regexp
component = args.shift.to_s
raise ArgumentError, "#{component} does not match #{want.inspect}" unless component =~ value
cpath << "/%s"; interpolations << component
end
when Regexp
compilable = false
component = args.shift.to_s
raise ArgumentError, "#{component} does not match #{want.inspect}" unless component =~ want
cpath << "/%s"; interpolations << component
end
end
raise ArgumentError, "Too many args" unless args.empty?
compiled_paths[template_key] = cpath if compilable
result
end

def process_hash( template, hash )
path = []
( "/#{ path * '/' }" ) if template.all? do | want |
( "/#{ path * '/' }" ) if template.all? do |want|
case want
when true then path += args
when Symbol
raise ArgumentError, "Path generator needs a value for #{want.inspect}" unless component = hash[want]
path << component
when String then path << want
when Symbol then path << args.shift
when Regexp then path << args.shift
when Hash
key, value = want.to_a.first
case value
when Regexp
raise ArgumentError, "Path generator needs a value for #{want.inspect}" unless component = hash[key]
raise ArgumentError, "#{component} does not match #{want.inspect}" unless component =~ value
path << component
when String, Symbol
hash.has_key?(key) ? path << hash[key] : path << value
when true
raise ArgumentError, "Path generator needs a value for #{want.inspect}" unless component = hash[key]
path += [component].flatten
end
when Regexp, true
raise ArgumentError, "Path generator can't take an args hash, as it contains a Regexp or the value true"
end
end
end

def original_generate( template, args )
if template.is_a? Array and not template.empty?
path = []
( "/#{ path * '/' }" ) if template.all? do | want |
case want
when true then path += args
when String, Symbol, RegExp then path << args.unshift
when String then path << want
when Symbol then path << args.shift
when Regexp
component = args.shift.to_s
raise ArgumentError, "#{component} does not match #{want.inspect}" unless component =~ want
path << component
when Hash
key, value = want.to_a.first
case value
when true then path += args
when String, Symbol
# if no args to interpolate, use hash element value as default
!args.empty? ? path << args.shift : path << value
when Regexp
component = args.shift.to_s
raise ArgumentError, "#{component} does not match #{want.inspect}" unless component =~ value
path << component
end
end
end
else
"/#{ args * '/' }"
end
end



end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/runtime/logger.rb
Expand Up @@ -26,7 +26,7 @@ def self.start

# Forwards logging methods to the logger.
def self.method_missing(name,*args,&block)
@log.send name,*args, &block if @log
cache_method_missing name, "@log.#{name} *args, &block if @log", *args, &block
end

end
Expand Down
12 changes: 9 additions & 3 deletions lib/runtime/request.rb
Expand Up @@ -53,10 +53,16 @@ def []( key ) ; @request.env[ key.to_s.upcase ] ; end

# access HTTP headers as methods
def method_missing( name, *args, &body )
return super unless args.empty? and body.nil?
key = "HTTP_#{name.to_s.upcase}"
@request.env[ key ] if @request.env.has_key?( key )
if args.empty? and not body
cache_method_missing name, <<-CODE, *args, &body
key = "HTTP_#{name.to_s.upcase}"
@request.env[ key ] if @request.env.has_key?( key )
CODE
else
super
end
end


# Raise a not found exception.
def not_found
Expand Down
2 changes: 1 addition & 1 deletion lib/runtime/response.rb
Expand Up @@ -36,7 +36,7 @@ def finish ; @response.finish ; end
# Methods not explicitly defined by Waves::Response are delegated to Rack::Response.
# Check the Rack documentation for more informations
def method_missing(name,*args)
@response.send(name,*args)
cache_method_missing name, "@response.#{name} *args", *args
end

end
Expand Down
4 changes: 2 additions & 2 deletions lib/runtime/response_mixin.rb
Expand Up @@ -10,7 +10,7 @@ module ResponseMixin
# Access the response.
def response; request.response; end

def resource ; traits.waves.resource ; end
def resource; traits.waves.resource || ( self if self.kind_of? Waves::Resources::Mixin ) ; end

def traits ; request.traits ; end

Expand Down Expand Up @@ -39,7 +39,7 @@ def log; Waves::Logger; end
def app_name ; self.class.rootname.snake_case.to_sym ; end
def app ; eval( "::#{app_name.to_s.camel_case}" ) ; end
def paths( rname = nil )
( rname.nil? ? resource.class.paths : app::Resources[ rname ].paths ).new( request )
( rname ? app::Resources[ rname ].paths : resource.class.paths ).new
end

# these take strings or operate on the path by default
Expand Down
4 changes: 3 additions & 1 deletion lib/runtime/runtime.rb
Expand Up @@ -25,7 +25,9 @@ def self.instance ; Waves::Runtime.instance ; end
def self.version ; File.read( File.expand_path( "#{File.dirname(__FILE__)}/../../doc/VERSION" ) ) ; end
def self.license ; File.read( File.expand_path( "#{File.dirname(__FILE__)}/../../doc/LICENSE" ) ) ; end

def self.method_missing(name,*args,&block) ; instance.send(name,*args,&block) ; end
def self.method_missing(name,*args,&block)
cache_method_missing name, "instance.#{name}( *args, &block)", *args, &block
end

# A Waves::Runtime takes an inert application module and gives it concrete, pokeable form.
# Waves::Server and Waves::Console are types of runtime.
Expand Down
55 changes: 55 additions & 0 deletions test/ext/object.rb
@@ -0,0 +1,55 @@
require "#{File.dirname(__FILE__)}/../helpers.rb"

describe "Object#cache_method_missing" do

before do
class A; end
end

after do
Object.instance_eval { remove_const(:A) if const_defined?(:A) }
end

it "defines the missing method" do
A.module_eval do
def method_missing(name, *args)
cache_method_missing name, "'hi'", *args
end
end
A.new.bar
A.new.should.respond_to :bar
end

it "passes along the args" do
A.module_eval do
def method_missing(name, *args, &block)
cache_method_missing name, "args.join('-')", *args
end
end

A.new.bar(1, 2, 3).should == "1-2-3"
end

it "passes along the block" do
A.module_eval do
def method_missing(name, *args, &block)
cache_method_missing name, "block.call", *args, &block
end
end

A.new.bar { 'bye' }.should == 'bye'
end

end

describe "Object#instance_exec" do

before do
@block = lambda { |guy| "Howdy, #{guy}!" }
end

it "works like instance_eval, but it takes args and gives them to the block" do
instance_exec( "Steve", &@block ).should == "Howdy, Steve!"
end

end
73 changes: 73 additions & 0 deletions test/ext/shortcuts.rb
@@ -0,0 +1,73 @@
require "#{File.dirname(__FILE__)}/../helpers.rb"

describe "Waves::Ext::String" do
it "defines / as syntactic sugar for File.join" do
( "lib" / "utilities" ).should == File.join( "lib", "utilities" )
( "lib" / :utilities ).should == File.join( "lib", "utilities" )
( "lib" / 3 ).should == File.join( "lib", "3" )
end
end

describe "A monkeypatch to Symbol" do

it "defines / as syntactic sugar for File.join" do
( :lib / :utilities ).should == File.join( "lib", "utilities")
end

end

describe "Waves::Ext::Hash" do

it "adds a non-destructive method for converting all hash keys to strings" do
h = { :a => 1, 'b' => 2, 3 => 3}
h.stringify_keys.should == { 'a' => 1, 'b' => 2, '3' => 3}
h.should == { :a => 1, 'b' => 2, 3 => 3}
end

it "adds a destructive method for converting hash keys to symbols" do
h = { "two" => 2, :three => 3}
h.symbolize_keys!
h.should == { :two => 2, :three => 3 }
end

end

describe "Waves::Ext::Integer" do

it "has an absolutely pedantic amount of helpers" do
1.kilobytes.should == "1_024".to_i
1.megabytes.should == "1_048_576".to_i
1.gigabytes.should == "1_073_741_824".to_i
1.terabytes.should == "1_099_511_627_776".to_i
1.petabytes.should == "1_125_899_906_842_624".to_i
1.exabytes.should == "1_152_921_504_606_846_976".to_i
1.zettabytes.should == "1_180_591_620_717_411_303_424".to_i
1.yottabytes.should == "1_208_925_819_614_629_174_706_176".to_i
end

end

describe "Waves::Ext::Module" do

before do
module Eenie; module Meenie; module Miney; end; end ; end
end

it "defines a basename method" do
Eenie::Meenie.basename.should == "Meenie"
Eenie::Meenie::Miney.basename.should == "Miney"
end

it "defines [] for easy access to namespaced constants" do
Eenie[:Meenie].should == Eenie::Meenie
end

it "defines a method for obtaining the outermost constant name" do
Eenie::Meenie::Miney.rootname.should == "Eenie"
end

it "defines a method for obtaining the outermost constant" do
Eenie::Meenie::Miney.root.should == Eenie
end

end
2 changes: 2 additions & 0 deletions test/helpers.rb
Expand Up @@ -12,3 +12,5 @@
alias_method :specification, :describe
alias_method :feature, :it
end

Bacon.summary_on_exit

0 comments on commit 982d16c

Please sign in to comment.