renee-core 0.0.1

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 ADDED
@@ -0,0 +1,242 @@
1
+ # Renee Core
2
+
3
+ ## Routing
4
+
5
+ Routing in `Renee` is different from any web framework you are likely to have used in the past. The syntax is most familiar to Sinatra but allows
6
+ for far more flexibility and freedom in the way that routes and actions are defined. In a Renee, routes are defined using the `path`, `var`, `query_string`, `extension`, `remainder` and request methods.
7
+
8
+ **Request Methods**
9
+
10
+ The bread and butter of Renee are the request verbs reminiscent of Sinatra:
11
+
12
+ ```ruby
13
+ run Renee::Core.new {
14
+ get { halt "a get!" }
15
+ post { halt "a post!" }
16
+ put { halt "a put!" }
17
+ delete { halt "a delete!" }
18
+ }
19
+ ```
20
+
21
+ These will declare the response to "/" for each of the common request types. Notice the use of the request method to
22
+ specify the http verb and the use of `halt` inside the block to send back the body of the response.
23
+
24
+ **Path**
25
+
26
+ Path is how Renee describes the basic uri path for a route:
27
+
28
+ ```ruby
29
+ run Renee::Core.new {
30
+ path('blog') { ... }
31
+ }
32
+ ```
33
+
34
+ All declarations inside that block will start with `/blog`. Paths can also be nested within one another:
35
+
36
+ ```ruby
37
+ run Renee::Core.new {
38
+ path('blog') {
39
+ path('foo') { get { halt "path is /blog/foo" } }
40
+ }
41
+ }
42
+ ```
43
+
44
+ You can also use `exact_path` for more precise path matching and/or `part` which doesn't look for leading slashes.
45
+
46
+ **Query String**
47
+
48
+ In addition to defining paths, you may find yourself wanting to describe the state of the query string for a request within the path:
49
+
50
+ ```ruby
51
+ path 'foo' do
52
+ query_string 'bar' do
53
+ get { halt 'BAR!' }
54
+ end
55
+
56
+ query_string 'baz' do
57
+ get { halt 'BAZ!' }
58
+ end
59
+ end
60
+ ```
61
+
62
+ This will respond to `/foo?bar` with "BAR!" and `/foo?baz` with "BAZ!". You can also specify query_string in a variety of other ways:
63
+
64
+ ```ruby
65
+ # Check key and value of query param
66
+ query_string 'foo=bar' do
67
+ post { halt [200,{},'foo'] }
68
+ end
69
+
70
+ # Declare query params as a hash
71
+ query :foo => "bar" do
72
+ halt 200
73
+ end
74
+
75
+ # Switch based on a query parameter
76
+ query :foo do |var|
77
+ case var
78
+ when 'bar' then halt 200
79
+ when 'bar2' then halt 500
80
+ end
81
+ end
82
+ ```
83
+
84
+ **Variables**
85
+
86
+ In Renee, you specify parameters for your request as explicit variables. Variables are declared like this:
87
+
88
+ ```ruby
89
+ path('blog') {
90
+ var { |id| get { halt "path is /blog/#{id}" } }
91
+ }
92
+ ```
93
+
94
+ You can access the variables (passed into the request) using the local variables yielded to the block. Variables are a powerful
95
+ way to express expected parameters for a given set of requests. You can specify variables that match a regex:
96
+
97
+ ```ruby
98
+ path('blog') {
99
+ var(/\d+/) { |id| get { halt "path is /blog/#{id}" } }
100
+ }
101
+ ```
102
+
103
+ and even explicitly cast your variable types:
104
+
105
+ ```ruby
106
+ path('blog') {
107
+ var :type => Integer do |id|
108
+ get { halt "path is /blog/#{id} and id is an integer" }
109
+ end
110
+ end
111
+ ```
112
+
113
+ **Extensions**
114
+
115
+ You can also use `extension` as a way to define formats:
116
+
117
+ ```ruby
118
+ path '/test' do
119
+ extension 'html' do
120
+ halt 'html'
121
+ end
122
+ extension 'json' do
123
+ halt 'json'
124
+ end
125
+ end
126
+ ```
127
+
128
+ This will have `test.html` respond with 'html' and `test.json` respond with 'json'.
129
+
130
+ **Remainder**
131
+
132
+ In the event that no route has been matched, the `remainder` keyword makes defining the else case rather easy:
133
+
134
+ ```ruby
135
+ path 'foo' do
136
+ path 'bar' do
137
+ halt "BAR!"
138
+ end
139
+
140
+ remainder do |rest|
141
+ halt "Rest was #{rest}"
142
+ end
143
+ end
144
+ ```
145
+
146
+ Notice this allows you to handle the cases within a particular route scope and manage them based on the "rest" of the uri yielded in the `remainder` block. You
147
+ can handle different remainders in all the different path blocks.
148
+
149
+ **Named Routes**
150
+
151
+ Once you have defined your routes, you can then "register" a particular path mapping that to a symbol. This is useful for referencing routes without
152
+ having to specify the entire path:
153
+
154
+ ```ruby
155
+ run Renee::Core.new {
156
+ register(:test, '/test/time')
157
+ register(:test_var, '/test/:id')
158
+ }
159
+ ```
160
+
161
+ You can then access these using the `path` method in a route or template:
162
+
163
+ ```ruby
164
+ path(:test) # => '/test/time'
165
+ path(:test_var, :id => 123) # => '/test/123'
166
+ ```
167
+
168
+ Using named routes makes referencing and modifying routes within an application much simpler to manage.
169
+
170
+ ## Responding
171
+
172
+ Responding to a request within a route can be managed with the `respond`, `halt`, `redirect` commands:
173
+
174
+ **Respond**
175
+
176
+ The `respond` command makes returning a rack response very explicit, you can respond as if you were constructing a Rack::Response
177
+
178
+ ```ruby
179
+ run Renee {
180
+ get { respond!("hello!", 403, "foo" => "bar") }
181
+ }
182
+ ```
183
+
184
+ or use the block DSL for convenience:
185
+
186
+ ```ruby
187
+ run Renee {
188
+ get { respond! { status 403; headers :foo => "bar"; body "hello!" } }
189
+ }
190
+ ```
191
+
192
+ **Halt**
193
+
194
+ Halting is the easiest way to render data within a route:
195
+
196
+ ```ruby
197
+ run Renee::Core.new {
198
+ get { halt 'easy' }
199
+ }
200
+ ```
201
+
202
+ This will return a 200 status code and 'easy' as the body. You can also specify status code and header explicitly in the halt response:
203
+
204
+ ```ruby
205
+ get { halt [200, {}, 'body'] }
206
+ ```
207
+
208
+ This will set the status code to 200, pass no headers and return 'body'. You can also use several variations of halt:
209
+
210
+ ```ruby
211
+ # Return just status code
212
+ halt 200
213
+
214
+ # Return status with symbol
215
+ halt :not_found
216
+
217
+ # Return 200 with body
218
+ halt "hello!"
219
+
220
+ # Return 500 with body
221
+ halt 500, "hello!"
222
+ ```
223
+
224
+ Halt is the most straightforward way to control the response for a request.
225
+
226
+ **Redirect**
227
+
228
+ A redirect is a common action within a web route and can be achieved with the convenience method `redirect` command:
229
+
230
+ ```ruby
231
+ get {
232
+ halt redirect('/hello')
233
+ }
234
+ ```
235
+
236
+ You can also specify the status code for the redirect:
237
+
238
+ ```ruby
239
+ get {
240
+ halt redirect('/hello', 303)
241
+ }
242
+ ```
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs.push "lib"
6
+ t.test_files = FileList[File.expand_path('../test/**/*_test.rb', __FILE__)]
7
+ t.verbose = true
8
+ end
data/ideal.rb ADDED
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ require 'benchmark'
3
+
4
+ $: << 'lib'
5
+ require 'renee'
6
+
7
+ router = Renee::Core.new {
8
+ path 'test/time' do
9
+ query_string 'ok' do
10
+ get { halt "ok" }
11
+ post { halt [200, {}, ['POSTED!']] }
12
+ end
13
+ end
14
+ variable do |id1, id2|
15
+ path 'more' do
16
+ get {
17
+ halt [200, {}, "this is the id1: #{id1} id2: #{id2}" ]
18
+ }
19
+ end
20
+ end
21
+ remainder do |rest|
22
+ halt "the rest is #{rest}"
23
+ end
24
+ }.setup {
25
+ view_path('views')
26
+ environment(:development)
27
+ }
28
+
29
+ app = Renee do
30
+ path "add" do
31
+ variable Integer do |first, second|
32
+ "#{first} + #{second} = #{first + second}"
33
+ end
34
+ end
35
+ end
36
+
37
+ p router.call(Rack::MockRequest.env_for('/add/3/4')) # => "3 + 4 = 7"
38
+
39
+ p router.call(Rack::MockRequest.env_for('/test/time?ok'))
40
+ p router.call(Rack::MockRequest.env_for('/test/josh/more'))
41
+ p router.call(Rack::MockRequest.env_for('/'))
42
+
43
+
44
+ #puts Benchmark.measure {
45
+ #50_000.times do
46
+ # router.call(Rack::MockRequest.env_for('/test/josh/more'))
47
+ #router.call(Rack::MockRequest.env_for('/test/time?ok', :method => 'POST' ))
48
+ #end
49
+ #}
@@ -0,0 +1,39 @@
1
+ class Renee
2
+ class Core
3
+ class Application
4
+ # A module that defines useful Rack interaction methods.
5
+ module RackInteraction
6
+
7
+ # Creates an ad-hoc Rack application within the context of a Rack::Builder.
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
+ def build!(&blk)
16
+ run! build(&blk)
17
+ end
18
+
19
+ # Runs a rack application
20
+ # @example
21
+ # get { halt run proc { |env| Renee::Core::Response.new("Hello!").finish } }
22
+ #
23
+ def run(app = nil, &blk)
24
+ (app || blk).call(env)
25
+ end
26
+
27
+ # Runs a rack application and responds immediately.
28
+ #
29
+ # @see #run
30
+ # @example
31
+ # get { run proc { |env| Renee::Core::Response.new("Hello!").finish } }
32
+ #
33
+ def run!(*args)
34
+ halt! run(*args)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,22 @@
1
+ class Renee
2
+ class Core
3
+ class Application
4
+ # This module deals with the Rack#call compilance. It defines #call and also defines several critical methods
5
+ # used by interaction by other application modules.
6
+ module RequestContext
7
+ attr_reader :env, :request, :detected_extension, :is_index_request
8
+ alias_method :is_index_request?, :is_index_request
9
+
10
+ # Provides a rack interface compliant call method.
11
+ # @param[Hash] env The rack environment.
12
+ def call(env)
13
+ @env, @request = env, Rack::Request.new(env)
14
+ @detected_extension = env['PATH_INFO'][/\.([^\.\/]+)$/, 1]
15
+ @is_index_request = env['PATH_INFO'][/^\/?$/]
16
+ # TODO clear template cache in development? `template_cache.clear`
17
+ catch(:halt) { instance_eval(&application_block); Renee::Core::Response.new("Not found", 404).finish }
18
+ end # call
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,122 @@
1
+ class Renee
2
+ class Core
3
+ class Application
4
+ # Collection of useful methods for responding within a {Renee::Core} app.
5
+ module Responding
6
+
7
+ # Codes used by Symbol lookup in interpret_response.
8
+ # @example
9
+ # halt! :unauthorized # would return a 401.
10
+ #
11
+ HTTP_CODES = {
12
+ :ok => 200,
13
+ :created => 201,
14
+ :accepted => 202,
15
+ :no_content => 204,
16
+ :no_content => 204,
17
+ :bad_request => 400,
18
+ :unauthorized => 401,
19
+ :payment_required => 403,
20
+ :not_found => 404,
21
+ :method_not_found => 405,
22
+ :not_acceptable => 406,
23
+ :gone => 410,
24
+ :error => 500,
25
+ :not_implemented => 501}.freeze
26
+
27
+ # Halts current processing to the top-level calling Renee application and uses that as a response. This method requies that
28
+ # the PATH_INFO be completely consumed.
29
+ # @param [Object...] response The response to use.
30
+ # @see #interpret_response
31
+ def halt(*response)
32
+ raise "PATH_INFO hasn't been entirely consumed, you still have #{env['PATH_INFO'].inspect} left. Try putting a #remainder block around it. " if env['PATH_INFO'] != ''
33
+ halt! *response
34
+ end
35
+
36
+ # Halts current processing to the top-level calling Renee application and uses that as a response. Unlike #halt, this does
37
+ # not require the path to be consumed.
38
+ # @param [Object...] response The response to use.
39
+ # @see #interpret_response
40
+ def halt!(*response)
41
+ throw :halt, interpret_response(response.size == 1 ? response.first : response)
42
+ end
43
+
44
+ ##
45
+ # Creates a response by allowing the response header, body and status to be passed into the block.
46
+ #
47
+ # @param [Array] body The contents to return.
48
+ # @param [Integer] status The status code to return.
49
+ # @param [Hash] header The headers to return.
50
+ # @param [Proc] &blk The response options to specify
51
+ #
52
+ # @example
53
+ # respond { status 200; body "Yay!" }
54
+ # respond("Hello", 200, "foo" => "bar")
55
+ #
56
+ def respond(body=[], status=200, header={}, &blk)
57
+ Renee::Core::Response.new(body, status, header).tap { |r| r.instance_eval(&blk) if block_given? }.finish
58
+ end
59
+
60
+ ##
61
+ # Creates a response by allowing the response header, body and status to be passed into the block.
62
+ #
63
+ # @example
64
+ # respond! { status 200; body "Yay!" }
65
+ #
66
+ # @param (see #respond)
67
+ # @see #respond
68
+ def respond!(*args, &blk)
69
+ halt respond(*args, &blk)
70
+ end
71
+
72
+ # Interprets responses returns by #halt.
73
+ #
74
+ # * If it is a Symbol, it will be looked up in {HTTP_CODES}.
75
+ # * If it is a Symbol, it will use Rack::Response to return the value.
76
+ # * If it is a Symbol, it will either be used as a Rack response or as a body and status code.
77
+ # * If it is an Integer, it will use Rack::Response to return the status code.
78
+ # * Otherwise, #to_s will be called on it and it will be treated as a Symbol.
79
+ #
80
+ # @param [Object] response This can be either a Symbol, String, Array or any Object.
81
+ #
82
+ def interpret_response(response)
83
+ case response
84
+ when Array then
85
+ case response.size
86
+ when 3 then response
87
+ when 2 then Renee::Core::Response.new(response[1], HTTP_CODES[response[0]] || response[0]).finish
88
+ else raise "I don't know how to render #{response.inspect}"
89
+ end
90
+ when String then Renee::Core::Response.new(response).finish
91
+ when Integer then Renee::Core::Response.new("Status code #{response}", response).finish
92
+ when Symbol then interpret_response(HTTP_CODES[response] || response.to_s)
93
+ else interpret_response(response.to_s)
94
+ end
95
+ end
96
+
97
+ # Returns a rack-based response for redirection.
98
+ # @param [String] path The URL to redirect to.
99
+ # @param [Integer] code The HTTP code to use.
100
+ # @example
101
+ # r = Renee::Core.new { get { halt redirect '/index' } }
102
+ # r.call(Rack::MockResponse("/")) # => [302, {"Location" => "/index"}, []]
103
+ def redirect(path, code = 302)
104
+ response = ::Rack::Response.new
105
+ response.redirect(path, code)
106
+ response.finish
107
+ end
108
+
109
+ # Halts with a rack-based response for redirection.
110
+ # @see #redirect
111
+ # @param [String] path The URL to redirect to.
112
+ # @param [Integer] code The HTTP code to use.
113
+ # @example
114
+ # r = Renee::Core.new { get { redirect! '/index' } }
115
+ # r.call(Rack::MockResponse("/")) # => [302, {"Location" => "/index"}, []]
116
+ def redirect!(path, code = 302)
117
+ halt redirect(path, code)
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end