prelands_rails 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +1456 -0
- data/LICENSE.txt +21 -0
- data/Makefile +4 -0
- data/README.md +4 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/prelands_rails/concerns/abstract_interactor.rb +48 -0
- data/lib/prelands_rails/concerns/base.rb +25 -0
- data/lib/prelands_rails/concerns/can_handle_errors.rb +31 -0
- data/lib/prelands_rails/concerns/hash.rb +11 -0
- data/lib/prelands_rails/concerns/my_aws_client.rb +123 -0
- data/lib/prelands_rails/create_simple_source/check_zip_files/detect_absent_files.rb +25 -0
- data/lib/prelands_rails/create_simple_source/check_zip_files/extract_files.rb +31 -0
- data/lib/prelands_rails/create_simple_source/check_zip_files.rb +79 -0
- data/lib/prelands_rails/create_simple_source/compile/html_compiler.rb +106 -0
- data/lib/prelands_rails/create_simple_source/compile.rb +44 -0
- data/lib/prelands_rails/create_simple_source/create_record.rb +44 -0
- data/lib/prelands_rails/create_simple_source/detect_incoming_locales.rb +35 -0
- data/lib/prelands_rails/create_simple_source/upload/uploader.rb +47 -0
- data/lib/prelands_rails/create_simple_source/upload.rb +83 -0
- data/lib/prelands_rails/create_simple_source/validate_zip_content/validate_html/html.rb +115 -0
- data/lib/prelands_rails/create_simple_source/validate_zip_content/validate_html.rb +40 -0
- data/lib/prelands_rails/create_simple_source/validate_zip_content/validate_js/js.rb +62 -0
- data/lib/prelands_rails/create_simple_source/validate_zip_content/validate_js.rb +32 -0
- data/lib/prelands_rails/create_simple_source/validate_zip_content.rb +26 -0
- data/lib/prelands_rails/create_simple_source.rb +48 -0
- data/lib/prelands_rails/update_simple_source/check_zip_files.rb +39 -0
- data/lib/prelands_rails/update_simple_source/compile.rb +38 -0
- data/lib/prelands_rails/update_simple_source/update_record.rb +41 -0
- data/lib/prelands_rails/update_simple_source/upload/delete_compiled_files.rb +30 -0
- data/lib/prelands_rails/update_simple_source/upload.rb +43 -0
- data/lib/prelands_rails/update_simple_source/validate_zip_content.rb +35 -0
- data/lib/prelands_rails/update_simple_source.rb +48 -0
- data/lib/prelands_rails/validate_simple_source.rb +26 -0
- data/lib/prelands_rails/version.rb +3 -0
- data/lib/prelands_rails.rb +41 -0
- data/prelands_rails.gemspec +48 -0
- metadata +314 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 C80609A
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/Makefile
ADDED
data/README.md
ADDED
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "prelands_rails"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PrelandsRails
|
4
|
+
module AbstractInteractor
|
5
|
+
include ::Interactor
|
6
|
+
include ::Interactor::Contracts
|
7
|
+
include CanHandleErrors
|
8
|
+
|
9
|
+
delegate :fail!, :to => :context
|
10
|
+
|
11
|
+
def call
|
12
|
+
begin
|
13
|
+
act
|
14
|
+
rescue ::Interactor::Failure # bad promises
|
15
|
+
report_error 'Bad promises: %s' % context.errors, context.to_h
|
16
|
+
fail! errors: context.errors
|
17
|
+
rescue ::ActiveRecord::RecordNotFound => e
|
18
|
+
report_error e.message, context.to_h
|
19
|
+
fail! errors: 'not found'
|
20
|
+
rescue StandardError => e
|
21
|
+
error_handler e, context.to_h
|
22
|
+
fail! errors: e.message
|
23
|
+
custom_error_handler e
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def act
|
30
|
+
raise NotImplementedError, "The `act` method must be defined in #{self.class}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def custom_error_handler(_e); end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def bad_expects(breaches)
|
38
|
+
errors = build_errors_message breaches
|
39
|
+
fail! errors: errors
|
40
|
+
end
|
41
|
+
|
42
|
+
# [{0=>["fail1", "fail2"], 1=>["fail1", "fail3"]}] => "fail1 ; fail2 ; fail3"
|
43
|
+
def build_errors_message(breaches)
|
44
|
+
msgs = breaches.map(&:messages).inspect.scan /(?<=")[^"\]\[{}]+(?=")/
|
45
|
+
(msgs - [', ']).map { |m| [m,nil] }.to_h.keys.join('; ')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PrelandsRails
|
4
|
+
module Base
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
RX_HTML_FILE = /\.html/
|
9
|
+
NAME_RX = /index_(\w{2})\.html/
|
10
|
+
|
11
|
+
# извлекаем входящие html файлы из контекста
|
12
|
+
def incoming_html_files
|
13
|
+
context.files_content.select { |key| key =~ RX_HTML_FILE }
|
14
|
+
end
|
15
|
+
|
16
|
+
# извлекаем скомпилированные html файлы из контекста
|
17
|
+
def compiled_html_files
|
18
|
+
context.compiled_htmls.select { |key| key =~ RX_HTML_FILE }
|
19
|
+
end
|
20
|
+
|
21
|
+
def make_tmp_path
|
22
|
+
'tmp/cache/%s' % SecureRandom.uuid
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# # frozen_string_literal: true
|
2
|
+
|
3
|
+
module PrelandsRails
|
4
|
+
module CanHandleErrors
|
5
|
+
private
|
6
|
+
|
7
|
+
# Отправит ошибку в Sentry, выведет в лог красным название класса, текст ошибки и backtrace
|
8
|
+
def error_handler(error, extra = {})
|
9
|
+
error_text = ['[%s] ' % self.class, error.message].join
|
10
|
+
Rails.logger.warn "\n\t\e[31m\e[1m%s\e[0m" % error_text
|
11
|
+
Rails.logger.warn "\n\e[31m\t%s \e[0m\n" % (error.backtrace[0..9] * "\n\t")
|
12
|
+
Raven.capture_exception(error, extra: extra) if defined?(Raven)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Отправит message в Sentry, в dev/test лог скинет pretty extra info
|
16
|
+
def report_error(error_message = context.errors, extra = {})
|
17
|
+
message = ['[%s] ' % self.class, error_message].join
|
18
|
+
|
19
|
+
if Rails.env.test? || Rails.env.development?
|
20
|
+
div = '-' * 120
|
21
|
+
Rails.logger.warn div
|
22
|
+
Rails.logger.warn "\e[31m\e[1m%s\e[0m\n" % message
|
23
|
+
# Rails.logger.ap extra
|
24
|
+
Rails.logger.warn div
|
25
|
+
else
|
26
|
+
Raven.capture_message(message, extra: extra) if defined?(Raven)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class Hash
|
2
|
+
def to_struct
|
3
|
+
Struct.new(*keys).new(*values)
|
4
|
+
end
|
5
|
+
|
6
|
+
# Convert Hash to OpenStruct recursively
|
7
|
+
# https://coderwall.com/p/74rajw/convert-a-complex-nested-hash-to-an-object
|
8
|
+
def to_o
|
9
|
+
JSON.parse to_json, object_class: OpenStruct
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aws-sdk-s3'
|
4
|
+
|
5
|
+
module PrelandsRails
|
6
|
+
class MyAwsClient
|
7
|
+
|
8
|
+
ACL_PUBLIC_READ = 'public-read'
|
9
|
+
|
10
|
+
def initialize(access_key, secret_key, region)
|
11
|
+
@s3 = Aws::S3::Resource.new(region: region, access_key_id: access_key, secret_access_key: secret_key)
|
12
|
+
end
|
13
|
+
|
14
|
+
def bucket_objects_count(bucket_name)
|
15
|
+
bucket = @s3.bucket bucket_name
|
16
|
+
result = bucket.objects.count
|
17
|
+
log :bucket_objects_count, 'bucket_name=%s, result=%s' % [bucket_name, result]
|
18
|
+
result
|
19
|
+
rescue Aws::S3::Errors::NoSuchBucket
|
20
|
+
log :bucket_objects_count, ['No such bucket: ', bucket_name].join
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [struct Aws::S3::Types::ListBucketsOutput]
|
24
|
+
def list_buckets
|
25
|
+
result = @s3.client.list_buckets({})
|
26
|
+
log :list_buckets, result.buckets.map(&:name)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [True]
|
30
|
+
def create_bucket_if_not_exists(bucket_name)
|
31
|
+
log :create_bucket_if_not_exists, 'bucket_name=%s' % bucket_name
|
32
|
+
@s3.create_bucket bucket: bucket_name
|
33
|
+
true
|
34
|
+
rescue Aws::S3::Errors::BucketAlreadyOwnedByYou
|
35
|
+
true
|
36
|
+
rescue StandardError => e
|
37
|
+
log :create_bucket_if_not_exists, e.message
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete_bucket(bucket_name)
|
42
|
+
log :delete_bucket, 'bucket_name=%s' % bucket_name
|
43
|
+
@s3.client.delete_bucket bucket: bucket_name
|
44
|
+
rescue StandardError => e
|
45
|
+
log :delete_bucket, e.message
|
46
|
+
false
|
47
|
+
end
|
48
|
+
|
49
|
+
def list_objects(bucket_name)
|
50
|
+
log :list_objects, 'bucket_name=%s' % bucket_name
|
51
|
+
result = (@s3.client.list_objects bucket: bucket_name).contents.map(&:key)
|
52
|
+
log :list_objects, result
|
53
|
+
result
|
54
|
+
rescue StandardError => e
|
55
|
+
log :list_objects, e.message
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
59
|
+
def delete_objects(bucket_name, objects)
|
60
|
+
log :delete_objects, 'bucket_name=%s' % bucket_name
|
61
|
+
result = @s3.client.delete_objects({
|
62
|
+
bucket: bucket_name,
|
63
|
+
delete: {
|
64
|
+
objects: objects
|
65
|
+
}
|
66
|
+
})
|
67
|
+
log :delete_objects, result
|
68
|
+
rescue StandardError => e
|
69
|
+
log :delete_objects, e.message
|
70
|
+
false
|
71
|
+
end
|
72
|
+
|
73
|
+
# @param [String] path_to_file Абсолютный к файлу на хосте.
|
74
|
+
# @param [String] s3_file_name Под каким именем будет сохранён файл на AWS.
|
75
|
+
# @param [String] bucket_name Название bucket
|
76
|
+
|
77
|
+
def upload_file(path_to_file, s3_file_name, bucket_name)
|
78
|
+
begin
|
79
|
+
obj = @s3.bucket(bucket_name).object s3_file_name
|
80
|
+
begin
|
81
|
+
if obj.get
|
82
|
+
log :upload_file, [s3_file_name, ' exists, skipping..'].join('')
|
83
|
+
end
|
84
|
+
rescue Aws::S3::Errors::NoSuchKey => _e
|
85
|
+
obj.upload_file path_to_file, acl: ACL_PUBLIC_READ
|
86
|
+
log :upload_file, [s3_file_name, ' uploaded to "', bucket_name, '".'].join('')
|
87
|
+
end
|
88
|
+
rescue Aws::S3::Errors::NoSuchBucket => _e
|
89
|
+
log :upload_file, 'No such bucket, create bucket: \'%s\'] ' % bucket_name
|
90
|
+
create_bucket_if_not_exists bucket_name
|
91
|
+
retry
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def log(method, output)
|
98
|
+
Rails.logger.info "\r\t\e[33m\e[1m<MyAwsClient.%s> %s\e[0m" % [method, output]
|
99
|
+
nil
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# require 'my_aws_client'
|
105
|
+
# client = MyAwsClient.new ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY'], ENV['AWS_S3_REGION']
|
106
|
+
#
|
107
|
+
# client.list_buckets
|
108
|
+
# => #<struct Aws::S3::Types::ListBucketsOutput
|
109
|
+
# buckets=
|
110
|
+
# #<struct Aws::S3::Types::Bucket name="psckeditor-dev", creation_date=2020-12-24 13:22:29 UTC>,
|
111
|
+
# owner=#<struct Aws::S3::Types::Owner display_name=nil, id="ca37dafdbd424d0f11ccb2c87a2152b6174bf3986ebbf22760d77eb306f608c2
|
112
|
+
#
|
113
|
+
# client.bucket_objects_count "psckeditor-dev"
|
114
|
+
# => 3
|
115
|
+
#
|
116
|
+
# client.bucket_objects_count "psckeditor-dev222222222"
|
117
|
+
# => nil
|
118
|
+
#
|
119
|
+
# client.create_bucket_if_not_exists 'psckeditor-dev2'
|
120
|
+
# => true
|
121
|
+
#
|
122
|
+
# MyAwsClient.create_bucket_if_not_exists 'psckeditor-dev2'
|
123
|
+
# => true
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PrelandsRails
|
4
|
+
class CreateSimpleSource
|
5
|
+
class CheckZipFiles
|
6
|
+
#
|
7
|
+
# Вернёт массив вида ['index.js not found',..]
|
8
|
+
# или пустой массив, если все ожидаемые файлы присутствуют в архиве с исходниками преленда
|
9
|
+
#
|
10
|
+
module DetectAbsentFiles
|
11
|
+
def detect_absent_files(expected_files, incoming_files)
|
12
|
+
raise 'expected_files must be present' unless expected_files.present?
|
13
|
+
|
14
|
+
# опрашиваем по списку пришедший контент
|
15
|
+
expected_files.map do |efile|
|
16
|
+
sought = incoming_files&.find { |ifile| ifile.ftype == efile.ftype && ifile.name == efile.name }
|
17
|
+
if sought.nil?
|
18
|
+
'%s not found' % efile.name
|
19
|
+
end
|
20
|
+
end.compact
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PrelandsRails
|
4
|
+
class CreateSimpleSource
|
5
|
+
class CheckZipFiles
|
6
|
+
module ExtractFiles
|
7
|
+
|
8
|
+
# @return [Hash] { file_name => file_content, ... }
|
9
|
+
def read_into_memory(expected_files, tempfile)
|
10
|
+
Zip::File.open(tempfile) do |zipfile|
|
11
|
+
zipfile.map do |entry|
|
12
|
+
next unless expected_files.find_index { |efile| efile.name == entry.name }.present?
|
13
|
+
|
14
|
+
# Extract
|
15
|
+
tmp_path = Rails.root.join make_tmp_path
|
16
|
+
entry.extract tmp_path
|
17
|
+
|
18
|
+
# Read into memory
|
19
|
+
content = entry.get_input_stream.read
|
20
|
+
|
21
|
+
# Remove tmp file
|
22
|
+
File.delete(tmp_path) if File.exist?(tmp_path)
|
23
|
+
|
24
|
+
[entry.name, content]
|
25
|
+
end.compact.to_h
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PrelandsRails
|
4
|
+
class CreateSimpleSource
|
5
|
+
#
|
6
|
+
# Проверит наличие файлов и директорий в архиве с исходниками преленда:
|
7
|
+
#
|
8
|
+
# * должен быть файл index.js
|
9
|
+
# * должен быть файл index.css
|
10
|
+
# * должна быть директория images/
|
11
|
+
# * должны быть все языковые файлы index_<lang>.html, согласно локалям преленда
|
12
|
+
# * размер любого файла не должен превышать 8мб
|
13
|
+
#
|
14
|
+
class CheckZipFiles
|
15
|
+
include ::PrelandsRails::AbstractInteractor
|
16
|
+
include ::Interactor
|
17
|
+
include ::Interactor::Contracts
|
18
|
+
include ::PrelandsRails::Base
|
19
|
+
include DetectAbsentFiles
|
20
|
+
include ExtractFiles
|
21
|
+
|
22
|
+
expects do
|
23
|
+
required(:archive).filled # Rack::Test::UploadedFile | ActionDispatch::Http::UploadedFile
|
24
|
+
required(:expected_locales).value(type?: Array)
|
25
|
+
required(:incoming_locales).filled(type?: Array)
|
26
|
+
end
|
27
|
+
|
28
|
+
assures do
|
29
|
+
required(:files_content).value(type?: Hash) # js,html,css: { file_name => file_content, ... }
|
30
|
+
end
|
31
|
+
|
32
|
+
before do
|
33
|
+
@expected_files =
|
34
|
+
[
|
35
|
+
{ ftype: :directory, name: 'images/' },
|
36
|
+
{ ftype: :file, name: 'index.css' },
|
37
|
+
{ ftype: :file, name: 'index.js' }
|
38
|
+
]
|
39
|
+
|
40
|
+
# читаем в память [и компилируем] все входящие с исходником локали
|
41
|
+
(context.expected_locales + context.incoming_locales).uniq.each do |short_name|
|
42
|
+
@expected_files << { ftype: :file, name: 'index_%s.html' % short_name }
|
43
|
+
end
|
44
|
+
|
45
|
+
@expected_files = @expected_files.map(&:to_struct).freeze
|
46
|
+
end
|
47
|
+
|
48
|
+
def act
|
49
|
+
# если нет хотя бы одного файла - уходим с ошибкой
|
50
|
+
absents = detect_absent_files @expected_files, incoming_files
|
51
|
+
if absents.present?
|
52
|
+
fail! errors: absents.join('; ')
|
53
|
+
end
|
54
|
+
|
55
|
+
# считываем в память файлы, которые необходимо валидировать
|
56
|
+
rx1 = /\.(css|js|html)/
|
57
|
+
files = @expected_files.select { |file| file.ftype == :file && file.name =~ rx1 }
|
58
|
+
context.files_content, context.tmp_paths = read_into_memory files, context.archive.tempfile
|
59
|
+
end
|
60
|
+
|
61
|
+
def incoming_files
|
62
|
+
@incoming_files ||= Zip::File.open(context.archive.tempfile) do |zipfile|
|
63
|
+
zipfile.map do |file|
|
64
|
+
if file.size > MAX_SIZE
|
65
|
+
fail! errors: 'File too large when extracted (must be less than %s bytes)' % MAX_SIZE
|
66
|
+
end
|
67
|
+
{ ftype: file.ftype, name: file.name }
|
68
|
+
end
|
69
|
+
end.map(&:to_struct).freeze
|
70
|
+
end
|
71
|
+
|
72
|
+
on_breach { |breaches| bad_expects breaches }
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
MAX_SIZE = 1024**2 * 8 # 8 MiB
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PrelandsRails
|
4
|
+
class CreateSimpleSource
|
5
|
+
class Compile
|
6
|
+
#
|
7
|
+
# Компилирует html преленда.
|
8
|
+
#
|
9
|
+
class HtmlCompiler
|
10
|
+
|
11
|
+
attr_reader :compiled
|
12
|
+
|
13
|
+
# @param [String] string Исходное содержимое входящего html файла.
|
14
|
+
# @param [String] index_css Исходное содержимое файла index.css
|
15
|
+
# @param [String] index_js Исходное содержимое файла index.js
|
16
|
+
# @param [String] lang Локаль
|
17
|
+
#
|
18
|
+
def initialize(string, index_css, index_js, lang, static_js_path)
|
19
|
+
@string = string.gsub(/(\r\n?|\n)/, '') # убираем переносы, мешающие регуляркам
|
20
|
+
@index_css = index_css
|
21
|
+
@index_js = index_js
|
22
|
+
@lang = lang
|
23
|
+
@static_js_path = static_js_path
|
24
|
+
prepare
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
META = '<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">'
|
30
|
+
HTML = '<!DOCTYPE html><html lang="%{lang}">%{head}%{body}</html>'
|
31
|
+
|
32
|
+
attr_accessor :head, :body
|
33
|
+
|
34
|
+
def prepare
|
35
|
+
build_head
|
36
|
+
build_body
|
37
|
+
build_html
|
38
|
+
end
|
39
|
+
|
40
|
+
# Из входящего head выберем только то, что нужно,
|
41
|
+
# добавим meta,
|
42
|
+
# впишем авторский стиль из index.css,
|
43
|
+
# добавим наш скрипт
|
44
|
+
|
45
|
+
def build_head
|
46
|
+
# фиксируем head
|
47
|
+
head_rx = /(?<=<head).+(?=<\/head>)/
|
48
|
+
result = @string[head_rx]
|
49
|
+
|
50
|
+
# фиксируем title
|
51
|
+
title_rx = /<title[^>]*>[^<]+<\/title>/
|
52
|
+
title = @string[title_rx]
|
53
|
+
|
54
|
+
# из head выбираем подключаемые скрипты (js)
|
55
|
+
head_script_rx = /<script[^>]*>[^<]*<\/script>/
|
56
|
+
head_scripts = result.scan head_script_rx
|
57
|
+
|
58
|
+
# из head выбираем подключаемые линки (css), исключая `index.css`
|
59
|
+
head_link_rx = /<link[^>]+>/
|
60
|
+
head_links = result.scan head_link_rx
|
61
|
+
head_links = head_links.select { |string| !string['index.css'] }.compact
|
62
|
+
|
63
|
+
# собираем извлечённое в один массив
|
64
|
+
@head = head_scripts.concat(head_links)
|
65
|
+
@head.unshift title
|
66
|
+
@head.unshift META
|
67
|
+
@head << ('<style>%s</style>' % @index_css)
|
68
|
+
@head = '<head>%s</head>' % @head.join('')
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
def build_body
|
73
|
+
body_rx = /<body[^>]*>.+<\/body>/
|
74
|
+
result = @string[body_rx]
|
75
|
+
@body = result
|
76
|
+
@body += static_js
|
77
|
+
end
|
78
|
+
|
79
|
+
# собираем результат
|
80
|
+
def build_html
|
81
|
+
@compiled =
|
82
|
+
HTML % {
|
83
|
+
lang: @lang,
|
84
|
+
head: @head,
|
85
|
+
body: @body
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def static_js
|
90
|
+
<<-JS
|
91
|
+
<script>
|
92
|
+
(function(d, s, id){
|
93
|
+
var js, fjs = d.getElementsByTagName(s)[0];
|
94
|
+
if (d.getElementById(id)) {return;}
|
95
|
+
js = d.createElement(s); js.id = id;
|
96
|
+
js.async = true;
|
97
|
+
js.src = "#{@static_js_path}?" + Math.random();
|
98
|
+
fjs.parentNode.insertBefore(js, fjs);
|
99
|
+
}(document, 'script', 'sdk'));
|
100
|
+
</script>
|
101
|
+
JS
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module PrelandsRails
|
2
|
+
class CreateSimpleSource
|
3
|
+
#
|
4
|
+
# Из валидных исходников соберёт преленд.
|
5
|
+
#
|
6
|
+
class Compile
|
7
|
+
include ::PrelandsRails::AbstractInteractor
|
8
|
+
include ::Interactor
|
9
|
+
include ::Interactor::Contracts
|
10
|
+
include ::PrelandsRails::Base
|
11
|
+
|
12
|
+
expects do
|
13
|
+
required(:files_content).value(type?: Hash) # { file_name[String] => file_content[String], ... }
|
14
|
+
required(:static_js_path).value(type?: String)
|
15
|
+
end
|
16
|
+
|
17
|
+
assures do
|
18
|
+
required(:compiled_htmls).value(type?: Hash) # { file_name[String] => compiled_content[String], ... }
|
19
|
+
end
|
20
|
+
|
21
|
+
def act
|
22
|
+
context[:compiled_htmls] =
|
23
|
+
incoming_html_files.map do |key, content|
|
24
|
+
if NAME_RX =~ key
|
25
|
+
compiled = HtmlCompiler.new(content, index_css, index_js, $1, context.static_js_path).compiled
|
26
|
+
[key, compiled]
|
27
|
+
end
|
28
|
+
end.to_h
|
29
|
+
end
|
30
|
+
|
31
|
+
on_breach { |breaches| bad_expects breaches }
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def index_js
|
36
|
+
context.files_content['index.js']
|
37
|
+
end
|
38
|
+
|
39
|
+
def index_css
|
40
|
+
context.files_content['index.css']
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module PrelandsRails
|
2
|
+
class CreateSimpleSource
|
3
|
+
#
|
4
|
+
# Создаст/обновит запись ::Preland::SimpleSource
|
5
|
+
#
|
6
|
+
class CreateRecord
|
7
|
+
include ::PrelandsRails::AbstractInteractor
|
8
|
+
include ::Interactor
|
9
|
+
include ::Interactor::Contracts
|
10
|
+
include ::PrelandsRails::Base
|
11
|
+
|
12
|
+
expects do
|
13
|
+
required(:archive).filled # Входящий архив с исходниками от фрилансера. Rack::Test::UploadedFile | ActionDispatch::Http::UploadedFile
|
14
|
+
required(:aws_prefix).value(type?: String) # имя директории в букете
|
15
|
+
required(:preland_id).filled
|
16
|
+
required(:preland_domain_ids).value(type?: Array)
|
17
|
+
required(:incoming_locales).filled(type?: Array)
|
18
|
+
required(:model_preland_simple_source).filled
|
19
|
+
end
|
20
|
+
|
21
|
+
assures do
|
22
|
+
required(:preland_simple_source).filled
|
23
|
+
end
|
24
|
+
|
25
|
+
before do
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
def act
|
30
|
+
params = {
|
31
|
+
aws_prefix: context.aws_prefix,
|
32
|
+
archive: context.archive,
|
33
|
+
preland_id: context.preland_id,
|
34
|
+
preland_domain_ids: context.preland_domain_ids,
|
35
|
+
locales: context.incoming_locales
|
36
|
+
}
|
37
|
+
context.preland_simple_source =
|
38
|
+
context.model_preland_simple_source.create! params
|
39
|
+
end
|
40
|
+
|
41
|
+
on_breach { |breaches| bad_expects breaches }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PrelandsRails
|
4
|
+
class CreateSimpleSource
|
5
|
+
#
|
6
|
+
# запоминаем, какие локали пришли в исходнике
|
7
|
+
#
|
8
|
+
class DetectIncomingLocales
|
9
|
+
include ::PrelandsRails::AbstractInteractor
|
10
|
+
include ::Interactor
|
11
|
+
include ::Interactor::Contracts
|
12
|
+
include ::PrelandsRails::Base
|
13
|
+
|
14
|
+
expects do
|
15
|
+
required(:archive).filled # Входящий архив с исходниками от фрилансера. Rack::Test::UploadedFile | ActionDispatch::Http::UploadedFile
|
16
|
+
end
|
17
|
+
|
18
|
+
assures do
|
19
|
+
required(:incoming_locales).filled(type?: Array)
|
20
|
+
end
|
21
|
+
|
22
|
+
def act
|
23
|
+
context.incoming_locales =
|
24
|
+
Zip::File.open(context.archive.tempfile) do |zipfile|
|
25
|
+
zipfile.map do |file|
|
26
|
+
file.name[NAME_RX]
|
27
|
+
$1
|
28
|
+
end
|
29
|
+
end.compact
|
30
|
+
end
|
31
|
+
|
32
|
+
on_breach { |breaches| bad_expects breaches }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|