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.
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
data/lib/flame/render.rb CHANGED
@@ -6,38 +6,54 @@ require 'tilt'
6
6
  require 'tilt/plain'
7
7
  require 'tilt/erb'
8
8
 
9
- require 'gorilla-patch/inflections'
9
+ require 'gorilla_patch/inflections'
10
10
 
11
11
  require_relative 'errors/template_not_found_error'
12
12
 
13
13
  module Flame
14
14
  ## Helper for render functionality
15
15
  class Render
16
+ ## Create a new instance from controller, by path and with options
17
+ ## @param controller [Flame::Controller]
18
+ ## controller for default scope, views directory and cache
19
+ ## @param path [Symbol, String] path (full or the last part) for view search
20
+ ## @param options [Hash] options for template
21
+ ## @option options [Object] :scope (controller)
22
+ ## scope of visibility in rendering
23
+ ## @option options [Symbol, String, false] :layout ('layout.*')
24
+ ## name of the layout file
25
+ ## @option options [Hash] :tilt options for Tilt
26
+ ## @option options [Hash] :locals ({}) local variables for rendering
16
27
  def initialize(controller, path, options = {})
17
28
  ## Take options for rendering
18
29
  @controller = controller
19
- @scope = options.delete(:scope) || @controller
20
- @layout = options.delete(:layout)
21
- @layout = 'layout.*' if @layout.nil?
30
+ @scope = options.delete(:scope) { @controller }
31
+ @layout = options.delete(:layout) { 'layout.*' }
32
+
33
+ ## Options for Tilt Template
34
+ @tilt_options = options.delete(:tilt)
35
+
22
36
  ## And get the rest variables to locals
23
- @locals = options.merge(options.delete(:locals) || {})
37
+ @locals = options.merge(options.delete(:locals) { {} })
38
+
24
39
  ## Find filename
25
40
  @filename = find_file(path)
26
- unless @filename
27
- raise Flame::Errors::TemplateNotFoundError.new(controller, path)
28
- end
41
+ raise Flame::Errors::TemplateNotFoundError.new(controller, path) unless @filename
42
+
29
43
  @layout = nil if File.basename(@filename)[0] == '_'
30
44
  end
31
45
 
32
- ## Render template
46
+ ## Render template with layout
33
47
  ## @param cache [Boolean] cache compiles or not
34
- def render(cache: true)
48
+ ## @return [String] compiled template
49
+ def render(cache: true, &block)
35
50
  @cache = cache
36
51
  ## Compile Tilt to instance hash
37
52
  return unless @filename
53
+
38
54
  tilt = compile_file
39
55
  ## Render Tilt from instance hash with new options
40
- layout_render tilt.render(@scope, @locals)
56
+ layout_render tilt.render(@scope, @locals, &block)
41
57
  end
42
58
 
43
59
  private
@@ -51,7 +67,8 @@ module Flame
51
67
  def compile_file(filename = @filename)
52
68
  cached = @controller.cached_tilts[filename]
53
69
  return cached if @cache && cached
54
- compiled = Tilt.new(filename)
70
+
71
+ compiled = Tilt.new(filename, nil, @tilt_options)
55
72
  @controller.cached_tilts[filename] ||= compiled if @cache
56
73
  compiled
57
74
  end
@@ -76,7 +93,17 @@ module Flame
76
93
 
77
94
  ## Find template-file by path
78
95
  def find_file(path)
79
- find_files(path, controller_dirs).find { |file| Tilt[file] }
96
+ caller_path = caller_locations(4..4).first.path
97
+
98
+ caller_dir =
99
+ begin
100
+ File.dirname(caller_path).sub(views_dir, '') if Tilt[caller_path]
101
+ rescue LoadError
102
+ nil
103
+ end
104
+
105
+ find_files(path, controller_dirs | Array(caller_dir))
106
+ .find { |file| Tilt[file] }
80
107
  end
