flame 4.18.1 → 5.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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