strelka 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/ChangeLog +156 -9
  4. data/History.rdoc +15 -0
  5. data/IDEAS.rdoc +17 -1
  6. data/MILESTONES.rdoc +1 -1
  7. data/Manifest.txt +10 -2
  8. data/Plugins.rdoc +4 -4
  9. data/README.rdoc +3 -3
  10. data/Rakefile +5 -4
  11. data/bin/strelka +19 -10
  12. data/contrib/hoetemplate/data/project/apps/file_name_app +1 -0
  13. data/contrib/hoetemplate/lib/file_name.rb.erb +3 -2
  14. data/examples/apps/hello-world +1 -0
  15. data/examples/apps/ws-chat +69 -0
  16. data/examples/apps/ws-echo +61 -0
  17. data/examples/gen-config.rb +6 -5
  18. data/lib/strelka/app/auth.rb +2 -2
  19. data/lib/strelka/app/errors.rb +1 -1
  20. data/lib/strelka/app/filters.rb +3 -2
  21. data/lib/strelka/app/negotiation.rb +2 -2
  22. data/lib/strelka/app/parameters.rb +1 -2
  23. data/lib/strelka/app/restresources.rb +3 -2
  24. data/lib/strelka/app/routing.rb +1 -1
  25. data/lib/strelka/app/sessions.rb +2 -2
  26. data/lib/strelka/app/templating.rb +7 -3
  27. data/lib/strelka/app.rb +5 -145
  28. data/lib/strelka/behavior/plugin.rb +4 -4
  29. data/lib/strelka/discovery.rb +211 -0
  30. data/lib/strelka/httprequest.rb +1 -0
  31. data/lib/strelka/httpresponse/negotiation.rb +7 -1
  32. data/lib/strelka/mixins.rb +4 -1
  33. data/lib/strelka/paramvalidator.rb +1 -1
  34. data/lib/strelka/plugins.rb +8 -6
  35. data/lib/strelka/websocketserver/routing.rb +116 -0
  36. data/lib/strelka/websocketserver.rb +147 -0
  37. data/lib/strelka.rb +5 -4
  38. data/spec/{lib/constants.rb → constants.rb} +3 -2
  39. data/spec/{lib/helpers.rb → helpers.rb} +15 -14
  40. data/spec/strelka/app/auth_spec.rb +145 -142
  41. data/spec/strelka/app/errors_spec.rb +20 -26
  42. data/spec/strelka/app/filters_spec.rb +67 -54
  43. data/spec/strelka/app/negotiation_spec.rb +8 -14
  44. data/spec/strelka/app/parameters_spec.rb +23 -29
  45. data/spec/strelka/app/restresources_spec.rb +98 -100
  46. data/spec/strelka/app/routing_spec.rb +57 -57
  47. data/spec/strelka/app/sessions_spec.rb +11 -17
  48. data/spec/strelka/app/templating_spec.rb +36 -40
  49. data/spec/strelka/app_spec.rb +48 -147
  50. data/spec/strelka/authprovider/basic_spec.rb +5 -11
  51. data/spec/strelka/authprovider/hostaccess_spec.rb +9 -15
  52. data/spec/strelka/authprovider_spec.rb +3 -9
  53. data/spec/strelka/cookie_spec.rb +32 -38
  54. data/spec/strelka/cookieset_spec.rb +31 -37
  55. data/spec/strelka/discovery_spec.rb +144 -0
  56. data/spec/strelka/exceptions_spec.rb +2 -8
  57. data/spec/strelka/httprequest/acceptparams_spec.rb +74 -83
  58. data/spec/strelka/httprequest/auth_spec.rb +5 -15
  59. data/spec/strelka/httprequest/negotiation_spec.rb +93 -103
  60. data/spec/strelka/httprequest/session_spec.rb +12 -22
  61. data/spec/strelka/httprequest_spec.rb +1 -7
  62. data/spec/strelka/httpresponse/negotiation_spec.rb +84 -76
  63. data/spec/strelka/httpresponse/session_spec.rb +25 -35
  64. data/spec/strelka/httpresponse_spec.rb +20 -26
  65. data/spec/strelka/mixins_spec.rb +66 -61
  66. data/spec/strelka/multipartparser_spec.rb +31 -37
  67. data/spec/strelka/paramvalidator_spec.rb +389 -373
  68. data/spec/strelka/plugins_spec.rb +17 -23
  69. data/spec/strelka/router/default_spec.rb +32 -38
  70. data/spec/strelka/router/exclusive_spec.rb +28 -34
  71. data/spec/strelka/router_spec.rb +2 -8
  72. data/spec/strelka/session/db_spec.rb +17 -15
  73. data/spec/strelka/session/default_spec.rb +22 -28
  74. data/spec/strelka/session_spec.rb +3 -9
  75. data/spec/strelka/websocketserver/routing_spec.rb +119 -0
  76. data/spec/strelka/websocketserver_spec.rb +149 -0
  77. data/spec/strelka_spec.rb +11 -13
  78. data.tar.gz.sig +3 -3
  79. metadata +22 -14
  80. metadata.gz.sig +0 -0
