padrino-core 0.1.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.5
1
+ 0.2.0
data/lib/padrino-core.rb CHANGED
@@ -13,17 +13,17 @@ module Padrino
13
13
  # Padrino.root("config", "settings.yml")
14
14
  # # returns PADRINO_ROOT + "/config/setting.yml"
15
15
  def self.root(*args)
16
- File.join(PADRINO_ROOT, *args)
16
+ File.expand_path(File.join(PADRINO_ROOT, *args))
17
17
  end
18
18
 
19
19
  # Helper method that return PADRINO_ENV
20
20
  def self.env
21
- PADRINO_ENV.to_s
21
+ PADRINO_ENV.to_s.downcase.to_sym
22
22
  end
23
23
 
24
24
  # Returns the resulting rack builder mapping each 'mounted' application
25
25
  def self.application
26
- raise ApplicationLoadError.new("At least one application must be mounted onto Padrino!") if self.mounted_apps.none?
26
+ raise ApplicationLoadError.new("At least one app must be mounted!") unless self.mounted_apps && self.mounted_apps.any?
27
27
  builder = Rack::Builder.new
28
28
  self.mounted_apps.each { |app| app.map_onto(builder) }
29
29
  builder
@@ -4,18 +4,12 @@ module Padrino
4
4
  # These subclassed applications can be easily mounted into other Padrino applications as well.
5
5
  class Application < Sinatra::Application
6
6
 
7
- def logger
8
- @log_stream ||= self.class.log_to_file? ? Padrino.root("log/#{PADRINO_ENV.downcase}.log") : $stdout
9
- @logger ||= Logger.new(@log_stream)
10
- end
11
-
12
7
  class << self
13
8
  def inherited(subclass)
14
9
  CALLERS_TO_IGNORE.concat(PADRINO_IGNORE_CALLERS)
15
10
  subclass.default_configuration!
16
11
  super # Loading the subclass
17
12
  subclass.register Padrino::Routing if defined?(Padrino::Routing)
18
- subclass.check_single_app
19
13
  end
20
14
 
21
15
  # Hooks into when a new instance of the application is created
@@ -30,19 +24,21 @@ module Padrino
30
24
  # Makes the routes defined in the block and in the Modules given
31
25
  # in `extensions` available to the application
32
26
  def controllers(*extensions, &block)
33
- self.reset_routes! if reload?
34
27
  instance_eval(&block) if block_given?
35
28
  include(*extensions) if extensions.any?
36
29
  end
37
30
 
38
- # Return true if the bootloader => Padrino.load! it's instatiated in the same
39
- # palace of the app.
40
- # Notice that <tt>signle_apps</tt> was not reloadable!
41
- def single_app?
42
- @_single_app
31
+ # Reloads the application files from all defined load paths
32
+ def reload!
33
+ reset_routes! # remove all existing user-defined application routes
34
+ Padrino.load_dependency(self.app_file) # reload the app file
35
+ load_paths.each { |path| Padrino.load_dependencies(File.join(self.root, path)) }
43
36
  end
44
37
 
45
- protected
38
+ # Resets application routes to only routes not defined by the user
39
+ def reset_routes!
40
+ @routes = Padrino::Application.dupe_routes
41
+ end
46
42
 
47
43
  # Setup the application by registering initializers, load paths and logger
48
44
  # Invoked automatically when an application is first instantiated
@@ -52,33 +48,28 @@ module Padrino
52
48
  self.calculate_paths
53
49
  self.register_initializers
54
50
  self.require_load_paths
55
- self.setup_logger
51
+ self.disable :logging # We need do that as default because Sinatra use commonlogger.
56
52
  @_configured = true
57
53
  end
58
54
 
55
+ protected
56
+
59
57
  # Defines default settings for Padrino application
60
58
  def default_configuration!
61
59
  # Overwriting Sinatra defaults
62
60
  set :app_file, caller_files.first || $0 # Assume app file is first caller
63
61
  set :environment, PADRINO_ENV.to_sym
64
62
  set :raise_errors, true if development?
65
- set :logging, !test?
63
+ set :logging, false#!test?
66
64
  set :sessions, true
67
- set :log_to_file, !development?
68
- set :reload, development?
69
65
  # Padrino specific
66
+ set :reload, development?
70
67
  set :app_name, self.to_s.underscore.to_sym
71
68
  set :default_builder, 'StandardFormBuilder'
72
- enable :flash
69
+ set :flash, defined?(Rack::Flash)
73
70
  # Plugin specific
74
- enable :padrino_mailer
75
- enable :padrino_helpers
76
- end
77
-
78
- def check_single_app
79
- @_single_app = File.identical?(self.app_file, Padrino.called_from.to_s)
80
- single_message = "=> Instantiated #{File.basename(self.app_file)} in single app mode, reload is not available"
81
- puts single_message if @_single_app && logging?
71
+ set :padrino_mailer, defined?(Padrino::Mailer)
72
+ set :padrino_helpers, defined?(Padrino::Helpers)
82
73
  end
83
74
 
84
75
  # Calculates any required paths after app_file and root have been properly configured
@@ -91,32 +82,23 @@ module Padrino
91
82
 
92
83
  # Requires the middleware and initializer modules to configure components
93
84
  def register_initializers
94
- use Rack::Session::Cookie
95
- use Rack::Flash if defined?(Rack::Flash) && flash?
96
- use Padrino::Reloader unless single_app?
97
- register DatabaseSetup if defined?(DatabaseSetup)
85
+ use Padrino::RackLogger
86
+ use Padrino::Reloader if reload?
87
+ use Rack::Flash if flash?
88
+ register DatabaseSetup if defined?(DatabaseSetup)
98
89
  @initializer_path ||= Padrino.root + '/config/initializers/*.rb'
99
90
  Dir[@initializer_path].each { |file| register_initializer(file) }
100
91
  end
101
92
 
102
93
  # Registers all desired padrino extension helpers/routing
103
94
  def register_framework_extensions
104
- register Padrino::Mailer if padrino_mailer? && defined?(Padrino::Mailer)
105
- register Padrino::Helpers if padrino_helpers? && defined?(Padrino::Helpers)
95
+ register Padrino::Mailer if padrino_mailer?
96
+ register Padrino::Helpers if padrino_helpers?
106
97
  end
