padrino-core 0.10.7 → 0.11.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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/lib/padrino-core.rb +58 -4
  3. data/lib/padrino-core/application.rb +38 -16
  4. data/lib/padrino-core/application/flash.rb +229 -0
  5. data/lib/padrino-core/application/rendering.rb +39 -11
  6. data/lib/padrino-core/application/rendering/extensions/erubis.rb +55 -0
  7. data/lib/padrino-core/application/rendering/extensions/haml.rb +26 -0
  8. data/lib/padrino-core/application/rendering/extensions/slim.rb +14 -0
  9. data/lib/padrino-core/application/routing.rb +106 -35
  10. data/lib/padrino-core/cli/base.rb +41 -38
  11. data/lib/padrino-core/cli/rake.rb +30 -9
  12. data/lib/padrino-core/cli/rake_tasks.rb +9 -14
  13. data/lib/padrino-core/loader.rb +23 -9
  14. data/lib/padrino-core/locale/fr.yml +1 -1
  15. data/lib/padrino-core/locale/ru.yml +1 -1
  16. data/lib/padrino-core/logger.rb +48 -32
  17. data/lib/padrino-core/module.rb +58 -0
  18. data/lib/padrino-core/mounter.rb +15 -5
  19. data/lib/padrino-core/reloader.rb +14 -12
  20. data/lib/padrino-core/server.rb +4 -4
  21. data/lib/padrino-core/support_lite.rb +43 -6
  22. data/lib/padrino-core/version.rb +1 -1
  23. data/padrino-core.gemspec +9 -4
  24. data/test/fixtures/app_gem/Gemfile +4 -0
  25. data/test/fixtures/app_gem/app/app.rb +3 -0
  26. data/test/fixtures/app_gem/app_gem.gemspec +17 -0
  27. data/test/fixtures/app_gem/lib/app_gem.rb +7 -0
  28. data/test/fixtures/app_gem/lib/app_gem/version.rb +3 -0
  29. data/test/mini_shoulda.rb +1 -1
  30. data/test/test_application.rb +38 -21
  31. data/test/test_csrf_protection.rb +80 -0
  32. data/test/test_filters.rb +70 -0
  33. data/test/test_flash.rb +168 -0
  34. data/test/test_logger.rb +27 -0
  35. data/test/test_mounter.rb +24 -2
  36. data/test/test_reloader_simple.rb +4 -4
  37. data/test/test_rendering.rb +75 -4
  38. data/test/test_routing.rb +164 -35
  39. data/test/test_support_lite.rb +56 -0
  40. metadata +52 -29
@@ -33,7 +33,7 @@ module Padrino
33
33
  # Specified constants can be excluded from the code unloading process.
34
34
  #
35
35
  def exclude_constants
36
- @_exclude_constants ||= []
36
+ @_exclude_constants ||= Set.new
37
37
  end
38
38
 
39
39
  ##
@@ -41,7 +41,7 @@ module Padrino
41
41
  # Default included constants are: [none]
42
42
  #
43
43
  def include_constants
44
- @_include_constants ||= []
44
+ @_include_constants ||= Set.new
45
45
  end
46
46
 
47
47
  ##
@@ -103,9 +103,12 @@ module Padrino
103
103
  # We lock dependencies sets to prevent reloading of protected constants
104
104
  #
105
105
  def lock!
106
- klasses = ObjectSpace.classes.map { |klass| klass._orig_klass_name.split('::')[0] }.uniq
106
+ klasses = ObjectSpace.classes do |klass|
107
+ klass._orig_klass_name.split('::')[0]
108
+ end
109
+
107
110
  klasses = klasses | Padrino.mounted_apps.map { |app| app.app_class }
108
- Padrino::Reloader.exclude_constants.concat(klasses)
111
+ Padrino::Reloader.exclude_constants.merge(klasses)
109
112
  end
110
113
 
111
114
  ##
@@ -130,8 +133,8 @@ module Padrino
130
133
  end
131
134
 
132
135
  # Duplicate objects and loaded features before load file
133
- klasses = ObjectSpace.classes.dup
134
- files = $LOADED_FEATURES.dup
136
+ klasses = ObjectSpace.classes
137
+ files = Set.new($LOADED_FEATURES.dup)
135
138
 
136
139
  # Now we can reload dependencies of our file
137
140
  if features = LOADED_FILES.delete(file)
@@ -142,7 +145,7 @@ module Padrino
142
145
  begin
143
146
  logger.devel :loading, began_at, file if !reload
