merb-core 0.9.5 → 0.9.6
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/CHANGELOG +925 -0
- data/CONTRIBUTORS +93 -0
- data/PUBLIC_CHANGELOG +85 -0
- data/Rakefile +18 -28
- data/bin/merb +34 -5
- data/lib/merb-core/autoload.rb +2 -3
- data/lib/merb-core/bootloader.rb +60 -66
- data/lib/merb-core/config.rb +7 -1
- data/lib/merb-core/controller/abstract_controller.rb +35 -21
- data/lib/merb-core/controller/merb_controller.rb +15 -42
- data/lib/merb-core/controller/mixins/authentication.rb +42 -6
- data/lib/merb-core/controller/mixins/conditional_get.rb +83 -0
- data/lib/merb-core/controller/mixins/render.rb +3 -3
- data/lib/merb-core/core_ext/kernel.rb +6 -19
- data/lib/merb-core/dispatch/cookies.rb +96 -80
- data/lib/merb-core/dispatch/default_exception/views/index.html.erb +2 -0
- data/lib/merb-core/dispatch/request.rb +18 -16
- data/lib/merb-core/dispatch/router/route.rb +6 -0
- data/lib/merb-core/dispatch/router.rb +4 -1
- data/lib/merb-core/dispatch/session/container.rb +64 -0
- data/lib/merb-core/dispatch/session/cookie.rb +91 -101
- data/lib/merb-core/dispatch/session/memcached.rb +38 -174
- data/lib/merb-core/dispatch/session/memory.rb +62 -208
- data/lib/merb-core/dispatch/session/store_container.rb +145 -0
- data/lib/merb-core/dispatch/session.rb +174 -48
- data/lib/merb-core/rack/middleware/conditional_get.rb +14 -8
- data/lib/merb-core/rack/middleware/csrf.rb +73 -0
- data/lib/merb-core/rack.rb +1 -0
- data/lib/merb-core/script.rb +112 -0
- data/lib/merb-core/server.rb +2 -0
- data/lib/merb-core/tasks/merb_rake_helper.rb +25 -0
- data/lib/merb-core/test/helpers/request_helper.rb +40 -3
- data/lib/merb-core/test/run_specs.rb +4 -3
- data/lib/merb-core/vendor/facets/inflect.rb +7 -10
- data/lib/merb-core/version.rb +1 -1
- data/lib/merb-core.rb +11 -40
- data/spec/private/core_ext/kernel_spec.rb +0 -11
- data/spec/private/dispatch/fixture/log/merb_test.log +893 -0
- data/spec/private/router/fixture/log/merb_test.log +12 -1728
- data/spec/private/router/route_spec.rb +4 -0
- data/spec/private/router/router_spec.rb +8 -0
- data/spec/private/vendor/facets/plural_spec.rb +1 -1
- data/spec/private/vendor/facets/singular_spec.rb +1 -1
- data/spec/public/abstract_controller/controllers/display.rb +8 -2
- data/spec/public/abstract_controller/controllers/filters.rb +18 -0
- data/spec/public/abstract_controller/display_spec.rb +6 -2
- data/spec/public/abstract_controller/filter_spec.rb +4 -0
- data/spec/public/controller/authentication_spec.rb +114 -43
- data/spec/public/controller/base_spec.rb +8 -0
- data/spec/public/controller/conditional_get_spec.rb +100 -0
- data/spec/public/controller/config/init.rb +1 -1
- data/spec/public/controller/controllers/authentication.rb +29 -0
- data/spec/public/controller/controllers/base.rb +13 -0
- data/spec/public/controller/controllers/conditional_get.rb +35 -0
- data/spec/public/controller/controllers/cookies.rb +10 -1
- data/spec/public/controller/cookies_spec.rb +38 -9
- data/spec/public/controller/spec_helper.rb +1 -0
- data/spec/public/controller/url_spec.rb +70 -1
- data/spec/public/directory_structure/directory/log/merb_test.log +461 -0
- data/spec/public/rack/conditinal_get_middleware_spec.rb +77 -89
- data/spec/public/rack/csrf_middleware_spec.rb +70 -0
- data/spec/public/reloading/directory/log/merb_test.log +52 -0
- data/spec/public/request/request_spec.rb +19 -1
- data/spec/public/router/fixation_spec.rb +26 -4
- data/spec/public/router/fixture/log/merb_test.log +234 -30332
- data/spec/public/session/controllers/sessions.rb +52 -0
- data/spec/public/session/cookie_session_spec.rb +73 -0
- data/spec/public/session/memcached_session_spec.rb +31 -0
- data/spec/public/session/memory_session_spec.rb +28 -0
- data/spec/public/session/multiple_sessions_spec.rb +74 -0
- data/spec/public/session/no_session_spec.rb +12 -0
- data/spec/public/session/session_spec.rb +91 -0
- data/spec/public/test/controllers/spec_helper_controller.rb +2 -1
- data/spec/public/test/request_helper_spec.rb +15 -0
- data/spec/spec_helper.rb +2 -2
- metadata +23 -5
- data/spec/private/dispatch/cookies_spec.rb +0 -219
- data/spec/private/dispatch/session_mixin_spec.rb +0 -47
@@ -1,19 +1,63 @@
|
|
1
|
+
require 'merb-core/dispatch/session/container'
|
2
|
+
require 'merb-core/dispatch/session/store_container'
|
3
|
+
|
1
4
|
module Merb
|
2
|
-
|
5
|
+
|
6
|
+
class Config
|
7
|
+
|
8
|
+
# List of all session_stores taken from :session_stores or :session_store
|
9
|
+
def self.session_stores
|
10
|
+
@session_stores ||= begin
|
11
|
+
config_stores = Array(
|
12
|
+
Merb::Config[:session_stores] || Merb::Config[:session_store]
|
13
|
+
)
|
14
|
+
config_stores.map { |name| name.to_sym }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
# The Merb::Session module gets mixed into Merb::SessionContainer to allow
|
21
|
+
# app-level functionality (usually found in app/models/merb/session.rb)
|
22
|
+
module Session
|
23
|
+
end
|
24
|
+
|
3
25
|
module SessionMixin
|
4
|
-
|
5
|
-
#
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
26
|
+
|
27
|
+
# Raised when no suitable session store has been setup.
|
28
|
+
class NoSessionContainer < StandardError; end
|
29
|
+
|
30
|
+
# Raised when storing more data than the available space reserved.
|
31
|
+
class SessionOverflow < StandardError; end
|
32
|
+
|
33
|
+
# Session configuration options:
|
34
|
+
#
|
35
|
+
# :session_id_key The key by which a session value/id is
|
36
|
+
# retrieved; defaults to _session_id
|
37
|
+
#
|
38
|
+
# :session_expiry When to expire the session cookie;
|
39
|
+
# defaults to 2 weeks
|
40
|
+
#
|
41
|
+
# :session_secret_key A secret string which is used to sign/validate
|
42
|
+
# session data; min. 16 chars
|
43
|
+
#
|
44
|
+
# :default_cookie_domain The default domain to write cookies for.
|
45
|
+
|
46
|
+
def self.included(base)
|
47
|
+
# Register a callback to finalize sessions - needs to run before the cookie
|
48
|
+
# callback extracts Set-Cookie headers from request.cookies.
|
49
|
+
base._after_dispatch_callbacks.unshift lambda { |c| c.request.finalize_session }
|
12
50
|
end
|
13
51
|
|
14
|
-
|
15
|
-
|
16
|
-
|
52
|
+
# ==== Parameters
|
53
|
+
# session_store<String>:: The type of session store to access.
|
54
|
+
#
|
55
|
+
# ==== Returns
|
56
|
+
# SessionContainer:: The session that was extracted from the request object.
|
57
|
+
def session(session_store = nil) request.session(session_store) end
|
58
|
+
|
59
|
+
# Module methods
|
60
|
+
|
17
61
|
# ==== Returns
|
18
62
|
# String:: A random 32 character string for use as a unique session ID.
|
19
63
|
def rand_uuid
|
@@ -34,45 +78,127 @@ module Merb
|
|
34
78
|
@_new_cookie = true
|
35
79
|
end
|
36
80
|
|
37
|
-
|
38
|
-
|
39
|
-
# you can recover.
|
40
|
-
#
|
41
|
-
# See session mixins documentation for details on
|
42
|
-
# session finalization.
|
43
|
-
#
|
44
|
-
# ==== Params
|
45
|
-
# &block::
|
46
|
-
# A block to be added to the callbacks that will be executed
|
47
|
-
# if there's exception on session finalization.
|
48
|
-
def finalize_session_exception_callbacks(&block)
|
49
|
-
if block_given?
|
50
|
-
@_finalize_session_exception_callbacks << block
|
51
|
-
else
|
52
|
-
@_finalize_session_exception_callbacks
|
53
|
-
end
|
81
|
+
def needs_new_cookie?
|
82
|
+
@_new_cookie
|
54
83
|
end
|
55
84
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
85
|
+
module_function :rand_uuid, :needs_new_cookie!, :needs_new_cookie?
|
86
|
+
|
87
|
+
module RequestMixin
|
88
|
+
|
89
|
+
def self.included(base)
|
90
|
+
base.extend ClassMethods
|
91
|
+
|
92
|
+
# Keep track of all known session store types.
|
93
|
+
base.cattr_accessor :registered_session_types
|
94
|
+
base.registered_session_types = Dictionary.new
|
95
|
+
base.class_inheritable_accessor :_session_id_key, :_session_secret_key,
|
96
|
+
:_session_expiry
|
97
|
+
|
98
|
+
base._session_id_key = Merb::Config[:session_id_key] || '_session_id'
|
99
|
+
base._session_expiry = Merb::Config[:session_expiry] || Merb::Const::WEEK * 2
|
100
|
+
base._session_secret_key = Merb::Config[:session_secret_key]
|
101
|
+
end
|
102
|
+
|
103
|
+
module ClassMethods
|
104
|
+
|
105
|
+
# ==== Parameters
|
106
|
+
# name<~to_sym>:: Name of the session type to register.
|
107
|
+
# class_name<String>:: The corresponding class name.
|
108
|
+
#
|
109
|
+
# === Notres
|
110
|
+
# This is automatically called when Merb::SessionContainer is subclassed.
|
111
|
+
def register_session_type(name, class_name)
|
112
|
+
self.registered_session_types[name.to_sym] = class_name
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
# The default session store type.
|
118
|
+
def default_session_store
|
119
|
+
Merb::Config[:session_store] && Merb::Config[:session_store].to_sym
|
120
|
+
end
|
121
|
+
|
122
|
+
# ==== Returns
|
123
|
+
# Hash:: All active session stores by type.
|
124
|
+
def session_stores
|
125
|
+
@session_stores ||= {}
|
126
|
+
end
|
127
|
+
|
128
|
+
# ==== Parameters
|
129
|
+
# session_store<String>:: The type of session store to access,
|
130
|
+
# defaults to default_session_store.
|
131
|
+
#
|
132
|
+
# === Notes
|
133
|
+
# If no suitable session store type is given, it defaults to
|
134
|
+
# cookie-based sessions.
|
135
|
+
def session(session_store = nil)
|
136
|
+
session_store ||= default_session_store
|
137
|
+
if class_name = self.class.registered_session_types[session_store]
|
138
|
+
session_stores[session_store] ||= Object.full_const_get(class_name).setup(self)
|
139
|
+
elsif fallback = self.class.registered_session_types.keys.first
|
140
|
+
Merb.logger.warn "Session store not found, '#{session_store}'."
|
141
|
+
Merb.logger.warn "Defaulting to #{fallback} sessions."
|
142
|
+
session(fallback)
|
143
|
+
else
|
144
|
+
Merb.logger.error "Can't use sessions because no session store is available."
|
145
|
+
raise NoSessionContainer, "No session store configured."
|
146
|
+
end
|
72
147
|
end
|
148
|
+
|
149
|
+
# ==== Parameters
|
150
|
+
# new_session<Merb::SessionContainer>:: A session store instance.
|
151
|
+
#
|
152
|
+
# === Notes
|
153
|
+
# The session is assigned internally by its session_store_type key.
|
154
|
+
def session=(new_session)
|
155
|
+
if self.session?(new_session.class.session_store_type)
|
156
|
+
original_session_id = self.session(new_session.class.session_store_type).session_id
|
157
|
+
if new_session.session_id != original_session_id
|
158
|
+
set_session_id_cookie(new_session.session_id)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
session_stores[new_session.class.session_store_type] = new_session
|
162
|
+
end
|
163
|
+
|
164
|
+
# Whether a session has been setup
|
165
|
+
def session?(session_store = nil)
|
166
|
+
(session_store ? [session_store] : session_stores).any? do |type, store|
|
167
|
+
store.is_a?(Merb::SessionContainer)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Teardown and/or persist the current sessions.
|
172
|
+
def finalize_session
|
173
|
+
session_stores.each { |name, store| store.finalize(self) }
|
174
|
+
end
|
175
|
+
alias :finalize_sessions :finalize_session
|
176
|
+
|
177
|
+
# Assign default cookie values
|
178
|
+
def default_cookies
|
179
|
+
defaults = {}
|
180
|
+
if route && route.allow_fixation? && params.key?(_session_id_key)
|
181
|
+
Merb.logger.info("Fixated session id: #{_session_id_key}")
|
182
|
+
defaults[_session_id_key] = params[_session_id_key]
|
183
|
+
end
|
184
|
+
defaults
|
185
|
+
end
|
186
|
+
|
187
|
+
# ==== Parameters
|
188
|
+
# value<String>:: The value of the session cookie; either the session id or the actual encoded data.
|
189
|
+
def set_session_cookie_value(value)
|
190
|
+
options = {}
|
191
|
+
options[:expires] = Time.now + _session_expiry
|
192
|
+
cookies.set_cookie(_session_id_key, value, options)
|
193
|
+
end
|
194
|
+
alias :set_session_id_cookie :set_session_cookie_value
|
195
|
+
|
196
|
+
# ==== Returns
|
197
|
+
# String:: The value of the session cookie; either the session id or the actual encoded data.
|
198
|
+
def session_cookie_value
|
199
|
+
cookies[_session_id_key]
|
200
|
+
end
|
201
|
+
alias :session_id :session_cookie_value
|
73
202
|
end
|
74
|
-
|
75
|
-
module_function :rand_uuid, :needs_new_cookie!, :finalize_session_exception_callbacks, :persist_exception_callbacks
|
76
203
|
end
|
77
|
-
|
78
204
|
end
|
@@ -5,18 +5,24 @@ module Merb
|
|
5
5
|
def call(env)
|
6
6
|
status, headers, body = @app.call(env)
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
if last_modified = headers[Merb::Const::LAST_MODIFIED]
|
15
|
-
status = 304 if last_modified == env[Merb::Const::HTTP_IF_MODIFIED_SINCE]
|
8
|
+
if document_not_modified?(env, headers)
|
9
|
+
status = 304
|
10
|
+
body = ""
|
11
|
+
# set Date header using RFC1123 date format as specified by HTTP
|
12
|
+
# RFC2616 section 3.3.1.
|
16
13
|
end
|
17
14
|
|
18
15
|
[status, headers, body]
|
19
16
|
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def document_not_modified?(env, headers)
|
20
|
+
if etag = headers[Merb::Const::ETAG]
|
21
|
+
etag == env[Merb::Const::HTTP_IF_NONE_MATCH]
|
22
|
+
elsif last_modified = headers[Merb::Const::LAST_MODIFIED]
|
23
|
+
last_modified == env[Merb::Const::HTTP_IF_MODIFIED_SINCE]
|
24
|
+
end
|
25
|
+
end
|
20
26
|
end
|
21
27
|
|
22
28
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module Merb
|
4
|
+
module Rack
|
5
|
+
|
6
|
+
class Csrf < Merb::Rack::Middleware
|
7
|
+
HTML_TYPES = %w(text/html application/xhtml+xml)
|
8
|
+
POST_FORM_RE = Regexp.compile('(<form\W[^>]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', Regexp::IGNORECASE)
|
9
|
+
ERROR_MSG = '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><body><h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p></body></html>'.freeze
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
status, header, body = @app.call(env)
|
13
|
+
|
14
|
+
if env[Merb::Const::REQUEST_METHOD] == Merb::Const::GET
|
15
|
+
body = process_response(body) if valid_content_type?(header[Merb::Const::CONTENT_TYPE])
|
16
|
+
elsif env[Merb::Const::REQUEST_METHOD] == Merb::Const::POST
|
17
|
+
status, body = process_request(env, status, body)
|
18
|
+
end
|
19
|
+
|
20
|
+
[status, header, body]
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def process_request(env, status, body)
|
25
|
+
session_id = Merb::Config[:session_id_key]
|
26
|
+
csrf_token = _make_token(session_id)
|
27
|
+
|
28
|
+
request_csrf_token = env['csrf_authentication_token']
|
29
|
+
|
30
|
+
unless csrf_token == request_csrf_token
|
31
|
+
exception = Merb::ControllerExceptions::Forbidden.new(ERROR_MSG)
|
32
|
+
status = exception.status
|
33
|
+
body = exception.message
|
34
|
+
|
35
|
+
return [status, body]
|
36
|
+
end
|
37
|
+
|
38
|
+
return [status, body]
|
39
|
+
end
|
40
|
+
|
41
|
+
def process_response(body)
|
42
|
+
session_id = Merb::Config[:session_id_key]
|
43
|
+
csrf_token = _make_token(session_id)
|
44
|
+
|
45
|
+
if csrf_token
|
46
|
+
modified_body = ''
|
47
|
+
body.scan(POST_FORM_RE) do |match|
|
48
|
+
modified_body << add_csrf_field($~, csrf_token)
|
49
|
+
end
|
50
|
+
|
51
|
+
body = modified_body
|
52
|
+
end
|
53
|
+
|
54
|
+
body
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_csrf_field(match, csrf_token)
|
58
|
+
modified_body = match.pre_match
|
59
|
+
modified_body << match.to_s
|
60
|
+
modified_body << "<div style='display: none;'><input type='hidden' id='csrf_authentication_token' name='csrf_authentication_token' value='#{csrf_token}' /></div>"
|
61
|
+
modified_body << match.post_match
|
62
|
+
end
|
63
|
+
|
64
|
+
def valid_content_type?(content_type)
|
65
|
+
HTML_TYPES.include?(content_type.split(';').first)
|
66
|
+
end
|
67
|
+
|
68
|
+
def _make_token(session_id)
|
69
|
+
Digest::MD5.hexdigest(Merb::Config[:session_secret_key] + session_id)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/merb-core/rack.rb
CHANGED
@@ -20,5 +20,6 @@ module Merb
|
|
20
20
|
autoload :Tracer, 'merb-core/rack/middleware/tracer'
|
21
21
|
autoload :ContentLength, 'merb-core/rack/middleware/content_length'
|
22
22
|
autoload :ConditionalGet, 'merb-core/rack/middleware/conditional_get'
|
23
|
+
autoload :Csrf, 'merb-core/rack/middleware/csrf'
|
23
24
|
end # Rack
|
24
25
|
end # Merb
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Merb
|
2
|
+
module ScriptHelpers
|
3
|
+
|
4
|
+
# Adapt rubygems - shortcut for setup_local_gems that figures out the
|
5
|
+
# local gem path to use.
|
6
|
+
def setup_local_gems!(rootdir = nil)
|
7
|
+
if bundled? && local_gems = setup_local_gems(File.join(rootdir || merb_root, 'gems'))
|
8
|
+
if local_gems.is_a?(Array)
|
9
|
+
puts "Using local gems in addition to system gems..."
|
10
|
+
if verbose?
|
11
|
+
puts "Found #{local_gems.length} local gems:"
|
12
|
+
local_gems.each { |name| puts "- #{name}" }
|
13
|
+
end
|
14
|
+
elsif local_gems
|
15
|
+
puts "Using MiniGems to locate local/system gems..."
|
16
|
+
end
|
17
|
+
elsif use_minigems?
|
18
|
+
puts "Using MiniGems to locate system gems..."
|
19
|
+
else
|
20
|
+
puts "Using system gems..."
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Adapt rubygems - because the /usr/bin/merb script already setup merb-core loadpaths
|
25
|
+
# from the system-wide rubygem paths. The code below will make sure local gems always
|
26
|
+
# get precedence over system gems and resolves any conflicts that may arise.
|
27
|
+
#
|
28
|
+
# Only native Gem methods are used to handle the internal logic transparently.
|
29
|
+
# These methods are proved either by minigems or standard rubygems.
|
30
|
+
#
|
31
|
+
# Note: currently the Kernel.load_dependency method will always load local gems.
|
32
|
+
def setup_local_gems(gems_path)
|
33
|
+
if File.directory?(gems_path)
|
34
|
+
if use_minigems?
|
35
|
+
# Reset all loaded system gems - replace with local gems
|
36
|
+
Gem.clear_paths
|
37
|
+
Gem.path.unshift(gems_path)
|
38
|
+
return true
|
39
|
+
else
|
40
|
+
# Remember originally loaded system gems and create a lookup of gems to load paths
|
41
|
+
system_gemspecs = Gem.cache.gems
|
42
|
+
system_load_paths = extract_gem_load_paths(system_gemspecs)
|
43
|
+
|
44
|
+
# Reset all loaded system gems - replace with local gems
|
45
|
+
Gem.clear_paths
|
46
|
+
Gem.path.unshift(gems_path)
|
47
|
+
Gem.cache.load_gems_in(File.join(gems_path, "specifications"))
|
48
|
+
|
49
|
+
# Collect any local gems we're going to use
|
50
|
+
local_gems = Gem.cache.map { |name, spec| name }
|
51
|
+
|
52
|
+
# Create a lookup of gems to load paths for all local gems
|
53
|
+
local_load_paths = extract_gem_load_paths(Gem.cache.gems)
|
54
|
+
|
55
|
+
# Filter out local gems from the originally loaded system gems to prevent conflicts
|
56
|
+
active_system_gems = []
|
57
|
+
system_gemspecs.each do |name, spec|
|
58
|
+
active_system_gems << spec unless local_load_paths[spec.name]
|
59
|
+
end
|
60
|
+
|
61
|
+
# Re-add the system gems - conflicts with local gems have been avoided
|
62
|
+
Gem.cache.add_specs(*active_system_gems)
|
63
|
+
|
64
|
+
# Add local paths to LOAD_PATH - remove overlapping system gem paths
|
65
|
+
local_load_paths.each do |name, paths|
|
66
|
+
$LOAD_PATH.unshift(*paths)
|
67
|
+
$LOAD_PATH.replace($LOAD_PATH - system_load_paths[name] || [])
|
68
|
+
end
|
69
|
+
return local_gems
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Figure out the merb root or default to current directory
|
75
|
+
def merb_root
|
76
|
+
root_key = %w[-m --merb-root].detect { |o| ARGV.index(o) }
|
77
|
+
root = ARGV[ARGV.index(root_key) + 1] if root_key
|
78
|
+
root.to_a.empty? ? Dir.getwd : root
|
79
|
+
end
|
80
|
+
|
81
|
+
# See if we're running merb locally - enabled by default
|
82
|
+
# The ENV variables are considered for Rakefile usage.
|
83
|
+
def bundled?
|
84
|
+
enabled = ENV.key?("BUNDLE") || %w[-B --bundle].detect { |o| ARGV.index(o) }
|
85
|
+
disabled = ENV.key?("NO_BUNDLE") || %w[--no-bundle].detect { |o| ARGV.index(o) }
|
86
|
+
enabled || !disabled
|
87
|
+
end
|
88
|
+
|
89
|
+
# Add some extra feedback if verbose is enabled
|
90
|
+
def verbose?
|
91
|
+
%w[-V --verbose].detect { |o| ARGV.index(o) }
|
92
|
+
end
|
93
|
+
|
94
|
+
# Whether minigems has been loaded instead of the full rubygems
|
95
|
+
def use_minigems?
|
96
|
+
Gem.respond_to?(:minigems?) && Gem.minigems?
|
97
|
+
end
|
98
|
+
|
99
|
+
# Helper method to extract a Hash lookup of gem name to load paths
|
100
|
+
def extract_gem_load_paths(source_index)
|
101
|
+
source_index.inject({}) do |load_paths, (name, spec)|
|
102
|
+
require_paths = spec.require_paths
|
103
|
+
require_paths << spec.bindir unless spec.executables.empty?
|
104
|
+
load_paths[spec.name] = require_paths.map do |path|
|
105
|
+
File.join(spec.full_gem_path, path)
|
106
|
+
end
|
107
|
+
load_paths
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
data/lib/merb-core/server.rb
CHANGED
@@ -50,6 +50,7 @@ module Merb
|
|
50
50
|
puts "Running bootloaders..." if Merb::Config[:verbose]
|
51
51
|
BootLoader.run
|
52
52
|
puts "Starting Rack adapter..." if Merb::Config[:verbose]
|
53
|
+
Merb.logger.info! "Starting Merb server listening at #{Merb::Config[:host]}:#{port}"
|
53
54
|
Merb.adapter.start(Merb::Config.to_hash)
|
54
55
|
end
|
55
56
|
end
|
@@ -103,6 +104,7 @@ module Merb
|
|
103
104
|
end
|
104
105
|
end
|
105
106
|
ensure
|
107
|
+
Merb.started = false
|
106
108
|
exit
|
107
109
|
end
|
108
110
|
end
|
@@ -9,4 +9,29 @@ end
|
|
9
9
|
|
10
10
|
def install_home
|
11
11
|
ENV['GEM_HOME'] ? "-i #{ENV['GEM_HOME']}" : ""
|
12
|
+
end
|
13
|
+
|
14
|
+
def install_command(gem_name, gem_version, options = '--no-update-sources --no-rdoc --no-ri')
|
15
|
+
options << " -i #{ENV['GEM_DIR']}" if ENV['GEM_DIR']
|
16
|
+
%{#{sudo} gem install #{install_home} --local pkg/#{gem_name}-#{gem_version}.gem #{options}}
|
17
|
+
end
|
18
|
+
|
19
|
+
def dev_install_command(gem_name, gem_version, options = '--no-update-sources --no-rdoc --no-ri')
|
20
|
+
options << ' --development'
|
21
|
+
install_command(gem_name, gem_version, options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def jinstall_command(gem_name, gem_version, options = '--no-update-sources --no-rdoc --no-ri')
|
25
|
+
options << " -i #{ENV['GEM_DIR']}" if ENV['GEM_DIR']
|
26
|
+
%{#{sudo} jruby -S gem install #{install_home} --local pkg/#{gem_name}-#{gem_version}.gem #{options}}
|
27
|
+
end
|
28
|
+
|
29
|
+
def dev_jinstall_command(gem_name, gem_version, options = '--no-update-sources --no-rdoc --no-ri')
|
30
|
+
options << ' --development'
|
31
|
+
jinstall_command(gem_name, gem_version, options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def uninstall_command(gem_name, options = '')
|
35
|
+
options << " -i #{ENV['GEM_DIR']}" if ENV['GEM_DIR']
|
36
|
+
%{#{sudo} gem uninstall #{gem_name} #{options}}
|
12
37
|
end
|
@@ -42,6 +42,23 @@ module Merb
|
|
42
42
|
}) unless defined?(DEFAULT_ENV)
|
43
43
|
end
|
44
44
|
|
45
|
+
# CookieJar keeps track of cookies in a simple Mash.
|
46
|
+
class CookieJar < Mash
|
47
|
+
|
48
|
+
# ==== Parameters
|
49
|
+
# request<Merb::Request, Merb::FakeRequest>:: The controller request.
|
50
|
+
def update_from_request(request)
|
51
|
+
request.cookies.each do |key, value|
|
52
|
+
if value.blank?
|
53
|
+
self.delete(key)
|
54
|
+
else
|
55
|
+
self[key] = Merb::Request.unescape(value)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
45
62
|
# ==== Parameters
|
46
63
|
# env<Hash>:: A hash of environment keys to be merged into the default list.
|
47
64
|
# opt<Hash>:: A hash of options (see below).
|
@@ -100,7 +117,27 @@ module Merb
|
|
100
117
|
params = merge_controller_and_action(controller_klass, action, params)
|
101
118
|
dispatch_request(build_request(params, env), controller_klass, action.to_s, &blk)
|
102
119
|
end
|
103
|
-
|
120
|
+
|
121
|
+
# Keep track of cookie values in CookieJar within the context of the
|
122
|
+
# block; you need to set this up for secific controllers.
|
123
|
+
#
|
124
|
+
# ==== Parameters
|
125
|
+
# *controller_classes:: Controller classes to operate on in the context of the block.
|
126
|
+
# &blk:: The context to operate on; optionally accepts the cookie jar as an argument.
|
127
|
+
def with_cookies(*controller_classes, &blk)
|
128
|
+
cookie_jar = CookieJar.new
|
129
|
+
before_cb = lambda { |c| c.cookies.update(cookie_jar) }
|
130
|
+
after_cb = lambda { |c| cookie_jar.update_from_request(c.request) }
|
131
|
+
controller_classes.each do |klass|
|
132
|
+
klass._before_dispatch_callbacks << before_cb
|
133
|
+
klass._after_dispatch_callbacks << after_cb
|
134
|
+
end
|
135
|
+
blk.arity == 1 ? blk.call(cookie_jar) : blk.call
|
136
|
+
controller_classes.each do |klass|
|
137
|
+
klass._before_dispatch_callbacks.delete before_cb
|
138
|
+
klass._after_dispatch_callbacks.delete after_cb
|
139
|
+
end
|
140
|
+
end
|
104
141
|
|
105
142
|
# Dispatches an action to the given class and using HTTP Basic Authentication
|
106
143
|
# This bypasses the router and is suitable for unit testing of controllers.
|
@@ -297,7 +334,7 @@ module Merb
|
|
297
334
|
# The workhorse for the dispatch*to helpers.
|
298
335
|
#
|
299
336
|
# ==== Parameters
|
300
|
-
# request<Merb::Test::FakeRequest, Merb::Request>::
|
337
|
+
# request<Merb::Test::RequestHelper::FakeRequest, Merb::Request>::
|
301
338
|
# A request object that has been setup for testing.
|
302
339
|
# controller_klass<Merb::Controller>::
|
303
340
|
# The class object off the controller to dispatch the action to.
|
@@ -328,7 +365,7 @@ module Merb
|
|
328
365
|
# Checks to see that a request is routable.
|
329
366
|
#
|
330
367
|
# ==== Parameters
|
331
|
-
# request<Merb::Test::FakeRequest, Merb::Request>::
|
368
|
+
# request<Merb::Test::RequestHelper::FakeRequest, Merb::Request>::
|
332
369
|
# The request object to inspect.
|
333
370
|
#
|
334
371
|
# ==== Raises
|
@@ -9,7 +9,8 @@ require 'benchmark'
|
|
9
9
|
# spec_cmd<~to_s>:: The spec command. Defaults to "spec".
|
10
10
|
# run_opts<String>:: Options to pass to spec commands, for instance,
|
11
11
|
# if you want to use profiling formatter.
|
12
|
-
|
12
|
+
# except<Array[String]>:: File paths to skip.
|
13
|
+
def run_specs(globs, spec_cmd='spec', run_opts = "-c", except = [])
|
13
14
|
require "optparse"
|
14
15
|
require "spec"
|
15
16
|
globs = globs.is_a?(Array) ? globs : [globs]
|
@@ -17,7 +18,7 @@ def run_specs(globs, spec_cmd='spec', run_opts = "-c")
|
|
17
18
|
|
18
19
|
time = Benchmark.measure do
|
19
20
|
globs.each do |glob|
|
20
|
-
Dir[glob].each do |spec|
|
21
|
+
(Dir[glob] - except).each do |spec|
|
21
22
|
STDOUT.puts "\n\nRunning #{spec}...\n"
|
22
23
|
response = Open3.popen3("#{spec_cmd} #{File.expand_path(spec)} #{run_opts}") do |i,o,e|
|
23
24
|
while out = o.gets
|
@@ -45,4 +46,4 @@ def run_specs(globs, spec_cmd='spec', run_opts = "-c")
|
|
45
46
|
puts "#{examples} examples, #{failures} failures, #{errors} errors, #{pending} pending, #{sprintf("suite run in %3.3f seconds", time.real)}"
|
46
47
|
# TODO: we need to report pending examples all together
|
47
48
|
print "\e[0m"
|
48
|
-
end
|
49
|
+
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
module Language
|
2
|
-
|
3
1
|
module English
|
4
2
|
|
5
3
|
# = English Nouns Number Inflection.
|
@@ -26,12 +24,12 @@ module English
|
|
26
24
|
#
|
27
25
|
# Here we define erratum/errata exception case:
|
28
26
|
#
|
29
|
-
#
|
27
|
+
# English::Inflect.word "erratum", "errata"
|
30
28
|
#
|
31
29
|
# In case singular and plural forms are the same omit
|
32
30
|
# second argument on call:
|
33
31
|
#
|
34
|
-
#
|
32
|
+
# English::Inflect.word 'information'
|
35
33
|
def word(singular, plural=nil)
|
36
34
|
plural = singular unless plural
|
37
35
|
singular_word(singular, plural)
|
@@ -88,7 +86,7 @@ module English
|
|
88
86
|
# capitalized (Man => Men) #
|
89
87
|
# ==== Examples
|
90
88
|
# Once the following rule is defined:
|
91
|
-
#
|
89
|
+
# English::Inflect.rule 'y', 'ies'
|
92
90
|
#
|
93
91
|
# You can see the following results:
|
94
92
|
# irb> "fly".plural
|
@@ -113,7 +111,7 @@ module English
|
|
113
111
|
#
|
114
112
|
# ==== Examples
|
115
113
|
# Once the following rule is defined:
|
116
|
-
#
|
114
|
+
# English::Inflect.singular_rule 'o', 'oes'
|
117
115
|
#
|
118
116
|
# You can see the following results:
|
119
117
|
# irb> "heroes".singular
|
@@ -132,7 +130,7 @@ module English
|
|
132
130
|
#
|
133
131
|
# ==== Examples
|
134
132
|
# Once the following rule is defined:
|
135
|
-
#
|
133
|
+
# English::Inflect.singular_rule 'fe', 'ves'
|
136
134
|
#
|
137
135
|
# You can see the following results:
|
138
136
|
# irb> "wife".plural
|
@@ -331,15 +329,14 @@ module English
|
|
331
329
|
|
332
330
|
end
|
333
331
|
end
|
334
|
-
end
|
335
332
|
|
336
333
|
class String
|
337
334
|
def singular
|
338
|
-
|
335
|
+
English::Inflect.singular(self)
|
339
336
|
end
|
340
337
|
alias_method(:singularize, :singular)
|
341
338
|
def plural
|
342
|
-
|
339
|
+
English::Inflect.plural(self)
|
343
340
|
end
|
344
341
|
alias_method(:pluralize, :plural)
|
345
342
|
end
|
data/lib/merb-core/version.rb
CHANGED