107
98
 
108
- # Require all files within the application's load paths
99
+ # Requires all files within the application load paths
109
100
  def require_load_paths
110
- load_paths.each { |path| Padrino.load_dependencies(File.join(self.root, path)) }
111
- end
112
-
113
- # Creates the log directory and redirects output to file if needed
114
- def setup_logger
115
- return unless logging? && log_to_file?
116
- FileUtils.mkdir_p("#{Padrino.root}/log") unless File.exists?("#{Padrino.root}/log")
117
- log = File.new("#{Padrino.root}/log/#{PADRINO_ENV.downcase}.log", "a+")
118
- $stdout.reopen(log)
119
- $stderr.reopen(log)
101
+ load_paths.each { |path| Padrino.require_dependencies(File.join(self.root, path)) }
120
102
  end
121
103
 
122
104
  # Returns the load_paths for the application (relative to the application root)
@@ -132,23 +114,15 @@ module Padrino
132
114
  @view_paths.find { |path| Dir[File.join(path, '/**/*')].any? }
133
115
  end
134
116
 
135
- # Resets application routes for use in reloading the application
136
- # This performs a basic routes reload (compatible with sinatra edge)
137
- def reset_routes!
138
- return if single_app? # Don't reset routes for single app
139
- @routes = Padrino::Application.dupe_routes
140
- load(self.app_file)
141
- end
142
-
143
117
  # Registers an initializer with the application
144
118
  # register_initializer('/path/to/initializer')
145
119
  def register_initializer(file_path)
146
- Padrino.load_dependencies(file_path)
120
+ Padrino.require_dependencies(file_path)
147
121
  file_class = File.basename(file_path, '.rb').camelize
148
122
  register "#{file_class}Initializer".constantize
149
123
  rescue NameError => e
150
- puts "The module '#{file_class}Initializer' (#{file_path}) didn't loaded properly!" if logging?
151
- puts " Initializer error was '#{e.message}'" if logging?
124
+ logger.error "The module '#{file_class}Initializer' (#{file_path}) didn't loaded properly!"
125
+ logger.error " Initializer error was '#{e.message}'"
152
126
  end
153
127
  end
154
128
  end
@@ -1,17 +1,23 @@
1
1
  module Padrino
2
2
  PADRINO_IGNORE_CALLERS = [
3
- %r{/padrino-.*$}, # all padrino code
4
- %r{/sinatra}, # all sinatra code
5
- %r{lib/tilt.*\.rb$}, # all tilt code
6
- %r{\(.*\)}, # generated code
7
- %r{shoulda/context\.rb$}, # shoulda hacks
8
- %r{mocha/integration}, # mocha hacks
9
- %r{test/unit}, # test unit hacks
10
- %r{rake_test_loader\.rb}, # rake hacks
11
- %r{custom_require\.rb$}, # rubygems require hacks
12
- %r{active_support}, # active_support require hacks
13
- %r{/thor}, # thor require hacks
14
- ]
3
+ %r{lib/padrino-.*$}, # all padrino code
4
+ %r{/padrino-.*/(lib|bin)}, # all padrino code
5
+ %r{/bin/padrino$}, # all padrino code
6
+ %r{/sinatra}, # all sinatra code
7
+ %r{lib/tilt.*\.rb$}, # all tilt code
8
+ %r{lib/rack.*\.rb$}, # all rack code
9
+ %r{lib/mongrel.*\.rb$}, # all mongrel code
10
+ %r{lib/shotgun.*\.rb$}, # all shotgun lib
11
+ %r{bin/shotgun$}, # shotgun binary
12
+ %r{\(.*\)}, # generated code
13
+ %r{shoulda/context\.rb$}, # shoulda hacks
14
+ %r{mocha/integration}, # mocha hacks
15
+ %r{test/unit}, # test unit hacks
16
+ %r{rake_test_loader\.rb}, # rake hacks
17
+ %r{custom_require\.rb$}, # rubygems require hacks
18
+ %r{active_support}, # active_support require hacks
19
+ %r{/thor}, # thor require hacks
20
+ ] unless defined?(PADRINO_IGNORE_CALLERS)
15
21
 
16
22
  # add rubinius (and hopefully other VM impls) ignore patterns ...
17
23
  PADRINO_IGNORE_CALLERS.concat(RUBY_IGNORE_CALLERS) if defined?(RUBY_IGNORE_CALLERS)
@@ -2,13 +2,20 @@ module Padrino
2
2
  class << self
3
3
  # Requires necessary dependencies as well as application files from root lib and models
4
4
  def load!
5
- return if loaded?
5
+ return false if loaded?
6
6
  @_called_from = first_caller
7
7
  load_required_gems # load bundler gems
8
- load_dependencies("#{root}/config/apps.rb", "#{root}/config/database.rb") # load configuration
9
- load_dependencies("#{root}/lib/**/*.rb", "#{root}/models/*.rb") # load root app dependencies
10
- reload! # We need to fill our Stat::CACHE but we do that only for development
11
- @_loaded = true
8
+ require_dependencies("#{root}/config/apps.rb", "#{root}/config/database.rb") # load configuration
9
+ require_dependencies("#{root}/lib/**/*.rb", "#{root}/models/*.rb") # load root app dependencies
10
+ Stat.reload! # We need to fill our Stat::CACHE but we do that only for development
11
+ Thread.current[:padrino_loaded] = true
12
+ end
13
+
14
+ # Method for reloading required applications and their files
15
+ def reload!
16
+ return unless Stat.changed?
17
+ Stat.reload! # detects the modified files
18
+ Padrino.mounted_apps.each { |m| m.app_object.reload! } # finally we reload all files for each app
12
19
  end
13
20
 
14
21
  # This adds the ablity to instantiate Padrino.load! after Padrino::Application definition.
@@ -18,7 +25,7 @@ module Padrino
18
25
 
19
26
  # Return true if Padrino was loaded with Padrino.load!
20
27
  def loaded?
21
- @_loaded
28
+ Thread.current[:padrino_loaded]
22
29
  end
23
30
 
