renee-core 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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