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
@@ -2,6 +2,9 @@
2
2
 
3
3
  module <%= @module_name %>
4
4
  ## Class for application (mounting controllers)
5
- class Application
5
+ class Application < Flame::Application
6
+ include <%= @short_module_name %>::Config
7
+
8
+ mount :site, '/'
6
9
  end
7
10
  end
@@ -1,20 +1,72 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ environment = ENV['RACK_ENV'].to_sym
4
+
5
+ is_development = environment == :development
6
+
3
7
  ## Require gems
4
8
  require 'bundler'
5
- Bundler.require
9
+ Bundler.require :default, environment
10
+
11
+ ## Require libs
12
+ # require 'money/bank/google_currency'
6
13
 
7
- ## Require project
8
- Dir[File.join(
9
- __dir__, '{config,models,helpers,controllers}', '{,**}', '{_,}*.rb'
10
- )].each { |file| require file }
14
+ ## Require dirs
15
+ Flame::Application.require_dirs(
16
+ %w[config lib models helpers exports mailers services controllers]
17
+ )
11
18
 
12
19
  ## Require application
13
- require_relative './app'
20
+ require './application'
21
+
22
+ ## Use session middleware
23
+ if <%= @short_module_name %>::Application.config[:session]
24
+ use Rack::Session::Cookie, <%= @short_module_name %>::Application.config[:session][:cookie]
25
+ end
26
+
27
+ ## Logger
28
+ require 'logger'
29
+ logs_dir = File.join(
30
+ __dir__,
31
+ <%= @short_module_name %>::Application.config[:server][environment.to_s][:logs_dir]
32
+ )
33
+
34
+ <%= @short_module_name %>::Application.config[:logger] = Logger.new(
35
+ is_development ? $stdout : File.join(logs_dir, 'stdout'),
36
+ 'weekly'
37
+ )
38
+
39
+ ## Access Logger
40
+ unless is_development
41
+ use Rack::CommonLogger, Logger.new(
42
+ File.join(logs_dir, 'access'),
43
+ 'weekly'
44
+ )
45
+ end
46
+
47
+ if <%= @short_module_name %>.const_defined?(:DB)
48
+ ## SQL Logger
49
+ if is_development
50
+ <%= @short_module_name %>::DB.loggers <<
51
+ if ENV['RACK_CONSOLE_INTRO']
52
+ Logger.new($stdout)
53
+ else
54
+ <%= @short_module_name %>.logger
55
+ end
56
+ end
57
+
58
+ ## Freeze DB (not for `rake console`)
59
+ <%= @short_module_name %>::DB.freeze unless ENV['RACK_CONSOLE_INTRO']
60
+ end
61
+
62
+ ## Remove invalid UTF-8 characters from requests
63
+ use Rack::UTF8Sanitizer
64
+
65
+ ## Remove trailing slashes from request path (and redirect)
66
+ # use Rack::RemoveTrailingSlashes
14
67
 
15
- ## Use middlewares
16
- use Rack::Session::Cookie, key: 'rack.session', secret: 'dummy_secret'
17
- use Rack::CommonLogger
68
+ ## CSRF
69
+ # use Rack::Csrf, raise: is_development
18
70
 
19
71
  ## Run application
20
- run <%= @module_name %>::Application
72
+ run <%= @short_module_name %>::Application
@@ -1,14 +1,56 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ ## Module for Application configuration
3
4
  module <%= @module_name %>
4
5
  ## Constants
5
- SITE_NAME = '<%= @module_name %>'.freeze
6
+ SITE_NAME = '<%= @module_name %>'
7
+ ORGANIZATION_NAME = '<%= @module_name %> LLC'
8
+ ::<%= @short_module_name %> = ::<%= @module_name %>
9
+
10
+ ## Helpers
11
+ def self.logger
12
+ <%= @short_module_name %>::Application.config[:logger]
13
+ end
6
14
 
