486 lines
12 KiB
Ruby
486 lines
12 KiB
Ruby
require 'thread'
|
|
require 'pathname'
|
|
require 'dotenv'
|
|
require 'hanami/utils'
|
|
require 'hanami/utils/hash'
|
|
require 'hanami/hanamirc'
|
|
|
|
module Hanami
|
|
# Define and expose information about the Hanami environment.
|
|
#
|
|
# @since 0.1.0
|
|
# @api private
|
|
class Environment
|
|
# Standard Rack ENV key
|
|
#
|
|
# @since 0.1.0
|
|
# @api private
|
|
RACK_ENV = 'RACK_ENV'.freeze
|
|
|
|
# Standard Hanami ENV key
|
|
#
|
|
# @since 0.1.0
|
|
# @api private
|
|
HANAMI_ENV = 'HANAMI_ENV'.freeze
|
|
|
|
# Default Hanami environment
|
|
#
|
|
# @since 0.1.0
|
|
# @api private
|
|
DEFAULT_ENV = 'development'.freeze
|
|
|
|
# Production environment
|
|
#
|
|
# @since 0.6.0
|
|
# @api private
|
|
PRODUCTION_ENV = 'production'.freeze
|
|
|
|
# Rack production environment (aka deployment)
|
|
#
|
|
# @since 0.6.0
|
|
# @api private
|
|
RACK_ENV_DEPLOYMENT = 'deployment'.freeze
|
|
|
|
# Default `.env` file name
|
|
#
|
|
# @since 0.2.0
|
|
# @api private
|
|
DEFAULT_DOTENV = '.env'.freeze
|
|
|
|
# Default `.env` per environment file name
|
|
#
|
|
# @since 0.2.0
|
|
# @api private
|
|
DEFAULT_DOTENV_ENV = '.env.%s'.freeze
|
|
|
|
# Default configuration directory under application root
|
|
#
|
|
# @since 0.2.0
|
|
# @api private
|
|
DEFAULT_CONFIG = 'config'.freeze
|
|
|
|
# Standard Hanami host ENV key
|
|
#
|
|
# @since 0.1.0
|
|
# @api private
|
|
HANAMI_HOST = 'HANAMI_HOST'.freeze
|
|
|
|
# Default HTTP host
|
|
#
|
|
# @since 0.1.0
|
|
# @api private
|
|
DEFAULT_HOST = 'localhost'.freeze
|
|
|
|
# Default IP address listen
|
|
#
|
|
# @since 0.1.0
|
|
# @api private
|
|
LISTEN_ALL_HOST = '0.0.0.0'.freeze
|
|
|
|
# Standard Hanami port ENV key
|
|
#
|
|
# @since 0.1.0
|
|
# @api private
|
|
HANAMI_PORT = 'HANAMI_PORT'.freeze
|
|
|
|
# Default Hanami HTTP port
|
|
#
|
|
# @since 0.1.0
|
|
# @api private
|
|
DEFAULT_PORT = 2300
|
|
|
|
# Default Rack configuration file
|
|
#
|
|
# @since 0.2.0
|
|
# @api private
|
|
DEFAULT_RACKUP = 'config.ru'.freeze
|
|
|
|
# Default environment configuration file
|
|
#
|
|
# @since 0.2.0
|
|
# @api private
|
|
DEFAULT_ENVIRONMENT_CONFIG = 'environment'.freeze
|
|
|
|
# Code reloading per environment
|
|
#
|
|
# @since 0.2.0
|
|
# @api private
|
|
CODE_RELOADING = { 'development' => true }.freeze
|
|
|
|
# @since 0.4.0
|
|
# @api private
|
|
CONTAINER = 'container'.freeze
|
|
|
|
# @since 0.4.0
|
|
# @api private
|
|
CONTAINER_PATH = 'apps'.freeze
|
|
|
|
# @since 0.4.0
|
|
# @api private
|
|
APPLICATION = 'app'.freeze
|
|
|
|
# @since 0.4.0
|
|
# @api private
|
|
APPLICATION_PATH = 'app'.freeze
|
|
|
|
# @since 0.4.0
|
|
# @api private
|
|
SERVE_STATIC_ASSETS = 'SERVE_STATIC_ASSETS'.freeze
|
|
|
|
# @since 0.4.0
|
|
# @api private
|
|
SERVE_STATIC_ASSETS_ENABLED = 'true'.freeze
|
|
|
|
# Initialize a Hanami environment
|
|
#
|
|
# It accepts an optional set of configurations from the CLI commands.
|
|
# Those settings override the defaults defined by this object.
|
|
#
|
|
# When initialized, it sets standard `ENV` variables for Rack and Hanami,
|
|
# such as `RACK_ENV` and `HANAMI_ENV`.
|
|
#
|
|
# It also evaluates configuration from `.env` and `.env.<environment>`
|
|
# located under the config directory. All the settings in those files will
|
|
# be exported as `ENV` variables.
|
|
#
|
|
# The format of those `.env` files is compatible with `dotenv` and `foreman`
|
|
# gems.
|
|
#
|
|
# @param options [Hash] override default options for various environment
|
|
# attributes
|
|
#
|
|
# @return [Hanami::Environment] the environment
|
|
#
|
|
# @see Hanami::Commands::Console
|
|
# @see Hanami::Commands::Routes
|
|
# @see Hanami::Commands::Server
|
|
# @see Hanami::Environment#config
|
|
#
|
|
# @example Define ENV variables from .env
|
|
#
|
|
# # % tree .
|
|
# # .
|
|
# # # ...
|
|
# # ├── .env
|
|
# # └── .env.development
|
|
#
|
|
# # % cat .env
|
|
# # FOO="bar"
|
|
# # XYZ="yes"
|
|
#
|
|
# # % cat .env.development
|
|
# # FOO="ok"
|
|
#
|
|
# require 'hanami/environment'
|
|
#
|
|
# env = Hanami::Environment.new
|
|
# env.environment # => "development"
|
|
#
|
|
# # Framework defined ENV vars
|
|
# ENV['HANAMI_ENV'] # => "development"
|
|
# ENV['RACK_ENV'] # => "development"
|
|
#
|
|
# ENV['HANAMI_HOST'] # => "localhost"
|
|
# ENV['HANAMI_PORT'] # => "2300"
|
|
#
|
|
# # User defined ENV vars
|
|
# ENV['FOO'] # => "ok"
|
|
# ENV['XYZ'] # => "yes"
|
|
#
|
|
# # Hanami::Environment evaluates `.env` first as master configuration.
|
|
# # Then it evaluates `.env.development` because the current environment
|
|
# # is "development". The settings defined in this last file override
|
|
# # the one defined in the parent (eg `FOO` is overwritten). All the
|
|
# # other settings (eg `XYZ`) will be left untouched.
|
|
def initialize(options = {})
|
|
@options = Hanami::Hanamirc.new(root).options
|
|
@options.merge! Utils::Hash.new(options.clone).symbolize!
|
|
@mutex = Mutex.new
|
|
@mutex.synchronize { set_env_vars! }
|
|
end
|
|
|
|
# The current environment
|
|
#
|
|
# In order to decide the value, it looks up to the following `ENV` vars:
|
|
#
|
|
# * HANAMI_ENV
|
|
# * RACK_ENV
|
|
#
|
|
# If those are missing it falls back to the defalt one: `"development"`.
|
|
#
|
|
# Rack environment `"deployment"` is translated to Hanami `"production"`.
|
|
#
|
|
# @return [String] the current environment
|
|
#
|
|
# @since 0.1.0
|
|
#
|
|
# @see Hanami::Environment::DEFAULT_ENV
|
|
def environment
|
|
@environment ||= ENV[HANAMI_ENV] || rack_env || DEFAULT_ENV
|
|
end
|
|
|
|
# @since 0.3.1
|
|
#
|
|
# @see Hanami.env?(name)
|
|
def environment?(*names)
|
|
names.map(&:to_s).include?(environment)
|
|
end
|
|
|
|
# A set of Bundler groups
|
|
#
|
|
# @return [Array] A set of groups
|
|
#
|
|
# @since 0.2.0
|
|
# @api private
|
|
#
|
|
# @see http://bundler.io/v1.7/groups.html
|
|
def bundler_groups
|
|
[:default, environment]
|
|
end
|
|
|
|
# Application's root
|
|
#
|
|
# It defaults to the current working directory.
|
|
# Hanami assumes that all the commands are executed from there.
|
|
#
|
|
# @return [Pathname] application's root
|
|
#
|
|
# @since 0.2.0
|
|
def root
|
|
@root ||= Pathname.new(Dir.pwd)
|
|
end
|
|
|
|
# Application's config directory
|
|
#
|
|
# It's the application where all the configurations are stored.
|
|
#
|
|
# In order to decide the value, it looks up the following sources:
|
|
#
|
|
# * CLI option `config`
|
|
#
|
|
# If those are missing it falls back to the default one: `"config/"`.
|
|
#
|
|
# When a relative path is given via CLI option, it assumes to be located
|
|
# under application's root. If absolute path, it will be used as it is.
|
|
#
|
|
# @return [Pathname] the config directory
|
|
#
|
|
# @since 0.2.0
|
|
#
|
|
# @see Hanami::Environment::DEFAULT_CONFIG
|
|
# @see Hanami::Environment#root
|
|
def config
|
|
@config ||= root.join(@options.fetch(:config) { DEFAULT_CONFIG })
|
|
end
|
|
|
|
# The HTTP host name
|
|
#
|
|
# In order to decide the value, it looks up the following sources:
|
|
#
|
|
# * CLI option `host`
|
|
# * HANAMI_HOST ENV var
|
|
#
|
|
# If those are missing it falls back to the following defaults:
|
|
#
|
|
# * `"localhost"` for development
|
|
# * `"0.0.0.0"` for all the other environments
|
|
#
|
|
# @return [String] the HTTP host name
|
|
#
|
|
# @since 0.1.0
|
|
#
|
|
# @see Hanami::Environment::DEFAULT_HOST
|
|
# @see Hanami::Environment::LISTEN_ALL_HOST
|
|
def host
|
|
@host ||= @options.fetch(:host) {
|
|
ENV[HANAMI_HOST] || default_host
|
|
}
|
|
end
|
|
|
|
# The HTTP port
|
|
#
|
|
# In order to decide the value, it looks up the following sources:
|
|
#
|
|
# * CLI option `port`
|
|
# * HANAMI_PORT ENV var
|
|
#
|
|
# If those are missing it falls back to the default one: `2300`.
|
|
#
|
|
# @return [Integer] the default port
|
|
#
|
|
# @since 0.1.0
|
|
#
|
|
# @see Hanami::Environment::DEFAULT_PORT
|
|
def port
|
|
@port ||= @options.fetch(:port) { ENV[HANAMI_PORT] || DEFAULT_PORT }.to_i
|
|
end
|
|
|
|
# Path to the Rack configuration file
|
|
#
|
|
# In order to decide the value, it looks up the following sources:
|
|
#
|
|
# * CLI option `rackup`
|
|
#
|
|
# If those are missing it falls back to the default one: `"config.ru"`.
|
|
#
|
|
# When a relative path is given via CLI option, it assumes to be located
|
|
# under application's root. If absolute path, it will be used as it is.
|
|
#
|
|
# @return [Pathname] path to the Rack configuration file
|
|
#
|
|
# @since 0.2.0
|
|
def rackup
|
|
root.join(@options.fetch(:rackup) { DEFAULT_RACKUP })
|
|
end
|
|
|
|
# Path to environment configuration file.
|
|
#
|
|
# In order to decide the value, it looks up the following sources:
|
|
#
|
|
# * CLI option `environment`
|
|
#
|
|
# If those are missing it falls back to the default one:
|
|
# `"config/environment.rb"`.
|
|
#
|
|
# When a relative path is given via CLI option, it assumes to be located
|
|
# under application's root. If absolute path, it will be used as it is.
|
|
#
|
|
# @return [Pathname] path to applications
|
|
#
|
|
# @since 0.1.0
|
|
#
|
|
# @see Hanami::Environment::DEFAULT_ENVIRONMENT_CONFIG
|
|
def env_config
|
|
root.join(@options.fetch(:environment) { config.join(DEFAULT_ENVIRONMENT_CONFIG) })
|
|
end
|
|
|
|
# Require application environment
|
|
#
|
|
# Eg <tt>require "config/environment"</tt>.
|
|
#
|
|
# @since 0.4.0
|
|
# @api private
|
|
def require_application_environment
|
|
require env_config.to_s
|
|
end
|
|
|
|
# Determine if activate code reloading for the current environment while
|
|
# running the server.
|
|
#
|
|
# In order to decide the value, it looks up the following sources:
|
|
#
|
|
# * CLI option `code_reloading`
|
|
#
|
|
# If those are missing it falls back to the following defaults:
|
|
#
|
|
# * true for development
|
|
# * false for all the other environments
|
|
#
|
|
# @return [TrueClass,FalseClass] the result of the check
|
|
#
|
|
# @since 0.2.0
|
|
#
|
|
# @see Hanami::Commands::Server
|
|
# @see Hanami::Environment::CODE_RELOADING
|
|
def code_reloading?
|
|
# JRuby doesn't implement fork that's why shotgun cannot be used.
|
|
if Utils.jruby?
|
|
puts "JRuby doesn't support code reloading."
|
|
false
|
|
else
|
|
@options.fetch(:code_reloading) { !!CODE_RELOADING[environment] }
|
|
end
|
|
end
|
|
|
|
# @since 0.4.0
|
|
# @api private
|
|
def architecture
|
|
@options.fetch(:architecture) {
|
|
puts "Cannot recognize Hanami architecture, please check `.hanamirc'"
|
|
exit 1
|
|
}
|
|
end
|
|
|
|
# @since 0.4.0
|
|
# @api private
|
|
def container?
|
|
architecture == CONTAINER
|
|
end
|
|
|
|
# @since 0.6.0
|
|
# @api private
|
|
def serve_static_assets?
|
|
SERVE_STATIC_ASSETS_ENABLED == ENV[SERVE_STATIC_ASSETS]
|
|
end
|
|
|
|
# @since 0.4.0
|
|
# @api private
|
|
def apps_path
|
|
@options.fetch(:path) {
|
|
case architecture
|
|
when CONTAINER then CONTAINER_PATH
|
|
when APPLICATION then APPLICATION_PATH
|
|
end
|
|
}
|
|
end
|
|
|
|
# Serialize the most relevant settings into a Hash
|
|
#
|
|
# @return [Hanami::Utils::Hash]
|
|
#
|
|
# @since 0.1.0
|
|
# @api private
|
|
def to_options
|
|
@options.merge(
|
|
environment: environment,
|
|
env_config: env_config,
|
|
apps_path: apps_path,
|
|
rackup: rackup,
|
|
host: host,
|
|
port: port
|
|
)
|
|
end
|
|
|
|
private
|
|
|
|
# @since 0.1.0
|
|
# @api private
|
|
def set_env_vars!
|
|
set_application_env_vars!
|
|
set_hanami_env_vars!
|
|
end
|
|
|
|
# @since 0.2.0
|
|
# @api private
|
|
def set_hanami_env_vars!
|
|
ENV[HANAMI_ENV] = ENV[RACK_ENV] = environment
|
|
ENV[HANAMI_HOST] = host
|
|
ENV[HANAMI_PORT] = port.to_s
|
|
end
|
|
|
|
# @since 0.2.0
|
|
# @api private
|
|
def set_application_env_vars!
|
|
Dotenv.load root.join(DEFAULT_DOTENV)
|
|
Dotenv.overload root.join(DEFAULT_DOTENV_ENV % environment)
|
|
end
|
|
|
|
# @since 0.1.0
|
|
# @api private
|
|
def default_host
|
|
environment == DEFAULT_ENV ? DEFAULT_HOST : LISTEN_ALL_HOST
|
|
end
|
|
|
|
# @since 0.6.0
|
|
# @api private
|
|
def rack_env
|
|
case ENV[RACK_ENV]
|
|
when RACK_ENV_DEPLOYMENT
|
|
PRODUCTION_ENV
|
|
else
|
|
ENV[RACK_ENV]
|
|
end
|
|
end
|
|
end
|
|
end
|