@@ -86,7 +86,7 @@ module Strelka::App::Errors
86
86
 
87
87
 
88
88
  # Plugin load order
89
- run_before :routing
89
+ run_outside :routing
90
90
 
91
91
 
92
92
  # Class-level functionality
@@ -10,7 +10,8 @@ require 'strelka/app' unless defined?( Strelka::App )
10
10
  module Strelka::App::Filters
11
11
  extend Strelka::Plugin
12
12
 
13
- run_before :routing, :templating
13
+ run_inside :templating
14
+ run_outside :routing
14
15
 
15
16
 
16
17
  # Class methods to add to classes with routing.
@@ -68,7 +69,7 @@ module Strelka::App::Filters
68
69
 
69
70
  self.apply_request_filters( request )
70
71
  response = super
71
- self.apply_response_filters( response )
72
+ self.apply_response_filters( request.response )
72
73
 
73
74
  return response
74
75
  end
@@ -29,8 +29,8 @@ module Strelka::App::Negotiation
29
29
  include Strelka::Constants
30
30
  extend Strelka::Plugin
31
31
 
32
- run_before :routing
33
- run_after :filters, :templating, :parameters
32
+ run_outside :routing, :filters
33
+ run_inside :templating, :parameters
34
34
 
35
35
 
36
36
  # Class methods to add to classes with content-negotiation.
@@ -73,8 +73,7 @@ require 'strelka/paramvalidator'
73
73
  module Strelka::App::Parameters
74
74
  extend Strelka::Plugin
75
75
 
76
- run_before :routing
77
- run_after :filters
76
+ run_outside :routing, :filters
78
77
 
79
78
 
80
79
  # Class methods to add to classes with routing.
@@ -369,7 +369,7 @@ module Strelka::App::RestResources
369
369
  end
370
370
 
371
371
 
372
- ### Add a handler method for updating all instances of +rsrcobj+ collection.
372
+ ### Add a handler method for replacing all instances of +rsrcobj+ collection.
373
373
  ### PUT /resources
374
374
  def add_collection_update_handler( route, rsrcobj, options )
375
375
  pkey = rsrcobj.primary_key
@@ -388,7 +388,8 @@ module Strelka::App::RestResources
388
388
  # Save it in a transaction, erroring if any of 'em fail validations
389
389
  begin
390
390
  rsrcobj.db.transaction do
391
- rsrcobj.update( newvals )
391
+ rsrcobj.truncate
392
+ rsrcobj.create( newvals )
392
393
  end
393
394
  rescue Sequel::ValidationFailed => err
394
395
  finish_with( HTTP::BAD_REQUEST, err.message )
@@ -106,7 +106,7 @@ module Strelka::App::Routing
106
106
  log_to :strelka
107
107
 
108
108
  # Plugins API -- set up load order
109
- run_after :templating, :filters, :parameters
109
+ run_inside :templating, :filters, :parameters
110
110
 
111
111
 
