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