flame 4.18.1 → 5.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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/dispatcher.rb
CHANGED
@@ -4,9 +4,10 @@ require 'gorilla-patch/symbolize'
|
|
4
4
|
|
5
5
|
require_relative 'dispatcher/request'
|
6
6
|
require_relative 'dispatcher/response'
|
7
|
-
|
8
7
|
require_relative 'dispatcher/cookies'
|
8
|
+
|
9
9
|
require_relative 'dispatcher/static'
|
10
|
+
require_relative 'dispatcher/routes'
|
10
11
|
|
11
12
|
require_relative 'errors/route_not_found_error'
|
12
13
|
|
@@ -15,15 +16,19 @@ module Flame
|
|
15
16
|
class Dispatcher
|
16
17
|
GEM_STATIC_FILES = File.join __dir__, '..', '..', 'public'
|
17
18
|
|
19
|
+
extend Forwardable
|
20
|
+
def_delegators :@app_class, :path_to
|
21
|
+
|
18
22
|
attr_reader :request, :response
|
19
23
|
|
20
24
|
include Flame::Dispatcher::Static
|
25
|
+
include Flame::Dispatcher::Routes
|
21
26
|
|
22
27
|
## Initialize Dispatcher from Application#call
|
23
|
-
## @param
|
28
|
+
## @param app_class [Class] application class
|
24
29
|
## @param env Rack-environment object
|
25
|
-
def initialize(
|
26
|
-
@
|
30
|
+
def initialize(app_class, env)
|
31
|
+
@app_class = app_class
|
27
32
|
@env = env
|
28
33
|
@request = Flame::Dispatcher::Request.new(env)
|
29
34
|
@response = Flame::Dispatcher::Response.new
|
@@ -32,7 +37,8 @@ module Flame
|
|
32
37
|
## Start of execution the request
|
33
38
|
def run!
|
34
39
|
catch :halt do
|
35
|
-
|
40
|
+
try_options ||
|
41
|
+
try_static ||
|
36
42
|
try_static(dir: GEM_STATIC_FILES) ||
|
37
43
|
try_route ||
|
38
44
|
halt(404)
|
@@ -80,28 +86,14 @@ module Flame
|
|
80
86
|
|
81
87
|
## Application-config object as Hash
|
82
88
|
def config
|
83
|
-
@
|
89
|
+
@app_class.config
|
84
90
|
end
|
85
91
|
|
86
|
-
##
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
## @return [String] path for requested method, controller and parameters
|
92
|
-
## @example Path for `show(id)` method of `ArticlesController` with `id: 2`
|
93
|
-
## path_to ArticlesController, :show, id: 2 # => "/articles/show/2"
|
94
|
-
## @example Path for `new` method of `ArticlesController` with params
|
95
|
-
## path_to ArticlesController, :new, params: { author_id: 1 }
|
96
|
-
## # => "/articles/new?author_id=1"
|
97
|
-
def path_to(ctrl, action = :index, args = {})
|
98
|
-
route = @app.class.router.find_route(controller: ctrl, action: action)
|
99
|
-
raise Errors::RouteNotFoundError.new(ctrl, action) unless route
|
100
|
-
query = Rack::Utils.build_nested_query args.delete(:params)
|
101
|
-
query = nil if query&.empty?
|
102
|
-
path = route.path.assign_arguments(args)
|
103
|
-
path = '/' if path.empty?
|
104
|
-
URI::Generic.build(path: path, query: query).to_s
|
92
|
+
## Available routes endpoint
|
93
|
+
def available_endpoint
|
94
|
+
return @available_endpoint if defined? @available_endpoint
|
95
|
+
@available_endpoint =
|
96
|
+
@app_class.router.routes.navigate(*request.path.parts)
|
105
97
|
end
|
106
98
|
|
107
99
|
## Interrupt the execution of route, and set new optional data
|
@@ -117,7 +109,7 @@ module Flame
|
|
117
109
|
## @example Halt with 404, render template
|
118
110
|
## halt 404, render('errors/404')
|
119
111
|
## @example Halt with 200, set new headers
|
120
|
-
## halt 200, 'Cats!', 'Content-Type' => 'animal/cat'
|
112
|
+
## halt 200, 'Cats!', 'Content-Type' # => 'animal/cat'
|
121
113
|
def halt(new_status = nil, new_body = nil, new_headers = {})
|
122
114
|
status new_status if new_status
|
123
115
|
body new_body || (default_body_of_nearest_route if body.empty?)
|
@@ -144,50 +136,17 @@ module Flame
|
|
144
136
|
|
145
137
|
## All cached tilts (views) for application by Flame::Render
|
146
138
|
def cached_tilts
|
147
|
-
@
|
139
|
+
@app_class.cached_tilts
|
148
140
|
end
|
149
141
|
|
150
142
|
private
|
151
143
|
|
152
|
-
##
|
153
|
-
def
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
return nil unless route
|
159
|
-
status 200
|
160
|
-
execute_route(route)
|
161
|
-
end
|
162
|
-
|
163
|
-
## Execute route
|
164
|
-
## @param route [Flame::Route] route that must be executed
|
165
|
-
def execute_route(route, action = route.action)
|
166
|
-
params.merge! route.path.extract_arguments(request.path)
|
167
|
-
# route.execute(self)
|
168
|
-
controller = route.controller.new(self)
|
169
|
-
controller.send(:execute, action)
|
170
|
-
rescue => exception
|
171
|
-
# p 'rescue from dispatcher'
|
172
|
-
dump_error(exception)
|
173
|
-
status 500
|
174
|
-
controller&.send(:server_error, exception)
|
175
|
-
# p 're raise exception from dispatcher'
|
176
|
-
# raise exception
|
177
|
-
end
|
178
|
-
|
179
|
-
## Generate a default body of nearest route
|
180
|
-
def default_body_of_nearest_route
|
181
|
-
## Return nil if must be no body for current HTTP status
|
182
|
-
return if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status)
|
183
|
-
## Find the nearest route by the parts of requested path
|
184
|
-
route = @app.router.find_nearest_route(request.path)
|
185
|
-
## Return nil if the route not found
|
186
|
-
## or it's `default_body` method not defined
|
187
|
-
return default_body unless route
|
188
|
-
## Execute `default_body` method for the founded route
|
189
|
-
execute_route(route, :default_body)
|
190
|
-
default_body if body.empty?
|
144
|
+
## Return response if HTTP-method is OPTIONS
|
145
|
+
def try_options
|
146
|
+
return unless request.http_method == :OPTIONS
|
147
|
+
allow = available_endpoint&.allow
|
148
|
+
halt 404 unless allow
|
149
|
+
response.headers['Allow'] = allow
|
191
150
|
end
|
192
151
|
end
|
193
152
|
end
|
@@ -4,6 +4,10 @@ module Flame
|
|
4
4
|
class Dispatcher
|
5
5
|
## Helper class for cookies
|
6
6
|
class Cookies
|
7
|
+
## Create an instance of Cookies
|
8
|
+
## @param request_cookies [Hash{String=>Object}] cookies from request
|
9
|
+
## @param response [Flame::Dispatcher::Response, Rack::Response]
|
10
|
+
## response object for cookies setting
|
7
11
|
def initialize(request_cookies, response)
|
8
12
|
@request_cookies = request_cookies
|
9
13
|
@response = response
|
@@ -11,6 +15,7 @@ module Flame
|
|
11
15
|
|
12
16
|
## Get request cookies
|
13
17
|
## @param key [String, Symbol] name of cookie
|
18
|
+
## @return [Object] value of cookie
|
14
19
|
def [](key)
|
15
20
|
@request_cookies[key.to_s]
|
16
21
|
end
|
@@ -23,8 +28,11 @@ module Flame
|
|
23
28
|
## @example Delete `cat` cookie
|
24
29
|
## cookies['cat'] = nil
|
25
30
|
def []=(key, new_value)
|
26
|
-
|
27
|
-
|
31
|
+
if new_value.nil?
|
32
|
+
@response.delete_cookie(key.to_s, path: '/')
|
33
|
+
else
|
34
|
+
@response.set_cookie(key.to_s, value: new_value, path: '/')
|
35
|
+
end
|
28
36
|
end
|
29
37
|
end
|
30
38
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flame
|
4
|
+
class Dispatcher
|
5
|
+
## Module for working with routes
|
6
|
+
module Routes
|
7
|
+
private
|
8
|
+
|
9
|
+
## Find route and try execute it
|
10
|
+
def try_route
|
11
|
+
http_method = request.http_method
|
12
|
+
http_method = :GET if http_method == :HEAD
|
13
|
+
return unless available_endpoint
|
14
|
+
route = available_endpoint[http_method]
|
15
|
+
return unless route || available_endpoint.allow
|
16
|
+
halt(405, nil, 'Allow' => available_endpoint.allow) unless route
|
17
|
+
status 200
|
18
|
+
execute_route route
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
## Execute route
|
23
|
+
## @param route [Flame::Route] route that must be executed
|
24
|
+
def execute_route(route, action = route.action)
|
25
|
+
params.merge!(
|
26
|
+
@app_class.router.path_of(route).extract_arguments(request.path)
|
27
|
+
)
|
28
|
+
# route.execute(self)
|
29
|
+
controller = route.controller.new(self)
|
30
|
+
controller.send(:execute, action)
|
31
|
+
rescue StandardError => exception
|
32
|
+
# p 'rescue from dispatcher'
|
33
|
+
dump_error(exception)
|
34
|
+
status 500
|
35
|
+
controller&.send(:server_error, exception)
|
36
|
+
# p 're raise exception from dispatcher'
|
37
|
+
# raise exception
|
38
|
+
end
|
39
|
+
|
40
|
+
## Generate a default body of nearest route
|
41
|
+
def default_body_of_nearest_route
|
42
|
+
## Return nil if must be no body for current HTTP status
|
43
|
+
return if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status)
|
44
|
+
## Find the nearest route by the parts of requested path
|
45
|
+
route = @app_class.router.find_nearest_route(request.path)
|
46
|
+
## Return standard `default_body` if the route not found
|
47
|
+
return default_body unless route
|
48
|
+
## Execute `default_body` method for the founded route
|
49
|
+
body route.controller.new(self).send(:default_body)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -4,6 +4,11 @@ module Flame
|
|
4
4
|
class Dispatcher
|
5
5
|
## Module for working with static files
|
6
6
|
module Static
|
7
|
+
## Find static file by path
|
8
|
+
## @param filename [String] relative path to the static file
|
9
|
+
## @param dir [String]
|
10
|
+
## absolute local path of the directory with static files
|
11
|
+
## @return [Flame::Dispatcher::Static::StaticFile] instance of static file
|
7
12
|
def find_static(filename = request.path_info, dir: config[:public_dir])
|
8
13
|
StaticFile.new(filename, dir)
|
9
14
|
end
|
@@ -18,9 +23,9 @@ module Flame
|
|
18
23
|
end
|
19
24
|
|
20
25
|
def return_static(file)
|
21
|
-
|
26
|
+
response[Rack::CACHE_CONTROL] = 'public, max-age=31536000' # one year
|
27
|
+
halt 304 unless file.newer? request.env['HTTP_IF_MODIFIED_SINCE']
|
22
28
|
response.content_type = file.extname
|
23
|
-
response[Rack::CACHE_CONTROL] = 'no-cache'
|
24
29
|
response['Last-Modified'] = file.mtime.httpdate
|
25
30
|
body file.content
|
26
31
|
end
|
@@ -29,27 +34,27 @@ module Flame
|
|
29
34
|
class StaticFile
|
30
35
|
def initialize(filename, dir)
|
31
36
|
@filename = filename.to_s
|
32
|
-
@
|
37
|
+
@file_path = File.join dir, URI.decode_www_form_component(@filename)
|
33
38
|
end
|
34
39
|
|
35
40
|
def exist?
|
36
|
-
File.exist?(@
|
41
|
+
File.exist?(@file_path) && File.file?(@file_path)
|
37
42
|
end
|
38
43
|
|
39
44
|
def mtime
|
40
|
-
File.mtime(@
|
45
|
+
File.mtime(@file_path)
|
41
46
|
end
|
42
47
|
|
43
48
|
def extname
|
44
|
-
File.extname(@
|
49
|
+
File.extname(@file_path)
|
45
50
|
end
|
46
51
|
|
47
52
|
def newer?(http_since)
|
48
|
-
http_since
|
53
|
+
!http_since || mtime.to_i > Time.httpdate(http_since).to_i
|
49
54
|
end
|
50
55
|
|
51
56
|
def content
|
52
|
-
File.read(@
|
57
|
+
File.read(@file_path)
|
53
58
|
end
|
54
59
|
|
55
60
|
def path(with_version: false)
|
@@ -57,6 +62,8 @@ module Flame
|
|
57
62
|
with_version ? "#{path}?v=#{mtime.to_i}" : path
|
58
63
|
end
|
59
64
|
end
|
65
|
+
|
66
|
+
private_constant :StaticFile
|
60
67
|
end
|
61
68
|
end
|
62
69
|
end
|
@@ -4,11 +4,17 @@ module Flame
|
|
4
4
|
module Errors
|
5
5
|
## Error for Flame::Dispatcher.path_to
|
6
6
|
class ArgumentNotAssignedError < StandardError
|
7
|
+
## Create a new instance of error
|
8
|
+
## @param path [Flame::Path, String] path without argument
|
9
|
+
## @param argument [Flame::Path::Part, String, Symbol]
|
10
|
+
## not assigned argument
|
7
11
|
def initialize(path, argument)
|
8
12
|
@path = path
|
9
13
|
@argument = argument
|
10
14
|
end
|
11
15
|
|
16
|
+
## Calculated message of the error
|
17
|
+
## @return [String] message of the error
|
12
18
|
def message
|
13
19
|
"Argument '#{@argument}' for path '#{@path}' is not assigned"
|
14
20
|
end
|
@@ -4,11 +4,17 @@ module Flame
|
|
4
4
|
module Errors
|
5
5
|
## Error for Route initialization
|
6
6
|
class RouteArgumentsOrderError < StandardError
|
7
|
+
## Create a new instance of error
|
8
|
+
## @param path [Flame::Path, String] path with wrong arguments order
|
9
|
+
## @param wrong_ordered_arguments [Array<Symbol>]
|
10
|
+
## two wrong ordered arguments
|
7
11
|
def initialize(path, wrong_ordered_arguments)
|
8
12
|
@path = path
|
9
13
|
@wrong_ordered_arguments = wrong_ordered_arguments
|
10
14
|
end
|
11
15
|
|
16
|
+
## Calculated message of the error
|
17
|
+
## @return [String] message of the error
|
12
18
|
def message
|
13
19
|
"Path '#{@path}' should have" \
|
14
20
|
" '#{@wrong_ordered_arguments.first}' argument before" \
|
@@ -4,6 +4,14 @@ module Flame
|
|
4
4
|
module Errors
|
5
5
|
## Error for Route initialization
|
6
6
|
class RouteExtraArgumentsError < StandardError
|
7
|
+
## Create a new instance of error
|
8
|
+
## @param ctrl [Flame::Controller] controller
|
9
|
+
## @param action [Symbol] action
|
10
|
+
## @param path [Flame::Path, String] path
|
11
|
+
## @param extra [Hash] extra arguments
|
12
|
+
## @option extra [Symbol] :type required or optional
|
13
|
+
## @option extra [Symbol] :place extra arguments in controller or path
|
14
|
+
## @option extra [Array<Symbol>] :args extra arguments
|
7
15
|
def initialize(ctrl, action, path, extra)
|
8
16
|
@ctrl = ctrl
|
9
17
|
@action = action
|
@@ -15,6 +23,8 @@ module Flame
|
|
15
23
|
}[@extra[:type]]
|
16
24
|
end
|
17
25
|
|
26
|
+
## Calculated message of the error
|
27
|
+
## @return [String] message of the error
|
18
28
|
def message
|
19
29
|
case @extra[:place]
|
20
30
|
when :ctrl
|
@@ -4,13 +4,19 @@ module Flame
|
|
4
4
|
module Errors
|
5
5
|
## Error for Flame::Dispatcher.path_to
|
6
6
|
class RouteNotFoundError < StandardError
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
## Create a new instance of error
|
8
|
+
## @param controller [Flame::Controller]
|
9
|
+
## controller with which route not found
|
10
|
+
## @param action [Symbol] action with which route not found
|
11
|
+
def initialize(controller, action)
|
12
|
+
@controller = controller
|
13
|
+
@action = action
|
10
14
|
end
|
11
15
|
|
16
|
+
## Calculated message of the error
|
17
|
+
## @return [String] message of the error
|
12
18
|
def message
|
13
|
-
"Route with controller '#{@
|
19
|
+
"Route with controller '#{@controller}' and action '#{@action}'" \
|
14
20
|
' not found in application routes'
|
15
21
|
end
|
16
22
|
end
|
@@ -4,12 +4,18 @@ module Flame
|
|
4
4
|
module Errors
|
5
5
|
## Error for not found template file in Render
|
6
6
|
class TemplateNotFoundError < StandardError
|
7
|
+
## Create a new instance of error
|
8
|
+
## @param controller [Flame::Controller]
|
9
|
+
## controller from which template not found
|
10
|
+
## @param path [String, Symbol] path of not founded template
|
7
11
|
def initialize(controller, path)
|
8
12
|
@controller = controller
|
9
13
|
@controller = @controller.class unless @controller.is_a? Class
|
10
14
|
@path = path
|
11
15
|
end
|
12
16
|
|
17
|
+
## Calculated message of the error
|
18
|
+
## @return [String] message of the error
|
13
19
|
def message
|
14
20
|
"Template '#{@path}' not found for '#{@controller}'"
|
15
21
|
end
|
data/lib/flame/path.rb
CHANGED
@@ -7,20 +7,29 @@ require_relative 'errors/argument_not_assigned_error'
|
|
7
7
|
module Flame
|
8
8
|
## Class for working with paths
|
9
9
|
class Path
|
10
|
+
## Merge parts of path to one path
|
11
|
+
## @param parts [Array<String, Flame::Path>] parts of expected path
|
12
|
+
## @return [Flame::Path] path from parts
|
10
13
|
def self.merge(*parts)
|
11
|
-
parts.join('/').gsub(%r{
|
14
|
+
parts.join('/').gsub(%r|/{2,}|, '/')
|
12
15
|
end
|
13
16
|
|
17
|
+
## Create a new instance
|
18
|
+
## @param paths [String, Flame::Path, Array<String, Flame::Path>]
|
19
|
+
## paths as parts for new path
|
14
20
|
def initialize(*paths)
|
15
21
|
@path = self.class.merge(*paths)
|
16
22
|
freeze
|
17
23
|
end
|
18
24
|
|
25
|
+
## Return parts of path, splitted by slash (`/`)
|
26
|
+
## @return [Array<Flame::Path::Part>] array of path parts
|
19
27
|
def parts
|
20
28
|
@parts ||= @path.to_s.split('/').reject(&:empty?)
|
21
|
-
.map! { |part|
|
29
|
+
.map! { |part| self.class::Part.new(part) }
|
22
30
|
end
|
23
31
|
|
32
|
+
## Freeze all strings in object
|
24
33
|
def freeze
|
25
34
|
@path.freeze
|
26
35
|
parts.each(&:freeze)
|
@@ -28,7 +37,16 @@ module Flame
|
|
28
37
|
super
|
29
38
|
end
|
30
39
|
|
40
|
+
## Create new instance from self and other by concatinating
|
41
|
+
## @param other [Flame::Path, String] other path which will be concatinated
|
42
|
+
## @return [Flame::Path] result of concatinating
|
43
|
+
def +(other)
|
44
|
+
self.class.new(self, other)
|
45
|
+
end
|
46
|
+
|
31
47
|
## Compare by parts count and the first arg position
|
48
|
+
## @param other [Flame::Path] other path
|
49
|
+
## @return [-1, 0, 1] result of comparing
|
32
50
|
def <=>(other)
|
33
51
|
self_parts, other_parts = [self, other].map(&:parts)
|
34
52
|
parts_size = self_parts.size <=> other_parts.size
|
@@ -41,36 +59,32 @@ module Flame
|
|
41
59
|
end
|
42
60
|
end
|
43
61
|
|
62
|
+
## Compare with other path by parts
|
63
|
+
## @param other [Flame::Path, String] other path
|
64
|
+
## @return [true, false] equal or not
|
44
65
|
def ==(other)
|
45
66
|
other = self.class.new(other) if other.is_a? String
|
46
67
|
parts == other.parts
|
47
68
|
end
|
48
69
|
|
49
70
|
## Complete path for the action of controller
|
71
|
+
## @param ctrl [Flame::Controller] to which controller adapt
|
72
|
+
## @param action [Symbol] to which action of controller adapt
|
73
|
+
## @return [Flame::Path] adapted path
|
50
74
|
## @todo Add :arg:type support (:id:num, :name:str, etc.)
|
51
75
|
def adapt(ctrl, action)
|
52
76
|
parameters = ctrl.instance_method(action).parameters
|
53
77
|
parameters.map! do |parameter|
|
54
78
|
parameter_type, parameter_name = parameter
|
55
|
-
path_part =
|
79
|
+
path_part = self.class::Part.new parameter_name, arg: parameter_type
|
56
80
|
path_part unless parts.include? path_part
|
57
81
|
end
|
58
82
|
self.class.new @path.empty? ? "/#{action}" : self, *parameters.compact
|
59
83
|
end
|
60
84
|
|
61
|
-
## Can recieve other as String
|
62
|
-
def match?(other)
|
63
|
-
other = self.class.new(other) if other.is_a? String
|
64
|
-
return false unless other_contain_required_parts?(other)
|
65
|
-
result = [self, other].map { |path| path.parts.size }.max.times do |i|
|
66
|
-
break false unless compare_parts parts[i], other.parts[i]
|
67
|
-
end
|
68
|
-
result = true if result
|
69
|
-
result
|
70
|
-
end
|
71
|
-
|
72
85
|
## Extract arguments from other path with values at arguments
|
73
86
|
## @param other_path [Flame::Path] other path with values at arguments
|
87
|
+
## @return [Hash{Symbol => String}] hash of arguments from two paths
|
74
88
|
def extract_arguments(other_path)
|
75
89
|
parts.each_with_index.with_object({}) do |(part, i), args|
|
76
90
|
other_part = other_path.parts[i].to_s
|
@@ -78,39 +92,35 @@ module Flame
|
|
78
92
|
break args if part.opt_arg? && other_part.empty?
|
79
93
|
args[
|
80
94
|
part[(part.opt_arg? ? 2 : 1)..-1].to_sym
|
81
|
-
] = URI.
|
95
|
+
] = URI.decode_www_form_component(other_part)
|
82
96
|
end
|
83
97
|
end
|
84
98
|
|
85
|
-
## Assign arguments to path for `Controller
|
99
|
+
## Assign arguments to path for `Controller#path_to`
|
86
100
|
## @param args [Hash] arguments for assigning
|
87
101
|
def assign_arguments(args = {})
|
88
102
|
result_parts = parts.map { |part| assign_argument(part, args) }.compact
|
89
103
|
self.class.merge result_parts.unshift(nil)
|
90
104
|
end
|
91
105
|
|
106
|
+
## @return [String] path as String
|
92
107
|
def to_s
|
93
108
|
@path
|
94
109
|
end
|
95
110
|
alias to_str to_s
|
96
111
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
112
|
+
## Path parts as keys of nested Hashes
|
113
|
+
## @return [Array(Flame::Router::Routes, Flame::Router::Routes)]
|
114
|
+
## whole Routes (parent) and the endpoint (most nested Routes)
|
115
|
+
def to_routes_with_endpoint
|
116
|
+
endpoint =
|
117
|
+
parts.reduce(result = Flame::Router::Routes.new) do |hash, part|
|
118
|
+
hash[part] ||= Flame::Router::Routes.new
|
119
|
+
end
|
120
|
+
[result, endpoint]
|
105
121
|
end
|
106
122
|
|
107
|
-
|
108
|
-
return unless part
|
109
|
-
return if other_part.nil? && !part.opt_arg?
|
110
|
-
# p other_part, part
|
111
|
-
return true if part.arg?
|
112
|
-
return true if other_part == part
|
113
|
-
end
|
123
|
+
private
|
114
124
|
|
115
125
|
## Helpers for `assign_arguments`
|
116
126
|
def assign_argument(part, args = {})
|
@@ -128,7 +138,7 @@ module Flame
|
|
128
138
|
end
|
129
139
|
|
130
140
|
## Class for one part of Path
|
131
|
-
class
|
141
|
+
class Part
|
132
142
|
extend Forwardable
|
133
143
|
|
134
144
|
def_delegators :@part, :[], :hash
|
@@ -136,33 +146,53 @@ module Flame
|
|
136
146
|
ARG_CHAR = ':'
|
137
147
|
ARG_CHAR_OPT = '?'
|
138
148
|
|
149
|
+
## Create new instance from String
|
150
|
+
## @param part [String] path part as String
|
151
|
+
## @param arg [Boolean] is this part an argument
|
139
152
|
def initialize(part, arg: false)
|
140
153
|
@part = "#{ARG_CHAR if arg}#{ARG_CHAR_OPT if arg == :opt}#{part}"
|
154
|
+
freeze
|
141
155
|
end
|
142
156
|
|
157
|
+
## Freeze object
|
143
158
|
def freeze
|
144
159
|
@part.freeze
|
145
160
|
super
|
146
161
|
end
|
147
162
|
|
163
|
+
## Compare with another
|
164
|
+
## @param other [Flame::Path::Part] other path part
|
165
|
+
## @return [true, false] equal or not
|
148
166
|
def ==(other)
|
149
167
|
to_s == other.to_s
|
150
168
|
end
|
151
169
|
|
152
170
|
alias eql? ==
|
153
171
|
|
172
|
+
## Convert path part to String
|
173
|
+
## @return [String] path part as String
|
154
174
|
def to_s
|
155
175
|
@part
|
156
176
|
end
|
157
177
|
|
178
|
+
## Is the path part an argument
|
179
|
+
## @return [true, false] an argument or not
|
158
180
|
def arg?
|
159
181
|
@part.start_with? ARG_CHAR
|
160
182
|
end
|
161
183
|
|
184
|
+
## Is the path part an optional argument
|
185
|
+
## @return [true, false] an optional argument or not
|
162
186
|
def opt_arg?
|
163
|
-
@part[1] == ARG_CHAR_OPT
|
187
|
+
arg? && @part[1] == ARG_CHAR_OPT
|
164
188
|
end
|
165
189
|
|
190
|
+
# def req_arg?
|
191
|
+
# arg? && !opt_arg?
|
192
|
+
# end
|
193
|
+
|
194
|
+
## Path part as a String without arguments characters
|
195
|
+
## @return [String] clean String
|
166
196
|
def clean
|
167
197
|
@part.delete ARG_CHAR + ARG_CHAR_OPT
|
168
198
|
end
|