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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9fbe52ed5ac3bee05b2739396b58c83dce8f6969
4
- data.tar.gz: b54f7a5c830967546d696a4fcccf93e22dcab9c5
3
+ metadata.gz: 7e3d7d2c5f0cd76e641b7b5cc3999eeff5bf5c11
4
+ data.tar.gz: aa3c3efc4fba9d10060a554592be063f3384a7d8
5
5
  SHA512:
6
- metadata.gz: 2c384ad693932e066041d145885cace94ebe75a3eaa1830853df0bb6ddacbcb423f9f69c36d5c2bcca2297079ab636529f9aa6294c1389378a43444b16dd72c1
7
- data.tar.gz: 4747eaf2eb6651776abf63ccd809e174ff747006be9289e1ff6aa564e55e4f7aa475ffbe7dee6d1b3b21fa382dbe69d787c452c183fceacbab7a48052472d1c9
6
+ metadata.gz: aeb8af2e6436404b2478a31ae95598f04a0677d13c3ea27b5ec0eb6bef859d3378cfecbbb8a19a1e79e2d37062b3bc58a05c0189bba75be09d93d40d21bb3940
7
+ data.tar.gz: ab7eb4d61a48c01cbf122d2a180ac8fdb8945c28d4a1024970c0ff68edd68fcdd92f5aec08bb5612143225dcda135578b798f3465c8f70f74270dad437603ceb
data/bin/flame CHANGED
@@ -2,70 +2,15 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'thor'
5
- require 'fileutils'
6
- require 'gorilla-patch/inflections'
7
- require 'erb'
8
5
 
9
- ## CLI Application
10
- class FlameCLI < Thor
11
- desc 'new APP', 'Generate new application directory with sub-directories'
12
- def new(app_name)
13
- FlameApp.new app_name
14
- end
15
-
16
- ## Class for Flame Application
17
- class FlameApp
18
- def initialize(app_name)
19
- @app_name = app_name
20
- New.build @app_name
21
- puts 'Done!'
22
- puts "\nMoving to '#{@app_name}' directory by:\n\ncd #{@app_name}\n\n"
23
- end
24
-
25
- ## Module for new application
26
- module New
27
- module_function
28
-
29
- using GorillaPatch::Inflections
30
-
31
- def build(app_name)
32
- @app_name = app_name
33
- @module_name = @app_name.camelize
34
- make_dir do
35
- copy_template
36
- end
37
- end
38
-
39
- def make_dir(&block)
40
- puts "Creating '#{@app_name}' directory..."
41
- FileUtils.mkdir @app_name
42
- FileUtils.cd @app_name, &block
43
- end
44
-
45
- def copy_template
46
- puts 'Copy template directories and files...'
47
- FileUtils.cp_r File.join(__dir__, '..', 'template', '.'), '.'
48
- clean_dirs
49
- render_templates
50
- end
51
-
52
- def clean_dirs
53
- puts 'Clean directories...'
54
- FileUtils.rm Dir[File.join('**', '*', '.keep')]
55
- end
6
+ require_relative 'new'
56
7
 
57
- def render_templates
58
- puts 'Replace module names in template...'
59
- Dir[File.join('**', '*.erb')].each do |file|
60
- basename = File.basename(file, '.*')
61
- puts "- #{basename}"
62
- content = ERB.new(File.read(file)).result(binding)
63
- File.write(File.join(File.dirname(file), basename), content)
64
- FileUtils.rm file
65
- end
66
- end
67
- end
8
+ module FlameCLI
9
+ ## CLI Application
10
+ class Flame < Thor
11
+ desc 'new ENTITY ...ARGS', 'create new entity'
12
+ subcommand 'new', New
68
13
  end
69
14
  end
70
15
 
71
- FlameCLI.start(ARGV)
16
+ FlameCLI::Flame.start(ARGV)
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'addressable'
3
4
  require 'rack'
4
5
 
5
6
  require_relative 'flame/application'
@@ -19,12 +19,28 @@ module Flame
19
19
  @cached_tilts ||= {}
20
20
  end
21
21
 
22
+ ## Require project directories, exclude executable files
23
+ ## @param dirs [Array<String>] Array of directories names
24
+ ## @example Regular require of project
25
+ ## Flame::Application.require_dirs(
26
+ ## %w[config lib models helpers mailers services controllers]
27
+ ## )
28
+ def require_dirs(dirs)
29
+ caller_dir = File.dirname caller_file
30
+ dirs.each do |dir|
31
+ Dir[File.join(caller_dir, dir, '**', '*.rb')]
32
+ .reject { |file| File.executable?(file) }
33
+ .sort_by { |s| [File.basename(s)[0], s] }
34
+ .each { |file| require File.expand_path(file) }
35
+ end
36
+ end
37
+
22
38
  ## Generating application config when inherited
