214 lines
7.0 KiB
Ruby
214 lines
7.0 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_relative "config"
|
|
require_relative "constants"
|
|
require_relative "slice"
|
|
require_relative "slice_name"
|
|
|
|
module Hanami
|
|
# The Hanami app is a singular slice tasked with managing the core components of the app and
|
|
# coordinating overall app boot.
|
|
#
|
|
# For smaller apps, the app may be the only slice present, whereas larger apps may consist of many
|
|
# slices, with the app reserved for holding a small number of shared components only.
|
|
#
|
|
# @see Slice
|
|
#
|
|
# @api public
|
|
# @since 2.0.0
|
|
class App < Slice
|
|
@_mutex = Mutex.new
|
|
|
|
# @api private
|
|
# @since 2.0.0
|
|
def self.inherited(subclass)
|
|
super
|
|
|
|
Hanami.app = subclass
|
|
|
|
subclass.extend(ClassMethods)
|
|
|
|
@_mutex.synchronize do
|
|
subclass.class_eval do
|
|
@config = Hanami::Config.new(app_name: slice_name, env: Hanami.env)
|
|
|
|
# Prepare the load path (based on the default root of `Dir.pwd`) as early as possible, so
|
|
# you can make a `require` inside the body of an `App` subclass, which may be useful for
|
|
# certain kinds of app configuration.
|
|
prepare_load_path
|
|
|
|
load_dotenv
|
|
end
|
|
end
|
|
end
|
|
|
|
# App class interface
|
|
module ClassMethods
|
|
# Returns the app's config.
|
|
#
|
|
# @return [Hanami::Config]
|
|
#
|
|
# @api public
|
|
# @since 2.0.0
|
|
attr_reader :config
|
|
|
|
# Returns the app's {SliceName}.
|
|
#
|
|
# @return [Hanami::SliceName]
|
|
#
|
|
# @see Slice::ClassMethods#slice_name
|
|
#
|
|
# @api public
|
|
# @since 2.0.0
|
|
def app_name
|
|
slice_name
|
|
end
|
|
|
|
# Prepares the $LOAD_PATH based on the app's configured root, prepending the `lib/` directory
|
|
# if it exists. If the lib directory is already added, this will do nothing.
|
|
#
|
|
# In ordinary circumstances, you should never have to call this method: this method is called
|
|
# immediately upon subclassing {Hanami::App}, as a convenicence to put lib/ (under the default
|
|
# root of `Dir.pwd`) on the load path automatically. This is helpful if you need to require
|
|
# files inside the subclass body for performing certain app configuration steps.
|
|
#
|
|
# If you change your app's `config.root` and you need to require files from its `lib/`
|
|
# directory within your {App} subclass body, you should call {.prepare_load_path} explicitly
|
|
# after setting the new root.
|
|
#
|
|
# Otherwise, this method is called again as part of the app {.prepare} step, so if you've
|
|
# changed your app's root and do _not_ need to require files within your {App} subclass body,
|
|
# then you don't need to call this method.
|
|
#
|
|
# @example
|
|
# module MyApp
|
|
# class App < Hanami::App
|
|
# config.root = Pathname(__dir__).join("../src")
|
|
# prepare_load_path
|
|
#
|
|
# # You can make requires for your files here
|
|
# end
|
|
# end
|
|
#
|
|
# @return [self]
|
|
#
|
|
# @api public
|
|
# @since 2.0.0
|
|
def prepare_load_path
|
|
if (lib_path = root.join(LIB_DIR)).directory?
|
|
path = lib_path.realpath.to_s
|
|
$LOAD_PATH.prepend(path) unless $LOAD_PATH.include?(path)
|
|
end
|
|
|
|
self
|
|
end
|
|
|
|
private
|
|
|
|
# Uses [dotenv](https://github.com/bkeepers/dotenv) (if available) to populate `ENV` from
|
|
# various `.env` files.
|
|
#
|
|
# For a given `HANAMI_ENV` environment, the `.env` files are looked up in the following order:
|
|
#
|
|
# - .env.{environment}.local
|
|
# - .env.local (unless the environment is `test`)
|
|
# - .env.{environment}
|
|
# - .env
|
|
#
|
|
# If dotenv is unavailable, the method exits and does nothing.
|
|
def load_dotenv
|
|
return unless Hanami.bundled?("dotenv")
|
|
|
|
hanami_env = Hanami.env
|
|
dotenv_files = [
|
|
".env.#{hanami_env}.local",
|
|
(".env.local" unless hanami_env == :test),
|
|
".env.#{hanami_env}",
|
|
".env"
|
|
].compact
|
|
|
|
require "dotenv"
|
|
Dotenv.load(*dotenv_files)
|
|
end
|
|
|
|
def prepare_all
|
|
prepare_load_path
|
|
|
|
# Make app-wide notifications available as early as possible
|
|
container.use(:notifications)
|
|
|
|
# Ensure all basic slice preparation is complete before we make adjustments below (which
|
|
# rely on the basic prepare steps having already run)
|
|
super
|
|
|
|
# Run specific prepare steps for the app slice. Note also that some standard steps have been
|
|
# skipped via the empty method overrides below.
|
|
prepare_app_component_dirs
|
|
prepare_app_providers
|
|
end
|
|
|
|
# Skip standard slice prepare steps that do not apply to the app
|
|
def prepare_container_component_dirs; end
|
|
def prepare_container_imports; end
|
|
|
|
# rubocop:disable Metrics/AbcSize
|
|
|
|
def prepare_app_component_dirs
|
|
# Component files in both `app/` and `app/lib/` define classes in the
|
|
# app's namespace
|
|
|
|
if root.join(APP_DIR, LIB_DIR).directory?
|
|
container.config.component_dirs.add(File.join(APP_DIR, LIB_DIR)) do |dir|
|
|
dir.namespaces.add_root(key: nil, const: app_name.name)
|
|
end
|
|
end
|
|
|
|
# When auto-registering components in app/, ignore files in `app/lib/` (these will be
|
|
# auto-registered as above), as well as the configured no_auto_register_paths
|
|
no_auto_register_paths = ([LIB_DIR] + config.no_auto_register_paths)
|
|
.map { |path|
|
|
path.end_with?(File::SEPARATOR) ? path : "#{path}#{File::SEPARATOR}"
|
|
}
|
|
|
|
if root.join(APP_DIR).directory?
|
|
container.config.component_dirs.add(APP_DIR) do |dir|
|
|
dir.namespaces.add_root(key: nil, const: app_name.name)
|
|
dir.auto_register = -> component {
|
|
relative_path = component.file_path.relative_path_from(root.join(APP_DIR)).to_s
|
|
!relative_path.start_with?(*no_auto_register_paths)
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
def prepare_app_providers
|
|
require_relative "providers/inflector"
|
|
register_provider(:inflector, source: Hanami::Providers::Inflector)
|
|
|
|
# Allow logger to be replaced by users with a manual provider, for advanced cases
|
|
unless container.providers.find_and_load_provider(:logger)
|
|
require_relative "providers/logger"
|
|
register_provider(:logger, source: Hanami::Providers::Logger)
|
|
end
|
|
|
|
require_relative "providers/rack"
|
|
register_provider(:rack, source: Hanami::Providers::Rack, namespace: true)
|
|
end
|
|
|
|
def prepare_autoloader
|
|
# Component dirs are automatically pushed to the autoloader by dry-system's zeitwerk plugin.
|
|
# This method adds other dirs that are not otherwise configured as component dirs.
|
|
|
|
# Autoload classes from `lib/[app_namespace]/`
|
|
if root.join(LIB_DIR, app_name.name).directory?
|
|
autoloader.push_dir(root.join(LIB_DIR, app_name.name), namespace: namespace)
|
|
end
|
|
|
|
autoloader.setup
|
|
end
|
|
|
|
# rubocop:enable Metrics/AbcSize
|
|
end
|
|
end
|
|
end
|