right_popen 1.0.7-x86-mswin32-60 → 1.0.9-x86-mswin32-60
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.
- data/LICENSE +20 -20
- data/README.rdoc +124 -124
- data/lib/right_popen.rb +66 -65
- data/lib/win32/right_popen.rb +461 -442
- data/lib/win32/right_popen.so +0 -0
- data/right_popen.gemspec +61 -64
- data/spec/increment.rb +2 -0
- data/spec/produce_status.rb +1 -1
- data/spec/right_popen_spec.rb +221 -210
- data/spec/spec_helper.rb +4 -4
- metadata +4 -3
data/lib/win32/right_popen.so
CHANGED
Binary file
|
data/right_popen.gemspec
CHANGED
@@ -1,64 +1,61 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
|
3
|
-
def is_windows?
|
4
|
-
return RUBY_PLATFORM =~ /mswin/
|
5
|
-
end
|
6
|
-
|
7
|
-
spec = Gem::Specification.new do |spec|
|
8
|
-
spec.name = 'right_popen'
|
9
|
-
spec.version = '1.0.
|
10
|
-
spec.authors = ['Scott Messier', 'Raphael Simon']
|
11
|
-
spec.email = 'scott@rightscale.com'
|
12
|
-
spec.homepage = 'https://github.com/rightscale/right_popen'
|
13
|
-
if is_windows?
|
14
|
-
spec.platform = 'x86-mswin32-60'
|
15
|
-
else
|
16
|
-
spec.platform = Gem::Platform::RUBY
|
17
|
-
end
|
18
|
-
spec.summary = 'Provides a platform-independent popen implementation'
|
19
|
-
spec.has_rdoc = true
|
20
|
-
spec.rdoc_options = ["--main", "README.rdoc", "--title", "RightPopen"]
|
21
|
-
spec.extra_rdoc_files = ["README.rdoc"]
|
22
|
-
spec.required_ruby_version = '>= 1.8.6'
|
23
|
-
spec.rubyforge_project = %q{right_popen}
|
24
|
-
|
25
|
-
spec.description = <<-EOF
|
26
|
-
RightPopen allows running external processes aynchronously while still
|
27
|
-
capturing their standard and error outputs. It relies on EventMachine for most
|
28
|
-
of its internal mechanisms. The Linux implementation is valid for any Linux
|
29
|
-
platform but there is also a native implementation for Windows platforms.
|
30
|
-
EOF
|
31
|
-
|
32
|
-
if is_windows?
|
33
|
-
extension_dir = "ext,"
|
34
|
-
else
|
35
|
-
extension_dir = ""
|
36
|
-
end
|
37
|
-
candidates = Dir.glob("{#{extension_dir}lib,spec}/**/*") +
|
38
|
-
["LICENSE", "README.rdoc", "Rakefile", "right_popen.gemspec"]
|
39
|
-
candidates = candidates.delete_if do |item|
|
40
|
-
item.include?("Makefile") || item.include?(".obj") || item.include?(".pdb") || item.include?(".def") || item.include?(".exp") || item.include?(".lib")
|
41
|
-
end
|
42
|
-
candidates = candidates.delete_if do |item|
|
43
|
-
if is_windows?
|
44
|
-
item.include?("/linux/")
|
45
|
-
else
|
46
|
-
item.include?("/win32/")
|
47
|
-
end
|
48
|
-
end
|
49
|
-
spec.files = candidates.sort!
|
50
|
-
|
51
|
-
# Current implementation supports >= 0.12.10
|
52
|
-
spec.add_runtime_dependency(%q<eventmachine>, [">= 0.12.10"])
|
53
|
-
if is_windows?
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
Gem.manage_gems if Gem::RubyGemsVersion.to_f < 1.0
|
63
|
-
Gem::Builder.new(spec).build
|
64
|
-
end
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
def is_windows?
|
4
|
+
return RUBY_PLATFORM =~ /mswin/
|
5
|
+
end
|
6
|
+
|
7
|
+
spec = Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'right_popen'
|
9
|
+
spec.version = '1.0.9'
|
10
|
+
spec.authors = ['Scott Messier', 'Raphael Simon']
|
11
|
+
spec.email = 'scott@rightscale.com'
|
12
|
+
spec.homepage = 'https://github.com/rightscale/right_popen'
|
13
|
+
if is_windows?
|
14
|
+
spec.platform = 'x86-mswin32-60'
|
15
|
+
else
|
16
|
+
spec.platform = Gem::Platform::RUBY
|
17
|
+
end
|
18
|
+
spec.summary = 'Provides a platform-independent popen implementation'
|
19
|
+
spec.has_rdoc = true
|
20
|
+
spec.rdoc_options = ["--main", "README.rdoc", "--title", "RightPopen"]
|
21
|
+
spec.extra_rdoc_files = ["README.rdoc"]
|
22
|
+
spec.required_ruby_version = '>= 1.8.6'
|
23
|
+
spec.rubyforge_project = %q{right_popen}
|
24
|
+
|
25
|
+
spec.description = <<-EOF
|
26
|
+
RightPopen allows running external processes aynchronously while still
|
27
|
+
capturing their standard and error outputs. It relies on EventMachine for most
|
28
|
+
of its internal mechanisms. The Linux implementation is valid for any Linux
|
29
|
+
platform but there is also a native implementation for Windows platforms.
|
30
|
+
EOF
|
31
|
+
|
32
|
+
if is_windows?
|
33
|
+
extension_dir = "ext,"
|
34
|
+
else
|
35
|
+
extension_dir = ""
|
36
|
+
end
|
37
|
+
candidates = Dir.glob("{#{extension_dir}lib,spec}/**/*") +
|
38
|
+
["LICENSE", "README.rdoc", "Rakefile", "right_popen.gemspec"]
|
39
|
+
candidates = candidates.delete_if do |item|
|
40
|
+
item.include?("Makefile") || item.include?(".obj") || item.include?(".pdb") || item.include?(".def") || item.include?(".exp") || item.include?(".lib")
|
41
|
+
end
|
42
|
+
candidates = candidates.delete_if do |item|
|
43
|
+
if is_windows?
|
44
|
+
item.include?("/linux/")
|
45
|
+
else
|
46
|
+
item.include?("/win32/")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
spec.files = candidates.sort!
|
50
|
+
|
51
|
+
# Current implementation supports >= 0.12.10
|
52
|
+
spec.add_runtime_dependency(%q<eventmachine>, [">= 0.12.10"])
|
53
|
+
if is_windows?
|
54
|
+
spec.add_runtime_dependency(%q<win32-process>, [">= 0.6.1"])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
if $PROGRAM_NAME == __FILE__
|
59
|
+
Gem.manage_gems if Gem::RubyGemsVersion.to_f < 1.0
|
60
|
+
Gem::Builder.new(spec).build
|
61
|
+
end
|
data/spec/increment.rb
ADDED
data/spec/produce_status.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
exit ARGV[0].to_i
|
1
|
+
exit ARGV[0].to_i
|
data/spec/right_popen_spec.rb
CHANGED
@@ -1,210 +1,221 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
-
|
3
|
-
RUBY_CMD = 'ruby'
|
4
|
-
STANDARD_MESSAGE = 'Standard message'
|
5
|
-
ERROR_MESSAGE = 'Error message'
|
6
|
-
EXIT_STATUS = 146
|
7
|
-
|
8
|
-
# manually bump count up for more aggressive multi-processor testing, lessen
|
9
|
-
# for a quick smoke test
|
10
|
-
LARGE_OUTPUT_COUNTER = 1000
|
11
|
-
|
12
|
-
# bump up count for most exhaustive leak detection.
|
13
|
-
REPEAT_TEST_COUNTER = 256
|
14
|
-
|
15
|
-
def is_windows?
|
16
|
-
return RUBY_PLATFORM =~ /mswin/
|
17
|
-
end
|
18
|
-
|
19
|
-
describe 'RightScale::popen3' do
|
20
|
-
|
21
|
-
module RightPopenSpec
|
22
|
-
|
23
|
-
class Runner
|
24
|
-
def initialize
|
25
|
-
@done = false
|
26
|
-
@output_text = nil
|
27
|
-
@error_text = nil
|
28
|
-
@status = nil
|
29
|
-
@last_exception = nil
|
30
|
-
@last_iteration = 0
|
31
|
-
end
|
32
|
-
|
33
|
-
attr_reader :output_text, :error_text, :status
|
34
|
-
|
35
|
-
def do_right_popen(command, env=nil)
|
36
|
-
@timeout = EM::Timer.new(
|
37
|
-
@output_text = ''
|
38
|
-
@error_text = ''
|
39
|
-
@status = nil
|
40
|
-
RightScale.popen3(:command => command,
|
41
|
-
:
|
42
|
-
:
|
43
|
-
:
|
44
|
-
:
|
45
|
-
:
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
@
|
52
|
-
@
|
53
|
-
@
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
@
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
runner.
|
93
|
-
runner.
|
94
|
-
runner.
|
95
|
-
runner.
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
runner.
|
102
|
-
runner.
|
103
|
-
runner.
|
104
|
-
runner.
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
runner.
|
112
|
-
runner.
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
runner.
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
runner.
|
127
|
-
runner.
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
runner.
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
runner.
|
142
|
-
runner.
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
runner.
|
161
|
-
runner.
|
162
|
-
runner.
|
163
|
-
runner.
|
164
|
-
runner.
|
165
|
-
runner.
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
runner
|
175
|
-
runner.
|
176
|
-
runner.
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
#
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
runner
|
188
|
-
runner.
|
189
|
-
runner.
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
runner.
|
196
|
-
runner.
|
197
|
-
runner.
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
runner
|
206
|
-
runner.
|
207
|
-
runner.
|
208
|
-
|
209
|
-
|
210
|
-
end
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
RUBY_CMD = 'ruby'
|
4
|
+
STANDARD_MESSAGE = 'Standard message'
|
5
|
+
ERROR_MESSAGE = 'Error message'
|
6
|
+
EXIT_STATUS = 146
|
7
|
+
|
8
|
+
# manually bump count up for more aggressive multi-processor testing, lessen
|
9
|
+
# for a quick smoke test
|
10
|
+
LARGE_OUTPUT_COUNTER = 1000
|
11
|
+
|
12
|
+
# bump up count for most exhaustive leak detection.
|
13
|
+
REPEAT_TEST_COUNTER = 256
|
14
|
+
|
15
|
+
def is_windows?
|
16
|
+
return RUBY_PLATFORM =~ /mswin/
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'RightScale::popen3' do
|
20
|
+
|
21
|
+
module RightPopenSpec
|
22
|
+
|
23
|
+
class Runner
|
24
|
+
def initialize
|
25
|
+
@done = false
|
26
|
+
@output_text = nil
|
27
|
+
@error_text = nil
|
28
|
+
@status = nil
|
29
|
+
@last_exception = nil
|
30
|
+
@last_iteration = 0
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :output_text, :error_text, :status
|
34
|
+
|
35
|
+
def do_right_popen(command, env=nil, input=nil)
|
36
|
+
@timeout = EM::Timer.new(2) { puts "\n** Failed to run #{command.inspect}: Timeout"; EM.stop }
|
37
|
+
@output_text = ''
|
38
|
+
@error_text = ''
|
39
|
+
@status = nil
|
40
|
+
RightScale.popen3(:command => command,
|
41
|
+
:input => input,
|
42
|
+
:target => self,
|
43
|
+
:environment => env,
|
44
|
+
:stdout_handler => :on_read_stdout,
|
45
|
+
:stderr_handler => :on_read_stderr,
|
46
|
+
:exit_handler => :on_exit)
|
47
|
+
end
|
48
|
+
|
49
|
+
def run_right_popen(command, env=nil, input=nil, count=1)
|
50
|
+
begin
|
51
|
+
@command = command
|
52
|
+
@env = env
|
53
|
+
@last_iteration = 0
|
54
|
+
@count = count
|
55
|
+
puts "#{count}>" if count > 1
|
56
|
+
EM.run { EM.next_tick { do_right_popen(command, env, input) } }
|
57
|
+
rescue Exception => e
|
58
|
+
puts "\n** Failed: #{e.message} FROM\n#{e.backtrace.join("\n")}"
|
59
|
+
raise e
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def on_read_stdout(data)
|
64
|
+
@output_text << data
|
65
|
+
end
|
66
|
+
|
67
|
+
def on_read_stderr(data)
|
68
|
+
@error_text << data
|
69
|
+
end
|
70
|
+
|
71
|
+
def on_exit(status)
|
72
|
+
@last_iteration += 1
|
73
|
+
@timeout.cancel if @timeout
|
74
|
+
if @last_iteration < @count
|
75
|
+
EM.next_tick do
|
76
|
+
print '+'
|
77
|
+
STDOUT.flush
|
78
|
+
do_right_popen(@command, @env)
|
79
|
+
end
|
80
|
+
else
|
81
|
+
puts "<" if @count > 1
|
82
|
+
EM.stop
|
83
|
+
end
|
84
|
+
@status = status
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should redirect output' do
|
91
|
+
command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'produce_output.rb'))}\" \"#{STANDARD_MESSAGE}\" \"#{ERROR_MESSAGE}\""
|
92
|
+
runner = RightPopenSpec::Runner.new
|
93
|
+
runner.run_right_popen(command)
|
94
|
+
runner.status.exitstatus.should == 0
|
95
|
+
runner.output_text.should == STANDARD_MESSAGE + "\n"
|
96
|
+
runner.error_text.should == ERROR_MESSAGE + "\n"
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should return the right status' do
|
100
|
+
command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'produce_status.rb'))}\" #{EXIT_STATUS}"
|
101
|
+
runner = RightPopenSpec::Runner.new
|
102
|
+
runner.run_right_popen(command)
|
103
|
+
runner.status.exitstatus.should == EXIT_STATUS
|
104
|
+
runner.output_text.should == ''
|
105
|
+
runner.error_text.should == ''
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'should preserve the integrity of stdout when stderr is unavailable' do
|
109
|
+
count = LARGE_OUTPUT_COUNTER
|
110
|
+
command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'produce_stdout_only.rb'))}\" #{count}"
|
111
|
+
runner = RightPopenSpec::Runner.new
|
112
|
+
runner.run_right_popen(command)
|
113
|
+
runner.status.exitstatus.should == 0
|
114
|
+
|
115
|
+
results = ''
|
116
|
+
count.times do |i|
|
117
|
+
results << "stdout #{i}\n"
|
118
|
+
end
|
119
|
+
runner.output_text.should == results
|
120
|
+
runner.error_text.should == ''
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should preserve the integrity of stderr when stdout is unavailable' do
|
124
|
+
count = LARGE_OUTPUT_COUNTER
|
125
|
+
command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'produce_stderr_only.rb'))}\" #{count}"
|
126
|
+
runner = RightPopenSpec::Runner.new
|
127
|
+
runner.run_right_popen(command)
|
128
|
+
runner.status.exitstatus.should == 0
|
129
|
+
|
130
|
+
results = ''
|
131
|
+
count.times do |i|
|
132
|
+
results << "stderr #{i}\n"
|
133
|
+
end
|
134
|
+
runner.error_text.should == results
|
135
|
+
runner.output_text.should == ''
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'should preserve the integrity of stdout and stderr despite interleaving' do
|
139
|
+
count = LARGE_OUTPUT_COUNTER
|
140
|
+
command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'produce_mixed_output.rb'))}\" #{count}"
|
141
|
+
runner = RightPopenSpec::Runner.new
|
142
|
+
runner.run_right_popen(command)
|
143
|
+
runner.status.exitstatus.should == 99
|
144
|
+
|
145
|
+
results = ''
|
146
|
+
count.times do |i|
|
147
|
+
results << "stdout #{i}\n"
|
148
|
+
end
|
149
|
+
runner.output_text.should == results
|
150
|
+
|
151
|
+
results = ''
|
152
|
+
count.times do |i|
|
153
|
+
(results << "stderr #{i}\n") if 0 == i % 10
|
154
|
+
end
|
155
|
+
runner.error_text.should == results
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'should setup environment variables' do
|
159
|
+
command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'print_env.rb'))}\""
|
160
|
+
runner = RightPopenSpec::Runner.new
|
161
|
+
runner.run_right_popen(command)
|
162
|
+
runner.status.exitstatus.should == 0
|
163
|
+
runner.output_text.should_not include('_test_')
|
164
|
+
runner.run_right_popen(command, :__test__ => '42')
|
165
|
+
runner.status.exitstatus.should == 0
|
166
|
+
runner.output_text.should match(/^__test__=42$/)
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'should restore environment variables' do
|
170
|
+
ENV['__test__'] = '41'
|
171
|
+
old_envs = {}
|
172
|
+
ENV.each { |k, v| old_envs[k] = v }
|
173
|
+
command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'print_env.rb'))}\""
|
174
|
+
runner = RightPopenSpec::Runner.new
|
175
|
+
runner.run_right_popen(command, :__test__ => '42')
|
176
|
+
runner.status.exitstatus.should == 0
|
177
|
+
runner.output_text.should match(/^__test__=42$/)
|
178
|
+
ENV.each { |k, v| old_envs[k].should == v }
|
179
|
+
old_envs.each { |k, v| ENV[k].should == v }
|
180
|
+
end
|
181
|
+
|
182
|
+
if is_windows?
|
183
|
+
# FIX: this behavior is currently specific to Windows but should probably be
|
184
|
+
# implemented for Linux.
|
185
|
+
it 'should merge the PATH variable instead of overriding it' do
|
186
|
+
command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'print_env.rb'))}\""
|
187
|
+
runner = RightPopenSpec::Runner.new
|
188
|
+
runner.run_right_popen(command, 'PATH' => "c:/bogus\\bin")
|
189
|
+
runner.status.exitstatus.should == 0
|
190
|
+
runner.output_text.should include('PATH=c:\\bogus\\bin;')
|
191
|
+
end
|
192
|
+
else
|
193
|
+
it 'should allow running bash command lines starting with a built-in command' do
|
194
|
+
command = "for i in 1 2 3 4 5; do echo $i;done"
|
195
|
+
runner = RightPopenSpec::Runner.new
|
196
|
+
runner.run_right_popen(command)
|
197
|
+
runner.status.exitstatus.should == 0
|
198
|
+
runner.output_text.should == "1\n2\n3\n4\n5\n"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'should run repeatedly without leaking resources' do
|
203
|
+
pending 'Set environment variable TEST_LEAK to enable' unless ENV['TEST_LEAK']
|
204
|
+
command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'produce_output.rb'))}\" \"#{STANDARD_MESSAGE}\" \"#{ERROR_MESSAGE}\""
|
205
|
+
runner = RightPopenSpec::Runner.new
|
206
|
+
runner.run_right_popen(command, nil, nil, REPEAT_TEST_COUNTER)
|
207
|
+
runner.status.exitstatus.should == 0
|
208
|
+
runner.output_text.should == STANDARD_MESSAGE + "\n"
|
209
|
+
runner.error_text.should == ERROR_MESSAGE + "\n"
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'should pass input to child process' do
|
213
|
+
command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'increment.rb'))}\""
|
214
|
+
runner = RightPopenSpec::Runner.new
|
215
|
+
runner.run_right_popen(command, nil, "42\n")
|
216
|
+
runner.status.exitstatus.should == 0
|
217
|
+
runner.output_text.should == "43\n"
|
218
|
+
runner.error_text.should be_empty
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|