112
112
  # Class methods to add to classes with routing.
@@ -94,9 +94,9 @@ module Strelka::App::Sessions
94
94
  # Configurability API -- specify which section of the config this class gets
95
95
  config_key :sessions
96
96
 
97
- # Plugins API -- Specify load order; run as late as possible so other plugins
97
+ # Plugins API -- Specify load order; run as early as possible so other plugins
98
98
  # can use the session
99
- run_after :templating, :filters, :parameters
99
+ run_outside :templating, :filters, :parameters
100
100
 
101
101
 
102
102
  # Class methods and instance variables to add to classes with sessions.
@@ -74,6 +74,10 @@ require 'strelka/plugins'
74
74
  #
75
75
  # It will be loaded, set as the response body, and the above common objects added to it.
76
76
  #
77
+ # :TODO: Explain how returning things other than responses doesn't work well with
78
+ # :filters and maybe other plugins that run inside :templating.
79
+ #
80
+ #
77
81
  # === Layouts
78
82
  #
79
83
  # Very often, you'll want all or most of the views in your app to share a common page
@@ -116,14 +120,13 @@ module Strelka::App::Templating
116
120
  log_to :strelka
117
121
 
118
122
  # Run order
119
- run_before :routing, :negotiation, :errors
120
- run_after :filters
123
+ run_outside :routing, :negotiation, :errors, :filters
121
124
 
122
125
 
123
126
  ### Return an Array of Pathnames to all directories named 'templates' under the
124
127
  ### data dirctories of loaded gems which have a dependency on Strelka.
125
128
  def self::discover_template_dirs
126
- directories = Strelka::App.discover_data_dirs.values.flatten
129
+ directories = Strelka::Discovery.discover_data_dirs.values.flatten
127
130
 
128
131
  self.log.debug "Discovered data directories: %p" % [ directories ]
129
132
 
@@ -282,6 +285,7 @@ module Strelka::App::Templating
282
285
  # Not templated; returned as-is
283
286
  else
284
287
  self.log.debug " response isn't templated; returning nil"
288
+ # :TODO: Return the response instead of nil
285
289
  return nil
286
290
  end
287
291
  end
data/lib/strelka/app.rb CHANGED
@@ -10,12 +10,14 @@ require 'mongrel2/handler'
10
10
  require 'strelka' unless defined?( Strelka )
11
11
  require 'strelka/mixins'
12
12
  require 'strelka/plugins'
13
+ require 'strelka/discovery'
13
14
 
14
15
 
15
16
  # The Strelka HTTP application base class.
16
17
  class Strelka::App < Mongrel2::Handler
17
18
  extend Loggability,
18
19
  Configurability,
20
+ Strelka::Discovery,
19
21
  Strelka::MethodUtilities,
20
22
  Strelka::PluginLoader
21
23
  include Strelka::Constants,
@@ -85,29 +87,14 @@ class Strelka::App < Mongrel2::Handler
85
87
  ### to the 'app' section of the config that will be passed to you when the config
86
88
  ### is loaded.
87
89
  def self::configure( config=nil )
88
- config = self.defaults.merge( config || {} )
89
-
90
+ config = Strelka::App.defaults.merge( config || {} )
90
91
  self.devmode = config[:devmode] || $DEBUG
91
- self.app_glob_pattern = config[:app_glob_pattern]
92
- self.local_data_dirs = config[:local_data_dirs]
93
-
94
92
  self.log.info "Enabled developer mode." if self.devmode?
95
93
  end
96
94
 
97
95
 
98
- ### Inheritance callback -- add subclasses to @subclasses so .load can figure out which
99
- ### classes correspond to which files.
100
- def self::inherited( subclass )
101
- super
102
- self.log.debug "Adding %p to the subclasses hash." % [ subclass ]
103
- Strelka::App.subclasses[ @loading_file ] << subclass #if self == Strelka::App
104
- end
105
-
106
-
107
-
108
96
  ### Overridden from Mongrel2::Handler -- use the value returned from .default_appid if
