250 lines
5.4 KiB
Ruby
250 lines
5.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "pathname"
|
|
require "zeitwerk"
|
|
require_relative "hanami/constants"
|
|
|
|
# A complete web framework for Ruby
|
|
#
|
|
# @since 0.1.0
|
|
#
|
|
# @see http://hanamirb.org
|
|
module Hanami
|
|
@_mutex = Mutex.new
|
|
@_bundled = {}
|
|
|
|
# @api private
|
|
# @since 2.0.0
|
|
def self.loader
|
|
@loader ||= Zeitwerk::Loader.for_gem.tap do |loader|
|
|
loader.ignore(
|
|
"#{loader.dirs.first}/hanami/{constants,boot,errors,prepare,rake_tasks,setup}.rb"
|
|
)
|
|
end
|
|
end
|
|
|
|
# Finds and loads the Hanami app file (`config/app.rb`).
|
|
#
|
|
# Raises an exception if the app file cannot be found.
|
|
#
|
|
# @return [app] the loaded app class
|
|
#
|
|
# @api public
|
|
# @since 2.0.0
|
|
def self.setup(raise_exception: true)
|
|
return app if app?
|
|
|
|
app_path = self.app_path
|
|
|
|
if app_path
|
|
prepare_load_path
|
|
require(app_path.to_s)
|
|
app
|
|
elsif raise_exception
|
|
raise(
|
|
AppLoadError,
|
|
"Could not locate your Hanami app file.\n\n" \
|
|
"Your app file should be at `config/app.rb` in your project's root directory."
|
|
)
|
|
end
|
|
end
|
|
|
|
# Prepare the load path as early as possible (based on the default root inferred from the location
|
|
# of `config/app.rb`), so `require` can work at the top of `config/app.rb`. This may be useful
|
|
# when external classes are needed for configuring certain aspects of the app.
|
|
#
|
|
# @api private
|
|
# @since 2.0.0
|
|
private_class_method def self.prepare_load_path
|
|
lib_path = app_path&.join("..", "..", LIB_DIR)
|
|
|
|
if lib_path&.directory?
|
|
path = lib_path.realpath.to_s
|
|
$LOAD_PATH.prepend(path) unless $LOAD_PATH.include?(path)
|
|
end
|
|
|
|
lib_path
|
|
end
|
|
|
|
# Returns the Hamami app class.
|
|
#
|
|
# To ensure your Hanami app is loaded, run {.setup} (or `require "hanami/setup"`) first.
|
|
#
|
|
# @return [Hanami::App] the app class
|
|
#
|
|
# @raise [AppLoadError] if the app has not been loaded
|
|
#
|
|
# @see .setup
|
|
#
|
|
# @api public
|
|
# @since 2.0.0
|
|
def self.app
|
|
@_mutex.synchronize do
|
|
unless defined?(@_app)
|
|
raise AppLoadError,
|
|
"Hanami.app is not yet configured. " \
|
|
"You may need to `require \"hanami/setup\"` to load your config/app.rb file."
|
|
end
|
|
|
|
@_app
|
|
end
|
|
end
|
|
|
|
# Returns true if the Hanami app class has been loaded.
|
|
#
|
|
# @return [Boolean]
|
|
#
|
|
# @api public
|
|
# @since 2.0.0
|
|
def self.app?
|
|
instance_variable_defined?(:@_app)
|
|
end
|
|
|
|
# @api private
|
|
# @since 2.0.0
|
|
def self.app=(klass)
|
|
@_mutex.synchronize do
|
|
if instance_variable_defined?(:@_app)
|
|
raise AppLoadError, "Hanami.app is already configured."
|
|
end
|
|
|
|
@_app = klass unless klass.name.nil?
|
|
end
|
|
end
|
|
|
|
# Finds and returns the absolute path for the Hanami app file (`config/app.rb`).
|
|
#
|
|
# Searches within the given directory, then searches upwards through parent directories until the
|
|
# app file can be found.
|
|
#
|
|
# @param dir [String, Pathname] The directory from which to start searching. Defaults to the
|
|
# current directory.
|
|
#
|
|
# @return [Pathname, nil] the app file path, or nil if not found.
|
|
#
|
|
# @api public
|
|
# @since 2.0.0
|
|
def self.app_path(dir = Dir.pwd)
|
|
dir = Pathname(dir).expand_path
|
|
path = dir.join(APP_PATH)
|
|
|
|
if path.file?
|
|
path
|
|
elsif !dir.root?
|
|
app_path(dir.parent)
|
|
end
|
|
end
|
|
|
|
# Returns the Hanami app environment as loaded from the `HANAMI_ENV` environment variable.
|
|
#
|
|
# @example
|
|
# Hanami.env # => :development
|
|
#
|
|
# @return [Symbol] the environment name
|
|
#
|
|
# @api public
|
|
# @since 2.0.0
|
|
def self.env
|
|
ENV.fetch("HANAMI_ENV") { ENV.fetch("RACK_ENV", "development") }.to_sym
|
|
end
|
|
|
|
# Returns true if {.env} matches any of the given names
|
|
#
|
|
# @example
|
|
# Hanami.env # => :development
|
|
# Hanami.env?(:development, :test) # => true
|
|
#
|
|
# @param names [Array<Symbol>] the environment names to check
|
|
#
|
|
# @return [Boolean]
|
|
#
|
|
# @api public
|
|
# @since 2.0.0
|
|
def self.env?(*names)
|
|
names.map(&:to_sym).include?(env)
|
|
end
|
|
|
|
# Returns the app's logger.
|
|
#
|
|
# Direct global access to the logger via this method is not recommended. Instead, consider
|
|
# accessing the logger via the app or slice container, in most cases as an dependency using the
|
|
# `Deps` mixin.
|
|
#
|
|
# @example
|
|
# # app/my_component.rb
|
|
#
|
|
# module MyApp
|
|
# class MyComponent
|
|
# include Deps["logger"]
|
|
#
|
|
# def some_method
|
|
# logger.info("hello")
|
|
# end
|
|
# end
|
|
# end
|
|
#
|
|
# @return [Dry::Logger::Dispatcher]
|
|
#
|
|
# @api public
|
|
# @since 1.0.0
|
|
def self.logger
|
|
app[:logger]
|
|
end
|
|
|
|
# Prepares the Hanami app.
|
|
#
|
|
# @see App::ClassMethods#prepare
|
|
#
|
|
# @api public
|
|
# @since 2.0.0
|
|
def self.prepare
|
|
app.prepare
|
|
end
|
|
|
|
# Boots the Hanami app.
|
|
#
|
|
# @see App::ClassMethods#boot
|
|
#
|
|
# @api public
|
|
# @since 2.0.0
|
|
def self.boot
|
|
app.boot
|
|
end
|
|
|
|
# Shuts down the Hanami app.
|
|
#
|
|
# @see App::ClassMethods#shutdown
|
|
#
|
|
# @api public
|
|
# @since 2.0.0
|
|
def self.shutdown
|
|
app.shutdown
|
|
end
|
|
|
|
# @api private
|
|
# @since 2.0.0
|
|
def self.bundled?(gem_name)
|
|
@_mutex.synchronize do
|
|
@_bundled[gem_name] ||= begin
|
|
gem(gem_name)
|
|
rescue Gem::LoadError
|
|
false
|
|
end
|
|
end
|
|
end
|
|
|
|
# Returns an array of bundler group names to be eagerly loaded by hanami-cli and other CLI
|
|
# extensions.
|
|
#
|
|
# @api private
|
|
# @since 2.0.0
|
|
def self.bundler_groups
|
|
[:plugins]
|
|
end
|
|
|
|
loader.setup
|
|
|
|
require_relative "hanami/errors"
|
|
require_relative "hanami/extensions"
|
|
end
|