easycompile 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of easycompile might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/README.md +198 -0
- data/bin/easycompile +7 -0
- data/doc/DESIGN_DECISIONS.md +34 -0
- data/doc/README.gen +181 -0
- data/easycompile.gemspec +65 -0
- data/lib/easycompile.rb +5 -0
- data/lib/easycompile/base/change_directory.rb +28 -0
- data/lib/easycompile/base/cmake.rb +53 -0
- data/lib/easycompile/base/colours.rb +88 -0
- data/lib/easycompile/base/commandline_arguments.rb +37 -0
- data/lib/easycompile/base/constants.rb +24 -0
- data/lib/easycompile/base/easycompile.rb +22 -0
- data/lib/easycompile/base/esystem.rb +59 -0
- data/lib/easycompile/base/gem.rb +35 -0
- data/lib/easycompile/base/help.rb +35 -0
- data/lib/easycompile/base/initialize.rb +33 -0
- data/lib/easycompile/base/menu.rb +140 -0
- data/lib/easycompile/base/meson_and_ninja.rb +36 -0
- data/lib/easycompile/base/misc.rb +1157 -0
- data/lib/easycompile/base/opn.rb +27 -0
- data/lib/easycompile/base/process_the_input.rb +107 -0
- data/lib/easycompile/base/remove.rb +77 -0
- data/lib/easycompile/base/reset.rb +140 -0
- data/lib/easycompile/base/run.rb +26 -0
- data/lib/easycompile/compile_as_appdir/compile_as_appdir.rb +45 -0
- data/lib/easycompile/constants/array_possible_archives.rb +45 -0
- data/lib/easycompile/constants/constants.rb +21 -0
- data/lib/easycompile/constants/file_and_directory_constants.rb +137 -0
- data/lib/easycompile/constants/misc.rb +23 -0
- data/lib/easycompile/constants/namespace.rb +16 -0
- data/lib/easycompile/constants/programs_directory.rb +46 -0
- data/lib/easycompile/easycompile/easycompile.rb +80 -0
- data/lib/easycompile/project/project.rb +29 -0
- data/lib/easycompile/requires/require_the_easycompile_project.rb +13 -0
- data/lib/easycompile/requires/require_the_toplevel_methods.rb +11 -0
- data/lib/easycompile/toplevel_methods/copy_file.rb +23 -0
- data/lib/easycompile/toplevel_methods/misc.rb +54 -0
- data/lib/easycompile/toplevel_methods/rinstall2.rb +29 -0
- data/lib/easycompile/version/version.rb +26 -0
- data/lib/easycompile/yaml/name_of_the_build_directory.yml +1 -0
- data/test/testing_easycompile.rb +29 -0
- metadata +144 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# Encoding: UTF-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
# =========================================================================== #
|
5
|
+
# require 'easycompile/base/opn.rb'
|
6
|
+
# =========================================================================== #
|
7
|
+
module Easycompile
|
8
|
+
|
9
|
+
class Base
|
10
|
+
|
11
|
+
# ========================================================================= #
|
12
|
+
# === opn_namespace
|
13
|
+
#
|
14
|
+
# We use a special namespace-name here.
|
15
|
+
# ========================================================================= #
|
16
|
+
def opn_namespace
|
17
|
+
opn(namespace: NAMESPACE)
|
18
|
+
end
|
19
|
+
|
20
|
+
# ========================================================================= #
|
21
|
+
# === show_namespace
|
22
|
+
# ========================================================================= #
|
23
|
+
def show_namespace
|
24
|
+
opn(namespace: Constants::NAMESPACE)
|
25
|
+
end
|
26
|
+
|
27
|
+
end; end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# Encoding: UTF-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
# =========================================================================== #
|
5
|
+
# require 'easycompile/base/process_the_input.rb'
|
6
|
+
# =========================================================================== #
|
7
|
+
module Easycompile
|
8
|
+
|
9
|
+
class Base
|
10
|
+
|
11
|
+
# ========================================================================= #
|
12
|
+
# === process_the_input
|
13
|
+
# ========================================================================= #
|
14
|
+
def process_the_input(
|
15
|
+
i = compile_these_programs?
|
16
|
+
)
|
17
|
+
i.each {|this_entry|
|
18
|
+
# ===================================================================== #
|
19
|
+
# The this_entry part may look like so:
|
20
|
+
#
|
21
|
+
# "php-7.4.2.tar.xz"
|
22
|
+
#
|
23
|
+
# ===================================================================== #
|
24
|
+
# The entry may be nil at this point, though. In that case, we
|
25
|
+
# fetch a random element.
|
26
|
+
# ===================================================================== #
|
27
|
+
this_entry = Dir['*'].first if this_entry.nil?
|
28
|
+
this_entry = this_entry.to_s
|
29
|
+
this_entry.sub!(/\^/,'') if this_entry.include? '^' # Remove ^ from the input here, if it is included.
|
30
|
+
# ===================================================================== #
|
31
|
+
# If input is a (positional) number, such as "3".
|
32
|
+
# ===================================================================== #
|
33
|
+
unless File.exist?(this_entry)
|
34
|
+
if this_entry =~ /^\d+$/
|
35
|
+
# ================================================================= #
|
36
|
+
# Obtain all entries next.
|
37
|
+
# ================================================================= #
|
38
|
+
entries = Dir['*']
|
39
|
+
unless entries.empty?
|
40
|
+
this_entry = this_entry.to_i
|
41
|
+
this_entry = entries.size if this_entry > entries.size # This is to fetch the proper last entry, if input is too long.
|
42
|
+
this_entry = entries.sort[ (this_entry.to_i - 1) ] # Fetch an entry here.
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
# ===================================================================== #
|
47
|
+
# === Handle .gem files too, early on - they won't need a build directory.
|
48
|
+
# ===================================================================== #
|
49
|
+
if this_entry =~ /\.gem$/ # If it is a gem file.
|
50
|
+
do_not_create_a_build_directory # Gem files typically do not need a build dir.
|
51
|
+
end
|
52
|
+
# ===================================================================== #
|
53
|
+
# === Handle yaml files first.
|
54
|
+
#
|
55
|
+
# If the input file is a yaml file, we will assume that the user wants
|
56
|
+
# to change directory first into the SRC_DIR hierarchy.
|
57
|
+
#
|
58
|
+
# Invoke this by doing something like:
|
59
|
+
# ===================================================================== #
|
60
|
+
if this_entry.include? '.yml'
|
61
|
+
yaml_dataset = get_dataset_from(this_entry)
|
62
|
+
this_entry = yaml_dataset['program_path']
|
63
|
+
end
|
64
|
+
if File.directory? this_entry
|
65
|
+
_ = Dir["#{this_entry}/*"].first # Fetch the first entry from this directory, in this case. But retain it only when it is an archive.
|
66
|
+
if is_archive? _
|
67
|
+
this_entry = _
|
68
|
+
end # else we discard it.
|
69
|
+
end
|
70
|
+
# ===================================================================== #
|
71
|
+
# Next, we need to check whether the input is an archive or whether
|
72
|
+
# it is not. If it is a directory, then we will simply enter
|
73
|
+
# this directory instead, without doing any further extraction.
|
74
|
+
# ===================================================================== #
|
75
|
+
unless this_entry.start_with?('--')
|
76
|
+
if File.directory? this_entry # No need to do anything special in this case.
|
77
|
+
# ================================================================= #
|
78
|
+
# In this case we will enter the given directory at hand.
|
79
|
+
# ================================================================= #
|
80
|
+
cd this_entry
|
81
|
+
consider_creating_a_build_directory
|
82
|
+
run_configure_make_and_make_install
|
83
|
+
else
|
84
|
+
if File.exist?(this_entry)
|
85
|
+
# ================================================================= #
|
86
|
+
# Past this point we know that the entry must exist.
|
87
|
+
# ================================================================= #
|
88
|
+
set_original_input(this_entry)
|
89
|
+
set_find_this_program(
|
90
|
+
ProgramInformation.return_name(this_entry)
|
91
|
+
)
|
92
|
+
# =================================================================== #
|
93
|
+
# The next method will also extract the archive, if the
|
94
|
+
# archive exists and there is no other instruction given
|
95
|
+
# by the user to NOT extract it.
|
96
|
+
# =================================================================== #
|
97
|
+
try_to_compile_or_install_this_program(this_entry)
|
98
|
+
consider_symlinking_appdir if is_an_appdir?
|
99
|
+
return_to_start_directory_again
|
100
|
+
consider_removing_this_archive(this_entry)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
end; end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# Encoding: UTF-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
# =========================================================================== #
|
5
|
+
# require 'easycompile/base/remove.rb'
|
6
|
+
# =========================================================================== #
|
7
|
+
module Easycompile
|
8
|
+
|
9
|
+
class Base
|
10
|
+
|
11
|
+
# ========================================================================= #
|
12
|
+
# === remove_directory
|
13
|
+
#
|
14
|
+
# This method will remove a directory (if the input argument is a
|
15
|
+
# directory that is).
|
16
|
+
# ========================================================================= #
|
17
|
+
def remove_directory(i = nil)
|
18
|
+
if i and File.directory?(i)
|
19
|
+
i = i.dup if i.frozen?
|
20
|
+
i.squeeze!('/')
|
21
|
+
FileUtils.rm_rf(i) unless i == '/' # Don't delete "/" ever.
|
22
|
+
end
|
23
|
+
end; alias remove_this_directory remove_directory # === remove_this_directory
|
24
|
+
|
25
|
+
# ========================================================================= #
|
26
|
+
# === remove_this_archive
|
27
|
+
#
|
28
|
+
# This method can only be used to remove an archive, that is
|
29
|
+
# some file like "foobar-1.2.0.tar.xz". It may never remove
|
30
|
+
# a directory.
|
31
|
+
# ========================================================================= #
|
32
|
+
def remove_this_archive(i)
|
33
|
+
if is_an_archive?(i)
|
34
|
+
remove_file(i)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# ========================================================================= #
|
39
|
+
# === remove_extension_from
|
40
|
+
# ========================================================================= #
|
41
|
+
def remove_extension_from(i)
|
42
|
+
i = i.dup # Work on a copy.
|
43
|
+
i.sub!(/\.xz$/,'') if i.end_with? '.xz'
|
44
|
+
i.sub!(/\.gz$/,'') if i.end_with? '.gz'
|
45
|
+
i.sub!(/\.bzip2$/,'') if i.end_with? '.bzip2'
|
46
|
+
i.sub!(/\.bzip$/,'') if i.end_with? '.bzip'
|
47
|
+
i.sub!(/\.tar$/,'') if i.end_with? '.tar'
|
48
|
+
i.sub!(/\.zip$/,'') if i.end_with? '.zip'
|
49
|
+
i
|
50
|
+
end; alias remove_file_suffix remove_extension_from # === remove_file_suffix
|
51
|
+
alias remove_archive_at_the_end remove_extension_from # === remove_archive_at_the_end
|
52
|
+
|
53
|
+
# ========================================================================= #
|
54
|
+
# === remove_this_file
|
55
|
+
# ========================================================================= #
|
56
|
+
def remove_this_file(i)
|
57
|
+
i = @start_dir+File.basename(i)
|
58
|
+
opn_namespace; e 'Now deleting `'+sfile(i)+'`.'
|
59
|
+
File.delete(i) if File.exist? i
|
60
|
+
end; alias remove remove_this_file # === remove
|
61
|
+
|
62
|
+
# ========================================================================= #
|
63
|
+
# === do_remove_all_after_compile
|
64
|
+
# ========================================================================= #
|
65
|
+
def do_remove_all_after_compile
|
66
|
+
opn_namespace; e 'We will remove all after compile.'
|
67
|
+
@remove_all_after_compile = true
|
68
|
+
end
|
69
|
+
|
70
|
+
# ========================================================================= #
|
71
|
+
# === remove_all_after_compile?
|
72
|
+
# ========================================================================= #
|
73
|
+
def remove_all_after_compile?
|
74
|
+
@remove_all_after_compile
|
75
|
+
end
|
76
|
+
|
77
|
+
end; end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# Encoding: UTF-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
# =========================================================================== #
|
5
|
+
# require 'easycompile/base/reset.rb'
|
6
|
+
# =========================================================================== #
|
7
|
+
require 'easycompile/base/colours.rb'
|
8
|
+
require 'easycompile/constants/file_and_directory_constants.rb'
|
9
|
+
|
10
|
+
module Easycompile
|
11
|
+
|
12
|
+
class Base
|
13
|
+
|
14
|
+
begin
|
15
|
+
require 'rbt/misc/colourize_parser.rb'
|
16
|
+
rescue LoadError; end
|
17
|
+
# ========================================================================= #
|
18
|
+
# === reset (reset tag)
|
19
|
+
#
|
20
|
+
# The instance variable we will use here:
|
21
|
+
#
|
22
|
+
# @prefix_to_base_directory # This is used to denote what is the base
|
23
|
+
# directory.
|
24
|
+
#
|
25
|
+
# ========================================================================= #
|
26
|
+
def reset
|
27
|
+
set_base_dir
|
28
|
+
set_start_dir
|
29
|
+
# ======================================================================= #
|
30
|
+
# === @commandline_arguments
|
31
|
+
#
|
32
|
+
# This Array will hold all commandline arguments, e. g. ARGV.
|
33
|
+
# ======================================================================= #
|
34
|
+
@commandline_arguments = []
|
35
|
+
# ======================================================================= #
|
36
|
+
# === @compile_these_programs
|
37
|
+
#
|
38
|
+
# These are the programs, as an Array, that we wish to compile.
|
39
|
+
# ======================================================================= #
|
40
|
+
@compile_these_programs = []
|
41
|
+
# ======================================================================= #
|
42
|
+
# === @program_name
|
43
|
+
# ======================================================================= #
|
44
|
+
@program_name = nil
|
45
|
+
# ======================================================================= #
|
46
|
+
# === @compile_with_dependencies
|
47
|
+
# ======================================================================= #
|
48
|
+
@compile_with_dependencies = false # If true then we will first compile all dependencies.
|
49
|
+
# ======================================================================= #
|
50
|
+
# === @program_version
|
51
|
+
# ======================================================================= #
|
52
|
+
@program_version = '1.0.0' # Default.
|
53
|
+
# ======================================================================= #
|
54
|
+
# Designate the default extract-to directory next.
|
55
|
+
# ======================================================================= #
|
56
|
+
set_extract_to(:default)
|
57
|
+
# ======================================================================= #
|
58
|
+
# === @hash
|
59
|
+
# ======================================================================= #
|
60
|
+
@hash = {}
|
61
|
+
# ======================================================================= #
|
62
|
+
# === @skip_extracting
|
63
|
+
# ======================================================================= #
|
64
|
+
@skip_extracting = false # Whether to skip extracting or not.
|
65
|
+
# ======================================================================= #
|
66
|
+
# === @continue_after_extracting
|
67
|
+
# ======================================================================= #
|
68
|
+
@continue_after_extracting = true # if true we continue after extracting.
|
69
|
+
# ======================================================================= #
|
70
|
+
# === @build_directory_is_stored_here
|
71
|
+
# ======================================================================= #
|
72
|
+
@build_directory_is_stored_here = nil
|
73
|
+
# ======================================================================= #
|
74
|
+
# === @continue_past_configure_stage
|
75
|
+
# ======================================================================= #
|
76
|
+
@continue_past_configure_stage = true
|
77
|
+
# ======================================================================= #
|
78
|
+
# === @skip_make_install
|
79
|
+
# ======================================================================= #
|
80
|
+
@skip_make_install = false
|
81
|
+
set_prefix # Default.
|
82
|
+
# ======================================================================= #
|
83
|
+
# === @prefix_to_base_directory
|
84
|
+
# ======================================================================= #
|
85
|
+
@prefix_to_base_directory = ''
|
86
|
+
# ======================================================================= #
|
87
|
+
# === @shall_we_create_a_build_directory
|
88
|
+
#
|
89
|
+
# This variable keeps track as to whether we will create a
|
90
|
+
# build directory or not.
|
91
|
+
# ======================================================================= #
|
92
|
+
@shall_we_create_a_build_directory = SHALL_WE_CREATE_A_BUILD_DIRECTORY
|
93
|
+
# ======================================================================= #
|
94
|
+
# === @prefix
|
95
|
+
#
|
96
|
+
# Which prefix is to be used. Defaults to /usr/.
|
97
|
+
# ======================================================================= #
|
98
|
+
@prefix = '/usr/'
|
99
|
+
# ======================================================================= #
|
100
|
+
# === @original_input
|
101
|
+
# ======================================================================= #
|
102
|
+
@original_input = nil
|
103
|
+
# ======================================================================= #
|
104
|
+
# === @temp_directory
|
105
|
+
# ======================================================================= #
|
106
|
+
@temp_directory = ::Easycompile.temp_directory?
|
107
|
+
# ======================================================================= #
|
108
|
+
# === @remove_all_after_compile
|
109
|
+
# ======================================================================= #
|
110
|
+
@remove_all_after_compile = false
|
111
|
+
# ======================================================================= #
|
112
|
+
# === @try_to_use_extended_configure_options
|
113
|
+
#
|
114
|
+
# If this variable is set to true then we will try to
|
115
|
+
# return the extended configure options, which are
|
116
|
+
# part of the RBT project.
|
117
|
+
# ======================================================================= #
|
118
|
+
@try_to_use_extended_configure_options = false
|
119
|
+
# ======================================================================= #
|
120
|
+
# === @colourize_parser
|
121
|
+
# ======================================================================= #
|
122
|
+
if cparser_is_available?
|
123
|
+
@colourize_parser = RBT::ColourizeParser.new
|
124
|
+
else
|
125
|
+
@colourize_parser = nil
|
126
|
+
end
|
127
|
+
# ======================================================================= #
|
128
|
+
# === @name_of_build_directory
|
129
|
+
#
|
130
|
+
# Choose a default name for the build directory. This can be
|
131
|
+
# overruled if we have a .yml file that tells us to do so.
|
132
|
+
# ======================================================================= #
|
133
|
+
set_name_of_build_directory 'BUILD_DIRECTORY/'
|
134
|
+
if File.exist? FILE_NAME_OF_THE_BUILD_DIRECTORY
|
135
|
+
require 'yaml'
|
136
|
+
set_name_of_build_directory(:load_the_yaml_file)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
end; end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# Encoding: UTF-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
# =========================================================================== #
|
5
|
+
# require 'easycompile/base/run.rb'
|
6
|
+
# =========================================================================== #
|
7
|
+
module Easycompile
|
8
|
+
|
9
|
+
class Base
|
10
|
+
|
11
|
+
# ========================================================================= #
|
12
|
+
# === run (run tag)
|
13
|
+
# ========================================================================= #
|
14
|
+
def run
|
15
|
+
@start_dir = return_pwd
|
16
|
+
determine_which_programs_to_compile
|
17
|
+
# ======================================================================= #
|
18
|
+
# Next check against the menu (commandline-interface).
|
19
|
+
# ======================================================================= #
|
20
|
+
menu(
|
21
|
+
return_all_arguments_with_hyphens
|
22
|
+
)
|
23
|
+
process_the_input
|
24
|
+
end
|
25
|
+
|
26
|
+
end; end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# Encoding: UTF-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
# =========================================================================== #
|
5
|
+
# === Easycompile::CompileAsAppdir
|
6
|
+
#
|
7
|
+
# This will compile in an App-Dir like approach.
|
8
|
+
# =========================================================================== #
|
9
|
+
# require 'easycompile/compile_as_appdir/compile_as_appdir.rb'
|
10
|
+
# =========================================================================== #
|
11
|
+
require 'easycompile/easycompile/easycompile.rb'
|
12
|
+
|
13
|
+
module Easycompile # === Easycompile
|
14
|
+
|
15
|
+
class CompileAsAppdir < Base # === Easycompile::CompileAsAppdir
|
16
|
+
|
17
|
+
begin
|
18
|
+
require 'rbt/requires/require_symlink_this_program.rb'
|
19
|
+
rescue LoadError; end
|
20
|
+
|
21
|
+
# ========================================================================= #
|
22
|
+
# === THIS_FILE
|
23
|
+
# ========================================================================= #
|
24
|
+
THIS_FILE =
|
25
|
+
"#{PROJECT_BASE_DIR}compile_as_appdir/compile_as_appdir.rb"
|
26
|
+
|
27
|
+
# ========================================================================= #
|
28
|
+
# === initialize
|
29
|
+
# ========================================================================= #
|
30
|
+
def initialize(i)
|
31
|
+
Easycompile.new(i) {{ prefix: :appdir }}
|
32
|
+
end
|
33
|
+
|
34
|
+
# ========================================================================= #
|
35
|
+
# === Easycompile::CompileAsAppdir[]
|
36
|
+
# ========================================================================= #
|
37
|
+
def self.[](i)
|
38
|
+
CompileAsAppdir.new(i)
|
39
|
+
end
|
40
|
+
|
41
|
+
end; end
|
42
|
+
|
43
|
+
if __FILE__ == $PROGRAM_NAME
|
44
|
+
Easycompile::CompileAsAppdir.new(ARGV)
|
45
|
+
end # rappdir
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# Encoding: UTF-8
|
3
|
+
# frozen_string_literal: true
|
4
|
+
# =========================================================================== #
|
5
|
+
# require 'easycompile/constants/array_possible_archives.rb'
|
6
|
+
# =========================================================================== #
|
7
|
+
module Easycompile # include Easycompile::Constants
|
8
|
+
|
9
|
+
module Constants # === Easycompile::Constants
|
10
|
+
|
11
|
+
# ========================================================================= #
|
12
|
+
# === ARRAY_POSSIBLE_ARCHIVES
|
13
|
+
#
|
14
|
+
# This Array will list possible archive "types", such as ".tar.xz" and
|
15
|
+
# so forth.
|
16
|
+
# ========================================================================= #
|
17
|
+
ARRAY_POSSIBLE_ARCHIVES = %w(
|
18
|
+
xz
|
19
|
+
tar
|
20
|
+
gz
|
21
|
+
zip
|
22
|
+
bz2
|
23
|
+
lz
|
24
|
+
)
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
# =========================================================================== #
|
29
|
+
# === Easycompile.is_an_archive?
|
30
|
+
# =========================================================================== #
|
31
|
+
def self.is_an_archive?(i)
|
32
|
+
extname = File.extname(File.basename(i)).delete('.')
|
33
|
+
Constants::ARRAY_POSSIBLE_ARCHIVES.include? extname
|
34
|
+
end; self.instance_eval { alias is_archive? is_an_archive? } # === Easycompile.is_archive?
|
35
|
+
|
36
|
+
# =========================================================================== #
|
37
|
+
# === Easycompile.try_to_randomly_fetch_an_archive_from_the_current_directory
|
38
|
+
# =========================================================================== #
|
39
|
+
def self.try_to_randomly_fetch_an_archive_from_the_current_directory
|
40
|
+
_ = Dir['*']
|
41
|
+
_.select! {|entry| is_archive? entry }
|
42
|
+
_
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|