renee-url-generation 0.4.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,66 @@
1
+ module Renee
2
+ class Core
3
+ # Module for creating chainable methods. To use this within your own modules, first `include Chaining`, then,
4
+ # mark methods you want to be available with `chain_method :method_name`.
5
+ # @example
6
+ # module MoreRoutingMethods
7
+ # include Chaining
8
+ # def other_routing_method
9
+ # # ..
10
+ # end
11
+ # chain_method :other_routing_method
12
+ #
13
+ module Chaining
14
+ # @private
15
+ class ChainingProxy
16
+ def initialize(target, m, args = nil)
17
+ @target, @calls = target, []
18
+ @calls << [m, args]
19
+ end
20
+
21
+ def method_missing(m, *args, &blk)
22
+ @calls << [m, args]
23
+ if blk.nil? && @target.class.respond_to?(:chainable?) && @target.class.chainable?(m)
24
+ self
25
+ else
26
+ inner_args = []
27
+ ret = nil
28
+ callback = proc do |*callback_args|
29
+ inner_args.concat(callback_args)
30
+ if @calls.size == 0
31
+ ret = blk.call(*inner_args) if blk
32
+ else
33
+ call = @calls.shift
34
+ ret = call.at(1) ? @target.send(call.at(0), *call.at(1), &callback) : @target.send(call.at(0), &callback)
35
+ end
36
+ end
37
+ ret = callback.call
38
+ ret
39
+ end
40
+ end
41
+ end
42
+
43
+ # @private
44
+ module ClassMethods
45
+ def chainable?(m)
46
+ method_defined?(:"#{m}_chainable")
47
+ end
48
+
49
+ def chainable(*methods)
50
+ methods.each do |m|
51
+ define_method(:"#{m}_chainable") { }
52
+ end
53
+ end
54
+
55
+ end
56
+
57
+ def create_chain_proxy(method_name, *args)
58
+ ChainingProxy.new(self, method_name, args)
59
+ end
60
+
61
+ def self.included(o)
62
+ o.extend(ClassMethods)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,72 @@
1
+ module Renee
2
+ class Core
3
+ # Defines class-level methods for creating accessors for variables in your environment.
4
+ module EnvAccessors
5
+
6
+ # Exception for attempting to define an env accessor cannot be written as a method name.
7
+ # @example
8
+ # env_accessor "current.user" # raises InvalidEnvNameError
9
+ # env_accessor "current.user" => :current_user # this works
10
+ InvalidEnvNameError = Class.new(RuntimeError)
11
+
12
+ # Class-methods included by this module.
13
+ module ClassMethods
14
+
15
+ # Defines getters and setters for a list of attributes. If the attributes cannot easily be expressed, use the
16
+ # hash-syntax for defining them.
17
+ # @example
18
+ # env_accessor "some_value" # will define methods to read and write env['some_value']
19
+ # env_accessor "current.user" => :current_user will define methods to read and write env['current.user']
20
+ def env_accessor(*attrs)
21
+ env_reader(*attrs)
22
+ env_writer(*attrs)
23
+ end
24
+
25
+ # Defines getters for a list of attributes.
26
+ # @see env_accessor
27
+ def env_reader(*attrs)
28
+ instance_eval do
29
+ env_attr_iter(*attrs) do |key, meth|
30
+ define_method(meth) do
31
+ env[key]
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ # Defines setters for a list of attributes.
38
+ # @see env_accessor
39
+ def env_writer(*attrs)
40
+ instance_eval do
41
+ env_attr_iter(*attrs) do |key, meth|
42
+ define_method("#{meth}=") do |val|
43
+ env[key] = val
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+ def env_attr_iter(*attrs)
51
+ attrs.each do |a|
52
+ case a
53
+ when Hash
54
+ a.each do |k, v|
55
+ yield k, v
56
+ end
57
+ else
58
+ raise InvalidEnvNameError, "Called env attr for #{a.inspect}, to use this, call your env method like this. env_reader #{a.inspect} => #{a.to_s.gsub(/-\./, '_').to_sym.inspect}" if a.to_s[/[-\.]/]
59
+ yield a, a.to_sym
60
+ end
61
+ end
62
+ end
63
+
64
+ end
65
+
66
+ # @private
67
+ def self.included(o)
68
+ o.extend(ClassMethods)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,15 @@
1
+ module Renee
2
+ class Core
3
+ # Used to indicate a client-error has occurred (e.g. 4xx)
4
+ class ClientError < StandardError
5
+ attr_reader :response
6
+
7
+ # @param [String] message The message for this exception.
8
+ # @yield The optional block to instance-eval in the case this error is raised.
9
+ def initialize(message, &response)
10
+ super(message)
11
+ @response = response
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,61 @@
1
+ module Renee
2
+ class Core
3
+ # Class used for variable matching.
4
+ class Matcher
5
+ attr_accessor :name
6
+
7
+ # @param [Regexp] matcher The regexp matcher to determine what is part of the variable.
8
+ def initialize(matcher)
9
+ @matcher = matcher
10
+ end
11
+
12
+ # Used to specific the error handler if the matcher doesn't match anything. By default, there is no error handler.
13
+ # @yield The block to be executed it fails to match.
14
+ def on_error(&blk)
15
+ @error_handler = blk
16
+ self
17
+ end
18
+
19
+ # Used to transform the value matched.
20
+ # @yield TODO
21
+ def on_transform(&blk)
22
+ @transform_handler = blk
23
+ self
24
+ end
25
+
26
+ # Convienence method to creating halting error handler.
27
+ # @param [Symbol, Integer] error_code The HTTP code to halt with.
28
+ # @see #interpret_response
29
+ def raise_on_error!(error_code = :bad_request)
30
+ on_error { halt error_code }
31
+ self
32
+ end
33
+
34
+ # Matcher for string
35
+ # @param [String] val The value to attempt to match on.
36
+ # @raise [ClientError] If the match fails to match and there is an error handler defined.
37
+ def [](val)
38
+ match = nil
39
+ case @matcher
40
+ when Array
41
+ match = nil
42
+ @matcher.find { |m| match = m[val] }
43
+ else
44
+ if match = /^#{@matcher.to_s}/.match(val)
45
+ match = [match[0]]
46
+ match << @transform_handler.call(match.first) if @transform_handler
47
+ match
48
+ end
49
+ end
50
+ if match
51
+ match
52
+ elsif @error_handler
53
+ raise ClientError.new("There was an error interpreting the value #{val.inspect} for #{name.inspect}", &@error_handler)
54
+ end
55
+ end
56
+ end
57
+
58
+ # Matcher for Integers
59
+ IntegerMatcher = Matcher.new(/\d+/).on_transform{|v| Integer(v)}
60
+ end
61
+ end
@@ -0,0 +1,31 @@
1
+ module Renee
2
+ class Core
3
+ module Plugins
4
+ attr_reader :init_blocks, :before_blocks, :after_blocks
5
+
6
+ def on_init(&blk)
7
+ init_blocks << blk
8
+ end
9
+
10
+ def init_blocks
11
+ (@init_blocks ||= [])
12
+ end
13
+
14
+ def on_before(&blk)
15
+ before_blocks << blk
16
+ end
17
+
18
+ def before_blocks
19
+ (@before_blocks ||= [])
20
+ end
21
+
22
+ def on_after(&blk)
23
+ before_blocks << blk
24
+ end
25
+
26
+ def after_blocks
27
+ (@after_blocks ||= [])
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,50 @@
1
+ module Renee
2
+ class Core
3
+ # A module that defines useful Rack interaction methods.
4
+ module RackInteraction
5
+ # Creates an ad-hoc Rack application within the context of a Rack::Builder.
6
+ # @yield The block to be used to instantiate the `Rack::Builder`.
7
+ #
8
+ # @example
9
+ # get { halt build { use Rack::ContentLength; run proc { |env| Rack::Response.new("Hello!").finish } } }
10
+ #
11
+ def build(&blk)
12
+ run Rack::Builder.new(&blk).to_app
13
+ end
14
+
15
+ # Creates an ad-hoc Rack application within the context of a Rack::Builder that immediately halts when done.
16
+ # @param (see #build)
17
+ #
18
+ # @example
19
+ # get { halt build { use Rack::ContentLength; run proc { |env| Rack::Response.new("Hello!").finish } } }
20
+ #
21
+ def build!(&blk)
22
+ halt build(&blk)
23
+ end
24
+
25
+ # Runs a rack application. You must either use `app` or `blk`.
26
+ # @param [#call] app The application to call.
27
+ # @yield [env] The block to yield to
28
+ #
29
+ #
30
+ # @example
31
+ # get { halt run proc { |env| Renee::Core::Response.new("Hello!").finish } }
32
+ #
33
+ def run(app = nil, &blk)
34
+ raise "You cannot supply both a block and an app" unless app.nil? ^ blk.nil?
35
+ (app || blk).call(env)
36
+ end
37
+
38
+ # Runs a rack application and halts immediately.
39
+ # @param (see #run)
40
+ #
41
+ # @see #run!
42
+ # @example
43
+ # get { run proc { |env| Renee::Core::Response.new("Hello!").finish } }
44
+ #
45
+ def run!(app = nil, &blk)
46
+ halt run(app, &blk)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,56 @@
1
+ module Renee
2
+ class Core
3
+ module ClassMethods
4
+ def use(mw, *args, &blk)
5
+ middlewares << [mw, args, blk]
6
+ end
7
+
8
+ def middlewares
9
+ @middlewares ||= []
10
+ end
11
+ end
12
+
13
+ # This module deals with the Rack#call compilance. It defines #call and also defines several critical methods
14
+ # used by interaction by other application modules.
15
+ module RequestContext
16
+ attr_reader :env, :request, :detected_extension
17
+
18
+ # Provides a rack interface compliant call method.
19
+ # @param[Hash] env The rack environment.
20
+ def call(e)
21
+ initialize_plugins
22
+ idx = 0
23
+ next_app = proc do |env|
24
+ if idx == self.class.middlewares.size
25
+ @env, @request = env, Rack::Request.new(env)
26
+ @detected_extension = env['PATH_INFO'][/\.([^\.\/]+)$/, 1]
27
+ # TODO clear template cache in development? `template_cache.clear`
28
+ out = catch(:halt) do
29
+ begin
30
+ self.class.before_blocks.each { |b| instance_eval(&b) }
31
+ instance_eval(&self.class.application_block)
32
+ rescue ClientError => e
33
+ e.response ? instance_eval(&e.response) : halt("There was an error with your request", 400)
34
+ rescue NotMatchedError => e
35
+ # unmatched, continue on
36
+ end
37
+ Renee::Core::Response.new("Not found", 404).finish
38
+ end
39
+ self.class.after_blocks.each { |a| out = instance_exec(out, &a) }
40
+ out
41
+ else
42
+ middleware = self.class.middlewares[idx]
43
+ idx += 1
44
+ middleware[0].new(next_app, *middleware[1], &middleware[2]).call(env)
45
+ end
46
+ end
47
+ next_app[e]
48
+ end # call
49
+
50
+ def initialize_plugins
51
+ self.class.init_blocks.each { |init_block| self.class.class_eval(&init_block) }
52
+ self.class.send(:define_method, :initialize_plugins) { }
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,112 @@
1
+ module Renee
2
+ class Core
3
+ # Collection of useful methods for responding within a {Renee::Core} app.
4
+ module Responding
5
+ # Codes used by Symbol lookup in interpret_response.
6
+ # @example
7
+ # halt :unauthorized # would return a 401.
8
+ #
9
+ HTTP_CODES = {
10
+ :ok => 200,
11
+ :created => 201,
12
+ :accepted => 202,
13
+ :no_content => 204,
14
+ :no_content => 204,
15
+ :bad_request => 400,
16
+ :unauthorized => 401,
17
+ :payment_required => 403,
18
+ :not_found => 404,
19
+ :method_not_found => 405,
20
+ :not_acceptable => 406,
21
+ :gone => 410,
22
+ :error => 500,
23
+ :not_implemented => 501}.freeze
24
+
25
+ # Halts current processing to the top-level calling Renee application and uses that as a response.
26
+ # @param [Object...] response The response to use.
27
+ # @see #interpret_response
28
+ def halt(*response)
29
+ throw :halt, interpret_response(response.size == 1 ? response.first : response)
30
+ end
31
+
32
+ ##
33
+ # Creates a response by allowing the response header, body and status to be passed into the block.
34
+ #
35
+ # @param [Array] body The contents to return.
36
+ # @param [Integer] status The status code to return.
37
+ # @param [Hash] header The headers to return.
38
+ # @param [Proc] &blk The response options to specify
39
+ #
40
+ # @example
41
+ # respond { status 200; body "Yay!" }
42
+ # respond("Hello", 200, "foo" => "bar")
43
+ #
44
+ def respond(body=[], status=200, header={}, &blk)
45
+ response = Renee::Core::Response.new(body, status, header)
46
+ response.instance_eval(&blk) if block_given?
47
+ response.finish
48
+ end
49
+
50
+ ##
51
+ # Creates a response by allowing the response header, body and status to be passed into the block.
52
+ #
53
+ # @example
54
+ # respond! { status 200; body "Yay!" }
55
+ #
56
+ # @param (see #respond)
57
+ # @see #respond
58
+ def respond!(*args, &blk)
59
+ halt respond(*args, &blk)
60
+ end
61
+
62
+ # Interprets responses returns by #halt.
63
+ #
64
+ # * If it is a Symbol, it will be looked up in {HTTP_CODES}.
65
+ # * If it is a Symbol, it will use Rack::Response to return the value.
66
+ # * If it is a Symbol, it will either be used as a Rack response or as a body and status code.
67
+ # * If it is an Integer, it will use Rack::Response to return the status code.
68
+ # * Otherwise, #to_s will be called on it and it will be treated as a Symbol.
69
+ #
70
+ # @param [Object] response This can be either a Symbol, String, Array or any Object.
71
+ #
72
+ def interpret_response(response)
73
+ case response
74
+ when Array then
75
+ case response.size
76
+ when 3 then response
77
+ when 2 then Renee::Core::Response.new(response[1], HTTP_CODES[response[0]] || response[0]).finish
78
+ else raise "I don't know how to render #{response.inspect}"
79
+ end
80
+ when String then Renee::Core::Response.new(response).finish
81
+ when Integer then Renee::Core::Response.new("Status code #{response}", response).finish
82
+ when Symbol then interpret_response(HTTP_CODES[response] || response.to_s)
83
+ when Proc then instance_eval(&response)
84
+ else response # pass through response
85
+ end
86
+ end
87
+
88
+ # Returns a rack-based response for redirection.
89
+ # @param [String] path The URL to redirect to.
90
+ # @param [Integer] code The HTTP code to use.
91
+ # @example
92
+ # r = Renee.core { get { halt redirect '/index' } }
93
+ # r.call(Rack::MockResponse("/")) # => [302, {"Location" => "/index"}, []]
94
+ def redirect(path, code = 302)
95
+ response = ::Rack::Response.new
96
+ response.redirect(path, code)
97
+ response.finish
98
+ end
99
+
100
+ # Halts with a rack-based response for redirection.
101
+ # @see #redirect
102
+ # @param [String] path The URL to redirect to.
103
+ # @param [Integer] code The HTTP code to use.
104
+ # @example
105
+ # r = Renee.core { get { redirect! '/index' } }
106
+ # r.call(Rack::MockResponse("/")) # => [302, {"Location" => "/index"}, []]
107
+ def redirect!(path, code = 302)
108
+ halt redirect(path, code)
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,78 @@
1
+ module Renee
2
+ class Core
3
+ # The response object for a Renee request. Inherits from the `Rack#Response` object.
4
+ class Response < Rack::Response
5
+ # Augment body to allow strings.
6
+ #
7
+ # @param [String] The contents for the response.
8
+ #
9
+ # @example
10
+ # res.body = "Hello"
11
+ #
12
+ # @api semipublic
13
+ def body=(value)
14
+ value = value.body while Rack::Response === value
15
+ @body = String === value ? [value.to_str] : value
16
+ end
17
+
18
+ # Alias status and body methods to allow redefinition
19
+ alias :status_attr :status
20
+ alias :status_attr= :status=
21
+ alias :body_attr :body
22
+ alias :body_attr= :body=
23
+
24
+ # Get or set the status of the response.
25
+ #
26
+ # @param [String] val The status code to return.
27
+ #
28
+ # @example
29
+ # res.status 400
30
+ # res.status => 400
31
+ #
32
+ # @api public
33
+ def status(val=nil)
34
+ val ? self.status_attr = val : self.status_attr
35
+ end
36
+
37
+ # Get or set the body of the response.
38
+ #
39
+ # @param [String] val The contents to return.
40
+ #
41
+ # @example
42
+ # res.body "hello"
43
+ # res.body => "hello"
44
+ #
45
+ # @api public
46
+ def body(val=nil)
47
+ val ? self.body_attr = val : self.body_attr
48
+ end
49
+
50
+ # Get or set the headers of the response.
51
+ #
52
+ # @param [Hash] attrs The contents to return.
53
+ #
54
+ # @example
55
+ # res.headers :foo => "bar"
56
+ # res.headers => { :foo => "bar" }
57
+ #
58
+ # @api public
59
+ def headers(attrs={})
60
+ attrs ? attrs.each { |k, v| self[k.to_s] = v } : self.header
61
+ end
62
+
63
+ # Finishs the response based on the accumulated options.
64
+ # Calculates the size of the body content length and removes headers for 1xx status codes.
65
+ def finish
66
+ if status.to_i / 100 == 1
67
+ headers.delete "Content-Length"
68
+ headers.delete "Content-Type"
69
+ elsif Array === body and not [204, 304].include?(status.to_i)
70
+ headers["Content-Length"] = body.inject(0) { |l, p| l + Rack::Utils.bytesize(p) }.to_s
71
+ end
72
+
73
+ status, headers, result = super
74
+ [status, headers, result]
75
+ end
76
+ end
77
+ end
78
+ end