smoke_detector 0.0.1
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 +15 -0
- data/MIT-LICENSE +20 -0
- data/README.md +50 -0
- data/Rakefile +30 -0
- data/lib/smoke_detector.rb +37 -0
- data/lib/smoke_detector/controller_methods.rb +11 -0
- data/lib/smoke_detector/engine.rb +16 -0
- data/lib/smoke_detector/exceptions.rb +3 -0
- data/lib/smoke_detector/providers.rb +37 -0
- data/lib/smoke_detector/providers/airbrake.rb +30 -0
- data/lib/smoke_detector/providers/provider.rb +15 -0
- data/lib/smoke_detector/providers/rollbar.rb +41 -0
- data/lib/smoke_detector/version.rb +3 -0
- data/lib/tasks/smoke_detector_tasks.rake +4 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/widgets_controller.rb +26 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/widget.rb +13 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/app/views/widgets/index.html.erb +1 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +69 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/watch_tower.rb +24 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +6 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/schema.rb +16 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +8618 -0
- data/spec/dummy/log/test.log +11306 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/tmp/cache/assets/C5F/270/sprockets%2F32a6d17723de2976b8456753922862fc +0 -0
- data/spec/dummy/tmp/cache/assets/CA3/870/sprockets%2F703bc6444d2a153516ad9804dc24b467 +0 -0
- data/spec/dummy/tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 +0 -0
- data/spec/dummy/tmp/cache/assets/D2F/510/sprockets%2F42e50ab0277768533b1cad2237fb5ade +0 -0
- data/spec/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/spec/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/spec/dummy/tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/spec/dummy/tmp/cache/assets/DAE/CC0/sprockets%2Fcf340221edaaed61b5821abd28052dbb +0 -0
- data/spec/dummy/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/spec/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/spec/models/providers/airbrake_spec.rb +63 -0
- data/spec/models/providers/provider_spec.rb +22 -0
- data/spec/models/providers/rollbar_spec.rb +75 -0
- data/spec/models/providers_spec.rb +83 -0
- data/spec/models/smoke_detector_spec.rb +35 -0
- data/spec/requests/aibrake_config_spec.rb +11 -0
- data/spec/requests/multi_provider_config_spec.rb +16 -0
- data/spec/requests/rollbar_config_spec.rb +13 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/shared_examples_for_providers.rb +63 -0
- metadata +326 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>The page you were looking for doesn't exist (404)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/404.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>The page you were looking for doesn't exist.</h1>
|
23
|
+
<p>You may have mistyped the address or the page may have moved.</p>
|
24
|
+
</div>
|
25
|
+
</body>
|
26
|
+
</html>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>The change you wanted was rejected (422)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/422.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>The change you wanted was rejected.</h1>
|
23
|
+
<p>Maybe you tried to change something you didn't have access to.</p>
|
24
|
+
</div>
|
25
|
+
</body>
|
26
|
+
</html>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>We're sorry, but something went wrong (500)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/500.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>We're sorry, but something went wrong.</h1>
|
23
|
+
</div>
|
24
|
+
</body>
|
25
|
+
</html>
|
File without changes
|
@@ -0,0 +1,6 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
|
3
|
+
|
4
|
+
APP_PATH = File.expand_path('../../config/application', __FILE__)
|
5
|
+
require File.expand_path('../../config/boot', __FILE__)
|
6
|
+
require 'rails/commands'
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SmokeDetector::Providers::Airbrake do
|
4
|
+
let(:provider) { SmokeDetector::Providers::Airbrake.new('api_key', settings) }
|
5
|
+
let(:settings) { {} }
|
6
|
+
let(:err) { mock('error', backtrace: [], message: 'bad news') }
|
7
|
+
let(:data) { {custom: :data} }
|
8
|
+
|
9
|
+
describe '#alert' do
|
10
|
+
it 'notifies Airbrake of the exception' do
|
11
|
+
Airbrake.should_receive(:notify).with(err)
|
12
|
+
provider.alert(err)
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'when passed a controller option' do
|
16
|
+
it 'ignores the option' do
|
17
|
+
Airbrake.should_receive(:notify).with(err)
|
18
|
+
provider.alert(err, controller: mock('controller'))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'when passed a data option' do
|
23
|
+
it 'includes the data in the Airbrake parameters' do
|
24
|
+
Airbrake.should_receive(:notify).with(err, parameters: data)
|
25
|
+
provider.alert(err, data: data)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#message' do
|
32
|
+
let(:message) { 'hello airbrake' }
|
33
|
+
|
34
|
+
it 'notifies Airbrake of the message' do
|
35
|
+
Airbrake.should_receive(:notify).with(message)
|
36
|
+
provider.message(message)
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'when passed options' do
|
40
|
+
it 'includes the options in the Airbrake parameters' do
|
41
|
+
Airbrake.should_receive(:notify).with(message, parameters: data)
|
42
|
+
provider.message(message, data: data)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'ControllerMethods' do
|
48
|
+
let(:controller) do
|
49
|
+
ActionController::Base.new.tap do |c|
|
50
|
+
c.class.send(:include, SmokeDetector::Providers::Airbrake::ControllerMethods)
|
51
|
+
c.stub(:rollbar_request_data)
|
52
|
+
c.stub(:rollbar_person_data)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#alert_smoke_detector' do
|
57
|
+
it 'notifies Airbrake of the exception' do
|
58
|
+
controller.should_receive(:notify_airbrake)
|
59
|
+
controller.alert_smoke_detector(err)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SmokeDetector::Providers::Provider do
|
4
|
+
let(:provider) { SmokeDetector::Providers::Provider.new }
|
5
|
+
|
6
|
+
describe '#alert' do
|
7
|
+
subject { provider.alert(Exception.new) }
|
8
|
+
|
9
|
+
it 'raises an error' do
|
10
|
+
expect { subject }.to raise_error NotImplementedError
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#message' do
|
15
|
+
subject { provider.message(Exception.new) }
|
16
|
+
|
17
|
+
it 'raises an error' do
|
18
|
+
expect { subject }.to raise_error NotImplementedError
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SmokeDetector::Providers::Rollbar do
|
4
|
+
let(:provider) { SmokeDetector::Providers::Rollbar.new('api_key', settings) }
|
5
|
+
let(:settings) { {} }
|
6
|
+
let(:err) { StandardError.new('error') }
|
7
|
+
let(:data) { {custom: :data} }
|
8
|
+
|
9
|
+
describe '#alert' do
|
10
|
+
it 'reports the exception to Rollbar' do
|
11
|
+
Rollbar.should_receive(:report_exception).with(err)
|
12
|
+
provider.alert(err)
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'when passed a data option' do
|
16
|
+
it 'includes the data in the exception message' do
|
17
|
+
err.message.should_receive(:<<).with(data.to_s)
|
18
|
+
provider.alert(err, data: data)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#message' do
|
25
|
+
let(:message) { 'Hello Rollbar' }
|
26
|
+
let(:level) { 'info' }
|
27
|
+
let(:options) { {} }
|
28
|
+
|
29
|
+
it 'reports the message to Rollbar' do
|
30
|
+
Rollbar.should_receive(:report_message).with(message, level, options)
|
31
|
+
provider.message(message)
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'when passed options' do
|
35
|
+
let(:options) { {custom: :data} }
|
36
|
+
|
37
|
+
it 'passes the options along as the data param' do
|
38
|
+
Rollbar.should_receive(:report_message).with(message, level, options)
|
39
|
+
provider.message(message, options)
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'including the level' do
|
43
|
+
let(:level) { 'debug' }
|
44
|
+
let(:options) { {custom: :data, level: level} }
|
45
|
+
|
46
|
+
it 'sets the message level' do
|
47
|
+
Rollbar.should_receive(:report_message).with(message, level, options)
|
48
|
+
provider.message(message, options)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'does not include the level in the data param' do
|
52
|
+
Rollbar.should_receive(:report_message).with(message, level, options)
|
53
|
+
provider.message(message, options)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'ControllerMethods' do
|
60
|
+
let(:controller) do
|
61
|
+
ActionController::Base.new.tap do |c|
|
62
|
+
c.class.send(:include, SmokeDetector::Providers::Rollbar::ControllerMethods)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#alert_smoke_detector' do
|
67
|
+
it 'notifies Rollbar of the exception' do
|
68
|
+
Rollbar.should_receive(:report_exception)
|
69
|
+
controller.should_receive(:rollbar_request_data)
|
70
|
+
controller.should_receive(:rollbar_person_data)
|
71
|
+
controller.alert_smoke_detector(err)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SmokeDetector do
|
4
|
+
let(:provider) { :rollbar }
|
5
|
+
let(:api_key) { 'some_key' }
|
6
|
+
let(:provider_class) { SmokeDetector::Providers::Rollbar }
|
7
|
+
|
8
|
+
describe '.register_provider' do
|
9
|
+
context 'with a supported provider' do
|
10
|
+
context 'that is unregistered' do
|
11
|
+
before do
|
12
|
+
SmokeDetector.stub(:registered_provider?).with(provider).and_return(false)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'adds the provider to the SmokeDetector providers' do
|
16
|
+
SmokeDetector.register_provider(provider, api_key)
|
17
|
+
SmokeDetector.providers.last.should be_a provider_class
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'that is registered' do
|
22
|
+
before do
|
23
|
+
SmokeDetector.stub(:registered_provider?).with(provider).and_return(true)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'raises an error' do
|
27
|
+
expect { SmokeDetector.register_provider(provider, api_key) }.to raise_error SmokeDetector::ProviderRegistrationError
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'with an unsupported provider' do
|
34
|
+
let(:provider) { :not_a_provider }
|
35
|
+
|
36
|
+
it 'raises an error' do
|
37
|
+
expect { SmokeDetector.register_provider(provider, api_key) }.to raise_error SmokeDetector::ProviderRegistrationError
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '.registered_provider?' do
|
43
|
+
subject { SmokeDetector.registered_provider?(provider) }
|
44
|
+
|
45
|
+
context "with a supported provider" do
|
46
|
+
context 'that is registered' do
|
47
|
+
before do
|
48
|
+
SmokeDetector.register_provider(provider, api_key)
|
49
|
+
end
|
50
|
+
|
51
|
+
it { should == true }
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'that is unregistered' do
|
55
|
+
it { should == false }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "with an unsupported provider" do
|
60
|
+
let(:provider) { :not_a_provider }
|
61
|
+
|
62
|
+
it 'raises an error' do
|
63
|
+
expect { subject }.to raise_error SmokeDetector::ProviderRegistrationError
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '.classify_provider' do
|
69
|
+
subject { SmokeDetector.classify_provider(provider) }
|
70
|
+
|
71
|
+
context "with a supported provider" do
|
72
|
+
it { should == provider_class }
|
73
|
+
end
|
74
|
+
|
75
|
+
context "with an unsupported provider" do
|
76
|
+
let(:provider) { :not_a_provider }
|
77
|
+
|
78
|
+
it 'raises an error' do
|
79
|
+
expect { subject }.to raise_error SmokeDetector::ProviderRegistrationError
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SmokeDetector do
|
4
|
+
describe '.alert' do
|
5
|
+
let(:err) { Exception.new }
|
6
|
+
|
7
|
+
before do
|
8
|
+
SmokeDetector.register_provider(:airbrake, 'key')
|
9
|
+
SmokeDetector.register_provider(:rollbar, 'key')
|
10
|
+
SmokeDetector.providers.size.should > 1
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'given an exception' do
|
14
|
+
it 'notifies the provider of the exception' do
|
15
|
+
SmokeDetector.providers.each do |provider|
|
16
|
+
provider.should_receive(:alert).once.with(err, {})
|
17
|
+
end
|
18
|
+
SmokeDetector.alert(err)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '.message' do
|
24
|
+
context 'given a message' do
|
25
|
+
let(:message) { "holy crap!" }
|
26
|
+
|
27
|
+
it 'notifies the provider of the message' do
|
28
|
+
SmokeDetector.providers.each do |provider|
|
29
|
+
provider.should_receive(:message).once.with(message, {})
|
30
|
+
end
|
31
|
+
SmokeDetector.message(message)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Airbrake config: An exception' do
|
4
|
+
|
5
|
+
before do
|
6
|
+
SmokeDetector.instance_variable_set(:@providers, [])
|
7
|
+
SmokeDetector.register_provider(:airbrake, 'key')
|
8
|
+
end
|
9
|
+
|
10
|
+
it_behaves_like 'Airbrake integrated error handler'
|
11
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Multi-provider config: An exception' do
|
4
|
+
|
5
|
+
before do
|
6
|
+
SmokeDetector.instance_variable_set(:@providers, [])
|
7
|
+
|
8
|
+
SmokeDetector.register_provider(:rollbar, 'key')
|
9
|
+
SmokeDetector.register_provider(:airbrake, 'key')
|
10
|
+
SmokeDetector.providers.size.should == 2
|
11
|
+
end
|
12
|
+
|
13
|
+
it_behaves_like 'Rollbar integrated error handler'
|
14
|
+
it_behaves_like 'Airbrake integrated error handler'
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Rollbar config: An exception' do
|
4
|
+
|
5
|
+
before do
|
6
|
+
SmokeDetector.instance_variable_set(:@providers, [])
|
7
|
+
|
8
|
+
SmokeDetector.register_provider(:rollbar, 'key')
|
9
|
+
end
|
10
|
+
|
11
|
+
it_behaves_like 'Rollbar integrated error handler'
|
12
|
+
|
13
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
ENV['RAILS_ENV'] ||= 'test'
|
2
|
+
|
3
|
+
require File.expand_path("../dummy/config/environment.rb", __FILE__)
|
4
|
+
require 'rspec/rails'
|
5
|
+
require 'rspec/autorun'
|
6
|
+
|
7
|
+
Rails.backtrace_cleaner.remove_silencers!
|
8
|
+
|
9
|
+
# Load support files
|
10
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.mock_with :rspec
|
14
|
+
config.use_transactional_fixtures = true
|
15
|
+
config.infer_base_class_for_anonymous_controllers = false
|
16
|
+
config.order = "random"
|
17
|
+
|
18
|
+
config.before(:each) do
|
19
|
+
SmokeDetector.instance_variable_set(:@providers, [])
|
20
|
+
|
21
|
+
# sandbox services
|
22
|
+
Airbrake.stub(:send_notice)
|
23
|
+
Rollbar.stub(:schedule_payload)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|