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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +921 -0
- data/LICENSE.txt +19 -0
- data/README.md +135 -0
- data/lib/flame.rb +12 -5
- data/lib/flame/application.rb +47 -46
- data/lib/flame/config.rb +73 -0
- data/lib/flame/controller.rb +45 -78
- data/lib/flame/controller/actions.rb +122 -0
- data/lib/flame/{dispatcher → controller}/cookies.rb +8 -3
- data/lib/flame/controller/path_to.rb +34 -10
- data/lib/flame/dispatcher.rb +14 -17
- data/lib/flame/dispatcher/request.rb +25 -6
- data/lib/flame/dispatcher/routes.rb +22 -14
- data/lib/flame/dispatcher/static.rb +13 -9
- data/lib/flame/errors/argument_not_assigned_error.rb +3 -8
- data/lib/flame/errors/config_file_not_found_error.rb +17 -0
- data/lib/flame/errors/controller_not_found_error.rb +19 -0
- data/lib/flame/errors/route_arguments_order_error.rb +5 -10
- data/lib/flame/errors/route_extra_arguments_error.rb +10 -20
- data/lib/flame/errors/route_not_found_error.rb +3 -8
- data/lib/flame/errors/template_not_found_error.rb +2 -8
- data/lib/flame/path.rb +36 -18
- data/lib/flame/render.rb +13 -5
- data/lib/flame/router.rb +7 -157
- data/lib/flame/router/controller_finder.rb +56 -0
- data/lib/flame/router/route.rb +9 -0
- data/lib/flame/router/routes.rb +58 -8
- data/lib/flame/router/routes_refine.rb +144 -0
- data/lib/flame/router/routes_refine/mounting.rb +57 -0
- data/lib/flame/validators.rb +14 -10
- data/lib/flame/version.rb +1 -1
- metadata +91 -99
- data/bin/flame +0 -16
- data/lib/flame/application/config.rb +0 -49
- data/template/.editorconfig +0 -15
- data/template/.gitignore +0 -28
- data/template/.rubocop.yml +0 -14
- data/template/Gemfile +0 -55
- data/template/Rakefile +0 -824
- data/template/application.rb.erb +0 -10
- data/template/config.ru.erb +0 -72
- data/template/config/config.rb.erb +0 -56
- data/template/config/database.example.yml +0 -5
- data/template/config/deploy.example.yml +0 -2
- data/template/config/puma.rb +0 -56
- data/template/config/sequel.rb.erb +0 -22
- data/template/config/server.example.yml +0 -32
- data/template/config/session.example.yml +0 -7
- data/template/controllers/_controller.rb.erb +0 -14
- data/template/controllers/site/_controller.rb.erb +0 -18
- data/template/controllers/site/index_controller.rb.erb +0 -12
- data/template/db/.keep +0 -0
- data/template/filewatchers.yml +0 -12
- data/template/helpers/.keep +0 -0
- data/template/lib/.keep +0 -0
- data/template/locales/en.yml +0 -0
- data/template/models/.keep +0 -0
- data/template/public/.keep +0 -0
- data/template/server +0 -200
- data/template/services/.keep +0 -0
- data/template/views/.keep +0 -0
- data/template/views/site/index.html.erb.erb +0 -1
- data/template/views/site/layout.html.erb.erb +0 -10
@@ -1,49 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Flame
|
4
|
-
class Application
|
5
|
-
## Class for Flame::Application.config
|
6
|
-
class Config < Hash
|
7
|
-
## Create an instance of application config
|
8
|
-
## @param app [Flame::Application] application
|
9
|
-
## @param hash [Hash] config content
|
10
|
-
def initialize(app, hash = {})
|
11
|
-
@app = app
|
12
|
-
replace(hash)
|
13
|
-
end
|
14
|
-
|
15
|
-
## Get config value by key
|
16
|
-
## @param key [Symbol] config key
|
17
|
-
## @return [Object] config value
|
18
|
-
def [](key)
|
19
|
-
result = super(key)
|
20
|
-
if result.class <= Proc && result.parameters.empty?
|
21
|
-
result = @app.class_exec(&result)
|
22
|
-
end
|
23
|
-
result
|
24
|
-
end
|
25
|
-
|
26
|
-
## Method for loading YAML-files from config directory
|
27
|
-
## @param file [String, Symbol] file name (typecast to String with '.yml')
|
28
|
-
## @param key [Symbol, String, nil]
|
29
|
-
## key for allocating YAML in config Hash (typecast to Symbol)
|
30
|
-
## @param set [Boolean] allocating YAML in Config Hash
|
31
|
-
## @example Load SMTP file from `config/smtp.yml' to config[]
|
32
|
-
## config.load_yaml('smtp.yml')
|
33
|
-
## @example Load SMTP file without extension, by Symbol
|
34
|
-
## config.load_yaml(:smtp)
|
35
|
-
## @example Load SMTP file with other key to config[:mail]
|
36
|
-
## config.load_yaml('smtp.yml', key: :mail)
|
37
|
-
## @example Load SMTP file without allocating in config[]
|
38
|
-
## config.load_yaml('smtp.yml', set: false)
|
39
|
-
def load_yaml(file, key: nil, set: true)
|
40
|
-
file = "#{file}.yml" if file.is_a? Symbol
|
41
|
-
file_path = File.join(self[:config_dir], file)
|
42
|
-
yaml = YAML.load_file(file_path)
|
43
|
-
key ||= File.basename(file, '.*')
|
44
|
-
self[key.to_sym] = yaml if set
|
45
|
-
yaml
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
data/template/.editorconfig
DELETED
data/template/.gitignore
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
# configuration
|
2
|
-
config/**/*
|
3
|
-
!config/**/*.example*
|
4
|
-
!config/**/*.rb
|
5
|
-
!config/**/*.erb
|
6
|
-
!config/nginx
|
7
|
-
!config/nginx/snippets
|
8
|
-
!config/nginx/snippets/*.example*
|
9
|
-
!config/nginx/snippets/ssl-params.conf
|
10
|
-
!config/nginx/sites
|
11
|
-
!config/nginx/sites/*.example*
|
12
|
-
|
13
|
-
# temp
|
14
|
-
.sass-cache/
|
15
|
-
log/
|
16
|
-
tmp/
|
17
|
-
*.bak
|
18
|
-
*~
|
19
|
-
|
20
|
-
# uploaded files
|
21
|
-
#public/files
|
22
|
-
|
23
|
-
# dumps
|
24
|
-
*.sql
|
25
|
-
|
26
|
-
# seo files
|
27
|
-
#views/site/sitemap.html
|
28
|
-
#public/robots.txt
|
data/template/.rubocop.yml
DELETED
data/template/Gemfile
DELETED
@@ -1,55 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
source 'https://rubygems.org'
|
4
|
-
|
5
|
-
## https://github.com/bundler/bundler/issues/4978
|
6
|
-
git_source(:github) { |name| "https://github.com/#{name}.git" }
|
7
|
-
|
8
|
-
## system
|
9
|
-
# gem 'gorilla_patch'
|
10
|
-
|
11
|
-
## server
|
12
|
-
gem 'flame', github: 'AlexWayfer/flame'
|
13
|
-
# gem 'flame-flash', github: 'AlexWayfer/flame-flash'
|
14
|
-
gem 'puma'
|
15
|
-
# gem 'rack-slashenforce'
|
16
|
-
gem 'rack-utf8_sanitizer'
|
17
|
-
# gem 'rack_csrf', require: 'rack/csrf'
|
18
|
-
|
19
|
-
group :development do
|
20
|
-
gem 'filewatcher'
|
21
|
-
gem 'pry-byebug'
|
22
|
-
end
|
23
|
-
|
24
|
-
group :linter do
|
25
|
-
# gem 'rubocop'
|
26
|
-
end
|
27
|
-
|
28
|
-
## database
|
29
|
-
# gem 'pg'
|
30
|
-
# gem 'sequel'
|
31
|
-
# gem 'sequel_pg', require: 'sequel'
|
32
|
-
|
33
|
-
## translations
|
34
|
-
# gem 'flame-r18n', github: 'AlexWayfer/flame-r18n'
|
35
|
-
## for named_variables filter
|
36
|
-
# gem 'r18n-rails-api'
|
37
|
-
|
38
|
-
## views
|
39
|
-
gem 'erubi', require: 'tilt/erubi'
|
40
|
-
|
41
|
-
## assets
|
42
|
-
# gem 'sass'
|
43
|
-
|
44
|
-
## others
|
45
|
-
# gem 'faker'
|
46
|
-
# gem 'google_currency'
|
47
|
-
# gem 'kramdown'
|
48
|
-
# gem 'mail'
|
49
|
-
# gem 'money'
|
50
|
-
# gem 'sentry-raven'
|
51
|
-
|
52
|
-
## tools
|
53
|
-
gem 'pry'
|
54
|
-
gem 'rack-console'
|
55
|
-
gem 'rake'
|
data/template/Rakefile
DELETED
@@ -1,824 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'yaml'
|
4
|
-
|
5
|
-
require 'pry-byebug' ## for `binding.pry` debugging
|
6
|
-
|
7
|
-
def alias_task(name, old_name)
|
8
|
-
t = Rake::Task[old_name]
|
9
|
-
desc t.full_comment if t.full_comment
|
10
|
-
task name, *t.arg_names do |_, args|
|
11
|
-
# values_at is broken on Rake::TaskArguments
|
12
|
-
args = t.arg_names.map { |a| args[a] }
|
13
|
-
t.invoke(*args)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def edit_file(filename)
|
18
|
-
sh "eval $EDITOR #{filename}"
|
19
|
-
end
|
20
|
-
|
21
|
-
def show_diff(filename, other_filename)
|
22
|
-
sh "diff -u --color=always #{filename} #{other_filename} || true"
|
23
|
-
puts
|
24
|
-
end
|
25
|
-
|
26
|
-
def env_true?(key)
|
27
|
-
%(true yes 1 y).include?(ENV[key.to_s].to_s.downcase)
|
28
|
-
end
|
29
|
-
|
30
|
-
## Class for questions
|
31
|
-
class Question
|
32
|
-
def initialize(text, possible_answers)
|
33
|
-
@text = text
|
34
|
-
@possible_answers = Set.new(possible_answers) << 'quit' << 'help'
|
35
|
-
end
|
36
|
-
|
37
|
-
def answer
|
38
|
-
while @answer.nil?
|
39
|
-
ask
|
40
|
-
@answer = @possible_answers.find do |possible_answer|
|
41
|
-
possible_answer.start_with? @real_answer
|
42
|
-
end
|
43
|
-
print_help if @answer.nil?
|
44
|
-
end
|
45
|
-
@answer
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
def print_question
|
51
|
-
print "#{@text} [#{@possible_answers.map(&:chr).join(',')}] : "
|
52
|
-
end
|
53
|
-
|
54
|
-
def print_help
|
55
|
-
@possible_answers.each do |possible_answer|
|
56
|
-
puts "#{possible_answer.chr} - #{possible_answer}"
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def ask
|
61
|
-
print_question
|
62
|
-
@real_answer = STDIN.gets.chomp.downcase
|
63
|
-
case @real_answer
|
64
|
-
when 'h'
|
65
|
-
print_help
|
66
|
-
return ask
|
67
|
-
when 'q'
|
68
|
-
exit
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
DB_CONFIG_FILE = File.join(__dir__, 'config/database.yml').freeze
|
74
|
-
|
75
|
-
if File.exist? DB_CONFIG_FILE
|
76
|
-
namespace :db do
|
77
|
-
## Require libs and config
|
78
|
-
require 'logger'
|
79
|
-
require 'sequel'
|
80
|
-
|
81
|
-
## Constants for DB directories
|
82
|
-
|
83
|
-
DB_DIR = File.join(__dir__, 'db')
|
84
|
-
DB_MIGRATIONS_DIR = File.join(DB_DIR, 'migrations')
|
85
|
-
DB_DUMPS_DIR = File.join(DB_DIR, 'dumps')
|
86
|
-
|
87
|
-
DB_CONFIG = YAML.load_file DB_CONFIG_FILE
|
88
|
-
|
89
|
-
env_db_name = ENV['DB_NAME']
|
90
|
-
DB_CONFIG[:database] = env_db_name if env_db_name
|
91
|
-
|
92
|
-
def db_connection
|
93
|
-
@db_connection ||= Sequel.connect DB_CONFIG
|
94
|
-
end
|
95
|
-
|
96
|
-
DB_ACCESS = "-U #{DB_CONFIG[:user]} -h #{DB_CONFIG[:host]}"
|
97
|
-
|
98
|
-
DB_EXTENSIONS = %w[citext pgcrypto].freeze
|
99
|
-
|
100
|
-
PGPASS_FILE = File.expand_path('~/.pgpass').freeze
|
101
|
-
|
102
|
-
PGPASS_LINE =
|
103
|
-
DB_CONFIG
|
104
|
-
.fetch_values(:host, :port, :database, :user, :password) { |_key| '*' }
|
105
|
-
.join(':')
|
106
|
-
|
107
|
-
def update_pgpass
|
108
|
-
pgpass_lines =
|
109
|
-
File.exist?(PGPASS_FILE) ? File.read(PGPASS_FILE).split($RS) : []
|
110
|
-
return if pgpass_lines&.include? PGPASS_LINE
|
111
|
-
File.write PGPASS_FILE, pgpass_lines.push(PGPASS_LINE, nil).join($RS)
|
112
|
-
File.chmod(0o600, PGPASS_FILE)
|
113
|
-
end
|
114
|
-
|
115
|
-
# db_connection.loggers << Logger.new($stdout)
|
116
|
-
|
117
|
-
namespace :migrations do
|
118
|
-
## Migration file
|
119
|
-
class MigrationFile
|
120
|
-
MIGRATION_CONTENT =
|
121
|
-
<<~STR
|
122
|
-
# frozen_string_literal: true
|
123
|
-
|
124
|
-
Sequel.migration do
|
125
|
-
change do
|
126
|
-
end
|
127
|
-
end
|
128
|
-
STR
|
129
|
-
|
130
|
-
DISABLING_EXT = '.bak'
|
131
|
-
|
132
|
-
def self.find(query, only_one: true, enabled: true, disabled: true)
|
133
|
-
filenames = Dir[File.join(DB_MIGRATIONS_DIR, "*#{query}*")]
|
134
|
-
filenames.select! { |filename| File.file? filename }
|
135
|
-
files = filenames.map { |filename| new filename: filename }.sort!
|
136
|
-
files.reject!(&:disabled) unless disabled
|
137
|
-
files.select!(&:disabled) unless enabled
|
138
|
-
return files unless only_one
|
139
|
-
return files.first if files.size < 2
|
140
|
-
raise 'More than one file mathes the query'
|
141
|
-
end
|
142
|
-
|
143
|
-
attr_accessor :version, :name, :disabled
|
144
|
-
|
145
|
-
def initialize(filename: nil, name: nil)
|
146
|
-
self.filename = filename
|
147
|
-
self.name = name if name
|
148
|
-
end
|
149
|
-
|
150
|
-
## Accessors
|
151
|
-
|
152
|
-
def basename
|
153
|
-
File.basename(@filename)
|
154
|
-
end
|
155
|
-
|
156
|
-
def filename=(value)
|
157
|
-
parse_filename value if value.is_a? String
|
158
|
-
@filename = value
|
159
|
-
end
|
160
|
-
|
161
|
-
def name=(value)
|
162
|
-
@name = value.tr(' ', '_').downcase
|
163
|
-
end
|
164
|
-
|
165
|
-
def disabled=(value)
|
166
|
-
@disabled =
|
167
|
-
case value
|
168
|
-
when String
|
169
|
-
[DISABLING_EXT, DISABLING_EXT[1..-1]].include? value
|
170
|
-
else
|
171
|
-
value
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
def <=>(other)
|
176
|
-
version <=> other.version
|
177
|
-
end
|
178
|
-
|
179
|
-
## Behavior
|
180
|
-
|
181
|
-
def print
|
182
|
-
datetime = Time.parse(version).strftime('%F %R')
|
183
|
-
fullname = name.tr('_', ' ').capitalize
|
184
|
-
fullname = "#{fullname} (disabled)" if disabled
|
185
|
-
version_color, name_color =
|
186
|
-
disabled ? ["\e[37m", "\e[37m- "] : ["\e[36m", '']
|
187
|
-
puts "\e[37m[#{version}]\e[0m #{version_color}#{datetime}\e[0m" \
|
188
|
-
" #{name_color}#{fullname}\e[0m"
|
189
|
-
end
|
190
|
-
|
191
|
-
def generate
|
192
|
-
self.version = new_version
|
193
|
-
FileUtils.mkdir_p File.dirname new_filename
|
194
|
-
File.write new_filename, MIGRATION_CONTENT
|
195
|
-
end
|
196
|
-
|
197
|
-
def reversion
|
198
|
-
rename version: new_version
|
199
|
-
end
|
200
|
-
|
201
|
-
def disable
|
202
|
-
abort 'Migration already disabled' if disabled
|
203
|
-
|
204
|
-
rename disabled: true
|
205
|
-
end
|
206
|
-
|
207
|
-
def enable
|
208
|
-
abort 'Migration already enabled' unless disabled
|
209
|
-
|
210
|
-
rename disabled: false
|
211
|
-
end
|
212
|
-
|
213
|
-
private
|
214
|
-
|
215
|
-
def parse_filename(value = @filename)
|
216
|
-
basename = File.basename value
|
217
|
-
self.version, parts = basename.split('_', 2)
|
218
|
-
self.name, _ext, self.disabled = parts.split('.')
|
219
|
-
end
|
220
|
-
|
221
|
-
def new_version
|
222
|
-
Time.now.strftime('%Y%m%d%H%M')
|
223
|
-
end
|
224
|
-
|
225
|
-
def rename(vars = {})
|
226
|
-
vars.each { |key, value| send :"#{key}=", value }
|
227
|
-
return unless @filename.is_a? String
|
228
|
-
File.rename @filename, new_filename
|
229
|
-
self.filename = new_filename
|
230
|
-
end
|
231
|
-
|
232
|
-
def new_filename
|
233
|
-
new_basename = "#{version}_#{name}.rb#{DISABLING_EXT if disabled}"
|
234
|
-
File.join DB_MIGRATIONS_DIR, new_basename
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
desc 'Run migrations'
|
239
|
-
task :run, %i[target current] do |_t, args|
|
240
|
-
Rake::Task['db:dump'].invoke
|
241
|
-
|
242
|
-
Sequel.extension :migration
|
243
|
-
Sequel.extension :inflector
|
244
|
-
# db_connection.extension :pg_enum
|
245
|
-
|
246
|
-
options = {
|
247
|
-
allow_missing_migration_files: env_true?(:ignore)
|
248
|
-
}
|
249
|
-
if (target = args[:target])
|
250
|
-
if target == '0'
|
251
|
-
puts 'Migrating all the way down'
|
252
|
-
else
|
253
|
-
file = MigrationFile.find target, disabled: false
|
254
|
-
|
255
|
-
abort 'Migration with this version not found' if file.nil?
|
256
|
-
|
257
|
-
current = args[:current] || 'current'
|
258
|
-
puts "Migrating from #{current} to #{file.basename}"
|
259
|
-
target = file.version
|
260
|
-
end
|
261
|
-
options[:current] = args[:current].to_i
|
262
|
-
options[:target] = target.to_i
|
263
|
-
else
|
264
|
-
puts 'Migrating to latest'
|
265
|
-
end
|
266
|
-
|
267
|
-
db_connection.loggers << Logger.new($stdout)
|
268
|
-
|
269
|
-
Sequel::Migrator.run(
|
270
|
-
db_connection,
|
271
|
-
DB_MIGRATIONS_DIR,
|
272
|
-
options
|
273
|
-
)
|
274
|
-
end
|
275
|
-
|
276
|
-
desc 'Rollback the database N steps'
|
277
|
-
task :rollback, :step do |_task, args|
|
278
|
-
Rake::Task['db:dump'].invoke
|
279
|
-
|
280
|
-
step = args[:step] ? Integer(args[:step]).abs : 1
|
281
|
-
|
282
|
-
file = MigrationFile.find('*', only_one: false)[-1 - step]
|
283
|
-
|
284
|
-
Rake::Task['db:migrations:run'].invoke(file.version)
|
285
|
-
|
286
|
-
puts "Rolled back to #{file.basename}"
|
287
|
-
end
|
288
|
-
|
289
|
-
desc 'Create migration'
|
290
|
-
task :new, :name do |_t, args|
|
291
|
-
abort 'You must specify a migration name' if args[:name].nil?
|
292
|
-
|
293
|
-
file = MigrationFile.new name: args[:name]
|
294
|
-
file.generate
|
295
|
-
end
|
296
|
-
|
297
|
-
desc 'Change version of migration to latest'
|
298
|
-
task :reversion, :filename do |_t, args|
|
299
|
-
# rubocop:disable Style/IfUnlessModifier
|
300
|
-
if args[:filename].nil?
|
301
|
-
abort 'You must specify a migration name or version'
|
302
|
-
end
|
303
|
-
|
304
|
-
file = MigrationFile.find args[:filename]
|
305
|
-
file.reversion
|
306
|
-
end
|
307
|
-
|
308
|
-
desc 'Disable migration'
|
309
|
-
task :disable, :filename do |_t, args|
|
310
|
-
if args[:filename].nil?
|
311
|
-
abort 'You must specify a migration name or version'
|
312
|
-
end
|
313
|
-
|
314
|
-
file = MigrationFile.find args[:filename]
|
315
|
-
file.disable
|
316
|
-
end
|
317
|
-
|
318
|
-
desc 'Enable migration'
|
319
|
-
task :enable, :filename do |_t, args|
|
320
|
-
if args[:filename].nil?
|
321
|
-
abort 'You must specify a migration name or version'
|
322
|
-
end
|
323
|
-
|
324
|
-
file = MigrationFile.find args[:filename]
|
325
|
-
file.enable
|
326
|
-
end
|
327
|
-
|
328
|
-
desc 'Show all migrations'
|
329
|
-
task :list do |_t, _args|
|
330
|
-
files = MigrationFile.find '*', only_one: false
|
331
|
-
files.each(&:print)
|
332
|
-
end
|
333
|
-
|
334
|
-
desc 'Check applied migrations'
|
335
|
-
task :check do
|
336
|
-
applied_names = db_connection[:schema_migrations].select_map(:filename)
|
337
|
-
applied = applied_names.map { |one| MigrationFile.new filename: one }
|
338
|
-
existing = MigrationFile.find '*', only_one: false, disabled: false
|
339
|
-
existing_names = existing.map(&:basename)
|
340
|
-
a_not_e = applied.reject { |one| existing_names.include? one.basename }
|
341
|
-
e_not_a = existing.reject { |one| applied_names.include? one.basename }
|
342
|
-
if a_not_e.any?
|
343
|
-
puts 'Applied, but not existing'
|
344
|
-
a_not_e.each(&:print)
|
345
|
-
puts "\n" if e_not_a.any?
|
346
|
-
end
|
347
|
-
if e_not_a.any?
|
348
|
-
puts 'Existing, but not applied'
|
349
|
-
e_not_a.each(&:print)
|
350
|
-
end
|
351
|
-
end
|
352
|
-
end
|
353
|
-
|
354
|
-
alias_task :migrate, 'migrations:run'
|
355
|
-
|
356
|
-
desc 'Run seeds'
|
357
|
-
task :seed do
|
358
|
-
require 'sequel/extensions/seed'
|
359
|
-
seeds_dir = File.join(DB_DIR, 'seeds')
|
360
|
-
|
361
|
-
## Doesn't support version yet
|
362
|
-
puts 'Seeding latest'
|
363
|
-
Sequel::Seeder.apply(db_connection, seeds_dir)
|
364
|
-
end
|
365
|
-
|
366
|
-
namespace :dumps do
|
367
|
-
## Class for single DB dump file
|
368
|
-
class DumpFile
|
369
|
-
DB_DUMP_TIMESTAMP = '%Y-%m-%d_%H-%M'
|
370
|
-
|
371
|
-
DB_DUMP_TIMESTAMP_REGEXP_MAP = {
|
372
|
-
'Y' => '\d{4}',
|
373
|
-
'm' => '\d{2}',
|
374
|
-
'd' => '\d{2}',
|
375
|
-
'H' => '\d{2}',
|
376
|
-
'M' => '\d{2}'
|
377
|
-
}.freeze
|
378
|
-
|
379
|
-
missing_keys =
|
380
|
-
DB_DUMP_TIMESTAMP.scan(/%(\w)/).flatten -
|
381
|
-
DB_DUMP_TIMESTAMP_REGEXP_MAP.keys
|
382
|
-
|
383
|
-
if missing_keys.any?
|
384
|
-
raise "`DB_DUMP_TIMESTAMP_REGEXP_MAP` doesn't contain keys" \
|
385
|
-
" #{missing_keys} for `DB_DUMP_TIMESTAMP`"
|
386
|
-
end
|
387
|
-
|
388
|
-
DB_DUMP_TIMESTAMP_REGEXP =
|
389
|
-
DB_DUMP_TIMESTAMP_REGEXP_MAP
|
390
|
-
.each_with_object(DB_DUMP_TIMESTAMP.dup) do |(key, value), result|
|
391
|
-
result.gsub! "%#{key}", value
|
392
|
-
end
|
393
|
-
|
394
|
-
DB_DUMP_FORMATS = %w[custom plain].freeze
|
395
|
-
|
396
|
-
DB_DUMP_EXTENSIONS = {
|
397
|
-
'plain' => '.sql',
|
398
|
-
'custom' => '.dump'
|
399
|
-
}.freeze
|
400
|
-
|
401
|
-
missing_formats = DB_DUMP_FORMATS.reject do |db_dump_format|
|
402
|
-
DB_DUMP_EXTENSIONS[db_dump_format]
|
403
|
-
end
|
404
|
-
|
405
|
-
if missing_formats.any?
|
406
|
-
raise "`DB_DUMP_EXTENSIONS` has no keys for #{missing_formats}" \
|
407
|
-
' from `DB_DUMP_FORMATS`'
|
408
|
-
end
|
409
|
-
|
410
|
-
regexp_escaped_db_dump_extensions =
|
411
|
-
DB_DUMP_EXTENSIONS.values.map do |db_dump_extension|
|
412
|
-
Regexp.escape(db_dump_extension)
|
413
|
-
end
|
414
|
-
|
415
|
-
DB_DUMP_REGEXP = /^
|
416
|
-
#{DB_DUMPS_DIR}#{Regexp.escape(File::SEPARATOR)}
|
417
|
-
#{DB_CONFIG[:database]}_#{DB_DUMP_TIMESTAMP_REGEXP}
|
418
|
-
(#{regexp_escaped_db_dump_extensions.join('|')})
|
419
|
-
$/xo
|
420
|
-
|
421
|
-
def self.all
|
422
|
-
Dir[File.join(DB_DUMPS_DIR, '*')]
|
423
|
-
.select { |file| file.match?(DB_DUMP_REGEXP) }
|
424
|
-
.map! { |file| new filename: file }
|
425
|
-
.sort!
|
426
|
-
end
|
427
|
-
|
428
|
-
attr_reader :version, :timestamp, :format
|
429
|
-
|
430
|
-
def initialize(filename: nil, format: 'custom')
|
431
|
-
if filename
|
432
|
-
@extension = File.extname(filename)
|
433
|
-
@format = DB_DUMP_EXTENSIONS.key(@extension)
|
434
|
-
self.version = filename[/#{DB_DUMP_TIMESTAMP_REGEXP}/o]
|
435
|
-
else
|
436
|
-
@format = format
|
437
|
-
@extension = DB_DUMP_EXTENSIONS[@format]
|
438
|
-
self.timestamp = Time.now
|
439
|
-
end
|
440
|
-
end
|
441
|
-
|
442
|
-
def <=>(other)
|
443
|
-
timestamp <=> other.timestamp
|
444
|
-
end
|
445
|
-
|
446
|
-
def to_s
|
447
|
-
"#{readable_timestamp} #{format}"
|
448
|
-
end
|
449
|
-
|
450
|
-
def print
|
451
|
-
puts to_s
|
452
|
-
end
|
453
|
-
|
454
|
-
def path
|
455
|
-
File.join(
|
456
|
-
DB_DUMPS_DIR, "#{DB_CONFIG[:database]}_#{version}#{@extension}"
|
457
|
-
)
|
458
|
-
end
|
459
|
-
|
460
|
-
private
|
461
|
-
|
462
|
-
def version=(value)
|
463
|
-
@version = value
|
464
|
-
@timestamp = Time.strptime(version, DB_DUMP_TIMESTAMP)
|
465
|
-
end
|
466
|
-
|
467
|
-
def timestamp=(value)
|
468
|
-
@timestamp = value
|
469
|
-
@version = timestamp.strftime(DB_DUMP_TIMESTAMP)
|
470
|
-
end
|
471
|
-
|
472
|
-
def readable_timestamp
|
473
|
-
datetime = timestamp.strftime('%F %R')
|
474
|
-
"\e[36m#{datetime}\e[0m"
|
475
|
-
end
|
476
|
-
end
|
477
|
-
|
478
|
-
desc 'Make DB dump'
|
479
|
-
task :create, :format do |_task, args|
|
480
|
-
dump_format =
|
481
|
-
if args[:format]
|
482
|
-
DumpFile::DB_DUMP_FORMATS.find do |db_dump_format|
|
483
|
-
db_dump_format.start_with? args[:format]
|
484
|
-
end
|
485
|
-
else
|
486
|
-
DumpFile::DB_DUMP_FORMATS.first
|
487
|
-
end
|
488
|
-
|
489
|
-
update_pgpass
|
490
|
-
|
491
|
-
filename = DumpFile.new(format: dump_format).path
|
492
|
-
sh "mkdir -p #{DB_DUMPS_DIR}"
|
493
|
-
sh "pg_dump #{DB_ACCESS} -F#{dump_format.chr}" \
|
494
|
-
" #{DB_CONFIG[:database]} > #{filename}"
|
495
|
-
end
|
496
|
-
|
497
|
-
desc 'Restore DB dump'
|
498
|
-
task :restore, :step do |_task, args|
|
499
|
-
step = args[:step] ? Integer(args[:step]) : -1
|
500
|
-
|
501
|
-
update_pgpass
|
502
|
-
|
503
|
-
dump_file = DumpFile.all[step]
|
504
|
-
|
505
|
-
abort 'Dump file not found' unless dump_file
|
506
|
-
|
507
|
-
if Question.new("Restore #{dump_file} ?", %w[yes no]).answer == 'no'
|
508
|
-
abort 'Okay'
|
509
|
-
end
|
510
|
-
|
511
|
-
Rake::Task['db:dump'].invoke
|
512
|
-
|
513
|
-
case dump_file.format
|
514
|
-
when 'custom'
|
515
|
-
sh "pg_restore #{DB_ACCESS} -n public -d #{DB_CONFIG[:database]}" \
|
516
|
-
" #{dump_file.path} --jobs=4 --clean --if-exists"
|
517
|
-
when 'plain'
|
518
|
-
Rake::Task['db:drop'].invoke
|
519
|
-
Rake::Task['db:create'].invoke
|
520
|
-
sh "psql #{DB_ACCESS} #{DB_CONFIG[:database]} < #{dump_file.path}"
|
521
|
-
else
|
522
|
-
raise 'Unknown DB dump file format'
|
523
|
-
end
|
524
|
-
end
|
525
|
-
|
526
|
-
desc 'List DB dumps'
|
527
|
-
task :list do
|
528
|
-
DumpFile.all.each(&:print)
|
529
|
-
end
|
530
|
-
end
|
531
|
-
|
532
|
-
alias_task :dumps, 'dumps:list'
|
533
|
-
alias_task :dump, 'dumps:create'
|
534
|
-
alias_task :restore, 'dumps:restore'
|
535
|
-
|
536
|
-
desc 'Create empty DB'
|
537
|
-
task :create do
|
538
|
-
sh "createdb -U postgres #{DB_CONFIG[:database]} -O #{DB_CONFIG[:user]}"
|
539
|
-
DB_EXTENSIONS.each do |db_extension|
|
540
|
-
sh "psql -U postgres -c 'CREATE EXTENSION #{db_extension}'" \
|
541
|
-
" #{DB_CONFIG[:database]}"
|
542
|
-
end
|
543
|
-
end
|
544
|
-
|
545
|
-
desc 'Drop DB'
|
546
|
-
task :drop, :force do |_task, args|
|
547
|
-
case Question.new("Drop #{DB_CONFIG[:database]} ?", %w[yes no]).answer
|
548
|
-
when 'no'
|
549
|
-
abort 'OK'
|
550
|
-
end
|
551
|
-
|
552
|
-
Rake::Task['db:dump'].invoke unless args[:force]
|
553
|
-
sh "dropdb #{DB_ACCESS} #{DB_CONFIG[:database]}"
|
554
|
-
end
|
555
|
-
end
|
556
|
-
end
|
557
|
-
|
558
|
-
namespace :locales do
|
559
|
-
CROWDIN_CONFIG_FILE = 'config/crowdin.yml'
|
560
|
-
|
561
|
-
desc 'Upload files for translation'
|
562
|
-
task :upload do
|
563
|
-
sh "crowdin --config #{CROWDIN_CONFIG_FILE} upload sources"
|
564
|
-
end
|
565
|
-
|
566
|
-
desc 'Download translated files'
|
567
|
-
task :download do
|
568
|
-
sh "crowdin --config #{CROWDIN_CONFIG_FILE} download translations"
|
569
|
-
end
|
570
|
-
|
571
|
-
desc 'Check locales'
|
572
|
-
task :check do
|
573
|
-
require 'yaml'
|
574
|
-
require 'json'
|
575
|
-
|
576
|
-
## Class for Locale file
|
577
|
-
class Locale
|
578
|
-
attr_reader :code, :hash
|
579
|
-
|
580
|
-
EXT = '.yml'
|
581
|
-
|
582
|
-
def self.load(locales_dir = 'locales')
|
583
|
-
Dir[File.join(__dir__, locales_dir, "*#{EXT}")].map do |file|
|
584
|
-
new File.basename(file, EXT), YAML.load_file(file)
|
585
|
-
end
|
586
|
-
end
|
587
|
-
|
588
|
-
def initialize(code, hash)
|
589
|
-
@code = code
|
590
|
-
@hash = hash
|
591
|
-
end
|
592
|
-
|
593
|
-
class HashCompare
|
594
|
-
def initialize(hash, other_hash)
|
595
|
-
@hash = hash
|
596
|
-
@other_hash = other_hash
|
597
|
-
@diff = {}
|
598
|
-
end
|
599
|
-
|
600
|
-
def different_keys
|
601
|
-
@hash.each_pair do |key, value|
|
602
|
-
other_value = @other_hash[key]
|
603
|
-
if value.is_a?(Hash) && other_value.is_a?(Hash)
|
604
|
-
add_differences_in_hash(value, other_value, key)
|
605
|
-
elsif value.is_a?(Array) && other_value.is_a?(Array)
|
606
|
-
add_differences_in_array(value, other_value, key)
|
607
|
-
elsif other_value.nil? || value.class != other_value.class
|
608
|
-
add_difference(value, key)
|
609
|
-
end
|
610
|
-
end
|
611
|
-
@diff
|
612
|
-
end
|
613
|
-
|
614
|
-
private
|
615
|
-
|
616
|
-
def add_difference(difference, key)
|
617
|
-
@diff[key] = difference unless difference.empty?
|
618
|
-
end
|
619
|
-
|
620
|
-
def add_differences_in_hash(hash, other_hash, key)
|
621
|
-
difference = self.class.new(hash, other_hash).different_keys
|
622
|
-
add_difference(difference, key)
|
623
|
-
end
|
624
|
-
|
625
|
-
def add_differences_in_array(array, other_array, key)
|
626
|
-
difference =
|
627
|
-
if array.size != other_array.size
|
628
|
-
array
|
629
|
-
else
|
630
|
-
differences_in_array(array, other_array)
|
631
|
-
end
|
632
|
-
add_difference(difference, key)
|
633
|
-
end
|
634
|
-
|
635
|
-
def differences_in_array(array, other_array)
|
636
|
-
array.each_with_object([]).with_index do |(object, diff), i|
|
637
|
-
other_object = other_array[i]
|
638
|
-
if object.is_a?(Hash) && other_object.is_a?(Hash)
|
639
|
-
difference = self.class.new(object, other_object).different_keys
|
640
|
-
diff << difference unless difference.empty?
|
641
|
-
end
|
642
|
-
end.compact
|
643
|
-
end
|
644
|
-
end
|
645
|
-
|
646
|
-
def diff(other)
|
647
|
-
HashCompare.new(hash, other.hash).different_keys
|
648
|
-
end
|
649
|
-
end
|
650
|
-
|
651
|
-
locales = Locale.load
|
652
|
-
|
653
|
-
def compare_locales(locale, other_locale)
|
654
|
-
puts "#{locale.code.upcase} -> #{other_locale.code.upcase}:\n\n"
|
655
|
-
puts locale.diff(other_locale).to_yaml
|
656
|
-
end
|
657
|
-
|
658
|
-
locales.each_with_index do |locale, ind|
|
659
|
-
locales[ind..-1].each do |other_locale|
|
660
|
-
next if locale == other_locale
|
661
|
-
compare_locales(locale, other_locale)
|
662
|
-
compare_locales(other_locale, locale)
|
663
|
-
end
|
664
|
-
end
|
665
|
-
end
|
666
|
-
end
|
667
|
-
|
668
|
-
namespace :static do
|
669
|
-
desc 'Check static files'
|
670
|
-
task :check do
|
671
|
-
Dir[File.join(__dir__, 'public/**/*')].each do |file|
|
672
|
-
basename = File.basename(file)
|
673
|
-
grep_options = '--exclude-dir={\.git,log} --color=always'
|
674
|
-
found = `grep -ir '#{basename}' ./ #{grep_options}`
|
675
|
-
next unless found.empty? && File.dirname(file) != @skipping_dir
|
676
|
-
filename = file.sub(__dir__, '')
|
677
|
-
case Question.new("Delete #{filename} ?", %w[yes no skip]).answer
|
678
|
-
when 'yes'
|
679
|
-
`git rm #{file.gsub(' ', '\ ')}`
|
680
|
-
when 'skip'
|
681
|
-
@skipping_dir = File.dirname(file)
|
682
|
-
end
|
683
|
-
end
|
684
|
-
end
|
685
|
-
end
|
686
|
-
|
687
|
-
namespace :config do
|
688
|
-
desc 'Check config files'
|
689
|
-
task :check do
|
690
|
-
example_suffix = '.example'
|
691
|
-
Dir[
|
692
|
-
File.join(__dir__, "config/**/*#{example_suffix}*")
|
693
|
-
].each do |example_filename|
|
694
|
-
regular_filename = example_filename.sub(example_suffix, '')
|
695
|
-
if File.exist? regular_filename
|
696
|
-
if File.mtime(example_filename) > File.mtime(regular_filename)
|
697
|
-
example_basename = File.basename example_filename
|
698
|
-
regular_basename = File.basename regular_filename
|
699
|
-
|
700
|
-
ask_what_to_do = proc do
|
701
|
-
case answer = Question.new(
|
702
|
-
"\e[32m\e[1m#{example_basename}\e[22m\e[0m was modified after" \
|
703
|
-
" \e[31m\e[1m#{regular_basename}\e[22m\e[0m." \
|
704
|
-
" Do you want to edit \e[31m\e[1m#{regular_basename}\e[22m\e[0m ?",
|
705
|
-
%w[yes no show quit]
|
706
|
-
).answer
|
707
|
-
when 'yes'
|
708
|
-
edit_file regular_filename
|
709
|
-
when 'show'
|
710
|
-
show_diff regular_filename, example_filename
|
711
|
-
answer = ask_what_to_do.call
|
712
|
-
end
|
713
|
-
|
714
|
-
answer
|
715
|
-
end
|
716
|
-
|
717
|
-
break if ask_what_to_do.call == 'quit'
|
718
|
-
end
|
719
|
-
else
|
720
|
-
FileUtils.cp example_filename, regular_filename
|
721
|
-
edit_file regular_filename
|
722
|
-
end
|
723
|
-
end
|
724
|
-
end
|
725
|
-
end
|
726
|
-
|
727
|
-
desc 'Start interactive console'
|
728
|
-
task :console, :environment do |_t, args|
|
729
|
-
require 'rack/console'
|
730
|
-
|
731
|
-
args = args.to_hash
|
732
|
-
args[:environment] ||= 'development'
|
733
|
-
ARGV.clear
|
734
|
-
Rack::Console.new(args).start
|
735
|
-
end
|
736
|
-
|
737
|
-
desc 'Start psql'
|
738
|
-
task :psql do
|
739
|
-
update_pgpass
|
740
|
-
sh "psql #{DB_ACCESS} #{DB_CONFIG[:database]}"
|
741
|
-
end
|
742
|
-
|
743
|
-
## Command for update server
|
744
|
-
desc 'Update from git'
|
745
|
-
task :update, :branch, :without_restart do |_t, args|
|
746
|
-
args = args.to_hash
|
747
|
-
args[:branch] ||= :master
|
748
|
-
server = './server'
|
749
|
-
sh "git checkout #{args[:branch]}"
|
750
|
-
sh "git pull origin #{args[:branch]}"
|
751
|
-
next if args[:without_restart]
|
752
|
-
sh 'bundle check || bundle install'
|
753
|
-
sh "#{server} stop"
|
754
|
-
sh 'rake config:check'
|
755
|
-
sh 'rake db:migrate'
|
756
|
-
sh "#{server} start"
|
757
|
-
end
|
758
|
-
|
759
|
-
## Command before creating new branch
|
760
|
-
desc 'Fetch origin and rebase branch from master'
|
761
|
-
task :rebase do
|
762
|
-
sh 'git fetch origin'
|
763
|
-
sh 'git rebase origin/master'
|
764
|
-
end
|
765
|
-
|
766
|
-
## Command for deploy code from git to server
|
767
|
-
## @example rake deploy
|
768
|
-
## Update from git with migrations and restart (for .rb and .erb files update)
|
769
|
-
## @example rake deploy[true]
|
770
|
-
## Update from git without migrations and restart (for static files update)
|
771
|
-
desc 'Deploy to production server'
|
772
|
-
task :deploy, :without_restart do |_t, args|
|
773
|
-
servers = YAML.load_file File.join(__dir__, 'config/deploy.yml')
|
774
|
-
rake_command = "rake update[master#{',true' if args.without_restart}]"
|
775
|
-
servers.each do |server|
|
776
|
-
update_command = "cd #{server[:path]} && #{rake_command}"
|
777
|
-
sh "ssh -t #{server[:ssh]} 'bash --login -c \"#{update_command}\"'"
|
778
|
-
end
|
779
|
-
end
|
780
|
-
|
781
|
-
namespace :assets do
|
782
|
-
assets_dir = File.join __dir__, 'assets'
|
783
|
-
public_dir = File.join __dir__, 'public'
|
784
|
-
|
785
|
-
styles_input_dir = File.join assets_dir, 'styles'
|
786
|
-
styles_input_file = File.join styles_input_dir, 'main.scss'
|
787
|
-
styles_output_dir = File.join public_dir, 'styles'
|
788
|
-
styles_output_file = File.join styles_output_dir, 'main.css'
|
789
|
-
|
790
|
-
scripts_input_dir = File.join assets_dir, 'scripts'
|
791
|
-
scripts_input_file = File.join scripts_input_dir, 'app.js'
|
792
|
-
scripts_output_dir = File.join public_dir, 'scripts', 'app', 'compiled'
|
793
|
-
scripts_output_file = 'app.js'
|
794
|
-
|
795
|
-
namespace :build do
|
796
|
-
desc 'Build all assets'
|
797
|
-
task all: %w[assets:build:styles assets:build:scripts]
|
798
|
-
|
799
|
-
desc 'Build styles assets'
|
800
|
-
task :styles do
|
801
|
-
next unless File.exist? styles_input_file
|
802
|
-
FileUtils.mkdir_p styles_output_dir
|
803
|
-
sh "sass #{styles_input_file} #{styles_output_file} -t compact"
|
804
|
-
end
|
805
|
-
|
806
|
-
desc 'Build scripts assets'
|
807
|
-
task :scripts do
|
808
|
-
next unless File.exist? scripts_input_file
|
809
|
-
sh 'yarn run webpack' \
|
810
|
-
" --entry #{scripts_input_file}" \
|
811
|
-
" --output-path #{scripts_output_dir}" \
|
812
|
-
" --output-filename #{scripts_output_file}"
|
813
|
-
end
|
814
|
-
end
|
815
|
-
|
816
|
-
alias_task :build, 'build:all'
|
817
|
-
|
818
|
-
namespace :watch do
|
819
|
-
desc 'Watch for styles assets'
|
820
|
-
task :styles do
|
821
|
-
sh "sass --watch #{styles_input_dir}:#{styles_output_dir} -t compact"
|
822
|
-
end
|
823
|
-
end
|
824
|
-
end
|