144
147
  logger.debug :reload, began_at, file if reload
145
- $LOADED_FEATURES.delete(file)
148
+ $LOADED_FEATURES.delete(file) if files.include?(file)
146
149
  verbosity_was, $-v = $-v, nil
147
150
  loaded = false
148
151
  require(file)
@@ -152,18 +155,17 @@ module Padrino
152
155
  logger.error "Cannot require #{file} due to a syntax error: #{e.message}"
153
156
  ensure
154
157
  $-v = verbosity_was
155
- new_constants = (ObjectSpace.classes - klasses).uniq
158
+ new_constants = ObjectSpace.new_classes(klasses)
156
159
  if loaded
157
160
  # Store the file details
158
161
  LOADED_CLASSES[file] = new_constants
159
- LOADED_FILES[file] = ($LOADED_FEATURES - files - [file]).uniq
162
+ LOADED_FILES[file] = Set.new($LOADED_FEATURES) - files - [file]
160
163
  # Track only features in our Padrino.root
161
164
  LOADED_FILES[file].delete_if { |feature| !in_root?(feature) }
162
165
  else
163
166
  logger.devel "Failed to load #{file}; removing partially defined constants"
164
167
  new_constants.each { |klass| remove_constant(klass) }
165
168
  end
166
-
167
169
  end
168
170
  end
169
171
 
@@ -183,8 +185,8 @@ module Padrino
183
185
  # Removes the specified class and constant.
184
186
  #
185
187
  def remove_constant(const)
186
- return if exclude_constants.compact.uniq.any? { |c| const._orig_klass_name.index(c) == 0 } &&
187
- !include_constants.compact.uniq.any? { |c| const._orig_klass_name.index(c) == 0 }
188
+ return if exclude_constants.any? { |c| const._orig_klass_name.index(c) == 0 } &&
189
+ !include_constants.any? { |c| const._orig_klass_name.index(c) == 0 }
188
190
  begin
189
191
  parts = const.to_s.sub(/^::(Object)?/, 'Object::').split('::')
190
192
  object = parts.pop
@@ -4,8 +4,8 @@ module Padrino
4
4
  # thin, mongrel, or webrick in that order.
5
5
  #
6
6
  # @example
7
- # Padrino.run! # with these defaults => host: "localhost", port: "3000", adapter: the first found
8
- # Padrino.run!("localhost", "4000", "mongrel") # use => host: "0.0.0.0", port: "3000", adapter: "mongrel"
7
+ # Padrino.run! # with these defaults => host: "127.0.0.1", port: "3000", adapter: the first found
8
+ # Padrino.run!("0.0.0.0", "4000", "mongrel") # use => host: "0.0.0.0", port: "4000", adapter: "mongrel"
9
9
  #
10
10
  def self.run!(options={})
11
11
  Padrino.load!
@@ -17,13 +17,13 @@ module Padrino
17
17
  #
18
18
  class Server < Rack::Server
19
19
  # Server Handlers
20
- Handlers = [:thin, :mongrel, :trinidad, :webrick]
20
+ Handlers = [:thin, :puma, :mongrel, :trinidad, :webrick]
21
21
 
22
22
  # Starts the application on the available server with specified options.
23
23
  def self.start(app, opts={})
24
24
  options = {}.merge(opts) # We use a standard hash instead of Thor::CoreExt::HashWithIndifferentAccess
25
25
  options.symbolize_keys!
26
- options[:Host] = options.delete(:host) || '0.0.0.0'
26
+ options[:Host] = options.delete(:host) || '127.0.0.1'
27
27
  options[:Port] = options.delete(:port) || 3000
28
28
  options[:AccessLog] = []
29
29
  if options[:daemonize]
@@ -9,6 +9,7 @@ require 'active_support/core_ext/object/blank' # present?
9
9
  require 'active_support/core_ext/array/extract_options' # extract_options
10
10
  require 'active_support/inflector/methods' # constantize
11
11
  require 'active_support/inflector/inflections' # pluralize
12
+ require 'active_support/core_ext/string/output_safety' # SafeBuffer and html_safe
12
13
  require 'active_support/inflections' # load default inflections
13
14
  require 'yaml' unless defined?(YAML) # load yaml for i18n
14
15
  require 'win32console' if RUBY_PLATFORM =~ /(win|m)32/ # ruby color support for win
@@ -110,13 +111,49 @@ end
110
111
 
111
112
  module ObjectSpace
112
113
  class << self
