snowglobe 0.1.0
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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +132 -0
- data/.travis.yml +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +35 -0
- data/LICENSE +21 -0
- data/README.md +38 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/snowglobe/bundle.rb +93 -0
- data/lib/snowglobe/command_runner.rb +232 -0
- data/lib/snowglobe/configuration.rb +43 -0
- data/lib/snowglobe/database.rb +28 -0
- data/lib/snowglobe/database_adapters/postgresql.rb +25 -0
- data/lib/snowglobe/database_adapters/sqlite3.rb +26 -0
- data/lib/snowglobe/database_configuration.rb +33 -0
- data/lib/snowglobe/database_configuration_registry.rb +28 -0
- data/lib/snowglobe/filesystem.rb +95 -0
- data/lib/snowglobe/gem_version.rb +55 -0
- data/lib/snowglobe/project_command_runner.rb +69 -0
- data/lib/snowglobe/rails_application.rb +194 -0
- data/lib/snowglobe/test_helpers.rb +19 -0
- data/lib/snowglobe/version.rb +3 -0
- data/lib/snowglobe.rb +13 -0
- data/snowglobe.gemspec +41 -0
- metadata +103 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
module Snowglobe
|
2
|
+
module DatabaseAdapters
|
3
|
+
class PostgreSQL
|
4
|
+
def self.name
|
5
|
+
:postgresql
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :database
|
9
|
+
|
10
|
+
def initialize(database)
|
11
|
+
@database = database
|
12
|
+
end
|
13
|
+
|
14
|
+
def adapter
|
15
|
+
self.class.name
|
16
|
+
end
|
17
|
+
|
18
|
+
def require_dependencies
|
19
|
+
require "pg"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
DatabaseConfigurationRegistry.instance.register(PostgreSQL)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Snowglobe
|
2
|
+
module DatabaseAdapters
|
3
|
+
class SQLite3
|
4
|
+
def self.name
|
5
|
+
:sqlite3
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(_database)
|
9
|
+
end
|
10
|
+
|
11
|
+
def adapter
|
12
|
+
self.class.name
|
13
|
+
end
|
14
|
+
|
15
|
+
def database
|
16
|
+
"db/db.sqlite3"
|
17
|
+
end
|
18
|
+
|
19
|
+
def require_dependencies
|
20
|
+
require "sqlite3"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
DatabaseConfigurationRegistry.instance.register(SQLite3)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative "database_configuration_registry"
|
2
|
+
require "delegate"
|
3
|
+
|
4
|
+
module Snowglobe
|
5
|
+
class DatabaseConfiguration < SimpleDelegator
|
6
|
+
ENVIRONMENTS = %w(development test production).freeze
|
7
|
+
|
8
|
+
attr_reader :adapter_class
|
9
|
+
|
10
|
+
def self.for(database_name, adapter_name)
|
11
|
+
config_class = DatabaseConfigurationRegistry.instance.get(adapter_name)
|
12
|
+
config = config_class.new(database_name)
|
13
|
+
new(config)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(config)
|
17
|
+
@adapter_class = config.class.to_s.split("::").last
|
18
|
+
super(config)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_hash
|
22
|
+
ENVIRONMENTS.each_with_object({}) do |env, config_as_hash|
|
23
|
+
config_as_hash[env] = inner_config_as_hash
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def inner_config_as_hash
|
30
|
+
{ "adapter" => adapter.to_s, "database" => database.to_s }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "singleton"
|
2
|
+
|
3
|
+
module Snowglobe
|
4
|
+
class DatabaseConfigurationRegistry
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@registry = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def register(config_class)
|
12
|
+
registry[config_class.name] = config_class
|
13
|
+
end
|
14
|
+
|
15
|
+
def get(name)
|
16
|
+
registry.fetch(name) do
|
17
|
+
raise KeyError, "No such adapter registered: #{name}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
attr_reader :registry
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
require_relative "database_adapters/postgresql"
|
28
|
+
require_relative "database_adapters/sqlite3"
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
|
3
|
+
module Snowglobe
|
4
|
+
class Filesystem
|
5
|
+
def clean
|
6
|
+
if root_directory.exist?
|
7
|
+
root_directory.rmtree
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def project_directory
|
12
|
+
root_directory.join(Snowglobe.configuration.project_name)
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_project
|
16
|
+
project_directory.mkpath
|
17
|
+
end
|
18
|
+
|
19
|
+
def within_project(&block)
|
20
|
+
Dir.chdir(project_directory, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def find_in_project(path)
|
24
|
+
project_directory.join(path)
|
25
|
+
end
|
26
|
+
|
27
|
+
def open_file(path, *args, &block)
|
28
|
+
wrap_file(path).open(*args, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def comment_lines_matching_in_file(path, pattern)
|
32
|
+
transform_file(path) do |lines|
|
33
|
+
lines.map do |line|
|
34
|
+
if line && line =~ pattern
|
35
|
+
"###{line}"
|
36
|
+
else
|
37
|
+
line
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def transform_file(path)
|
44
|
+
content = read_file(path)
|
45
|
+
lines = content.split(/\n/)
|
46
|
+
transformed_lines = yield lines
|
47
|
+
write_file(path, transformed_lines.join("\n") + "\n")
|
48
|
+
end
|
49
|
+
|
50
|
+
def read_file(path)
|
51
|
+
wrap_file(path).read
|
52
|
+
end
|
53
|
+
|
54
|
+
def write_file(path, content)
|
55
|
+
pathname = wrap_file(path)
|
56
|
+
create_parents_of(pathname)
|
57
|
+
pathname.open("w") { |f| f.write(content) }
|
58
|
+
pathname
|
59
|
+
end
|
60
|
+
|
61
|
+
def append_to_file(path, content, _options = {})
|
62
|
+
pathname = wrap_file(path)
|
63
|
+
create_parents_of(pathname)
|
64
|
+
pathname.open("a") { |f| f.puts(content + "\n") }
|
65
|
+
end
|
66
|
+
|
67
|
+
def remove_from_file(path, pattern)
|
68
|
+
unless pattern.is_a?(Regexp)
|
69
|
+
pattern = Regexp.new("^" + Regexp.escape(pattern) + "$")
|
70
|
+
end
|
71
|
+
|
72
|
+
transform(path) do |lines|
|
73
|
+
lines.reject { |line| line =~ pattern }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def root_directory
|
80
|
+
Snowglobe.configuration.temporary_directory
|
81
|
+
end
|
82
|
+
|
83
|
+
def wrap_file(path)
|
84
|
+
if path.is_a?(Pathname)
|
85
|
+
path
|
86
|
+
else
|
87
|
+
find_in_project(path)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def create_parents_of(pathname)
|
92
|
+
pathname.dirname.mkpath
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Snowglobe
|
2
|
+
class GemVersion
|
3
|
+
def initialize(version)
|
4
|
+
@version = Gem::Version.new(version.to_s + "")
|
5
|
+
end
|
6
|
+
|
7
|
+
def major
|
8
|
+
segments[0]
|
9
|
+
end
|
10
|
+
|
11
|
+
def minor
|
12
|
+
segments[1]
|
13
|
+
end
|
14
|
+
|
15
|
+
def <(other)
|
16
|
+
compare?(:<, other)
|
17
|
+
end
|
18
|
+
|
19
|
+
def <=(other)
|
20
|
+
compare?(:<=, other)
|
21
|
+
end
|
22
|
+
|
23
|
+
def ==(other)
|
24
|
+
compare?(:==, other)
|
25
|
+
end
|
26
|
+
|
27
|
+
def >=(other)
|
28
|
+
compare?(:>=, other)
|
29
|
+
end
|
30
|
+
|
31
|
+
def >(other)
|
32
|
+
compare?(:>, other)
|
33
|
+
end
|
34
|
+
|
35
|
+
def =~(other)
|
36
|
+
Gem::Requirement.new(other).satisfied_by?(version)
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
version.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
attr_reader :version
|
46
|
+
|
47
|
+
def segments
|
48
|
+
@_segments ||= version.to_s.split(".")
|
49
|
+
end
|
50
|
+
|
51
|
+
def compare?(op, other)
|
52
|
+
Gem::Requirement.new("#{op} #{other}").satisfied_by?(version)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Snowglobe
|
2
|
+
class ProjectCommandRunner
|
3
|
+
def initialize(fs)
|
4
|
+
@fs = fs
|
5
|
+
end
|
6
|
+
|
7
|
+
def run_migrations!
|
8
|
+
run_rake_tasks!(["db:drop", "db:create", "db:migrate"])
|
9
|
+
end
|
10
|
+
|
11
|
+
def run_n_unit_tests(*paths)
|
12
|
+
run_command_within_bundle("ruby -I lib -I test", *paths)
|
13
|
+
end
|
14
|
+
|
15
|
+
def run_n_unit_test_suite
|
16
|
+
run_rake_tasks("test", env: { TESTOPTS: "-v" })
|
17
|
+
end
|
18
|
+
|
19
|
+
def run_rake_tasks!(*args, **options, &block)
|
20
|
+
run_rake_tasks(
|
21
|
+
*args,
|
22
|
+
**options,
|
23
|
+
run_successfully: true,
|
24
|
+
&block
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def run_rake_tasks(*tasks)
|
29
|
+
options = tasks.last.is_a?(Hash) ? tasks.pop : {}
|
30
|
+
args = ["bundle", "exec", "rake", *tasks, "--trace"] + [options]
|
31
|
+
run(*args)
|
32
|
+
end
|
33
|
+
|
34
|
+
def run_within_bundle(*args)
|
35
|
+
run(*args) do |runner|
|
36
|
+
runner.command_prefix = "bundle exec"
|
37
|
+
runner.env["BUNDLE_GEMFILE"] = fs.find_in_project("Gemfile").to_s
|
38
|
+
|
39
|
+
runner.around_command do |run_command|
|
40
|
+
Bundler.with_clean_env(&run_command)
|
41
|
+
end
|
42
|
+
|
43
|
+
yield runner if block_given?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def run!(*args, **options, &block)
|
48
|
+
CommandRunner.run!(
|
49
|
+
*args,
|
50
|
+
directory: fs.project_directory,
|
51
|
+
**options,
|
52
|
+
&block
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
def run(*args, **options, &block)
|
57
|
+
CommandRunner.run(
|
58
|
+
*args,
|
59
|
+
directory: fs.project_directory,
|
60
|
+
**options,
|
61
|
+
&block
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
attr_reader :fs
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
require "yaml"
|
3
|
+
|
4
|
+
require_relative "bundle"
|
5
|
+
require_relative "database"
|
6
|
+
require_relative "filesystem"
|
7
|
+
require_relative "project_command_runner"
|
8
|
+
|
9
|
+
module Snowglobe
|
10
|
+
class RailsApplication
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
def_delegators :bundle, :add_gem
|
14
|
+
def_delegators :fs, :append_to_file, :write_file
|
15
|
+
def_delegators :command_runner, :run_migrations!, :run_n_unit_test_suite
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@fs = Filesystem.new
|
19
|
+
@command_runner = ProjectCommandRunner.new(fs)
|
20
|
+
@bundle = Bundle.new(fs: fs, command_runner: command_runner)
|
21
|
+
@database = Database.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def create
|
25
|
+
fs.clean
|
26
|
+
generate
|
27
|
+
|
28
|
+
fs.within_project do
|
29
|
+
# install_gems
|
30
|
+
remove_unwanted_gems
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# def load
|
35
|
+
# load_environment
|
36
|
+
# run_migrations
|
37
|
+
# end
|
38
|
+
|
39
|
+
def evaluate!(code)
|
40
|
+
command_runner = nil
|
41
|
+
|
42
|
+
Dir::Tmpname.create(
|
43
|
+
["", ".rb"],
|
44
|
+
fs.find_in_project("tmp"),
|
45
|
+
) do |path, _, _, _|
|
46
|
+
tempfile = fs.write_file(path, code)
|
47
|
+
|
48
|
+
command_runner = run_command!(
|
49
|
+
"ruby",
|
50
|
+
tempfile.to_s,
|
51
|
+
err: nil,
|
52
|
+
env: { "RUBYOPT" => "" },
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
command_runner.output
|
57
|
+
end
|
58
|
+
|
59
|
+
def run_command!(*args, &block)
|
60
|
+
command_runner.run!(*args, &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
# def gemfile_path
|
64
|
+
# fs.find("Gemfile")
|
65
|
+
# end
|
66
|
+
|
67
|
+
# def temp_views_directory
|
68
|
+
# fs.find_in_project("tmp/views")
|
69
|
+
# end
|
70
|
+
|
71
|
+
# def create_temp_view(path, contents)
|
72
|
+
# full_path = temp_view_path_for(path)
|
73
|
+
# full_path.dirname.mkpath
|
74
|
+
# full_path.open("w") { |f| f.write(contents) }
|
75
|
+
# end
|
76
|
+
|
77
|
+
# def delete_temp_views
|
78
|
+
# if temp_views_directory.exist?
|
79
|
+
# temp_views_directory.rmtree
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
|
83
|
+
# def draw_routes(&block)
|
84
|
+
# Rails.application.routes.draw(&block)
|
85
|
+
# Rails.application.routes
|
86
|
+
# end
|
87
|
+
|
88
|
+
def migration_class_name
|
89
|
+
if rails_version > 5
|
90
|
+
number = [rails_version.major, rails_version.minor].join(".").to_f
|
91
|
+
"ActiveRecord::Migration[#{number}]"
|
92
|
+
else
|
93
|
+
"ActiveRecord::Migration"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
attr_reader :fs, :command_runner, :bundle, :database
|
100
|
+
|
101
|
+
def rails_version
|
102
|
+
@_rails_version ||= bundle.version_of("rails")
|
103
|
+
end
|
104
|
+
|
105
|
+
def migrations_directory
|
106
|
+
fs.find_in_project("db/migrate")
|
107
|
+
end
|
108
|
+
|
109
|
+
def temp_view_path_for(path)
|
110
|
+
temp_views_directory.join(path)
|
111
|
+
end
|
112
|
+
|
113
|
+
def generate
|
114
|
+
rails_new
|
115
|
+
fix_available_locales_warning
|
116
|
+
remove_bootsnap
|
117
|
+
write_database_configuration
|
118
|
+
|
119
|
+
if bundle.version_of("rails") >= 5
|
120
|
+
add_initializer_for_time_zone_aware_types
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def rails_new
|
125
|
+
CommandRunner.run!(
|
126
|
+
%W(rails new #{fs.project_directory} --skip-bundle --no-rc),
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
def fix_available_locales_warning
|
131
|
+
# See here for more on this:
|
132
|
+
# http://stackoverflow.com/questions/20361428/rails-i18n-validation-deprecation-warning
|
133
|
+
fs.transform_file("config/application.rb") do |lines|
|
134
|
+
lines.insert(-3, <<-TEXT)
|
135
|
+
if I18n.respond_to?(:enforce_available_locales=)
|
136
|
+
I18n.enforce_available_locales = false
|
137
|
+
end
|
138
|
+
TEXT
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def remove_bootsnap
|
143
|
+
# Rails 5.2 introduced bootsnap, which is helpful when you're developing
|
144
|
+
# or deploying an app, but we don't really need it (and it messes with
|
145
|
+
# Zeus anyhow)
|
146
|
+
fs.comment_lines_matching_in_file(
|
147
|
+
"config/boot.rb",
|
148
|
+
%r{\Arequire 'bootsnap/setup'},
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
152
|
+
def write_database_configuration
|
153
|
+
fs.open_file("config/database.yml", "w") do |f|
|
154
|
+
YAML.dump(database.config.to_hash, f)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def add_initializer_for_time_zone_aware_types
|
159
|
+
path = "config/initializers/configure_time_zone_aware_types.rb"
|
160
|
+
fs.write_file(path, <<-TEXT)
|
161
|
+
Rails.application.configure do
|
162
|
+
config.active_record.time_zone_aware_types = [:datetime, :time]
|
163
|
+
end
|
164
|
+
TEXT
|
165
|
+
end
|
166
|
+
|
167
|
+
# def load_environment
|
168
|
+
# require environment_file_path
|
169
|
+
# end
|
170
|
+
|
171
|
+
# def environment_file_path
|
172
|
+
# fs.find_in_project("config/environment")
|
173
|
+
# end
|
174
|
+
|
175
|
+
# def run_migrations
|
176
|
+
# fs.within_project do
|
177
|
+
# run_command! "bundle exec rake db:drop db:create db:migrate"
|
178
|
+
# end
|
179
|
+
# end
|
180
|
+
|
181
|
+
# def install_gems
|
182
|
+
# bundle.install_gems
|
183
|
+
# end
|
184
|
+
|
185
|
+
def remove_unwanted_gems
|
186
|
+
bundle.updating do
|
187
|
+
bundle.remove_gem "debugger"
|
188
|
+
bundle.remove_gem "byebug"
|
189
|
+
bundle.add_gem "pry-byebug"
|
190
|
+
bundle.remove_gem "web-console"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
data/lib/snowglobe.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative "snowglobe/configuration"
|
2
|
+
require_relative "snowglobe/rails_application"
|
3
|
+
require_relative "snowglobe/version"
|
4
|
+
|
5
|
+
module Snowglobe
|
6
|
+
def self.configure(&block)
|
7
|
+
configuration.update!(&block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.configuration
|
11
|
+
@_configuration ||= Configuration.new
|
12
|
+
end
|
13
|
+
end
|
data/snowglobe.gemspec
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "snowglobe/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "snowglobe"
|
8
|
+
spec.version = Snowglobe::VERSION
|
9
|
+
spec.authors = ["Elliot Winkler"]
|
10
|
+
spec.email = ["elliot.winkler@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Create temporary Rails applications for use in testing}
|
13
|
+
spec.description = %q{Snowglobe is a gem that helps erect and destroy Rails applications for use in tests.}
|
14
|
+
spec.homepage = "https://github.com/mcmire/snowglobe"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
21
|
+
|
22
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
23
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
24
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
25
|
+
else
|
26
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
27
|
+
"public gem pushes."
|
28
|
+
end
|
29
|
+
|
30
|
+
# Specify which files should be added to the gem when it is released.
|
31
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
32
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
33
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
34
|
+
end
|
35
|
+
spec.bindir = "exe"
|
36
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
37
|
+
spec.require_paths = ["lib"]
|
38
|
+
|
39
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
40
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
41
|
+
end
|