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/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
|