114
+ ##
113
115
  # Returns all the classes in the object space.
114
- def classes
115
- ObjectSpace.each_object(Module).select do |klass|
116
- # Why? Ruby, when you remove a costant dosen't remove it from
117
- # rb_tables, this mean that here we can find classes that was
118
- # removed.
119
- klass.name rescue false
116
+ # Optionally, a block can be passed, for example the following code
117
+ # would return the classes that start with the character "A":
118
+ #
119
+ # ObjectSpace.classes do |klass|
120
+ # if klass.to_s[0] == "A"
121
+ # klass
122
+ # end
123
+ # end
124
+ #
125
+ def classes(&block)
126
+ rs = Set.new
127
+
128
+ ObjectSpace.each_object(Class).each do |klass|
129
+ if block
130
+ if r = block.call(klass)
131
+ # add the returned value if the block returns something
132
+ rs << r
133
+ end
134
+ else
135
+ rs << klass
136
+ end
137
+ end
138
+
139
+ rs
140
+ end
141
+
142
+ ##
143
+ # Returns a list of existing classes that are not included in "snapshot"
144
+ # This method is useful to get the list of new classes that were loaded
145
+ # after an event like requiring a file.
146
+ # Usage:
147
+ #
148
+ # snapshot = ObjectSpace.classes
149
+ # # require a file
150
+ # ObjectSpace.new_classes(snapshot)
151
+ #
152
+ def new_classes(snapshot)
153
+ self.classes do |klass|
154
+ if !snapshot.include?(klass)
155
+ klass
156
+ end
120
157
  end
121
158
  end
122
159
  end
@@ -6,7 +6,7 @@
6
6
  #
7
7
  module Padrino
8
8
  # The version constant for the current version of Padrino.
9
- VERSION = '0.10.7' unless defined?(Padrino::VERSION)
9
+ VERSION = '0.11.0' unless defined?(Padrino::VERSION)
10
10
 
11
11
  #
12
12
  # The current Padrino version.
data/padrino-core.gemspec CHANGED
@@ -31,8 +31,13 @@ Gem::Specification.new do |s|
31
31
  # s.post_install_message << "\n\e[32m" + ("*" * 20) + "\n\e[0m"
32
32
 
33
33
  s.add_dependency("tilt", "~> 1.3.0")
34
- s.add_dependency("sinatra", "~> 1.3.1")
35
- s.add_dependency("http_router", "~> 0.10.2")
36
- s.add_dependency("thor", "~> 0.15.2")
37
- s.add_dependency("activesupport", "~> 3.2.0")
34
+ if ENV["SINATRA_EDGE"]
35
+ s.add_dependency("sinatra")
36
+ else
37
+ s.add_dependency("sinatra", "~> 1.4.2")
38
+ end
39
+ s.add_dependency("http_router", "~> 0.11.0")
40
+ s.add_dependency("thor", "~> 0.17.0")
41
+ s.add_dependency("activesupport", ">= 3.1.0")
42
+ s.add_dependency("rack-protection", ">= 1.5.0")
38
43
  end
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in app_gem.gemspec
4
+ gemspec
@@ -0,0 +1,3 @@
1
+ class AppGem::App < Padrino::Application
2
+ set :version, AppGem::VERSION
3
+ end
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/app_gem/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Florian Gilcher"]
6
+ gem.email = ["florian.gilcher@asquera.de"]
7
+ gem.description = %q{TODO: Write a gem description}
8
+ gem.summary = %q{TODO: Write a gem summary}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "app_gem"
15
+ gem.require_paths = ["app", "lib"]
16
+ gem.version = AppGem::VERSION
17
+ end
@@ -0,0 +1,7 @@
1
+ require 'padrino'
2
+
3
+ module AppGem
4
+ extend Padrino::Module
5
+
6
+ gem! 'app_gem'
7
+ end
@@ -0,0 +1,3 @@
1
+ module AppGem
2
+ VERSION = "0.0.1"
3
+ end
data/test/mini_shoulda.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  gem 'minitest'
2
2
  require 'minitest/spec'
3
3
  require 'minitest/autorun'
4
- require 'mocha' # Load mocha after minitest
4
+ require 'mocha/setup'
5
5
 
6
6
  begin
7
7
  require 'ruby-debug'
@@ -1,17 +1,13 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/helper')
2
+ require 'haml'
2
3
 
3
4
  class PadrinoPristine < Padrino::Application; end
