foreman 0.46.0 → 0.47.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +6 -0
- data/bin/foreman-runner +3 -7
- data/data/export/bluepill/master.pill.erb +2 -1
- data/data/export/launchd/launchd.plist.erb +22 -0
- data/data/export/upstart/process.conf.erb +1 -1
- data/lib/foreman/cli.rb +3 -2
- data/lib/foreman/engine.rb +2 -1
- data/lib/foreman/export.rb +1 -0
- data/lib/foreman/export/base.rb +15 -0
- data/lib/foreman/export/launchd.rb +27 -0
- data/lib/foreman/process.rb +1 -1
- data/lib/foreman/version.rb +1 -1
- data/man/foreman.1 +1 -1
- data/spec/foreman/cli_spec.rb +3 -3
- data/spec/foreman/engine_spec.rb +7 -0
- data/spec/foreman/export/launchd_spec.rb +24 -0
- data/spec/foreman/export/upstart_spec.rb +6 -0
- data/spec/foreman/process_spec.rb +16 -2
- data/spec/resources/export/bluepill/app-concurrency.pill +2 -0
- data/spec/resources/export/bluepill/app.pill +2 -0
- data/spec/resources/export/launchd/launchd-a.default +22 -0
- data/spec/resources/export/launchd/launchd-b.default +22 -0
- metadata +9 -10
data/README.md
CHANGED
@@ -27,6 +27,12 @@ Manage Procfile-based applications
|
|
27
27
|
* [wiki](http://github.com/ddollar/foreman/wiki)
|
28
28
|
* [changelog](https://github.com/ddollar/foreman/blob/master/Changelog.md)
|
29
29
|
|
30
|
+
## Ports
|
31
|
+
|
32
|
+
* [shoreman](https://github.com/hecticjeff/shoreman) - shell
|
33
|
+
* [honcho](https://github.com/nickstenning/honcho) - python
|
34
|
+
* [norman](https://github.com/josh/norman) - node.js
|
35
|
+
|
30
36
|
## Authors
|
31
37
|
|
32
38
|
#### Created and maintained by
|
data/bin/foreman-runner
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/bin/sh
|
2
2
|
#
|
3
|
-
#/ Usage: foreman-runner [-d <dir>] <command>
|
3
|
+
#/ Usage: foreman-runner [-d <dir>] <command> [<args>...]
|
4
4
|
#/
|
5
5
|
#/ Run a command with exec, optionally changing directory first
|
6
6
|
|
@@ -27,10 +27,6 @@ done
|
|
27
27
|
|
28
28
|
shift $((OPTIND-1))
|
29
29
|
|
30
|
-
|
30
|
+
[ -z "$1" ] && usage
|
31
31
|
|
32
|
-
|
33
|
-
usage
|
34
|
-
fi
|
35
|
-
|
36
|
-
exec $1
|
32
|
+
exec "$@"
|
@@ -11,8 +11,9 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
|
|
11
11
|
|
12
12
|
process.working_dir = "<%= engine.directory %>"
|
13
13
|
process.daemonize = true
|
14
|
-
process.environment = {"PORT" => "<%= port %>"}
|
14
|
+
process.environment = {"PORT" => "<%= port %>"<% engine.environment.each_pair do |var,env| %> , "<%= var.upcase %>" => "<%= env %>" <% end %>}
|
15
15
|
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
16
|
+
process.stop_grace_time = 45.seconds
|
16
17
|
|
17
18
|
process.stdout = process.stderr = "<%= log_root %>/<%= app %>-<%= process.name %>-<%=num%>.log"
|
18
19
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3
|
+
<plist version="1.0">
|
4
|
+
<dict>
|
5
|
+
<key>Label</key>
|
6
|
+
<string><%= "#{app}-#{process.name}-#{num}" %></string>
|
7
|
+
<key>ProgramArguments</key>
|
8
|
+
<array>
|
9
|
+
<string><%= process.command %></string>
|
10
|
+
</array>
|
11
|
+
<key>KeepAlive</key>
|
12
|
+
<true/>
|
13
|
+
<key>RunAtLoad</key>
|
14
|
+
<true/>
|
15
|
+
<key>StandardErrorPath</key>
|
16
|
+
<string><%= log_root %>/<%= app %>-<%= process.name %>-<%=num%>.log</string>
|
17
|
+
<key>UserName</key>
|
18
|
+
<string><%= user %></string>
|
19
|
+
<key>WorkingDirectory</key>
|
20
|
+
<string><%= engine.directory %></string>
|
21
|
+
</dict>
|
22
|
+
</plist>
|
@@ -2,4 +2,4 @@ start on starting <%= app %>-<%= process.name %>
|
|
2
2
|
stop on stopping <%= app %>-<%= process.name %>
|
3
3
|
respawn
|
4
4
|
|
5
|
-
exec su - <%= user %> -c 'cd <%= engine.directory %>; export PORT=<%= port %>;<% engine.environment.each_pair do |var,env| %> export <%= var.upcase %>=<%= env %>; <% end %> <%= process.command %> >> <%= log_root %>/<%=process.name%>-<%=num%>.log 2>&1'
|
5
|
+
exec su - <%= user %> -c 'cd <%= engine.directory %>; export PORT=<%= port %>;<% engine.environment.each_pair do |var,env| %> export <%= var.upcase %>=<%= shell_quote(env) %>; <% end %> <%= process.command %> >> <%= log_root %>/<%=process.name%>-<%=num%>.log 2>&1'
|
data/lib/foreman/cli.rb
CHANGED
@@ -2,6 +2,7 @@ require "foreman"
|
|
2
2
|
require "foreman/helpers"
|
3
3
|
require "foreman/engine"
|
4
4
|
require "foreman/export"
|
5
|
+
require "shellwords"
|
5
6
|
require "thor"
|
6
7
|
require "yaml"
|
7
8
|
|
@@ -59,12 +60,12 @@ class Foreman::CLI < Thor
|
|
59
60
|
puts "valid procfile detected (#{engine.procfile.process_names.join(', ')})"
|
60
61
|
end
|
61
62
|
|
62
|
-
desc "run COMMAND", "Run a command using your application's environment"
|
63
|
+
desc "run COMMAND [ARGS...]", "Run a command using your application's environment"
|
63
64
|
|
64
65
|
def run(*args)
|
65
66
|
engine.apply_environment!
|
66
67
|
begin
|
67
|
-
exec args.
|
68
|
+
exec args.shelljoin
|
68
69
|
rescue Errno::EACCES
|
69
70
|
error "not executable: #{args.first}"
|
70
71
|
rescue Errno::ENOENT
|
data/lib/foreman/engine.rb
CHANGED
@@ -36,6 +36,7 @@ class Foreman::Engine
|
|
36
36
|
|
37
37
|
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
|
38
38
|
trap("INT") { puts "SIGINT received"; terminate_gracefully }
|
39
|
+
trap("HUP") { puts "SIGHUP received"; terminate_gracefully }
|
39
40
|
|
40
41
|
assign_colors
|
41
42
|
spawn_processes
|
@@ -84,7 +85,7 @@ private ######################################################################
|
|
84
85
|
end
|
85
86
|
|
86
87
|
def base_port
|
87
|
-
options[:port] || 5000
|
88
|
+
options[:port] || environment["PORT"] || ENV["PORT"] || 5000
|
88
89
|
end
|
89
90
|
|
90
91
|
def kill_all(signal="SIGTERM")
|
data/lib/foreman/export.rb
CHANGED
data/lib/foreman/export/base.rb
CHANGED
@@ -48,4 +48,19 @@ private ######################################################################
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
# Quote a string to be used on the command line. Backslashes are escapde to \\ and quotes
|
52
|
+
# escaped to \"
|
53
|
+
#
|
54
|
+
# str - string to be quoted
|
55
|
+
#
|
56
|
+
# Examples
|
57
|
+
#
|
58
|
+
# shell_quote("FB|123\"\\1")
|
59
|
+
# # => "\"FB|123\"\\"\\\\1\""
|
60
|
+
#
|
61
|
+
# Returns the the escaped string surrounded by quotes
|
62
|
+
def shell_quote(str)
|
63
|
+
"\"#{str.gsub(/\\/){ '\\\\' }.gsub(/["]/){ "\\\"" }}\""
|
64
|
+
end
|
65
|
+
|
51
66
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "erb"
|
2
|
+
require "foreman/export"
|
3
|
+
|
4
|
+
class Foreman::Export::Launchd < Foreman::Export::Base
|
5
|
+
|
6
|
+
def export
|
7
|
+
error("Must specify a location") unless location
|
8
|
+
|
9
|
+
app = self.app || File.basename(engine.directory)
|
10
|
+
user = self.user || app
|
11
|
+
log_root = self.log || "/var/log/#{app}"
|
12
|
+
template_root = self.template
|
13
|
+
|
14
|
+
FileUtils.mkdir_p(location)
|
15
|
+
|
16
|
+
engine.procfile.entries.each do |process|
|
17
|
+
1.upto(self.concurrency[process.name]) do |num|
|
18
|
+
|
19
|
+
master_template = export_template("launchd", "launchd.plist.erb", template_root)
|
20
|
+
master_config = ERB.new(master_template).result(binding)
|
21
|
+
write_file "#{location}/#{app}-#{process.name}-#{num}.plist", master_config
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/lib/foreman/process.rb
CHANGED
data/lib/foreman/version.rb
CHANGED
data/man/foreman.1
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
.\" generated with Ronn/v0.7.3
|
2
2
|
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
3
3
|
.
|
4
|
-
.TH "FOREMAN" "1" "April 2012" "Foreman 0.
|
4
|
+
.TH "FOREMAN" "1" "April 2012" "Foreman 0.46.0" "Foreman Manual"
|
5
5
|
.
|
6
6
|
.SH "NAME"
|
7
7
|
\fBforeman\fR \- manage Procfile\-based applications
|
data/spec/foreman/cli_spec.rb
CHANGED
@@ -144,7 +144,7 @@ describe "Foreman::CLI", :fakefs do
|
|
144
144
|
before { write_procfile }
|
145
145
|
|
146
146
|
describe "and a command" do
|
147
|
-
let(:command) { ["ls", "-l"] }
|
147
|
+
let(:command) { ["ls", "-l", "foo bar"] }
|
148
148
|
|
149
149
|
before(:each) do
|
150
150
|
stub(subject).exec
|
@@ -160,8 +160,8 @@ describe "Foreman::CLI", :fakefs do
|
|
160
160
|
ENV["FOO"].should be_nil
|
161
161
|
end
|
162
162
|
|
163
|
-
it "should
|
164
|
-
mock(subject).exec(command.
|
163
|
+
it "should exec the argument list as a shell command" do
|
164
|
+
mock(subject).exec(command.shelljoin)
|
165
165
|
subject.run *command
|
166
166
|
end
|
167
167
|
end
|
data/spec/foreman/engine_spec.rb
CHANGED
@@ -100,6 +100,13 @@ describe "Foreman::Engine", :fakefs do
|
|
100
100
|
engine.start
|
101
101
|
end
|
102
102
|
|
103
|
+
it "should set port from .env if specified" do
|
104
|
+
File.open(".env", "w") { |f| f.puts("PORT=8017") }
|
105
|
+
engine = Foreman::Engine.new("Procfile")
|
106
|
+
engine.send(:base_port).should == "8017"
|
107
|
+
engine.start
|
108
|
+
end
|
109
|
+
|
103
110
|
it "should be loaded relative to the Procfile" do
|
104
111
|
FileUtils.mkdir_p "/some/app"
|
105
112
|
File.open("/some/app/.env", "w") { |f| f.puts("FOO=qoo") }
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "foreman/engine"
|
3
|
+
require "foreman/export/launchd"
|
4
|
+
require "tmpdir"
|
5
|
+
|
6
|
+
describe Foreman::Export::Launchd, :fakefs do
|
7
|
+
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
|
8
|
+
let(:engine) { Foreman::Engine.new(procfile) }
|
9
|
+
let(:options) { Hash.new }
|
10
|
+
let(:launchd) { Foreman::Export::Launchd.new("/tmp/init", engine, options) }
|
11
|
+
|
12
|
+
before(:each) { load_export_templates_into_fakefs("launchd") }
|
13
|
+
before(:each) { stub(launchd).say }
|
14
|
+
|
15
|
+
it "exports to the filesystem" do
|
16
|
+
launchd.export
|
17
|
+
|
18
|
+
normalize_space(File.read("/tmp/init/app-alpha-1.plist")).should == normalize_space(example_export_file("launchd/launchd-a.default"))
|
19
|
+
|
20
|
+
normalize_space(File.read("/tmp/init/app-bravo-1.plist")).should == normalize_space(example_export_file("launchd/launchd-b.default"))
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -33,6 +33,12 @@ describe Foreman::Export::Upstart, :fakefs do
|
|
33
33
|
upstart.export
|
34
34
|
end
|
35
35
|
|
36
|
+
it "quotes and escapes environment variables" do
|
37
|
+
engine.environment['KEY'] = 'd"\|d'
|
38
|
+
upstart.export
|
39
|
+
File.read("/tmp/init/app-alpha-1.conf").should include('KEY="d\"\\\\|d"')
|
40
|
+
end
|
41
|
+
|
36
42
|
context "with concurrency" do
|
37
43
|
let(:options) { Hash[:concurrency => "alpha=2"] }
|
38
44
|
|
@@ -121,11 +121,25 @@ describe Foreman::Process do
|
|
121
121
|
output.should include('777')
|
122
122
|
end
|
123
123
|
|
124
|
-
it 'should handle arguments' do
|
125
|
-
pending
|
124
|
+
it 'should handle multi-word arguments (old test)' do
|
125
|
+
# TODO: This test used to be marked pending; it now passes,
|
126
|
+
# but is very slow. The next test is a fast replacement.
|
126
127
|
run %{ sh -c "trap '' TERM; sleep 10" }
|
127
128
|
subject.should be_alive
|
128
129
|
end
|
130
|
+
|
131
|
+
it 'should handle multi-word arguments' do
|
132
|
+
# We have to be a little clever here since Foreman will always
|
133
|
+
# print a status message that includes the command.
|
134
|
+
run %{ sh -c 'echo abcdef | tr a-c x | tr d-f y' }
|
135
|
+
output.should include('xxxyyy')
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'should not clobber "$x"-subexpressions' do
|
139
|
+
pending 'this conflicts with the variable interpolation hack'
|
140
|
+
run %{ sh -c 'echo \$abcdef | tr \$ %' }
|
141
|
+
output.should include('%abcdef')
|
142
|
+
end
|
129
143
|
end
|
130
144
|
end
|
131
145
|
end
|
@@ -13,6 +13,7 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
|
|
13
13
|
process.daemonize = true
|
14
14
|
process.environment = {"PORT" => "5000"}
|
15
15
|
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
16
|
+
process.stop_grace_time = 45.seconds
|
16
17
|
|
17
18
|
process.stdout = process.stderr = "/var/log/app/app-alpha-1.log"
|
18
19
|
|
@@ -31,6 +32,7 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
|
|
31
32
|
process.daemonize = true
|
32
33
|
process.environment = {"PORT" => "5001"}
|
33
34
|
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
35
|
+
process.stop_grace_time = 45.seconds
|
34
36
|
|
35
37
|
process.stdout = process.stderr = "/var/log/app/app-alpha-2.log"
|
36
38
|
|
@@ -13,6 +13,7 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
|
|
13
13
|
process.daemonize = true
|
14
14
|
process.environment = {"PORT" => "5000"}
|
15
15
|
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
16
|
+
process.stop_grace_time = 45.seconds
|
16
17
|
|
17
18
|
process.stdout = process.stderr = "/var/log/app/app-alpha-1.log"
|
18
19
|
|
@@ -30,6 +31,7 @@ Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepil
|
|
30
31
|
process.daemonize = true
|
31
32
|
process.environment = {"PORT" => "5100"}
|
32
33
|
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
34
|
+
process.stop_grace_time = 45.seconds
|
33
35
|
|
34
36
|
process.stdout = process.stderr = "/var/log/app/app-bravo-1.log"
|
35
37
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3
|
+
<plist version="1.0">
|
4
|
+
<dict>
|
5
|
+
<key>Label</key>
|
6
|
+
<string>app-alpha-1</string>
|
7
|
+
<key>ProgramArguments</key>
|
8
|
+
<array>
|
9
|
+
<string>./alpha</string>
|
10
|
+
</array>
|
11
|
+
<key>KeepAlive</key>
|
12
|
+
<true/>
|
13
|
+
<key>RunAtLoad</key>
|
14
|
+
<true/>
|
15
|
+
<key>StandardErrorPath</key>
|
16
|
+
<string>/var/log/app/app-alpha-1.log</string>
|
17
|
+
<key>UserName</key>
|
18
|
+
<string>app</string>
|
19
|
+
<key>WorkingDirectory</key>
|
20
|
+
<string>/tmp/app</string>
|
21
|
+
</dict>
|
22
|
+
</plist>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3
|
+
<plist version="1.0">
|
4
|
+
<dict>
|
5
|
+
<key>Label</key>
|
6
|
+
<string>app-bravo-1</string>
|
7
|
+
<key>ProgramArguments</key>
|
8
|
+
<array>
|
9
|
+
<string>./bravo</string>
|
10
|
+
</array>
|
11
|
+
<key>KeepAlive</key>
|
12
|
+
<true/>
|
13
|
+
<key>RunAtLoad</key>
|
14
|
+
<true/>
|
15
|
+
<key>StandardErrorPath</key>
|
16
|
+
<string>/var/log/app/app-bravo-1.log</string>
|
17
|
+
<key>UserName</key>
|
18
|
+
<string>app</string>
|
19
|
+
<key>WorkingDirectory</key>
|
20
|
+
<string>/tmp/app</string>
|
21
|
+
</dict>
|
22
|
+
</plist>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.47.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-06-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: thor
|
16
|
-
requirement: &
|
16
|
+
requirement: &70170917701380 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: 0.13.6
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70170917701380
|
25
25
|
description: Process manager for applications with multiple components
|
26
26
|
email: ddollar@gmail.com
|
27
27
|
executables:
|
@@ -38,6 +38,7 @@ files:
|
|
38
38
|
- data/example/ticker
|
39
39
|
- data/example/utf8
|
40
40
|
- data/export/bluepill/master.pill.erb
|
41
|
+
- data/export/launchd/launchd.plist.erb
|
41
42
|
- data/export/runit/log_run.erb
|
42
43
|
- data/export/runit/run.erb
|
43
44
|
- data/export/supervisord/app.conf.erb
|
@@ -51,6 +52,7 @@ files:
|
|
51
52
|
- lib/foreman/export/base.rb
|
52
53
|
- lib/foreman/export/bluepill.rb
|
53
54
|
- lib/foreman/export/inittab.rb
|
55
|
+
- lib/foreman/export/launchd.rb
|
54
56
|
- lib/foreman/export/runit.rb
|
55
57
|
- lib/foreman/export/supervisord.rb
|
56
58
|
- lib/foreman/export/upstart.rb
|
@@ -69,6 +71,7 @@ files:
|
|
69
71
|
- spec/foreman/export/base_spec.rb
|
70
72
|
- spec/foreman/export/bluepill_spec.rb
|
71
73
|
- spec/foreman/export/inittab_spec.rb
|
74
|
+
- spec/foreman/export/launchd_spec.rb
|
72
75
|
- spec/foreman/export/runit_spec.rb
|
73
76
|
- spec/foreman/export/supervisord_spec.rb
|
74
77
|
- spec/foreman/export/upstart_spec.rb
|
@@ -84,6 +87,8 @@ files:
|
|
84
87
|
- spec/resources/export/bluepill/app.pill
|
85
88
|
- spec/resources/export/inittab/inittab.concurrency
|
86
89
|
- spec/resources/export/inittab/inittab.default
|
90
|
+
- spec/resources/export/launchd/launchd-a.default
|
91
|
+
- spec/resources/export/launchd/launchd-b.default
|
87
92
|
- spec/resources/export/runit/app-alpha-1-log-run
|
88
93
|
- spec/resources/export/runit/app-alpha-1-run
|
89
94
|
- spec/resources/export/runit/app-alpha-2-log-run
|
@@ -114,18 +119,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
114
119
|
- - ! '>='
|
115
120
|
- !ruby/object:Gem::Version
|
116
121
|
version: '0'
|
117
|
-
segments:
|
118
|
-
- 0
|
119
|
-
hash: -4034722301368458418
|
120
122
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
123
|
none: false
|
122
124
|
requirements:
|
123
125
|
- - ! '>='
|
124
126
|
- !ruby/object:Gem::Version
|
125
127
|
version: '0'
|
126
|
-
segments:
|
127
|
-
- 0
|
128
|
-
hash: -4034722301368458418
|
129
128
|
requirements: []
|
130
129
|
rubyforge_project:
|
131
130
|
rubygems_version: 1.8.11
|