flame 4.18.1 → 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 -4
- data/lib/flame/application.rb +93 -40
- data/lib/flame/config.rb +73 -0
- data/lib/flame/controller.rb +62 -98
- data/lib/flame/controller/actions.rb +122 -0
- data/lib/flame/controller/cookies.rb +44 -0
- data/lib/flame/controller/path_to.rb +63 -0
- data/lib/flame/dispatcher.rb +44 -73
- data/lib/flame/dispatcher/request.rb +33 -4
- data/lib/flame/dispatcher/routes.rb +66 -0
- data/lib/flame/dispatcher/static.rb +26 -15
- data/lib/flame/errors/argument_not_assigned_error.rb +7 -6
- 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 +9 -8
- data/lib/flame/errors/route_extra_arguments_error.rb +18 -18
- data/lib/flame/errors/route_not_found_error.rb +8 -7
- data/lib/flame/errors/template_not_found_error.rb +6 -6
- data/lib/flame/path.rb +141 -55
- data/lib/flame/render.rb +46 -15
- data/lib/flame/router.rb +41 -127
- data/lib/flame/router/controller_finder.rb +56 -0
- data/lib/flame/router/route.rb +16 -54
- data/lib/flame/router/routes.rb +136 -0
- data/lib/flame/router/routes_refine.rb +144 -0
- data/lib/flame/router/routes_refine/mounting.rb +57 -0
- data/lib/flame/validators.rb +21 -11
- data/lib/flame/version.rb +1 -1
- metadata +139 -84
- data/bin/flame +0 -71
- data/lib/flame/application/config.rb +0 -43
- data/lib/flame/dispatcher/cookies.rb +0 -31
- data/template/.gitignore +0 -11
- data/template/Gemfile +0 -15
- data/template/Rakefile.erb +0 -64
- data/template/app.rb.erb +0 -7
- data/template/config.ru.erb +0 -20
- data/template/config/config.rb.erb +0 -14
- data/template/config/database.example.yml +0 -5
- data/template/config/sequel.rb.erb +0 -15
- data/template/config/thin.example.yml +0 -18
- data/template/controllers/_base_controller.rb.erb +0 -13
- data/template/db/.keep +0 -0
- 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 -49
- data/template/views/.keep +0 -0
@@ -0,0 +1,66 @@
|
|
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
|
+
|
15
|
+
route = available_endpoint[http_method]
|
16
|
+
return unless route || available_endpoint.allow
|
17
|
+
|
18
|
+
halt(405, nil, 'Allow' => available_endpoint.allow) unless route
|
19
|
+
status 200
|
20
|
+
execute_route route
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
## Execute route
|
25
|
+
## @param route [Flame::Route] route that must be executed
|
26
|
+
def execute_route(route, action = route.action)
|
27
|
+
params.merge!(
|
28
|
+
router.path_of(route).extract_arguments(request.path)
|
29
|
+
)
|
30
|
+
# route.execute(self)
|
31
|
+
@current_controller = route.controller.new(self)
|
32
|
+
@current_controller.send(:execute, action)
|
33
|
+
rescue StandardError, SyntaxError => e
|
34
|
+
# p 'rescue from dispatcher'
|
35
|
+
dump_error(e)
|
36
|
+
status 500
|
37
|
+
@current_controller&.send(:server_error, e)
|
38
|
+
# p 're raise error from dispatcher'
|
39
|
+
# raise e
|
40
|
+
end
|
41
|
+
|
42
|
+
## Generate a default body of nearest route
|
43
|
+
def default_body_of_nearest_route
|
44
|
+
## Return nil if must be no body for current HTTP status
|
45
|
+
return if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status)
|
46
|
+
|
47
|
+
## Find the nearest route by the parts of requested path
|
48
|
+
route = router.find_nearest_route(request.path)
|
49
|
+
## Return standard `default_body` if the route not found
|
50
|
+
return default_body unless route
|
51
|
+
|
52
|
+
return not_found_body(route) if response.not_found?
|
53
|
+
|
54
|
+
controller =
|
55
|
+
(@current_controller if defined?(@current_controller)) || route.controller.new(self)
|
56
|
+
controller.send :default_body
|
57
|
+
end
|
58
|
+
|
59
|
+
def not_found_body(route)
|
60
|
+
## Execute `not_found` method as action for the founded route
|
61
|
+
execute_route(route, :not_found)
|
62
|
+
body
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -1,9 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'cgi'
|
4
|
+
|
3
5
|
module Flame
|
4
6
|
class Dispatcher
|
5
7
|
## Module for working with static files
|
6
8
|
module Static
|
9
|
+
## Find static file by path
|
10
|
+
## @param filename [String] relative path to the static file
|
11
|
+
## @param dir [String]
|
12
|
+
## absolute local path of the directory with static files
|
13
|
+
## @return [Flame::Dispatcher::Static::StaticFile] instance of static file
|
7
14
|
def find_static(filename = request.path_info, dir: config[:public_dir])
|
8
15
|
StaticFile.new(filename, dir)
|
9
16
|
end
|
@@ -11,52 +18,56 @@ module Flame
|
|
11
18
|
private
|
12
19
|
|
13
20
|
## Find static files and try return it
|
14
|
-
def try_static(*args)
|
15
|
-
file = find_static(*args)
|
21
|
+
def try_static(*args, **kwargs)
|
22
|
+
file = find_static(*args, **kwargs)
|
16
23
|
return nil unless file.exist?
|
24
|
+
|
25
|
+
halt 400 unless file.within_directory
|
17
26
|
return_static(file)
|
18
27
|
end
|
19
28
|
|
20
29
|
def return_static(file)
|
21
|
-
|
30
|
+
response[Rack::CACHE_CONTROL] = 'public, max-age=31536000' # one year
|
31
|
+
halt 304 unless file.newer? request.env['HTTP_IF_MODIFIED_SINCE']
|
22
32
|
response.content_type = file.extname
|
23
|
-
response[Rack::CACHE_CONTROL] = 'no-cache'
|
24
33
|
response['Last-Modified'] = file.mtime.httpdate
|
25
34
|
body file.content
|
26
35
|
end
|
27
36
|
|
28
37
|
## Class for static files with helpers methods
|
29
38
|
class StaticFile
|
39
|
+
attr_reader :extname, :within_directory
|
40
|
+
|
30
41
|
def initialize(filename, dir)
|
31
42
|
@filename = filename.to_s
|
32
|
-
@
|
43
|
+
@directory = File.expand_path dir
|
44
|
+
@file_path = File.expand_path File.join dir, CGI.unescape(@filename)
|
45
|
+
@extname = File.extname(@file_path)
|
46
|
+
@within_directory = @file_path.start_with? @directory
|
33
47
|
end
|
34
48
|
|
35
49
|
def exist?
|
36
|
-
File.exist?(@
|
50
|
+
File.exist?(@file_path) && File.file?(@file_path)
|
37
51
|
end
|
38
52
|
|
39
53
|
def mtime
|
40
|
-
File.mtime(@
|
41
|
-
end
|
42
|
-
|
43
|
-
def extname
|
44
|
-
File.extname(@path)
|
54
|
+
File.mtime(@file_path)
|
45
55
|
end
|
46
56
|
|
47
57
|
def newer?(http_since)
|
48
|
-
http_since
|
58
|
+
!http_since || mtime.to_i > Time.httpdate(http_since).to_i
|
49
59
|
end
|
50
60
|
|
51
61
|
def content
|
52
|
-
File.read(@
|
62
|
+
File.read(@file_path)
|
53
63
|
end
|
54
64
|
|
55
65
|
def path(with_version: false)
|
56
|
-
|
57
|
-
with_version ? "#{path}?v=#{mtime.to_i}" : path
|
66
|
+
with_version ? "#{@filename}?v=#{mtime.to_i}" : @filename
|
58
67
|
end
|
59
68
|
end
|
69
|
+
|
70
|
+
private_constant :StaticFile
|
60
71
|
end
|
61
72
|
end
|
62
73
|
end
|
@@ -4,13 +4,14 @@ 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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def message
|
13
|
-
"Argument '#{@argument}' for path '#{@path}' is not assigned"
|
12
|
+
super(
|
13
|
+
"Argument '#{argument}' for path '#{path}' is not assigned"
|
14
|
+
)
|
14
15
|
end
|
15
16
|
end
|
16
17
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flame
|
4
|
+
module Errors
|
5
|
+
## Error for not found config file in Config
|
6
|
+
class ConfigFileNotFoundError < StandardError
|
7
|
+
## Create a new instance of error
|
8
|
+
## @param file_name [String]
|
9
|
+
## file name mask by which file was not found
|
10
|
+
## @param directory [String] directory in which file was not found
|
11
|
+
def initialize(file_name, directory)
|
12
|
+
directory = directory.sub(%r{^/+}, '').sub(%r{/+$}, '')
|
13
|
+
super "Config file '#{file_name}' not found in '#{directory}/'"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flame
|
4
|
+
module Errors
|
5
|
+
## Error for not found controller by name in namespace
|
6
|
+
class ControllerNotFoundError < StandardError
|
7
|
+
## Create a new instance of error
|
8
|
+
## @param controller_name [Symbol, String]
|
9
|
+
## name of controller which not found
|
10
|
+
## @param namespace [Module]
|
11
|
+
## namespace for which controller not found
|
12
|
+
def initialize(controller_name, namespace)
|
13
|
+
super(
|
14
|
+
"Controller '#{controller_name}' not found for '#{namespace}'"
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -4,15 +4,16 @@ 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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
"Path '#{@path}' should have" \
|
14
|
-
" '#{@wrong_ordered_arguments.first}' argument before" \
|
15
|
-
" '#{@wrong_ordered_arguments.last}'"
|
12
|
+
super(
|
13
|
+
"Path '#{path}' should have" \
|
14
|
+
" '#{wrong_ordered_arguments.first}' argument before" \
|
15
|
+
" '#{wrong_ordered_arguments.last}'"
|
16
|
+
)
|
16
17
|
end
|
17
18
|
end
|
18
19
|
end
|
@@ -4,29 +4,29 @@ 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
|
-
|
9
|
-
@action = action
|
10
|
-
@path = path
|
11
|
-
@extra = extra
|
12
|
-
@extra[:type_name] = {
|
13
|
-
req: 'required',
|
14
|
-
opt: 'optional'
|
15
|
-
}[@extra[:type]]
|
16
|
-
end
|
16
|
+
extra[:type_name] = { req: 'required', opt: 'optional' }[extra[:type]]
|
17
17
|
|
18
|
-
|
19
|
-
case @extra[:place]
|
20
|
-
when :ctrl
|
18
|
+
entity = {
|
21
19
|
## Error if path has no arguments, that controller's method has
|
22
20
|
## NOTE: It isn't using because `Flame::Path#adopt`
|
23
|
-
"Path '#{
|
24
|
-
" arguments #{@extra[:args].inspect}"
|
25
|
-
when :path
|
21
|
+
ctrl: "Path '#{path}'",
|
26
22
|
## Error if path has more arguments, than controller's method
|
27
|
-
"Action '#{
|
28
|
-
|
29
|
-
|
23
|
+
path: "Action '#{ctrl}##{action}'"
|
24
|
+
}[extra[:place]]
|
25
|
+
|
26
|
+
super(
|
27
|
+
"#{entity} has no " \
|
28
|
+
"#{extra[:type_name]} arguments #{extra[:args].inspect}"
|
29
|
+
)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -4,14 +4,15 @@ module Flame
|
|
4
4
|
module Errors
|
5
5
|
## Error for Flame::Dispatcher.path_to
|
6
6
|
class RouteNotFoundError < StandardError
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
+
super(
|
13
|
+
"Route with controller '#{controller}' and action '#{action}'" \
|
14
14
|
' not found in application routes'
|
15
|
+
)
|
15
16
|
end
|
16
17
|
end
|
17
18
|
end
|
@@ -4,14 +4,14 @@ 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
|
-
|
9
|
-
@controller = @controller.class unless @controller.is_a? Class
|
10
|
-
@path = path
|
11
|
-
end
|
12
|
+
controller = controller.class unless controller.is_a? Class
|
12
13
|
|
13
|
-
|
14
|
-
"Template '#{@path}' not found for '#{@controller}'"
|
14
|
+
super "Template '#{path}' not found for '#{controller}'"
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
data/lib/flame/path.rb
CHANGED
@@ -1,26 +1,41 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'forwardable'
|
4
|
+
require 'memery'
|
4
5
|
|
5
6
|
require_relative 'errors/argument_not_assigned_error'
|
6
7
|
|
7
8
|
module Flame
|
8
9
|
## Class for working with paths
|
9
10
|
class Path
|
11
|
+
include Memery
|
12
|
+
|
13
|
+
extend Forwardable
|
14
|
+
def_delegators :to_s, :include?
|
15
|
+
|
16
|
+
## Merge parts of path to one path
|
17
|
+
## @param parts [Array<String, Flame::Path>] parts of expected path
|
18
|
+
## @return [Flame::Path] path from parts
|
10
19
|
def self.merge(*parts)
|
11
|
-
parts.join('/').gsub(%r{
|
20
|
+
parts.join('/').gsub(%r|/{2,}|, '/')
|
12
21
|
end
|
13
22
|
|
23
|
+
## Create a new instance
|
24
|
+
## @param paths [String, Flame::Path, Array<String, Flame::Path>]
|
25
|
+
## paths as parts for new path
|
14
26
|
def initialize(*paths)
|
15
27
|
@path = self.class.merge(*paths)
|
16
28
|
freeze
|
17
29
|
end
|
18
30
|
|
19
|
-
|
20
|
-
|
21
|
-
|
31
|
+
## Return parts of path, splitted by slash (`/`)
|
32
|
+
## @return [Array<Flame::Path::Part>] array of path parts
|
33
|
+
memoize def parts
|
34
|
+
@path.to_s.split('/').reject(&:empty?)
|
35
|
+
.map! { |part| self.class::Part.new(part) }
|
22
36
|
end
|
23
37
|
|
38
|
+
## Freeze all strings in object
|
24
39
|
def freeze
|
25
40
|
@path.freeze
|
26
41
|
parts.each(&:freeze)
|
@@ -28,143 +43,214 @@ module Flame
|
|
28
43
|
super
|
29
44
|
end
|
30
45
|
|
46
|
+
## Create new instance from self and other by concatinating
|
47
|
+
## @param other [Flame::Path, String] other path which will be concatinated
|
48
|
+
## @return [Flame::Path] result of concatinating
|
49
|
+
def +(other)
|
50
|
+
self.class.new(self, other)
|
51
|
+
end
|
52
|
+
|
31
53
|
## Compare by parts count and the first arg position
|
54
|
+
## @param other [Flame::Path] other path
|
55
|
+
## @return [-1, 0, 1] result of comparing
|
32
56
|
def <=>(other)
|
33
57
|
self_parts, other_parts = [self, other].map(&:parts)
|
34
|
-
|
35
|
-
return
|
36
|
-
|
37
|
-
|
38
|
-
break -1 if self_part.arg? && !other_part.arg?
|
39
|
-
break 1 if other_part.arg? && !self_part.arg?
|
40
|
-
result
|
41
|
-
end
|
58
|
+
by_parts_size = self_parts.size <=> other_parts.size
|
59
|
+
return by_parts_size unless by_parts_size.zero?
|
60
|
+
|
61
|
+
compare_by_args_in_parts self_parts.zip(other_parts)
|
42
62
|
end
|
43
63
|
|
64
|
+
## Compare with other path by parts
|
65
|
+
## @param other [Flame::Path, String] other path
|
66
|
+
## @return [true, false] equal or not
|
44
67
|
def ==(other)
|
45
68
|
other = self.class.new(other) if other.is_a? String
|
46
69
|
parts == other.parts
|
47
70
|
end
|
48
71
|
|
49
72
|
## Complete path for the action of controller
|
73
|
+
## @param ctrl [Flame::Controller] to which controller adapt
|
74
|
+
## @param action [Symbol] to which action of controller adapt
|
75
|
+
## @return [Flame::Path] adapted path
|
50
76
|
## @todo Add :arg:type support (:id:num, :name:str, etc.)
|
51
77
|
def adapt(ctrl, action)
|
52
78
|
parameters = ctrl.instance_method(action).parameters
|
53
79
|
parameters.map! do |parameter|
|
54
80
|
parameter_type, parameter_name = parameter
|
55
|
-
path_part =
|
81
|
+
path_part = self.class::Part.new parameter_name, arg: parameter_type
|
56
82
|
path_part unless parts.include? path_part
|
57
83
|
end
|
58
84
|
self.class.new @path.empty? ? "/#{action}" : self, *parameters.compact
|
59
85
|
end
|
60
86
|
|
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
87
|
## Extract arguments from other path with values at arguments
|
73
88
|
## @param other_path [Flame::Path] other path with values at arguments
|
89
|
+
## @return [Hash{Symbol => String}] hash of arguments from two paths
|
74
90
|
def extract_arguments(other_path)
|
75
|
-
|
76
|
-
other_part = other_path.parts[i].to_s
|
77
|
-
next args unless part.arg?
|
78
|
-
break args if part.opt_arg? && other_part.empty?
|
79
|
-
args[
|
80
|
-
part[(part.opt_arg? ? 2 : 1)..-1].to_sym
|
81
|
-
] = URI.decode(other_part)
|
82
|
-
end
|
91
|
+
Extractor.new(parts, other_path.parts).run
|
83
92
|
end
|
84
93
|
|
85
|
-
## Assign arguments to path for `Controller
|
94
|
+
## Assign arguments to path for `Controller#path_to`
|
86
95
|
## @param args [Hash] arguments for assigning
|
87
96
|
def assign_arguments(args = {})
|
88
97
|
result_parts = parts.map { |part| assign_argument(part, args) }.compact
|
89
98
|
self.class.merge result_parts.unshift(nil)
|
90
99
|
end
|
91
100
|
|
101
|
+
## @return [String] path as String
|
92
102
|
def to_s
|
93
103
|
@path
|
94
104
|
end
|
95
105
|
alias to_str to_s
|
96
106
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
107
|
+
## Path parts as keys of nested Hashes
|
108
|
+
## @return [Array(Flame::Router::Routes, Flame::Router::Routes)]
|
109
|
+
## whole Routes (parent) and the endpoint (most nested Routes)
|
110
|
+
def to_routes_with_endpoint
|
111
|
+
endpoint =
|
112
|
+
parts.reduce(result = Flame::Router::Routes.new) do |hash, part|
|
113
|
+
hash[part] ||= Flame::Router::Routes.new
|
114
|
+
end
|
115
|
+
[result, endpoint]
|
105
116
|
end
|
106
117
|
|
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
|
118
|
+
private
|
114
119
|
|
115
120
|
## Helpers for `assign_arguments`
|
116
121
|
def assign_argument(part, args = {})
|
117
122
|
## Not argument
|
118
123
|
return part unless part.arg?
|
119
124
|
## Not required argument
|
120
|
-
return args
|
125
|
+
return args.delete(part[2..-1].to_sym) if part.opt_arg?
|
126
|
+
|
121
127
|
## Required argument
|
122
|
-
param = args
|
128
|
+
param = args.delete(part[1..-1].to_sym)
|
123
129
|
## Required argument is nil
|
124
130
|
error = Errors::ArgumentNotAssignedError.new(@path, part)
|
125
131
|
raise error if param.nil?
|
132
|
+
|
126
133
|
## All is ok
|
127
134
|
param
|
128
135
|
end
|
129
136
|
|
137
|
+
def compare_by_args_in_parts(self_and_other_parts)
|
138
|
+
result = 0
|
139
|
+
|
140
|
+
self_and_other_parts.each do |self_part, other_part|
|
141
|
+
if self_part.arg?
|
142
|
+
break result = -1 unless other_part.arg?
|
143
|
+
elsif other_part.arg?
|
144
|
+
break result = 1
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
result
|
149
|
+
end
|
150
|
+
|
151
|
+
## Class for extracting arguments from other path
|
152
|
+
class Extractor
|
153
|
+
def initialize(parts, other_parts)
|
154
|
+
@parts = parts
|
155
|
+
@other_parts = other_parts
|
156
|
+
|
157
|
+
@index = 0
|
158
|
+
@other_index = 0
|
159
|
+
|
160
|
+
@args = {}
|
161
|
+
end
|
162
|
+
|
163
|
+
def run
|
164
|
+
@parts.each do |part|
|
165
|
+
next static_part_found unless part.arg?
|
166
|
+
|
167
|
+
break if part.opt_arg? && @other_parts.count <= @other_index
|
168
|
+
|
169
|
+
@args[part.to_sym] = extract
|
170
|
+
@index += 1
|
171
|
+
end
|
172
|
+
|
173
|
+
@args
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
|
178
|
+
def static_part_found
|
179
|
+
@index += 1
|
180
|
+
@other_index += 1
|
181
|
+
end
|
182
|
+
|
183
|
+
def extract
|
184
|
+
other_part = @other_parts[@other_index]
|
185
|
+
|
186
|
+
return if @parts[@index.next] == other_part
|
187
|
+
|
188
|
+
@other_index += 1
|
189
|
+
URI.decode_www_form_component(other_part)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
private_constant :Extractor
|
194
|
+
|
130
195
|
## Class for one part of Path
|
131
|
-
class
|
196
|
+
class Part
|
132
197
|
extend Forwardable
|
133
198
|
|
134
|
-
def_delegators
|
199
|
+
def_delegators :to_s, :[], :hash, :size, :empty?, :b, :inspect
|
135
200
|
|
136
201
|
ARG_CHAR = ':'
|
137
202
|
ARG_CHAR_OPT = '?'
|
138
203
|
|
204
|
+
## Create new instance from String
|
205
|
+
## @param part [String] path part as String
|
206
|
+
## @param arg [Boolean] is this part an argument
|
139
207
|
def initialize(part, arg: false)
|
140
208
|
@part = "#{ARG_CHAR if arg}#{ARG_CHAR_OPT if arg == :opt}#{part}"
|
209
|
+
freeze
|
141
210
|
end
|
142
211
|
|
212
|
+
## Freeze object
|
143
213
|
def freeze
|
144
214
|
@part.freeze
|
145
215
|
super
|
146
216
|
end
|
147
217
|
|
218
|
+
## Compare with another
|
219
|
+
## @param other [Flame::Path::Part] other path part
|
220
|
+
## @return [true, false] equal or not
|
148
221
|
def ==(other)
|
149
222
|
to_s == other.to_s
|
150
223
|
end
|
151
224
|
|
152
225
|
alias eql? ==
|
153
226
|
|
227
|
+
## Convert path part to String
|
228
|
+
## @return [String] path part as String
|
154
229
|
def to_s
|
155
230
|
@part
|
156
231
|
end
|
232
|
+
alias to_str to_s
|
157
233
|
|
234
|
+
## Is the path part an argument
|
235
|
+
## @return [true, false] an argument or not
|
158
236
|
def arg?
|
159
237
|
@part.start_with? ARG_CHAR
|
160
238
|
end
|
161
239
|
|
240
|
+
## Is the path part an optional argument
|
241
|
+
## @return [true, false] an optional argument or not
|
162
242
|
def opt_arg?
|
163
|
-
@part[1] == ARG_CHAR_OPT
|
243
|
+
arg? && @part[1] == ARG_CHAR_OPT
|
164
244
|
end
|
165
245
|
|
166
|
-
def
|
167
|
-
|
246
|
+
# def req_arg?
|
247
|
+
# arg? && !opt_arg?
|
248
|
+
# end
|
249
|
+
|
250
|
+
## Path part as a Symbol without arguments characters
|
251
|
+
## @return [Symbol] clean Symbol
|
252
|
+
def to_sym
|
253
|
+
@part.delete(ARG_CHAR + ARG_CHAR_OPT).to_sym
|
168
254
|
end
|
169
255
|
end
|
170
256
|
end
|