81
108
 
82
109
  ## Find layout-files by path
@@ -88,11 +115,13 @@ module Flame
88
115
 
89
116
  using GorillaPatch::Inflections
90
117
 
118
+ CONTROLLER_SUFFIXES = %w[_controller _ctrl].freeze
119
+ private_constant :CONTROLLER_SUFFIXES
120
+
91
121
  ## Find possible directories for the controller
92
122
  def controller_dirs
93
123
  parts = @controller.class.underscore.split('/').map do |part|
94
- %w[_controller _ctrl]
95
- .find { |suffix| part.chomp! suffix }
124
+ CONTROLLER_SUFFIXES.find { |suffix| part.chomp! suffix }
96
125
  part
97
126
  ## Alternative, but slower by ~50%:
98
127
  # part.sub(/_(controller|ctrl)$/, '')
@@ -128,8 +157,10 @@ module Flame
128
157
  ## @param result [String] result of template rendering
129
158
  def layout_render(content)
130
159
  return content unless @layout
160
+
131
161
  layout_files = find_layouts(@layout)
132
162
  return content if layout_files.empty?
163
+
133
164
  layout_files.each_with_object(content.dup) do |layout_file, result|
134
165
  layout = compile_file(layout_file)
135
166
  result.replace layout.render(@scope, @locals) { result }
data/lib/flame/router.rb CHANGED
@@ -1,153 +1,67 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'router/route'
3
+ require 'gorilla_patch/deep_merge'
4
+ require 'gorilla_patch/inflections'
5
+ require 'gorilla_patch/namespace'
6
+
7
+ require_relative 'router/controller_finder'
8
+ require_relative 'errors/controller_not_found_error'
4
9
 
5
10
  module Flame
6
11
  ## Router class for routing
7
12
  class Router
8
- attr_reader :app, :routes
13
+ HTTP_METHODS = %i[GET POST PUT PATCH DELETE].freeze
14
+
15
+ require_relative 'router/route'
16
+ require_relative 'router/routes'
17
+ require_relative 'router/routes_refine'
18
+
19
+ extend Forwardable
20
+ def_delegators :routes, :navigate
21
+
22
+ attr_reader :app, :routes, :reverse_routes
9
23
 
24
+ ## @param app [Flame::Application] host application
10
25
  def initialize(app)
11
26
  @app = app
12
- @routes = []
27
+ @routes = Flame::Router::Routes.new
28
+ @reverse_routes = {}
13
29
  end
14
30
 
15
- ## Add the controller with it's methods to routes
16
- ## @param ctrl [Flame::Controller] class of the controller which will be added
17
- ## @param path [String, nil] root path for controller's methods
18
- ## @yield block for routes refine
19
- def add_controller(ctrl, path = nil, &block)
20
- ## @todo Add Regexp paths
31
+ using GorillaPatch::DeepMerge
21
32
 
22
- ## Add routes from controller to glob array
23
- route_refine = RouteRefine.new(self, ctrl, path, block)
24
- concat_routes(route_refine)
25
- end
26
-
27
- ## Find route by any attributes
28
- ## @param attrs [Hash] attributes for comparing
29
- ## @return [Flame::Route, nil] return the found route, otherwise `nil`
30
- def find_route(attrs)
31
- route = routes.find { |r| r.compare_attributes(attrs) }
32
- route.dup if route
33
+ ## Add RoutesRefine to Router
34
+ ## @param routes_refine [Flame::Router::RoutesRefine] refined routes
35
+ def add(routes_refine)
36
+ routes.deep_merge! routes_refine.routes
37
+ reverse_routes.merge! routes_refine.reverse_routes
33
38
  end
34
39
 
35
40
  ## Find the nearest route by path
36
41
  ## @param path [Flame::Path] path for route finding
37
42
  ## @return [Flame::Route, nil] return the found nearest route or `nil`
