adhearsion 2.0.0.beta1 → 2.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +2 -4
- data/CHANGELOG.md +34 -4
- data/README.markdown +2 -1
- data/Rakefile +22 -1
- data/adhearsion.gemspec +1 -0
- data/bin/ahn +0 -2
- data/features/cli_daemon.feature +2 -0
- data/features/cli_restart.feature +19 -0
- data/features/cli_start.feature +4 -6
- data/features/cli_stop.feature +3 -0
- data/features/step_definitions/app_generator_steps.rb +2 -0
- data/features/step_definitions/cli_steps.rb +2 -0
- data/features/support/aruba_helper.rb +2 -0
- data/features/support/env.rb +8 -46
- data/features/support/utils.rb +2 -0
- data/lib/adhearsion.rb +4 -6
- data/lib/adhearsion/call.rb +71 -17
- data/lib/adhearsion/call_controller.rb +25 -14
- data/lib/adhearsion/call_controller/dial.rb +34 -15
- data/lib/adhearsion/call_controller/input.rb +186 -144
- data/lib/adhearsion/call_controller/output.rb +10 -6
- data/lib/adhearsion/call_controller/record.rb +11 -13
- data/lib/adhearsion/call_controller/utility.rb +2 -0
- data/lib/adhearsion/calls.rb +4 -2
- data/lib/adhearsion/cli.rb +4 -0
- data/lib/adhearsion/cli_commands.rb +8 -2
- data/lib/adhearsion/configuration.rb +7 -3
- data/lib/adhearsion/console.rb +17 -17
- data/lib/adhearsion/events.rb +10 -4
- data/lib/adhearsion/foundation.rb +9 -0
- data/lib/adhearsion/foundation/custom_daemonizer.rb +3 -1
- data/lib/adhearsion/foundation/exception_handler.rb +2 -0
- data/lib/adhearsion/foundation/libc.rb +2 -0
- data/lib/adhearsion/foundation/object.rb +3 -0
- data/lib/adhearsion/foundation/thread_safety.rb +5 -11
- data/lib/adhearsion/generators.rb +2 -0
- data/lib/adhearsion/generators/app/app_generator.rb +2 -0
- data/lib/adhearsion/generators/app/templates/README.md +9 -0
- data/lib/adhearsion/generators/app/templates/config/adhearsion.rb +38 -16
- data/lib/adhearsion/generators/app/templates/config/environment.rb +2 -0
- data/lib/adhearsion/generators/app/templates/lib/simon_game.rb +5 -3
- data/lib/adhearsion/generators/controller/controller_generator.rb +2 -0
- data/lib/adhearsion/generators/controller/templates/lib/controller.rb +2 -0
- data/lib/adhearsion/generators/controller/templates/spec/controller_spec.rb +2 -0
- data/lib/adhearsion/generators/generator.rb +3 -1
- data/lib/adhearsion/generators/plugin/plugin_generator.rb +2 -0
- data/lib/adhearsion/initializer.rb +31 -17
- data/lib/adhearsion/linux_proc_name.rb +2 -0
- data/lib/adhearsion/logging.rb +5 -3
- data/lib/adhearsion/menu_dsl.rb +2 -0
- data/lib/adhearsion/menu_dsl/calculated_match.rb +2 -0
- data/lib/adhearsion/menu_dsl/calculated_match_collection.rb +2 -0
- data/lib/adhearsion/menu_dsl/fixnum_match_calculator.rb +2 -0
- data/lib/adhearsion/menu_dsl/match_calculator.rb +2 -0
- data/lib/adhearsion/menu_dsl/menu.rb +58 -4
- data/lib/adhearsion/menu_dsl/menu_builder.rb +14 -1
- data/lib/adhearsion/menu_dsl/range_match_calculator.rb +4 -1
- data/lib/adhearsion/menu_dsl/string_match_calculator.rb +2 -0
- data/lib/adhearsion/outbound_call.rb +2 -0
- data/lib/adhearsion/plugin.rb +9 -7
- data/lib/adhearsion/plugin/collection.rb +3 -1
- data/lib/adhearsion/plugin/initializer.rb +3 -1
- data/lib/adhearsion/process.rb +8 -2
- data/lib/adhearsion/punchblock_plugin.rb +3 -1
- data/lib/adhearsion/punchblock_plugin/initializer.rb +34 -11
- data/lib/adhearsion/router.rb +4 -2
- data/lib/adhearsion/router/route.rb +2 -0
- data/lib/adhearsion/script_ahn_loader.rb +2 -0
- data/lib/adhearsion/tasks.rb +2 -0
- data/lib/adhearsion/tasks/configuration.rb +2 -0
- data/lib/adhearsion/tasks/debugging.rb +8 -0
- data/lib/adhearsion/tasks/environment.rb +2 -0
- data/lib/adhearsion/tasks/plugins.rb +2 -0
- data/lib/adhearsion/tasks/testing.rb +2 -0
- data/lib/adhearsion/version.rb +3 -1
- data/pre-commit +2 -0
- data/spec/adhearsion/call_controller/dial_spec.rb +114 -25
- data/spec/adhearsion/call_controller/input_spec.rb +192 -169
- data/spec/adhearsion/call_controller/output_spec.rb +26 -12
- data/spec/adhearsion/call_controller/record_spec.rb +29 -77
- data/spec/adhearsion/call_controller/utility_spec.rb +69 -0
- data/spec/adhearsion/call_controller_spec.rb +90 -15
- data/spec/adhearsion/call_spec.rb +92 -24
- data/spec/adhearsion/calls_spec.rb +9 -7
- data/spec/adhearsion/configuration_spec.rb +58 -56
- data/spec/adhearsion/console_spec.rb +4 -2
- data/spec/adhearsion/events_spec.rb +9 -7
- data/spec/adhearsion/generators_spec.rb +3 -1
- data/spec/adhearsion/initializer_spec.rb +16 -14
- data/spec/adhearsion/logging_spec.rb +11 -9
- data/spec/adhearsion/menu_dsl/calculated_match_collection_spec.rb +6 -4
- data/spec/adhearsion/menu_dsl/calculated_match_spec.rb +6 -4
- data/spec/adhearsion/menu_dsl/fixnum_match_calculator_spec.rb +3 -1
- data/spec/adhearsion/menu_dsl/match_calculator_spec.rb +2 -0
- data/spec/adhearsion/menu_dsl/menu_builder_spec.rb +42 -11
- data/spec/adhearsion/menu_dsl/menu_spec.rb +197 -36
- data/spec/adhearsion/menu_dsl/range_match_calculator_spec.rb +4 -2
- data/spec/adhearsion/menu_dsl/string_match_calculator_spec.rb +5 -3
- data/spec/adhearsion/outbound_call_spec.rb +7 -5
- data/spec/adhearsion/plugin_spec.rb +19 -15
- data/spec/adhearsion/process_spec.rb +12 -7
- data/spec/adhearsion/punchblock_plugin/initializer_spec.rb +35 -15
- data/spec/adhearsion/punchblock_plugin_spec.rb +4 -1
- data/spec/adhearsion/router/route_spec.rb +8 -6
- data/spec/adhearsion/router_spec.rb +12 -10
- data/spec/adhearsion_spec.rb +13 -2
- data/spec/capture_warnings.rb +33 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/call_controller_test_helpers.rb +2 -4
- data/spec/support/initializer_stubs.rb +8 -5
- data/spec/support/logging_helpers.rb +2 -0
- data/spec/support/punchblock_mocks.rb +2 -0
- metadata +84 -71
- data/EVENTS +0 -11
- data/lib/adhearsion/call_controller/menu.rb +0 -124
- data/lib/adhearsion/foundation/all.rb +0 -8
- data/spec/adhearsion/call_controller/menu_spec.rb +0 -120
- data/spec/adhearsion/menu_dsl_spec.rb +0 -12
@@ -1,27 +1,28 @@
|
|
1
|
-
#
|
2
|
-
# - Execute rake adhearsion:config:desc to get the configuration options
|
3
|
-
# - Execute rake adhearsion:config:show to get the configuration values
|
4
|
-
#
|
5
|
-
# To update a plugin configuration you can write either:
|
6
|
-
#
|
7
|
-
# * Option 1
|
8
|
-
# Adhearsion.config.<plugin-name> do |config|
|
9
|
-
# config.<key> = <value>
|
10
|
-
# end
|
11
|
-
#
|
12
|
-
# * Option 2
|
13
|
-
# Adhearsion.config do |config|
|
14
|
-
# config.<plugin-name>.<key> = <value>
|
15
|
-
# end
|
1
|
+
# encoding: utf-8
|
16
2
|
|
17
3
|
Adhearsion.config do |config|
|
18
4
|
|
5
|
+
# Centralized way to specify any Adhearsion platform or plugin configuration
|
6
|
+
# - Execute rake config:show to view the active configuration values
|
7
|
+
#
|
8
|
+
# To update a plugin configuration you can write either:
|
9
|
+
#
|
10
|
+
# * Option 1
|
11
|
+
# Adhearsion.config.<plugin-name> do |config|
|
12
|
+
# config.<key> = <value>
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# * Option 2
|
16
|
+
# Adhearsion.config do |config|
|
17
|
+
# config.<plugin-name>.<key> = <value>
|
18
|
+
# end
|
19
|
+
|
19
20
|
config.development do |dev|
|
20
21
|
dev.platform.logging.level = :debug
|
21
22
|
end
|
22
23
|
|
23
24
|
##
|
24
|
-
# Use with Voxeo PRISM
|
25
|
+
# Use with Rayo (eg Voxeo PRISM)
|
25
26
|
#
|
26
27
|
# config.punchblock.username = "" # Your XMPP JID for use with Rayo
|
27
28
|
# config.punchblock.password = "" # Your XMPP password
|
@@ -36,6 +37,27 @@ Adhearsion.config do |config|
|
|
36
37
|
# config.punchblock.port = 5038 # Your AMI port
|
37
38
|
end
|
38
39
|
|
40
|
+
Adhearsion::Events.draw do
|
41
|
+
|
42
|
+
# Register global handlers for events
|
43
|
+
#
|
44
|
+
# eg. Handling Punchblock events
|
45
|
+
# punchblock do |event|
|
46
|
+
# ...
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# eg Handling PeerStatus AMI events
|
50
|
+
# ami :name => 'PeerStatus' do |event|
|
51
|
+
# ...
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
end
|
55
|
+
|
39
56
|
Adhearsion.router do
|
57
|
+
|
58
|
+
#
|
59
|
+
# Specify your call routes, directing calls with particular attributes to a controller
|
60
|
+
#
|
61
|
+
|
40
62
|
route 'default', SimonGame
|
41
63
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
class SimonGame < Adhearsion::CallController
|
2
4
|
def run
|
3
5
|
answer
|
@@ -19,7 +21,7 @@ class SimonGame < Adhearsion::CallController
|
|
19
21
|
|
20
22
|
def say_number
|
21
23
|
update_number
|
22
|
-
|
24
|
+
say @number
|
23
25
|
end
|
24
26
|
|
25
27
|
def collect_attempt
|
@@ -28,9 +30,9 @@ class SimonGame < Adhearsion::CallController
|
|
28
30
|
|
29
31
|
def verify_attempt
|
30
32
|
if attempt_correct?
|
31
|
-
|
33
|
+
say 'good'
|
32
34
|
else
|
33
|
-
|
35
|
+
say "#{@number.length - 1} times wrong, try again smarty"
|
34
36
|
reset
|
35
37
|
end
|
36
38
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
begin
|
2
4
|
require 'thor/group'
|
3
5
|
rescue LoadError
|
@@ -66,7 +68,7 @@ module Adhearsion
|
|
66
68
|
def self.generator_name
|
67
69
|
@generator_name ||= begin
|
68
70
|
if generator = name.to_s.split('::').last
|
69
|
-
generator.sub!
|
71
|
+
generator.sub!(/Generator$/, '')
|
70
72
|
generator.underscore
|
71
73
|
end
|
72
74
|
end
|
@@ -1,5 +1,8 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require 'adhearsion/punchblock_plugin'
|
2
4
|
require 'adhearsion/linux_proc_name'
|
5
|
+
require 'rbconfig'
|
3
6
|
|
4
7
|
module Adhearsion
|
5
8
|
class Initializer
|
@@ -41,6 +44,7 @@ module Adhearsion
|
|
41
44
|
initialize_log_paths
|
42
45
|
daemonize! if should_daemonize?
|
43
46
|
start_logging
|
47
|
+
debugging_log
|
44
48
|
launch_console if need_console?
|
45
49
|
catch_termination_signal
|
46
50
|
create_pid_file
|
@@ -52,10 +56,9 @@ module Adhearsion
|
|
52
56
|
run_plugins
|
53
57
|
trigger_after_initialized_hooks
|
54
58
|
|
55
|
-
if Adhearsion.status == :booting
|
56
|
-
|
57
|
-
|
58
|
-
end
|
59
|
+
Adhearsion::Process.booted if Adhearsion.status == :booting
|
60
|
+
|
61
|
+
logger.info "Adhearsion v#{Adhearsion::VERSION} initialized in \"#{Adhearsion.config.platform.environment}\"!" if Adhearsion.status == :running
|
59
62
|
|
60
63
|
# This method will block until all important threads have finished.
|
61
64
|
# When it does, the process will exit.
|
@@ -63,25 +66,37 @@ module Adhearsion
|
|
63
66
|
self
|
64
67
|
end
|
65
68
|
|
69
|
+
def debugging_items
|
70
|
+
[
|
71
|
+
"OS: #{RbConfig::CONFIG['host_os']} - RUBY: #{RUBY_ENGINE} #{RUBY_VERSION}",
|
72
|
+
"Environment: #{ENV.inspect}",
|
73
|
+
Adhearsion.config.description(:all),
|
74
|
+
"Gem versions: #{Gem.loaded_specs.inject([]) { |c,g| c << "#{g[0]} #{g[1].version}" }}"
|
75
|
+
]
|
76
|
+
end
|
77
|
+
|
78
|
+
def debugging_log
|
79
|
+
debugging_items.each do |item|
|
80
|
+
logger.trace item
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
66
84
|
def update_rails_env_var
|
67
85
|
env = ENV['AHN_ENV']
|
68
86
|
if env && Adhearsion.config.valid_environment?(env.to_sym)
|
69
|
-
|
70
|
-
logger.info "
|
71
|
-
else
|
72
|
-
logger.warn "Setting RAILS_ENV variable to <#{env}>"
|
87
|
+
unless ENV['RAILS_ENV']
|
88
|
+
logger.info "Copying AHN_ENV (#{env}) to RAILS_ENV"
|
73
89
|
ENV['RAILS_ENV'] = env
|
74
90
|
end
|
75
91
|
else
|
76
|
-
|
77
|
-
if env
|
78
|
-
logger.info "Using the configured value for RAILS_ENV : <#{env}>"
|
79
|
-
else
|
92
|
+
unless ENV['RAILS_ENV']
|
80
93
|
env = Adhearsion.config.platform.environment.to_s
|
81
|
-
|
94
|
+
ENV['AHN_ENV'] = env
|
95
|
+
logger.info "Setting RAILS_ENV to \"#{env}\""
|
82
96
|
ENV['RAILS_ENV'] = env
|
83
97
|
end
|
84
98
|
end
|
99
|
+
logger.warn "AHN_ENV(#{ENV['AHN_ENV']}) does not match RAILS_ENV(#{ENV['RAILS_ENV']})!" unless ENV['RAILS_ENV'] == ENV['AHN_ENV']
|
85
100
|
env
|
86
101
|
end
|
87
102
|
|
@@ -202,7 +217,7 @@ module Adhearsion
|
|
202
217
|
|
203
218
|
def daemonize!
|
204
219
|
logger.info "Daemonizing now!"
|
205
|
-
logger.
|
220
|
+
logger.debug "Creating PID file #{pid_file}"
|
206
221
|
extend Adhearsion::CustomDaemonizer
|
207
222
|
daemonize resolve_log_file_path
|
208
223
|
end
|
@@ -211,7 +226,6 @@ module Adhearsion
|
|
211
226
|
Adhearsion::Process.important_threads << Thread.new do
|
212
227
|
catching_standard_errors do
|
213
228
|
Adhearsion::Console.run
|
214
|
-
Adhearsion::Process.shutdown
|
215
229
|
end
|
216
230
|
end
|
217
231
|
end
|
@@ -241,8 +255,8 @@ module Adhearsion
|
|
241
255
|
end
|
242
256
|
|
243
257
|
def initialize_exception_logger
|
244
|
-
Events.register_handler :exception do |e|
|
245
|
-
logger.error e
|
258
|
+
Events.register_handler :exception do |e, l|
|
259
|
+
(l || logger).error e
|
246
260
|
end
|
247
261
|
end
|
248
262
|
|
data/lib/adhearsion/logging.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require 'logging'
|
2
4
|
|
3
5
|
module Adhearsion
|
@@ -11,14 +13,14 @@ module Adhearsion
|
|
11
13
|
|
12
14
|
::Logging.color_scheme 'bright',
|
13
15
|
:levels => {
|
16
|
+
:debug => :magenta,
|
14
17
|
:info => :green,
|
15
18
|
:warn => :yellow,
|
16
19
|
:error => :red,
|
17
20
|
:fatal => [:white, :on_red]
|
18
21
|
},
|
19
|
-
:date => :blue,
|
20
|
-
:logger => :cyan
|
21
|
-
:message => :magenta
|
22
|
+
:date => [:bold, :blue],
|
23
|
+
:logger => :cyan
|
22
24
|
|
23
25
|
def adhearsion_pattern
|
24
26
|
'[%d] %-5l %c: %m\n'
|
data/lib/adhearsion/menu_dsl.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
module Adhearsion
|
2
4
|
module MenuDSL
|
3
5
|
|
@@ -6,21 +8,40 @@ module Adhearsion
|
|
6
8
|
DEFAULT_MAX_NUMBER_OF_TRIES = 1
|
7
9
|
DEFAULT_TIMEOUT = 5
|
8
10
|
|
9
|
-
|
11
|
+
InvalidStructureError = Class.new StandardError
|
12
|
+
|
13
|
+
attr_reader :builder, :timeout, :tries_count, :max_number_of_tries, :terminator, :limit, :interruptible, :status
|
10
14
|
|
11
15
|
def initialize(options = {}, &block)
|
12
16
|
@tries_count = 0 # Counts the number of tries the menu's been executed
|
13
17
|
@timeout = options[:timeout] || DEFAULT_TIMEOUT
|
14
18
|
@max_number_of_tries = options[:tries] || DEFAULT_MAX_NUMBER_OF_TRIES
|
19
|
+
@terminator = options[:terminator].to_s
|
20
|
+
@limit = options[:limit]
|
21
|
+
@interruptible = options.has_key?(:interruptible) ? options[:interruptible] : true
|
15
22
|
@builder = MenuDSL::MenuBuilder.new
|
23
|
+
@terminated = false
|
16
24
|
|
17
|
-
@builder.build
|
25
|
+
@builder.build(&block) if block
|
18
26
|
|
19
27
|
initialize_digit_buffer
|
20
28
|
end
|
21
29
|
|
30
|
+
def validate(mode = nil)
|
31
|
+
case mode
|
32
|
+
when :basic
|
33
|
+
@terminator.present? || !!@limit || raise(InvalidStructureError, "You must specify at least one of limit or terminator")
|
34
|
+
else
|
35
|
+
@builder.has_matchers? || raise(InvalidStructureError, "You must specify one or more matchers")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
22
39
|
def <<(other)
|
23
|
-
|
40
|
+
if other == terminator
|
41
|
+
@terminated = true
|
42
|
+
else
|
43
|
+
digit_buffer << other
|
44
|
+
end
|
24
45
|
end
|
25
46
|
|
26
47
|
def digit_buffer
|
@@ -30,6 +51,7 @@ module Adhearsion
|
|
30
51
|
def digit_buffer_string
|
31
52
|
digit_buffer.to_s
|
32
53
|
end
|
54
|
+
alias :result :digit_buffer_string
|
33
55
|
|
34
56
|
def digit_buffer_empty?
|
35
57
|
digit_buffer.empty?
|
@@ -38,6 +60,11 @@ module Adhearsion
|
|
38
60
|
def continue
|
39
61
|
return get_another_digit_or_timeout! if digit_buffer_empty?
|
40
62
|
|
63
|
+
return menu_terminated! if @terminated
|
64
|
+
return menu_limit_reached! if limit && digit_buffer.size >= limit
|
65
|
+
|
66
|
+
return menu_validator_terminated! if execute_validator_hook
|
67
|
+
|
41
68
|
calculated_matches = builder.calculate_matches_for digit_buffer_string
|
42
69
|
|
43
70
|
if calculated_matches.exact_match_count >= 1
|
@@ -47,7 +74,7 @@ module Adhearsion
|
|
47
74
|
else
|
48
75
|
get_another_digit_or_finish! first_exact_match.match_payload, first_exact_match.query
|
49
76
|
end
|
50
|
-
elsif calculated_matches.potential_match_count >= 1
|
77
|
+
elsif calculated_matches.potential_match_count >= 1 || !@builder.has_matchers?
|
51
78
|
get_another_digit_or_timeout!
|
52
79
|
else
|
53
80
|
invalid!
|
@@ -75,6 +102,10 @@ module Adhearsion
|
|
75
102
|
builder.execute_hook_for :failure, digit_buffer_string
|
76
103
|
end
|
77
104
|
|
105
|
+
def execute_validator_hook
|
106
|
+
builder.execute_hook_for :validator, digit_buffer_string
|
107
|
+
end
|
108
|
+
|
78
109
|
protected
|
79
110
|
|
80
111
|
# If you're using a more complex class in subclasses, you may want to override this method in addition to the
|
@@ -84,18 +115,37 @@ module Adhearsion
|
|
84
115
|
end
|
85
116
|
|
86
117
|
def invalid!
|
118
|
+
@status = :invalid
|
87
119
|
MenuResultInvalid.new
|
88
120
|
end
|
89
121
|
|
90
122
|
def menu_result_found!(match_object, new_extension)
|
123
|
+
@status = :matched
|
91
124
|
MenuResultFound.new(match_object, new_extension)
|
92
125
|
end
|
93
126
|
|
127
|
+
def menu_terminated!
|
128
|
+
@status = :terminated
|
129
|
+
MenuTerminated.new
|
130
|
+
end
|
131
|
+
|
132
|
+
def menu_validator_terminated!
|
133
|
+
@status = :validator_terminated
|
134
|
+
MenuValidatorTerminated.new
|
135
|
+
end
|
136
|
+
|
137
|
+
def menu_limit_reached!
|
138
|
+
@status = :limited
|
139
|
+
MenuLimitReached.new
|
140
|
+
end
|
141
|
+
|
94
142
|
def get_another_digit_or_finish!(match_payload, new_extension)
|
143
|
+
@status = :multi_matched
|
95
144
|
MenuGetAnotherDigitOrFinish.new(match_payload, new_extension)
|
96
145
|
end
|
97
146
|
|
98
147
|
def get_another_digit_or_timeout!
|
148
|
+
@status = :potential
|
99
149
|
MenuGetAnotherDigitOrTimeout.new
|
100
150
|
end
|
101
151
|
|
@@ -128,6 +178,10 @@ module Adhearsion
|
|
128
178
|
|
129
179
|
MenuResultInvalid = Class.new MenuResult
|
130
180
|
|
181
|
+
MenuTerminated = Class.new MenuResultDone
|
182
|
+
MenuValidatorTerminated = Class.new MenuResultDone
|
183
|
+
MenuLimitReached = Class.new MenuResultDone
|
184
|
+
|
131
185
|
# For our default purpose, we need the digit_buffer to behave much like a normal String except that it should
|
132
186
|
# handle its own resetting (clearing)
|
133
187
|
class ClearableStringBuffer < String
|