flame 5.0.0.rc5 → 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.
Files changed (64) 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 -5
  6. data/lib/flame/application.rb +47 -46
  7. data/lib/flame/config.rb +73 -0
  8. data/lib/flame/controller.rb +45 -78
  9. data/lib/flame/controller/actions.rb +122 -0
  10. data/lib/flame/{dispatcher → controller}/cookies.rb +8 -3
  11. data/lib/flame/controller/path_to.rb +34 -10
  12. data/lib/flame/dispatcher.rb +14 -17
  13. data/lib/flame/dispatcher/request.rb +25 -6
  14. data/lib/flame/dispatcher/routes.rb +22 -14
  15. data/lib/flame/dispatcher/static.rb +13 -9
  16. data/lib/flame/errors/argument_not_assigned_error.rb +3 -8
  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 +5 -10
  20. data/lib/flame/errors/route_extra_arguments_error.rb +10 -20
  21. data/lib/flame/errors/route_not_found_error.rb +3 -8
  22. data/lib/flame/errors/template_not_found_error.rb +2 -8
  23. data/lib/flame/path.rb +36 -18
  24. data/lib/flame/render.rb +13 -5
  25. data/lib/flame/router.rb +7 -157
  26. data/lib/flame/router/controller_finder.rb +56 -0
  27. data/lib/flame/router/route.rb +9 -0
  28. data/lib/flame/router/routes.rb +58 -8
  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 +14 -10
  32. data/lib/flame/version.rb +1 -1
  33. metadata +91 -99
  34. data/bin/flame +0 -16
  35. data/lib/flame/application/config.rb +0 -49
  36. data/template/.editorconfig +0 -15
  37. data/template/.gitignore +0 -28
  38. data/template/.rubocop.yml +0 -14
  39. data/template/Gemfile +0 -55
  40. data/template/Rakefile +0 -824
  41. data/template/application.rb.erb +0 -10
  42. data/template/config.ru.erb +0 -72
  43. data/template/config/config.rb.erb +0 -56
  44. data/template/config/database.example.yml +0 -5
  45. data/template/config/deploy.example.yml +0 -2
  46. data/template/config/puma.rb +0 -56
  47. data/template/config/sequel.rb.erb +0 -22
  48. data/template/config/server.example.yml +0 -32
  49. data/template/config/session.example.yml +0 -7
  50. data/template/controllers/_controller.rb.erb +0 -14
  51. data/template/controllers/site/_controller.rb.erb +0 -18
  52. data/template/controllers/site/index_controller.rb.erb +0 -12
  53. data/template/db/.keep +0 -0
  54. data/template/filewatchers.yml +0 -12
  55. data/template/helpers/.keep +0 -0
  56. data/template/lib/.keep +0 -0
  57. data/template/locales/en.yml +0 -0
  58. data/template/models/.keep +0 -0
  59. data/template/public/.keep +0 -0
  60. data/template/server +0 -200
  61. data/template/services/.keep +0 -0
  62. data/template/views/.keep +0 -0
  63. data/template/views/site/index.html.erb.erb +0 -1
  64. data/template/views/site/layout.html.erb.erb +0 -10
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flame
4
+ ## Comment due to `private_constant`
5
+ class Router
6
+ ## Class for controller constant finding in namespace by names
7
+ class ControllerFinder
8
+ attr_reader :controller
9
+
10
+ def initialize(namespace_name, controller_or_name)
11
+ @namespace =
12
+ namespace_name.empty? ? Object : Object.const_get(namespace_name)
13
+
14
+ if controller_or_name.is_a?(Class)
15
+ @controller = controller_or_name
16
+ @controller_name = controller_or_name.name
17
+ else
18
+ @controller_name = controller_or_name
19
+ @controller = find
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def find
26
+ found_controller_name = controller_name_variations.find do |variation|
27
+ @namespace.const_defined?(variation) unless variation.empty?
28
+ end
29
+
30
+ raise_controller_not_found_error unless found_controller_name
31
+
32
+ controller = @namespace.const_get(found_controller_name)
33
+ return controller if controller < Flame::Controller
34
+
35
+ controller::IndexController
36
+ end
37
+
38
+ using GorillaPatch::Inflections
39
+
40
+ TRASNFORMATION_METHODS = %i[camelize upcase].freeze
41
+
42
+ def controller_name_variations
43
+ TRASNFORMATION_METHODS.each_with_object([]) do |method, result|
44
+ transformed = @controller_name.to_s.send(method)
45
+ result.push transformed, "#{transformed}Controller"
46
+ end
47
+ end
48
+
49
+ def raise_controller_not_found_error
50
+ raise Errors::ControllerNotFoundError.new(@controller_name, @namespace)
51
+ end
52
+ end
53
+
54
+ private_constant :ControllerFinder
55
+ end
56
+ end
@@ -7,6 +7,10 @@ module Flame
7
7
  class Router
