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
@@ -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