24
31
  # Attempts to require all dependencies with bundler; if this fails, uses system wide gems
@@ -27,23 +34,33 @@ module Padrino
27
34
  require_vendored_gems
28
35
  end
29
36
 
30
- # Attempts to load/require all dependency libs that we need.
37
+ # Attempts to require all dependency libs that we need.
31
38
  # If you use this method we can perform correctly a Padrino.reload!
32
39
  #
33
- # @param paths [Array] Path where is necessary require or load a dependency
34
- # @example For load all our app libs we need to do:
35
- # load_dependencies("#{Padrino.root}/lib/**/*.rb")
36
- def load_dependencies(*paths)
40
+ # ==== Parameters
41
+ # paths:: Path where is necessary require a dependency
42
+ #
43
+ # Example:
44
+ # # For require all our app libs we need to do:
45
+ # require_dependencies("#{Padrino.root}/lib/**/*.rb")
46
+ def require_dependencies(*paths)
37
47
  paths.each do |path|
38
48
  Dir[path].each { |file| require(file) }
39
49
  end
40
50
  end
41
- alias_method :load_dependency, :load_dependencies
51
+ alias :require_dependency :require_dependencies
42
52
 
43
- # Method for reload required classes
44
- def reload!
45
- Stat::reload!
53
+ # Attempts to load all dependency libs that we need.
54
+ # If you use this method we can perform correctly a Padrino.reload!
55
+ #
56
+ # ==== Parameters
57
+ # paths:: Path where is necessary to load a dependency
58
+ def load_dependencies(*paths)
59
+ paths.each do |path|
60
+ Dir[path].each { |file| load(file) }
61
+ end
46
62
  end
63
+ alias :load_dependency :load_dependencies
47
64
 
48
65
  protected
49
66
 
@@ -57,17 +74,22 @@ module Padrino
57
74
  say " ... Not Found"
58
75
  end
59
76
 
60
- # Loads bundled gems if they exist
77
+ # Require bundled gems if they exist
61
78
  def require_vendored_gems
62
- load_dependencies(root('/../vendor', 'gems', PADRINO_ENV))
63
- say " (Loading bundled gems)\n"
79
+ require_dependencies(root('/../vendor', 'gems', PADRINO_ENV))
80
+ say! " (Loading bundled gems)"
64
81
  rescue LoadError => e
65
- say " (Loading system gems)\n"
82
+ say! " (Loading system gems)"
66
83
  end
67
84
 
68
85
  # Prints out a message to the stdout if not in test environment
69
86
  def say(text)
70
- print text if Padrino.env != 'test'
87
+ print text if Padrino.env != :test
88
+ end
89
+
90
+ # Puts out a message to the stdout if not in test environment
91
+ def say!(text)
92
+ puts text if Padrino.env != :test
71
93
  end
72
94
  end
