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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/bin/flame +7 -62
  3. data/lib/flame.rb +1 -0
  4. data/lib/flame/application.rb +75 -17
  5. data/lib/flame/application/config.rb +6 -0
  6. data/lib/flame/controller.rb +36 -76
  7. data/lib/flame/controller/path_to.rb +39 -0
  8. data/lib/flame/dispatcher.rb +25 -66
  9. data/lib/flame/dispatcher/cookies.rb +10 -2
  10. data/lib/flame/dispatcher/routes.rb +53 -0
  11. data/lib/flame/dispatcher/static.rb +15 -8
  12. data/lib/flame/errors/argument_not_assigned_error.rb +6 -0
  13. data/lib/flame/errors/route_arguments_order_error.rb +6 -0
  14. data/lib/flame/errors/route_extra_arguments_error.rb +10 -0
  15. data/lib/flame/errors/route_not_found_error.rb +10 -4
  16. data/lib/flame/errors/template_not_found_error.rb +6 -0
  17. data/lib/flame/path.rb +63 -33
  18. data/lib/flame/render.rb +21 -8
  19. data/lib/flame/router.rb +112 -66
  20. data/lib/flame/router/route.rb +9 -56
  21. data/lib/flame/router/routes.rb +86 -0
  22. data/lib/flame/validators.rb +7 -1
  23. data/lib/flame/version.rb +1 -1
  24. data/template/.editorconfig +15 -0
  25. data/template/.gitignore +19 -2
  26. data/template/.rubocop.yml +14 -0
  27. data/template/Gemfile +48 -8
  28. data/template/Rakefile +824 -0
  29. data/template/{app.rb.erb → application.rb.erb} +4 -1
  30. data/template/config.ru.erb +62 -10
  31. data/template/config/config.rb.erb +44 -2
  32. data/template/config/database.example.yml +1 -1
  33. data/template/config/deploy.example.yml +2 -0
  34. data/template/config/puma.rb +56 -0
  35. data/template/config/sequel.rb.erb +13 -6
  36. data/template/config/server.example.yml +32 -0
  37. data/template/config/session.example.yml +7 -0
  38. data/template/controllers/{_base_controller.rb.erb → _controller.rb.erb} +5 -4
  39. data/template/controllers/site/_controller.rb.erb +18 -0
  40. data/template/controllers/site/index_controller.rb.erb +12 -0
  41. data/template/filewatchers.yml +12 -0
  42. data/template/server +172 -21
  43. data/template/services/.keep +0 -0
  44. data/template/views/site/index.html.erb.erb +1 -0
  45. data/template/views/site/layout.html.erb.erb +10 -0
  46. metadata +112 -54
  47. data/template/Rakefile.erb +0 -64
  48. data/template/config/thin.example.yml +0 -18
@@ -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 app [Flame::Application] application object
28
+ ## @param app_class [Class] application class
24
29
  ## @param env Rack-environment object
25
- def initialize(app, env)
26
- @app = app
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
- try_static ||
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
- @app.config
89
+ @app_class.config
84
90
  end
85
91
 
86
- ## Build a path to the given controller and action, with any expected params
87
- ##
88
- ## @param ctrl [Flame::Controller] class of controller
89
- ## @param action [Symbol] method of controller
90
- ## @param args [Hash] parameters for method of controller
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
- @app.class.cached_tilts
139
+ @app_class.cached_tilts
148
140
  end
149
141
 
150
142
  private
151
143
 
152
- ## Find route and try execute it
153
- def try_route
154
- route = @app.class.router.find_route(
155
- method: request.http_method,
156
- path: request.path
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
- return @response.delete_cookie(key.to_s, path: '/') if new_value.nil?
27
- @response.set_cookie(key.to_s, value: new_value, path: '/')
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
- halt 304 if file.newer? request.env['HTTP_IF_MODIFIED_SINCE']
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
- @path = File.join dir, URI.decode(@filename)
37
+ @file_path = File.join dir, URI.decode_www_form_component(@filename)
33
38
  end
34
39
 
35
40
  def exist?
36
- File.exist?(@path) && File.file?(@path)
41
+ File.exist?(@file_path) && File.file?(@file_path)
37
42
  end
38
43
 
39
44
  def mtime
40
- File.mtime(@path)
45
+ File.mtime(@file_path)
41
46
  end
42
47
 
43
48
  def extname
44
- File.extname(@path)
49
+ File.extname(@file_path)
45
50
  end
46
51
 
47
52
  def newer?(http_since)
48
- http_since && Time.httpdate(http_since).to_i >= mtime.to_i
53
+ !http_since || mtime.to_i > Time.httpdate(http_since).to_i
49
54
  end
50
55
 
51
56
  def content
52
- File.read(@path)
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
- def initialize(ctrl, method)
8
- @ctrl = ctrl
9
- @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
+ @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 '#{@ctrl}' and method '#{@method}'" \
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
@@ -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{\/{2,}}, '/')
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| PathPart.new(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 = PathPart.new parameter_name, arg: parameter_type
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.decode(other_part)
95
+ ] = URI.decode_www_form_component(other_part)
82
96
  end
83
97
  end
84
98
 
85
- ## Assign arguments to path for `Controller.path_to`
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
- 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
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
- 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
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 PathPart
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