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.
Files changed (78) hide show
  1. data/CHANGELOG +925 -0
  2. data/CONTRIBUTORS +93 -0
  3. data/PUBLIC_CHANGELOG +85 -0
  4. data/Rakefile +18 -28
  5. data/bin/merb +34 -5
  6. data/lib/merb-core/autoload.rb +2 -3
  7. data/lib/merb-core/bootloader.rb +60 -66
  8. data/lib/merb-core/config.rb +7 -1
  9. data/lib/merb-core/controller/abstract_controller.rb +35 -21
  10. data/lib/merb-core/controller/merb_controller.rb +15 -42
  11. data/lib/merb-core/controller/mixins/authentication.rb +42 -6
  12. data/lib/merb-core/controller/mixins/conditional_get.rb +83 -0
  13. data/lib/merb-core/controller/mixins/render.rb +3 -3
  14. data/lib/merb-core/core_ext/kernel.rb +6 -19
  15. data/lib/merb-core/dispatch/cookies.rb +96 -80
  16. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +2 -0
  17. data/lib/merb-core/dispatch/request.rb +18 -16
  18. data/lib/merb-core/dispatch/router/route.rb +6 -0
  19. data/lib/merb-core/dispatch/router.rb +4 -1
  20. data/lib/merb-core/dispatch/session/container.rb +64 -0
  21. data/lib/merb-core/dispatch/session/cookie.rb +91 -101
  22. data/lib/merb-core/dispatch/session/memcached.rb +38 -174
  23. data/lib/merb-core/dispatch/session/memory.rb +62 -208
  24. data/lib/merb-core/dispatch/session/store_container.rb +145 -0
  25. data/lib/merb-core/dispatch/session.rb +174 -48
  26. data/lib/merb-core/rack/middleware/conditional_get.rb +14 -8
  27. data/lib/merb-core/rack/middleware/csrf.rb +73 -0
  28. data/lib/merb-core/rack.rb +1 -0
  29. data/lib/merb-core/script.rb +112 -0
  30. data/lib/merb-core/server.rb +2 -0
  31. data/lib/merb-core/tasks/merb_rake_helper.rb +25 -0
  32. data/lib/merb-core/test/helpers/request_helper.rb +40 -3
  33. data/lib/merb-core/test/run_specs.rb +4 -3
  34. data/lib/merb-core/vendor/facets/inflect.rb +7 -10
  35. data/lib/merb-core/version.rb +1 -1
  36. data/lib/merb-core.rb +11 -40
  37. data/spec/private/core_ext/kernel_spec.rb +0 -11
  38. data/spec/private/dispatch/fixture/log/merb_test.log +893 -0
  39. data/spec/private/router/fixture/log/merb_test.log +12 -1728
  40. data/spec/private/router/route_spec.rb +4 -0
  41. data/spec/private/router/router_spec.rb +8 -0
  42. data/spec/private/vendor/facets/plural_spec.rb +1 -1
  43. data/spec/private/vendor/facets/singular_spec.rb +1 -1
  44. data/spec/public/abstract_controller/controllers/display.rb +8 -2
  45. data/spec/public/abstract_controller/controllers/filters.rb +18 -0
  46. data/spec/public/abstract_controller/display_spec.rb +6 -2
  47. data/spec/public/abstract_controller/filter_spec.rb +4 -0
  48. data/spec/public/controller/authentication_spec.rb +114 -43
  49. data/spec/public/controller/base_spec.rb +8 -0
  50. data/spec/public/controller/conditional_get_spec.rb +100 -0
  51. data/spec/public/controller/config/init.rb +1 -1
  52. data/spec/public/controller/controllers/authentication.rb +29 -0
  53. data/spec/public/controller/controllers/base.rb +13 -0
  54. data/spec/public/controller/controllers/conditional_get.rb +35 -0
  55. data/spec/public/controller/controllers/cookies.rb +10 -1
  56. data/spec/public/controller/cookies_spec.rb +38 -9
  57. data/spec/public/controller/spec_helper.rb +1 -0
  58. data/spec/public/controller/url_spec.rb +70 -1
  59. data/spec/public/directory_structure/directory/log/merb_test.log +461 -0
  60. data/spec/public/rack/conditinal_get_middleware_spec.rb +77 -89
  61. data/spec/public/rack/csrf_middleware_spec.rb +70 -0
  62. data/spec/public/reloading/directory/log/merb_test.log +52 -0
  63. data/spec/public/request/request_spec.rb +19 -1
  64. data/spec/public/router/fixation_spec.rb +26 -4
  65. data/spec/public/router/fixture/log/merb_test.log +234 -30332
  66. data/spec/public/session/controllers/sessions.rb +52 -0
  67. data/spec/public/session/cookie_session_spec.rb +73 -0
  68. data/spec/public/session/memcached_session_spec.rb +31 -0
  69. data/spec/public/session/memory_session_spec.rb +28 -0
  70. data/spec/public/session/multiple_sessions_spec.rb +74 -0
  71. data/spec/public/session/no_session_spec.rb +12 -0
  72. data/spec/public/session/session_spec.rb +91 -0
  73. data/spec/public/test/controllers/spec_helper_controller.rb +2 -1
  74. data/spec/public/test/request_helper_spec.rb +15 -0
  75. data/spec/spec_helper.rb +2 -2
  76. metadata +23 -5
  77. data/spec/private/dispatch/cookies_spec.rb +0 -219
  78. 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
