adhearsion 0.7.7 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +9 -42
- data/EVENTS +11 -0
- data/README.txt +5 -0
- data/Rakefile +94 -84
- data/adhearsion.gemspec +148 -0
- data/app_generators/ahn/USAGE +5 -0
- data/app_generators/ahn/ahn_generator.rb +87 -0
- data/app_generators/ahn/templates/.ahnrc +34 -0
- data/app_generators/ahn/templates/README +8 -0
- data/app_generators/ahn/templates/Rakefile +23 -0
- data/app_generators/ahn/templates/components/ami_remote/ami_remote.rb +15 -0
- data/app_generators/ahn/templates/components/disabled/HOW_TO_ENABLE +7 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +47 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/config.yml +12 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb +34 -0
- data/app_generators/ahn/templates/components/restful_rpc/README.markdown +11 -0
- data/app_generators/ahn/templates/components/restful_rpc/config.yml +34 -0
- data/app_generators/ahn/templates/components/restful_rpc/example-client.rb +48 -0
- data/app_generators/ahn/templates/components/restful_rpc/restful_rpc.rb +87 -0
- data/app_generators/ahn/templates/components/restful_rpc/spec/restful_rpc_spec.rb +263 -0
- data/app_generators/ahn/templates/components/simon_game/simon_game.rb +56 -0
- data/app_generators/ahn/templates/config/startup.rb +50 -0
- data/app_generators/ahn/templates/dialplan.rb +3 -0
- data/app_generators/ahn/templates/events.rb +32 -0
- data/bin/ahn +28 -0
- data/bin/ahnctl +68 -0
- data/bin/jahn +42 -0
- data/examples/asterisk_manager_interface/standalone.rb +51 -0
- data/lib/adhearsion.rb +35 -953
- data/lib/adhearsion/cli.rb +223 -0
- data/lib/adhearsion/component_manager.rb +208 -0
- data/lib/adhearsion/component_manager/component_tester.rb +55 -0
- data/lib/adhearsion/component_manager/spec_framework.rb +24 -0
- data/lib/adhearsion/events_support.rb +84 -0
- data/lib/adhearsion/foundation/all.rb +9 -0
- data/lib/adhearsion/foundation/blank_slate.rb +5 -0
- data/lib/adhearsion/foundation/custom_daemonizer.rb +45 -0
- data/lib/adhearsion/foundation/event_socket.rb +203 -0
- data/lib/adhearsion/foundation/future_resource.rb +36 -0
- data/lib/adhearsion/foundation/global.rb +1 -0
- data/lib/adhearsion/foundation/metaprogramming.rb +17 -0
- data/lib/adhearsion/foundation/numeric.rb +13 -0
- data/lib/adhearsion/foundation/pseudo_guid.rb +10 -0
- data/lib/adhearsion/foundation/relationship_properties.rb +42 -0
- data/lib/adhearsion/foundation/string.rb +26 -0
- data/lib/adhearsion/foundation/synchronized_hash.rb +96 -0
- data/lib/adhearsion/foundation/thread_safety.rb +7 -0
- data/lib/adhearsion/host_definitions.rb +67 -0
- data/lib/adhearsion/initializer.rb +373 -0
- data/lib/adhearsion/initializer/asterisk.rb +81 -0
- data/lib/adhearsion/initializer/configuration.rb +254 -0
- data/lib/adhearsion/initializer/database.rb +49 -0
- data/lib/adhearsion/initializer/drb.rb +31 -0
- data/lib/adhearsion/initializer/freeswitch.rb +22 -0
- data/lib/adhearsion/initializer/rails.rb +40 -0
- data/lib/adhearsion/logging.rb +92 -0
- data/lib/adhearsion/tasks.rb +16 -0
- data/lib/adhearsion/tasks/database.rb +5 -0
- data/lib/adhearsion/tasks/deprecations.rb +59 -0
- data/lib/adhearsion/tasks/generating.rb +20 -0
- data/lib/adhearsion/tasks/lint.rb +4 -0
- data/lib/adhearsion/tasks/testing.rb +37 -0
- data/lib/adhearsion/version.rb +9 -0
- data/lib/adhearsion/voip/asterisk.rb +4 -0
- data/lib/adhearsion/voip/asterisk/agi_server.rb +81 -0
- data/lib/adhearsion/voip/asterisk/commands.rb +1284 -0
- data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
- data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +101 -0
- data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
- data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
- data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
- data/lib/adhearsion/voip/asterisk/manager_interface.rb +562 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +1754 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +286 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +78 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +87 -0
- data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
- data/lib/adhearsion/voip/asterisk/super_manager.rb +19 -0
- data/lib/adhearsion/voip/call.rb +440 -0
- data/lib/adhearsion/voip/call_routing.rb +64 -0
- data/lib/adhearsion/voip/commands.rb +9 -0
- data/lib/adhearsion/voip/constants.rb +39 -0
- data/lib/adhearsion/voip/conveniences.rb +18 -0
- data/lib/adhearsion/voip/dial_plan.rb +218 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
- data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
- data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
- data/lib/adhearsion/voip/dsl/dialplan/parser.rb +71 -0
- data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
- data/lib/adhearsion/voip/dsl/numerical_string.rb +117 -0
- data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
- data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
- data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
- data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
- data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
- data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
- data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
- data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +58 -0
- data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
- data/lib/theatre.rb +151 -0
- data/lib/theatre/README.markdown +64 -0
- data/lib/theatre/callback_definition_loader.rb +84 -0
- data/lib/theatre/guid.rb +23 -0
- data/lib/theatre/invocation.rb +121 -0
- data/lib/theatre/namespace_manager.rb +153 -0
- data/lib/theatre/version.rb +2 -0
- metadata +160 -140
- data/.version +0 -1
- data/TODO +0 -71
- data/ahn +0 -223
- data/apps/default/Rakefile +0 -65
- data/apps/default/config/adhearsion.sqlite3 +0 -0
- data/apps/default/config/adhearsion.yml +0 -95
- data/apps/default/config/database.rb +0 -50
- data/apps/default/config/database.yml +0 -12
- data/apps/default/config/helpers/drb_server.yml +0 -43
- data/apps/default/config/helpers/factorial.alien.c.yml +0 -1
- data/apps/default/config/helpers/growler.yml +0 -21
- data/apps/default/config/helpers/lookup.yml +0 -1
- data/apps/default/config/helpers/manager_proxy.yml +0 -8
- data/apps/default/config/helpers/micromenus.yml +0 -1
- data/apps/default/config/helpers/micromenus/collab.rb +0 -60
- data/apps/default/config/helpers/micromenus/images/arrow-off.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/arrow-on.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/error.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/folder-off.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/folder-on.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/folder.png +0 -0
- data/apps/default/config/helpers/micromenus/images/ggbridge.jpg +0 -0
- data/apps/default/config/helpers/micromenus/images/green.png +0 -0
- data/apps/default/config/helpers/micromenus/images/microbrowser.bg.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/red.png +0 -0
- data/apps/default/config/helpers/micromenus/images/tux.bmp +0 -0
- data/apps/default/config/helpers/micromenus/images/url-off.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/url-on.gif +0 -0
- data/apps/default/config/helpers/micromenus/images/yellow.png +0 -0
- data/apps/default/config/helpers/micromenus/javascripts/animation.js +0 -1341
- data/apps/default/config/helpers/micromenus/javascripts/carousel.js +0 -1238
- data/apps/default/config/helpers/micromenus/javascripts/columnav.js +0 -306
- data/apps/default/config/helpers/micromenus/javascripts/connection.js +0 -965
- data/apps/default/config/helpers/micromenus/javascripts/container.js +0 -4727
- data/apps/default/config/helpers/micromenus/javascripts/container_core.js +0 -2915
- data/apps/default/config/helpers/micromenus/javascripts/dom.js +0 -892
- data/apps/default/config/helpers/micromenus/javascripts/dragdrop.js +0 -2958
- data/apps/default/config/helpers/micromenus/javascripts/event.js +0 -1771
- data/apps/default/config/helpers/micromenus/javascripts/yahoo.js +0 -433
- data/apps/default/config/helpers/micromenus/stylesheets/carousel.css +0 -78
- data/apps/default/config/helpers/micromenus/stylesheets/columnav.css +0 -135
- data/apps/default/config/helpers/micromenus/stylesheets/microbrowsers.css +0 -42
- data/apps/default/config/helpers/multi_messenger.yml +0 -9
- data/apps/default/config/helpers/weather.yml +0 -1
- data/apps/default/config/helpers/xbmc.yml +0 -2
- data/apps/default/config/migration.rb +0 -59
- data/apps/default/extensions.rb +0 -41
- data/apps/default/helpers/factorial.alien.c +0 -32
- data/apps/default/helpers/growler.rb +0 -53
- data/apps/default/helpers/lookup.rb +0 -44
- data/apps/default/helpers/manager_proxy.rb +0 -112
- data/apps/default/helpers/micromenus.rb +0 -514
- data/apps/default/helpers/multi_messenger.rb +0 -53
- data/apps/default/helpers/oscar_wilde_quotes.rb +0 -197
- data/apps/default/helpers/weather.rb +0 -85
- data/apps/default/helpers/xbmc.rb +0 -39
- data/apps/default/logs/adhearsion.log +0 -0
- data/apps/default/logs/database.log +0 -0
- data/lib/constants.rb +0 -24
- data/lib/core_extensions.rb +0 -180
- data/lib/drb_server.rb +0 -101
- data/lib/logging.rb +0 -85
- data/lib/phone_number.rb +0 -85
- data/lib/rami.rb +0 -823
- data/lib/servlet_container.rb +0 -174
- data/lib/sexy_migrations.rb +0 -70
- data/test/asterisk_module_test.rb +0 -14
- data/test/core_extensions_test.rb +0 -26
- data/test/dial_test.rb +0 -43
- data/test/specs/numerical_string_spec.rb +0 -53
- data/test/test_micromenus.rb +0 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'calculated_match')
|
2
|
+
|
3
|
+
module Adhearsion
|
4
|
+
module VoIP
|
5
|
+
class MatchCalculator
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def build_with_pattern(pattern, match_payload, &block)
|
10
|
+
class_for_pattern_type(pattern.class.name).new(pattern, match_payload, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def inherited(klass)
|
14
|
+
subclasses << klass
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def class_for_pattern_type(pattern_type)
|
20
|
+
sought_class_name = "Adhearsion::VoIP::#{pattern_type.camelize}MatchCalculator"
|
21
|
+
subclasses.find { |klass| klass.name == sought_class_name }
|
22
|
+
end
|
23
|
+
|
24
|
+
def subclasses
|
25
|
+
@@subclasses ||= []
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :pattern, :match_payload
|
31
|
+
def initialize(pattern, match_payload)
|
32
|
+
@pattern, @match_payload = pattern, match_payload
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def new_calculated_match(options)
|
38
|
+
CalculatedMatch.new({:pattern => pattern, :match_payload => match_payload}.merge(options))
|
39
|
+
end
|
40
|
+
|
41
|
+
def coerce_to_numeric(victim)
|
42
|
+
victim.kind_of?(Numeric) ? victim : (victim.to_s =~ /^\d+$/ ? victim.to_s.to_i : nil )
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class RangeMatchCalculator < MatchCalculator
|
47
|
+
|
48
|
+
def initialize(pattern, match_payload)
|
49
|
+
raise unless pattern.first.kind_of?(Numeric) && pattern.last.kind_of?(Numeric)
|
50
|
+
super
|
51
|
+
end
|
52
|
+
|
53
|
+
def match(query)
|
54
|
+
numerical_query = coerce_to_numeric query
|
55
|
+
if numerical_query
|
56
|
+
exact_match = pattern.include?(numerical_query) ? query : nil
|
57
|
+
potential_matches = numbers_in_range_like numerical_query
|
58
|
+
potential_matches.reject! { |m| m.to_s == exact_match.to_s } if exact_match
|
59
|
+
|
60
|
+
new_calculated_match :query => query, :exact_matches => exact_match,
|
61
|
+
:potential_matches => potential_matches
|
62
|
+
else
|
63
|
+
CalculatedMatch.failed_match!(pattern, query, match_payload)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# Returns all numbers in the range (@pattern) that +begin with+ the number given
|
70
|
+
# as the first argument.
|
71
|
+
#
|
72
|
+
# NOTE! If you're having trouble reading what this method is actually doing, it's
|
73
|
+
# effectively a much more efficient version of this:
|
74
|
+
#
|
75
|
+
# pattern.to_a.select { |x| x.to_s.starts_with? num.to_s }.flatten
|
76
|
+
#
|
77
|
+
# Huge thanks to Dave Troy (http://davetroy.blogspot.com) for this awesomely
|
78
|
+
# efficient code!
|
79
|
+
def numbers_in_range_like(num)
|
80
|
+
return (pattern === 0 ? [0] : nil) if num == 0
|
81
|
+
raise ArgumentError unless num.kind_of?(Numeric)
|
82
|
+
returning Array.new do |matches|
|
83
|
+
first, last = pattern.first, pattern.last
|
84
|
+
power = 0
|
85
|
+
while num < last
|
86
|
+
ones_count = 10**power - 1
|
87
|
+
matches.concat((([num, first].max)..[(num+ones_count), last].min).to_a)
|
88
|
+
num *= 10
|
89
|
+
power += 1
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class FixnumMatchCalculator < MatchCalculator
|
96
|
+
def match(query)
|
97
|
+
numeric_query = coerce_to_numeric query
|
98
|
+
exact_match, potential_match = nil
|
99
|
+
if pattern == numeric_query
|
100
|
+
exact_match = pattern
|
101
|
+
elsif pattern.to_s.starts_with? query.to_s
|
102
|
+
potential_match = pattern
|
103
|
+
end
|
104
|
+
new_calculated_match :query => query, :exact_matches => exact_match, :potential_matches => potential_match
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class StringMatchCalculator < MatchCalculator
|
109
|
+
def match(query)
|
110
|
+
args = { :query => query, :exact_matches => nil,
|
111
|
+
:potential_matches => nil }
|
112
|
+
|
113
|
+
if pattern == query.to_s
|
114
|
+
args[:exact_matches] = [pattern]
|
115
|
+
elsif pattern.starts_with? query.to_s
|
116
|
+
args[:potential_matches] = [pattern]
|
117
|
+
end
|
118
|
+
|
119
|
+
new_calculated_match args
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'matchers.rb')
|
2
|
+
|
3
|
+
module Adhearsion
|
4
|
+
module VoIP
|
5
|
+
class MenuBuilder
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@patterns = []
|
9
|
+
@menu_callbacks = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(match_payload, *patterns, &block)
|
13
|
+
name_string = match_payload.to_s
|
14
|
+
if patterns.any?
|
15
|
+
patterns.each do |pattern|
|
16
|
+
@patterns << MatchCalculator.build_with_pattern(pattern, match_payload)
|
17
|
+
end
|
18
|
+
else
|
19
|
+
raise ArgumentError, "You cannot call this method without patterns!"
|
20
|
+
end
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def weighted_match_calculators
|
25
|
+
@patterns
|
26
|
+
end
|
27
|
+
|
28
|
+
def execute_hook_for(symbol, input)
|
29
|
+
callback = @menu_callbacks[symbol]
|
30
|
+
callback.call input if callback
|
31
|
+
end
|
32
|
+
|
33
|
+
def on_invalid(&block)
|
34
|
+
raise LocalJumpError, "Must supply a block!" unless block_given?
|
35
|
+
@menu_callbacks[:invalid] = block
|
36
|
+
end
|
37
|
+
|
38
|
+
def on_premature_timeout(&block)
|
39
|
+
raise LocalJumpError, "Must supply a block!" unless block_given?
|
40
|
+
@menu_callbacks[:premature_timeout] = block
|
41
|
+
end
|
42
|
+
|
43
|
+
def on_failure(&block)
|
44
|
+
raise LocalJumpError, "Must supply a block!" unless block_given?
|
45
|
+
@menu_callbacks[:failure] = block
|
46
|
+
end
|
47
|
+
|
48
|
+
def calculate_matches_for(result)
|
49
|
+
returning CalculatedMatchCollection.new do |collection|
|
50
|
+
weighted_match_calculators.each do |pattern|
|
51
|
+
collection << pattern.match(result)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'adhearsion/voip/menu_state_machine/menu_builder'
|
2
|
+
module Adhearsion
|
3
|
+
module VoIP
|
4
|
+
class Menu
|
5
|
+
|
6
|
+
DEFAULT_MAX_NUMBER_OF_TRIES = 1
|
7
|
+
DEFAULT_TIMEOUT = 5 # seconds
|
8
|
+
|
9
|
+
relationships :menu_builder => MenuBuilder
|
10
|
+
|
11
|
+
attr_reader :builder, :timeout, :tries_count, :max_number_of_tries
|
12
|
+
def initialize(options={}, &block)
|
13
|
+
@tries_count = 0 # Counts the number of tries the menu's been executed
|
14
|
+
|
15
|
+
@timeout = options[:timeout] || DEFAULT_TIMEOUT
|
16
|
+
@max_number_of_tries = options[:tries] || DEFAULT_MAX_NUMBER_OF_TRIES
|
17
|
+
|
18
|
+
@builder = menu_builder.new
|
19
|
+
yield @builder
|
20
|
+
|
21
|
+
initialize_digit_buffer
|
22
|
+
end
|
23
|
+
|
24
|
+
def <<(other)
|
25
|
+
digit_buffer << other
|
26
|
+
end
|
27
|
+
|
28
|
+
def digit_buffer
|
29
|
+
@digit_buffer
|
30
|
+
end
|
31
|
+
|
32
|
+
def digit_buffer_string
|
33
|
+
digit_buffer.to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
def digit_buffer_empty?
|
37
|
+
digit_buffer.empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
def continue
|
41
|
+
raise MenuGetAnotherDigitOrTimeout if digit_buffer_empty?
|
42
|
+
|
43
|
+
calculated_matches = builder.calculate_matches_for digit_buffer_string
|
44
|
+
|
45
|
+
if calculated_matches.exact_match_count >= 1
|
46
|
+
first_exact_match = calculated_matches.exact_matches.first
|
47
|
+
if calculated_matches.potential_match_count.zero?
|
48
|
+
# Match found with no extenuating ambiguities! Go with the first exact match
|
49
|
+
menu_result_found! first_exact_match.match_payload, digit_buffer_string
|
50
|
+
else
|
51
|
+
get_another_digit_or_finish!(first_exact_match.match_payload, first_exact_match.query)
|
52
|
+
end
|
53
|
+
elsif calculated_matches.potential_match_count >= 1
|
54
|
+
get_another_digit_or_timeout!
|
55
|
+
else
|
56
|
+
invalid!
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def should_continue?
|
61
|
+
tries_count < max_number_of_tries
|
62
|
+
end
|
63
|
+
|
64
|
+
def restart!
|
65
|
+
@tries_count += 1
|
66
|
+
digit_buffer.clear!
|
67
|
+
end
|
68
|
+
|
69
|
+
def execute_invalid_hook
|
70
|
+
builder.execute_hook_for(:invalid, digit_buffer_string)
|
71
|
+
end
|
72
|
+
|
73
|
+
def execute_timeout_hook
|
74
|
+
builder.execute_hook_for(:premature_timeout, digit_buffer_string)
|
75
|
+
end
|
76
|
+
|
77
|
+
def execute_failure_hook
|
78
|
+
builder.execute_hook_for(:failure, digit_buffer_string)
|
79
|
+
end
|
80
|
+
|
81
|
+
protected
|
82
|
+
|
83
|
+
# If you're using a more complex class in subclasses, you may want to override this method in addition to the
|
84
|
+
# digit_buffer, digit_buffer_empty, and digit_buffer_string methods
|
85
|
+
def initialize_digit_buffer
|
86
|
+
@digit_buffer = ClearableStringBuffer.new
|
87
|
+
end
|
88
|
+
|
89
|
+
def invalid!
|
90
|
+
raise MenuResultInvalid
|
91
|
+
end
|
92
|
+
|
93
|
+
def menu_result_found!(match_payload, new_extension)
|
94
|
+
raise MenuResultFound.new(match_payload, new_extension)
|
95
|
+
end
|
96
|
+
|
97
|
+
def get_another_digit_or_finish!(match_payload, new_extension)
|
98
|
+
raise MenuGetAnotherDigitOrFinish.new(match_payload, new_extension)
|
99
|
+
end
|
100
|
+
|
101
|
+
def get_another_digit_or_timeout!
|
102
|
+
raise MenuGetAnotherDigitOrTimeout
|
103
|
+
end
|
104
|
+
|
105
|
+
# The superclass from which all message-like exceptions decend. It should never
|
106
|
+
# be instantiated directly.
|
107
|
+
class MenuResult < Exception; end
|
108
|
+
|
109
|
+
# Raised when the user's input matches
|
110
|
+
class MenuResultFound < MenuResult
|
111
|
+
attr_reader :match_payload, :new_extension
|
112
|
+
def initialize(match_payload, new_extension)
|
113
|
+
super()
|
114
|
+
@match_payload = match_payload
|
115
|
+
@new_extension = new_extension
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
module MenuGetAnotherDigit; end
|
120
|
+
|
121
|
+
class MenuGetAnotherDigitOrFinish < MenuResultFound
|
122
|
+
include MenuGetAnotherDigit
|
123
|
+
end
|
124
|
+
|
125
|
+
class MenuGetAnotherDigitOrTimeout < MenuResult
|
126
|
+
include MenuGetAnotherDigit
|
127
|
+
end
|
128
|
+
|
129
|
+
class MenuResultFound < MenuResult; end
|
130
|
+
|
131
|
+
# Raised when the user's input matches no patterns
|
132
|
+
class MenuResultInvalid < MenuResult; end
|
133
|
+
|
134
|
+
# For our default purposes, we need the digit_buffer to behave much like a normal String except that it should
|
135
|
+
# handle its own resetting (clearing).
|
136
|
+
class ClearableStringBuffer < String
|
137
|
+
def clear!
|
138
|
+
replace ""
|
139
|
+
end
|
140
|
+
|
141
|
+
def <<(other)
|
142
|
+
super other.to_s
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/lib/theatre.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'rubygems'
|
3
|
+
|
4
|
+
$: << File.expand_path(File.dirname(__FILE__))
|
5
|
+
|
6
|
+
require 'theatre/version'
|
7
|
+
require 'theatre/namespace_manager'
|
8
|
+
require 'theatre/invocation'
|
9
|
+
require 'theatre/callback_definition_loader'
|
10
|
+
|
11
|
+
module Theatre
|
12
|
+
|
13
|
+
class Theatre
|
14
|
+
|
15
|
+
attr_reader :namespace_manager
|
16
|
+
|
17
|
+
##
|
18
|
+
# Creates a new stopped Theatre. You must call start!() after you instantiate this for it to begin processing events.
|
19
|
+
#
|
20
|
+
# @param [Fixnum] thread_count Number of Threads to spawn when started.
|
21
|
+
#
|
22
|
+
def initialize(thread_count=6)
|
23
|
+
@thread_count = thread_count
|
24
|
+
@started = false
|
25
|
+
@namespace_manager = ActorNamespaceManager.new
|
26
|
+
@thread_group = ThreadGroup.new
|
27
|
+
@master_queue = Queue.new
|
28
|
+
@loader_mixins = []
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Send a message to this Theatre for asynchronous processing.
|
33
|
+
#
|
34
|
+
# @param [String] namespace The namespace to which the payload should be sent
|
35
|
+
# @param [Object] payload The actual content to be sent to the callback. Optional.
|
36
|
+
# @return [Array<Theatre::Invocation>] An Array of Invocation objects
|
37
|
+
# @raise Theatre::NamespaceNotFound Raised when told to enqueue an unrecognized namespace
|
38
|
+
#
|
39
|
+
def trigger(namespace, payload=:argument_undefined)
|
40
|
+
@namespace_manager.callbacks_for_namespaces(namespace).map do |callback|
|
41
|
+
invocation = if payload.equal?(:argument_undefined)
|
42
|
+
Invocation.new(namespace, callback)
|
43
|
+
else
|
44
|
+
Invocation.new(namespace, callback, payload)
|
45
|
+
end
|
46
|
+
invocation.queued
|
47
|
+
@master_queue << invocation
|
48
|
+
invocation
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# Send a message to this Theatre for synchronous processing. The execution of this will not go through this Theatre's
|
54
|
+
# Thread pool. If an error occurred in any of callbacks, the Exception object will be placed in the returned Array
|
55
|
+
# instead for you to act upon.
|
56
|
+
#
|
57
|
+
# @param [String] namespace The namespace to which the payload should be sent
|
58
|
+
# @param [Object] payload The actual content to be sent to the callback. Optional.
|
59
|
+
# @return [Array] An Array containing each callback's return value (or Exception raised, if any) when given the payload
|
60
|
+
# @raise Theatre::NamespaceNotFound Raised when told to enqueue an unrecognized namespace
|
61
|
+
#
|
62
|
+
def trigger_immediately(namespace, payload=:argument_undefined)
|
63
|
+
@namespace_manager.callbacks_for_namespaces(namespace).map do |callback|
|
64
|
+
begin
|
65
|
+
invocation = if payload.equal?(:argument_undefined)
|
66
|
+
callback.call
|
67
|
+
else
|
68
|
+
callback.call payload
|
69
|
+
end
|
70
|
+
rescue => captured_error_to_be_returned
|
71
|
+
captured_error_to_be_returned
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def load_events_code(code, *args)
|
77
|
+
loader = CallbackDefinitionLoader.new(self, *args)
|
78
|
+
loader.load_events_code code
|
79
|
+
end
|
80
|
+
|
81
|
+
def load_events_file(file, *args)
|
82
|
+
loader = CallbackDefinitionLoader.new(self, *args)
|
83
|
+
loader.load_events_file file
|
84
|
+
end
|
85
|
+
|
86
|
+
def register_namespace_name(*args)
|
87
|
+
@namespace_manager.register_namespace_name(*args)
|
88
|
+
end
|
89
|
+
|
90
|
+
def register_callback_at_namespace(*args)
|
91
|
+
@namespace_manager.register_callback_at_namespace(*args)
|
92
|
+
end
|
93
|
+
|
94
|
+
def register_loader_mixin(mod)
|
95
|
+
@loader_mixins << mod
|
96
|
+
end
|
97
|
+
|
98
|
+
def join
|
99
|
+
@thread_group.list.each do |thread|
|
100
|
+
begin
|
101
|
+
thread.join
|
102
|
+
rescue
|
103
|
+
# Ignore any exceptions
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# Starts this Theatre.
|
110
|
+
#
|
111
|
+
# When this method is called, the Threads are spawned and begin pulling messages off this Theatre's master queue.
|
112
|
+
#
|
113
|
+
def start!
|
114
|
+
return false if @thread_group.list.any? # Already started
|
115
|
+
@started_time = Time.now
|
116
|
+
@thread_count.times do
|
117
|
+
@thread_group.add Thread.new(&method(:thread_loop))
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# Notifies all Threads for this Theatre to stop by sending them special messages. Any messages which were queued and
|
123
|
+
# untriggered when this method is received will still be processed. Note: you may start this Theatre again later once it
|
124
|
+
# has been stopped.
|
125
|
+
#
|
126
|
+
def graceful_stop!
|
127
|
+
@thread_count.times { @master_queue << :THEATRE_SHUTDOWN! }
|
128
|
+
@started_time = nil
|
129
|
+
end
|
130
|
+
|
131
|
+
protected
|
132
|
+
|
133
|
+
# This will use the Adhearsion logger eventually.
|
134
|
+
def warn(exception)
|
135
|
+
# STDERR.puts exception.message, *exception.backtrace
|
136
|
+
end
|
137
|
+
|
138
|
+
def thread_loop
|
139
|
+
loop do
|
140
|
+
begin
|
141
|
+
next_invocation = @master_queue.pop
|
142
|
+
return :stopped if next_invocation.equal? :THEATRE_SHUTDOWN!
|
143
|
+
next_invocation.start
|
144
|
+
rescue => error
|
145
|
+
warn error
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|