8
8
  ## Class for Route in Router.routes
9
9
  class Route
10
+ extend Forwardable
11
+
12
+ def_delegators :to_s, :inspect
13
+
10
14
  attr_reader :controller, :action
11
15
 
12
16
  ## Create a new instance
@@ -22,12 +26,17 @@ module Flame
22
26
  ## @return [true, false] equal or not
23
27
  def ==(other)
24
28
  return false unless other.is_a? self.class
29
+
25
30
  %i[controller action].reduce(true) do |result, method|
26
31
  result && (
27
32
  public_send(method) == other.public_send(method)
28
33
  )
29
34
  end
30
35
  end
36
+
37
+ def to_s
38
+ "#{controller}##{action}"
39
+ end
31
40
  end
32
41
  end
33
42
  end
@@ -12,10 +12,15 @@ module Flame
12
12
  ## Flame::Router::Routes.new('/foo/bar/baz')
13
13
  ## # => { 'foo' => { 'bar' => { 'baz' => {} } } }
14
14
  def initialize(*path_parts)
15
+ super()
16
+
15
17
  path = Flame::Path.new(*path_parts)
16
18
  return if path.parts.empty?
19
+
17
20
  nested_routes = self.class.new Flame::Path.new(*path.parts[1..-1])
18
- # path.parts.reduce(result) { |hash, part| hash[part] ||= self.class.new }
21
+ # path.parts.reduce(result) do |hash, part|
22
+ # hash[part] ||= self.class.new
23
+ # end
19
24
  self[path.parts.first] = nested_routes
20
25
  end
21
26
 
@@ -38,6 +43,12 @@ module Flame
38
43
  super
39
44
  end
40
45
 
46
+ ## Return the first available route (at the first level).
47
+ ## @return [Flame::Router::Route] the first route
48
+ def first_route
49
+ values.find { |value| value.is_a?(Route) }
50
+ end
51
+
41
52
  ## Navigate to Routes or Route through static parts or arguments
42
53
  ## @param path_parts [Array<String, Flame::Path, Flame::Path::Part>]
43
54
  ## path or path parts as keys for navigating
@@ -48,31 +59,70 @@ module Flame
48
59
  def navigate(*path_parts)
49
60
  path_parts = Flame::Path.new(*path_parts).parts
50
61
  return dig_through_opt_args if path_parts.empty?
62
+
51
63
  endpoint =
52
- self[path_parts.first] ||
53
- dig(first_opt_arg_key, path_parts.first) ||
54
- self[first_arg_key]
55
- endpoint&.navigate(*path_parts[1..-1])
64
+ self[path_parts.first] || dig(first_opt_arg_key, path_parts.first)
65
+
66
+ endpoint&.navigate(*path_parts[1..-1]) ||
67
+ find_among_arg_keys(path_parts[1..-1])
56
68
  end
57
69
 
58
70
  ## Dig through optional arguments as keys
59
71
  ## @return [Flame::Router::Routes] return most nested end-point
60
72
  ## without optional arguments
61
73
  def dig_through_opt_args
62
- self[first_opt_arg_key]&.dig_through_opt_args || self
74
+ [
75
+ self[first_opt_arg_key]&.dig_through_opt_args,
76
+ self
77
+ ]
78
+ .compact.find(&:first_route)
63
79
  end
64
80
 
65
81
  def allow
66
82
  methods = keys.select { |key| key.is_a? Symbol }
67
83
  return if methods.empty?
84
+
68
85
  methods.push(:OPTIONS).join(', ')
69
86
  end
70
87
 
