banter 0.4.0
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 +7 -0
- data/.gitignore +22 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +113 -0
- data/Rakefile +1 -0
- data/banter.gemspec +37 -0
- data/bin/start_subscribers +14 -0
- data/config/pubsub.yml +17 -0
- data/lib/banter.rb +47 -0
- data/lib/banter/cli.rb +94 -0
- data/lib/banter/configuration.rb +42 -0
- data/lib/banter/context.rb +70 -0
- data/lib/banter/db_logger.rb +52 -0
- data/lib/banter/exceptions/payload_validation_error.rb +4 -0
- data/lib/banter/logger.rb +49 -0
- data/lib/banter/logging.rb +42 -0
- data/lib/banter/message.rb +50 -0
- data/lib/banter/middleware.rb +14 -0
- data/lib/banter/publisher.rb +93 -0
- data/lib/banter/railtie.rb +27 -0
- data/lib/banter/server.rb +7 -0
- data/lib/banter/server/client_queue_listener.rb +56 -0
- data/lib/banter/server/rabbit_mq_subscriber.rb +75 -0
- data/lib/banter/server/subscriber_server.rb +84 -0
- data/lib/banter/subscriber.rb +100 -0
- data/lib/banter/version.rb +3 -0
- data/spec/banter/cli_spec.rb +145 -0
- data/spec/banter/server/client_queue_listener_spec.rb +76 -0
- data/spec/banter/server/client_worker_spec.rb +161 -0
- data/spec/banter/server/rabbitmq_subscriber_spec.rb +5 -0
- data/spec/config.yml +20 -0
- data/spec/logger_spec.rb +110 -0
- data/spec/message_spec.rb +65 -0
- data/spec/spec_helper.rb +49 -0
- metadata +261 -0
data/spec/config.yml
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
test:
|
2
|
+
# Documentation for parameters for rabbit and bunny is located here: http://rubybunny.info/articles/connecting.html
|
3
|
+
connection:
|
4
|
+
host: localhost
|
5
|
+
port: 5672
|
6
|
+
# username: rabbit
|
7
|
+
# password: rabbit
|
8
|
+
heartbeat: 60 # in seconds
|
9
|
+
log_level: 0
|
10
|
+
log_file: test_rabbit.log
|
11
|
+
network_recovery_interval: 10 # in seconds
|
12
|
+
continuation_timeout: 4000 # in milliseconds
|
13
|
+
|
14
|
+
logger:
|
15
|
+
enabled: true
|
16
|
+
level: warn
|
17
|
+
file: test_pubsub.log
|
18
|
+
|
19
|
+
|
20
|
+
pid: pids/banter
|
data/spec/logger_spec.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Banter::Logger do
|
4
|
+
let(:routing_key) { "test/logger" }
|
5
|
+
let(:subject) { Banter::Logger.new }
|
6
|
+
let(:payload) { {"hit"=>"me"} }
|
7
|
+
let(:message) { Banter::Message.new.serialize(context, routing_key, payload) }
|
8
|
+
let(:config_buffer) { StringIO.new }
|
9
|
+
let(:output_string) { config_buffer.string }
|
10
|
+
let(:config_override) { {} }
|
11
|
+
let(:context) { {unique_id: "1234", orig_ip_address: "127.0.0.1"}}
|
12
|
+
|
13
|
+
before do
|
14
|
+
Banter::Logging.instance_variable_set(:"@logger", nil)
|
15
|
+
directory = File::dirname(__FILE__)
|
16
|
+
full_name = File.join(directory, "config.yml")
|
17
|
+
@config_data = HashWithIndifferentAccess.new( YAML.load_file(full_name)[ENV["RAILS_ENV"]] )
|
18
|
+
@config_data.merge!(config_override)
|
19
|
+
@config_data[:logger][:file] = config_buffer
|
20
|
+
allow(Banter::Configuration).to receive(:configuration).at_least(1).and_return(@config_data)
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#log_publish" do
|
24
|
+
let(:result) { subject.log_publish(routing_key, message)}
|
25
|
+
|
26
|
+
|
27
|
+
it "should not fail" do
|
28
|
+
expect{ result }.not_to raise_error()
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should write a row to the log" do
|
32
|
+
result
|
33
|
+
expect(output_string.length).not_to eq(0)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#failed_publish" do
|
39
|
+
let(:result) { subject.failed_publish(routing_key, {}, message)}
|
40
|
+
|
41
|
+
context "warning log level" do
|
42
|
+
|
43
|
+
it "should not fail" do
|
44
|
+
expect{ result }.not_to raise_error()
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should respect the log level of the file" do
|
48
|
+
result
|
49
|
+
expect(output_string.length).not_to eq(0)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "fatal log level" do
|
54
|
+
let(:config_override) { { 'logger'=> { 'level'=>'fatal' } } }
|
55
|
+
|
56
|
+
it "should not fail" do
|
57
|
+
expect{ result }.not_to raise_error()
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should respect the log level of the file" do
|
61
|
+
result
|
62
|
+
expect(output_string.length).to eq(0)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "#log_receive" do
|
68
|
+
let(:result) { subject.log_receive(routing_key, message)}
|
69
|
+
|
70
|
+
it "should not fail" do
|
71
|
+
expect{ result }.not_to raise_error()
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should respect the log level of the file" do
|
75
|
+
result
|
76
|
+
expect(output_string.length).not_to eq(0)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "#log_service" do
|
81
|
+
let(:service_name) { "test" }
|
82
|
+
let(:result) { subject.log_service(service_name, log_level, message)}
|
83
|
+
|
84
|
+
context "debugger log" do
|
85
|
+
let(:log_level) { :debug }
|
86
|
+
it "should not fail" do
|
87
|
+
expect{ result }.not_to raise_error()
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should respect the log level of the file" do
|
91
|
+
result
|
92
|
+
expect(output_string.length).to eq(0)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context "warn log" do
|
97
|
+
let(:log_level) { :warn }
|
98
|
+
it "should not fail" do
|
99
|
+
expect{ result }.not_to raise_error()
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should respect the log level of the file" do
|
103
|
+
result
|
104
|
+
expect(output_string.length).not_to eq(0)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Banter::Message do
|
4
|
+
let(:original) { {'data'=> 'found'} }
|
5
|
+
let(:subject) { Banter::Message.new }
|
6
|
+
let(:routing_key) { "test.message" }
|
7
|
+
let(:context) { {unique_id: "1234", orig_ip_address: "127.0.0.1"}}
|
8
|
+
|
9
|
+
describe "#serialize" do
|
10
|
+
let(:result) { subject.serialize(context, routing_key, original)}
|
11
|
+
|
12
|
+
it "should not fail" do
|
13
|
+
expect{ result }.not_to raise_error()
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should have all the header items" do
|
17
|
+
expect(result[:ts]).not_to be_nil
|
18
|
+
expect(result[:v]).not_to be_nil
|
19
|
+
expect(result[:pub]).not_to be_nil
|
20
|
+
expect(result[:payload]).not_to be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should have the data in the payload" do
|
24
|
+
expect(result[:payload][:data]).to eq('found')
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should have the attributes readable" do
|
28
|
+
result
|
29
|
+
expect(subject.ts).not_to be_nil
|
30
|
+
expect(subject.version).not_to be_nil
|
31
|
+
expect(subject.pub).not_to be_nil
|
32
|
+
expect(subject.payload).not_to be_nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#parse" do
|
37
|
+
let(:payload) { subject.serialize(context, routing_key, original).to_json }
|
38
|
+
let(:result) { subject.parse(payload) }
|
39
|
+
|
40
|
+
it "should not fail" do
|
41
|
+
expect{ result }.not_to raise_error()
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should have all the hash items" do
|
45
|
+
expect(result[:ts]).not_to be_nil
|
46
|
+
expect(result[:v]).not_to be_nil
|
47
|
+
expect(result[:pub]).not_to be_nil
|
48
|
+
expect(result[:payload]).not_to be_nil
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should have the data in the payload" do
|
52
|
+
expect(result[:payload][:data]).to eq('found')
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should have the attributes readable" do
|
56
|
+
result
|
57
|
+
expect(subject.ts).not_to be_nil
|
58
|
+
expect(subject.version).not_to be_nil
|
59
|
+
expect(subject.pub).not_to be_nil
|
60
|
+
expect(subject.payload).not_to be_nil
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
|
8
|
+
require 'awesome_print'
|
9
|
+
require 'banter'
|
10
|
+
require 'banter/server'
|
11
|
+
ENV['RAILS_ENV'] ||= "test"
|
12
|
+
ENV['RACK_ENV'] ||= "test"
|
13
|
+
|
14
|
+
|
15
|
+
RSpec.configure do |config|
|
16
|
+
# config.treat_symbols_as_metadata_keys_with_true_values = true
|
17
|
+
config.run_all_when_everything_filtered = true
|
18
|
+
# Don't want to filter on anything right now.
|
19
|
+
config.filter_run :focus
|
20
|
+
|
21
|
+
# Run specs in random order to surface order dependencies. If you find an
|
22
|
+
# order dependency and want to debug it, you can fix the order by providing
|
23
|
+
# the seed, which is printed after each run.
|
24
|
+
# --seed 1234
|
25
|
+
# For now, always in same order
|
26
|
+
config.order = 'random'
|
27
|
+
end
|
28
|
+
|
29
|
+
Banter::Configuration.configure_with("test", File.join(Banter.root,"spec/config.yml") )
|
30
|
+
|
31
|
+
# Some test subscriber classes to make testing easier
|
32
|
+
class MyTestSubscriber1 < Banter::Subscriber
|
33
|
+
|
34
|
+
def perform(payload)
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class MyTestSubscriber2 < Banter::Subscriber
|
40
|
+
def perform(payload)
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class MyTestSubscriber3 < MyTestSubscriber1
|
46
|
+
def perform(payload)
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
metadata
ADDED
@@ -0,0 +1,261 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: banter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- The Honest Company
|
8
|
+
- Thanh Lim
|
9
|
+
- Tushar Ranka
|
10
|
+
- Joel Jackson
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2014-07-01 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: bundler
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '1.3'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.3'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - '>='
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '10.0'
|
37
|
+
type: :development
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '10.0'
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: rspec
|
46
|
+
requirement: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ~>
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 3.0.0
|
51
|
+
type: :development
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ~>
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 3.0.0
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: debugger
|
60
|
+
requirement: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
type: :development
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: activesupport
|
74
|
+
requirement: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '3.2'
|
79
|
+
type: :runtime
|
80
|
+
prerelease: false
|
81
|
+
version_requirements: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '3.2'
|
86
|
+
- !ruby/object:Gem::Dependency
|
87
|
+
name: awesome_print
|
88
|
+
requirement: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ~>
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 1.2.0
|
93
|
+
type: :runtime
|
94
|
+
prerelease: false
|
95
|
+
version_requirements: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ~>
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: 1.2.0
|
100
|
+
- !ruby/object:Gem::Dependency
|
101
|
+
name: bunny
|
102
|
+
requirement: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '1.2'
|
107
|
+
type: :runtime
|
108
|
+
prerelease: false
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - '>='
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '1.2'
|
114
|
+
- !ruby/object:Gem::Dependency
|
115
|
+
name: airbrake
|
116
|
+
requirement: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - '>='
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '3.1'
|
121
|
+
type: :runtime
|
122
|
+
prerelease: false
|
123
|
+
version_requirements: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - '>='
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '3.1'
|
128
|
+
- !ruby/object:Gem::Dependency
|
129
|
+
name: hashie
|
130
|
+
requirement: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - '>='
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '1.2'
|
135
|
+
type: :runtime
|
136
|
+
prerelease: false
|
137
|
+
version_requirements: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '1.2'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: json
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - '>='
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '1.8'
|
149
|
+
type: :runtime
|
150
|
+
prerelease: false
|
151
|
+
version_requirements: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - '>='
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '1.8'
|
156
|
+
- !ruby/object:Gem::Dependency
|
157
|
+
name: celluloid
|
158
|
+
requirement: !ruby/object:Gem::Requirement
|
159
|
+
requirements:
|
160
|
+
- - '>='
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: '0.15'
|
163
|
+
type: :runtime
|
164
|
+
prerelease: false
|
165
|
+
version_requirements: !ruby/object:Gem::Requirement
|
166
|
+
requirements:
|
167
|
+
- - '>='
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: '0.15'
|
170
|
+
- !ruby/object:Gem::Dependency
|
171
|
+
name: celluloid-io
|
172
|
+
requirement: !ruby/object:Gem::Requirement
|
173
|
+
requirements:
|
174
|
+
- - '>='
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
version: '0.15'
|
177
|
+
type: :runtime
|
178
|
+
prerelease: false
|
179
|
+
version_requirements: !ruby/object:Gem::Requirement
|
180
|
+
requirements:
|
181
|
+
- - '>='
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: '0.15'
|
184
|
+
description: Publish & subscribe to messages
|
185
|
+
email:
|
186
|
+
- webadmin@honest.com
|
187
|
+
- tusharranka@gmail.com
|
188
|
+
- jackson.joel@gmail.com
|
189
|
+
executables:
|
190
|
+
- start_subscribers
|
191
|
+
extensions: []
|
192
|
+
extra_rdoc_files: []
|
193
|
+
files:
|
194
|
+
- .gitignore
|
195
|
+
- .ruby-version
|
196
|
+
- Gemfile
|
197
|
+
- LICENSE.txt
|
198
|
+
- README.md
|
199
|
+
- Rakefile
|
200
|
+
- banter.gemspec
|
201
|
+
- bin/start_subscribers
|
202
|
+
- config/pubsub.yml
|
203
|
+
- lib/banter.rb
|
204
|
+
- lib/banter/cli.rb
|
205
|
+
- lib/banter/configuration.rb
|
206
|
+
- lib/banter/context.rb
|
207
|
+
- lib/banter/db_logger.rb
|
208
|
+
- lib/banter/exceptions/payload_validation_error.rb
|
209
|
+
- lib/banter/logger.rb
|
210
|
+
- lib/banter/logging.rb
|
211
|
+
- lib/banter/message.rb
|
212
|
+
- lib/banter/middleware.rb
|
213
|
+
- lib/banter/publisher.rb
|
214
|
+
- lib/banter/railtie.rb
|
215
|
+
- lib/banter/server.rb
|
216
|
+
- lib/banter/server/client_queue_listener.rb
|
217
|
+
- lib/banter/server/rabbit_mq_subscriber.rb
|
218
|
+
- lib/banter/server/subscriber_server.rb
|
219
|
+
- lib/banter/subscriber.rb
|
220
|
+
- lib/banter/version.rb
|
221
|
+
- spec/banter/cli_spec.rb
|
222
|
+
- spec/banter/server/client_queue_listener_spec.rb
|
223
|
+
- spec/banter/server/client_worker_spec.rb
|
224
|
+
- spec/banter/server/rabbitmq_subscriber_spec.rb
|
225
|
+
- spec/config.yml
|
226
|
+
- spec/logger_spec.rb
|
227
|
+
- spec/message_spec.rb
|
228
|
+
- spec/spec_helper.rb
|
229
|
+
homepage: ''
|
230
|
+
licenses:
|
231
|
+
- MIT
|
232
|
+
metadata: {}
|
233
|
+
post_install_message:
|
234
|
+
rdoc_options: []
|
235
|
+
require_paths:
|
236
|
+
- lib
|
237
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
238
|
+
requirements:
|
239
|
+
- - '>='
|
240
|
+
- !ruby/object:Gem::Version
|
241
|
+
version: '0'
|
242
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
243
|
+
requirements:
|
244
|
+
- - '>='
|
245
|
+
- !ruby/object:Gem::Version
|
246
|
+
version: '0'
|
247
|
+
requirements: []
|
248
|
+
rubyforge_project:
|
249
|
+
rubygems_version: 2.3.0
|
250
|
+
signing_key:
|
251
|
+
specification_version: 4
|
252
|
+
summary: Library for pub-sub (Message Bus)
|
253
|
+
test_files:
|
254
|
+
- spec/banter/cli_spec.rb
|
255
|
+
- spec/banter/server/client_queue_listener_spec.rb
|
256
|
+
- spec/banter/server/client_worker_spec.rb
|
257
|
+
- spec/banter/server/rabbitmq_subscriber_spec.rb
|
258
|
+
- spec/config.yml
|
259
|
+
- spec/logger_spec.rb
|
260
|
+
- spec/message_spec.rb
|
261
|
+
- spec/spec_helper.rb
|