109
- ### one is not specified, and automatically install the config DB if it hasn't been
110
- ### already.
97
+ ### one is not specified.
111
98
  def self::run( appid=nil )
112
99
  appid ||= self.default_appid
113
100
  self.log.info "Starting up with appid %p." % [ appid ]
@@ -134,134 +121,6 @@ class Strelka::App < Mongrel2::Handler
134
121
  end
135
122
 
136
123
 
137
- ### Return a Hash of glob patterns for matching data directories for the latest
138
- ### versions of all installed gems which have a dependency on Strelka, keyed
139
- ### by gem name.
140
- def self::discover_data_dirs
141
- datadirs = {
142
- '' => self.local_data_dirs
143
- }
144
-
145
- # Find all the gems that depend on Strelka
146
- gems = Gem::Specification.find_all do |gemspec|
147
- gemspec.dependencies.find {|dep| dep.name == 'strelka'}
148
- end
149
-
150
- self.log.debug "Found %d gems with a Strelka dependency" % [ gems.length ]
151
-
152
- # Find all the files under those gems' data directories that match the application
153
- # pattern
154
- gems.sort.reverse.each do |gemspec|
155
- # Only look at the latest version of the gem
156
- next if datadirs.key?( gemspec.name )
157
- datadirs[ gemspec.name ] = File.join( gemspec.full_gem_path, "data", gemspec.name )
158
- end
159
-
160
- self.log.debug " returning data directories: %p" % [ datadirs ]
161
- return datadirs
162
- end
163
-
164
-
165
- ### Return a Hash of Strelka app files as Pathname objects from installed gems,
166
- ### keyed by gemspec name .
167
- def self::discover_paths
168
- appfiles = {}
169
-
170
- self.discover_data_dirs.each do |gemname, dir|
171
- pattern = File.join( dir, self.app_glob_pattern )
172
- appfiles[ gemname ] = Pathname.glob( pattern )
173
- end
174
-
175
- return appfiles
176
- end
177
-
178
-
179
- ### Return an Array of Strelka::App classes loaded from the installed Strelka gems.
180
- def self::discover
181
- discovered_apps = []
182
- app_paths = self.discover_paths
183
-
184
- self.log.debug "Loading apps from %d discovered paths" % [ app_paths.length ]
185
- app_paths.each do |gemname, paths|
186
- self.log.debug " loading gem %s" % [ gemname ]
187
- gem( gemname ) unless gemname == ''
188
-
189
- self.log.debug " loading apps from %s: %d handlers" % [ gemname, paths.length ]
190
- paths.each do |path|
191
- classes = begin
192
- Strelka::App.load( path )
193
- rescue StandardError, ScriptError => err
194
- self.log.error "%p while loading Strelka apps from %s: %s" %
195
- [ err.class, path, err.message ]
196
- self.log.debug "Backtrace: %s" % [ err.backtrace.join("\n\t") ]
197
- []
198
- end
199
- self.log.debug " loaded app classes: %p" % [ classes ]
200
-
201
- discovered_apps += classes
202
- end
203
- end
204
-
205
- return discovered_apps
206
- end
207
-
208
-
209
- ### Find the first app with the given +appname+ and return the path to its file and the name of
210
- ### the gem it's from. If the optional +gemname+ is given, only consider apps from that gem.
211
- ### Raises a RuntimeError if no app with the given +appname+ was found.
212
- def self::find( appname, gemname=nil )
213
- discovered_apps = self.discover_paths
214
-
215
- path = nil
216
- if gemname
217
- discovered_apps[ gemname ].each do |apppath|
218
- self.log.debug " %s (%s)" % [ apppath, apppath.basename('.rb') ]
219
- if apppath.basename('.rb').to_s == appname
220
- path = apppath
221
- break
222
- end
223
- end
224
- else
225
- self.log.debug "No gem name; searching them all:"
226
- discovered_apps.each do |disc_gemname, paths|
227
- self.log.debug " %s: %d paths" % [ disc_gemname, paths.length ]
228
- path = paths.find do |apppath|
229
- self.log.debug " %s (%s)" % [ apppath, apppath.basename('.rb') ]
230
- self.log.debug " %p vs. %p" % [ apppath.basename('.rb').to_s, appname ]
231
- apppath.basename('.rb').to_s == appname
232
- end or next
233
- gemname = disc_gemname
234
- break
235
- end
236
- end
237
-
238
- unless path
239
- msg = "Couldn't find an app named '#{appname}'"
240
- msg << " in the #{gemname} gem" if gemname
241
- raise( msg )
242
- end
243
- self.log.debug " found: %s" % [ path ]
244
-
245
- return path, gemname
246
- end
247
-
248
-
249
- ### Load the specified +file+, and return any Strelka::App subclasses that are loaded
250
- ### as a result.
251
- def self::load( file )
252
- self.log.debug "Loading application/s from %p" % [ file ]
253
- @loading_file = Pathname( file ).expand_path
254
- self.subclasses.delete( @loading_file )
255
- Kernel.load( @loading_file.to_s )
256
- new_subclasses = self.subclasses[ @loading_file ]
257
- self.log.debug " loaded %d new app class/es" % [ new_subclasses.size ]
258
-
259
- return new_subclasses
260
- ensure
261
- @loading_file = nil
262
- end
263
-
264
-
265
124
  #
