renee-core 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -10,7 +10,7 @@ for far more flexibility and freedom in the way that routes and actions are defi
10
10
  The bread and butter of Renee are the request verbs reminiscent of Sinatra:
11
11
 
12
12
  ```ruby
13
- run Renee::Core.new {
13
+ run Renee.core {
14
14
  get { halt "a get!" }
15
15
  post { halt "a post!" }
16
16
  put { halt "a put!" }
@@ -26,7 +26,7 @@ specify the http verb and the use of `halt` inside the block to send back the bo
26
26
  Path is how Renee describes the basic uri path for a route:
27
27
 
28
28
  ```ruby
29
- run Renee::Core.new {
29
+ run Renee.core {
30
30
  path('blog') { ... }
31
31
  }
32
32
  ```
@@ -34,7 +34,7 @@ run Renee::Core.new {
34
34
  All declarations inside that block will start with `/blog`. Paths can also be nested within one another:
35
35
 
36
36
  ```ruby
37
- run Renee::Core.new {
37
+ run Renee.core {
38
38
  path('blog') {
39
39
  path('foo') { get { halt "path is /blog/foo" } }
40
40
  }
@@ -152,7 +152,7 @@ Once you have defined your routes, you can then "register" a particular path map
152
152
  having to specify the entire path:
153
153
 
154
154
  ```ruby
155
- run Renee::Core.new {
155
+ run Renee.core {
156
156
  register(:test, '/test/time')
157
157
  register(:test_var, '/test/:id')
158
158
  }
@@ -194,7 +194,7 @@ run Renee {
194
194
  Halting is the easiest way to render data within a route:
195
195
 
196
196
  ```ruby
197
- run Renee::Core.new {
197
+ run Renee.core {
198
198
  get { halt 'easy' }
199
199
  }
200
200
  ```
data/lib/renee_core.rb ADDED
@@ -0,0 +1,91 @@
1
+ require 'rack'
2
+ require 'renee_core/matcher'
3
+ require 'renee_core/chaining'
4
+ require 'renee_core/response'
5
+ require 'renee_core/url_generation'
6
+ require 'renee_core/exceptions'
7
+ require 'renee_core/rack_interaction'
8
+ require 'renee_core/request_context'
9
+ require 'renee_core/transform'
10
+ require 'renee_core/routing'
11
+ require 'renee_core/responding'
12
+ require 'renee_core/env_accessors'
13
+
14
+ # Top-level Renee constant
15
+ module Renee
16
+ # @example
17
+ # Renee.core { path('/hello') { halt :ok } }
18
+ def self.core(&blk)
19
+ cls = Class.new(Renee::Core)
20
+ cls.app(&blk) if blk
21
+ cls
22
+ end
23
+
24
+ # The top-level class for creating core application.
25
+ # For convience you can also used a method named #Renee
26
+ # for decalaring new instances.
27
+ class Core
28
+ # Class methods that are included in new instances of {Core}
29
+ module ClassMethods
30
+ include URLGeneration
31
+
32
+ # The application block used to create your application.
33
+ attr_reader :application_block
34
+
35
+ # Provides a rack interface compliant call method. This method creates a new instance of your class and calls
36
+ # #call on it.
37
+ def call(env)
38
+ new.call(env)
39
+ end
40
+
41
+ # Allows you to set the #application_block on your class.
42
+ # @yield The application block
43
+ def app(&app)
44
+ @application_block = app
45
+ setup do
46
+ register_variable_type :integer, IntegerMatcher
47
+ register_variable_type :int, :integer
48
+ end
49
+ self
50
+ end
51
+
52
+ # Runs class methods on your application.
53
+ def setup(&blk)
54
+ instance_eval(&blk)
55
+ self
56
+ end
57
+
58
+ # The currently available variable types you've defined.
59
+ def variable_types
60
+ @variable_types ||= {}
61
+ end
62
+
63
+ # Registers a new variable type for use within {Renee::Core::Routing#variable} and others.
64
+ # @param [Symbol] name The name of the variable.
65
+ # @param [Regexp] matcher A regexp describing what part of an arbitrary string to capture.
66
+ # @return [Renee::Core::Matcher] A matcher
67
+ def register_variable_type(name, matcher)
68
+ matcher = case matcher
69
+ when Matcher then matcher
70
+ when Array then Matcher.new(matcher.map{|m| variable_types[m]})
71
+ when Symbol then variable_types[matcher]
72
+ else Matcher.new(matcher)
73
+ end
74
+ matcher.name = name
75
+ variable_types[name] = matcher
76
+ end
77
+ end
78
+
79
+ include Chaining
80
+ include RequestContext
81
+ include Routing
82
+ include Responding
83
+ include RackInteraction
84
+ include Transform
85
+ include EnvAccessors
86
+
87
+ class << self
88
+ include ClassMethods
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,71 @@
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, proxy_blk)
17
+ @target, @proxy_blk, @calls = target, proxy_blk, []
18
+ end
19
+
20
+ def method_missing(m, *args, &blk)
21
+ @calls << [m, args]
22
+ if blk.nil? && @target.class.private_method_defined?(:"#{m}_without_chain")
23
+ self
24
+ else
25
+ ret = nil
26
+ @proxy_blk.call(proc do |*inner_args|
27
+ callback = proc do |*callback_args|
28
+ inner_args.concat(callback_args)
29
+ if @calls.size == 0
30
+ return blk.call(*inner_args) if blk
31
+ else
32
+ call = @calls.shift
33
+ ret = @target.send(call.at(0), *call.at(1), &callback)
34
+ end
35
+ end
36
+ call = @calls.shift
37
+ ret = @target.send(call.at(0), *call.at(1), &callback)
38
+ end)
39
+ ret
40
+ end
41
+ end
42
+ end
43
+
44
+ # @private
45
+ module ClassMethods
46
+ def chain_method(*methods)
47
+ methods.each do |m|
48
+ class_eval <<-EOT, __FILE__, __LINE__ + 1
49
+ alias_method :#{m}_without_chain, :#{m}
50
+ def #{m}(*args, &blk)
51
+ chain(blk) do |subblk|
52
+ #{m}_without_chain(*args, &subblk)
53
+ end
54
+ end
55
+ private :#{m}_without_chain
56
+ EOT
57
+ end
58
+ end
59
+ end
60
+
61
+ private
62
+ def chain(blk, &proxy)
63
+ blk ? yield(blk) : ChainingProxy.new(self, proxy)
64
+ end
65
+
66
+ def self.included(o)
67
+ o.extend(ClassMethods)
68
+ end
69
+ end
70
+ end
71
+ 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 InvalidEnvName
9
+ # env_accessor "current.user" => :current_user # this works
10
+ InvalidEnvName = 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 InvalidEnvName, "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
@@ -1,4 +1,4 @@
1
- class Renee
1
+ module Renee
2
2
  class Core
3
3
  # Used to indicate a client-error has occurred (e.g. 4xx)
4
4
  class ClientError < StandardError
@@ -1,4 +1,4 @@
1
- class Renee
1
+ module Renee
2
2
  class Core
3
3
  # Class used for variable matching.
4
4
  class Matcher
@@ -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
+ run! 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,25 @@
1
+ module Renee
2
+ class Core
3
+ # This module deals with the Rack#call compilance. It defines #call and also defines several critical methods
4
+ # used by interaction by other application modules.
5
+ module RequestContext
6
+ attr_reader :env, :request, :detected_extension
7
+
8
+ # Provides a rack interface compliant call method.
9
+ # @param[Hash] env The rack environment.
10
+ def call(env)
11
+ @env, @request = env, Rack::Request.new(env)
12
+ @detected_extension = env['PATH_INFO'][/\.([^\.\/]+)$/, 1]
13
+ # TODO clear template cache in development? `template_cache.clear`
14
+ catch(:halt) do
15
+ begin
16
+ instance_eval(&self.class.application_block)
17
+ rescue ClientError => e
18
+ e.response ? instance_eval(&e.response) : halt("There was an error with your request", 400)
19
+ end
20
+ Renee::Core::Response.new("Not found", 404).finish
21
+ end
22
+ end # call
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,110 @@
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
+ Renee::Core::Response.new(body, status, header).tap { |r| r.instance_eval(&blk) if block_given? }.finish
46
+ end
47
+
48
+ ##
49
+ # Creates a response by allowing the response header, body and status to be passed into the block.
50
+ #
51
+ # @example
52
+ # respond! { status 200; body "Yay!" }
53
+ #
54
+ # @param (see #respond)
55
+ # @see #respond
56
+ def respond!(*args, &blk)
57
+ halt respond(*args, &blk)
58
+ end
59
+
60
+ # Interprets responses returns by #halt.
61
+ #
62
+ # * If it is a Symbol, it will be looked up in {HTTP_CODES}.
63
+ # * If it is a Symbol, it will use Rack::Response to return the value.
64
+ # * If it is a Symbol, it will either be used as a Rack response or as a body and status code.
65
+ # * If it is an Integer, it will use Rack::Response to return the status code.
66
+ # * Otherwise, #to_s will be called on it and it will be treated as a Symbol.
67
+ #
68
+ # @param [Object] response This can be either a Symbol, String, Array or any Object.
69
+ #
70
+ def interpret_response(response)
71
+ case response
72
+ when Array then
73
+ case response.size
74
+ when 3 then response
75
+ when 2 then Renee::Core::Response.new(response[1], HTTP_CODES[response[0]] || response[0]).finish
76
+ else raise "I don't know how to render #{response.inspect}"
77
+ end
78
+ when String then Renee::Core::Response.new(response).finish
79
+ when Integer then Renee::Core::Response.new("Status code #{response}", response).finish
80
+ when Symbol then interpret_response(HTTP_CODES[response] || response.to_s)
81
+ when Proc then instance_eval(&response)
82
+ else raise "Unable to render #{response.inspect}"
83
+ end
84
+ end
85
+
86
+ # Returns a rack-based response for redirection.
87
+ # @param [String] path The URL to redirect to.
88
+ # @param [Integer] code The HTTP code to use.
89
+ # @example
90
+ # r = Renee.core { get { halt redirect '/index' } }
91
+ # r.call(Rack::MockResponse("/")) # => [302, {"Location" => "/index"}, []]
92
+ def redirect(path, code = 302)
93
+ response = ::Rack::Response.new
94
+ response.redirect(path, code)
95
+ response.finish
96
+ end
97
+
98
+ # Halts with a rack-based response for redirection.
99
+ # @see #redirect
100
+ # @param [String] path The URL to redirect to.
101
+ # @param [Integer] code The HTTP code to use.
102
+ # @example
103
+ # r = Renee.core { get { redirect! '/index' } }
104
+ # r.call(Rack::MockResponse("/")) # => [302, {"Location" => "/index"}, []]
105
+ def redirect!(path, code = 302)
106
+ halt redirect(path, code)
107
+ end
108
+ end
109
+ end
110
+ end