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 +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
|