flame 4.18.1 → 5.0.0.rc6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +921 -0
  3. data/LICENSE.txt +19 -0
  4. data/README.md +135 -0
  5. data/lib/flame.rb +12 -4
  6. data/lib/flame/application.rb +93 -40
  7. data/lib/flame/config.rb +73 -0
  8. data/lib/flame/controller.rb +62 -98
  9. data/lib/flame/controller/actions.rb +122 -0
  10. data/lib/flame/controller/cookies.rb +44 -0
  11. data/lib/flame/controller/path_to.rb +63 -0
  12. data/lib/flame/dispatcher.rb +44 -73
  13. data/lib/flame/dispatcher/request.rb +33 -4
  14. data/lib/flame/dispatcher/routes.rb +66 -0
  15. data/lib/flame/dispatcher/static.rb +26 -15
  16. data/lib/flame/errors/argument_not_assigned_error.rb +7 -6
  17. data/lib/flame/errors/config_file_not_found_error.rb +17 -0
  18. data/lib/flame/errors/controller_not_found_error.rb +19 -0
  19. data/lib/flame/errors/route_arguments_order_error.rb +9 -8
  20. data/lib/flame/errors/route_extra_arguments_error.rb +18 -18
  21. data/lib/flame/errors/route_not_found_error.rb +8 -7
  22. data/lib/flame/errors/template_not_found_error.rb +6 -6
  23. data/lib/flame/path.rb +141 -55
  24. data/lib/flame/render.rb +46 -15
  25. data/lib/flame/router.rb +41 -127
  26. data/lib/flame/router/controller_finder.rb +56 -0
  27. data/lib/flame/router/route.rb +16 -54
  28. data/lib/flame/router/routes.rb +136 -0
  29. data/lib/flame/router/routes_refine.rb +144 -0
  30. data/lib/flame/router/routes_refine/mounting.rb +57 -0
  31. data/lib/flame/validators.rb +21 -11
  32. data/lib/flame/version.rb +1 -1
  33. metadata +139 -84
  34. data/bin/flame +0 -71
  35. data/lib/flame/application/config.rb +0 -43
  36. data/lib/flame/dispatcher/cookies.rb +0 -31
  37. data/template/.gitignore +0 -11
  38. data/template/Gemfile +0 -15
  39. data/template/Rakefile.erb +0 -64
  40. data/template/app.rb.erb +0 -7
  41. data/template/config.ru.erb +0 -20
  42. data/template/config/config.rb.erb +0 -14
  43. data/template/config/database.example.yml +0 -5
  44. data/template/config/sequel.rb.erb +0 -15
  45. data/template/config/thin.example.yml +0 -18
  46. data/template/controllers/_base_controller.rb.erb +0 -13
  47. data/template/db/.keep +0 -0
  48. data/template/helpers/.keep +0 -0
  49. data/template/lib/.keep +0 -0
  50. data/template/locales/en.yml +0 -0
  51. data/template/models/.keep +0 -0
  52. data/template/public/.keep +0 -0
  53. data/template/server +0 -49
  54. 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
- halt 304 if file.newer? request.env['HTTP_IF_MODIFIED_SINCE']
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
- @path = File.join dir, URI.decode(@filename)
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?(@path) && File.file?(@path)
50
+ File.exist?(@file_path) && File.file?(@file_path)
37
51
  end
38
52
 
39
53
  def mtime
40
- File.mtime(@path)
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 && Time.httpdate(http_since).to_i >= mtime.to_i
58
+ !http_since || mtime.to_i > Time.httpdate(http_since).to_i
49
59
  end
50
60
 
51
61
  def content
52
- File.read(@path)
62
+ File.read(@file_path)
53
63
  end
54
64
 
55
65
  def path(with_version: false)
56
- path = @filename
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
- @path = path
9
- @argument = argument
10
- end
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
- @path = path
9
- @wrong_ordered_arguments = wrong_ordered_arguments
10
- end
11
-
12
- def message
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
- @ctrl = ctrl
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
- def message
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 '#{@path}' has no #{@extra[:type_name]}" \
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 '#{@ctrl}##{@action}' has no #{@extra[:type_name]}" \
28
- " arguments #{@extra[:args].inspect}"
29
- end
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
- def initialize(ctrl, method)
8
- @ctrl = ctrl
9
- @method = method
10
- end
11
-
12
- def message
13
- "Route with controller '#{@ctrl}' and method '#{@method}'" \
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
- @controller = controller
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
- def message
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{\/{2,}}, '/')
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
- def parts
20
- @parts ||= @path.to_s.split('/').reject(&:empty?)
21
- .map! { |part| PathPart.new(part) }
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
- parts_size = self_parts.size <=> other_parts.size
35
- return parts_size unless parts_size.zero?
36
- self_parts.zip(other_parts)
37
- .reduce(0) do |result, (self_part, other_part)|
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 = PathPart.new parameter_name, arg: parameter_type
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
- parts.each_with_index.with_object({}) do |(part, i), args|
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.path_to`
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
- private
98
-
99
- def other_contain_required_parts?(other)
100
- other_parts = other.parts
101
- req_path_parts = parts.reject(&:opt_arg?)
102
- fixed_path_parts = parts.reject(&:arg?)
103
- (fixed_path_parts - other_parts).empty? &&
104
- other_parts.count >= req_path_parts.count
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
- def compare_parts(part, other_part)
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[part[2..-1].to_sym] if part.opt_arg?
125
+ return args.delete(part[2..-1].to_sym) if part.opt_arg?
126
+
121
127
  ## Required argument
122
- param = args[part[1..-1].to_sym]
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 PathPart
196
+ class Part
132
197
  extend Forwardable
133
198
 
134
- def_delegators :@part, :[], :hash
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 clean
167
- @part.delete ARG_CHAR + ARG_CHAR_OPT
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