- # Sets the session id cookie, along with the correct
5
- # expiry and domain -- used for new or reset sessions
6
- def set_session_id_cookie(key)
7
- options = {}
8
- options[:value] = key
9
- options[:expires] = Time.now + _session_expiry if _session_expiry > 0
10
- options[:domain] = _session_cookie_domain if _session_cookie_domain
11
- cookies[_session_id_key] = options
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
- @_finalize_session_exception_callbacks = []
15
- @_persist_exception_callbacks = []
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
- # Adds a callback to the list of callbacks run when
38
- # exception is raised on session finalization, so
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
- # Adds a callback to the list of callbacks run when
57
- # exception is raised on session persisting, so
58
- # you can recover.
59
- #
60
- # See session mixins documentation for details on
61
- # session persisting.
62
- #
63
- # ==== Params
64
- # &block::
65
- # A block to be added to the callbacks that will be executed
66
- # if there's exception on session persisting.
67
- def persist_exception_callbacks(&block)
68
- if block_given?
69
- @_persist_exception_callbacks << block
70
- else
71
- @_persist_exception_callbacks
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
- # set Date header using RFC1123 date format as specified by HTTP
9
- # RFC2616 section 3.3.1.
10
- if etag = headers['ETag']
11
- status = 304 if etag == env[Merb::Const::HTTP_IF_NONE_MATCH]
12
- end
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
@@ -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
@@ -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
- def run_specs(globs, spec_cmd='spec', run_opts = "-c")
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
- # Language::English::Inflector.word "erratum", "errata"
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
- # Language::English::Inflector.word 'information'
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
- # Language::English::Inflector.rule 'y', 'ies'
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
- # Language::English::Inflector.singular_rule 'o', 'oes'
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
- # Language::English::Inflector.singular_rule 'fe', 'ves'
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
- Language::English::Inflect.singular(self)
335
+ English::Inflect.singular(self)
339
336
  end
340
337
  alias_method(:singularize, :singular)
341
338
  def plural
342
- Language::English::Inflect.plural(self)
339
+ English::Inflect.plural(self)
343
340
  end
344
341
  alias_method(:pluralize, :plural)
345
342
  end
@@ -1,5 +1,5 @@
1
1
  module Merb
2
- VERSION = '0.9.5' unless defined?(Merb::VERSION)
2
+ VERSION = '0.9.6' unless defined?(Merb::VERSION)
3
3
 
4
4
  # Merb::RELEASE meanings:
5
5
  # 'dev' : unreleased