38
43
  def find_nearest_route(path)
39
- path = Flame::Path.new(path) if path.is_a? String
40
44
  path_parts = path.parts.dup
41
- while path_parts.size >= 0
42
- route = find_route path: Flame::Path.new(*path_parts)
43
- break if route || path_parts.empty?
44
- path_parts.pop
45
+ loop do
46
+ route = routes.navigate(*path_parts)&.first_route
47
+ break route if route || path_parts.pop.nil?
45
48
  end
46
- route
47
49
  end
48
50
 
49
- private
50
-
51
- ## Add `RouteRefine` routes to the routes of `Flame::Router`
52
- ## @param route_refine [Flame::Router::RouteRefine] `RouteRefine` with routes
53
- def concat_routes(route_refine)
54
- routes.concat(route_refine.routes)
55
- end
56
-
57
- ## Helper class for controller routing refine
58
- class RouteRefine
59
- attr_accessor :rest_routes
60
- attr_reader :ctrl, :routes
61
-
62
- ## Defaults REST routes (methods, pathes, controllers actions)
63
- def rest_routes
64
- @rest_routes ||= [
65
- { method: :GET, path: '/', action: :index },
66
- { method: :POST, path: '/', action: :create },
67
- { method: :GET, path: '/', action: :show },
68
- { method: :PUT, path: '/', action: :update },
69
- { method: :DELETE, path: '/', action: :delete }
70
- ]
71
- end
72
-
73
- def initialize(router, ctrl, path, block)
74
- @router = router
75
- @ctrl = ctrl
76
- @path = path || @ctrl.default_path
77
- @routes = []
78
- execute(&block)
79
- end
80
-
81
- %i[GET POST PUT PATCH DELETE].each do |request_method|
82
- ## Define refine methods for all HTTP methods
83
- ## @overload post(path, action)
84
- ## Execute action on requested path and HTTP method
85
- ## @param path [String] path of method for the request
86
- ## @param action [Symbol] name of method for the request
87
- ## @example Set path to '/bye' and method to :POST for action `goodbye`
88
- ## post '/bye', :goodbye
89
- ## @overload post(action)
90
- ## Execute action on requested HTTP method
91
- ## @param action [Symbol] name of method for the request
92
- ## @example Set method to :POST for action `goodbye`
93
- ## post :goodbye
94
- method = request_method.downcase
95
- define_method(method) do |path, action = nil|
96
- ## Swap arguments if action in path variable
97
- unless action
98
- action = path.to_sym
99
- path = nil
100
- end
101
- ## Init new Route
102
- route = Route.new(@ctrl, action, method, @path, path)
103
- ## Try to find route with the same action
104
- index = find_route_index(action: action)
105
- ## Overwrite route if needed
106
- index ? @routes[index] = route : @routes.push(route)
107
- end
108
- end
109
-
110
- ## Assign remaining methods of the controller
111
- ## to defaults pathes and HTTP methods
112
- def defaults
113
- rest
114
- @ctrl.actions.each do |action|
115
- next if find_route_index(action: action)
116
- send(:GET.downcase, action)
117
- end
118
- end
119
-
120
- ## Assign methods of the controller to REST architecture
121
- def rest
122
- rest_routes.each do |rest_route|
123
- action = rest_route[:action]
124
- next if !@ctrl.actions.include?(action) ||
125
- find_route_index(action: action)
126
- send(*rest_route.values.map(&:downcase))
127
- end
128
- end
129
-
130
- ## Mount controller inside other (parent) controller
131
- ## @param ctrl [Flame::Controller] class of mounting controller
132
- ## @param path [String, nil] root path for mounting controller
133
- ## @yield Block of code for routes refine
134
- def mount(ctrl, path = nil, &block)
135
- path = Flame::Path.merge(@path, path || ctrl.default_path)
136
- @router.add_controller(ctrl, path, &block)
137
- end
138
-
139
- private
140
-
141
- ## Execute block of refinings end sorting routes
142
- def execute(&block)
143
- instance_exec(&block) if block
144
- defaults
145
- @routes.sort!
146
- end
147
-
148
- def find_route_index(attrs)
149
- @routes.find_index { |route| route.compare_attributes(attrs) }
51
+ ## Find the path of route
52
+ ## @param route_or_controller [Flame::Router::Route, Flame::Controller]
53
+ ## route or controller
54
+ ## @param action [Symbol, nil] action (or not for route)
55
+ ## @return [Flame::Path] mounted path to action of controller
56
+ def path_of(route_or_controller, action = nil)
57
+ if route_or_controller.is_a?(Flame::Router::Route)
58
+ route = route_or_controller
59
+ controller = route.controller
60
+ action = route.action
61
+ else
62
+ controller = route_or_controller
150
63
  end
