rackal 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +17 -0
- data/README.md +71 -0
- data/lib/rackal.rb +46 -0
- data/lib/rackal/internal/application.rb +99 -0
- data/lib/rackal/internal/configuration_file.rb +24 -0
- data/lib/rackal/internal/database_configuration.rb +39 -0
- data/lib/rackal/internal/protection.rb +18 -0
- data/lib/rackal/internal/rack_environment.rb +53 -0
- data/lib/rackal/internal/yaml_file.rb +17 -0
- metadata +54 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0ead58575158b4ee56a407d2dc1efc3b9ca7dbe00fef20cfd2f6151fbc1a6585
|
4
|
+
data.tar.gz: 2b325e4277a93d86795c280acd6e3d83b4ec8090322e2f198889379d5e6e9244
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b407bd38f1d6e5dac470a165c8111b022150b501acd0f1b59b40c9a71216a7650888295aed80a8969e2206fec9e76b6f33fbf439c76e626eda430cd8caffcdba
|
7
|
+
data.tar.gz: 53a49cea9ac29e8bd342867ae1714f72acdf5a3cf457b6d7d63a9962bbad9a5944bb186e9f9b627ae2c34e7c9950f3c7871aca2511d3df724f81bb34468defd4
|
data/LICENSE
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Copyright (c) 2021 Richard Newman
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
4
|
+
software and associated documentation files (the "Software"), to deal in the Software
|
5
|
+
without restriction, including without limitation the rights to use, copy, modify, merge,
|
6
|
+
publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
7
|
+
persons to whom the Software is furnished to do so, subject to the following conditions:
|
8
|
+
|
9
|
+
The above copyright notice and this permission notice shall be included in all copies or
|
10
|
+
substantial portions of the Software.
|
11
|
+
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
13
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
14
|
+
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
15
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
16
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
17
|
+
OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# Rackal
|
2
|
+
|
3
|
+
The `rackal` gem provides helpers in configuring directly configuring Rack.
|
4
|
+
|
5
|
+
The main intent is abstract away some common boilerplate and reduce hardcoded details in
|
6
|
+
application configuration, especially in `config.ru`, in a way that still allows some
|
7
|
+
direct control of how it is applied.
|
8
|
+
|
9
|
+
Rackal was originally developed to simplify set up of Roda apps but is intended to not
|
10
|
+
assume or depend on Roda. As of this writing, no testing with Sinatra or bare Rack apps has
|
11
|
+
been performed, so milage may vary. This gem is still considered experimental.
|
12
|
+
|
13
|
+
## Features
|
14
|
+
* Environment aware: "production", "staging", "test", and "development" are recognized (based on ENV['RACK_ENV'])
|
15
|
+
* Database configuration: easy access to database configuration (based on YAML)
|
16
|
+
* Application metadata: easy access to application details (trivial YAML configuration)
|
17
|
+
* Application protection: easy use of Refrigerator gem if used
|
18
|
+
|
19
|
+
## Example `config.ru`
|
20
|
+
```
|
21
|
+
require 'rackal'
|
22
|
+
|
23
|
+
# support logging
|
24
|
+
if Rackal.environment.unprotected? # by default: "development" or "test" is unprotected
|
25
|
+
require 'logger'
|
26
|
+
logger = Logger.new($stdout)
|
27
|
+
end
|
28
|
+
|
29
|
+
require 'roda'
|
30
|
+
|
31
|
+
Gem.ruby
|
32
|
+
|
33
|
+
# these output lines demonstrate some use
|
34
|
+
puts "Starting #{Rackal.application.name} in #{Rackal.environment.env} environment."
|
35
|
+
#=> Starting MyApp in production environment.
|
36
|
+
puts "(root directory is #{Rackal.application.root})"
|
37
|
+
#=> (root directory is /home/someuser/src/my_app)
|
38
|
+
|
39
|
+
run(Rackal.application.main_class.freeze.app) # main_class is the driving class of the application
|
40
|
+
|
41
|
+
Rackal.protect! # applies Refrigerator if gem is present in production settings
|
42
|
+
```
|
43
|
+
|
44
|
+
## Performance
|
45
|
+
Given that one key feature of Roda is its performance and memory footprint, care was taken
|
46
|
+
to avoid interferring with that. Any cost is constrained as much as possible to only
|
47
|
+
application startup:
|
48
|
+
* Rackal is expected to add minimal time to application startup.
|
49
|
+
* Rackal is expected to consume marginally additional memory in the application for holding its
|
50
|
+
state once the application has started.
|
51
|
+
|
52
|
+
Rigorous performance testing has not yet been done to ensure those claims.
|
53
|
+
|
54
|
+
## Contributing
|
55
|
+
Since a Gemfile is used for testing and development conveniences without polluting the
|
56
|
+
gem itself, be sure to `bundle install`. RSpec, simplecov, and rubocop are assumed, but
|
57
|
+
only RSpec itself is required.
|
58
|
+
|
59
|
+
The provided RSpec tests have strong coverage. Presently focus on mutation testing is
|
60
|
+
ongoing.
|
61
|
+
|
62
|
+
Further development is invited to better support Roda applications, extensions and
|
63
|
+
generalization for non-Roda Rack applications, and performance improvements.
|
64
|
+
|
65
|
+
## Acknowledgements
|
66
|
+
Jeremy Evans's `roda-sequel-stack` [repository](https://github.com/jeremyevans/roda-sequel-stack) was used as the original reference for boilerplate.
|
67
|
+
|
68
|
+
Some application metadata concepts were inspired by Rails, with a much lighter approach taken.
|
69
|
+
|
70
|
+
## License
|
71
|
+
Rackal is released under the [MIT License](https://opensource.org/licenses/MIT).
|
data/lib/rackal.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rackal/internal/application'
|
2
|
+
require 'rackal/internal/database_configuration'
|
3
|
+
require 'rackal/internal/rack_environment'
|
4
|
+
require 'rackal/internal/protection'
|
5
|
+
|
6
|
+
module Rackal
|
7
|
+
# Retrieves current Rack environment with convenience methods
|
8
|
+
#
|
9
|
+
# @param [Hash] options
|
10
|
+
# @option options [true, false] :reset If true, resets cache and ensure fresh retrieval (defaults to: false)
|
11
|
+
# @return [Object] an object
|
12
|
+
def self.environment(options = {})
|
13
|
+
reload = options.delete(:reload) || false
|
14
|
+
@environment = nil if reload
|
15
|
+
|
16
|
+
@environment = Internal::RackEnvironment.new(options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.database_parameters(options = {})
|
20
|
+
reload = options.delete(:reload) || false
|
21
|
+
@database_parameters = nil if reload
|
22
|
+
|
23
|
+
@database_parameters ||= Internal::DatabaseConfiguration.parameters
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.env(key, options = {})
|
27
|
+
reload = options.delete(:reload) || false
|
28
|
+
@env = nil if reload
|
29
|
+
|
30
|
+
@env ||= {}
|
31
|
+
|
32
|
+
@env[key] || (@env[key] = ENV.fetch(key.to_s, nil))
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.application
|
36
|
+
@application ||= Internal::Application.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.protect!
|
40
|
+
Internal::Protection.apply
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.root
|
44
|
+
@root ||= application.root
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require_relative 'configuration_file'
|
2
|
+
|
3
|
+
module Rackal
|
4
|
+
module Internal
|
5
|
+
class Application
|
6
|
+
include ConfigurationFile
|
7
|
+
|
8
|
+
# TODO: -- drop this?!
|
9
|
+
def self.root
|
10
|
+
new.root
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@config = parse
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
@config[:app_main]
|
19
|
+
end
|
20
|
+
|
21
|
+
# only supports bare class names (not under a namespace)
|
22
|
+
def main_class
|
23
|
+
raise NameError unless name
|
24
|
+
|
25
|
+
@main_class ||= Object.const_get(name)
|
26
|
+
end
|
27
|
+
|
28
|
+
# lightly inspired by Rails implementation in
|
29
|
+
# railties/lib/rails/engine.rb#find_root_with_flag
|
30
|
+
def root
|
31
|
+
return @root if defined?(@root) && @root
|
32
|
+
|
33
|
+
paths = potential_root_paths
|
34
|
+
|
35
|
+
# 1) preference is to find "config.ru" if possible
|
36
|
+
# 2) if not (1), try to find main app
|
37
|
+
# 3) if not (1) or (2) look for nearest lib/ directory
|
38
|
+
[:configru, :app, :lib].each do |key|
|
39
|
+
info = paths.detect { |path| path[key] }
|
40
|
+
@root = info&.fetch(:dirname, nil)
|
41
|
+
return @root if @root
|
42
|
+
end
|
43
|
+
|
44
|
+
# give up if we can't find it
|
45
|
+
@root = Dir.pwd
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def parse
|
51
|
+
@parse = read_configuration('rackal') { |content| content.fetch('rackal') }
|
52
|
+
end
|
53
|
+
|
54
|
+
# only works in linux style OS (assumes path delimiter "/")
|
55
|
+
def potential_root_paths
|
56
|
+
appname = main_class_rb_filename
|
57
|
+
|
58
|
+
caller_locations.lazy.map { |location| assess_location(location, appname) }
|
59
|
+
# assess_location
|
60
|
+
# path = location.absolute_path || location.path
|
61
|
+
# dirname = File.dirname(path)
|
62
|
+
|
63
|
+
# {
|
64
|
+
# dirname: dirname,
|
65
|
+
# configru: File.directory?(dirname) && File.exist?("#{dirname}/config.ru"),
|
66
|
+
# app: appname && (File.directory?(dirname) && File.exist?("#{dirname}/#{appname}")),
|
67
|
+
# # configru: File.directory?(path) && File.exist?("#{path}/config.ru"),
|
68
|
+
# # app: appname && (File.directory?(path) && File.exist?("#{path}/#{appname}")),
|
69
|
+
# lib: dirname.match?(/.*\/lib\/\z/)
|
70
|
+
# }
|
71
|
+
# end
|
72
|
+
end
|
73
|
+
|
74
|
+
def assess_location(location, appname)
|
75
|
+
path = location.absolute_path || location.path
|
76
|
+
dirname = File.dirname(path)
|
77
|
+
|
78
|
+
{
|
79
|
+
dirname: dirname,
|
80
|
+
configru: File.directory?(dirname) && File.exist?("#{dirname}/config.ru"),
|
81
|
+
app: appname && (File.directory?(dirname) && File.exist?("#{dirname}/#{appname}")),
|
82
|
+
# configru: File.directory?(path) && File.exist?("#{path}/config.ru"),
|
83
|
+
# app: appname && (File.directory?(path) && File.exist?("#{path}/#{appname}")),
|
84
|
+
lib: dirname.match?(/.*\/lib\/\z/)
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
def main_class_rb_filename
|
89
|
+
return nil unless name
|
90
|
+
|
91
|
+
# stolen from Rails ActiveSupport (but simpler)
|
92
|
+
word = name.gsub(/([A-Z\d]+)(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) do
|
93
|
+
(Regexp.last_match(1) || Regexp.last_match(2)) << '_'
|
94
|
+
end
|
95
|
+
"#{word.tr('-', '_').downcase}.rb"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative 'yaml_file'
|
2
|
+
|
3
|
+
module Rackal
|
4
|
+
module Internal
|
5
|
+
module ConfigurationFile
|
6
|
+
include YamlFile
|
7
|
+
|
8
|
+
def configuration_directory
|
9
|
+
'config'
|
10
|
+
end
|
11
|
+
|
12
|
+
def read_configuration(yaml_filename)
|
13
|
+
filename = yaml_filename&.strip || ''
|
14
|
+
if filename.empty?
|
15
|
+
raise ArgumentError, "must apply a YAML filename within #{configuration_directory}"
|
16
|
+
end
|
17
|
+
|
18
|
+
filepath = "#{configuration_directory}/#{yaml_filename}.yml"
|
19
|
+
content = yaml_content(filepath)
|
20
|
+
(block_given? ? yield(content) : content)&.transform_keys(&:to_sym)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative 'configuration_file'
|
2
|
+
|
3
|
+
module Rackal
|
4
|
+
module Internal
|
5
|
+
class DatabaseConfiguration
|
6
|
+
include ConfigurationFile
|
7
|
+
|
8
|
+
attr_reader :parameters
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def parameters
|
12
|
+
new.parameters
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@parameters = parse
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def parse
|
23
|
+
requested_key = Rackal.environment.env.to_s
|
24
|
+
used_key = requested_key
|
25
|
+
|
26
|
+
result = read_configuration('database') do |content|
|
27
|
+
content.fetch(requested_key) { |_| content.fetch(used_key = 'default') }
|
28
|
+
end
|
29
|
+
|
30
|
+
result[:configuration_key] = {
|
31
|
+
requested: requested_key,
|
32
|
+
used: used_key
|
33
|
+
}
|
34
|
+
|
35
|
+
result
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Rackal
|
2
|
+
module Internal
|
3
|
+
module Protection
|
4
|
+
def self.apply
|
5
|
+
return false if Rackal.environment.unprotected?
|
6
|
+
|
7
|
+
begin
|
8
|
+
require 'refrigerator'
|
9
|
+
rescue LoadError
|
10
|
+
false
|
11
|
+
else
|
12
|
+
Refrigerator.freeze_core
|
13
|
+
true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Rackal
|
2
|
+
module Internal
|
3
|
+
class RackEnvironment
|
4
|
+
attr_reader :env
|
5
|
+
|
6
|
+
# @api private
|
7
|
+
def initialize(options = {})
|
8
|
+
@env = (ENV['RACK_ENV'] || 'development').to_sym
|
9
|
+
|
10
|
+
protect_test = options&.fetch(:protect_test, false) || false
|
11
|
+
@protect_test = protect_test ? true : false # force boolean
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return Array list of environments recognized as supported by RackEnvironment
|
15
|
+
def self.supported
|
16
|
+
[:production, :staging, :test, :development]
|
17
|
+
end
|
18
|
+
|
19
|
+
supported.each do |supported_name|
|
20
|
+
# @!macro [attach] supported_name
|
21
|
+
# @method $1?()
|
22
|
+
# @return Boolean true if current environment is $1
|
23
|
+
define_method("#{supported_name}?") { env == supported_name }
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return Boolean true if current environment is supported
|
27
|
+
def supported?
|
28
|
+
@supported ||= self.class.supported.include? env
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return Boolean true if current environment is not supported
|
32
|
+
def unsupported?
|
33
|
+
!supported?
|
34
|
+
end
|
35
|
+
|
36
|
+
# return Array list environments treated as protected (e.g., :production)
|
37
|
+
def protected
|
38
|
+
@protected ||= self.class.supported -
|
39
|
+
(@protect_test ? [:development] : [:development, :test])
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return Boolean true if current environment is to be treated as protected
|
43
|
+
def protected?
|
44
|
+
@is_protected ||= protected.include? env
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return Boolean true if current environment is not to be treated as protected
|
48
|
+
def unprotected?
|
49
|
+
@is_unprotected ||= !protected?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Rackal
|
5
|
+
module Internal
|
6
|
+
module YamlFile
|
7
|
+
def yaml_content(filepath)
|
8
|
+
raise ArgumentError unless filepath.is_a?(String)
|
9
|
+
|
10
|
+
YAML.safe_load(
|
11
|
+
ERB.new(File.read(filepath)).result,
|
12
|
+
aliases: true
|
13
|
+
)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rackal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Richard Newman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-07-10 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: 'Rack application helpers
|
14
|
+
|
15
|
+
'
|
16
|
+
email:
|
17
|
+
- richard@newmanworks.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- LICENSE
|
23
|
+
- README.md
|
24
|
+
- lib/rackal.rb
|
25
|
+
- lib/rackal/internal/application.rb
|
26
|
+
- lib/rackal/internal/configuration_file.rb
|
27
|
+
- lib/rackal/internal/database_configuration.rb
|
28
|
+
- lib/rackal/internal/protection.rb
|
29
|
+
- lib/rackal/internal/rack_environment.rb
|
30
|
+
- lib/rackal/internal/yaml_file.rb
|
31
|
+
homepage: https://rubygems.org/gems/rackal
|
32
|
+
licenses:
|
33
|
+
- MIT
|
34
|
+
metadata: {}
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '2.6'
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubygems_version: 3.1.4
|
51
|
+
signing_key:
|
52
|
+
specification_version: 4
|
53
|
+
summary: Rackal
|
54
|
+
test_files: []
|