73
- end
95
+ end
@@ -0,0 +1,204 @@
1
+ module Padrino
2
+
3
+ # Setup a new logger
4
+ def self.setup_logger!
5
+ case Padrino.env
6
+ when :production
7
+ FileUtils.mkdir_p("#{Padrino.root}/log") unless File.exists?("#{Padrino.root}/log")
8
+ log = File.new("#{Padrino.root}/log/#{PADRINO_ENV.downcase}.log", "a+")
9
+ Thread.current[:padrino_logger] = Padrino::Logger.new(:log_level => :error, :stream => log)
10
+ when :development
11
+ Thread.current[:padrino_logger] = Padrino::Logger.new
12
+ when :test
13
+ Thread.current[:padrino_logger] = Padrino::Logger.new(:stream => StringIO.new)
14
+ end
15
+ Thread.current[:padrino_logger]
16
+ end
17
+
18
+ class Logger
19
+
20
+ attr_accessor :level
21
+ attr_accessor :auto_flush
22
+ attr_reader :buffer
23
+ attr_reader :log
24
+ attr_reader :init_args
25
+
26
+ # ==== Notes
27
+ # Ruby (standard) logger levels:
28
+ #
29
+ # :fatal:: An unhandleable error that results in a program crash
30
+ # :error:: A handleable error condition
31
+ # :warn:: A warning
32
+ # :info:: generic (useful) information about system operation
33
+ # :debug:: low-level information for developers
34
+ Levels = {
35
+ :fatal => 7,
36
+ :error => 6,
37
+ :warn => 4,
38
+ :info => 3,
39
+ :debug => 0
40
+ } unless const_defined?(:Levels)
41
+
42
+ @@mutex = {}
43
+
44
+ public
45
+
46
+ # To initialize the logger you create a new object, proxies to set_log.
47
+ #
48
+ # ==== Options can be:
49
+ #
50
+ # :stream:: Either an IO object or a name of a logfile. Defaults to $stdout
51
+ # :log_level::
52
+ # The log level from, e.g. :fatal or :info. Defaults to :debug in the
53
+ # production environment and :debug otherwise.
54
+ # :auto_flush::
55
+ # Whether the log should automatically flush after new messages are
56
+ # added. Defaults to true.
57
+ # :format_datetime:: Format of datetime. Defaults to: "%d/%b/%Y %H:%M:%S"
58
+ # :format_message:: Format of message. Defaults to: ""%s - - [%s] \"%s\"""
59
+ def initialize(options={})
60
+ @buffer = []
61
+ @auto_flush = options.has_key?(:auto_flush) ? options[:auto_flush] : true
62
+ @level = options[:log_level] ? Levels[options[:log_level]] : Levels[:debug]
63
+ @log = options[:stream] || $stdout
64
+ @log.sync = true
65
+ @mutex = @@mutex[@log] ||= Mutex.new
66
+ @format_datetime = options[:format_datetime] || "%d/%b/%Y %H:%M:%S"
67
+ @format_message = options[:format_message] || "%s - [%s] \"%s\""
68
+ end
69
+
70
+ # Flush the entire buffer to the log object.
71
+ def flush
72
+ return unless @buffer.size > 0
73
+ @mutex.synchronize do
74
+ @log.write(@buffer.slice!(0..-1).join(''))
75
+ end
76
+ end
77
+
78
+ # Close and remove the current log object.
79
+ def close
80
+ flush
81
+ @log.close if @log.respond_to?(:close) && !@log.tty?
82
+ @log = nil
83
+ end
84
+
85
+ # Appends a message to the log. The methods yield to an optional block and
86
+ # the output of this block will be appended to the message.
87
+ #
88
+ # ==== Parameters
89
+ # message:: The message to be logged. Defaults to nil.
90
+ #
91
+ # ==== Returns
92
+ # message:: The resulting message added to the log file.
93
+ def push(message = nil, level = nil)
94
+ self << @format_message % [level.to_s.upcase, Time.now.strftime(@format_datetime), message.to_s]
95
+ end
96
+
97
+ def <<(message = nil)
98
+ message << "\n" unless message[-1] == ?\n
99
+ @buffer << message
100
+ flush if @auto_flush
101
+ message
102
+ end
103
+
104
+ # Generate the logging methods for Padrino.logger for each log level.
105
+ Levels.each_pair do |name, number|
106
+ class_eval <<-LEVELMETHODS, __FILE__, __LINE__
107
+
108
+ # Appends a message to the log if the log level is at least as high as
109
+ # the log level of the logger.
110
+ #
111
+ # ==== Parameters
112
+ # message:: The message to be logged. Defaults to nil.
113
+ #
114
+ # ==== Returns
115
+ # self:: The logger object for chaining.
116
+ def #{name}(message = nil)
117
+ if #{number} >= level
118
+ message = block_given? ? yield : message
119
+ self.push(message, :#{name}) if #{number} >= level
120
+ end
121
+ self
122
+ end
123
+
124
+ # Appends a message to the log if the log level is at least as high as
125
+ # the log level of the logger. The bang! version of the method also auto
126
+ # flushes the log buffer to disk.
127
+ #
128
+ # ==== Parameters
129
+ # message:: The message to be logged. Defaults to nil.
130
+ #
131
+ # ==== Returns
132
+ # self:: The logger object for chaining.
133
+ def #{name}!(message = nil)
134
+ if #{number} >= level
135
+ message = block_given? ? yield : message
136
+ self.push(message, :#{name}) if #{number} >= level
137
+ flush if #{number} >= level
138
+ end
139
+ self
140
+ end
141
+
142
+ # ==== Returns
143
+ # Boolean:: True if this level will be logged by this logger.
144
+ def #{name}?
145
+ #{number} >= level
146
+ end
147
+ LEVELMETHODS
148
+ end
149
+
150
+ end
151
+
152
+ # RackLogger forwards every request to an +app+ given, and
153
+ # logs a line in the Apache common log format to the +logger+, or
154
+ # rack.errors by default.
155
+ class RackLogger
156
+ # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
157
+ # "lilith.local - - GET / HTTP/1.1 500 -"
158
+ # %{%s - %s %s %s%s %s - %d %s %0.4f}
159
+ FORMAT = %{%s - %s %s %s%s %s - %d %s %0.4f}
160
+
161
+ def initialize(app)
162
+ @app = app
163
+ end
164
+
165
+ def call(env)
166
+ began_at = Time.now
167
+ status, header, body = @app.call(env)
168
+ log(env, status, header, began_at)
169
+ [status, header, body]
170
+ end
171
+
172
+ private
173
+
174
+ def log(env, status, header, began_at)
175
+ now = Time.now
176
+ length = extract_content_length(header)
177
+
178
+ logger.debug FORMAT % [
179
+ env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
180
+ env["REMOTE_USER"] || "-",
181
+ env["REQUEST_METHOD"],
182
+ env["PATH_INFO"],
183
+ env["QUERY_STRING"].empty? ? "" : "?"+env["QUERY_STRING"],
184
+ env["HTTP_VERSION"],
185
+ status.to_s[0..3],
186
+ length,
187
+ now - began_at ]
188
+ end
189
+
190
+ def extract_content_length(headers)
191
+ headers.each do |key, value|
192
+ if key.downcase == 'content-length'
193
+ return value.to_s == '0' ? '-' : value
194
+ end
195
+ end
196
+ '-'
197
+ end
198
+ end
199
+ end
200
+
201
+ # Define a logger available every where in our app
202
+ def logger
203
+ Thread.current[:padrino_logger] ||= Padrino::setup_logger!
204
+ end
@@ -4,19 +4,21 @@ module Padrino
4
4
  # @example Mounter.new("blog_app", :app_class => "Blog").to("/blog")
5
5
  # @example Mounter.new("blog_app", :app_file => "/path/to/blog/app.rb").to("/blog")
6
6
  class Mounter
7
- attr_accessor :name, :uri_root, :app_file, :app_class, :app_root
7
+ attr_accessor :name, :uri_root, :app_file, :app_class, :app_root, :app_obj
8
+
8
9
  def initialize(name, options={})
9
10
  @name = name.downcase
10
11
  @app_class = options[:app_class] || name.classify
11
12
  @app_file = options[:app_file] || locate_app_file
12
13
  @app_root = options[:app_root]
14
+ @app_obj = self.app_object
13
15
  end
14
16
 
15
17
  # Registers the mounted application onto Padrino
16
18
  # @example Mounter.new("blog_app").to("/blog")
17
19
  def to(mount_url)
18
- @uri_root = mount_url
19
- Padrino.mounted_apps << self
20
+ @uri_root = mount_url
21
+ Padrino.insert_mounted_app(self)
20
22
  self
21
23
  end
22
24
 
@@ -24,22 +26,32 @@ module Padrino
24
26
  # For use in constructing a Rack application
25
27
  # @example @app.map_onto(@builder)
26
28
  def map_onto(builder)
27
- self.app_class.constantize rescue require(self.app_file)
28
- app_data, app_class = self, self.app_class.constantize
29
+ app_data, app_obj = self, @app_obj
29
30
  builder.map self.uri_root do
30
- app_class.set :uri_root, app_data.uri_root
31
- app_class.set :app_file, app_data.app_file
32
- app_class.set :app_name, app_data.name
33
- app_class.set :root, app_data.app_root if app_data.app_root
34
- run app_class
31
+ app_obj.set :uri_root, app_data.uri_root
32
+ app_obj.set :app_name, app_data.name
33
+ app_obj.set :app_file, app_data.app_file unless File.exist?(app_obj.app_file)
34
+ app_obj.set :root, app_data.app_root unless app_data.app_root.blank?
35
+ run app_obj
35
36
  end
36
37
  end
37
38
 
39
+ # Return the class for the app
40
+ def app_object
41
+ app_class.constantize rescue Padrino.require_dependency(app_file)
42
+ app_class.constantize
43
+ end
44
+
38
45
  # Returns the determined location of the mounted application main file
39
46
  def locate_app_file
40
47
  callers_are_identical = File.identical?(Padrino.first_caller.to_s, Padrino.called_from.to_s)
41
48
  callers_are_identical ? Padrino.first_caller : Padrino.mounted_root(name, "app.rb")
42
49
  end
50
+
51
+ # Makes two Mounters equal if they have the same name and uri_root
52
+ def ==(other)
53
+ other.is_a?(Mounter) && self.name == other.name && self.uri_root == other.uri_root
54
+ end
43
55
  end
44
56
 
45
57
  class << self
@@ -55,6 +67,12 @@ module Padrino
55
67
  @mounted_apps ||= []
56
68
  end
57
69
 
70
+ # Inserts a Mounter object into the mounted applications (avoids duplicates)
71
+ def insert_mounted_app(mounter)
72
+ return false if Padrino.mounted_apps.include?(mounter)
73
+ Padrino.mounted_apps << mounter
74
+ end
75
+
58
76
  # Mounts the core application onto Padrino project with given app settings (file, class, root)
59
77
  # @example Padrino.mount_core("Blog")
60
78
  # @example Padrino.mount_core(:app_file => "/path/to/file", :app_class => "Blog")
@@ -7,7 +7,7 @@ module Padrino
7
7
  # also respects a cool down time, during which nothing will be done.
8
8
  class Reloader
9
9
 
10
- def initialize(app, cooldown = 1, backend = Stat)
10
+ def initialize(app, cooldown = 1)
11
11
  @app = app
12
12
  @cooldown = cooldown
13
13
  @last = (Time.now - cooldown)
@@ -17,9 +17,18 @@ module Padrino
17
17
  end
18
18
  end
19
19
 
20
+ def changed?
21
+ changed = false
22
+ rotation do |file, mtime|
23
+ previous_mtime = MTIMES[file] ||= mtime
24
+ changed = true if mtime > MTIMES[file]
25
+ end
26
+ changed
27
+ end
28
+
20
29
  # A safe Kernel::load, issuing the hooks depending on the results
21
30
  def safe_load(file, mtime)
22
- puts "=> Reloading #{file}"
31
+ Padrino.say! "=> Reloading #{file}"
23
32
  load(file)
24
33
  file
25
34
  rescue LoadError, SyntaxError => ex
@@ -32,9 +41,8 @@ module Padrino
32
41
  files = [$0, *$LOADED_FEATURES].uniq
33
42
  paths = ['./', *$LOAD_PATH].uniq
34
43
 
35
- files.map{|file|
36
- next if file =~ /\.(so|bundle)$/ # cannot reload compiled files
37
-
44
+ files.map{ |file|
45
+ next if file =~ /\.(so|bundle)$/ # cannot reload compiled files
38
46
  found, stat = figure_path(file, paths)
39
47
  next unless found && stat && mtime = stat.mtime
40
48
 
@@ -11,6 +11,7 @@ Required for Padrino to run:
11
11
  * Array#extract_options!
12
12
  * Object#blank?
13
13
  * Object#present?
14
+ * Hash#slice, Hash#slice!
14
15
  * Hash#symbolize_keys
15
16
  * Hash#reverse_merge, Hash#reverse_merge!
16
17
  * SupportLite::OrderedHash
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Class#cattr_accessor
4
4
  require 'active_support/core_ext/class/attribute_accessors' unless Class.method_defined?(:cattr_accessor)
5
- ## Hash#symbolize_keys, Hash#reverse_merge, Hash#reverse_merge!, Hash#extract_options!
5
+ ## Hash#symbolize_keys, Hash#reverse_merge, Hash#reverse_merge!, Hash#extract_options!, Hash#slice!
6
6
  require 'active_support/core_ext/hash' unless Hash.method_defined?(:reverse_merge)
7
7
  ## String#inflectors
8
8
  require 'active_support/inflector' unless String.method_defined?(:constantize)
@@ -24,6 +24,18 @@ unless Hash.method_defined?(:symbolize_keys)
24
24
  end
25
25
  end
26
26
 
27
+ ## Hash#slice, Hash#slice!
28
+ unless Hash.method_defined?(:slice)
29
+ require 'extlib/hash'
30
+ class Hash
31
+ def slice(*keys)
32
+ keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
33
+ hash = self.class.new
34
+ keys.each { |k| hash[k] = self[k] if has_key?(k) }
35
+ hash
36
+ end
37
+ end
38
+ end
27
39
 
28
40
  ## Hash#reverse_merge, Hash#reverse_merge!
29
41
  unless Hash.method_defined?(:present?)
@@ -16,6 +16,7 @@ module Padrino
16
16
  method_option :port, :type => :numeric, :aliases => "-p", :required => true, :default => 3000
17
17
  method_option :boot, :type => :string, :aliases => "-b", :required => true, :default => "config/boot.rb"
18
18
  method_option :daemonize, :type => :boolean, :aliases => "-d"
19
+
19
20
  def start
20
21
  require File.dirname(__FILE__) + "/tasks/adapter"
21
22
  chdir(options.chdir)
@@ -47,11 +48,11 @@ module Padrino
47
48
  end
48
49
  ENV["PADRINO_ENV"] ||= environment
49
50
  puts "=> Loading #{environment} console (Padrino v.#{Padrino.version})"
50
- irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
51
- libs = " -r irb/completion"
52
- libs << " -r #{boot}"
53
- libs << " -r #{File.dirname(__FILE__)}/tasks/console"
54
- exec "#{irb} #{libs} --simple-prompt"
51
+ require 'irb'
52
+ require "irb/completion"
53
+ require boot
54
+ require File.dirname(__FILE__) + '/tasks/console'
55
+ IRB.start
55
56
  end
56
57
  end
57
58
  end
@@ -1,7 +1,6 @@
1
1
  # Reloads classes
2
2
  def reload!
3
3
  Padrino.reload!
4
- true
5
4
  end
6
5
 
7
6
  # Show applications
@@ -17,5 +16,6 @@ end
17
16
  # Load apps
18
17
  Padrino.mounted_apps.each do |app|
19
18
  puts "=> Loading Application #{app.name}"
20
- Padrino.load_dependency(app.app_file)
21
- end
19
+ Padrino.require_dependency(app.app_file)
20
+ app.app_object.setup_application!
21
+ end
data/padrino-core.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{padrino-core}
8
- s.version = "0.1.5"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Padrino Team", "Nathan Esquenazi", "Davide D'Agostino", "Arthur Chiu"]
12
- s.date = %q{2009-11-23}
12
+ s.date = %q{2009-11-24}
13
13
  s.default_executable = %q{padrino}
14
14
  s.description = %q{The Padrino core gem required for use of this framework}
15
15
  s.email = %q{nesquena@gmail.com}
@@ -29,6 +29,7 @@ Gem::Specification.new do |s|
29
29
  "lib/padrino-core/application.rb",
30
30
  "lib/padrino-core/caller.rb",
31
31
  "lib/padrino-core/loader.rb",
32
+ "lib/padrino-core/logger.rb",
32
33
  "lib/padrino-core/mounter.rb",
33
34
  "lib/padrino-core/reloader.rb",
34
35
  "lib/padrino-core/stat.rb",
@@ -44,11 +45,14 @@ Gem::Specification.new do |s|
44
45
  "padrino-core.gemspec",
45
46
  "test/fixtures/apps/.components",
46
47
  "test/fixtures/apps/.gitignore",
47
- "test/fixtures/apps/app.rb",
48
- "test/fixtures/apps/multi.rb",
48
+ "test/fixtures/apps/complex.rb",
49
+ "test/fixtures/apps/simple.rb",
49
50
  "test/helper.rb",
51
+ "test/test_application.rb",
50
52
  "test/test_padrino_core.rb",
51
- "test/test_padrino_mounter.rb"
53
+ "test/test_padrino_mounter.rb",
54
+ "test/test_reloader_complex.rb",
55
+ "test/test_reloader_simple.rb"
52
56
  ]
53
57
  s.homepage = %q{http://github.com/padrino/padrino-framework/tree/master/padrino-core}
54
58
  s.rdoc_options = ["--charset=UTF-8"]
@@ -0,0 +1,29 @@
1
+ PADRINO_ROOT = File.dirname(__FILE__) unless defined? PADRINO_ROOT
2
+
3
+ module LibDemo
4
+ module_function
5
+
6
+ def give_me_a_random
7
+ @rand ||= rand(100)
8
+ end
9
+ end
10
+
11
+ class Complex1Demo < Padrino::Application
12
+ set :reload, true
13
+ get("/old"){ "Old Sinatra Way" }
14
+ end
15
+
16
+ class Complex2Demo < Padrino::Application
17
+ set :reload, true
18
+ get("/old"){ "Old Sinatra Way" }
19
+ end
20
+
21
+ Complex1Demo.controllers do
22
+ get(""){ "Given random #{LibDemo.give_me_a_random}" }
23
+ end
24
+
25
+ Complex2Demo.controllers do
26
+ get(""){ "The magick number is: 88!" } # Change only the number!!!
27
+ end
28
+
29
+ Padrino.load!
@@ -0,0 +1,23 @@
1
+ PADRINO_ROOT = File.dirname(__FILE__) unless defined? PADRINO_ROOT
2
+ # Remove this comment if you want do some like this: ruby PADRINO_ENV=test app.rb
3
+ #
4
+ # require 'rubygems'
5
+ # require 'lib/padrino-core'
6
+ #
7
+ # Use this for prevent (when reload is in use) to re run the server.
8
+ #
9
+ # if Padrino.load!
10
+ # SingleDemo.run!
11
+ # end
12
+
13
+ class SimpleDemo < Padrino::Application
14
+ set :reload, true
15
+ end
16
+
17
+ SimpleDemo.controllers do
18
+ get "/" do
19
+ 'The magick number is: 66!' # Change only the number!!!
20
+ end
21
+ end
22
+
23
+ Padrino.load! # Remove this if you will run the app standalone
data/test/helper.rb CHANGED
@@ -7,19 +7,17 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
7
  require 'rubygems'
8
8
  require 'padrino-core'
9
9
  require 'test/unit'
10
- require 'shoulda'
11
- require 'mocha'
12
10
  require 'rack/test'
13
- require 'webrat'
11
+ require 'rack'
12
+ require 'shoulda'
13
+
14
+ class Padrino::Application
15
+ # Allow assertions in request context
16
+ include Test::Unit::Assertions
17
+ end
14
18
 
15
19
  class Test::Unit::TestCase
16
20
  include Rack::Test::Methods
17
- include Webrat::Methods
18
- include Webrat::Matchers
19
-
20
- Webrat.configure do |config|
21
- config.mode = :rack
22
- end
23
21
 
24
22
  # Test App
25
23
  class PadrinoTestApp < Padrino::Application; end
@@ -46,24 +44,27 @@ class Test::Unit::TestCase
46
44
  assert File.exist?(file), "File '#{file}' does not exist!"
47
45
  assert_match pattern, File.read(file)
48
46
  end
49
- end
50
-
51
- class Object
52
- # Silences the output by redirecting to stringIO
53
- # silence_logger { ...commands... } => "...output..."
54
- def silence_logger(&block)
55
- orig_stdout = $stdout
56
- $stdout = log_buffer = StringIO.new
57
- block.call
58
- $stdout = orig_stdout
59
- log_buffer.rewind && log_buffer.read
60
- end
61
- end
62
-
63
- module Webrat
64
- module Logging
65
- def logger # :nodoc:
66
- @logger = nil
47
+
48
+ # Delegate other missing methods to response.
49
+ def method_missing(name, *args, &block)
50
+ if response && response.respond_to?(name)
51
+ response.send(name, *args, &block)
52
+ else
53
+ super
67
54
  end
68
55
  end
69
- end
56
+
57
+ alias :response :last_response
58
+ end
59
+ #
60
+ # class Object
61
+ # # Silences the output by redirecting to stringIO
62
+ # # silence_logger { ...commands... } => "...output..."
63
+ # def silence_logger(&block)
64
+ # orig_stdout = $stdout
65
+ # $stdout = log_buffer = StringIO.new
66
+ # block.call
67
+ # $stdout = orig_stdout
68
+ # log_buffer.rewind && log_buffer.read
69
+ # end
70
+ # end
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class TestApplication < Test::Unit::TestCase
4
+
5
+ context 'for application functionality' do
6
+
7
+ should 'check default options' do
8
+ assert_match %r{test/helper.rb}, PadrinoTestApp.app_file
9
+ assert_equal :test, PadrinoTestApp.environment
10
+ assert_equal Padrino.root("views"), PadrinoTestApp.views
11
+ assert PadrinoTestApp.raise_errors
12
+ assert !PadrinoTestApp.logging
13
+ assert PadrinoTestApp.sessions
14
+ assert 'PadrinoTestApp', PadrinoTestApp.app_name
15
+ end
16
+
17
+ should 'check padrino specific options' do
18
+ assert !PadrinoTestApp.instance_variable_get(:@_configured)
19
+ PadrinoTestApp.send(:setup_application!)
20
+ assert PadrinoTestApp.instance_variable_get(:@_configured)
21
+ assert !PadrinoTestApp.send(:find_view_path)
22
+ assert !PadrinoTestApp.reload?
23
+ assert 'padrino_test_app', PadrinoTestApp.app_name
24
+ assert 'StandardFormBuilder', PadrinoTestApp.default_builder
25
+ assert !PadrinoTestApp.flash
26
+ assert !PadrinoTestApp.padrino_mailer
27
+ assert !PadrinoTestApp.padrino_helpers
28
+ end
29
+ end
30
+ end
@@ -13,7 +13,7 @@ class TestPadrinoCore < Test::Unit::TestCase
13
13
  end
14
14
 
15
15
  should 'validate global helpers' do
16
- assert_equal "test", Padrino.env
16
+ assert_equal :test, Padrino.env
17
17
  assert_match /\/test/, Padrino.root
18
18
  end
19
19
 
@@ -27,15 +27,15 @@ class TestPadrinoMounter < Test::Unit::TestCase
27
27
  mounter.to("/test")
28
28
  assert_equal "test", mounter.name
29
29
  assert_equal "Test", mounter.app_class
30
- assert_equal __FILE__, mounter.app_file
30
+ assert_match %r{test/apps/test/app.rb}, mounter.app_file
31
31
  assert_equal "/test", mounter.uri_root
32
32
  assert_nil mounter.app_root
33
33
  end
34
34
 
35
35
  should 'mount an app' do
36
- class AnApp < Padrino::Application; end
37
-
36
+ class ::AnApp < Padrino::Application; end
38
37
  Padrino.mount_core("an_app")
38
+ assert_equal AnApp, Padrino.mounted_apps.first.app_obj
39
39
  assert_equal ["core"], Padrino.mounted_apps.collect(&:name)
40
40
  end
41
41
 
@@ -43,18 +43,25 @@ class TestPadrinoMounter < Test::Unit::TestCase
43
43
  mounter = Padrino.mount_core("test")
44
44
  assert_equal "core", mounter.name
45
45
  assert_equal "Test", mounter.app_class
46
+ assert_equal Test, mounter.app_obj
46
47
  assert_equal Padrino.root('app/app.rb'), mounter.app_file
47
48
  assert_equal "/", mounter.uri_root
48
49
  assert_equal Padrino.root, mounter.app_root
49
50
  end
50
51
 
51
52
  should 'mount multiple apps' do
52
- class OneApp < Padrino::Application; end
53
- class TwoApp < Padrino::Application; end
53
+ class ::OneApp < Padrino::Application; end
54
+ class ::TwoApp < Padrino::Application; end
54
55
 
56
+ Padrino.mount("one_app").to("/one_app")
57
+ Padrino.mount("two_app").to("/two_app")
58
+ # And testing no duplicates
55
59
  Padrino.mount("one_app").to("/one_app")
56
60
  Padrino.mount("two_app").to("/two_app")
57
61
 
62
+ assert_equal OneApp, Padrino.mounted_apps[0].app_obj
63
+ assert_equal TwoApp, Padrino.mounted_apps[1].app_obj
64
+ assert_equal 2, Padrino.mounted_apps.size, "should not mount duplicate apps"
58
65
  assert_equal ["one_app", "two_app"], Padrino.mounted_apps.collect(&:name)
59
66
  end
60
67
 
@@ -74,9 +81,9 @@ class TestPadrinoMounter < Test::Unit::TestCase
74
81
  end
75
82
 
76
83
  get '/demo_1'
77
- assert_contain "Im Demo 1"
78
- visit '/demo_2'
79
- assert_contain "Im Demo 2"
84
+ assert_equal "Im Demo 1", body
85
+ get '/demo_2'
86
+ assert_equal "Im Demo 2", body
80
87
  end
81
88
  end
82
89
  end
@@ -0,0 +1,59 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+ require 'fixtures/apps/complex'
3
+
4
+ class TestComplexReloader < Test::Unit::TestCase
5
+
6
+ context 'for complex reload functionality' do
7
+
8
+ should 'correctly instantiate Complex(1-2)Demo fixture' do
9
+ Padrino.mounted_apps.clear
10
+ Padrino.mount("complex_1_demo").to("/complex_1_demo")
11
+ Padrino.mount("complex_2_demo").to("/complex_2_demo")
12
+ assert_equal ["complex_1_demo", "complex_2_demo"], Padrino.mounted_apps.collect(&:name)
13
+ assert Complex1Demo.reload?
14
+ assert Complex2Demo.reload?
15
+ assert_match %r{fixtures/apps/complex.rb}, Complex1Demo.app_file
16
+ assert_match %r{fixtures/apps/complex.rb}, Complex2Demo.app_file
17
+ end
18
+
19
+ should 'correctly reload Complex(1-2)Demo fixture' do
20
+ assert_match %r{fixtures/apps/complex.rb}, Complex1Demo.app_file
21
+ @app = Padrino.application
22
+
23
+ get "/"
24
+ assert_equal 404, status
25
+
26
+ get "/complex_1_demo"
27
+ assert_equal "Given random #{LibDemo.give_me_a_random}", body
28
+
29
+ get "/complex_2_demo"
30
+ assert_equal 200, status
31
+
32
+ get "/complex_1_demo/old"
33
+ assert_equal 200, status
34
+
35
+ get "/complex_2_demo/old"
36
+ assert_equal 200, status
37
+
38
+ new_phrase = "The magick number is: #{rand(100)}!"
39
+ buffer = File.read(Complex1Demo.app_file).gsub!(/The magick number is: \d+!/, new_phrase)
40
+ File.open(Complex1Demo.app_file, "w") { |f| f.write(buffer) }
41
+ sleep 1.2 # We need at least a cooldown of 1 sec.
42
+ get "/complex_2_demo"
43
+ assert_equal new_phrase, body
44
+
45
+ # Re-Check that we didn't forget any route
46
+ get "/complex_1_demo"
47
+ assert_equal "Given random #{LibDemo.give_me_a_random}", body
48
+
49
+ get "/complex_2_demo"
50
+ assert_equal 200, status
51
+
52
+ get "/complex_1_demo/old"
53
+ assert_equal 200, status
54
+
55
+ get "/complex_2_demo/old"
56
+ assert_equal 200, status
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,48 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+ require 'fixtures/apps/simple'
3
+
4
+ class TestSimpleReloader < Test::Unit::TestCase
5
+
6
+ context 'for simple reset functionality' do
7
+
8
+ should 'reset routes' do
9
+ mock_app do
10
+ 1.step(10).each do |i|
11
+ get("/#{i}") { "Foo #{i}" }
12
+ end
13
+ end
14
+ 1.step(10).each do |i|
15
+ get "/#{i}"
16
+ assert_equal "Foo #{i}", body
17
+ end
18
+ @app.reset_routes!
19
+ 1.step(10).each do |i|
20
+ get "/#{i}"
21
+ assert_equal 404, status
22
+ end
23
+ end
24
+ end
25
+
26
+ context 'for simple reload functionality' do
27
+
28
+ should 'correctly instantiate SimpleDemo fixture' do
29
+ Padrino.mounted_apps.clear
30
+ Padrino.mount_core("simple_demo")
31
+ assert_equal ["core"], Padrino.mounted_apps.collect(&:name)
32
+ assert SimpleDemo.reload?
33
+ assert_match %r{fixtures/apps/simple.rb}, SimpleDemo.app_file
34
+ end
35
+
36
+ should 'correctly reload SimpleDemo fixture' do
37
+ @app = SimpleDemo
38
+ get "/"
39
+ assert_equal 200, status
40
+ new_phrase = "The magick number is: #{rand(100)}!"
41
+ buffer = File.read(SimpleDemo.app_file).gsub!(/The magick number is: \d+!/, new_phrase)
42
+ File.open(SimpleDemo.app_file, "w") { |f| f.write(buffer) }
43
+ sleep 1.2 # We need at least a cooldown of 1 sec.
44
+ get "/"
45
+ assert_equal new_phrase, body
46
+ end
47
+ end
48
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: padrino-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Padrino Team
@@ -12,7 +12,7 @@ autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
14
 
15
- date: 2009-11-23 00:00:00 -08:00
15
+ date: 2009-11-24 00:00:00 -08:00
16
16
  default_executable: padrino
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
@@ -115,6 +115,7 @@ files:
115
115
  - lib/padrino-core/application.rb
116
116
  - lib/padrino-core/caller.rb
117
117
  - lib/padrino-core/loader.rb
118
+ - lib/padrino-core/logger.rb
118
119
  - lib/padrino-core/mounter.rb
119
120
  - lib/padrino-core/reloader.rb
120
121
  - lib/padrino-core/stat.rb
@@ -130,11 +131,14 @@ files:
130
131
  - padrino-core.gemspec
131
132
  - test/fixtures/apps/.components
132
133
  - test/fixtures/apps/.gitignore
133
- - test/fixtures/apps/app.rb
134
- - test/fixtures/apps/multi.rb
134
+ - test/fixtures/apps/complex.rb
135
+ - test/fixtures/apps/simple.rb
135
136
  - test/helper.rb
137
+ - test/test_application.rb
136
138
  - test/test_padrino_core.rb
137
139
  - test/test_padrino_mounter.rb
140
+ - test/test_reloader_complex.rb
141
+ - test/test_reloader_simple.rb
138
142
  has_rdoc: true
139
143
  homepage: http://github.com/padrino/padrino-framework/tree/master/padrino-core
140
144
  licenses: []
@@ -1,15 +0,0 @@
1
- PADRINO_ROOT = File.dirname(__FILE__) unless defined? PADRINO_ROOT
2
- require 'rubygems'
3
- require 'sinatra/base'
4
- require 'lib/padrino-core'
5
-
6
- class SingleDemo < Padrino::Application; end
7
-
8
- SingleDemo.controllers do
9
- get "/test" do
10
- 'This should work'
11
- end
12
- end
13
-
14
- Padrino.mount_core("single_demo")
15
- Padrino.load!
@@ -1,27 +0,0 @@
1
- PADRINO_ROOT = File.dirname(__FILE__) unless defined? PADRINO_ROOT
2
- require 'sinatra/base'
3
- require 'padrino-core'
4
-
5
- class Multi1Demo < Padrino::Application
6
- disable :padrino_routing
7
- disable :padrino_mailer
8
- disable :padrino_helpers
9
-
10
- get "" do
11
- "Im Core1Demo"
12
- end
13
- end
14
-
15
- class Mutli2Demo < Padrino::Application
16
- disable :padrino_routing
17
- disable :padrino_mailer
18
- disable :padrino_helpers
19
-
20
- get "" do
21
- "Im Core2Demo"
22
- end
23
- end
24
-
25
- Padrino.mount("multi_1_demo").to("/multi_1_demo")
26
- Padrino.mount("multi_2_demo").to("/multi_2_demo")
27
- Padrino.load!