lib/hanami/view.rb in hanami-view-1.3.3 vs lib/hanami/view.rb in hanami-view-2.0.0.alpha2
- old
+ new
@@ -1,267 +1,252 @@
-require 'set'
-require 'pathname'
-require 'hanami/utils/class_attribute'
-require 'hanami/view/version'
-require 'hanami/view/configuration'
-require 'hanami/view/inheritable'
-require 'hanami/view/rendering'
-require 'hanami/view/escape'
-require 'hanami/view/dsl'
-require 'hanami/view/errors'
-require 'hanami/layout'
-require 'hanami/presenter'
+# frozen_string_literal: true
-# Hanami
-#
-# @since 0.1.0
+require "dry/configurable"
+require "dry/core/cache"
+require "dry/core/equalizer"
+require "dry/inflector"
+
+require_relative "view/application_view"
+require_relative "view/context"
+require_relative "view/exposures"
+require_relative "view/errors"
+require_relative "view/part_builder"
+require_relative "view/path"
+require_relative "view/render_environment"
+require_relative "view/rendered"
+require_relative "view/renderer"
+require_relative "view/scope_builder"
+require_relative "view/standalone_view"
+
module Hanami
- # View
+ # A standalone, template-based view rendering system that offers everything
+ # you need to write well-factored view code.
#
- # @since 0.1.0
- module View
- include Utils::ClassAttribute
- # Framework configuration
- #
- # @since 0.2.0
+ # This represents a single view, holding the configuration and exposures
+ # necessary for rendering its template.
+ #
+ # @abstract Subclass this and provide your own configuration and exposures to
+ # define your own view (along with a custom `#initialize` if you wish to
+ # inject dependencies into your subclass)
+ #
+ # @see https://dry-rb.org/gems/dry-view/
+ #
+ # @api public
+ class View
# @api private
- class_attribute :configuration
- self.configuration = Configuration.new
+ DEFAULT_RENDERER_OPTIONS = {default_encoding: "utf-8"}.freeze
- # Configure the framework.
- # It yields the given block in the context of the configuration
+ include Dry::Equalizer(:config, :exposures)
+
+ extend Dry::Core::Cache
+
+ extend Dry::Configurable
+
+ # @!group Configuration
+
+ # @overload config.paths=(paths)
+ # Set an array of directories that will be searched for all templates
+ # (templates, partials, and layouts).
#
- # @param blk [Proc] the configuration block
+ # These will be converted into Path objects and used for template lookup
+ # when rendering.
#
- # @since 0.2.0
+ # This is a **required setting**.
#
- # @see Hanami::View::Configuration
+ # @param paths [String, Path, Array<String, Path>] the paths
#
- # @example
- # require 'hanami/view'
- #
- # Hanami::View.configure do
- # root '/path/to/root'
- # end
- def self.configure(&blk)
- configuration.instance_eval(&blk)
+ # @api public
+ # @!scope class
+ setting :paths do |paths|
+ Array(paths).map { |path| Path[path] }
end
- # Duplicate Hanami::View in order to create a new separated instance
- # of the framework.
+ # @overload config.template=(name)
+ # Set the name of the template for rendering this view. Template name
+ # should be relative to the configured `paths`.
#
- # The new instance of the framework will be completely decoupled from the
- # original. It will inherit the configuration, but all the changes that
- # happen after the duplication, won't be reflected on the other copies.
+ # This is a **required setting**.
#
- # @return [Module] a copy of Hanami::View
+ # @param name [String] template name
+ # @api public
+ # @!scope class
+ setting :template
+
+ # @overload config.template_inference_base=(base_path)
+ # Set the base path to strip away when when inferring a view's template
+ # names from its class name.
#
- # @since 0.2.0
- # @api private
+ # **This setting only applies for views within an Hanami application.**
#
- # @example Basic usage
- # require 'hanami/view'
+ # For example, given a view `Main::Views::Articles::Index`, in the `Main`
+ # slice, and a template_inference_base of "views", the inferred template
+ # name will be "articles/index".
#
- # module MyApp
- # View = Hanami::View.dupe
- # end
+ # @param base_path [String, nil] base templates path
+ # @api public
+ # @!scope class
+ setting :template_inference_base
+
+ # @overload config.layout=(name)
+ # Set the name of the layout to render templates within. Layouts will be
+ # looked up within the configured `layouts_dir`, within the configured
+ # `paths`.
#
- # MyApp::View == Hanami::View # => false
+ # A false or nil value will use no layout. Defaults to `nil`.
#
- # MyApp::View.configuration ==
- # Hanami::View.configuration # => false
+ # @param name [String, FalseClass, nil] layout name, or false to indicate no layout
+ # @api public
+ # @!scope class
+ setting :layout, false
+
+ # @overload config.layouts_dir=(dir)
+ # Set the name of the directory (within the configured `paths`) holding
+ # the layouts. Defaults to `"layouts"`
#
- # @example Inheriting configuration
- # require 'hanami/view'
+ # @param dir [String] directory name
+ # @api public
+ # @!scope class
+ setting :layouts_dir, "layouts"
+
+ # @overload config.scope=(scope_class)
+ # Set the scope class to use when rendering the view's template.
#
- # Hanami::View.configure do
- # root '/path/to/root'
- # end
+ # Configuring a custom scope class allows you to provide extra behaviour
+ # (alongside exposures) to the template.
#
- # module MyApp
- # View = Hanami::View.dupe
- # end
+ # @see https://dry-rb.org/gems/dry-view/scopes/
#
- # module MyApi
- # View = Hanami::View.dupe
- # View.configure do
- # root '/another/root'
- # end
- # end
- #
- # Hanami::View.configuration.root # => #<Pathname:/path/to/root>
- # MyApp::View.configuration.root # => #<Pathname:/path/to/root>
- # MyApi::View.configuration.root # => #<Pathname:/another/root>
- def self.dupe
- dup.tap do |duplicated|
- duplicated.configuration = configuration.duplicate
- end
- end
+ # @param scope_class [Class] scope class (inheriting from `Hanami::View::Scope`)
+ # @api public
+ # @!scope class
+ setting :scope
- # Duplicate the framework and generate modules for the target application
+ # @overload config.default_context=(context)
+ # Set the default context object to use when rendering. This will be used
+ # unless another context object is applied at render-time to `View#call`
#
- # @param mod [Module] the Ruby namespace of the application
- # @param views [String] the optional namespace where the application's
- # views will live
- # @param blk [Proc] an optional block to configure the framework
+ # Defaults to a frozen instance of `Hanami::View::Context`.
#
- # @return [Module] a copy of Hanami::View
+ # @see View#call
#
- # @since 0.2.0
+ # @param context [Hanami::View::Context] context object
+ # @api public
+ # @!scope class
+ setting :default_context, Context.new.freeze
+
+ # @overload config.default_format=(format)
+ # Set the default format to use when rendering.
#
- # @see Hanami::View#dupe
- # @see Hanami::View::Configuration
- # @see Hanami::View::Configuration#namespace
+ # Defaults to `:html`.
#
- # @example Basic usage
- # require 'hanami/view'
+ # @param format [Symbol]
+ # @api public
+ # @!scope class
+ setting :default_format, :html
+
+ # @overload config.scope_namespace=(namespace)
+ # Set a namespace that will be searched when building scope classes.
#
- # module MyApp
- # View = Hanami::View.duplicate(self)
- # end
+ # @param namespace [Module, Class]
#
- # # It will:
- # #
- # # 1. Generate MyApp::View
- # # 2. Generate MyApp::Layout
- # # 3. Generate MyApp::Presenter
- # # 4. Generate MyApp::Views
- # # 5. Configure MyApp::Views as the default namespace for views
+ # @see Scope
#
- # module MyApp::Views::Dashboard
- # class Index
- # include MyApp::View
- # end
- # end
+ # @api public
+ # @!scope class
+ setting :part_namespace
+
+ # @overload config.part_builder=(part_builder)
+ # Set a custom part builder class
#
- # @example Compare code
- # require 'hanami/view'
+ # @see https://dry-rb.org/gems/dry-view/parts/
#
- # module MyApp
- # View = Hanami::View.duplicate(self) do
- # # ...
- # end
- # end
+ # @param part_builder [Class]
+ # @api public
+ # @!scope class
+ setting :part_builder, PartBuilder
+
+ # @overload config.scope_namespace=(namespace)
+ # Set a namespace that will be searched when building scope classes.
#
- # # it's equivalent to:
+ # @param namespace [Module, Class]
#
- # module MyApp
- # View = Hanami::View.dupe
- # Layout = Hanami::Layout.dup
+ # @see Scope
#
- # module Views
- # end
+ # @api public
+ # @!scope class
+ setting :scope_namespace
+
+ # @overload config.scope_builder=(scope_builder)
+ # Set a custom scope builder class
#
- # View.configure do
- # namespace 'MyApp::Views'
- # end
+ # @see https://dry-rb.org/gems/dry-view/scopes/
#
- # View.configure do
- # # ...
- # end
- # end
+ # @param scope_builder [Class]
+ # @api public
+ # @!scope class
+ setting :scope_builder, ScopeBuilder
+
+ # @overload config.inflector=(inflector)
+ # Set an inflector to provide to the part_builder and scope_builder.
#
- # @example Custom views module
- # require 'hanami/view
+ # Defaults to `Dry::Inflector.new`.
#
- # module MyApp
- # View = Hanami::View.duplicate(self, 'Vs')
- # end
+ # @param inflector
+ # @api public
+ # @!scope class
+ setting :inflector, Dry::Inflector.new
+
+ # @overload config.renderer_options=(options)
+ # A hash of options to pass to the template engine. Template engines are
+ # provided by Tilt; see Tilt's documentation for what options your
+ # template engine may support.
#
- # defined?(MyApp::Views) # => nil
- # defined?(MyApp::Vs) # => "constant"
+ # Defaults to `{default_encoding: "utf-8"}`. Any options passed will be
+ # merged onto the defaults.
#
- # # Developers can namespace views under Vs
- # module MyApp::Vs::Dashboard
- # # ...
- # end
+ # @see https://github.com/rtomayko/tilt
#
- # @example Nil views module
- # require 'hanami/view'
- #
- # module MyApp
- # View = Hanami::View.duplicate(self, nil)
- # end
- #
- # defined?(MyApp::Views) # => nil
- #
- # # Developers can namespace views under MyApp
- # module MyApp
- # # ...
- # end
- #
- # @example Block usage
- # require 'hanami/view'
- #
- # module MyApp
- # View = Hanami::View.duplicate(self) do
- # root '/path/to/root'
- # end
- # end
- #
- # Hanami::View.configuration.root # => #<Pathname:.>
- # MyApp::View.configuration.root # => #<Pathname:/path/to/root>
- def self.duplicate(mod, views = 'Views', &blk)
- dupe.tap do |duplicated|
- mod.module_eval %{ module #{ views }; end } if views
- mod.module_eval %{
- Layout = Hanami::Layout.dup
- Presenter = Hanami::Presenter.dup
- }
-
- duplicated.configure do
- namespace [mod, views].compact.join '::'
- end
-
- duplicated.configure(&blk) if block_given?
- end
+ # @param options [Hash] renderer options
+ # @api public
+ # @!scope class
+ setting :renderer_options, DEFAULT_RENDERER_OPTIONS do |options|
+ DEFAULT_RENDERER_OPTIONS.merge(options.to_h).freeze
end
- # Override Ruby's hook for modules.
- # It includes basic Hanami::View modules to the given Class.
- # It sets a copy of the framework configuration
+ # @overload config.renderer_engine_mapping=(mapping)
+ # A hash specifying the (Tilt-compatible) template engine class to use
+ # for a given format. Template engine detection is automatic based on
+ # format; use this setting only if you want to force a non-preferred
+ # engine.
#
- # @param base [Class] the target view
+ # @example
+ # config.renderer_engine_mapping = {erb: Tilt::ErubiTemplate}
#
- # @since 0.1.0
- # @api private
+ # @see https://github.com/rtomayko/tilt
#
- # @see http://www.ruby-doc.org/core-2.1.2/Module.html#method-i-included
- #
- # @see Hanami::View::Dsl
- # @see Hanami::View::Inheritable
- # @see Hanami::View::Rendering
- #
- # @example
- # require 'hanami/view'
- #
- # class IndexView
- # include Hanami::View
- # end
- def self.included(base)
- conf = self.configuration
- conf.add_view(base)
+ # @param mapping [Hash<Symbol, Class>] engine mapping
+ # @api public
+ # @!scope class
+ setting :renderer_engine_mapping
- base.class_eval do
- extend Inheritable
- extend Dsl
- extend Rendering
- extend Escape
+ # @!endgroup
- include Utils::ClassAttribute
- class_attribute :configuration
+ include StandaloneView
- self.configuration = conf.duplicate
- end
+ def self.inherited(subclass)
+ super
- conf.copy!(base)
+ # If inheriting directly from Hanami::View within an Hanami app, configure
+ # the view for the application
+ if subclass.superclass == View && (provider = application_provider(subclass))
+ subclass.include ApplicationView.new(provider)
+ end
end
- # Load the framework
- #
- # @since 0.1.0
- # @api private
- def self.load!
- configuration.load!
+ def self.application_provider(subclass)
+ if Hanami.respond_to?(:application?) && Hanami.application?
+ Hanami.application.component_provider(subclass)
+ end
end
+ private_class_method :application_provider
end
end