7
15
  ## Configuration for application
8
16
  module Config
9
17
  def self.included(app)
10
18
  ## Translations
11
- # Flame::R18n.config(app)
19
+ # app.include Flame::R18n::Configuration
20
+
21
+ ## Mail
22
+ # app.include Config::Mail
23
+
24
+ ## Sentry
25
+ # app.include Config::Sentry
26
+
27
+ ## Threads (for mails)
28
+ Thread.abort_on_exception = true
29
+
30
+ ## Currencies rates
31
+ # Money.default_bank = Money::Bank::GoogleCurrency.new
32
+ ### https://github.com/RubyMoney/money#troubleshooting
33
+ # I18n.enforce_available_locales = false
34
+
35
+ ## Required configs
36
+ load_configs(app, :server, require: true)
37
+
38
+ ## Not-required configs
39
+ # load_configs(app, :google_maps_api)
40
+
41
+ ## Mails directory
42
+ # app.config[:mails_dir] = proc { File.join config[:views_dir], 'mail' }
43
+ end
44
+
45
+ def self.load_configs(app, *keys, require: false)
46
+ keys.each do |key|
47
+ begin
48
+ app.config.load_yaml key
49
+ rescue => exception
50
+ next unless require
51
+ raise exception
52
+ end
53
+ end
12
54
  end
13
55
  end
14
56
  end
@@ -2,4 +2,4 @@
2
2
  :host: 'localhost'
3
3
  :database: 'database'
4
4
  :user: 'user'
5
- :password: 'password'
5
+ # :password: 'password'
@@ -0,0 +1,2 @@
1
+ - :ssh: 'server alias from ~/.ssh/config (or user@host)'
2
+ :path: '/path/to/root/of/project'
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env puma
2
+ # frozen_string_literal: true
3
+
4
+ require 'yaml'
5
+ config = YAML.load_file(File.join(__dir__, 'server.yml'))
6
+
7
+ environment = ENV['RACK_ENV'] || config[:environment]
8
+ env_config = config[environment]
9
+
10
+ root_dir = File.join(__dir__, '..')
11
+ directory root_dir
12
+
13
+ prune_bundler
14
+
15
+ rackup 'config.ru'
16
+
17
+ require 'fileutils'
18
+
19
+ raise 'Unknown directory for pid files!' unless env_config[:pids_dir]
20
+ pids_dir = File.join root_dir, env_config[:pids_dir]
21
+ FileUtils.mkdir_p pids_dir
22
+
23
+ pidfile File.join pids_dir, env_config[:pid_file]
24
+ state_path File.join pids_dir, 'puma.state'
25
+
26
+ raise 'Unknown directory for log files!' unless env_config[:logs_dir]
27
+ log_dir = File.join root_dir, env_config[:logs_dir]
28
+ FileUtils.mkdir_p log_dir
29
+
30
+ if env_config[:daemonize]
31
+ stdout_redirect(
32
+ File.join(log_dir, 'stdout'),
33
+ File.join(log_dir, 'stderr'),
34
+ true # append to file
35
+ )
36
+ end
37
+
38
+ environment environment
39
+
40
+ # preload_app! if config['environment'] != 'production'
41
+
42
+ cores = Etc.nprocessors
43
+ workers_count = env_config[:workers_count] || (cores < 2 ? 1 : 2)
44
+
45
+ workers workers_count
46
+ worker_timeout env_config[:daemonize] ? 15 : 1_000_000
47
+ threads 0, env_config[:threads_count] || 4
48
+ daemonize env_config[:daemonize]
49
+
50
+ # bind 'unix://' + File.join(%w[tmp sockets puma.sock])
51
+ env_config[:binds].each do |type, value|
52
+ value = "#{value[:host]}:#{value[:port]}" if type == :tcp
53
+ FileUtils.mkdir_p File.join(root_dir, File.dirname(value)) if type == :unix
54
+ bind "#{type}://#{value}"
55
+ end
56
+ # activate_control_app 'tcp://0.0.0.0:3000'
@@ -4,12 +4,19 @@ require 'yaml'
4
4
 
