flame 4.18.1 → 5.0.0.rc1
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 +4 -4
- data/bin/flame +7 -62
- data/lib/flame.rb +1 -0
- data/lib/flame/application.rb +75 -17
- data/lib/flame/application/config.rb +6 -0
- data/lib/flame/controller.rb +36 -76
- data/lib/flame/controller/path_to.rb +39 -0
- data/lib/flame/dispatcher.rb +25 -66
- data/lib/flame/dispatcher/cookies.rb +10 -2
- data/lib/flame/dispatcher/routes.rb +53 -0
- data/lib/flame/dispatcher/static.rb +15 -8
- data/lib/flame/errors/argument_not_assigned_error.rb +6 -0
- data/lib/flame/errors/route_arguments_order_error.rb +6 -0
- data/lib/flame/errors/route_extra_arguments_error.rb +10 -0
- data/lib/flame/errors/route_not_found_error.rb +10 -4
- data/lib/flame/errors/template_not_found_error.rb +6 -0
- data/lib/flame/path.rb +63 -33
- data/lib/flame/render.rb +21 -8
- data/lib/flame/router.rb +112 -66
- data/lib/flame/router/route.rb +9 -56
- data/lib/flame/router/routes.rb +86 -0
- data/lib/flame/validators.rb +7 -1
- data/lib/flame/version.rb +1 -1
- data/template/.editorconfig +15 -0
- data/template/.gitignore +19 -2
- data/template/.rubocop.yml +14 -0
- data/template/Gemfile +48 -8
- data/template/Rakefile +824 -0
- data/template/{app.rb.erb → application.rb.erb} +4 -1
- data/template/config.ru.erb +62 -10
- data/template/config/config.rb.erb +44 -2
- data/template/config/database.example.yml +1 -1
- data/template/config/deploy.example.yml +2 -0
- data/template/config/puma.rb +56 -0
- data/template/config/sequel.rb.erb +13 -6
- data/template/config/server.example.yml +32 -0
- data/template/config/session.example.yml +7 -0
- data/template/controllers/{_base_controller.rb.erb → _controller.rb.erb} +5 -4
- data/template/controllers/site/_controller.rb.erb +18 -0
- data/template/controllers/site/index_controller.rb.erb +12 -0
- data/template/filewatchers.yml +12 -0
- data/template/server +172 -21
- data/template/services/.keep +0 -0
- data/template/views/site/index.html.erb.erb +1 -0
- data/template/views/site/layout.html.erb.erb +10 -0
- metadata +112 -54
- data/template/Rakefile.erb +0 -64
- data/template/config/thin.example.yml +0 -18
data/lib/flame/render.rb
CHANGED
@@ -13,14 +13,26 @@ require_relative 'errors/template_not_found_error'
|
|
13
13
|
module Flame
|
14
14
|
## Helper for render functionality
|
15
15
|
class Render
|
16
|
+
## Create a new instance from controller, by path and with options
|
17
|
+
## @param controller [Flame::Controller]
|
18
|
+
## controller for default scope, views directory and cache
|
19
|
+
## @param path [Symbol, String] path (full or the last part) for view search
|
20
|
+
## @param options [Hash] options for template
|
21
|
+
## @option options [Object] :scope (controller)
|
22
|
+
## scope of visibility in rendering
|
23
|
+
## @option options [Symbol, String, false] :layout ('layout.*')
|
24
|
+
## name of the layout file
|
25
|
+
## @option options [Hash] :tilt options for Tilt
|
26
|
+
## @option options [Hash] :locals ({}) local variables for rendering
|
16
27
|
def initialize(controller, path, options = {})
|
17
28
|
## Take options for rendering
|
18
29
|
@controller = controller
|
19
|
-
@scope = options.delete(:scope)
|
20
|
-
@layout = options.delete(:layout)
|
21
|
-
|
30
|
+
@scope = options.delete(:scope) { @controller }
|
31
|
+
@layout = options.delete(:layout) { 'layout.*' }
|
32
|
+
## Options for Tilt Template
|
33
|
+
@tilt_options = options.delete(:tilt)
|
22
34
|
## And get the rest variables to locals
|
23
|
-
@locals = options.merge(options.delete(:locals)
|
35
|
+
@locals = options.merge(options.delete(:locals) { {} })
|
24
36
|
## Find filename
|
25
37
|
@filename = find_file(path)
|
26
38
|
unless @filename
|
@@ -29,15 +41,16 @@ module Flame
|
|
29
41
|
@layout = nil if File.basename(@filename)[0] == '_'
|
30
42
|
end
|
31
43
|
|
32
|
-
## Render template
|
44
|
+
## Render template with layout
|
33
45
|
## @param cache [Boolean] cache compiles or not
|
34
|
-
|
46
|
+
## @return [String] compiled template
|
47
|
+
def render(cache: true, &block)
|
35
48
|
@cache = cache
|
36
49
|
## Compile Tilt to instance hash
|
37
50
|
return unless @filename
|
38
51
|
tilt = compile_file
|
39
52
|
## Render Tilt from instance hash with new options
|
40
|
-
layout_render tilt.render(@scope, @locals)
|
53
|
+
layout_render tilt.render(@scope, @locals, &block)
|
41
54
|
end
|
42
55
|
|
43
56
|
private
|
@@ -51,7 +64,7 @@ module Flame
|
|
51
64
|
def compile_file(filename = @filename)
|
52
65
|
cached = @controller.cached_tilts[filename]
|
53
66
|
return cached if @cache && cached
|
54
|
-
compiled = Tilt.new(filename)
|
67
|
+
compiled = Tilt.new(filename, nil, @tilt_options)
|
55
68
|
@controller.cached_tilts[filename] ||= compiled if @cache
|
56
69
|
compiled
|
57
70
|
end
|
data/lib/flame/router.rb
CHANGED
@@ -1,66 +1,58 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'gorilla-patch/deep_merge'
|
4
|
+
require 'gorilla-patch/inflections'
|
5
|
+
require 'gorilla-patch/namespace'
|
6
|
+
require 'gorilla-patch/transform'
|
7
|
+
|
8
|
+
require_relative 'router/routes'
|
3
9
|
require_relative 'router/route'
|
4
10
|
|
5
11
|
module Flame
|
6
12
|
## Router class for routing
|
7
13
|
class Router
|
8
|
-
attr_reader :app, :routes
|
14
|
+
attr_reader :app, :routes, :reverse_routes
|
9
15
|
|
16
|
+
## @param app [Flame::Application] host application
|
10
17
|
def initialize(app)
|
11
18
|
@app = app
|
12
|
-
@routes =
|
13
|
-
|
14
|
-
|
15
|
-
## Add the controller with it's methods to routes
|
16
|
-
## @param ctrl [Flame::Controller] class of the controller which will be added
|
17
|
-
## @param path [String, nil] root path for controller's methods
|
18
|
-
## @yield block for routes refine
|
19
|
-
def add_controller(ctrl, path = nil, &block)
|
20
|
-
## @todo Add Regexp paths
|
21
|
-
|
22
|
-
## Add routes from controller to glob array
|
23
|
-
route_refine = RouteRefine.new(self, ctrl, path, block)
|
24
|
-
concat_routes(route_refine)
|
25
|
-
end
|
26
|
-
|
27
|
-
## Find route by any attributes
|
28
|
-
## @param attrs [Hash] attributes for comparing
|
29
|
-
## @return [Flame::Route, nil] return the found route, otherwise `nil`
|
30
|
-
def find_route(attrs)
|
31
|
-
route = routes.find { |r| r.compare_attributes(attrs) }
|
32
|
-
route.dup if route
|
19
|
+
@routes = Flame::Router::Routes.new
|
20
|
+
@reverse_routes = {}
|
33
21
|
end
|
34
22
|
|
35
23
|
## Find the nearest route by path
|
36
24
|
## @param path [Flame::Path] path for route finding
|
37
25
|
## @return [Flame::Route, nil] return the found nearest route or `nil`
|
38
26
|
def find_nearest_route(path)
|
39
|
-
path = Flame::Path.new(path) if path.is_a? String
|
40
27
|
path_parts = path.parts.dup
|
41
|
-
|
42
|
-
route =
|
43
|
-
break if route || path_parts.
|
44
|
-
path_parts.pop
|
28
|
+
loop do
|
29
|
+
route = routes.navigate(*path_parts)&.values&.grep(Route)&.first
|
30
|
+
break route if route || path_parts.pop.nil?
|
45
31
|
end
|
46
|
-
route
|
47
32
|
end
|
48
33
|
|
49
|
-
|
50
|
-
|
51
|
-
##
|
52
|
-
## @param
|
53
|
-
|
54
|
-
|
34
|
+
## Find the path of route
|
35
|
+
## @param route_or_controller [Flame::Router::Route, Flame::Controller]
|
36
|
+
## route or controller
|
37
|
+
## @param action [Symbol, nil] action (or not for route)
|
38
|
+
## @return [Flame::Path] mounted path to action of controller
|
39
|
+
def path_of(route_or_controller, action = nil)
|
40
|
+
if route_or_controller.is_a?(Flame::Router::Route)
|
41
|
+
route = route_or_controller
|
42
|
+
controller = route.controller
|
43
|
+
action = route.action
|
44
|
+
else
|
45
|
+
controller = route_or_controller
|
46
|
+
end
|
47
|
+
reverse_routes.dig(controller.to_s, action)
|
55
48
|
end
|
56
49
|
|
57
50
|
## Helper class for controller routing refine
|
58
|
-
class
|
59
|
-
|
60
|
-
attr_reader :ctrl, :routes
|
51
|
+
class RoutesRefine
|
52
|
+
attr_reader :routes, :reverse_routes
|
61
53
|
|
62
54
|
## Defaults REST routes (methods, pathes, controllers actions)
|
63
|
-
def rest_routes
|
55
|
+
def self.rest_routes
|
64
56
|
@rest_routes ||= [
|
65
57
|
{ method: :GET, path: '/', action: :index },
|
66
58
|
{ method: :POST, path: '/', action: :create },
|
@@ -70,15 +62,33 @@ module Flame
|
|
70
62
|
]
|
71
63
|
end
|
72
64
|
|
73
|
-
def initialize(router,
|
65
|
+
def initialize(router, namespace_name, controller_name, path, &block)
|
74
66
|
@router = router
|
75
|
-
@
|
76
|
-
@path = path || @
|
77
|
-
@routes =
|
67
|
+
@controller = constantize_controller namespace_name, controller_name
|
68
|
+
@path = Flame::Path.new(path || @controller.default_path)
|
69
|
+
@routes, @endpoint = @path.to_routes_with_endpoint
|
70
|
+
@reverse_routes = {}
|
78
71
|
execute(&block)
|
79
72
|
end
|
80
73
|
|
81
|
-
|
74
|
+
private
|
75
|
+
|
76
|
+
using GorillaPatch::Inflections
|
77
|
+
|
78
|
+
def constantize_controller(namespace_name, controller_name)
|
79
|
+
controller_name = controller_name.to_s.camelize
|
80
|
+
namespace =
|
81
|
+
namespace_name.empty? ? Object : Object.const_get(namespace_name)
|
82
|
+
if namespace.const_defined?(controller_name)
|
83
|
+
controller = namespace.const_get(controller_name)
|
84
|
+
return controller if controller < Flame::Controller
|
85
|
+
controller::IndexController
|
86
|
+
else
|
87
|
+
namespace.const_get("#{controller_name}Controller")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
%i[GET POST PUT PATCH DELETE].each do |http_method|
|
82
92
|
## Define refine methods for all HTTP methods
|
83
93
|
## @overload post(path, action)
|
84
94
|
## Execute action on requested path and HTTP method
|
@@ -91,19 +101,24 @@ module Flame
|
|
91
101
|
## @param action [Symbol] name of method for the request
|
92
102
|
## @example Set method to :POST for action `goodbye`
|
93
103
|
## post :goodbye
|
94
|
-
|
95
|
-
define_method(method) do |path, action = nil|
|
104
|
+
define_method(http_method.downcase) do |action_path, action = nil|
|
96
105
|
## Swap arguments if action in path variable
|
97
106
|
unless action
|
98
|
-
action =
|
99
|
-
|
107
|
+
action = action_path.to_sym
|
108
|
+
action_path = nil
|
100
109
|
end
|
101
|
-
##
|
102
|
-
route = Route.new(@
|
103
|
-
##
|
104
|
-
|
105
|
-
##
|
106
|
-
|
110
|
+
## Initialize new route
|
111
|
+
route = Route.new(@controller, action)
|
112
|
+
## Make path by controller method with parameners
|
113
|
+
action_path = Flame::Path.new(action_path).adapt(@controller, action)
|
114
|
+
## Validate action path
|
115
|
+
validate_action_path(action, action_path)
|
116
|
+
## Merge action path with controller path
|
117
|
+
path = Flame::Path.new(@path, action_path)
|
118
|
+
## Remove the same route if needed
|
119
|
+
remove_old_routes(action, route)
|
120
|
+
## Add new route
|
121
|
+
add_new_route(route, action, path, http_method)
|
107
122
|
end
|
108
123
|
end
|
109
124
|
|
@@ -111,42 +126,73 @@ module Flame
|
|
111
126
|
## to defaults pathes and HTTP methods
|
112
127
|
def defaults
|
113
128
|
rest
|
114
|
-
@
|
115
|
-
next if
|
129
|
+
@controller.actions.each do |action|
|
130
|
+
next if find_reverse_route(action)
|
116
131
|
send(:GET.downcase, action)
|
117
132
|
end
|
118
133
|
end
|
119
134
|
|
120
135
|
## Assign methods of the controller to REST architecture
|
121
136
|
def rest
|
122
|
-
rest_routes.each do |rest_route|
|
137
|
+
self.class.rest_routes.each do |rest_route|
|
123
138
|
action = rest_route[:action]
|
124
|
-
next if !@
|
125
|
-
|
139
|
+
next if !@controller.actions.include?(action) ||
|
140
|
+
find_reverse_route(action)
|
126
141
|
send(*rest_route.values.map(&:downcase))
|
127
142
|
end
|
128
143
|
end
|
129
144
|
|
145
|
+
using GorillaPatch::Namespace
|
146
|
+
using GorillaPatch::Transform
|
147
|
+
using GorillaPatch::DeepMerge
|
148
|
+
|
130
149
|
## Mount controller inside other (parent) controller
|
131
|
-
## @param
|
150
|
+
## @param controller [Flame::Controller] class of mounting controller
|
132
151
|
## @param path [String, nil] root path for mounting controller
|
133
152
|
## @yield Block of code for routes refine
|
134
|
-
def mount(
|
135
|
-
|
136
|
-
|
153
|
+
def mount(controller_name, path = nil, &block)
|
154
|
+
routes_refine = self.class.new(
|
155
|
+
@router, @controller.deconstantize, controller_name, path, &block
|
156
|
+
)
|
157
|
+
|
158
|
+
@endpoint.deep_merge! routes_refine.routes
|
159
|
+
|
160
|
+
@reverse_routes.merge!(
|
161
|
+
routes_refine.reverse_routes.transform_values do |hash|
|
162
|
+
hash.transform_values { |action_path| @path + action_path }
|
163
|
+
end
|
164
|
+
)
|
137
165
|
end
|
138
166
|
|
139
|
-
private
|
167
|
+
# private
|
140
168
|
|
141
169
|
## Execute block of refinings end sorting routes
|
142
170
|
def execute(&block)
|
143
171
|
instance_exec(&block) if block
|
144
172
|
defaults
|
145
|
-
@routes.sort!
|
146
173
|
end
|
147
174
|
|
148
|
-
def
|
149
|
-
@
|
175
|
+
def find_reverse_route(action)
|
176
|
+
@reverse_routes.dig(@controller.to_s, action)
|
177
|
+
end
|
178
|
+
|
179
|
+
def validate_action_path(action, action_path)
|
180
|
+
Validators::RouteArgumentsValidator.new(
|
181
|
+
@controller, action_path, action
|
182
|
+
).valid?
|
183
|
+
end
|
184
|
+
|
185
|
+
def remove_old_routes(action, new_route)
|
186
|
+
return unless (old_path = @reverse_routes[@controller.to_s]&.delete(action))
|
187
|
+
@routes.dig(*old_path.parts)
|
188
|
+
.delete_if { |_method, old_route| old_route == new_route }
|
189
|
+
end
|
190
|
+
|
191
|
+
def add_new_route(route, action, path, http_method)
|
192
|
+
path_routes, endpoint = path.to_routes_with_endpoint
|
193
|
+
endpoint[http_method] = route
|
194
|
+
@routes.deep_merge!(path_routes)
|
195
|
+
(@reverse_routes[@controller.to_s] ||= {})[action] = path
|
150
196
|
end
|
151
197
|
end
|
152
198
|
end
|
data/lib/flame/router/route.rb
CHANGED
@@ -7,74 +7,27 @@ module Flame
|
|
7
7
|
class Router
|
8
8
|
## Class for Route in Router.routes
|
9
9
|
class Route
|
10
|
-
attr_reader :
|
10
|
+
attr_reader :controller, :action
|
11
11
|
|
12
|
-
|
12
|
+
## Create a new instance
|
13
|
+
## @param controller [Flame::Controller] controller
|
14
|
+
## @param action [Symbol] action
|
15
|
+
def initialize(controller, action)
|
13
16
|
@controller = controller
|
14
17
|
@action = action
|
15
|
-
@method = method.to_sym.upcase
|
16
|
-
## Make path by controller method with parameners
|
17
|
-
action_path = Flame::Path.new(action_path).adapt(controller, action)
|
18
|
-
## Merge action path with controller path
|
19
|
-
@path = Flame::Path.new(ctrl_path, action_path)
|
20
|
-
Validators::RouteArgumentsValidator.new(
|
21
|
-
@controller, action_path, @action
|
22
|
-
).valid?
|
23
|
-
freeze
|
24
|
-
end
|
25
|
-
|
26
|
-
def freeze
|
27
|
-
@path.freeze
|
28
|
-
super
|
29
|
-
end
|
30
|
-
|
31
|
-
## Compare attributes for `Router.find_route`
|
32
|
-
## @param attrs [Hash] Hash of attributes for comparing
|
33
|
-
def compare_attributes(attrs)
|
34
|
-
attrs.each do |name, value|
|
35
|
-
next true if compare_attribute(name, value)
|
36
|
-
break false
|
37
|
-
end
|
38
18
|
end
|
39
19
|
|
40
20
|
## Method for Routes comparison
|
21
|
+
## @param other [Flame::Router::Route] other route
|
22
|
+
## @return [true, false] equal or not
|
41
23
|
def ==(other)
|
42
|
-
|
24
|
+
return false unless other.is_a? self.class
|
25
|
+
%i[controller action].reduce(true) do |result, method|
|
43
26
|
result && (
|
44
27
|
public_send(method) == other.public_send(method)
|
45
28
|
)
|
46
29
|
end
|
47
30
|
end
|
48
|
-
|
49
|
-
## Compare by:
|
50
|
-
## 1. path parts count (more is matter);
|
51
|
-
## 2. args position (father is matter);
|
52
|
-
## 3. HTTP-method (default).
|
53
|
-
def <=>(other)
|
54
|
-
path_result = other.path <=> path
|
55
|
-
return path_result unless path_result.zero?
|
56
|
-
method <=> other.method
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
## Helpers for `compare_attributes`
|
62
|
-
def compare_attribute(name, value)
|
63
|
-
case name
|
64
|
-
when :method
|
65
|
-
compare_method(value)
|
66
|
-
when :path
|
67
|
-
path.match? value
|
68
|
-
else
|
69
|
-
send(name) == value
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def compare_method(request_method)
|
74
|
-
request_method = request_method.upcase.to_sym
|
75
|
-
request_method = :GET if request_method == :HEAD
|
76
|
-
method.upcase.to_sym == request_method
|
77
|
-
end
|
78
31
|
end
|
79
32
|
end
|
80
33
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flame
|
4
|
+
class Router
|
5
|
+
## Custom Hash for routes
|
6
|
+
class Routes < Hash
|
7
|
+
## @param path_parts [Array<String, Flame::Path, Flame::Path::Part>]
|
8
|
+
## path parts for nested keys
|
9
|
+
## @example Initialize without keys
|
10
|
+
## Flame::Router::Routes.new # => {}
|
11
|
+
## @example Initialize with nested keys
|
12
|
+
## Flame::Router::Routes.new('/foo/bar/baz')
|
13
|
+
## # => { 'foo' => { 'bar' => { 'baz' => {} } } }
|
14
|
+
def initialize(*path_parts)
|
15
|
+
path = Flame::Path.new(*path_parts)
|
16
|
+
return if path.parts.empty?
|
17
|
+
nested_routes = self.class.new Flame::Path.new(*path.parts[1..-1])
|
18
|
+
# path.parts.reduce(result) { |hash, part| hash[part] ||= self.class.new }
|
19
|
+
self[path.parts.first] = nested_routes
|
20
|
+
end
|
21
|
+
|
22
|
+
## Move into Hash by equal key
|
23
|
+
## @param path_part [String, Flame::Path::Part, Symbol] requested key
|
24
|
+
## @return [Flame::Router::Routes, Flame::Router::Route, nil] found value
|
25
|
+
## @example Move by static path part
|
26
|
+
## routes = Flame::Router::Routes.new('/foo/bar/baz')
|
27
|
+
## routes['foo'] # => { 'bar' => { 'baz' => {} } }
|
28
|
+
## @example Move by HTTP-method
|
29
|
+
## routes = Flame::Router::Routes.new('/foo/bar')
|
30
|
+
## routes['foo']['bar'][:GET] = 42
|
31
|
+
## routes['foo']['bar'][:GET] # => 42
|
32
|
+
def [](key)
|
33
|
+
if key.is_a? String
|
34
|
+
key = Flame::Path::Part.new(key)
|
35
|
+
elsif !key.is_a?(Flame::Path::Part) && !key.is_a?(Symbol)
|
36
|
+
return
|
37
|
+
end
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
## Navigate to Routes or Route through static parts or arguments
|
42
|
+
## @param path_parts [Array<String, Flame::Path, Flame::Path::Part>]
|
43
|
+
## path or path parts as keys for navigating
|
44
|
+
## @return [Flame::Router::Routes, Flame::Router::Route, nil] found value
|
45
|
+
## @example Move by static path part and argument
|
46
|
+
## routes = Flame::Router::Routes.new('/foo/:first/bar')
|
47
|
+
## routes.navigate('foo', 'value') # => { 'bar' => {} }
|
48
|
+
def navigate(*path_parts)
|
49
|
+
path_parts = Flame::Path.new(*path_parts).parts
|
50
|
+
return dig_through_opt_args if path_parts.empty?
|
51
|
+
endpoint =
|
52
|
+
self[path_parts.first] ||
|
53
|
+
dig(first_opt_arg_key, path_parts.first) ||
|
54
|
+
self[first_arg_key]
|
55
|
+
endpoint&.navigate(*path_parts[1..-1])
|
56
|
+
end
|
57
|
+
|
58
|
+
## Dig through optional arguments as keys
|
59
|
+
## @return [Flame::Router::Routes] return most nested end-point
|
60
|
+
## without optional arguments
|
61
|
+
def dig_through_opt_args
|
62
|
+
self[first_opt_arg_key]&.dig_through_opt_args || self
|
63
|
+
end
|
64
|
+
|
65
|
+
def allow
|
66
|
+
methods = keys.select { |key| key.is_a? Symbol }
|
67
|
+
return if methods.empty?
|
68
|
+
methods.push(:OPTIONS).join(', ')
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def first_arg_key
|
74
|
+
keys.find do |key|
|
75
|
+
key.is_a?(Flame::Path::Part) && key.arg?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def first_opt_arg_key
|
80
|
+
keys.find do |key|
|
81
|
+
key.is_a?(Flame::Path::Part) && key.opt_arg?
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|