4
- class PadrinoTestApp < Padrino::Application; end
5
+ class PadrinoTestApp < Padrino::Application; end
5
6
  class PadrinoTestApp2 < Padrino::Application; end
6
7
 
7
8
  describe "Application" do
8
- def setup
9
- Padrino.clear!
10
- end
11
-
12
- def teardown
13
- remove_views
14
- end
9
+ before { Padrino.clear! }
10
+ after { remove_views }
15
11
 
16
12
  context 'for application functionality' do
17
13
 
@@ -19,24 +15,36 @@ describe "Application" do
19
15
  assert File.identical?(__FILE__, PadrinoPristine.app_file)
20
16
  assert_equal :padrino_pristine, PadrinoPristine.app_name
21
17
  assert_equal :test, PadrinoPristine.environment
22
- assert_equal Padrino.root("views"), PadrinoPristine.views
23
- assert PadrinoPristine.raise_errors
18
+ assert_equal Padrino.root('views'), PadrinoPristine.views
19
+ assert PadrinoPristine.raise_errors
24
20
  assert !PadrinoPristine.logging
25
21
  assert !PadrinoPristine.sessions
26
22
  assert !PadrinoPristine.dump_errors
27
23
  assert !PadrinoPristine.show_exceptions
28
- assert PadrinoPristine.raise_errors
24
+ assert PadrinoPristine.raise_errors
29
25
  assert !Padrino.configure_apps
30
26
  end
31
27
 
28
+ should 'check haml options on production' do
29
+ assert defined?(Haml), 'Haml not defined'
30
+ assert_equal :test, PadrinoPristine.environment
31
+ assert !PadrinoPristine.haml[:ugly]
32
+ Padrino.stub :env, :production do
33
+ PadrinoPristine.send :default_configuration!
34
+ assert_equal :production, Padrino.env
35
+ assert_equal :production, PadrinoPristine.environment
36
+ assert PadrinoPristine.haml[:ugly]
37
+ PadrinoPristine.environment = :test
38
+ end
39
+ end
40
+
32
41
  should 'check padrino specific options' do
33
42
  assert !PadrinoPristine.instance_variable_get(:@_configured)
34
43
  PadrinoPristine.send(:setup_application!)
35
44
  assert_equal :padrino_pristine, PadrinoPristine.app_name
36
45
  assert_equal 'StandardFormBuilder', PadrinoPristine.default_builder
37
- assert PadrinoPristine.instance_variable_get(:@_configured)
46
+ assert PadrinoPristine.instance_variable_get(:@_configured)
38
47
  assert !PadrinoPristine.reload?
39
- assert !PadrinoPristine.flash
40
48
  end
41
49
 
42
50
  should 'set global project settings' do
@@ -48,18 +56,27 @@ describe "Application" do
48
56
  assert_equal PadrinoTestApp.session_secret, PadrinoTestApp2.session_secret
49
57
  end
50
58
 
59
+ should 'be able to configure_apps multiple times' do
60
+ Padrino.configure_apps { set :foo1, "bar" }
61
+ Padrino.configure_apps { set :foo1, "bam" }
62
+ Padrino.configure_apps { set :foo2, "baz" }
63
+ PadrinoTestApp.send(:default_configuration!)
64
+ assert_equal "bam", PadrinoTestApp.settings.foo1, "should have foo1 assigned to bam"
65
+ assert_equal "baz", PadrinoTestApp.settings.foo2, "should have foo2 assigned to baz"
66
+ end
67
+
51
68
  should "have shared sessions accessible in project" do
52
69
  Padrino.configure_apps { enable :sessions; set :session_secret, 'secret' }
53
70
  Padrino.mount("PadrinoTestApp").to("/write")
54
71
  Padrino.mount("PadrinoTestApp2").to("/read")
55
- PadrinoTestApp.tap { |app| app.send(:default_configuration!)
56
- app.get("/") { session[:foo] = "shared" } }
57
- PadrinoTestApp2.tap { |app| app.send(:default_configuration!)
58
- app.get("/") { session[:foo] } }
59
- browser = Rack::Test::Session.new(Rack::MockSession.new(Padrino.application))
60
- browser.get '/write'
61
- browser.get '/read'
62
- assert_equal 'shared', browser.last_response.body
72
+ PadrinoTestApp.send :default_configuration!
73
+ PadrinoTestApp.get('/') { session[:foo] = "shared" }
74
+ PadrinoTestApp2.send(:default_configuration!)
75
+ PadrinoTestApp2.get('/') { session[:foo] }
76
+ @app = Padrino.application
77
+ get '/write'
78
+ get '/read'
79
+ assert_equal 'shared', body
63
80
  end