23
39
  def inherited(app)
24
40
  app.config = Config.new(
25
41
  app,
26
42
  default_config_dirs(
27
- root_dir: File.dirname(caller[0].split(':')[0])
43
+ root_dir: File.dirname(caller_file)
28
44
  ).merge(
29
45
  environment: ENV['RACK_ENV'] || 'development'
30
46
  )
@@ -37,24 +53,75 @@ module Flame
37
53
  @app.call env
38
54
  end
39
55
 
56
+ ## Build a path to the given controller and action
57
+ ##
58
+ ## @param ctrl [Flame::Controller] class of controller
59
+ ## @param action [Symbol] method of controller
60
+ ## @param args [Hash] parameters for method of controller
61
+ ## @return [String] path for requested method, controller and parameters
62
+ ## @example Path for `show(id)` method of `ArticlesController`
63
+ ## path_to ArticlesController, :show, id: 2 # => "/articles/show/2"
64
+ ## @example Path for `new` method of `ArticlesController` with params
65
+ ## path_to ArticlesController, :new, params: { author_id: 1 }
66
+ ## # => "/articles/new?author_id=1"
67
+ def path_to(ctrl, action = :index, args = {})
68
+ path = router.path_of(ctrl, action)
69
+ raise Errors::RouteNotFoundError.new(ctrl, action) unless path
70
+ query = Rack::Utils.build_nested_query args.delete(:params)
71
+ query = nil if query&.empty?
72
+ path = path.assign_arguments(args)
73
+ path = '/' if path.empty?
74
+ Addressable::URI.new(path: path, query: query).to_s
75
+ end
76
+
40
77
  private
41
78
 
79
+ ## Get filename from caller of method
80
+ ## @return [String] filename of caller
81
+ def caller_file
82
+ caller(2..2).first.split(':')[0]
83
+ end
84
+
85
+ using GorillaPatch::DeepMerge
86
+
42
87
  ## Mount controller in application class
43
- ## @param ctrl [Flame::Controller] the mounted controller class
88
+ ## @param controller [Symbol] the snake-cased name of mounted controller
89
+ ## (without `Controller` or `::IndexController` for namespaces)
44
90
  ## @param path [String, nil] root path for the mounted controller
45
91
  ## @yield refine defaults pathes for a methods of the mounted controller
46
92
  ## @example Mount controller with defaults
47
- ## mount ArticlesController
93
+ ## mount :articles # ArticlesController
48
94
  ## @example Mount controller with specific path
49
- ## mount HomeController, '/welcome'
95
+ ## mount :home, '/welcome' # HomeController
50
96
  ## @example Mount controller with specific path of methods
51
- ## mount HomeController do
97
+ ## mount :home do # HomeController
52
98
  ## get '/bye', :goodbye
53
99
  ## post '/greetings', :new
54
100
  ## defaults
55
101
  ## end
56
- def mount(ctrl, path = nil, &block)
57
- router.add_controller(ctrl, path, &block)
102
+ ## @example Mount controller with nested controllers
103
+ ## mount :cabinet do # Cabinet::IndexController
104
+ ## mount :articles # Cabinet::ArticlesController
105
+ ## end
106
+ def mount(controller_name, path = nil, &block)
107
+ ## Add routes from controller to glob array
108
+
109
+ routes_refine = Router::RoutesRefine.new(
110
+ router, namespace, controller_name, path, &block
111
+ )
112
+
113
+ router.routes.deep_merge! routes_refine.routes
114
+ router.reverse_routes.merge! routes_refine.reverse_routes
115
+ end
116
+
117
+ using GorillaPatch::Namespace
118
+
119
+ def namespace
120
+ namespace = self
121
+ while namespace.name.nil? && namespace.superclass != Flame::Application
122
+ namespace = superclass
123
+ end
124
+ namespace.deconstantize
58
125
  end
59
126
 
60
127
  ## Initialize default for config directories
@@ -67,15 +134,6 @@ module Flame
67
134
  end
68
135
  end
69
136
 
70
- ## Framework configuration
71
- def config
72
- self.class.config
73
- end
74
-
75
- def router
76
- self.class.router
77
- end
78
-
79
137
  def initialize(app = nil)
80
138
  @app = app
81
139
  end
@@ -83,7 +141,7 @@ module Flame
83
141
  ## Request recieving method
84
142
  def call(env)
85
143
  @app.call(env) if @app.respond_to? :call
86
- Flame::Dispatcher.new(self, env).run!
144
+ Flame::Dispatcher.new(self.class, env).run!
87
145
  end
88
146
  end
89
147
  end
@@ -4,11 +4,17 @@ module Flame
4
4
  class Application
5
5
  ## Class for Flame::Application.config
6
6
  class Config < Hash
7
+ ## Create an instance of application config
8
+ ## @param app [Flame::Application] application
9
+ ## @param hash [Hash] config content
7
10
  def initialize(app, hash = {})
8
11
  @app = app
9
12
  replace(hash)
10
13
  end
11
14
 
15
+ ## Get config value by key
16
+ ## @param key [Symbol] config key
17
+ ## @return [Object] config value
12
18
  def [](key)
13
19
  result = super(key)
14
20
  if result.class <= Proc && result.parameters.empty?
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'forwardable'
4
+ require 'gorilla-patch/namespace'
5
+
6
+ require_relative 'controller/path_to'
4
7
  require_relative 'render'
5
8
 
6
9
  module Flame
@@ -9,13 +12,34 @@ module Flame
9
12
  class Controller
10
13
  extend Forwardable
11
14
 
12
- FORBIDDEN_ACTIONS = [].freeze
13
-
14
15
  ## Shortcut for not-inherited public methods: actions
16
+ ## @return [Array<Symbol>] array of actions (public instance methods)
15
17
  def self.actions
16
18
  public_instance_methods(false)
17
19
  end
18
20
 
21
+ ## Re-define public instance methods (actions) from parent
22
+ ## @param actions [Array<Symbol>] Actions for inheritance
23
+ ## @param exclude [Array<Symbol>] Actions for excluding from inheritance
24
+ ## @example Inherit all parent actions
25
+ ## class MyController < BaseController
26
+ ## inherit_actions
27
+ ## end
28
+ ## @example Inherit certain parent actions
29
+ ## class MyController < BaseController
30
+ ## inherit_actions :index, :show
31
+ ## end
32
+ ## @example Inherit all parent actions exclude certain
33
+ ## class MyController < BaseController
34
+ ## inherit_actions exclude: %i[edit update]
35
+ ## end
36
+ def self.inherit_actions(actions = superclass.actions, exclude: [])
37
+ (actions - exclude).each do |public_method|
38
+ um = superclass.public_instance_method(public_method)
39
+ define_method public_method, um
40
+ end
41
+ end
42
+
19
43
  def_delegators(
20
44
  :@dispatcher,
21
45
  :config, :request, :params, :halt, :session, :response, :status, :body,
@@ -23,29 +47,12 @@ module Flame
23
47
  )
24
48
 
25
49
  ## Initialize the controller for request execution
26
- ## @param dispatcher [Flame::Dispatcher] dispatcher object
50
+ ## @param dispatcher [Flame::Dispatcher] host dispatcher
27
51
  def initialize(dispatcher)
28
52
  @dispatcher = dispatcher
29
53
  end
30
54
 
31
- ## Helpers
32
- def path_to(*args)
33
- add_controller_class(args)
34
- @dispatcher.path_to(*args)
35
- end
36
-
37
- ## Build a URI to the given controller and action, or path
38
- def url_to(*args, **options)
39
- first_arg = args.first
40
- path =
41
- if first_arg.is_a?(String) || first_arg.is_a?(Flame::Path)
42
- static_file = find_static(first_arg)
43
- static_file.path(with_version: options[:version])
44
- else
45
- path_to(*args, **options)
46
- end
47
- "#{request.scheme}://#{request.host_with_port}#{path}"
48
- end
55
+ include Flame::Controller::PathTo
49
56
 
50
57
  ## Redirect for response
51
58
  ## @overload redirect(path, status)
@@ -107,7 +114,7 @@ module Flame
107
114
  ## @param path [Symbol, nil] path to the template file
108
115
  ## @param options [Hash] options for the `Flame::Render` rendering
109
116
  ## @return [String] rendered template
110
- def view(path = nil, options = {})
117
+ def view(path = nil, options = {}, &block)
111
118
  cache = options.delete(:cache)
112
119
  cache = config[:environment] == 'production' if cache.nil?
113
120
  template = Flame::Render.new(
@@ -115,7 +122,7 @@ module Flame
115
122
  (path || caller_locations(1, 1)[0].label.to_sym),
116
123
  options
117
124
  )
118
- template.render(cache: cache)
125
+ template.render(cache: cache, &block)
119
126
  end
120
127
  alias render view
121
128
 
@@ -128,6 +135,8 @@ module Flame
128
135
  end
129
136
 
130
137
  ## Default method for Internal Server Error, can be inherited
138
+ ## @param _exception [Exception] exception from code executing
139
+ ## @return [String] content of exception page
131
140
  def server_error(_exception)
132
141
  body default_body
133
142
  end
@@ -150,17 +159,17 @@ module Flame
150
159
  end
151
160
 
152
161
  def extract_params_for(action)
153
- # Take parameters from action method
162
+ ## Take parameters from action method
154
163
  parameters = method(action).parameters
155
- # Fill variables with values from params
164
+ ## Fill variables with values from params
156
165
  req_values, opt_values = %i[req opt].map! do |type|
157
166
  params.values_at(
158
167
  *parameters.select { |key, _value| key == type }.map!(&:last)
159
168
  )
160
169
  end
161
- # Remove nils from the end of optional values
170
+ ## Remove nils from the end of optional values
162
171
  opt_values.pop while opt_values.last.nil? && !opt_values.empty?
163
- # Concat values
172
+ ## Concat values
164
173
  req_values + opt_values
165
174
  end
166
175
 
@@ -181,55 +190,6 @@ module Flame
181
190
  parts = [modules.last] if parts.empty?
182
191
  Flame::Path.merge nil, parts.join('_')
183
192
  end
184
-
185
- ## Re-define public instance method from parent
186
- ## @example Inherit controller with parent actions by method
187
- ## class MyController < BaseController.with_actions
188
- ## end
189
- ## @example Define actions from module in controller
190
- ## class MyController < BaseController
191
- ## include with_actions Module1
192
- ## include with_actions Module2
193
- ## ....
194
- ## end
195
- def with_actions(mod = nil)
196
- return mod.extend(ModuleActions) if mod
197
- @with_actions ||= Class.new(self) { extend ParentActions }
198
- end
199
- end
200
-
201
- ## Extension for modules whose public methods will be defined as actions
202
- ## via including
203
- module ModuleActions
204
- def included(ctrl)
205
- public_instance_methods.each do |meth|
206
- ctrl.send :define_method, meth, public_instance_method(meth)
207
- end
208
- end
209
- end
210
-
211
- ## Module for public instance methods re-defining from superclass
212
- ## @example Inherit controller with parent actions without forbidden
213
- ## actions by `extend`
214
- ## class MyController < BaseController
215
- ## FORBIDDEN_ACTIONS = %[foo bar baz].freeze
216
- ## extend Flame::Controller::ParentActions
217
- ## end
218
- module ParentActions
219
- def inherited(ctrl)
220
- ctrl.define_parent_actions
221
- end
222
-
223
- def self.extended(ctrl)
224
- ctrl.define_parent_actions
225
- end
226
-
227
- def define_parent_actions
228
- (superclass.actions - self::FORBIDDEN_ACTIONS).each do |public_method|
229
- um = superclass.public_instance_method(public_method)
230
- define_method public_method, um
231
- end
232
- end
233
193
  end
234
194
  end
235
195
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flame
4
+ class Controller
5
+ ## Module with methods for path or URL building
6
+ module PathTo
7
+ ## Look documentation at {Flame::Dispatcher#path_to}
8
+ def path_to(*args)
9
+ add_controller_class(args)
10
+ @dispatcher.path_to(*args)
11
+ end
12
+
13
+ ## Build a URI to the given controller and action, or path
14
+ def url_to(*args, **options)
15
+ first_arg = args.first
16
+ path =
17
+ if first_arg.is_a?(String) || first_arg.is_a?(Flame::Path)
18
+ find_static(first_arg).path(with_version: options[:version])
19
+ else
20
+ path_to(*args, **options)
21
+ end
22
+ Addressable::URI.new(
23
+ scheme: request.scheme, host: request.host_with_port, path: path
24
+ ).to_s
25
+ end
26
+
27
+ using GorillaPatch::Namespace
28
+
29
+ ## Path to previous page, or to index action, or to Index controller
30
+ ## @return [String] path to previous page or to index
31
+ def path_to_back
32
+ back_path = request.referer
33
+ return back_path if back_path && back_path != request.url
34
+ return path_to :index if self.class.actions.include?(:index)
35
+ '/'
36
+ end
37
+ end
38
+ end
39
+ end