aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/abstract_controller
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/abstract_controller')
-rw-r--r--actionpack/lib/abstract_controller/base.rb18
-rw-r--r--actionpack/lib/abstract_controller/collector.rb2
-rw-r--r--actionpack/lib/abstract_controller/compatibility.rb18
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb8
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb134
-rw-r--r--actionpack/lib/abstract_controller/localized_cache.rb49
-rw-r--r--actionpack/lib/abstract_controller/logger.rb4
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb212
-rw-r--r--actionpack/lib/abstract_controller/view_paths.rb73
9 files changed, 187 insertions, 331 deletions
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 3119ee498b..c12b584144 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -1,3 +1,5 @@
+require 'active_support/ordered_options'
+
module AbstractController
class Error < StandardError; end
class ActionNotFound < StandardError; end
@@ -5,7 +7,6 @@ module AbstractController
class Base
attr_internal :response_body
attr_internal :action_name
- attr_internal :formats
class << self
attr_reader :abstract
@@ -28,6 +29,14 @@ module AbstractController
@descendants ||= []
end
+ def config
+ @config ||= ActiveSupport::InheritableOptions.new(superclass < Base ? superclass.config : {})
+ end
+
+ def configure
+ yield config
+ end
+
# A list of all internal methods for a controller. This finds the first
# abstract superclass of a controller, and gets a list of all public
# instance methods on that abstract class. Public instance methods of
@@ -84,15 +93,14 @@ module AbstractController
# ==== Returns
# String
def controller_path
- @controller_path ||= name && name.sub(/Controller$/, '').underscore
+ @controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous?
end
end
abstract!
- # Initialize controller with nil formats.
- def initialize #:nodoc:
- @_formats = nil
+ def config
+ @config ||= ActiveSupport::InheritableOptions.new(self.class.config)
end
# Calls the action going through the entire action dispatch stack.
diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb
index d429333661..81fb514770 100644
--- a/actionpack/lib/abstract_controller/collector.rb
+++ b/actionpack/lib/abstract_controller/collector.rb
@@ -1,3 +1,5 @@
+require "action_dispatch/http/mime_type"
+
module AbstractController
module Collector
def self.generate_method_for_mime(mime)
diff --git a/actionpack/lib/abstract_controller/compatibility.rb b/actionpack/lib/abstract_controller/compatibility.rb
deleted file mode 100644
index 7fb93a0eb5..0000000000
--- a/actionpack/lib/abstract_controller/compatibility.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-module AbstractController
- module Compatibility
- extend ActiveSupport::Concern
-
- def _find_layout(name, details)
- details[:prefix] = nil if name =~ /\blayouts/
- super
- end
-
- # Move this into a "don't run in production" module
- def _default_layout(details, require_layout = false)
- super
- rescue ActionView::MissingTemplate
- _find_layout(_layout({}), {})
- nil
- end
- end
-end
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index 578b884a4d..f875213afb 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -1,6 +1,4 @@
require 'active_support/dependencies'
-require 'active_support/core_ext/class/attribute'
-require 'active_support/core_ext/module/delegation'
module AbstractController
module Helpers
@@ -27,7 +25,7 @@ module AbstractController
def inherited(klass)
helpers = _helpers
klass._helpers = Module.new { include helpers }
- klass.class_eval { default_helper_module! unless name.blank? }
+ klass.class_eval { default_helper_module! unless anonymous? }
super
end
@@ -99,7 +97,7 @@ module AbstractController
def helper(*args, &block)
self._helper_serial = AbstractController::Helpers.next_serial + 1
- _modules_for_helpers(args).each do |mod|
+ modules_for_helpers(args).each do |mod|
add_template_helper(mod)
end
@@ -134,7 +132,7 @@ module AbstractController
# ==== Returns
# Array[Module]:: A normalized list of modules for the list of
# helpers provided.
- def _modules_for_helpers(args)
+ def modules_for_helpers(args)
args.flatten.map! do |arg|
case arg
when String, Symbol
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index 0d214396aa..2f9616124a 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -1,6 +1,3 @@
-require 'active_support/core_ext/class/attribute'
-require 'active_support/core_ext/module/delegation'
-
module AbstractController
# Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
# repeated setups. The inclusion pattern has pages that look like this:
@@ -173,27 +170,7 @@ module AbstractController
module ClassMethods
def inherited(klass)
super
- klass.class_eval do
- _write_layout_method
- @found_layouts = {}
- end
- end
-
- def clear_template_caches!
- @found_layouts.clear if defined? @found_layouts
- super
- end
-
- def cache_layout(details)
- layout = @found_layouts
- key = Thread.current[:format_locale_key]
-
- # Cache nil
- if layout.key?(key)
- return layout[key]
- else
- layout[key] = yield
- end
+ klass._write_layout_method
end
# This module is mixed in if layout conditions are provided. This means
@@ -262,10 +239,10 @@ module AbstractController
def _write_layout_method
case defined?(@_layout) ? @_layout : nil
when String
- self.class_eval %{def _layout(details) #{@_layout.inspect} end}
+ self.class_eval %{def _layout; #{@_layout.inspect} end}
when Symbol
self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
- def _layout(details)
+ def _layout
#{@_layout}.tap do |layout|
unless layout.is_a?(String) || !layout
raise ArgumentError, "Your layout method :#{@_layout} returned \#{layout}. It " \
@@ -276,21 +253,21 @@ module AbstractController
ruby_eval
when Proc
define_method :_layout_from_proc, &@_layout
- self.class_eval %{def _layout(details) _layout_from_proc(self) end}
+ self.class_eval %{def _layout; _layout_from_proc(self) end}
when false
- self.class_eval %{def _layout(details) end}
+ self.class_eval %{def _layout; end}
when true
raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil"
when nil
if name
+ _prefix = "layouts" unless _implied_layout_name =~ /\blayouts/
+
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def _layout(details)
- self.class.cache_layout(details) do
- if template_exists?("#{_implied_layout_name}", details, :_prefix => "layouts")
- "#{_implied_layout_name}"
- else
- super
- end
+ def _layout
+ if template_exists?("#{_implied_layout_name}", #{_prefix.inspect})
+ "#{_implied_layout_name}"
+ else
+ super
end
end
RUBY
@@ -300,42 +277,20 @@ module AbstractController
end
end
- def render_to_body(options = {})
- # In the case of a partial with a layout, handle the layout
- # here, and make sure the view does not try to handle it
- layout = options.delete(:layout) if options.key?(:partial)
-
- response = super
+ def _normalize_options(options)
+ super
- # This is a little bit messy. We need to explicitly handle partial
- # layouts here since the core lookup logic is in the view, but
- # we need to determine the layout based on the controller
- #
- # TODO: An easier way to handle this would probably be to override
- # render_template
- if layout
- layout = _layout_for_option(layout, options[:_template].details)
- response = layout.render(view_context, options[:locals] || {}) { response }
+ if _include_layout?(options)
+ layout = options.key?(:layout) ? options.delete(:layout) : :default
+ value = _layout_for_option(layout)
+ options[:layout] = (value =~ /\blayouts/ ? value : "layouts/#{value}") if value
end
-
- response
end
private
# This will be overwritten by _write_layout_method
- def _layout(details) end
-
- # Determine the layout for a given name and details.
- #
- # ==== Parameters
- # name<String>:: The name of the template
- # details<Hash{Symbol => Object}>:: A list of details to restrict
- # the lookup to. By default, layout lookup is limited to the
- # formats specified for the current request.
- def _layout_for_name(name, details)
- name && _find_layout(name, details)
- end
+ def _layout; end
# Determine the layout for a given name and details, taking into account
# the name type.
@@ -345,11 +300,11 @@ module AbstractController
# details<Hash{Symbol => Object}>:: A list of details to restrict
# the lookup to. By default, layout lookup is limited to the
# formats specified for the current request.
- def _layout_for_option(name, details)
+ def _layout_for_option(name)
case name
- when String then _layout_for_name(name, details)
- when true then _default_layout(details, true)
- when :default then _default_layout(details, false)
+ when String then name
+ when true then _default_layout(true)
+ when :default then _default_layout(false)
when false, nil then nil
else
raise ArgumentError,
@@ -357,29 +312,6 @@ module AbstractController
end
end
- def _determine_template(options)
- super
-
- return unless (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout)
- layout = options.key?(:layout) ? options[:layout] : :default
- options[:_layout] = _layout_for_option(layout, options[:_template].details)
- end
-
- # Take in the name and details and find a Template.
- #
- # ==== Parameters
- # name<String>:: The name of the template to retrieve
- # details<Hash>:: A list of details to restrict the search by. This
- # might include details like the format or locale of the template.
- #
- # ==== Returns
- # Template:: A template object matching the name and details
- def _find_layout(name, details)
- # TODO: Make prefix actually part of details in ViewPath#find_by_parts
- prefix = details.key?(:prefix) ? details.delete(:prefix) : "layouts"
- find_template(name, details, :_prefix => prefix)
- end
-
# Returns the default layout for this controller and a given set of details.
# Optionally raises an exception if the layout could not be found.
#
@@ -392,18 +324,24 @@ module AbstractController
#
# ==== Returns
# Template:: The template object for the default layout (or nil)
- def _default_layout(details, require_layout = false)
- if require_layout && _action_has_layout? && !_layout(details)
- raise ArgumentError,
- "There was no default layout for #{self.class} in #{view_paths.inspect}"
- end
-
+ def _default_layout(require_layout = false)
begin
- _layout_for_name(_layout(details), details) if _action_has_layout?
+ layout_name = _layout if _action_has_layout?
rescue NameError => e
raise NoMethodError,
"You specified #{@_layout.inspect} as the layout, but no such method was found"
end
+
+ if require_layout && _action_has_layout? && !layout_name
+ raise ArgumentError,
+ "There was no default layout for #{self.class} in #{view_paths.inspect}"
+ end
+
+ layout_name
+ end
+
+ def _include_layout?(options)
+ (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout)
end
def _action_has_layout?
diff --git a/actionpack/lib/abstract_controller/localized_cache.rb b/actionpack/lib/abstract_controller/localized_cache.rb
deleted file mode 100644
index 5e3efa002c..0000000000
--- a/actionpack/lib/abstract_controller/localized_cache.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-module AbstractController
- class HashKey
- @hash_keys = Hash.new {|h,k| h[k] = Hash.new {|sh,sk| sh[sk] = {} } }
-
- def self.get(klass, formats, locale)
- @hash_keys[klass][formats][locale] ||= new(klass, formats, locale)
- end
-
- attr_accessor :hash
- def initialize(klass, formats, locale)
- @formats, @locale = formats, locale
- @hash = [formats, locale].hash
- end
-
- alias_method :eql?, :equal?
-
- def inspect
- "#<HashKey -- formats: #{@formats.inspect} locale: #{@locale.inspect}>"
- end
- end
-
- module LocalizedCache
- extend ActiveSupport::Concern
-
- module ClassMethods
- def clear_template_caches!
- ActionView::Partials::PartialRenderer::TEMPLATES.clear
- template_cache.clear
- super
- end
-
- def template_cache
- @template_cache ||= Hash.new {|h,k| h[k] = {} }
- end
- end
-
- def render(*args)
- Thread.current[:format_locale_key] = HashKey.get(self.class, formats, I18n.locale)
- super
- end
-
- private
-
- def with_template_cache(name)
- self.class.template_cache[Thread.current[:format_locale_key]][name] ||= super
- end
-
- end
-end
diff --git a/actionpack/lib/abstract_controller/logger.rb b/actionpack/lib/abstract_controller/logger.rb
index a23a13e1d6..9318f5e369 100644
--- a/actionpack/lib/abstract_controller/logger.rb
+++ b/actionpack/lib/abstract_controller/logger.rb
@@ -1,5 +1,5 @@
-require 'active_support/core_ext/logger'
-require 'active_support/benchmarkable'
+require "active_support/core_ext/logger"
+require "active_support/benchmarkable"
module AbstractController
module Logger
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 619a49571b..42f4939108 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -1,7 +1,4 @@
require "abstract_controller/base"
-require 'active_support/core_ext/class/attribute'
-require 'active_support/core_ext/module/delegation'
-require 'active_support/core_ext/array/wrap'
module AbstractController
class DoubleRenderError < Error
@@ -12,32 +9,47 @@ module AbstractController
end
end
+ # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request,
+ # it will trigger the lookup_context and consequently expire the cache.
+ # TODO Add some deprecation warnings to remove I18n.locale from controllers
+ class I18nProxy < ::I18n::Config #:nodoc:
+ attr_reader :i18n_config, :lookup_context
+
+ def initialize(i18n_config, lookup_context)
+ @i18n_config, @lookup_context = i18n_config, lookup_context
+ end
+
+ def locale
+ @i18n_config.locale
+ end
+
+ def locale=(value)
+ @i18n_config.locale = value
+ @lookup_context.update_details(:locale => @i18n_config.locale)
+ end
+ end
+
module Rendering
extend ActiveSupport::Concern
+ include AbstractController::ViewPaths
- included do
- class_attribute :_view_paths
- delegate :_view_paths, :to => :'self.class'
- self._view_paths = ActionView::PathSet.new
+ # Overwrite process to setup I18n proxy.
+ def process(*) #:nodoc:
+ old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
+ super
+ ensure
+ I18n.config = old_config
end
# An instance of a view class. The default view class is ActionView::Base
#
# The view class must have the following methods:
- # View.for_controller[controller] Create a new ActionView instance for a
- # controller
- # View#render_partial[options]
- # - responsible for setting options[:_template]
- # - Returns String with the rendered partial
- # options<Hash>:: see _render_partial in ActionView::Base
- # View#render_template[template, layout, options, partial]
- # - Returns String with the rendered template
- # template<ActionView::Template>:: The template to render
- # layout<ActionView::Template>:: The layout to render around the template
- # options<Hash>:: See _render_template_with_layout in ActionView::Base
- # partial<Boolean>:: Whether or not the template to render is a partial
+ # View.for_controller[controller]
+ # Create a new ActionView instance for a controller
+ # View#render_template[options]
+ # Returns String with the rendered template
#
- # Override this method in a to change the default behavior.
+ # Override this method in a module to change the default behavior.
def view_context
@_view_context ||= ActionView::Base.for_controller(self)
end
@@ -45,57 +57,29 @@ module AbstractController
# Mostly abstracts the fact that calling render twice is a DoubleRenderError.
# Delegates render_to_body and sticks the result in self.response_body.
def render(*args, &block)
- options = _normalize_options(*args, &block)
+ options = _normalize_args(*args, &block)
+ _normalize_options(options)
self.response_body = render_to_body(options)
end
# Raw rendering of a template to a Rack-compatible body.
- #
- # ==== Options
- # _partial_object<Object>:: The object that is being rendered. If this
- # exists, we are in the special case of rendering an object as a partial.
- #
# :api: plugin
def render_to_body(options = {})
- # TODO: Refactor so we can just use the normal template logic for this
- if options.key?(:partial)
- _render_partial(options)
- else
- _determine_template(options)
- _render_template(options)
- end
+ _process_options(options)
+ _render_template(options)
end
# Raw rendering of a template to a string. Just convert the results of
# render_to_body into a String.
- #
# :api: plugin
- def render_to_string(*args)
- options = _normalize_options(*args)
+ def render_to_string(options={})
+ _normalize_options(options)
AbstractController::Rendering.body_to_s(render_to_body(options))
end
- # Renders the template from an object.
- #
- # ==== Options
- # _template<ActionView::Template>:: The template to render
- # _layout<ActionView::Template>:: The layout to wrap the template in (optional)
+ # Find and renders a template based on the options given.
def _render_template(options)
- view_context.render_template(options)
- end
-
- # Renders the given partial.
- #
- # ==== Options
- # partial<String|Object>:: The partial name or the object to be rendered
- def _render_partial(options)
- view_context.render_partial(options)
- end
-
- # The list of view paths for this controller. See ActionView::ViewPathSet for
- # more details about writing custom view paths.
- def view_paths
- _view_paths
+ view_context.render_template(options) { |template| _with_template_hook(template) }
end
# The prefix used in render "foo" shortcuts.
@@ -117,122 +101,42 @@ module AbstractController
private
- # Normalize options, by converting render "foo" to render :template => "prefix/foo"
- # and render "/foo" to render :file => "/foo".
- def _normalize_options(action=nil, options={})
+ # Normalize options by converting render "foo" to render :action => "foo" and
+ # render "foo/bar" to render :file => "foo/bar".
+ def _normalize_args(action=nil, options={})
case action
+ when NilClass
when Hash
options, action = action, nil
when String, Symbol
action = action.to_s
- case action.index("/")
- when NilClass
- options[:_prefix] = _prefix
- options[:_template_name] = action
- when 0
- options[:file] = action
- else
- options[:template] = action
- end
+ key = action.include?(?/) ? :file : :action
+ options[key] = action
+ else
+ options.merge!(:partial => action)
end
options
end
- # Take in a set of options and determine the template to render
- #
- # ==== Options
- # _template<ActionView::Template>:: If this is provided, the search is over
- # _template_name<#to_s>:: The name of the template to look up. Otherwise,
- # use the current action name.
- # _prefix<String>:: The prefix to look inside of. In a file system, this corresponds
- # to a directory.
- # _partial<TrueClass, FalseClass>:: Whether or not the file to look up is a partial
- def _determine_template(options)
- if options.key?(:text)
- options[:_template] = ActionView::Template::Text.new(options[:text], format_for_text)
- elsif options.key?(:inline)
- handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb")
- template = ActionView::Template.new(options[:inline], "inline template", handler, {})
- options[:_template] = template
- elsif options.key?(:template)
- options[:_template_name] = options[:template]
- elsif options.key?(:file)
- options[:_template_name] = options[:file]
+ def _normalize_options(options)
+ if options[:partial] == true
+ options[:partial] = action_name
end
- name = (options[:_template_name] || options[:action] || action_name).to_s
- options[:_prefix] ||= _prefix if (options.keys & [:partial, :file, :template]).empty?
-
- details = _normalize_details(options)
- options[:_template] ||= with_template_cache(name) do
- find_template(name, details, options)
+ if (options.keys & [:partial, :file, :template]).empty?
+ options[:prefix] ||= _prefix
end
- end
-
- def _normalize_details(options)
- details = { :formats => formats }
- details[:formats] = Array(options[:format]) if options[:format]
- details[:locale] = Array(options[:locale]) if options[:locale]
- details
- end
- def find_template(name, details, options)
- view_paths.find(name, details, options[:_prefix], options[:_partial])
- end
-
- def template_exists?(name, details, options)
- view_paths.exists?(name, details, options[:_prefix], options[:_partial])
- end
-
- def with_template_cache(name)
- yield
+ options[:template] ||= (options[:action] || action_name).to_s
+ options
end
- def format_for_text
- Mime[:text]
+ def _process_options(options)
end
- module ClassMethods
- def clear_template_caches!
- end
-
- # Append a path to the list of view paths for this controller.
- #
- # ==== Parameters
- # path<String, ViewPath>:: If a String is provided, it gets converted into
- # the default view path. You may also provide a custom view path
- # (see ActionView::ViewPathSet for more information)
- def append_view_path(path)
- self.view_paths = view_paths.dup + Array.wrap(path)
- end
-
- # Prepend a path to the list of view paths for this controller.
- #
- # ==== Parameters
- # path<String, ViewPath>:: If a String is provided, it gets converted into
- # the default view path. You may also provide a custom view path
- # (see ActionView::ViewPathSet for more information)
- def prepend_view_path(path)
- clear_template_caches!
- self.view_paths = Array.wrap(path) + view_paths.dup
- end
-
- # A list of all of the default view paths for this controller.
- def view_paths
- _view_paths
- end
-
- # Set the view paths.
- #
- # ==== Parameters
- # paths<ViewPathSet, Object>:: If a ViewPathSet is provided, use that;
- # otherwise, process the parameter into a ViewPathSet.
- def view_paths=(paths)
- clear_template_caches!
- self._view_paths = paths.is_a?(ActionView::PathSet) ? paths : ActionView::Base.process_view_paths(paths)
- _view_paths.freeze
- end
+ def _with_template_hook(template)
+ self.formats = template.formats
end
end
end
diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb
new file mode 100644
index 0000000000..c2a9f6336d
--- /dev/null
+++ b/actionpack/lib/abstract_controller/view_paths.rb
@@ -0,0 +1,73 @@
+module AbstractController
+ module ViewPaths
+ extend ActiveSupport::Concern
+
+ included do
+ class_attribute :_view_paths
+ self._view_paths = ActionView::PathSet.new
+ end
+
+ delegate :template_exists?, :view_paths, :formats, :formats=,
+ :locale, :locale=, :to => :lookup_context
+
+ # LookupContext is the object responsible to hold all information required to lookup
+ # templates, i.e. view paths and details. Check ActionView::LookupContext for more
+ # information.
+ def lookup_context
+ @lookup_context ||= ActionView::LookupContext.new(self.class._view_paths, details_for_lookup)
+ end
+
+ def details_for_lookup
+ { }
+ end
+
+ def append_view_path(path)
+ lookup_context.view_paths.push(*path)
+ end
+
+ def prepend_view_path(path)
+ lookup_context.view_paths.unshift(*path)
+ end
+
+ def template_exists?(*args)
+ lookup_context.exists?(*args)
+ end
+
+ module ClassMethods
+ # Append a path to the list of view paths for this controller.
+ #
+ # ==== Parameters
+ # path<String, ViewPath>:: If a String is provided, it gets converted into
+ # the default view path. You may also provide a custom view path
+ # (see ActionView::ViewPathSet for more information)
+ def append_view_path(path)
+ self.view_paths = view_paths.dup + Array(path)
+ end
+
+ # Prepend a path to the list of view paths for this controller.
+ #
+ # ==== Parameters
+ # path<String, ViewPath>:: If a String is provided, it gets converted into
+ # the default view path. You may also provide a custom view path
+ # (see ActionView::ViewPathSet for more information)
+ def prepend_view_path(path)
+ self.view_paths = Array(path) + view_paths.dup
+ end
+
+ # A list of all of the default view paths for this controller.
+ def view_paths
+ _view_paths
+ end
+
+ # Set the view paths.
+ #
+ # ==== Parameters
+ # paths<ViewPathSet, Object>:: If a ViewPathSet is provided, use that;
+ # otherwise, process the parameter into a ViewPathSet.
+ def view_paths=(paths)
+ self._view_paths = paths.is_a?(ActionView::PathSet) ? paths : ActionView::Base.process_view_paths(paths)
+ self._view_paths.freeze
+ end
+ end
+ end
+end \ No newline at end of file