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 +5 -5
- data/lib/renee_core.rb +91 -0
- data/lib/renee_core/chaining.rb +71 -0
- data/lib/renee_core/env_accessors.rb +72 -0
- data/lib/{renee-core → renee_core}/exceptions.rb +1 -1
- data/lib/{renee-core → renee_core}/matcher.rb +1 -1
- data/lib/renee_core/rack_interaction.rb +50 -0
- data/lib/renee_core/request_context.rb +25 -0
- data/lib/renee_core/responding.rb +110 -0
- data/lib/{renee-core → renee_core}/response.rb +2 -2
- data/lib/renee_core/routing.rb +322 -0
- data/lib/renee_core/transform.rb +18 -0
- data/lib/{renee-core → renee_core}/url_generation.rb +3 -3
- data/lib/renee_core/version.rb +6 -0
- data/renee-core.gemspec +1 -1
- data/test/env_accessors_test.rb +43 -0
- data/test/include_test.rb +1 -1
- data/test/responding_test.rb +1 -1
- data/test/routing_test.rb +5 -5
- data/test/test_helper.rb +1 -1
- data/test/url_generation_test.rb +8 -8
- metadata +19 -18
- data/lib/renee-core.rb +0 -78
- data/lib/renee-core/application.rb +0 -32
- data/lib/renee-core/application/chaining.rb +0 -73
- data/lib/renee-core/application/rack_interaction.rb +0 -45
- data/lib/renee-core/application/request_context.rb +0 -29
- data/lib/renee-core/application/responding.rb +0 -112
- data/lib/renee-core/application/routing.rb +0 -319
- data/lib/renee-core/application/transform.rb +0 -20
- data/lib/renee-core/settings.rb +0 -63
- data/lib/renee-core/version.rb +0 -6
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
|
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
|
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
|
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
|
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
|
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
|
@@ -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
|