88
+ PADDING_SIZE = Router::HTTP_METHODS.map(&:size).max
89
+ PADDING_FORMAT = "%#{PADDING_SIZE}.#{PADDING_SIZE}s"
90
+
91
+ ## Output routes in human readable format
92
+ def to_s(prefix = '/')
93
+ sort.map do |key, value|
94
+ if key.is_a?(Symbol)
95
+ <<~OUTPUT
96
+ \e[1m#{format PADDING_FORMAT, key} #{prefix}\e[22m
97
+ #{' ' * PADDING_SIZE} \e[3m\e[36m#{value}\e[0m\e[23m
98
+ OUTPUT
99
+ else
100
+ value.to_s(Flame::Path.new(prefix, key))
101
+ end
102
+ end.join
103
+ end
104
+
105
+ ## Sort routes for human readability
106
+ def sort
107
+ sort_by do |key, _value|
108
+ [
109
+ if key.is_a?(Symbol)
110
+ then Router::HTTP_METHODS.index(key)
111
+ else Float::INFINITY
112
+ end,
113
+ key.to_s
114
+ ]
115
+ end
116
+ end
117
+
71
118
  private
72
119
 
73
- def first_arg_key
120
+ def find_among_arg_keys(path_parts)
74
121
  keys.find do |key|
75
- key.is_a?(Flame::Path::Part) && key.arg?
122
+ next unless key.is_a?(Flame::Path::Part) && key.arg?
123
+
124
+ result = self[key].navigate(*path_parts)
125
+ break result if result
76
126
  end
77
127
  end
78
128
 
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'routes_refine/mounting'
4
+
5
+ module Flame
6
+ class Router
7
+ ## Helper class for controller routing refine
8
+ class RoutesRefine
9
+ attr_reader :routes, :reverse_routes
10
+
11
+ class << self
12
+ include Memery
13
+
14
+ ## Defaults REST routes (methods, pathes, controllers actions)
15
+ def rest_routes
16
+ [
17
+ { method: :GET, path: '/', action: :index },
18
+ { method: :POST, path: '/', action: :create },
19
+ { method: :GET, path: '/', action: :show },
20
+ { method: :PUT, path: '/', action: :update },
21
+ { method: :DELETE, path: '/', action: :delete }
22
+ ]
23
+ end
24
+ end
25
+
26
+ using GorillaPatch::Namespace
27
+
28
+ def initialize(
29
+ namespace_name, controller_or_name, path, nested: true, &block
30
+ )
31
+ @controller =
32
+ ControllerFinder.new(namespace_name, controller_or_name).controller
33
+ @namespace_name = @controller.deconstantize
34
+ @path = Flame::Path.new(path || @controller.path)
35
+ @controller.path_arguments = @path.parts.select(&:arg?).map(&:to_sym)
36
+ @routes, @endpoint = @path.to_routes_with_endpoint
37
+ @reverse_routes = {}
38
+ @mount_nested = nested
39
+ execute(&block)
40
+ end
41
+
42
+ private
43
+
44
+ HTTP_METHODS.each do |http_method|
45
+ ## Define refine methods for all HTTP methods
46
+ ## @overload post(path, action)
47
+ ## Execute action on requested path and HTTP method
48
+ ## @param path [String] path of method for the request
49
+ ## @param action [Symbol] name of method for the request
50
+ ## @example
51
+ ## Set path to '/bye' and method to :POST for action `goodbye`
52
+ ## post '/bye', :goodbye
53
+ ## @overload post(action)
54
+ ## Execute action on requested HTTP method
55
+ ## @param action [Symbol] name of method for the request
56
+ ## @example Set method to :POST for action `goodbye`
57
+ ## post :goodbye
58
+ define_method(http_method.downcase) do |action_path, action = nil|
59
+ ## Swap arguments if action in path variable
60
+ unless action
61
+ action = action_path.to_sym
62
+ action_path = nil
63
+ end
64
+ ## Initialize new route
65
+ route = Route.new(@controller, action)
66
+ ## Make path by controller method with parameners
67
+ action_path = Flame::Path.new(action_path).adapt(@controller, action)
68
+ ## Validate action path
69
+ validate_action_path(action, action_path)
70
+ ## Merge action path with controller path
71
+ path = Flame::Path.new(@path, action_path)
72
+ ## Remove the same route if needed
73
+ remove_old_routes(action, route)
74
+ ## Add new route
75
+ add_new_route(route, action, path, http_method)
76
+ end
77
+ end
78
+
79
+ ## Assign remaining methods of the controller
80
+ ## to defaults pathes and HTTP methods
81
+ def defaults
82
+ rest
83
+
84
+ @controller.actions.each do |action|
85
+ get action unless find_reverse_route(action)
86
+ end
87
+
88
+ mount_nested_controllers if @mount_nested && (
89
+ @controller.demodulize == 'IndexController' &&
90
+ !@namespace_name.empty?
91
+ )
92
+ end
93
+
94
+ ## Assign methods of the controller to REST architecture
95
+ def rest
96
+ self.class.rest_routes.each do |rest_route|
97
+ action = rest_route[:action]
98
+ next if !@controller.actions.include?(action) || find_reverse_route(action)
99
+
100
+ send(*rest_route.values.map(&:downcase))
101
+ end
102
+ end
103
+
104
+ include Mounting
105
+
106
+ ## Execute block of refinings end sorting routes
107
+ def execute(&block)
108
+ @controller.refined_http_methods
109
+ .each do |action, (http_method, action_path)|
110
+ send(http_method, action_path, action)
111
+ end
112
+ instance_exec(&block) if block
113
+ defaults
114
+ end
115
+
116
+ def find_reverse_route(action)
117
+ @reverse_routes.dig(@controller.to_s, action)
118
+ end
119
+
120
+ def validate_action_path(action, action_path)
121
+ Validators::RouteArgumentsValidator
122
+ .new(@controller, action_path, action)
123
+ .valid?
124
+ end
125
+
126
+ def remove_old_routes(action, new_route)
127
+ old_path = @reverse_routes[@controller.to_s]&.delete(action)
128
+ return unless old_path
129
+
130
+ @routes.dig(*old_path.parts)
131
+ .delete_if { |_method, old_route| old_route == new_route }
132
+ end
133
+
134
+ using GorillaPatch::DeepMerge
135
+
136
+ def add_new_route(route, action, path, http_method)
137
+ path_routes, endpoint = path.to_routes_with_endpoint
138
+ endpoint[http_method] = route
139
+ @routes.deep_merge!(path_routes)
140
+ (@reverse_routes[@controller.to_s] ||= {})[action] = path
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flame
4
+ class Router
5
+ class RoutesRefine
6
+ ## Module for mounting in RoutesRefine
7
+ module Mounting
8
+ private
9
+
10
+ using GorillaPatch::DeepMerge
11
+
12
+ ## Mount controller inside other (parent) controller
13
+ ## @param controller [Flame::Controller] class of mounting controller
14
+ ## @param path [String, nil] root path for mounting controller
15
+ ## @yield Block of code for routes refine
16
+ def mount(controller_name, path = nil, &block)
17
+ routes_refine = self.class.new(
18
+ @namespace_name, controller_name, path, &block
19
+ )
20
+
21
+ @endpoint.deep_merge! routes_refine.routes
22
+
23
+ @reverse_routes.merge!(
24
+ routes_refine.reverse_routes.transform_values do |hash|
25
+ hash.transform_values { |action_path| @path + action_path }
26
+ end
27
+ )
28
+ end
29
+
30
+ using GorillaPatch::Namespace
31
+
32
+ def mount_nested_controllers
33
+ namespace = Object.const_get(@namespace_name)
34
+
35
+ namespace.constants.each do |constant_name|
36
+ constant = namespace.const_get(constant_name)
37
+ if constant < Flame::Controller || constant.instance_of?(Module)
38
+ mount_nested_controller constant
39
+ end
40
+ end
41
+ end
42
+
43
+ def mount_nested_controller(nested_controller)
44
+ mount nested_controller if should_be_mounted? nested_controller
45
+ end
46
+
47
+ def should_be_mounted?(controller)
48
+ if controller.instance_of?(Module)
49
+ controller.const_defined?(:IndexController, false)
50
+ else
51
+ controller.actions.any? && !@reverse_routes.key?(controller.to_s)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -7,6 +7,8 @@ module Flame
7
7
  module Validators
8
8
  ## Compare arguments from path and from controller's action
9
9
  class RouteArgumentsValidator
10
+ include Memery
11
+
10
12
  ## Create a new instance of validator
11
13
  ## @param ctrl [Flame::Controller] controller of route
12
14
  ## @param path [Flame::Path, String] path of route
@@ -29,6 +31,7 @@ module Flame
29
31
  extra_arguments = first_extra_arguments
30
32
  ## Raise error if extra arguments
31
33
  return true unless extra_arguments
34
+
32
35
  raise Errors::RouteExtraArgumentsError.new(
33
36
  @ctrl, @action, @path, extra_arguments
34
37
  )
@@ -37,27 +40,28 @@ module Flame
37
40
  def order_valid?
38
41
  wrong_ordered_arguments = first_wrong_ordered_arguments
39
42
  return true unless wrong_ordered_arguments
43
+
40
44
  raise Errors::RouteArgumentsOrderError.new(
41
45
  @path, wrong_ordered_arguments
42
46
  )
43
47
  end
44
48
 
45
49
  ## Split path to args array
46
- def path_arguments
47
- @path_arguments ||= @path.parts
48
- .each_with_object(req: [], opt: []) do |part, hash|
49
- ## Take only argument parts
50
- next unless part.arg?
51
- ## Memorize arguments
52
- hash[part.opt_arg? ? :opt : :req] << part.clean.to_sym
53
- end
50
+ memoize def path_arguments
51
+ @path.parts.each_with_object(req: [], opt: []) do |part, hash|
52
+ ## Take only argument parts
53
+ next unless part.arg?
54
+
55
+ ## Memorize arguments
56
+ hash[part.opt_arg? ? :opt : :req] << part.to_sym
57
+ end
54
58
  end
55
59
 
56
60
  ## Take args from controller's action
57
- def action_arguments
61
+ memoize def action_arguments
58
62
  ## Get all parameters (arguments) from method
59
63
  ## Than collect and sort parameters into hash
60
- @action_arguments ||= @ctrl.instance_method(@action).parameters
64
+ @ctrl.instance_method(@action).parameters
61
65
  .each_with_object(req: [], opt: []) do |param, hash|
62
66
  ## Only required parameters must be in `:req`
63
67
  hash[param[0]] << param[1]