5
5
  ## Database initialize for Sequel
6
6
  module <%= @module_name %>
7
- # Sequel::Model.plugin :timestamps
8
- # Sequel::Model.raise_on_save_failure = false
7
+ database_config_file = File.join(__dir__, 'database.yml')
8
+ if File.exist?(database_config_file)
9
+ Sequel::Model.plugin :timestamps
10
+ Sequel::Model.plugin :json_serializer
11
+ Sequel::Model.raise_on_save_failure = false
9
12
 
10
- DB = Sequel.connect(
11
- YAML.load_file(File.join(__dir__, 'database.yml'))
12
- )
13
+ database_config = YAML.load_file(database_config_file)
14
+ env_db_name = ENV['DB_NAME']
15
+ database_config[:database] = env_db_name if env_db_name
16
+ DB = Sequel.connect database_config
13
17
 
14
- # DB.extension :pg_enum
18
+
19
+ # DB.extension :pg_enum
20
+ DB.extension :error_sql
21
+ end
15
22
  end
@@ -0,0 +1,32 @@
1
+ :environment: development
2
+ # :environment: production
3
+
4
+ :default: &default
5
+ :binds:
6
+ :tcp:
7
+ :host: '0.0.0.0'
8
+ :port: 3000
9
+ # :unix: 'tmp/sockets/puma.sock'
10
+ :daemonize: false
11
+ :pids_dir: tmp/pids
12
+ :logs_dir: log
13
+ :pid_file: puma.pid
14
+ # :workers_count: 1
15
+ # :threads_count: 4
16
+
17
+ development: &development
18
+ <<: *default
19
+ :workers_count: 1
20
+ :threads_count: 4
21
+
22
+ test: &test
23
+ <<: *default
24
+ :daemonize: false
25
+
26
+ production: &production
27
+ <<: *default
28
+ :binds:
29
+ # :tcp:
30
+ # :host: '0.0.0.0'
31
+ # :port: 3000
32
+ :unix: 'tmp/sockets/puma.sock'
@@ -0,0 +1,7 @@
1
+ :cookie:
2
+ :key: 'rack.session'
3
+ #:domain: 'foo.com'
4
+ #:path: '/'
5
+ :expire_after: 2592000 # 30 days
6
+ :secret: 'change_me'
7
+ #:old_secret: 'also_change_me'
@@ -3,11 +3,12 @@
3
3
  module <%= @module_name %>
4
4
  ## Base controller for any others controllers
5
5
  class Controller < Flame::Controller
6
- # include Flame::R18n
6
+ # include Flame::R18n::Initialization
7
7
 
8
- def execute(method)
9
- # session[:locale] = params[:locale] || 'ru'
10
- super
8
+ private
9
+
10
+ def logger
11
+ <%= @short_module_name %>.logger
11
12
  end
12
13
  end
13
14
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= @module_name %>
4
+ module Site
5
+ ## Base controller for site-section
6
+ class Controller < <%= @short_module_name %>::Controller
7
+ # include Flame::Flash
8
+ # include Flame::R18n::LocaleInPath
9
+
10
+ protected
11
+
12
+ def execute(method)
13
+ response.headers[Rack::CONTENT_TYPE] = 'text/html; charset=utf-8'
14
+ super
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= @module_name %>
4
+ module Site
5
+ ## Index controller for example
6
+ class IndexController < <%= @short_module_name %>::Site::Controller
7
+ def index
8
+ view
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ - :pattern: '**/{Gemfile,*.{rb,ru,yml}}'
2
+ :exclude: '**/{spec/**/*,config/**/*.example*}'
3
+ :command: bundle exec pumactl restart -F config/puma.rb
4
+
5
+ # - :pattern: 'assets/styles/**/*'
6
+ # :command: rake assets:build:styles
7
+ #
8
+ # - :pattern: '{assets/scripts/**/*,{.babelrc,webpack.config.js}}'
9
+ # :command: rake assets:build:scripts
10
+ #
11
+ # - :pattern: 'package.json'
12
+ # :command: yarn install && rake assets:build:scripts
@@ -1,49 +1,200 @@
1
1
  #!/usr/bin/env ruby
