kangaru 0.1.0
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/.rspec +2 -0
- data/.rubocop.yml +75 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +139 -0
- data/LICENSE.txt +21 -0
- data/README.md +49 -0
- data/Rakefile +10 -0
- data/Steepfile +12 -0
- data/kangaru.gemspec +27 -0
- data/lib/kangaru/application.rb +73 -0
- data/lib/kangaru/argument_parser.rb +34 -0
- data/lib/kangaru/components/component.rb +19 -0
- data/lib/kangaru/concerns/attributes_concern.rb +31 -0
- data/lib/kangaru/concerns/concern.rb +33 -0
- data/lib/kangaru/concerns/configurable.rb +22 -0
- data/lib/kangaru/concerns/validatable.rb +46 -0
- data/lib/kangaru/config.rb +45 -0
- data/lib/kangaru/configurators/application_configurator.rb +7 -0
- data/lib/kangaru/configurators/configurator.rb +22 -0
- data/lib/kangaru/configurators/database_configurator.rb +7 -0
- data/lib/kangaru/configurators/external_configurator.rb +6 -0
- data/lib/kangaru/configurators/open_configurator.rb +33 -0
- data/lib/kangaru/configurators/request_configurator.rb +7 -0
- data/lib/kangaru/configurators.rb +12 -0
- data/lib/kangaru/controller.rb +53 -0
- data/lib/kangaru/database.rb +54 -0
- data/lib/kangaru/inflectors/class_inflector.rb +13 -0
- data/lib/kangaru/inflectors/constant_inflector.rb +13 -0
- data/lib/kangaru/inflectors/constantiser.rb +18 -0
- data/lib/kangaru/inflectors/human_inflector.rb +15 -0
- data/lib/kangaru/inflectors/inflector.rb +98 -0
- data/lib/kangaru/inflectors/inflector_macros.rb +33 -0
- data/lib/kangaru/inflectors/path_inflector.rb +21 -0
- data/lib/kangaru/inflectors/screaming_snakecase_inflector.rb +11 -0
- data/lib/kangaru/inflectors/snakecase_inflector.rb +11 -0
- data/lib/kangaru/inflectors/tokeniser.rb +20 -0
- data/lib/kangaru/initialiser.rb +12 -0
- data/lib/kangaru/initialisers/rspec.rb +11 -0
- data/lib/kangaru/injected_methods.rb +23 -0
- data/lib/kangaru/model.rb +7 -0
- data/lib/kangaru/patches/constantise.rb +11 -0
- data/lib/kangaru/patches/inflections.rb +23 -0
- data/lib/kangaru/patches/source.rb +11 -0
- data/lib/kangaru/patches/symboliser.rb +29 -0
- data/lib/kangaru/path_parser.rb +35 -0
- data/lib/kangaru/paths.rb +93 -0
- data/lib/kangaru/renderer.rb +15 -0
- data/lib/kangaru/request.rb +40 -0
- data/lib/kangaru/request_builder.rb +33 -0
- data/lib/kangaru/router.rb +38 -0
- data/lib/kangaru/validation/attribute_validator.rb +32 -0
- data/lib/kangaru/validation/error.rb +32 -0
- data/lib/kangaru/validators/required_validator.rb +17 -0
- data/lib/kangaru/validators/validator.rb +22 -0
- data/lib/kangaru/version.rb +3 -0
- data/lib/kangaru.rb +58 -0
- data/rbs_collection.lock.yaml +83 -0
- data/rbs_collection.yaml +24 -0
- metadata +173 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
module Kangaru
|
2
|
+
module Configurators
|
3
|
+
class Configurator
|
4
|
+
include Concerns::AttributesConcern
|
5
|
+
|
6
|
+
using Patches::Inflections
|
7
|
+
|
8
|
+
def self.key
|
9
|
+
to_s.gsub(/^.*::(?!.*::)/, "")
|
10
|
+
.delete_suffix("Configurator")
|
11
|
+
.to_snakecase
|
12
|
+
.to_sym
|
13
|
+
end
|
14
|
+
|
15
|
+
def serialise
|
16
|
+
self.class.attributes.to_h do |setting|
|
17
|
+
[setting, send(setting)]
|
18
|
+
end.compact
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Similar to a standard configurator, except on initialisation, it will set
|
2
|
+
# accessors for every attribute specified. This means that the super call will
|
3
|
+
# lead to each value being set as if the accessor was defined in the class.
|
4
|
+
module Kangaru
|
5
|
+
module Configurators
|
6
|
+
class OpenConfigurator < Configurator
|
7
|
+
using Patches::Symboliser
|
8
|
+
|
9
|
+
def initialize(**)
|
10
|
+
set_accessors!(**)
|
11
|
+
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
# Import contents of a yaml file
|
16
|
+
def self.from_yaml_file(path)
|
17
|
+
raise "path does not exist" unless File.exist?(path)
|
18
|
+
|
19
|
+
attributes = YAML.load_file(path).symbolise
|
20
|
+
|
21
|
+
new(**attributes)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def set_accessors!(**attributes)
|
27
|
+
attributes.each_key do |key|
|
28
|
+
self.class.class_eval { attr_accessor key }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Kangaru
|
2
|
+
module Configurators
|
3
|
+
# These are not set as accessors by Config instances as they are abstract.
|
4
|
+
BASE_CONFIGURATORS = [Configurator, OpenConfigurator].freeze
|
5
|
+
|
6
|
+
def self.classes
|
7
|
+
constants.map { |constant| const_get(constant) }
|
8
|
+
.select { |constant| constant.is_a?(Class) }
|
9
|
+
.reject { |constant| BASE_CONFIGURATORS.include?(constant) }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Kangaru
|
2
|
+
class Controller
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
using Patches::Inflections
|
6
|
+
|
7
|
+
SUFFIX = "Controller".freeze
|
8
|
+
|
9
|
+
attr_reader :request
|
10
|
+
|
11
|
+
def initialize(request)
|
12
|
+
@request = request
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute
|
16
|
+
public_send(request.action)
|
17
|
+
|
18
|
+
renderer_for(request.action.to_s).render(binding)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the partial path for the controller based on the class name.
|
22
|
+
# The first module namespace is removed as this is either Kangaru or the
|
23
|
+
# target gem namespace. Used to infer the location of view files.
|
24
|
+
def self.path
|
25
|
+
name&.delete_suffix(SUFFIX)&.gsub(/^.*?::/, "")&.to_snakecase || raise
|
26
|
+
end
|
27
|
+
|
28
|
+
def_delegators :request, :params
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def view_path(file)
|
33
|
+
Kangaru.application.view_path(self.class.path, file)
|
34
|
+
end
|
35
|
+
|
36
|
+
def renderer_for(file)
|
37
|
+
Renderer.new(view_path(file))
|
38
|
+
end
|
39
|
+
|
40
|
+
# The binding passed to the renderer is not scoped to the application
|
41
|
+
# namespace, and as such, all application classes must be prefixed with the
|
42
|
+
# namespace module. This change emulates the binding being created from
|
43
|
+
# within the application namespace by delegating const lookups to said
|
44
|
+
# namespace if the constant is not in scope from the current class.
|
45
|
+
def self.const_missing(const)
|
46
|
+
return super unless Kangaru.application.namespace.const_defined?(const)
|
47
|
+
|
48
|
+
Kangaru.application.namespace.const_get(const)
|
49
|
+
end
|
50
|
+
|
51
|
+
private_class_method :const_missing
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Kangaru
|
2
|
+
class Database
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
include Concerns::AttributesConcern
|
6
|
+
|
7
|
+
PLUGINS = %i[
|
8
|
+
enum
|
9
|
+
timestamps
|
10
|
+
].freeze
|
11
|
+
|
12
|
+
attr_accessor :adaptor, :path, :migration_path
|
13
|
+
|
14
|
+
attr_reader :handler
|
15
|
+
|
16
|
+
def setup!
|
17
|
+
raise "adaptor can't be blank" if adaptor.nil?
|
18
|
+
|
19
|
+
@handler = case adaptor
|
20
|
+
when :sqlite then setup_sqlite!
|
21
|
+
else raise "invalid adaptor '#{adaptor}'"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def migrate!
|
26
|
+
return unless handler
|
27
|
+
return unless migrations_exist?
|
28
|
+
|
29
|
+
Sequel.extension(:migration)
|
30
|
+
|
31
|
+
Sequel::Migrator.run(handler, migration_path)
|
32
|
+
end
|
33
|
+
|
34
|
+
def_delegators :handler, :tables
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def migrations_exist?
|
39
|
+
return false if migration_path.nil?
|
40
|
+
|
41
|
+
Dir.exist?(migration_path) && !Dir.empty?(migration_path)
|
42
|
+
end
|
43
|
+
|
44
|
+
def setup_sqlite!
|
45
|
+
raise "path can't be blank" if path.nil?
|
46
|
+
|
47
|
+
FileUtils.mkdir_p(File.dirname(path))
|
48
|
+
|
49
|
+
Sequel.sqlite(path).tap do
|
50
|
+
PLUGINS.each { |plugin| Sequel::Model.plugin(plugin) }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Kangaru
|
2
|
+
module Inflectors
|
3
|
+
class Constantiser
|
4
|
+
using Patches::Inflections
|
5
|
+
|
6
|
+
def self.constantise(string, root: Object)
|
7
|
+
as_class = string.to_class_name
|
8
|
+
as_constant = string.to_constant_name
|
9
|
+
|
10
|
+
if root.const_defined?(as_class)
|
11
|
+
root.const_get(as_class)
|
12
|
+
elsif root.const_defined?(as_constant)
|
13
|
+
root.const_get(as_constant)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Kangaru
|
2
|
+
module Inflectors
|
3
|
+
class Inflector
|
4
|
+
extend InflectorMacros
|
5
|
+
|
6
|
+
DEFAULT_GROUP_JOINER = "/".freeze
|
7
|
+
|
8
|
+
attr_reader :string
|
9
|
+
|
10
|
+
def initialize(string)
|
11
|
+
@string = filter_input(string)
|
12
|
+
end
|
13
|
+
|
14
|
+
def inflect
|
15
|
+
post_process(
|
16
|
+
join_groups(
|
17
|
+
transform_and_join_tokens(tokeniser.split)
|
18
|
+
)
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.inflect(string)
|
23
|
+
new(string).inflect
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def tokeniser
|
29
|
+
@tokeniser ||= Tokeniser.new(string)
|
30
|
+
end
|
31
|
+
|
32
|
+
def class_attribute(key)
|
33
|
+
self.class.instance_variable_get(:"@#{key}")
|
34
|
+
end
|
35
|
+
|
36
|
+
def input_filter
|
37
|
+
class_attribute(:input_filter)
|
38
|
+
end
|
39
|
+
|
40
|
+
def token_transformer
|
41
|
+
class_attribute(:token_transformer)
|
42
|
+
end
|
43
|
+
|
44
|
+
def token_joiner
|
45
|
+
class_attribute(:token_joiner)
|
46
|
+
end
|
47
|
+
|
48
|
+
def group_joiner
|
49
|
+
class_attribute(:group_joiner) || DEFAULT_GROUP_JOINER
|
50
|
+
end
|
51
|
+
|
52
|
+
def post_processor
|
53
|
+
class_attribute(:post_processor)
|
54
|
+
end
|
55
|
+
|
56
|
+
def filter_input(input)
|
57
|
+
case input_filter
|
58
|
+
when Regexp then input.gsub(input_filter, "")
|
59
|
+
else input
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def transform_and_join_tokens(token_groups)
|
64
|
+
token_groups.map do |tokens|
|
65
|
+
join_tokens(
|
66
|
+
tokens.map { |token| transform_token(token) }
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def transform_token(token)
|
72
|
+
case token_transformer
|
73
|
+
when Proc then token_transformer.call(token)
|
74
|
+
when Symbol then token.send(token_transformer)
|
75
|
+
else token
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def join_tokens(tokens)
|
80
|
+
tokens.join(token_joiner)
|
81
|
+
end
|
82
|
+
|
83
|
+
def join_groups(words)
|
84
|
+
words.join(group_joiner)
|
85
|
+
end
|
86
|
+
|
87
|
+
def post_process(string)
|
88
|
+
return string if post_processor.nil?
|
89
|
+
|
90
|
+
case post_processor
|
91
|
+
when Proc then post_processor.call(string)
|
92
|
+
when Symbol then string.send(post_processor)
|
93
|
+
else string
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Kangaru
|
2
|
+
module Inflectors
|
3
|
+
module InflectorMacros
|
4
|
+
def inherited(child_class)
|
5
|
+
instance_variables.each do |rule|
|
6
|
+
value = instance_variable_get(rule)
|
7
|
+
|
8
|
+
child_class.instance_variable_set(rule, value)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def filter_input_with(pattern)
|
13
|
+
@input_filter = pattern
|
14
|
+
end
|
15
|
+
|
16
|
+
def transform_tokens_with(symbol = nil, &block)
|
17
|
+
@token_transformer = symbol || block
|
18
|
+
end
|
19
|
+
|
20
|
+
def join_tokens_with(joiner)
|
21
|
+
@token_joiner = joiner
|
22
|
+
end
|
23
|
+
|
24
|
+
def join_groups_with(joiner)
|
25
|
+
@group_joiner = joiner
|
26
|
+
end
|
27
|
+
|
28
|
+
def post_process_with(symbol = nil, &block)
|
29
|
+
@post_processor = symbol || block
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Kangaru
|
2
|
+
module Inflectors
|
3
|
+
class PathInflector < Inflector
|
4
|
+
filter_input_with(/\.[a-z]+$/)
|
5
|
+
|
6
|
+
transform_tokens_with :downcase
|
7
|
+
|
8
|
+
join_tokens_with "_"
|
9
|
+
|
10
|
+
join_groups_with "/"
|
11
|
+
|
12
|
+
def inflect(with_ext: nil)
|
13
|
+
inflection = super()
|
14
|
+
|
15
|
+
return inflection unless with_ext
|
16
|
+
|
17
|
+
"#{inflection}.#{with_ext}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Kangaru
|
2
|
+
module Inflectors
|
3
|
+
class Tokeniser
|
4
|
+
GROUP_DELIMITER = %r{/|::}
|
5
|
+
TOKEN_DELIMITER = /[_-]+|(?=[A-Z][a-z])/
|
6
|
+
|
7
|
+
attr_reader :string
|
8
|
+
|
9
|
+
def initialize(string)
|
10
|
+
@string = string
|
11
|
+
end
|
12
|
+
|
13
|
+
def split
|
14
|
+
string.split(GROUP_DELIMITER).map do |group|
|
15
|
+
group.split(TOKEN_DELIMITER)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Kangaru
|
2
|
+
module Initialiser
|
3
|
+
def self.extended(namespace)
|
4
|
+
source = caller[0].gsub(/:.*$/, "")
|
5
|
+
|
6
|
+
Kangaru.application = Application.new(source:, namespace:)
|
7
|
+
Kangaru.eager_load(Initialisers)
|
8
|
+
|
9
|
+
namespace.extend InjectedMethods
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Kangaru
|
2
|
+
module InjectedMethods
|
3
|
+
def run!(*argv)
|
4
|
+
Kangaru.application.run!(*argv)
|
5
|
+
end
|
6
|
+
|
7
|
+
def config
|
8
|
+
Kangaru.application.config
|
9
|
+
end
|
10
|
+
|
11
|
+
def configure(env = nil, &)
|
12
|
+
Kangaru.application.configure(env, &)
|
13
|
+
end
|
14
|
+
|
15
|
+
def apply_config!
|
16
|
+
Kangaru.application.apply_config!
|
17
|
+
end
|
18
|
+
|
19
|
+
def database
|
20
|
+
Kangaru.application.database
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Kangaru
|
2
|
+
module Patches
|
3
|
+
module Inflections
|
4
|
+
refine String do
|
5
|
+
def to_class_name
|
6
|
+
Inflectors::ClassInflector.inflect(self)
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_constant_name
|
10
|
+
Inflectors::ConstantInflector.inflect(self)
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_snakecase
|
14
|
+
Inflectors::SnakecaseInflector.inflect(self)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_humanised
|
18
|
+
Inflectors::HumanInflector.inflect(self)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Kangaru
|
2
|
+
module Patches
|
3
|
+
module Symboliser
|
4
|
+
refine Hash do
|
5
|
+
def symbolise
|
6
|
+
to_h do |key, value|
|
7
|
+
value = case value
|
8
|
+
when Array, Hash then value.symbolise
|
9
|
+
else value
|
10
|
+
end
|
11
|
+
|
12
|
+
[key.to_sym, value]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
refine Array do
|
18
|
+
def symbolise
|
19
|
+
map do |value|
|
20
|
+
case value
|
21
|
+
when Array, Hash then value.symbolise
|
22
|
+
else value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Kangaru
|
2
|
+
class PathParser
|
3
|
+
attr_reader :path
|
4
|
+
|
5
|
+
def initialize(path)
|
6
|
+
@path = path
|
7
|
+
end
|
8
|
+
|
9
|
+
def controller
|
10
|
+
match(:controller)
|
11
|
+
end
|
12
|
+
|
13
|
+
def action
|
14
|
+
match(:action, cast: :to_sym)
|
15
|
+
end
|
16
|
+
|
17
|
+
def id
|
18
|
+
match(:id, cast: :to_i)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
MATCHERS = {
|
24
|
+
controller: %r{/(.*)/[A-z]+(/\d+)?$},
|
25
|
+
action: %r{^.*/([A-z]+)(/\d+)?$},
|
26
|
+
id: %r{^.*/(\d+)$}
|
27
|
+
}.freeze
|
28
|
+
|
29
|
+
def match(key, cast: :to_s)
|
30
|
+
return unless (value = path.scan(MATCHERS[key]).flatten.first)
|
31
|
+
|
32
|
+
value.send(cast)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|