266
125
  # :section: Application declarative methods
267
126
  #
@@ -463,6 +322,7 @@ class Strelka::App < Mongrel2::Handler
463
322
  # Some status codes allow explanatory text to be returned; some forbid it. Append the
464
323
  # message for those that allow one.
465
324
  unless request.verb == :HEAD || response.bodiless?
325
+ self.log.debug "Writing plain-text response body: %p" % [ message ]
466
326
  response.content_type = 'text/plain'
467
327
  response.puts( message )
468
328
  end
@@ -10,7 +10,7 @@ require 'strelka/plugins'
10
10
 
11
11
 
12
12
  # This is a shared behavior for specs which different Strelka::App
13
- # plugins share in common. If you're creating a Strelka::App plugin,
13
+ # plugins share in common. If you're creating A Strelka Plugin,
14
14
  # you can test its conformity to the expectations placed on them by
15
15
  # adding this to your spec:
16
16
  #
@@ -18,11 +18,11 @@ require 'strelka/plugins'
18
18
  #
19
19
  # describe YourPlugin do
20
20
  #
21
- # it_should_behave_like "A Strelka::App Plugin"
21
+ # it_should_behave_like "A Strelka Plugin"
22
22
  #
23
23
  # end
24
24
 
25
- shared_examples_for "A Strelka::App Plugin" do
25
+ shared_examples_for "A Strelka Plugin" do
26
26
 
27
27
  let( :plugin ) do
28
28
  described_class
@@ -30,7 +30,7 @@ shared_examples_for "A Strelka::App Plugin" do
30
30
 
31
31
 
32
32
  it "extends Strelka::Plugin" do
33
- plugin.should be_a( Strelka::Plugin )
33
+ expect( plugin ).to be_a( Strelka::Plugin )
34
34
  end
35
35
 
36
36
  end