2
+
2
3
  # frozen_string_literal: true
3
4
 
5
+ require 'shellwords'
6
+ require 'yaml'
7
+
4
8
  ## Functons
5
9
  def show_usage
6
- puts 'Usage: ./server COMMAND'
7
- puts 'COMMAND is one of:'
8
- puts ' start - Start server'
9
- puts ' stop - Stop server'
10
- puts ' restart - Stop/Start server'
11
- puts ' monitor - Show log'
12
- puts ' devel - Restart/Monitor server'
10
+ puts <<~USAGE
11
+ Usage: ./server COMMAND
12
+
13
+ COMMAND is one of:
14
+ start - Start server
15
+ stop - Stop server
16
+ kill - Kill server (and filewatcher)
17
+ restart - Restart server
18
+ monitor - Show log
19
+ devel - Restart and monitor server
20
+ ps - Show processes of server
21
+ USAGE
22
+ end
23
+
24
+ def bash(command, print: true)
25
+ puts command if print
26
+ system bash_command(command)
27
+ end
28
+
29
+ def bash_command(command)
30
+ escaped_command = Shellwords.escape(command)
31
+ "bash -c #{escaped_command}"
32
+ end
33
+
34
+ def bash_spawn(command)
35
+ puts "spawn #{command}"
36
+ spawn bash_command command
37
+ end
38
+
39
+ def server(command)
40
+ if %i[start restart].include?(command) &&
41
+ !(dependencies_check && assets_build)
42
+ exit
43
+ end
44
+ if %i[stop restart].include?(command)
45
+ kill_each read_filewatcher_pids
46
+ delete_filewatcher_pids_file
47
+ end
48
+ web_server(command)
49
+ end
50
+
51
+ def web_server(command)
52
+ pumactl_command = "bundle exec pumactl #{command} -F #{puma_config_file}"
53
+ if environment == 'production' || command != :restart
54
+ waiting_mailing_lock if %i[stop restart].include?(command)
55
+ return bash pumactl_command
56
+ end
57
+ development_restart pumactl_command
58
+ end
59
+
60
+ def development_restart(pumactl_command)
61
+ filewatcher_pids =
62
+ development_filewatchers.map { |command| bash_spawn command }
63
+ dump_filewatcher_pids filewatcher_pids
64
+ # web_server :stop if File.exist? puma_pid_file
65
+ # web_server :start
66
+ File.exist?(puma_pid_file) ? bash(pumactl_command) : web_server(:start)
67
+ rescue SystemExit, Interrupt
68
+ kill_each filewatcher_pids
69
+ delete_filewatcher_pids_file
70
+ end
71
+
72
+ def kill_each(pids)
73
+ Array(pids).each do |pid|
74
+ bash "kill #{pid}"
75
+ end
76
+ end
77
+
78
+ def filewatcher_command(pattern, execute, exclude: nil)
79
+ <<-CMD.split.join(' ')
80
+ bundle exec "
81
+ filewatcher
82
+ '#{pattern}'
83
+ #{"--exclude '#{exclude}'" unless exclude.nil?}
84
+ '#{execute}'
85
+ "
86
+ CMD
87
+ end
88
+
89
+ def development_filewatchers
90
+ YAML.load_file(File.join(__dir__, 'filewatchers.yml')).map do |args|
91
+ filewatcher_command args[:pattern], args[:command], exclude: args[:exclude]
92
+ end
93
+ end
94
+
95
+ def dump_filewatcher_pids(filewatcher_pids)
96
+ FileUtils.mkdir_p server_config_pids_dir
97
+ File.write(
98
+ filewatcher_pids_file,
99
+ filewatcher_pids.join($RS)
100
+ )
101
+ end
102
+
103
+ def read_filewatcher_pids
104
+ return unless File.exist?(filewatcher_pids_file)
105
+ File.read(filewatcher_pids_file).split($RS)
106
+ end
107
+
108
+ def delete_filewatcher_pids_file
109
+ return unless File.exist?(filewatcher_pids_file)
110
+ File.delete filewatcher_pids_file
111
+ end
112
+
113
+ def server_config
114
+ @server_config ||= YAML.load_file(File.join(__dir__, 'config', 'server.yml'))
115
+ end
116
+
117
+ def puma_config_file
118
+ @puma_config_file ||= File.join(__dir__, 'config', 'puma.rb')
119
+ end
120
+
121
+ def puma_pid_file
122
+ File.join(
123
+ __dir__, *server_config[environment].values_at(:pids_dir, :pid_file)
124
+ )
13
125
  end