64
81
 
65
82
  # compare to: test_routing: allow global provides
@@ -0,0 +1,80 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/helper')
2
+
3
+ describe "Application" do
4
+ before { Padrino.clear! }
5
+ after { remove_views }
6
+
7
+ context 'CSRF protection' do
8
+ context "with CSRF protection on" do
9
+ before do
10
+ mock_app do
11
+ enable :sessions
12
+ enable :protect_from_csrf
13
+ post('/'){ 'HI' }
14
+ end
15
+ end
16
+
17
+ should "not allow requests without tokens" do
18
+ post "/"
19
+ assert_equal 403, status
20
+ end
21
+
22
+ should "allow requests with correct tokens" do
23
+ post "/", {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "a"}
24
+ assert_equal 200, status
25
+ end
26
+
27
+ should "not allow requests with incorrect tokens" do
28
+ post "/", {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "b"}
29
+ assert_equal 403, status
30
+ end
31
+ end
32
+
33
+ context "without CSRF protection on" do
34
+ before do
35
+ mock_app do
36
+ enable :sessions
37
+ disable :protect_from_csrf
38
+ post('/'){ 'HI' }
39
+ end
40
+ end
41
+
42
+ should "allows requests without tokens" do
43
+ post "/"
44
+ assert_equal 200, status
45
+ end
46
+
47
+ should "allow requests with correct tokens" do
48
+ post "/", {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "a"}
49
+ assert_equal 200, status
50
+ end
51
+
52
+ should "allow requests with incorrect tokens" do
53
+ post "/", {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "b"}
54
+ assert_equal 200, status
55
+ end
56
+ end
57
+
58
+ context "with optional CSRF protection" do
59
+ before do
60
+ mock_app do
61
+ enable :sessions
62
+ enable :protect_from_csrf
63
+ set :allow_disabled_csrf, true
64
+ post('/on') { 'HI' }
65
+ post('/off', :csrf_protection => false) { 'HI' }
66
+ end
67
+ end
68
+
69
+ should "allow access to routes with csrf_protection off" do
70
+ post "/off"
71
+ assert_equal 200, status
72
+ end
73
+
74
+ should "not allow access to routes with csrf_protection on" do
75
+ post "/on"
76
+ assert_equal 403, status
77
+ end
78
+ end
79
+ end
80
+ end
data/test/test_filters.rb CHANGED
@@ -275,4 +275,74 @@ describe "Filters" do
275
275
  get '/foo'
276
276
  assert_equal 'before', test
277
277
  end
278
+
279
+ should "call before filters only once" do
280
+ once = ''
281
+ mock_app do
282
+ error 500 do
283
+ 'error 500'
284
+ end
285
+ before do
286
+ once += 'before'
287
+ end
288
+ get :index do
289
+ raise Exception, 'Oops'
290
+ end
291
+ end
292
+
293
+ get '/'
294
+ assert_equal 'before', once
295
+ end
296
+
297
+ should 'catch exceptions in before filters' do
298
+ doodle = nil
299
+ mock_app do
300
+ after do
301
+ doodle = 'Been after'
302
+ end
303
+ before do
304
+ raise StandardError, "before"
305
+ end
306
+ get :index do
307
+ doodle = 'Been now'
308
+ end
309
+ error 500 do
310
+ "We broke #{env['sinatra.error'].message}"
311
+ end
312
+ end
313
+
314
+ get '/'
315
+ assert_equal 'We broke before', body
316
+ assert_equal nil, doodle
317
+ end
318
+
319
+ should 'catch exceptions in after filters if no exceptions caught before' do
320
+ doodle = ''
321
+ mock_app do
322
+ after do
323
+ doodle += ' and after'
324
+ raise StandardError, "after"
325
+ end
326
+ get :foo do
327
+ doodle = 'Been now'
328
+ raise StandardError, "now"
329
+ end
330
+ get :index do
331
+ doodle = 'Been now'
332
+ end
333
+ error 500 do
334
+ "We broke #{env['sinatra.error'].message}"
335
+ end
336
+ end
337
+
338
+ get '/foo'
339
+ assert_equal 'We broke now', body
340
+ assert_equal 'Been now', doodle
341
+
342
+ doodle = ''
343
+ get '/'
344
+ assert_equal 'We broke after', body
345
+ assert_equal 'Been now and after', doodle
346
+ end
347
+
278
348
  end