@@ -0,0 +1,211 @@
1
+ # -*- ruby -*-
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # encoding: utf-8
4
+
5
+ require 'rubygems'
6
+ require 'strelka' unless defined?( Strelka )
7
+
8
+
9
+ # The Strelka application-discovery system.
10
+ module Strelka::Discovery
11
+ extend Loggability,
12
+ Configurability,
13
+ Strelka::MethodUtilities
14
+
15
+
16
+ # Loggability API -- log to the Strelka logger
17
+ log_to :strelka
18
+
19
+ # Configurability API -- use the 'discovery' section of the config
20
+ config_key :discovery
21
+
22
+
23
+ # Default config
24
+ CONFIG_DEFAULTS = {
25
+ app_glob_pattern: '{apps,handlers}/**/*',
26
+ local_data_dirs: 'data/*',
27
+ }.freeze
28
+
29
+
30
+ ##
31
+ # The Hash of Strelka::App subclasses, keyed by the Pathname of the file they were
32
+ # loaded from, or +nil+ if they weren't loaded via ::load.
33
+ singleton_attr_reader :subclasses
34
+
35
+ ##
36
+ # The glob(3) pattern for matching Apps during discovery
37
+ singleton_attr_accessor :app_glob_pattern
38
+
39
+ ##
40
+ # The glob(3) pattern for matching local data directories during discovery. Local
41
+ # data directories are evaluated relative to the CWD.
42
+ singleton_attr_accessor :local_data_dirs
43
+
44
+ ##
45
+ # The name of the file that's currently being loaded (if any)
46
+ singleton_attr_reader :loading_file
47
+
48
+
49
+ # Module instance variables
50
+ @subclasses = Hash.new {|h,k| h[k] = [] }
51
+ @loading_file = nil
52
+ @app_glob_pattern = CONFIG_DEFAULTS[:app_glob_pattern]
53
+ @local_data_dirs = CONFIG_DEFAULTS[:local_data_dirs]
54
+
55
+
56
+ ### Configure the App. Override this if you wish to add additional configuration
57
+ ### to the 'app' section of the config that will be passed to you when the config
58
+ ### is loaded.
59
+ def self::configure( config=nil )
60
+ config = self.defaults.merge( config || {} )
61
+
62
+ self.app_glob_pattern = config[:app_glob_pattern]
63
+ self.local_data_dirs = config[:local_data_dirs]
64
+ end
65
+
66
+
67
+ ### Return a Hash of glob patterns for matching data directories for the latest
68
+ ### versions of all installed gems which have a dependency on Strelka, keyed
69
+ ### by gem name.
70
+ def self::discover_data_dirs
71
+ datadirs = {
72
+ '' => self.local_data_dirs
73
+ }
74
+
75
+ # Find all the gems that depend on Strelka
76
+ gems = Gem::Specification.find_all do |gemspec|
77
+ gemspec.dependencies.find {|dep| dep.name == 'strelka'}
78
+ end
79
+
80
+ self.log.debug "Found %d gems with a Strelka dependency" % [ gems.length ]
81
+
82
+ # Find all the files under those gems' data directories that match the application
83
+ # pattern
84
+ gems.sort.reverse.each do |gemspec|
85
+ # Only look at the latest version of the gem
86
+ next if datadirs.key?( gemspec.name )
87
+ datadirs[ gemspec.name ] = File.join( gemspec.full_gem_path, "data", gemspec.name )
88
+ end
89
+
90
+ self.log.debug " returning data directories: %p" % [ datadirs ]
91
+ return datadirs
92
+ end
93
+
94
+
95
+ ### Return a Hash of Strelka app files as Pathname objects from installed gems,
96
+ ### keyed by gemspec name .
97
+ def self::discover_paths
98
+ appfiles = {}
99
+
100
+ self.discover_data_dirs.each do |gemname, dir|
101
+ pattern = File.join( dir, self.app_glob_pattern )
102
+ appfiles[ gemname ] = Pathname.glob( pattern )
103
+ end
104
+
105
+ return appfiles
106
+ end
107
+
108
+
109
+ ### Return an Array of Strelka::App classes loaded from the installed Strelka gems.
110
+ def self::discover
111
+ discovered_apps = []
112
+ app_paths = self.discover_paths
113
+
114
+ self.log.debug "Loading apps from %d discovered paths" % [ app_paths.length ]
115
+ app_paths.each do |gemname, paths|
116
+ self.log.debug " loading gem %s" % [ gemname ]
117
+ gem( gemname ) unless gemname == ''
118
+
119
+ self.log.debug " loading apps from %s: %d handlers" % [ gemname, paths.length ]
120
+ paths.each do |path|
121
+ classes = begin
122
+ self.load( path )
123
+ rescue StandardError, ScriptError => err
124
+ self.log.error "%p while loading Strelka apps from %s: %s" %
125
+ [ err.class, path, err.message ]
126
+ self.log.debug "Backtrace: %s" % [ err.backtrace.join("\n\t") ]
127
+ []
128
+ end
129
+ self.log.debug " loaded app classes: %p" % [ classes ]
130
+
131
+ discovered_apps += classes
132
+ end
133
+ end
134
+
135
+ return discovered_apps
136
+ end
137
+
138
+
139
+ ### Find the first app with the given +appname+ and return the path to its file and the name of
140
+ ### the gem it's from. If the optional +gemname+ is given, only consider apps from that gem.
141
+ ### Raises a RuntimeError if no app with the given +appname+ was found.
142
+ def self::find( appname, gemname=nil )
143
+ discovered_apps = self.discover_paths
144
+
145
+ path = nil
146
+ if gemname
147
+ discovered_apps[ gemname ].each do |apppath|
148
+ self.log.debug " %s (%s)" % [ apppath, apppath.basename('.rb') ]
149
+ if apppath.basename('.rb').to_s == appname
150
+ path = apppath
151
+ break
152
+ end
153
+ end
154
+ else
155
+ self.log.debug "No gem name; searching them all:"
156
+ discovered_apps.each do |disc_gemname, paths|
157
+ self.log.debug " %s: %d paths" % [ disc_gemname, paths.length ]
158
+ path = paths.find do |apppath|
159
+ self.log.debug " %s (%s)" % [ apppath, apppath.basename('.rb') ]
160
+ self.log.debug " %p vs. %p" % [ apppath.basename('.rb').to_s, appname ]
161
+ apppath.basename('.rb').to_s == appname
162
+ end or next
163
+ gemname = disc_gemname
164
+ break
165
+ end
166
+ end
167
+
168
+ unless path
169
+ msg = "Couldn't find an app named '#{appname}'"
170
+ msg << " in the #{gemname} gem" if gemname
171
+ raise( msg )
172
+ end
173
+ self.log.debug " found: %s" % [ path ]
174
+
175
+ return path, gemname
176
+ end
177
+
178
+
179
+ ### Load the specified +file+, and return any Strelka::App subclasses that are loaded
180
+ ### as a result.
181
+ def self::load( file )
182
+ self.log.debug "Loading application/s from %p" % [ file ]
183
+ @loading_file = Pathname( file ).expand_path
184
+ self.subclasses.delete( @loading_file )
185
+ Kernel.load( @loading_file.to_s )
186
+ new_subclasses = self.subclasses[ @loading_file ]
187
+ self.log.debug " loaded %d new app class/es" % [ new_subclasses.size ]
188
+
189
+ return new_subclasses
190
+ ensure
191
+ @loading_file = nil
192
+ end
193
+
194
+
195
+ ### Register the given +subclass+ as having inherited a class that has been extended
196
+ ### with Discovery.
197
+ def self::add_inherited_class( subclass )
198
+ self.log.debug "Registering discovered subclass %p" % [ subclass ]
199
+ self.subclasses[ self.loading_file ] << subclass
200
+ end
201
+
202
+
203
+ ### Inheritance callback -- register the subclass with its parent for discovery.
204
+ def inherited( subclass )
205
+ super
206
+ Strelka::Discovery.log.info "%p inherited by discoverable class %p" % [ self, subclass ]
207
+ Strelka::Discovery.add_inherited_class( subclass )
208
+ end
209
+
210
+ end # module Strelka::Discovery
211
+
@@ -211,6 +211,7 @@ class Strelka::HTTPRequest < Mongrel2::HTTPRequest
211
211
  when 'application/json', 'text/javascript'
