strelka 0.6.0 → 0.7.0

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 (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?