vito 0.0.4 → 0.0.5
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 +4 -4
- data/.travis.yml +11 -0
- data/README.md +32 -19
- data/Rakefile +5 -20
- data/bin/vito +1 -1
- data/lib/vito.rb +5 -1
- data/lib/vito/command_line/command.rb +21 -0
- data/lib/vito/command_line/document_flags.rb +30 -0
- data/lib/vito/command_line/options.rb +41 -0
- data/lib/vito/command_line/string.rb +35 -0
- data/lib/vito/commands/help.rb +62 -0
- data/lib/vito/commands/install.rb +17 -0
- data/lib/vito/core_ext/string.rb +33 -0
- data/lib/vito/dsl/installation.rb +5 -2
- data/lib/vito/dsl/server.rb +3 -3
- data/lib/vito/dsl_file.rb +11 -5
- data/lib/vito/operating_systems/ubuntu_10.rb +4 -0
- data/lib/vito/output.rb +3 -1
- data/lib/vito/recipe.rb +29 -4
- data/lib/vito/recipes/apache/install.rb +155 -0
- data/lib/vito/recipes/apache/service.rb +20 -0
- data/lib/vito/recipes/git/install.rb +39 -0
- data/lib/vito/recipes/passenger/install.rb +83 -0
- data/lib/vito/recipes/passenger/paths.rb +27 -0
- data/lib/vito/recipes/postgres/install.rb +106 -0
- data/lib/vito/recipes/rbenv/install.rb +54 -0
- data/lib/vito/recipes/ruby/install.rb +57 -0
- data/lib/vito/recipes/ruby/paths.rb +19 -0
- data/lib/vito/tasks/vagrant_bootstrap.rb +49 -0
- data/lib/vito/tests/vagrant_test_box.rb +44 -0
- data/lib/vito/utils/program_version.rb +2 -2
- data/lib/vito/version.rb +1 -1
- data/spec/acceptance/recipes/apache_acceptance_spec.rb +25 -0
- data/spec/acceptance/recipes/git_acceptance_spec.rb +11 -11
- data/spec/acceptance/recipes/postgres_acceptance_spec.rb +11 -11
- data/spec/acceptance/recipes/rbenv_acceptance_spec.rb +13 -13
- data/spec/acceptance/recipes/ruby_acceptance_spec.rb +15 -15
- data/spec/support/vagrant.rb +21 -16
- data/spec/vagrant_boxes/centos63/.gitkeep +0 -0
- data/{Vagrantfile → spec/vagrant_boxes/centos63/Vagrantfile} +5 -4
- data/spec/vagrant_boxes/ubuntu10/Vagrantfile +100 -0
- data/spec/vagrant_boxes/ubuntu12/Vagrantfile +100 -0
- data/spec/vito/command_line/command_spec.rb +20 -0
- data/spec/vito/command_line/options_spec.rb +50 -0
- data/spec/vito/command_line/string_spec.rb +32 -0
- data/spec/vito/commands/install_spec.rb +9 -0
- data/spec/vito/connection_spec.rb +2 -2
- data/spec/vito/core_ext/string_spec.rb +19 -0
- data/spec/vito/dsl/installation_spec.rb +2 -2
- data/spec/vito/dsl_file_spec.rb +56 -18
- data/spec/vito/output_spec.rb +2 -2
- data/spec/vito/recipe_spec.rb +53 -3
- data/spec/vito/recipes/apache/install_spec.rb +140 -0
- data/spec/vito/recipes/passenger/paths_spec.rb +18 -0
- data/spec/vito/recipes/ruby_spec.rb +5 -5
- data/spec/vito/tasks/vagrant_bootstrap_spec.rb +65 -0
- data/spec/vito/tests/vagrant_test_box_spec.rb +69 -0
- data/spec/vito/utils/program_version_spec.rb +4 -2
- data/templates/apache2/vito_site +12 -0
- data/vito.gemspec +1 -1
- data/vito.rb +7 -5
- metadata +53 -13
- data/lib/vito/recipes/git.rb +0 -37
- data/lib/vito/recipes/postgres.rb +0 -104
- data/lib/vito/recipes/rbenv.rb +0 -47
- data/lib/vito/recipes/ruby.rb +0 -43
- data/lib/vito/shell_initializer.rb +0 -21
- data/spec/vito/shell_initializer_spec.rb +0 -22
@@ -0,0 +1,106 @@
|
|
1
|
+
module Vito
|
2
|
+
module Recipes
|
3
|
+
module Postgres
|
4
|
+
class Install < Vito::Recipe
|
5
|
+
def install
|
6
|
+
if installed?
|
7
|
+
Vito::Log.write "Postgres already installed."
|
8
|
+
else
|
9
|
+
Vito::Log.write "Installing Postgres"
|
10
|
+
install_os_dependencies
|
11
|
+
install_postgres
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def postgres_installed?
|
18
|
+
@memo ||= query("psql --version").success?
|
19
|
+
end
|
20
|
+
|
21
|
+
def user_exists?
|
22
|
+
query("sudo su - postgres -c \"psql -tAc \\\\\"SELECT 1 FROM pg_roles WHERE rolname='#{username}'\\\\\" | grep -q 1 || echo 'hey'\"").result == ""
|
23
|
+
end
|
24
|
+
|
25
|
+
def utf_template?
|
26
|
+
query("sudo su - postgres -c \"psql -d template1 -c \\\\\"SHOW SERVER_ENCODING;\\\\\" | grep -q UTF || echo 'hey'\"").result == ""
|
27
|
+
end
|
28
|
+
|
29
|
+
def installed?
|
30
|
+
# Only verifies Postgres configuration if it's installed
|
31
|
+
if postgres_installed?
|
32
|
+
Vito::Log.write "User doesn't exist. I will create one." unless user_exists?
|
33
|
+
Vito::Log.write "template1 database's encoding should be UTF-8. I'll adjust this." unless utf_template?
|
34
|
+
end
|
35
|
+
|
36
|
+
postgres_installed? &&
|
37
|
+
user_exists? &&
|
38
|
+
utf_template?
|
39
|
+
end
|
40
|
+
|
41
|
+
def username
|
42
|
+
@options.fetch(:username, 'vitouser')
|
43
|
+
end
|
44
|
+
|
45
|
+
def password
|
46
|
+
@options.fetch(:password, '123456')
|
47
|
+
end
|
48
|
+
|
49
|
+
def install_postgres
|
50
|
+
string = []
|
51
|
+
run_command "sudo apt-get install -y postgresql-9.1 libpq-dev"
|
52
|
+
|
53
|
+
# Create user only if it doesn't exist yet
|
54
|
+
unless user_exists?
|
55
|
+
string << "sudo su - postgres -c \"createuser -d -r -s #{username}\""
|
56
|
+
end
|
57
|
+
|
58
|
+
run_command(string.join(" && ")) unless string.empty?
|
59
|
+
convert_template1_to_utf8 unless utf_template?
|
60
|
+
string = []
|
61
|
+
|
62
|
+
if os.is?(:ubuntu, "10")
|
63
|
+
string << "sudo /etc/init.d/postgresql restart"
|
64
|
+
else
|
65
|
+
string << "sudo service postgresql restart"
|
66
|
+
end
|
67
|
+
run_command "sudo su - postgres -c \"psql -d template1 -c \\\\\"alter user #{username} with password '#{password}'\\\\\";\""
|
68
|
+
|
69
|
+
run_command(string.join(" && "))
|
70
|
+
end
|
71
|
+
|
72
|
+
def install_os_dependencies
|
73
|
+
return if postgres_installed?
|
74
|
+
Vito::Log.write "Installing some OS dependencies first"
|
75
|
+
if os.is?(:ubuntu, "10")
|
76
|
+
run_command("sudo apt-get remove -y postgresql")
|
77
|
+
run_command("sudo apt-get autoremove -y")
|
78
|
+
run_command("sudo apt-get install -y python-software-properties")
|
79
|
+
run_command("sudo add-apt-repository ppa:pitti/postgresql && sudo apt-get update")
|
80
|
+
end
|
81
|
+
|
82
|
+
super(os_dependencies)
|
83
|
+
end
|
84
|
+
|
85
|
+
def os_dependencies
|
86
|
+
%w(libpq-dev postgresql-contrib)
|
87
|
+
end
|
88
|
+
|
89
|
+
def convert_template1_to_utf8
|
90
|
+
# In some cases, the OS locale won't be compatible with UTF-8. When
|
91
|
+
# creating a database, Rails will try to use Postgres' template1 with
|
92
|
+
# UTF-8.
|
93
|
+
#
|
94
|
+
# Here, we delete template1, then login in template0 to recreate
|
95
|
+
# template1 with UTF-8 encoding.
|
96
|
+
run_command "sudo su - postgres -c \"psql -d template1 -c \\\\\"UPDATE pg_database SET datallowconn = TRUE where datname = 'template0';\\\\\"\""
|
97
|
+
run_command "sudo su - postgres -c \"psql -d template0 -c \\\\\"UPDATE pg_database SET datistemplate = FALSE where datname = 'template1'; \\\\\"\""
|
98
|
+
run_command "sudo su - postgres -c \"psql -d template0 -c \\\\\"drop database template1; \\\\\"\""
|
99
|
+
run_command "sudo su - postgres -c \"psql -d template0 -c \\\\\"create database template1 with template = template0 encoding = 'UNICODE' LC_CTYPE = 'en_US.UTF-8' LC_COLLATE = 'C'; \\\\\"\""
|
100
|
+
run_command "sudo su - postgres -c \"psql -d template0 -c \\\\\" UPDATE pg_database SET datistemplate = TRUE where datname = 'template1'; \\\\\"\""
|
101
|
+
run_command "sudo su - postgres -c \"psql -d template1 -c \\\\\" UPDATE pg_database SET datallowconn = FALSE where datname = 'template0'; \\\\\"\""
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Vito
|
2
|
+
module Recipes
|
3
|
+
module Rbenv
|
4
|
+
class Install < Vito::Recipe
|
5
|
+
def install
|
6
|
+
if rbenv_installed?
|
7
|
+
Vito::Log.write "Rbenv already installed."
|
8
|
+
else
|
9
|
+
Vito::Log.write "Installing Rbenv"
|
10
|
+
depends_on_recipe(:git)
|
11
|
+
install_rbenv
|
12
|
+
end
|
13
|
+
|
14
|
+
install_ruby_build
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def rbenv_installed?
|
20
|
+
string = []
|
21
|
+
string << "cd ~/"
|
22
|
+
string << "rbenv commands"
|
23
|
+
query(string.join(" && ")).success?
|
24
|
+
end
|
25
|
+
|
26
|
+
def install_rbenv
|
27
|
+
puts "Installing Rbenv..."
|
28
|
+
string = []
|
29
|
+
# TODO verify if this folder already exists
|
30
|
+
run_command "git clone git://github.com/sstephenson/rbenv.git ~/.rbenv"
|
31
|
+
|
32
|
+
# TODO verify if .bashrc already have these lines
|
33
|
+
string = []
|
34
|
+
string << "echo 'eval \"$(rbenv init -)\"' | cat - ~/.bashrc > ~/vitotemp && mv ~/vitotemp ~/.bashrc"
|
35
|
+
string << "echo 'export PATH=\"\\$HOME/.rbenv/bin:\\$PATH\"' | cat - ~/.bashrc > vitotemp && mv vitotemp ~/.bashrc"
|
36
|
+
|
37
|
+
run_command string.join(" && ")
|
38
|
+
|
39
|
+
query("rm ~/vitotemp")
|
40
|
+
end
|
41
|
+
|
42
|
+
def install_ruby_build
|
43
|
+
if query("[ -d ~/.rbenv/plugins/ruby-build ] && echo 1").result != "1"
|
44
|
+
puts "Installing ruby-build embedded into rbenv..."
|
45
|
+
string = []
|
46
|
+
# TODO verify if this dir already exists
|
47
|
+
string << "git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build"
|
48
|
+
run_command string.join(" && ")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Vito
|
2
|
+
module Recipes
|
3
|
+
module Ruby
|
4
|
+
class Install < Vito::Recipe
|
5
|
+
def install
|
6
|
+
if ruby_exists?
|
7
|
+
Vito::Log.write "Ruby version #{version} is already installed."
|
8
|
+
else
|
9
|
+
Vito::Log.write "Installing Ruby"
|
10
|
+
install_os_dependencies(os_dependencies)
|
11
|
+
depends_on_recipe(:rbenv)
|
12
|
+
install_ruby
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def install_ruby
|
17
|
+
version_to_install = version
|
18
|
+
if version_to_install.nil?
|
19
|
+
version_to_install = "2.0.0-p247"
|
20
|
+
Vito::Log.write "No Ruby version specified, installing #{version_to_install}."
|
21
|
+
end
|
22
|
+
string = []
|
23
|
+
string << "rbenv install #{version_to_install}"
|
24
|
+
string << "rbenv global #{version_to_install}"
|
25
|
+
string << "rbenv rehash"
|
26
|
+
string << "gem install bundler"
|
27
|
+
run_command string.join(" && ")
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def ruby_exists?
|
33
|
+
if version.nil?
|
34
|
+
if @options[:dependent]
|
35
|
+
Vito::Log.write "#{@options[:dependent]} requires Ruby, but didn't specify a version."
|
36
|
+
else
|
37
|
+
Vito::Log.write "No Ruby version specified. Checking for any version instead."
|
38
|
+
end
|
39
|
+
end
|
40
|
+
program_version("ruby -v").matches?(version)
|
41
|
+
end
|
42
|
+
|
43
|
+
def os_dependencies
|
44
|
+
%w(build-essential openssl libreadline6
|
45
|
+
libreadline6-dev zlib1g zlib1g-dev libssl-dev
|
46
|
+
libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3
|
47
|
+
libxml2-dev libxslt1-dev autoconf libc6-dev
|
48
|
+
libncurses5-dev)
|
49
|
+
end
|
50
|
+
|
51
|
+
def version
|
52
|
+
@options[:version]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Vito
|
2
|
+
module Recipes
|
3
|
+
module Ruby
|
4
|
+
class Paths
|
5
|
+
def initialize(recipe)
|
6
|
+
@recipe = recipe
|
7
|
+
end
|
8
|
+
|
9
|
+
def ruby_path
|
10
|
+
@path ||= recipe.query("rbenv which ruby").result.gsub(/\n/, "")
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
attr_reader :recipe
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Vito
|
2
|
+
module Tasks
|
3
|
+
class VagrantBootstrap
|
4
|
+
def initialize(os_name)
|
5
|
+
@os_name = os_name
|
6
|
+
end
|
7
|
+
|
8
|
+
def install
|
9
|
+
puts "=> Initializing #{os_name} box called #{os_name}_test_box into: #{box_location(os_name)}"
|
10
|
+
pwd = Dir.pwd
|
11
|
+
Dir.chdir("spec/vagrant_boxes/#{os_name}")
|
12
|
+
|
13
|
+
puts "Attempting to download and prepare the box..."
|
14
|
+
system "vagrant up && vagrant halt"
|
15
|
+
puts "Attempting to take snapshot of clean box..."
|
16
|
+
|
17
|
+
install_snapshot_plugin
|
18
|
+
system "vagrant snapshot take #{snapshot_name}" unless snapshot_exist?
|
19
|
+
|
20
|
+
puts "Done.\n"
|
21
|
+
Dir.chdir(pwd)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :os_name
|
27
|
+
|
28
|
+
def install_snapshot_plugin
|
29
|
+
snapshot_plugin_name = "vagrant-vbox-snapshot"
|
30
|
+
unless system("vagrant plugin list|grep #{snapshot_plugin_name} &> /dev/null && echo 1")
|
31
|
+
system("vagrant plugin install vagrant-vbox-snapshot")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def snapshot_exist?
|
36
|
+
`vagrant snapshot list|grep #{snapshot_name} &> /dev/null && echo 1`
|
37
|
+
$?.success?
|
38
|
+
end
|
39
|
+
|
40
|
+
def snapshot_name
|
41
|
+
"#{os_name}_initial_box"
|
42
|
+
end
|
43
|
+
|
44
|
+
def box_location(os)
|
45
|
+
"spec/vagrant_boxes/#{os}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Vito
|
2
|
+
module Tests
|
3
|
+
class VagrantTestBox
|
4
|
+
BOXES_PATH = "spec/vagrant_boxes/"
|
5
|
+
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
def initialize(name)
|
9
|
+
@name = name.to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
def ssh_port
|
13
|
+
grep_result = `grep "network :forwarded_port, guest: 22, host" #{vagrantfile_path}`
|
14
|
+
grep_result.match(/host: ([0-9]{4})/)[1]
|
15
|
+
end
|
16
|
+
|
17
|
+
def path
|
18
|
+
"#{BOXES_PATH}#{name}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def initial_snapshot_name
|
22
|
+
"#{name}_initial_box"
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.boxes(*boxes)
|
26
|
+
vagrant_boxes = []
|
27
|
+
if boxes.size > 0
|
28
|
+
vagrant_boxes = boxes
|
29
|
+
else
|
30
|
+
Dir["#{BOXES_PATH}*"].each do |box|
|
31
|
+
vagrant_boxes << File.basename(box) if File.directory?(box)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
vagrant_boxes.map { |box| new(box) }
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def vagrantfile_path
|
40
|
+
"#{path}/Vagrantfile"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/vito/version.rb
CHANGED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Apache recipe" do
|
4
|
+
Vito::Tests::VagrantTestBox.boxes(:ubuntu10, :ubuntu12).each do |box|
|
5
|
+
it "tests on #{box.name}" do
|
6
|
+
setup_vagrant(box)
|
7
|
+
|
8
|
+
assert_installation(box, "git --version").should be_false
|
9
|
+
|
10
|
+
Vito::DslFile.new.run do
|
11
|
+
server do
|
12
|
+
connection :ssh, command: "ssh -i ~/.vagrant.d/insecure_private_key vagrant@localhost -p#{box.ssh_port}", verbose: true
|
13
|
+
install :apache do
|
14
|
+
with :passenger
|
15
|
+
vhosts with: :ssl, path: "/var/projects/someapp/current/public"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
assert_installation(box, "git --version").should be_true
|
21
|
+
assert_installation(box, "gem list|grep passenger").should be_true
|
22
|
+
assert_installation(box, "apache2 -v").should be_true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,20 +1,20 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe "Git recipe" do
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
Vito::Tests::VagrantTestBox.boxes(:ubuntu10, :ubuntu12).each do |box|
|
5
|
+
it "tests on #{box.name}" do
|
6
|
+
setup_vagrant(box)
|
7
7
|
|
8
|
-
|
9
|
-
assert_installation("git --version").should be_false
|
8
|
+
assert_installation(box, "git --version").should be_false
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
Vito::DslFile.new.run do
|
11
|
+
server do
|
12
|
+
connection :ssh, command: "ssh -i ~/.vagrant.d/insecure_private_key vagrant@localhost -p#{box.ssh_port}", verbose: true
|
13
|
+
install :git
|
14
|
+
end
|
15
15
|
end
|
16
|
-
end
|
17
16
|
|
18
|
-
|
17
|
+
assert_installation(box, "git --version").should be_true
|
18
|
+
end
|
19
19
|
end
|
20
20
|
end
|
@@ -1,20 +1,20 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe "Postgres recipe" do
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
Vito::Tests::VagrantTestBox.boxes(:ubuntu10, :ubuntu12).each do |box|
|
5
|
+
it "tests on #{box.name}" do
|
6
|
+
setup_vagrant(box)
|
7
7
|
|
8
|
-
|
9
|
-
assert_installation("psql --version").should be_false
|
8
|
+
assert_installation(box, "psql --version").should be_false
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
Vito::DslFile.new.run do
|
11
|
+
server do
|
12
|
+
connection :ssh, command: "ssh -i ~/.vagrant.d/insecure_private_key vagrant@localhost -p#{box.ssh_port}", verbose: true
|
13
|
+
install :postgres, username: 'sebasoga', password: 'kicks_ass'
|
14
|
+
end
|
15
15
|
end
|
16
|
-
end
|
17
16
|
|
18
|
-
|
17
|
+
assert_installation(box, "psql --version").should be_true
|
18
|
+
end
|
19
19
|
end
|
20
20
|
end
|
@@ -1,22 +1,22 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe "Rbenv recipe" do
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
Vito::Tests::VagrantTestBox.boxes(:ubuntu10, :ubuntu12).each do |box|
|
5
|
+
it "tests on #{box.name}" do
|
6
|
+
setup_vagrant(box)
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
assert_installation("rbenv --version").should be_false
|
8
|
+
assert_installation(box, "git --version").should be_false
|
9
|
+
assert_installation(box, "rbenv --version").should be_false
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
Vito::DslFile.new.run do
|
12
|
+
server do
|
13
|
+
connection :ssh, command: "ssh -i ~/.vagrant.d/insecure_private_key vagrant@localhost -p#{box.ssh_port}", verbose: true
|
14
|
+
install :rbenv
|
15
|
+
end
|
16
16
|
end
|
17
|
-
end
|
18
17
|
|
19
|
-
|
20
|
-
|
18
|
+
assert_installation(box, "git --version").should be_true
|
19
|
+
assert_installation(box, "rbenv --version").should be_true
|
20
|
+
end
|
21
21
|
end
|
22
22
|
end
|