212
212
  return Yajl.load( self.body )
213
213
  when 'text/x-yaml', 'application/x-yaml'
214
+ return nil if self.body.eof?
214
215
  return YAML.load( self.body, safe: true )
215
216
  when 'multipart/form-data'
216
217
  boundary = self.content_type[ /\bboundary=(\S+)/, 1 ] or
@@ -352,7 +352,13 @@ module Strelka::HTTPResponse::Negotiation
352
352
  def try_content_type_callback( mimetype, callback )
353
353
  self.log.debug " trying content-type callback %p (%s)" % [ callback, mimetype ]
354
354
 
355
- new_body = callback.call( mimetype ) or return false
355
+ new_body = begin
356
+ callback.call( mimetype )
357
+ rescue => err
358
+ self.log.error " %p raised from the callback for %s: %s" %
359
+ [ err.class, mimetype, err.message ]
360
+ return false
361
+ end
356
362
 
357
363
  self.log.debug " successfully transformed: %p! Setting up response." % [ new_body.class ]
358
364
  stringifiers = Strelka::HTTPResponse::Negotiation.stringifiers
@@ -2,6 +2,8 @@
2
2
  # vim: set nosta noet ts=4 sw=4:
3
3
  # encoding: utf-8
4
4
 
5
+ require 'tempfile'
6
+
5
7
  require 'strelka' unless defined?( Strelka )