14
126
 
15
- def start_server
16
- system 'rm log/*'
17
- system 'thin -C config/thin.yml start'
127
+ def server_config_pids_dir
128
+ server_config[environment][:pids_dir]
18
129
  end
19
130
 
20
- def stop_server
21
- system 'thin -C config/thin.yml stop'
131
+ def filewatcher_pids_file
132
+ File.join server_config_pids_dir, 'filewatcher.pids'
22
133
  end
23
134
 
24
- def restart_server
25
- stop_server
26
- start_server
135
+ def environment
136
+ ENV['RACK_ENV'] || server_config[:environment]
137
+ end
138
+
139
+ def log_files
140
+ File.join(__dir__, %w[log {stdout,stderr}])
141
+ end
142
+
143
+ def waiting_mailing_lock
144
+ while Dir[File.join(__dir__, 'tmp', 'mailing_*')].any?
145
+ puts "\e[31m\e[1mMails sending in progress!\e[22m\e[0m\nWaiting..."
146
+ sleep 1
147
+ end
27
148
  end
28
149
 
29
150
  def monitor_server
30
- system 'tail -f log/thin.*.log'
151
+ bash "tail -f #{log_files}"
152
+ end
153
+
154
+ def dependencies_check
155
+ bash('bundle check || bundle install') # && bash('yarn install')
156
+ end
157
+
158
+ def assets_build
159
+ bash 'rake assets:build', print: false
160
+ end
161
+
162
+ def ps_with_grep(pattern)
163
+ bash "ps aux | grep #{pattern} --color", print: false
31
164
  end
32
165
 
33
166
  ## Runtime
34
167
  case ARGV[0]
35
168
  when 'start'
36
- start_server
169
+ server :start
37
170
  when 'stop'
38
- stop_server
171
+ server :stop
39
172
  when 'restart'
40
- restart_server
173
+ server :restart
174
+ when 'kill'
175
+ server :stop
176
+ bash 'pkill -f filewatcher'
177
+ bash 'pkill -f puma'
41
178
  when 'monitor'
42
179
  monitor_server
43
180
  when 'devel'
44
- restart_server
45
- monitor_server
181
+ server :restart
182
+ if environment == 'production'
183
+ puts 'Waiting for logs...'
184
+ sleep 1.5
185
+ monitor_server
186
+ end
187
+ when 'ps'
188
+ puts
189
+ puts 'Filewatcher:'
190
+ puts
191
+ ps_with_grep '[f]ilewatcher'
192
+ puts
193
+ puts 'Puma:'
194
+ puts
195
+ ps_with_grep '[p]uma[\ :]'
46
196
  else
47
197
  puts "Unknown command #{ARGV[0]}"
198
+ puts
48
199
  show_usage
49
200
  end