wisper 1.4.0 → 1.5.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 CHANGED
@@ -1,7 +1,7 @@
1
- ---
2
- SHA1:
3
- metadata.gz: c905b4f311d82eadf52d61a11a0515109335d592
4
- data.tar.gz: 9be198a62d94f3c8fbc46253cd109a438d857206
5
- SHA512:
6
- metadata.gz: 44ddd88e30752991d23dafa678086611a05f0206f152066b18b0283b0eb9462728b06be38f46fdf08caba0ead38919e88530ad1566cc2d8d92d751813805e851
7
- data.tar.gz: d7ee6c49e1c6320596e00f1a79624e8ea3bb7f413b4fa76348415e3ff40225772522d1f861946dd83aaf1fbff0e86a89eccb72dd0dca1ca91843a72d78a3193a
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b7eced3bc4dc2f86f783081e2a3e9f592078deb4
4
+ data.tar.gz: 3ce6be1cf3f79319bf8582c86eced10458c2e65f
5
+ SHA512:
6
+ metadata.gz: c680af80578aa1d5170d4334ccda9545be5c0cbcea4ad105e82d5a4e0d5d4ab24a8f835ba1d5e4b9a7c8eb723ca7ff36c2ec0dfdc2da8fcf2e51347a95316280
7
+ data.tar.gz: 5b6501efcf475b2a157e8975e6bf7c1ab83118b73313fd3ea7b8e2c5160bb93a301572f6aa30568b6d264c2fc4d779808dfd2b1ffc8395808274c7785712ecc6
data/.rspec CHANGED
@@ -1,2 +1,4 @@
1
1
  --color
2
2
  --format progress
3
+ --require spec_helper
4
+ --warnings
@@ -0,0 +1 @@
1
+ 2.1.2
@@ -1,5 +1,5 @@
1
1
  language: ruby
2
- bundler_args: --without=metrics
2
+ bundler_args: --without=extras
3
3
  script: rspec spec
4
4
  rvm:
5
5
  - '1.9.2'
@@ -1,6 +1,13 @@
1
1
  ## Unreleased (no breaking changes)
2
2
 
3
- None
3
+ ## 1.5.0 (6th Oct 2014)
4
+
5
+ Authors: Kris Leech
6
+
7
+ * feature: allow events to be published asynchronously
8
+ * feature: broadcasting of events is plugable and configurable
9
+ * feature: broadcasters can be aliased via a symbol
10
+ * feature: logging broadcaster
4
11
 
5
12
  ## 1.4.0 (8th Sept 2014)
6
13
 
data/Gemfile CHANGED
@@ -4,13 +4,11 @@ gemspec
4
4
 
5
5
  gem 'rubysl', '~> 2.0', :platforms => :rbx
6
6
 
7
+ gem 'rake', '~> 10.3.0'
8
+ gem 'rspec', '~> 3.0.0'
7
9
  gem 'coveralls', require: false
8
10
 
9
- group :metrics do
11
+ group :extras do
10
12
  gem 'flay'
11
- end
12
-
13
- group :development do
14
- gem 'rake', '~> 10.3.0'
15
- gem 'rspec', '~> 3.0.0'
13
+ gem 'pry'
16
14
  end
data/README.md CHANGED
@@ -70,7 +70,13 @@ end
70
70
 
71
71
  ### Asynchronous Publishing
72
72
 