6
8
  require 'strelka/constants'
7
9
 
@@ -192,7 +194,8 @@ module Strelka
192
194
  return obj if obj.class.name == 'RSpec::Mocks::Mock'
193
195
 
194
196
  return case obj
195
- when NilClass, Numeric, TrueClass, FalseClass, Symbol, Module
197
+ when NilClass, Numeric, TrueClass, FalseClass, Symbol,
198
+ Module, Encoding, IO, Tempfile
196
199
  obj
197
200
 
198
201
  when Array
@@ -753,7 +753,7 @@ class Strelka::ParamValidator
753
753
  ### the given +value+, and add the field to the appropriate field list based on the
754
754
  ### result.
755
755
  def apply_constraint( constraint, value )
756
- if !value.nil?
756
+ if !( value.nil? || value == '' )
757
757
  result = constraint.apply( value, self.untaint_all? )
758
758
 
759
759
  if !result.nil?
@@ -57,7 +57,7 @@ module Strelka
57
57
  name = object.plugin_name
58
58
  if (( deps = pluggable.loaded_plugins[name] ))
59
59
  self.log.debug " installing deferred deps for %p" % [ name ]
60
- object.run_after( *deps )
60
+ object.run_inside( *deps )
61
61
  end
62
62
 
63
63
  self.log.debug " adding %p (%p) to the plugin registry for %p" %
@@ -87,28 +87,30 @@ module Strelka
87
87
 
88
88
  ### Register the receiver as needing to be run before +other_plugins+ for requests, and
89
89
  ### *after* them for responses.
90
- def run_before( *other_plugins )
90
+ def run_outside( *other_plugins )
91
91
  name = self.plugin_name
92
92
  other_plugins.each do |other_name|
93
93
  self.pluggable.loaded_plugins[ other_name ] ||= []
94
94
  mod = self.pluggable.loaded_plugins[ other_name ]
95
95
 
96
- if mod.respond_to?( :run_after )
97
- mod.run_after( name )
96
+ if mod.respond_to?( :run_inside )
97
+ mod.run_inside( name )
98
98
  else
99
99
  self.log.debug "%p plugin not yet loaded; setting up pending deps" % [ other_name ]
100
100
  mod << name
101
101
  end
102
102
  end
103
103
  end
104
+ alias_method :run_before, :run_outside
104
105
 
105
106
 
106
107
  ### Register the receiver as needing to be run after +other_plugins+ for requests, and
107
108
  ### *before* them for responses.
108
- def run_after( *other_plugins )
109
+ def run_inside( *other_plugins )
109
110
  self.log.debug " %p will run after %p" % [ self, other_plugins ]
110
111
  self.successors.merge( other_plugins )
111
112
  end
113
+ alias_method :run_after, :run_inside
112
114
 
113
115
  end # module Plugin
114
116
 
@@ -244,7 +246,7 @@ module Strelka
244
246
 
245
247
 
246
248
  ### Install the mixin part of the plugin, in the order determined by
247
- ### the plugin registry based on the run_before and run_after specifications
249
+ ### the plugin registry based on the run_outside and run_inside specifications
248
250
  ### of the plugins themselves.
249
251
  def install_plugins
250
252
  if self.plugins_installed?