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.
@@ -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