flame 5.0.0.rc5 → 5.0.0.rc6
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 +5 -5
- data/CHANGELOG.md +921 -0
- data/LICENSE.txt +19 -0
- data/README.md +135 -0
- data/lib/flame.rb +12 -5
- data/lib/flame/application.rb +47 -46
- data/lib/flame/config.rb +73 -0
- data/lib/flame/controller.rb +45 -78
- data/lib/flame/controller/actions.rb +122 -0
- data/lib/flame/{dispatcher → controller}/cookies.rb +8 -3
- data/lib/flame/controller/path_to.rb +34 -10
- data/lib/flame/dispatcher.rb +14 -17
- data/lib/flame/dispatcher/request.rb +25 -6
- data/lib/flame/dispatcher/routes.rb +22 -14
- data/lib/flame/dispatcher/static.rb +13 -9
- data/lib/flame/errors/argument_not_assigned_error.rb +3 -8
- data/lib/flame/errors/config_file_not_found_error.rb +17 -0
- data/lib/flame/errors/controller_not_found_error.rb +19 -0
- data/lib/flame/errors/route_arguments_order_error.rb +5 -10
- data/lib/flame/errors/route_extra_arguments_error.rb +10 -20
- data/lib/flame/errors/route_not_found_error.rb +3 -8
- data/lib/flame/errors/template_not_found_error.rb +2 -8
- data/lib/flame/path.rb +36 -18
- data/lib/flame/render.rb +13 -5
- data/lib/flame/router.rb +7 -157
- data/lib/flame/router/controller_finder.rb +56 -0
- data/lib/flame/router/route.rb +9 -0
- data/lib/flame/router/routes.rb +58 -8
- data/lib/flame/router/routes_refine.rb +144 -0
- data/lib/flame/router/routes_refine/mounting.rb +57 -0
- data/lib/flame/validators.rb +14 -10
- data/lib/flame/version.rb +1 -1
- metadata +91 -99
- data/bin/flame +0 -16
- data/lib/flame/application/config.rb +0 -49
- data/template/.editorconfig +0 -15
- data/template/.gitignore +0 -28
- data/template/.rubocop.yml +0 -14
- data/template/Gemfile +0 -55
- data/template/Rakefile +0 -824
- data/template/application.rb.erb +0 -10
- data/template/config.ru.erb +0 -72
- data/template/config/config.rb.erb +0 -56
- data/template/config/database.example.yml +0 -5
- data/template/config/deploy.example.yml +0 -2
- data/template/config/puma.rb +0 -56
- data/template/config/sequel.rb.erb +0 -22
- data/template/config/server.example.yml +0 -32
- data/template/config/session.example.yml +0 -7
- data/template/controllers/_controller.rb.erb +0 -14
- data/template/controllers/site/_controller.rb.erb +0 -18
- data/template/controllers/site/index_controller.rb.erb +0 -12
- data/template/db/.keep +0 -0
- data/template/filewatchers.yml +0 -12
- data/template/helpers/.keep +0 -0
- data/template/lib/.keep +0 -0
- data/template/locales/en.yml +0 -0
- data/template/models/.keep +0 -0
- data/template/public/.keep +0 -0
- data/template/server +0 -200
- data/template/services/.keep +0 -0
- data/template/views/.keep +0 -0
- data/template/views/site/index.html.erb.erb +0 -1
- data/template/views/site/layout.html.erb.erb +0 -10
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flame
|
4
|
+
## Comment due to `private_constant`
|
5
|
+
class Router
|
6
|
+
## Class for controller constant finding in namespace by names
|
7
|
+
class ControllerFinder
|
8
|
+
attr_reader :controller
|
9
|
+
|
10
|
+
def initialize(namespace_name, controller_or_name)
|
11
|
+
@namespace =
|
12
|
+
namespace_name.empty? ? Object : Object.const_get(namespace_name)
|
13
|
+
|
14
|
+
if controller_or_name.is_a?(Class)
|
15
|
+
@controller = controller_or_name
|
16
|
+
@controller_name = controller_or_name.name
|
17
|
+
else
|
18
|
+
@controller_name = controller_or_name
|
19
|
+
@controller = find
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def find
|
26
|
+
found_controller_name = controller_name_variations.find do |variation|
|
27
|
+
@namespace.const_defined?(variation) unless variation.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
raise_controller_not_found_error unless found_controller_name
|
31
|
+
|
32
|
+
controller = @namespace.const_get(found_controller_name)
|
33
|
+
return controller if controller < Flame::Controller
|
34
|
+
|
35
|
+
controller::IndexController
|
36
|
+
end
|
37
|
+
|
38
|
+
using GorillaPatch::Inflections
|
39
|
+
|
40
|
+
TRASNFORMATION_METHODS = %i[camelize upcase].freeze
|
41
|
+
|
42
|
+
def controller_name_variations
|
43
|
+
TRASNFORMATION_METHODS.each_with_object([]) do |method, result|
|
44
|
+
transformed = @controller_name.to_s.send(method)
|
45
|
+
result.push transformed, "#{transformed}Controller"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def raise_controller_not_found_error
|
50
|
+
raise Errors::ControllerNotFoundError.new(@controller_name, @namespace)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private_constant :ControllerFinder
|
55
|
+
end
|
56
|
+
end
|
data/lib/flame/router/route.rb
CHANGED
@@ -7,6 +7,10 @@ module Flame
|
|
7
7
|
class Router
|
8
8
|
## Class for Route in Router.routes
|
9
9
|
class Route
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
def_delegators :to_s, :inspect
|
13
|
+
|
10
14
|
attr_reader :controller, :action
|
11
15
|
|
12
16
|
## Create a new instance
|
@@ -22,12 +26,17 @@ module Flame
|
|
22
26
|
## @return [true, false] equal or not
|
23
27
|
def ==(other)
|
24
28
|
return false unless other.is_a? self.class
|
29
|
+
|
25
30
|
%i[controller action].reduce(true) do |result, method|
|
26
31
|
result && (
|
27
32
|
public_send(method) == other.public_send(method)
|
28
33
|
)
|
29
34
|
end
|
30
35
|
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
"#{controller}##{action}"
|
39
|
+
end
|
31
40
|
end
|
32
41
|
end
|
33
42
|
end
|
data/lib/flame/router/routes.rb
CHANGED
@@ -12,10 +12,15 @@ module Flame
|
|
12
12
|
## Flame::Router::Routes.new('/foo/bar/baz')
|
13
13
|
## # => { 'foo' => { 'bar' => { 'baz' => {} } } }
|
14
14
|
def initialize(*path_parts)
|
15
|
+
super()
|
16
|
+
|
15
17
|
path = Flame::Path.new(*path_parts)
|
16
18
|
return if path.parts.empty?
|
19
|
+
|
17
20
|
nested_routes = self.class.new Flame::Path.new(*path.parts[1..-1])
|
18
|
-
# path.parts.reduce(result)
|
21
|
+
# path.parts.reduce(result) do |hash, part|
|
22
|
+
# hash[part] ||= self.class.new
|
23
|
+
# end
|
19
24
|
self[path.parts.first] = nested_routes
|
20
25
|
end
|
21
26
|
|
@@ -38,6 +43,12 @@ module Flame
|
|
38
43
|
super
|
39
44
|
end
|
40
45
|
|
46
|
+
## Return the first available route (at the first level).
|
47
|
+
## @return [Flame::Router::Route] the first route
|
48
|
+
def first_route
|
49
|
+
values.find { |value| value.is_a?(Route) }
|
50
|
+
end
|
51
|
+
|
41
52
|
## Navigate to Routes or Route through static parts or arguments
|
42
53
|
## @param path_parts [Array<String, Flame::Path, Flame::Path::Part>]
|
43
54
|
## path or path parts as keys for navigating
|
@@ -48,31 +59,70 @@ module Flame
|
|
48
59
|
def navigate(*path_parts)
|
49
60
|
path_parts = Flame::Path.new(*path_parts).parts
|
50
61
|
return dig_through_opt_args if path_parts.empty?
|
62
|
+
|
51
63
|
endpoint =
|
52
|
-
self[path_parts.first] ||
|
53
|
-
|
54
|
-
|
55
|
-
|
64
|
+
self[path_parts.first] || dig(first_opt_arg_key, path_parts.first)
|
65
|
+
|
66
|
+
endpoint&.navigate(*path_parts[1..-1]) ||
|
67
|
+
find_among_arg_keys(path_parts[1..-1])
|
56
68
|
end
|
57
69
|
|
58
70
|
## Dig through optional arguments as keys
|
59
71
|
## @return [Flame::Router::Routes] return most nested end-point
|
60
72
|
## without optional arguments
|
61
73
|
def dig_through_opt_args
|
62
|
-
|
74
|
+
[
|
75
|
+
self[first_opt_arg_key]&.dig_through_opt_args,
|
76
|
+
self
|
77
|
+
]
|
78
|
+
.compact.find(&:first_route)
|
63
79
|
end
|
64
80
|
|
65
81
|
def allow
|
66
82
|
methods = keys.select { |key| key.is_a? Symbol }
|
67
83
|
return if methods.empty?
|
84
|
+
|
68
85
|
methods.push(:OPTIONS).join(', ')
|
69
86
|
end
|
70
87
|
|
88
|
+
PADDING_SIZE = Router::HTTP_METHODS.map(&:size).max
|
89
|
+
PADDING_FORMAT = "%#{PADDING_SIZE}.#{PADDING_SIZE}s"
|
90
|
+
|
91
|
+
## Output routes in human readable format
|
92
|
+
def to_s(prefix = '/')
|
93
|
+
sort.map do |key, value|
|
94
|
+
if key.is_a?(Symbol)
|
95
|
+
<<~OUTPUT
|
96
|
+
\e[1m#{format PADDING_FORMAT, key} #{prefix}\e[22m
|
97
|
+
#{' ' * PADDING_SIZE} \e[3m\e[36m#{value}\e[0m\e[23m
|
98
|
+
OUTPUT
|
99
|
+
else
|
100
|
+
value.to_s(Flame::Path.new(prefix, key))
|
101
|
+
end
|
102
|
+
end.join
|
103
|
+
end
|
104
|
+
|
105
|
+
## Sort routes for human readability
|
106
|
+
def sort
|
107
|
+
sort_by do |key, _value|
|
108
|
+
[
|
109
|
+
if key.is_a?(Symbol)
|
110
|
+
then Router::HTTP_METHODS.index(key)
|
111
|
+
else Float::INFINITY
|
112
|
+
end,
|
113
|
+
key.to_s
|
114
|
+
]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
71
118
|
private
|
72
119
|
|
73
|
-
def
|
120
|
+
def find_among_arg_keys(path_parts)
|
74
121
|
keys.find do |key|
|
75
|
-
key.is_a?(Flame::Path::Part) && key.arg?
|
122
|
+
next unless key.is_a?(Flame::Path::Part) && key.arg?
|
123
|
+
|
124
|
+
result = self[key].navigate(*path_parts)
|
125
|
+
break result if result
|
76
126
|
end
|
77
127
|
end
|
78
128
|
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'routes_refine/mounting'
|
4
|
+
|
5
|
+
module Flame
|
6
|
+
class Router
|
7
|
+
## Helper class for controller routing refine
|
8
|
+
class RoutesRefine
|
9
|
+
attr_reader :routes, :reverse_routes
|
10
|
+
|
11
|
+
class << self
|
12
|
+
include Memery
|
13
|
+
|
14
|
+
## Defaults REST routes (methods, pathes, controllers actions)
|
15
|
+
def rest_routes
|
16
|
+
[
|
17
|
+
{ method: :GET, path: '/', action: :index },
|
18
|
+
{ method: :POST, path: '/', action: :create },
|
19
|
+
{ method: :GET, path: '/', action: :show },
|
20
|
+
{ method: :PUT, path: '/', action: :update },
|
21
|
+
{ method: :DELETE, path: '/', action: :delete }
|
22
|
+
]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
using GorillaPatch::Namespace
|
27
|
+
|
28
|
+
def initialize(
|
29
|
+
namespace_name, controller_or_name, path, nested: true, &block
|
30
|
+
)
|
31
|
+
@controller =
|
32
|
+
ControllerFinder.new(namespace_name, controller_or_name).controller
|
33
|
+
@namespace_name = @controller.deconstantize
|
34
|
+
@path = Flame::Path.new(path || @controller.path)
|
35
|
+
@controller.path_arguments = @path.parts.select(&:arg?).map(&:to_sym)
|
36
|
+
@routes, @endpoint = @path.to_routes_with_endpoint
|
37
|
+
@reverse_routes = {}
|
38
|
+
@mount_nested = nested
|
39
|
+
execute(&block)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
HTTP_METHODS.each do |http_method|
|
45
|
+
## Define refine methods for all HTTP methods
|
46
|
+
## @overload post(path, action)
|
47
|
+
## Execute action on requested path and HTTP method
|
48
|
+
## @param path [String] path of method for the request
|
49
|
+
## @param action [Symbol] name of method for the request
|
50
|
+
## @example
|
51
|
+
## Set path to '/bye' and method to :POST for action `goodbye`
|
52
|
+
## post '/bye', :goodbye
|
53
|
+
## @overload post(action)
|
54
|
+
## Execute action on requested HTTP method
|
55
|
+
## @param action [Symbol] name of method for the request
|
56
|
+
## @example Set method to :POST for action `goodbye`
|
57
|
+
## post :goodbye
|
58
|
+
define_method(http_method.downcase) do |action_path, action = nil|
|
59
|
+
## Swap arguments if action in path variable
|
60
|
+
unless action
|
61
|
+
action = action_path.to_sym
|
62
|
+
action_path = nil
|
63
|
+
end
|
64
|
+
## Initialize new route
|
65
|
+
route = Route.new(@controller, action)
|
66
|
+
## Make path by controller method with parameners
|
67
|
+
action_path = Flame::Path.new(action_path).adapt(@controller, action)
|
68
|
+
## Validate action path
|
69
|
+
validate_action_path(action, action_path)
|
70
|
+
## Merge action path with controller path
|
71
|
+
path = Flame::Path.new(@path, action_path)
|
72
|
+
## Remove the same route if needed
|
73
|
+
remove_old_routes(action, route)
|
74
|
+
## Add new route
|
75
|
+
add_new_route(route, action, path, http_method)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
## Assign remaining methods of the controller
|
80
|
+
## to defaults pathes and HTTP methods
|
81
|
+
def defaults
|
82
|
+
rest
|
83
|
+
|
84
|
+
@controller.actions.each do |action|
|
85
|
+
get action unless find_reverse_route(action)
|
86
|
+
end
|
87
|
+
|
88
|
+
mount_nested_controllers if @mount_nested && (
|
89
|
+
@controller.demodulize == 'IndexController' &&
|
90
|
+
!@namespace_name.empty?
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
## Assign methods of the controller to REST architecture
|
95
|
+
def rest
|
96
|
+
self.class.rest_routes.each do |rest_route|
|
97
|
+
action = rest_route[:action]
|
98
|
+
next if !@controller.actions.include?(action) || find_reverse_route(action)
|
99
|
+
|
100
|
+
send(*rest_route.values.map(&:downcase))
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
include Mounting
|
105
|
+
|
106
|
+
## Execute block of refinings end sorting routes
|
107
|
+
def execute(&block)
|
108
|
+
@controller.refined_http_methods
|
109
|
+
.each do |action, (http_method, action_path)|
|
110
|
+
send(http_method, action_path, action)
|
111
|
+
end
|
112
|
+
instance_exec(&block) if block
|
113
|
+
defaults
|
114
|
+
end
|
115
|
+
|
116
|
+
def find_reverse_route(action)
|
117
|
+
@reverse_routes.dig(@controller.to_s, action)
|
118
|
+
end
|
119
|
+
|
120
|
+
def validate_action_path(action, action_path)
|
121
|
+
Validators::RouteArgumentsValidator
|
122
|
+
.new(@controller, action_path, action)
|
123
|
+
.valid?
|
124
|
+
end
|
125
|
+
|
126
|
+
def remove_old_routes(action, new_route)
|
127
|
+
old_path = @reverse_routes[@controller.to_s]&.delete(action)
|
128
|
+
return unless old_path
|
129
|
+
|
130
|
+
@routes.dig(*old_path.parts)
|
131
|
+
.delete_if { |_method, old_route| old_route == new_route }
|
132
|
+
end
|
133
|
+
|
134
|
+
using GorillaPatch::DeepMerge
|
135
|
+
|
136
|
+
def add_new_route(route, action, path, http_method)
|
137
|
+
path_routes, endpoint = path.to_routes_with_endpoint
|
138
|
+
endpoint[http_method] = route
|
139
|
+
@routes.deep_merge!(path_routes)
|
140
|
+
(@reverse_routes[@controller.to_s] ||= {})[action] = path
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flame
|
4
|
+
class Router
|
5
|
+
class RoutesRefine
|
6
|
+
## Module for mounting in RoutesRefine
|
7
|
+
module Mounting
|
8
|
+
private
|
9
|
+
|
10
|
+
using GorillaPatch::DeepMerge
|
11
|
+
|
12
|
+
## Mount controller inside other (parent) controller
|
13
|
+
## @param controller [Flame::Controller] class of mounting controller
|
14
|
+
## @param path [String, nil] root path for mounting controller
|
15
|
+
## @yield Block of code for routes refine
|
16
|
+
def mount(controller_name, path = nil, &block)
|
17
|
+
routes_refine = self.class.new(
|
18
|
+
@namespace_name, controller_name, path, &block
|
19
|
+
)
|
20
|
+
|
21
|
+
@endpoint.deep_merge! routes_refine.routes
|
22
|
+
|
23
|
+
@reverse_routes.merge!(
|
24
|
+
routes_refine.reverse_routes.transform_values do |hash|
|
25
|
+
hash.transform_values { |action_path| @path + action_path }
|
26
|
+
end
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
using GorillaPatch::Namespace
|
31
|
+
|
32
|
+
def mount_nested_controllers
|
33
|
+
namespace = Object.const_get(@namespace_name)
|
34
|
+
|
35
|
+
namespace.constants.each do |constant_name|
|
36
|
+
constant = namespace.const_get(constant_name)
|
37
|
+
if constant < Flame::Controller || constant.instance_of?(Module)
|
38
|
+
mount_nested_controller constant
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def mount_nested_controller(nested_controller)
|
44
|
+
mount nested_controller if should_be_mounted? nested_controller
|
45
|
+
end
|
46
|
+
|
47
|
+
def should_be_mounted?(controller)
|
48
|
+
if controller.instance_of?(Module)
|
49
|
+
controller.const_defined?(:IndexController, false)
|
50
|
+
else
|
51
|
+
controller.actions.any? && !@reverse_routes.key?(controller.to_s)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/flame/validators.rb
CHANGED
@@ -7,6 +7,8 @@ module Flame
|
|
7
7
|
module Validators
|
8
8
|
## Compare arguments from path and from controller's action
|
9
9
|
class RouteArgumentsValidator
|
10
|
+
include Memery
|
11
|
+
|
10
12
|
## Create a new instance of validator
|
11
13
|
## @param ctrl [Flame::Controller] controller of route
|
12
14
|
## @param path [Flame::Path, String] path of route
|
@@ -29,6 +31,7 @@ module Flame
|
|
29
31
|
extra_arguments = first_extra_arguments
|
30
32
|
## Raise error if extra arguments
|
31
33
|
return true unless extra_arguments
|
34
|
+
|
32
35
|
raise Errors::RouteExtraArgumentsError.new(
|
33
36
|
@ctrl, @action, @path, extra_arguments
|
34
37
|
)
|
@@ -37,27 +40,28 @@ module Flame
|
|
37
40
|
def order_valid?
|
38
41
|
wrong_ordered_arguments = first_wrong_ordered_arguments
|
39
42
|
return true unless wrong_ordered_arguments
|
43
|
+
|
40
44
|
raise Errors::RouteArgumentsOrderError.new(
|
41
45
|
@path, wrong_ordered_arguments
|
42
46
|
)
|
43
47
|
end
|
44
48
|
|
45
49
|
## Split path to args array
|
46
|
-
def path_arguments
|
47
|
-
@
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
50
|
+
memoize def path_arguments
|
51
|
+
@path.parts.each_with_object(req: [], opt: []) do |part, hash|
|
52
|
+
## Take only argument parts
|
53
|
+
next unless part.arg?
|
54
|
+
|
55
|
+
## Memorize arguments
|
56
|
+
hash[part.opt_arg? ? :opt : :req] << part.to_sym
|
57
|
+
end
|
54
58
|
end
|
55
59
|
|
56
60
|
## Take args from controller's action
|
57
|
-
def action_arguments
|
61
|
+
memoize def action_arguments
|
58
62
|
## Get all parameters (arguments) from method
|
59
63
|
## Than collect and sort parameters into hash
|
60
|
-
@
|
64
|
+
@ctrl.instance_method(@action).parameters
|
61
65
|
.each_with_object(req: [], opt: []) do |param, hash|
|
62
66
|
## Only required parameters must be in `:req`
|
63
67
|
hash[param[0]] << param[1]
|