64
+ reverse_routes.dig(controller.to_s, action)
151
65
  end
152
66
  end
153
67
  end
@@ -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,73 +7,35 @@ module Flame
7
7
  class Router
8
8
  ## Class for Route in Router.routes
9
9
  class Route
10
- attr_reader :method, :controller, :action, :path
10
+ extend Forwardable
11
11
 
12
- def initialize(controller, action, method, ctrl_path, action_path)
13
- @controller = controller
14
- @action = action
15
- @method = method.to_sym.upcase
16
- ## Make path by controller method with parameners
17
- action_path = Flame::Path.new(action_path).adapt(controller, action)
18
- ## Merge action path with controller path
19
- @path = Flame::Path.new(ctrl_path, action_path)
20
- Validators::RouteArgumentsValidator.new(
21
- @controller, action_path, @action
22
- ).valid?
23
- freeze
24
- end
12
+ def_delegators :to_s, :inspect
25
13
 
26
- def freeze
27
- @path.freeze
28
- super
29
- end
14
+ attr_reader :controller, :action
30
15
 
31
- ## Compare attributes for `Router.find_route`
32
- ## @param attrs [Hash] Hash of attributes for comparing
33
- def compare_attributes(attrs)
34
- attrs.each do |name, value|
35
- next true if compare_attribute(name, value)
36
- break false
37
- end
16
+ ## Create a new instance
17
+ ## @param controller [Flame::Controller] controller
18
+ ## @param action [Symbol] action
19
+ def initialize(controller, action)
20
+ @controller = controller
21
+ @action = action
38
22
  end
39
23
 
40
24
  ## Method for Routes comparison
25
+ ## @param other [Flame::Router::Route] other route
26
+ ## @return [true, false] equal or not
41
27
  def ==(other)
42
- %i[controller action method path].reduce(true) do |result, method|
28
+ return false unless other.is_a? self.class
29
+
30
+ %i[controller action].reduce(true) do |result, method|
43
31
  result && (
44
32
  public_send(method) == other.public_send(method)
45
33
  )
46
34
  end
47
35
  end
48
36
 
49
- ## Compare by:
50
- ## 1. path parts count (more is matter);
51
- ## 2. args position (father is matter);
52
- ## 3. HTTP-method (default).
53
- def <=>(other)
54
- path_result = other.path <=> path
55
- return path_result unless path_result.zero?
56
- method <=> other.method
57
- end
58
-
59
- private
60
-
61
- ## Helpers for `compare_attributes`
62
- def compare_attribute(name, value)
63
- case name
64
- when :method
65
- compare_method(value)
66
- when :path
67
- path.match? value
68
- else
69
- send(name) == value
70
- end
71
- end
72
-
73
- def compare_method(request_method)
74
- request_method = request_method.upcase.to_sym
75
- request_method = :GET if request_method == :HEAD
76
- method.upcase.to_sym == request_method
37
+ def to_s
38
+ "#{controller}##{action}"
77
39
  end
78
40
  end
79
41
  end