renee-core 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,245 @@
1
+ class Renee
2
+ class Core
3
+ class Application
4
+ # Collection of useful methods for routing within a {Renee::Core} app.
5
+ module Routing
6
+ # Match a path to respond to.
7
+ #
8
+ # @param [String] p
9
+ # path to match.
10
+ # @param [Proc] blk
11
+ # block to yield
12
+ #
13
+ # @example
14
+ # path('/') { ... } #=> '/'
15
+ # path('test') { ... } #=> '/test'
16
+ #
17
+ # path 'foo' do
18
+ # path('bar') { ... } #=> '/foo/bar'
19
+ # end
20
+ #
21
+ # @api public
22
+ def path(p, &blk)
23
+ p = p[1, p.size] if p[0] == ?/
24
+ part(/^\/#{Regexp.quote(p)}(\/?$)?/, &blk)
25
+ end
26
+
27
+ # Like #path, but requires the entire path to be consumed.
28
+ # @see #path
29
+ def whole_path(p, &blk)
30
+ path(p) { complete(&blk) }
31
+ end
32
+
33
+ # Like #path, but doesn't automatically match trailing-slashes.
34
+ # @see #path
35
+ def exact_path(p, &blk)
36
+ p = p[1, part.size] if p[0] == ?/
37
+ part(/^\/#{Regexp.quote(p)}/, &blk)
38
+ end
39
+
40
+ # Like #path, doesn't look for leading slashes.
41
+ def part(p)
42
+ p = /\/?#{Regexp.quote(p)}/ if p.is_a?(String)
43
+ if match = env['PATH_INFO'][p]
44
+ with_path_part(match) { yield }
45
+ end
46
+ end
47
+
48
+ # Match parts off the path as variables.
49
+ #
50
+ # @example
51
+ # path '/' do
52
+ # variable { |id| halt [200, {}, id }
53
+ # end
54
+ # GET /hey #=> [200, {}, 'hey']
55
+ #
56
+ # path '/test' do
57
+ # variable { |foo, bar| halt [200, {}, "#{foo}-#{bar}"] }
58
+ # end
59
+ # GET /test/hey/there #=> [200, {}, 'hey-there']
60
+ #
61
+ # @api public
62
+ def variable(*args, &blk)
63
+ args << {} unless args.last.is_a?(Hash)
64
+ args.last[:prepend] = '/'
65
+ partial_variable(*args, &blk)
66
+ end
67
+ alias_method :var, :variable
68
+
69
+ # Match parts off the path as variables without a leading slash.
70
+ # @see #variable
71
+ # @api public
72
+ def partial_variable(*args, &blk)
73
+ opts = args.last.is_a?(Hash) ? args.pop : nil
74
+ type = args.first || opts && opts[:type]
75
+ prepend = opts && opts[:prepend] || ''
76
+ if type == Integer
77
+ complex_variable(/#{Regexp.quote(prepend)}(\d+)/, proc{|v| Integer(v)}, &blk)
78
+ else case type
79
+ when nil
80
+ complex_variable(/#{Regexp.quote(prepend)}([^\/]+)/, &blk)
81
+ when Regexp
82
+ complex_variable(/#{Regexp.quote(prepend)}(#{type.to_s})/, &blk)
83
+ else
84
+ raise "Unexpected variable type #{type.inspect}"
85
+ end
86
+ end
87
+ end
88
+ alias_method :part_var, :partial_variable
89
+
90
+ # Match an extension.
91
+ #
92
+ # @example
93
+ # extension('html') { |path| halt [200, {}, path] }
94
+ #
95
+ # @api public
96
+ def extension(ext)
97
+ if detected_extension && match = detected_extension[ext]
98
+ if match == detected_extension
99
+ (ext_match = env['PATH_INFO'][/\/?\.#{match}/]) ?
100
+ with_path_part(ext_match) { yield } : yield
101
+ end
102
+ end
103
+ end
104
+ alias_method :ext, :extension
105
+
106
+ # Match no extension.
107
+ #
108
+ # @example
109
+ # no_extension { |path| halt [200, {}, path] }
110
+ #
111
+ # @api public
112
+ def no_extension
113
+ yield if detected_extension.nil?
114
+ end
115
+
116
+ # Match any remaining path.
117
+ #
118
+ # @example
119
+ # remainder { |path| halt [200, {}, path] }
120
+ #
121
+ # @api public
122
+ def remainder
123
+ with_path_part(env['PATH_INFO']) { |var| yield var }
124
+ end
125
+ alias_method :catchall, :remainder
126
+
127
+ # Respond to a GET request and yield the block.
128
+ #
129
+ # @example
130
+ # get { halt [200, {}, "hello world"] }
131
+ #
132
+ # @api public
133
+ def get(path = nil)
134
+ request_method('GET', path) { yield }
135
+ end
136
+
137
+ # Respond to a POST request and yield the block.
138
+ #
139
+ # @example
140
+ # post { halt [200, {}, "hello world"] }
141
+ #
142
+ # @api public
143
+ def post(path = nil)
144
+ request_method('POST', path) { yield }
145
+ end
146
+
147
+ # Respond to a PUT request and yield the block.
148
+ #
149
+ # @example
150
+ # put { halt [200, {}, "hello world"] }
151
+ #
152
+ # @api public
153
+ def put(path = nil)
154
+ request_method('PUT', path) { yield }
155
+ end
156
+
157
+ # Respond to a DELETE request and yield the block.
158
+ #
159
+ # @example
160
+ # delete { halt [200, {}, "hello world"] }
161
+ #
162
+ # @api public
163
+ def delete(path = nil)
164
+ request_method('DELETE', path) { yield }
165
+ end
166
+
167
+ # Match only when the path has been completely consumed.
168
+ #
169
+ # @example
170
+ # delete { halt [200, {}, "hello world"] }
171
+ #
172
+ # @api public
173
+ def complete
174
+ with_path_part(env['PATH_INFO']) { yield } if env['PATH_INFO'] == '' || is_index_request
175
+ end
176
+
177
+ # Match variables within the query string.
178
+ #
179
+ # @param [Array, Hash] q
180
+ # Either an array or hash of things to match query string variables. If given
181
+ # an array, if you pass the values for each key as parameters to the block given.
182
+ # If given a hash, then every value must be able to #=== match each value in the query
183
+ # parameters for each key in the hash.
184
+ #
185
+ # @example
186
+ # query(:key => 'value') { halt [200, {}, "hello world"] }
187
+ #
188
+ # @example
189
+ # query(:key) { |val| halt [200, {}, "key is #{val}"] }
190
+ #
191
+ # @api public
192
+ def query(q, &blk)
193
+ case q
194
+ when Hash then q.any? {|k,v| !(v === request[k.to_s]) } ? return : yield
195
+ when Array then yield *q.map{|qk| request[qk.to_s] or return }
196
+ else query([q], &blk)
197
+ end
198
+ end
199
+
200
+ # Yield block if the query string matches.
201
+ #
202
+ # @param [String] qs
203
+ # The query string to match.
204
+ #
205
+ # @example
206
+ # path 'test' do
207
+ # query_string 'foo=bar' do
208
+ # halt [200, {}, 'matched']
209
+ # end
210
+ # end
211
+ # GET /test?foo=bar #=> 'matched'
212
+ #
213
+ # @api public
214
+ def query_string(qs)
215
+ yield if qs === env['QUERY_STRING']
216
+ end
217
+
218
+ private
219
+ def complex_variable(matcher = nil, transformer = nil, &blk)
220
+ warn "variable currently isn't taking any parameters" unless blk.arity > 0
221
+ if var_value = /^#{(matcher ? matcher.to_s : '\/([^\/]+)') * blk.arity}/.match(env['PATH_INFO'])
222
+ vars = var_value.to_a
223
+ with_path_part(vars.shift) { blk.call *vars.map{|v| transformer ? transformer[v[0, v.size]] : v[0, v.size]} }
224
+ end
225
+ end
226
+
227
+ def with_path_part(part)
228
+ old_path_info = env['PATH_INFO']
229
+ old_script_name = env['SCRIPT_NAME']
230
+ old_path_info[part.size, old_path_info.size - part.size]
231
+ script_part, remaining_part = old_path_info[0, part.size], old_path_info[part.size, old_path_info.size]
232
+ env['SCRIPT_NAME'] += script_part
233
+ env['PATH_INFO'] = remaining_part
234
+ yield script_part
235
+ env['PATH_INFO'] = old_path_info
236
+ env['SCRIPT_NAME'] = old_script_name
237
+ end
238
+
239
+ def request_method(method, path = nil)
240
+ path ? whole_path(path) { yield } : complete { yield } if env['REQUEST_METHOD'] == method
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,28 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/application/request_context")
2
+ require File.expand_path(File.dirname(__FILE__) + "/application/routing")
3
+ require File.expand_path(File.dirname(__FILE__) + "/application/responding")
4
+ require File.expand_path(File.dirname(__FILE__) + "/application/rack_interaction")
5
+
6
+ class Renee
7
+ class Core
8
+ # This is the main class used to do the respond to requests. {Routing} provides route helpers.
9
+ # {RequestContext} adds the RequestContext#call method to respond to Rack applications.
10
+ # {Responding} defines the method Responding#halt which stops processing within a {Renee::Application}.
11
+ # It also has methods to interpret arguments to #halt and redirection response helpers.
12
+ # {RackInteraction} adds methods for interacting with Rack.
13
+ class Application
14
+ include RequestContext
15
+ include Routing
16
+ include Responding
17
+ include RackInteraction
18
+
19
+ attr_reader :application_block, :settings
20
+
21
+ # @param [Proc] application_block The block that will be #instance_eval'd on each invocation to #call
22
+ def initialize(settings, &application_block)
23
+ @settings = settings
24
+ @application_block = application_block
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,78 @@
1
+ class Renee
2
+ class Core
3
+ # The response object for a Renee request. Inherits from the `Rack#Response` object.
4
+ class Response < Rack::Response
5
+ # Augment body to allow strings.
6
+ #
7
+ # @param [String] The contents for the response.
8
+ #
9
+ # @example
10
+ # res.body = "Hello"
11
+ #
12
+ # @api semipublic
13
+ def body=(value)
14
+ value = value.body while Rack::Response === value
15
+ @body = String === value ? [value.to_str] : value
16
+ end
17
+
18
+ # Alias status and body methods to allow redefinition
19
+ alias :status_attr :status
20
+ alias :status_attr= :status=
21
+ alias :body_attr :body
22
+ alias :body_attr= :body=
23
+
24
+ # Get or set the status of the response.
25
+ #
26
+ # @param [String] val The status code to return.
27
+ #
28
+ # @example
29
+ # res.status 400
30
+ # res.status => 400
31
+ #
32
+ # @api public
33
+ def status(val=nil)
34
+ val ? self.status_attr = val : self.status_attr
35
+ end
36
+
37
+ # Get or set the body of the response.
38
+ #
39
+ # @param [String] val The contents to return.
40
+ #
41
+ # @example
42
+ # res.body "hello"
43
+ # res.body => "hello"
44
+ #
45
+ # @api public
46
+ def body(val=nil)
47
+ val ? self.body_attr = val : self.body_attr
48
+ end
49
+
50
+ # Get or set the headers of the response.
51
+ #
52
+ # @param [Hash] attrs The contents to return.
53
+ #
54
+ # @example
55
+ # res.headers :foo => "bar"
56
+ # res.headers => { :foo => "bar" }
57
+ #
58
+ # @api public
59
+ def headers(attrs={})
60
+ attrs ? attrs.each { |k, v| self[k.to_s] = v } : self.header
61
+ end
62
+
63
+ # Finishs the response based on the accumulated options.
64
+ # Calculates the size of the body content length and removes headers for 1xx status codes.
65
+ def finish
66
+ if status.to_i / 100 == 1
67
+ headers.delete "Content-Length"
68
+ headers.delete "Content-Type"
69
+ elsif Array === body and not [204, 304].include?(status.to_i)
70
+ headers["Content-Length"] = body.inject(0) { |l, p| l + Rack::Utils.bytesize(p) }.to_s
71
+ end
72
+
73
+ status, headers, result = super
74
+ [status, headers, result]
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,34 @@
1
+ class Renee
2
+ class Core
3
+ ##
4
+ # Stores configuration settings for a particular Renee application.
5
+ # Powers the Renee setup block which is instance eval'ed into this object.
6
+ #
7
+ # @example
8
+ # Renee::Core.new { ... }.setup { views_path "./views" }
9
+ #
10
+ class Settings
11
+ attr_reader :includes
12
+ def initialize
13
+ @includes = []
14
+ end
15
+
16
+ # Get or sets the views_path for an application.
17
+ #
18
+ # @param [String] path The path to the view files.
19
+ #
20
+ # @example
21
+ # views_path("./views") => nil
22
+ # views_path => "./views"
23
+ #
24
+ # @api public
25
+ def views_path(path = nil)
26
+ path ? @views_path = path : @views_path
27
+ end
28
+
29
+ def include(mod)
30
+ includes << mod
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,109 @@
1
+ require 'uri'
2
+
3
+ class Renee
4
+ class Core
5
+ # URL generator for creating paths and URLs within your application.
6
+ module URLGeneration
7
+
8
+ # Registers new paths for generation.
9
+ # @param [Symbol] name The name of the path
10
+ # @param [String] pattern The pattern used for generation.
11
+ # @param [Hash, nil] defaults Any default values used for generation.
12
+ #
13
+ # @example
14
+ # renee.register(:path, "/my/:var/path")
15
+ # renee.path(:path, 123) # => "/my/123/path"
16
+ # renee.path(:path, :var => 'hey you') # => "/my/hey%20you/path"
17
+ def register(name, pattern, defaults = nil)
18
+ url_generators[name] = Generator.new("#{@generation_prefix}#{pattern}", defaults_for_generation(defaults))
19
+ end
20
+
21
+ # Allows the creation of generation contexts.
22
+ # @param [String] prefix The prefix to add to subsequent calls to #register.
23
+ # @param [Hash, nil] defaults The defaults to add to subsequent calls to #register.
24
+ # @see #register
25
+ #
26
+ # @example
27
+ # renee.prefix("/prefix") {
28
+ # renee.register(:prefix_path, "/path") # would register /prefix/path
29
+ # }
30
+ def prefix(prefix, defaults = nil, &blk)
31
+ generator = self
32
+ subgenerator = Class.new {
33
+ include URLGeneration
34
+ define_method(:url_generators) { generator.send(:url_generators) }
35
+ }.new
36
+ subgenerator.instance_variable_set(:@generation_prefix, "#{@generation_prefix}#{prefix}")
37
+ subgenerator.instance_variable_set(:@generation_defaults, defaults_for_generation(defaults))
38
+ if block_given?
39
+ old_prefix, old_defaults = @generation_prefix, @generation_defaults
40
+ @generation_prefix, @generation_defaults = "#{@generation_prefix}#{prefix}", defaults_for_generation(defaults)
41
+ subgenerator.instance_eval(&blk)
42
+ @generation_prefix, @generation_defaults = old_prefix, old_defaults
43
+ end
44
+ subgenerator
45
+ end
46
+
47
+ # Generates a path for a given name.
48
+ # @param [Symbol] name The name of the path
49
+ # @param [Object] args The values used to generate the path. Can be named with using :name => "value" or supplied
50
+ # in the order for which the variables were decalared in #register.
51
+ #
52
+ # @see #register
53
+ def path(name, *args)
54
+ generator = url_generators[name]
55
+ generator ? generator.path(*args) : raise("Generator for #{name} doesn't exist")
56
+ end
57
+
58
+ # Generates a url for a given name.
59
+ # @param (see #path)
60
+ # @see #path
61
+ def url(name, *args)
62
+ generator = url_generators[name]
63
+ generator ? generator.url(*args) : raise("Generator for #{name} doesn't exist")
64
+ end
65
+
66
+ private
67
+ def url_generators
68
+ @url_generators ||= {}
69
+ end
70
+
71
+ def defaults_for_generation(defaults)
72
+ @generation_defaults && defaults ? @generation_defaults.merge(defaults) : (defaults || @generation_defaults)
73
+ end
74
+
75
+ # Manages generating paths and urls for a given name.
76
+ # @private
77
+ class Generator
78
+ attr_reader :defaults
79
+
80
+ def initialize(template, defaults = nil)
81
+ @defaults = defaults
82
+ parsed_template = URI.parse(template)
83
+ @host = parsed_template.host
84
+ @template = parsed_template.path
85
+ @scheme = parsed_template.scheme
86
+ port = parsed_template.port
87
+ if !port.nil? and (@scheme.nil? or @scheme == "http" && port != '80' or @scheme == "https" && port != '443')
88
+ @port_part = ":#{port}"
89
+ end
90
+ end
91
+
92
+ def path(*args)
93
+ opts = args.last.is_a?(Hash) ? args.pop : nil
94
+ opts = opts ? defaults.merge(opts) : defaults.dup if defaults
95
+ path = @template.gsub(/:([a-zA-Z0-9_]+)/) { |name|
96
+ name = name[1, name.size - 1].to_sym
97
+ (opts && opts.delete(name)) || (defaults && defaults[name]) || args.shift || raise("variable #{name.inspect} not found")
98
+ }
99
+ URI.encode(opts.nil? || opts.empty? ? path : "#{path}?#{Rack::Utils.build_query(opts)}")
100
+ end
101
+
102
+ def url(*args)
103
+ raise "This URL cannot be generated as no host has been defined." if @host.nil?
104
+ "#{@scheme}://#{@host}#{@port_part}#{path(*args)}"
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,6 @@
1
+ class Renee
2
+ class Core
3
+ # The version for the renee-core gem.
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
data/lib/renee-core.rb ADDED
@@ -0,0 +1,64 @@
1
+ require 'rack'
2
+ require 'renee-core/version'
3
+ require 'renee-core/settings'
4
+ require 'renee-core/response'
5
+ require 'renee-core/application'
6
+ require 'renee-core/url_generation'
7
+
8
+ # Renee top-level constant
9
+ class Renee
10
+ # The top-level class for creating core application.
11
+ # For convience you can also used a method named #Renee
12
+ # for decalaring new instances.
13
+ #
14
+ # @example
15
+ # Renee::Core.new { path('/hello') { halt :ok } }
16
+ #
17
+ class Core
18
+ include URLGeneration
19
+
20
+ attr_reader :application_block, :settings
21
+
22
+ # @param [Proc] application_block The block of code that will be executed on each invocation of call #call.
23
+ # Each time #call is called, a new instance of {Renee::Core::Application} will
24
+ # be created. The block given will be #instance_eval 'd within
25
+ # the context of that new instance.
26
+ #
27
+ def initialize(&application_block)
28
+ @application_block = application_block
29
+ @settings = Settings.new
30
+ end
31
+
32
+ # This is a rack-compliant `Rack#call`.
33
+ #
34
+ # @param [Hash] env The environment hash.
35
+ #
36
+ # @return [Array] A rack compliant return.
37
+ #
38
+ # @see http://rack.rubyforge.org/doc/SPEC.html
39
+ #
40
+ def call(env)
41
+ Application.new(settings, &application_block).call(env)
42
+ end
43
+ alias_method :[], :call
44
+
45
+ ##
46
+ # Configure settings for your Renee application. Accepts a settings file path
47
+ # or a block containing the configuration settings.
48
+ #
49
+ # @example
50
+ # Renee::Core.new { ... }.setup { views_path "./views" }
51
+ #
52
+ # @api public
53
+ def setup(path = nil, &blk)
54
+ raise "Must be either path or blk to configure settings" if path && blk
55
+ case path
56
+ when nil then settings.instance_eval(&blk)
57
+ when Settings then @settings = path
58
+ when String then File.exist?(path) ? settings.instance_eval(File.read(path), path, 1) : raise("The settings file #{path} does not exist")
59
+ else raise "Could not setup with #{path.inspect}"
60
+ end
61
+ self
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "renee-core/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "renee-core"
7
+ s.version = Renee::Core::VERSION
8
+ s.authors = ["Josh Hull", "Nathan Esquenazi", "Arthur Chiu"]
9
+ s.email = ["joshbuddy@gmail.com", "nesquena@gmail.com", "mr.arthur.chiu@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{The super-friendly rack helpers.}
12
+ s.description = %q{The super-friendly rack helpers.}
13
+
14
+ s.rubyforge_project = "renee"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_runtime_dependency 'rack', "~> 1.3.0"
22
+ s.add_development_dependency 'minitest', "~> 2.6.1"
23
+ s.add_development_dependency 'bundler', "~> 1.0.10"
24
+ s.add_development_dependency "rack-test", ">= 0.5.0"
25
+ s.add_development_dependency "rake", "0.8.7"
26
+ end