73
- Please refer to the [wisper-async](https://github.com/krisleech/wisper-async) gem.
73
+ ```ruby
74
+ my_publisher.subscribe(MyListener.new, async: true)
75
+ ```
76
+
77
+ Please refer to
78
+ [wisper-celluloid](https://github.com/krisleech/wisper-celluloid) or
79
+ [wisper-sidekiq](https://github.com/krisleech/wisper-sidekiq).
74
80
 
75
81
  ### ActiveRecord
76
82
 
@@ -1,11 +1,14 @@
1
1
  require 'set'
2
2
  require 'wisper/version'
3
+ require 'wisper/configuration'
3
4
  require 'wisper/publisher'
4
5
  require 'wisper/registration/registration'
5
6
  require 'wisper/registration/object'
6
7
  require 'wisper/registration/block'
7
8
  require 'wisper/global_listeners'
8
9
  require 'wisper/temporary_listeners'
10
+ require 'wisper/broadcasters/send_broadcaster'
11
+ require 'wisper/broadcasters/logger_broadcaster'
9
12
 
10
13
  module Wisper
11
14
  def self.included(base)
@@ -33,4 +36,20 @@ module Wisper
33
36
  end
34
37
  end
35
38
  end
39
+
40
+ def self.configure
41
+ yield(configuration)
42
+ end
43
+
44
+ def self.configuration
45
+ @configuration ||= Configuration.new
46
+ end
47
+
48
+ def self.setup
49
+ configure do |config|
50
+ config.broadcaster(:default, Broadcasters::SendBroadcaster.new)
51
+ end
52
+ end
36
53
  end
54
+
55
+ Wisper.setup
@@ -0,0 +1,31 @@
1
+ # Provides a way of wrapping another broadcaster with logging
2
+
3
+ module Wisper
4
+ module Broadcasters
5
+ class LoggerBroadcaster
6
+ def initialize(logger, broadcaster)
7
+ @logger = logger
8
+ @broadcaster = broadcaster
9
+ end
10
+
11
+ def broadcast(subscriber, publisher, event, args)
12
+ @logger.info("[WISPER] #{name(publisher)} published #{event} to #{name(subscriber)} with #{arg_names(args)}")
13
+ @broadcaster.broadcast(subscriber, publisher, event, args)
14
+ end
15
+
16
+ private
17
+
18
+ def name(object)
19
+ id_method = %w(id uuid key object_id).find { |id_method| object.respond_to?(id_method) }
20
+ id = object.send(id_method)
21
+ class_name = object.class == Class ? object.name : object.class.name
22
+ "#{class_name}##{id}"
23
+ end
24
+
25
+ def arg_names(args)
26
+ return 'no arguments' if args.empty?
27
+ args.map { |arg| name(arg) }.join(', ')
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,9 @@
1
+ module Wisper
2
+ module Broadcasters
3
+ class SendBroadcaster
4
+ def broadcast(subscriber, publisher, event, args)
5
+ subscriber.public_send(event, *args)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,28 @@
1
+ module Wisper
2
+ class Configuration
3
+ attr_reader :broadcasters
4
+
5
+ def initialize
6
+ @broadcasters = Broadcasters.new
7
+ end
8
+
9
+ def broadcaster(name, broadcaster)
10
+ @broadcasters[name] = broadcaster
11
+ end
12
+
13
+ class Broadcasters
14
+ extend Forwardable
15
+
16
+ def_delegators :@data, :fetch, :[], :[]=, :empty?, :include?, :clear
17
+
18
+ def initialize
19
+ @data = {}
20
+ end
21
+
22
+ def fetch(key)
23
+ raise KeyError, "broadcaster not found for #{key}" unless include?(key)
24
+ @data[key]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,19 +1,19 @@
1
1
  module Wisper
2
2
  class ObjectRegistration < Registration
3
- attr_reader :with, :prefix, :allowed_classes
3
+ attr_reader :with, :prefix, :allowed_classes, :broadcaster
4
4
 
5
5
  def initialize(listener, options)
6
6
  super(listener, options)
7
7
  @with = options[:with]
8
8
  @prefix = stringify_prefix(options[:prefix])
9
9
  @allowed_classes = Array(options[:scope]).map(&:to_s).to_set
10
- fail_on_async if options.has_key?(:async)
10
+ @broadcaster = map_broadcaster(options[:async] || options[:broadcaster])
11
11
  end
12
12
 
13
13
  def broadcast(event, publisher, *args)
14
14
  method_to_call = map_event_to_method(event)
15
15
  if should_broadcast?(event) && listener.respond_to?(method_to_call) && publisher_in_scope?(publisher)
16
- listener.public_send(method_to_call, *args)
16
+ broadcaster.broadcast(listener, publisher, method_to_call, args)
17
17
  end
18
18
  end
19
19
 
@@ -42,8 +42,15 @@ module Wisper
42
42
  'on'
43
43
  end
44
44
 
45
- def fail_on_async
46
- raise 'The async feature has been moved to the wisper-async gem'
45
+ def map_broadcaster(value)
46
+ return value if value.respond_to?(:broadcast)
47
+ value = :async if value == true
48
+ value = :default if value == nil
49
+ configuration.broadcasters.fetch(value)
50
+ end
51
+
52
+ def configuration
53
+ Wisper.configuration
47
54
  end
48
55
  end
49
56
  end
@@ -1,3 +1,3 @@
1
1
  module Wisper
2
- VERSION = "1.4.0"
2
+ VERSION = "1.5.0"
3
3
  end
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  describe Wisper::GlobalListeners do
4
2
  let(:global_listener) { double('listener') }
5
3
  let(:local_listener) { double('listener') }
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  # Example
4
2
  class MyCommand
5
3
  include Wisper::Publisher
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  class MyPublisher
4
2
  include Wisper::Publisher
5
3
 
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  describe Wisper::TemporaryListeners do
4
2
  let(:listener_1) { double('listener', :to_a => nil) } # [1]
5
3
  let(:listener_2) { double('listener', :to_a => nil) }
@@ -0,0 +1,73 @@
1
+ module Wisper
2
+ module Broadcasters
3
+
4
+ describe LoggerBroadcaster do
5
+
6
+ describe 'integration tests:' do
7
+ let(:publisher) { publisher_class.new }
8
+ let(:listener) { double }
9
+ let(:logger) { double.as_null_object }
10
+
11
+ it 'broadcasts the event to the listener' do
12
+ publisher.subscribe(listener, :broadcaster => LoggerBroadcaster.new(logger, Wisper::Broadcasters::SendBroadcaster.new))
13
+ expect(listener).to receive(:it_happened).with(1, 2)
14
+ publisher.send(:broadcast, :it_happened, 1, 2)
15
+ end
16
+ end
17
+
18
+ describe 'unit tests:' do
19
+ let(:publisher) { classy_double('Publisher', id: 1) }
20
+ let(:subscriber) { classy_double('Subscriber', id: 2) }
21
+ let(:logger) { double('Logger').as_null_object }
22
+ let(:broadcaster) { double('Broadcaster').as_null_object }
23
+ let(:event) { 'thing_created' }
24
+
25
+ subject { LoggerBroadcaster.new(logger, broadcaster) }
26
+
27
+ describe '#broadcast' do
28
+ context 'without arguments' do
29
+ let(:args) { [] }
30
+
31
+ it 'logs publised event' do
32
+ expect(logger).to receive(:info).with('[WISPER] Publisher#1 published thing_created to Subscriber#2 with no arguments')
33
+ subject.broadcast(subscriber, publisher, event, args)
34
+ end
35
+
36
+ it 'delgates broadcast to given broadcaster' do
37
+ expect(broadcaster).to receive(:broadcast).with(subscriber, publisher, event, args)
38
+ subject.broadcast(subscriber, publisher, event, args)
39
+ end
40
+ end
41
+
42
+ context 'with arguments' do
43
+ let(:args) { [arg_double(id: 3), arg_double(id: 4)] }
44
+
45
+ it 'logs published event and arguments' do
46
+ expect(logger).to receive(:info).with('[WISPER] Publisher#1 published thing_created to Subscriber#2 with Argument#3, Argument#4')
47
+ subject.broadcast(subscriber, publisher, event, args)
48
+ end
49
+
50
+ it 'delgates broadcast to given broadcaster' do
51
+ expect(broadcaster).to receive(:broadcast).with(subscriber, publisher, event, args)
52
+ subject.broadcast(subscriber, publisher, event, args)
53
+ end
54
+ end
55
+
56
+ end
57
+
58
+ # provides a way to specify `double.class.name` easily
59
+ def classy_double(klass, options)
60
+ double(klass, options.merge(class: double_class(klass)))
61
+ end
62
+
63
+ def arg_double(options)
64
+ classy_double('Argument', options)
65
+ end
66
+
67
+ def double_class(name)
68
+ double(name: name)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,28 @@
1
+ module Wisper
2
+ module Broadcasters
3
+ describe SendBroadcaster do
4
+ let(:subscriber) { double('subscriber') }
5
+ let(:event) { 'thing_created' }
6
+
7
+ describe '#broadcast' do
8
+ context 'without arguments' do
9
+ let(:args) { [] }
10
+
11
+ it 'sends event to subscriber without any arguments' do
12
+ expect(subscriber).to receive(event).with(no_args())
13
+ subject.broadcast(subscriber, anything, event, args)
14
+ end
15
+ end
16
+
17
+ context 'with arguments' do
18
+ let(:args) { [1,2,3] }
19
+
20
+ it 'sends event to subscriber with arguments' do
21
+ expect(subscriber).to receive(event).with(*args)
22
+ subject.broadcast(subscriber, anything, event, args)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ module Wisper
2
+ describe Configuration do
3
+ describe 'broadcasters' do
4
+ let(:broadcaster) { double }
5
+ let(:key) { :default }
6
+
7
+ it '#broadcasters returns empty collection' do
8
+ expect(subject.broadcasters).to be_empty
9
+ end
10
+
11
+ it '#broadcaster adds given broadcaster' do
12
+ subject.broadcaster(key, broadcaster)
13
+ expect(subject.broadcasters).to include key
14
+ expect(subject.broadcasters[key]).to eql broadcaster
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  describe Wisper::Publisher do
4
2
  let(:listener) { double('listener') }
5
3
  let(:publisher) { publisher_class.new }
@@ -82,9 +80,6 @@ describe Wisper::Publisher do
82
80
  let(:listener_1) { double('Listener') }
83
81
  let(:listener_2) { double('Listener') }
84
82
 
85
- before do
86
- end
87
-
88
83
  it 'scopes listener to given class' do
89
84
  expect(listener_1).to receive(:it_happended)
90
85
  expect(listener_2).not_to receive(:it_happended)
@@ -115,6 +110,66 @@ describe Wisper::Publisher do
115
110
  end
116
111
  end
117
112
 
113
+ describe ':broadcaster argument'do
114
+ let(:broadcaster) { double('broadcaster') }
115
+ let(:listener) { double('listener') }
116
+ let(:event_name) { 'it_happened' }
117
+
118
+ before do
119
+ Wisper.configuration.broadcasters.clear
120
+ allow(listener).to receive(event_name)
121
+ allow(broadcaster).to receive(:broadcast)
122
+ end
123
+
124
+ after { Wisper.setup } # restore default configuration
125
+
126
+ it 'given an object which responds_to broadcast it uses object' do
127
+ publisher.subscribe(listener, broadcaster: broadcaster)
128
+ expect(broadcaster).to receive('broadcast')
129
+ publisher.send(:broadcast, event_name)
130
+ end
131
+
132
+ it 'given a key it uses a configured broadcaster' do
133
+ Wisper.configure { |c| c.broadcaster(:foobar, broadcaster) }
134
+ publisher.subscribe(listener, broadcaster: :foobar)
135
+ expect(broadcaster).to receive('broadcast')
136
+ publisher.send(:broadcast, event_name)
137
+ end
138
+
139
+ it 'given an unknown key it raises error' do
140
+ expect { publisher.subscribe(listener, broadcaster: :foobar) }.to raise_error(KeyError, /broadcaster not found/)
141
+ end
142
+
143
+ it 'given nothing it uses the default broadcaster' do
144
+ Wisper.configure { |c| c.broadcaster(:default, broadcaster) }
145
+ publisher.subscribe(listener)
146
+ expect(broadcaster).to receive('broadcast')
147
+ publisher.send(:broadcast, event_name)
148
+ end
149
+
150
+ describe 'async alias' do
151
+ it 'given an object which responds_to broadcast it uses object' do
152
+ publisher.subscribe(listener, async: broadcaster)
153
+ expect(broadcaster).to receive('broadcast')
154
+ publisher.send(:broadcast, event_name)
155
+ end
156
+
157
+ it 'given true it uses configured async broadcaster' do
158
+ Wisper.configure { |c| c.broadcaster(:async, broadcaster) }
159
+ publisher.subscribe(listener, async: true)
160
+ expect(broadcaster).to receive('broadcast')
161
+ publisher.send(:broadcast, event_name)
162
+ end
163
+
164
+ it 'given false it uses configured default broadcaster' do
165
+ Wisper.configure { |c| c.broadcaster(:default, broadcaster) }
166
+ publisher.subscribe(listener, async: false)
167
+ expect(broadcaster).to receive('broadcast')
168
+ publisher.send(:broadcast, event_name)
169
+ end
170
+ end
171
+ end
172
+
118
173
  it 'returns publisher so methods can be chained' do
119
174
  expect(publisher.add_listener(listener, :on => 'so_did_this')).to \
120
175
  eq publisher
@@ -0,0 +1,14 @@
1
+ describe Wisper::ObjectRegistration do
2
+
3
+ describe 'broadcaster' do
4
+ it 'defaults to SendBroadcaster' do
5
+ subject = Wisper::ObjectRegistration.new(double('listener'), {})
6
+ expect(subject.broadcaster).to be_instance_of(Wisper::Broadcasters::SendBroadcaster)
7
+ end
8
+
9
+ it 'default is lazily evaluated' do
10
+ expect(Wisper::Broadcasters::SendBroadcaster).to_not receive :new
11
+ Wisper::ObjectRegistration.new(double('listener'), broadcaster: double('DifferentBroadcaster').as_null_object)
12
+ end
13
+ end
14
+ end
@@ -1,4 +1,3 @@
1
- require 'spec_helper'
2
1
  require 'wisper/rspec/matchers'
3
2
 
4
3
  RSpec::configure do |config|
@@ -1,5 +1,3 @@
1
- require 'spec_helper'
2
-
3
1
  describe Wisper do
4
2
  before do
5
3
  # assign the stderr to a new StringIO to test errors
@@ -79,4 +77,18 @@ describe Wisper do
79
77
  end
80
78
  end
81
79
  end
80
+
81
+ it '.configuration returns configuration' do
82
+ expect(Wisper.configuration).to be_an_instance_of(Wisper::Configuration)
83
+ end
84
+
85
+ it '.configure yields block to configuration' do
86
+ Wisper.configure do |config|
87
+ expect(config).to be_an_instance_of(Wisper::Configuration)
88
+ end
89
+ end
90
+
91
+ it 'has a default broadcaster' do
92
+ expect(Wisper.configuration.broadcasters[:default]).to be_instance_of(Wisper::Broadcasters::SendBroadcaster)
93
+ end
82
94
  end
metadata CHANGED
@@ -1,36 +1,35 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: wisper
3
- version: !ruby/object:Gem::Version
4
- version: 1.4.0
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.5.0
5
5
  platform: ruby
6
- authors:
6
+ authors:
7
7
  - Kris Leech
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
-
12
- date: 2014-09-08 00:00:00 Z
11
+ date: 2014-10-06 00:00:00.000000000 Z
13
12
  dependencies: []
14
-
15
13
  description: pub/sub for Ruby objects
16
- email:
14
+ email:
17
15
  - kris.leech@gmail.com
18
16
  executables: []
19
-
20
17
  extensions: []
21
-
22
18
  extra_rdoc_files: []
23
-
24
- files:
25
- - .gitignore
26
- - .rspec
27
- - .travis.yml
19
+ files:
20
+ - ".gitignore"
21
+ - ".rspec"
22
+ - ".ruby-version"
23
+ - ".travis.yml"
28
24
  - CHANGELOG.md
29
25
  - Gemfile
30
26
  - Guardfile
31
27
  - README.md
32
28
  - Rakefile
33
29
  - lib/wisper.rb
30
+ - lib/wisper/broadcasters/logger_broadcaster.rb
31
+ - lib/wisper/broadcasters/send_broadcaster.rb
32
+ - lib/wisper/configuration.rb
34
33
  - lib/wisper/global_listeners.rb
35
34
  - lib/wisper/publisher.rb
36
35
  - lib/wisper/registration/block.rb
@@ -40,50 +39,54 @@ files:
40
39
  - lib/wisper/rspec/stub_wisper_publisher.rb
41
40
  - lib/wisper/temporary_listeners.rb
42
41
  - lib/wisper/version.rb
43
- - spec/lib/async_spec.rb
44
42
  - spec/lib/global_subscribers_spec.rb
45
43
  - spec/lib/integration_spec.rb
46
44
  - spec/lib/simple_example_spec.rb
47
45
  - spec/lib/temporary_global_listeners_spec.rb
46
+ - spec/lib/wisper/broadcasters/logger_broadcaster_spec.rb
47
+ - spec/lib/wisper/broadcasters/send_broadcaster_spec.rb
48
+ - spec/lib/wisper/configuration_spec.rb
48
49
  - spec/lib/wisper/publisher_spec.rb
50
+ - spec/lib/wisper/registrations/object_spec.rb
49
51
  - spec/lib/wisper/rspec/matchers_spec.rb
50
52
  - spec/lib/wisper/rspec/stub_wisper_publisher_spec.rb
51
53
  - spec/lib/wisper_spec.rb
52
54
  - spec/spec_helper.rb
53
55
  - wisper.gemspec
54
56
  homepage: https://github.com/krisleech/wisper
55
- licenses:
57
+ licenses:
56
58
  - MIT
57
59
  metadata: {}
58
-
59
60
  post_install_message:
60
61
  rdoc_options: []
61
-
62
- require_paths:
62
+ require_paths:
63
63
  - lib
64
- required_ruby_version: !ruby/object:Gem::Requirement
65
- requirements:
66
- - &id001
67
- - ">="
68
- - !ruby/object:Gem::Version
69
- version: "0"
70
- required_rubygems_version: !ruby/object:Gem::Requirement
71
- requirements:
72
- - *id001
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
73
74
  requirements: []
74
-
75
75
  rubyforge_project:
76
76
  rubygems_version: 2.2.2
77
77
  signing_key:
78
78
  specification_version: 4
79
79
  summary: pub/sub for Ruby objects
80
- test_files:
81
- - spec/lib/async_spec.rb
80
+ test_files:
82
81
  - spec/lib/global_subscribers_spec.rb
83
82
  - spec/lib/integration_spec.rb
84
83
  - spec/lib/simple_example_spec.rb
85
84
  - spec/lib/temporary_global_listeners_spec.rb
85
+ - spec/lib/wisper/broadcasters/logger_broadcaster_spec.rb
86
+ - spec/lib/wisper/broadcasters/send_broadcaster_spec.rb
87
+ - spec/lib/wisper/configuration_spec.rb
86
88
  - spec/lib/wisper/publisher_spec.rb
89
+ - spec/lib/wisper/registrations/object_spec.rb
87
90
  - spec/lib/wisper/rspec/matchers_spec.rb
88
91
  - spec/lib/wisper/rspec/stub_wisper_publisher_spec.rb
89
92
  - spec/lib/wisper_spec.rb
@@ -1,10 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe 'async option' do
4
- let(:listener) { double('listener') }
5
- let(:publisher) { publisher_class.new }
6
-
7
- it 'it raises a deprecation exception' do
8
- expect { publisher.add_listener(listener, :async => true) }.to raise_error
9
- end
10
- end