eric-adhearsion 0.7.999 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. data/CHANGELOG +8 -2
  2. data/EVENTS +11 -0
  3. data/Rakefile +96 -24
  4. data/adhearsion.gemspec +148 -0
  5. data/app_generators/ahn/ahn_generator.rb +24 -9
  6. data/app_generators/ahn/templates/.ahnrc +25 -3
  7. data/app_generators/ahn/templates/Rakefile +22 -2
  8. data/app_generators/ahn/templates/components/ami_remote/ami_remote.rb +15 -0
  9. data/app_generators/ahn/templates/components/disabled/HOW_TO_ENABLE +7 -0
  10. data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +47 -0
  11. data/app_generators/ahn/templates/components/disabled/stomp_gateway/config.yml +12 -0
  12. data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb +34 -0
  13. data/app_generators/ahn/templates/components/simon_game/{lib/simon_game.rb → simon_game.rb} +14 -19
  14. data/app_generators/ahn/templates/config/startup.rb +3 -6
  15. data/app_generators/ahn/templates/dialplan.rb +2 -3
  16. data/app_generators/ahn/templates/events.rb +32 -0
  17. data/bin/jahn +10 -0
  18. data/examples/asterisk_manager_interface/standalone.rb +51 -0
  19. data/lib/adhearsion.rb +17 -11
  20. data/lib/adhearsion/cli.rb +141 -24
  21. data/lib/adhearsion/component_manager.rb +169 -238
  22. data/lib/adhearsion/component_manager/component_tester.rb +55 -0
  23. data/lib/adhearsion/component_manager/spec_framework.rb +24 -0
  24. data/lib/adhearsion/events_support.rb +84 -0
  25. data/lib/adhearsion/{core_extensions → foundation}/all.rb +0 -0
  26. data/lib/adhearsion/{blank_slate.rb → foundation/blank_slate.rb} +0 -0
  27. data/lib/adhearsion/{core_extensions → foundation}/custom_daemonizer.rb +0 -0
  28. data/lib/adhearsion/foundation/event_socket.rb +203 -0
  29. data/lib/adhearsion/foundation/future_resource.rb +36 -0
  30. data/lib/adhearsion/{core_extensions → foundation}/global.rb +0 -0
  31. data/lib/adhearsion/{core_extensions → foundation}/metaprogramming.rb +0 -0
  32. data/lib/adhearsion/foundation/numeric.rb +13 -0
  33. data/lib/adhearsion/foundation/pseudo_guid.rb +10 -0
  34. data/lib/adhearsion/{core_extensions → foundation}/relationship_properties.rb +2 -0
  35. data/lib/adhearsion/foundation/string.rb +26 -0
  36. data/lib/adhearsion/foundation/synchronized_hash.rb +96 -0
  37. data/lib/adhearsion/{core_extensions → foundation}/thread_safety.rb +0 -0
  38. data/lib/adhearsion/host_definitions.rb +5 -1
  39. data/lib/adhearsion/initializer.rb +229 -73
  40. data/lib/adhearsion/initializer/asterisk.rb +33 -11
  41. data/lib/adhearsion/initializer/configuration.rb +58 -6
  42. data/lib/adhearsion/initializer/database.rb +3 -46
  43. data/lib/adhearsion/initializer/drb.rb +9 -3
  44. data/lib/adhearsion/initializer/freeswitch.rb +3 -3
  45. data/lib/adhearsion/initializer/rails.rb +1 -1
  46. data/lib/adhearsion/tasks.rb +2 -1
  47. data/lib/adhearsion/tasks/deprecations.rb +59 -0
  48. data/lib/adhearsion/version.rb +3 -3
  49. data/lib/adhearsion/voip/asterisk.rb +2 -2
  50. data/lib/adhearsion/voip/asterisk/agi_server.rb +9 -6
  51. data/lib/adhearsion/voip/asterisk/commands.rb +106 -4
  52. data/lib/adhearsion/voip/asterisk/manager_interface.rb +562 -0
  53. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +1754 -0
  54. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +286 -0
  55. data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +78 -0
  56. data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +87 -0
  57. data/lib/adhearsion/voip/asterisk/super_manager.rb +19 -0
  58. data/lib/adhearsion/voip/call.rb +51 -2
  59. data/lib/adhearsion/voip/dial_plan.rb +74 -61
  60. data/lib/adhearsion/voip/dsl/dialing_dsl.rb +1 -1
  61. data/lib/adhearsion/voip/dsl/dialplan/parser.rb +2 -6
  62. data/lib/adhearsion/voip/dsl/numerical_string.rb +2 -2
  63. data/lib/adhearsion/voip/freeswitch/oes_server.rb +2 -2
  64. data/lib/theatre.rb +151 -0
  65. data/lib/theatre/README.markdown +64 -0
  66. data/lib/theatre/callback_definition_loader.rb +84 -0
  67. data/lib/theatre/guid.rb +23 -0
  68. data/lib/theatre/invocation.rb +121 -0
  69. data/lib/theatre/namespace_manager.rb +153 -0
  70. data/lib/theatre/version.rb +2 -0
  71. metadata +63 -138
  72. data/Manifest.txt +0 -149
  73. data/README.txt +0 -6
  74. data/ahn_generators/component/USAGE +0 -5
  75. data/ahn_generators/component/component_generator.rb +0 -57
  76. data/ahn_generators/component/templates/configuration.rb +0 -0
  77. data/ahn_generators/component/templates/lib/lib.rb.erb +0 -3
  78. data/ahn_generators/component/templates/test/test.rb.erb +0 -12
  79. data/ahn_generators/component/templates/test/test_helper.rb +0 -14
  80. data/app_generators/ahn/templates/components/simon_game/configuration.rb +0 -0
  81. data/app_generators/ahn/templates/components/simon_game/test/test_helper.rb +0 -14
  82. data/app_generators/ahn/templates/components/simon_game/test/test_simon_game.rb +0 -31
  83. data/lib/adhearsion/core_extensions/array.rb +0 -0
  84. data/lib/adhearsion/core_extensions/guid.rb +0 -5
  85. data/lib/adhearsion/core_extensions/hash.rb +0 -0
  86. data/lib/adhearsion/core_extensions/numeric.rb +0 -4
  87. data/lib/adhearsion/core_extensions/proc.rb +0 -0
  88. data/lib/adhearsion/core_extensions/pseudo_uuid.rb +0 -11
  89. data/lib/adhearsion/core_extensions/publishable.rb +0 -73
  90. data/lib/adhearsion/core_extensions/string.rb +0 -26
  91. data/lib/adhearsion/core_extensions/thread.rb +0 -13
  92. data/lib/adhearsion/core_extensions/time.rb +0 -0
  93. data/lib/adhearsion/distributed/gateways/dbus_gateway.rb +0 -0
  94. data/lib/adhearsion/distributed/gateways/osa_gateway.rb +0 -0
  95. data/lib/adhearsion/distributed/gateways/rest_gateway.rb +0 -9
  96. data/lib/adhearsion/distributed/gateways/soap_gateway.rb +0 -9
  97. data/lib/adhearsion/distributed/gateways/xmlrpc_gateway.rb +0 -9
  98. data/lib/adhearsion/distributed/peer_finder.rb +0 -0
  99. data/lib/adhearsion/distributed/remote_cli.rb +0 -0
  100. data/lib/adhearsion/hooks.rb +0 -57
  101. data/lib/adhearsion/initializer/paths.rb +0 -55
  102. data/lib/adhearsion/services/scheduler.rb +0 -5
  103. data/lib/adhearsion/voip/asterisk/ami.rb +0 -147
  104. data/lib/adhearsion/voip/asterisk/ami/actions.rb +0 -238
  105. data/lib/adhearsion/voip/asterisk/ami/machine.rb +0 -871
  106. data/lib/adhearsion/voip/asterisk/ami/machine.rl +0 -109
  107. data/lib/adhearsion/voip/asterisk/ami/parser.rb +0 -262
  108. data/script/destroy +0 -14
  109. data/script/generate +0 -14
  110. data/spec/fixtures/dialplan.rb +0 -3
  111. data/spec/initializer/test_configuration.rb +0 -267
  112. data/spec/initializer/test_loading.rb +0 -162
  113. data/spec/initializer/test_paths.rb +0 -43
  114. data/spec/silence.rb +0 -10
  115. data/spec/test_ahn_command.rb +0 -149
  116. data/spec/test_code_quality.rb +0 -87
  117. data/spec/test_component_manager.rb +0 -97
  118. data/spec/test_constants.rb +0 -8
  119. data/spec/test_drb.rb +0 -104
  120. data/spec/test_helper.rb +0 -94
  121. data/spec/test_hooks.rb +0 -37
  122. data/spec/test_host_definitions.rb +0 -79
  123. data/spec/test_initialization.rb +0 -105
  124. data/spec/test_logging.rb +0 -80
  125. data/spec/test_relationship_properties.rb +0 -54
  126. data/spec/voip/asterisk/ami_response_definitions.rb +0 -23
  127. data/spec/voip/asterisk/config_file_generators/test_agents.rb +0 -253
  128. data/spec/voip/asterisk/config_file_generators/test_queues.rb +0 -325
  129. data/spec/voip/asterisk/config_file_generators/test_voicemail.rb +0 -306
  130. data/spec/voip/asterisk/menu_command/test_calculated_match.rb +0 -111
  131. data/spec/voip/asterisk/menu_command/test_matchers.rb +0 -98
  132. data/spec/voip/asterisk/mock_ami_server.rb +0 -176
  133. data/spec/voip/asterisk/test_agi_server.rb +0 -451
  134. data/spec/voip/asterisk/test_ami.rb +0 -227
  135. data/spec/voip/asterisk/test_commands.rb +0 -2006
  136. data/spec/voip/asterisk/test_config_manager.rb +0 -129
  137. data/spec/voip/dsl/dispatcher_spec_helper.rb +0 -45
  138. data/spec/voip/dsl/test_dialing_dsl.rb +0 -268
  139. data/spec/voip/dsl/test_dispatcher.rb +0 -82
  140. data/spec/voip/dsl/test_parser.rb +0 -87
  141. data/spec/voip/freeswitch/test_basic_connection_manager.rb +0 -39
  142. data/spec/voip/freeswitch/test_inbound_connection_manager.rb +0 -39
  143. data/spec/voip/freeswitch/test_oes_server.rb +0 -9
  144. data/spec/voip/test_call_routing.rb +0 -127
  145. data/spec/voip/test_dialplan_manager.rb +0 -372
  146. data/spec/voip/test_numerical_string.rb +0 -48
  147. data/spec/voip/test_phone_number.rb +0 -36
  148. data/test/test_ahn_generator.rb +0 -59
  149. data/test/test_component_generator.rb +0 -52
  150. data/test/test_generator_helper.rb +0 -20
@@ -0,0 +1,7 @@
1
+ Type "ahn enable component COMPONENT_NAME_FROM_DISABLED_FOLDER
2
+
3
+ For example:
4
+
5
+ ~/Desktop $ ahn create myapp
6
+ ~/Desktop $ cd myapp
7
+ ~/Desktop/myapp $ ahn enable component stomp_gateway
@@ -0,0 +1,47 @@
1
+ What is Stomp?
2
+ ==============
3
+
4
+ Stomp is a very simple message-queue protocol with which two separate systems can communicate. Because the protocol is so simple, there are many Stomp server implementations from which you can choose. Some of these include
5
+
6
+ - ActiveMQ (http://activemq.com)
7
+ - Ruby "stompserver" gem (gem install stompserver)
8
+ - RabbitMQ (http://rabbitmq.com)
9
+
10
+ If you wish to get up and running with a development environment, the Ruby stompserver gem is a fantastic starting point. For a critical production system, ActiveMQ should probably be used but it bears the cumbersome paradigm of many "enterprisey" Java applications.
11
+
12
+ How does it work?
13
+ =================
14
+
15
+ Stomp is used when certain processes have defined responsibilities. For example, your Adhearsion application's responsibility is to communicate with your Asterisk machine. Other processes (e.g. a Rails web application) will probably need to instruct Adhearsion to do something. Instructions may include
16
+
17
+ - Start a new call between two given phone numbers
18
+ - Have a particular call do something based on a new event
19
+ - Hangup a call
20
+
21
+ Below is a diagram which should give you a better idea of how it works.
22
+
23
+ Process Process Process (e.g. Rails)
24
+ \ | /
25
+ \ | /
26
+ Stomp Server (e.g. ActiveMQ)
27
+ |
28
+ |
29
+ Process (e.g. Adhearsion)
30
+
31
+ Note: Adhearsion could also be the sender of messages through the Stomp server which are consumed by a number of handlers.
32
+
33
+ Setting up a Ruby Stomp server
34
+ ==============================
35
+
36
+ Install the pure-Ruby Stomp server by doing "gem install stompserver". This will add the "stompserver" command to your system. When running it without any parameters, it starts without requiring authentication. If you're wanting to get a quick experiment running, I recommend simply doing that.
37
+
38
+ Open the config.yml file in the stomp_gateway component folder. Comment out the four settings at the top of the file named "user", "pass", "host" and "port" by prepending a "#" to their line. This will cause the component to choose defaults for those properties. The component's defaults will match the expected credentials for the experimental stompserver you're already running on your computer.
39
+
40
+ You also need specify a subscription name in
41
+
42
+ events.stomp.start_call.each do |event|
43
+ # The "event" variable holds a Stomp::Message object.
44
+ name = event.headers
45
+ end
46
+
47
+ You a
@@ -0,0 +1,12 @@
1
+ # Comment out any of these properties to use a default.
2
+
3
+ user: stomp_user
4
+ pass: secret_password
5
+ host: localhost
6
+ port: 61613
7
+
8
+ ### Add your list of subscriptions below that this gateway should proxy to events.rb
9
+
10
+ # subscriptions:
11
+ # - start_call
12
+ # - hangup_call
@@ -0,0 +1,34 @@
1
+ require 'stomp'
2
+
3
+ # TODO: Recover from a disconnect!
4
+
5
+ initialization do
6
+ user = COMPONENTS.stomp_gateway[:user] || ""
7
+ pass = COMPONENTS.stomp_gateway[:pass] || ""
8
+ host = COMPONENTS.stomp_gateway[:host] || "localhost"
9
+ port = COMPONENTS.stomp_gateway[:port] || 61613
10
+
11
+ ::StompGatewayConnection = Stomp::Client.open(user, pass, host, port)
12
+
13
+ subscriptions = COMPONENTS.stomp_gateway["subscriptions"]
14
+
15
+ ahn_log.stomp_gateway "Connection established. Subscriptions: #{subscriptions.inspect}"
16
+
17
+ Events.register_namespace_name "/stomp"
18
+
19
+ subscriptions.each do |subscription|
20
+ Events.register_namespace_name "/stomp/#{subscription}"
21
+ ::StompGatewayConnection.subscribe subscription do |event|
22
+ Adhearsion::Events.trigger ["stomp", subscription], event
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ methods_for :global do
29
+ def send_stomp(destination, message, headers={})
30
+ ::StompGatewayConnection.send(destination, message, headers)
31
+ end
32
+ end
33
+
34
+ # In the future, I may add a methods_for(:events) method which allows synchronous messaging.
@@ -1,10 +1,14 @@
1
+ methods_for :dialplan do
2
+ def simon_game
3
+ SimonGame.new(self).start
4
+ end
5
+ end
6
+
1
7
  class SimonGame
2
- add_call_context :as => :call_context
3
8
 
4
- attr_accessor :number, :attempt
5
- def initialize
6
- initialize_number
7
- initialize_attempt
9
+ def initialize(call)
10
+ @call = call
11
+ reset
8
12
  end
9
13
 
10
14
  def start
@@ -20,38 +24,29 @@ class SimonGame
20
24
  end
21
25
 
22
26
  def update_number
23
- initialize_attempt
24
27
  @number << random_number
25
28
  end
26
29
 
27
30
  def say_number
28
31
  update_number
29
- call_context.say_digits number
32
+ @call.say_digits @number
30
33
  end
31
34
 
32
35
  def collect_attempt
33
- @attempt = call_context.input(number.size)
36
+ @attempt = @call.input @number.length
34
37
  end
35
38
 
36
39
  def verify_attempt
37
40
  if attempt_correct?
38
- call_context.play 'good'
41
+ @call.play 'good'
39
42
  else
40
- call_context.play %W(#{number.size - 1} times wrong-try-again-smarty)
43
+ @call.play %W[#{@number.length-1} times wrong-try-again-smarty]
41
44
  reset
42
45
  end
43
46
  end
44
47
 
45
48
  def attempt_correct?
46
- attempt == number
47
- end
48
-
49
- def initialize_attempt
50
- @attempt ||= ''
51
- end
52
-
53
- def initialize_number
54
- @number ||= ''
49
+ @attempt == @number
55
50
  end
56
51
 
57
52
  def reset
@@ -26,12 +26,9 @@ Adhearsion::Configuration.configure do |config|
26
26
 
27
27
  # By default Asterisk is enabled with the default settings
28
28
  config.enable_asterisk
29
- # config.asterisk.enable_ami :host => "127.0.0.1", :username => "admin", :password => "password"
29
+ # config.asterisk.enable_ami :host => "127.0.0.1", :username => "admin", :password => "password", :events => true
30
30
 
31
- # To change the host IP or port on which the AGI server listens, use this:
32
- # config.enable_asterisk :listening_port => 4574, :listening_host => "127.0.0.1"
33
-
34
- # config.enable_drb
31
+ # config.enable_drb
35
32
 
36
33
  # Streamlined Rails integration! The first argument should be a relative or absolute path to
37
34
  # the Rails app folder with which you're integrating. The second argument must be one of the
@@ -50,4 +47,4 @@ Adhearsion::Configuration.configure do |config|
50
47
  # :host => 'db.example.org'
51
48
  end
52
49
 
53
- Adhearsion::Initializer.start_from_init_file(__FILE__, File.dirname(__FILE__) + "/..")
50
+ Adhearsion::Initializer.start_from_init_file(__FILE__, File.dirname(__FILE__) + "/..")
@@ -1,4 +1,3 @@
1
1
  adhearsion {
2
- simon = new_simon_game
3
- simon.start
4
- }
2
+ simon_game
3
+ }
@@ -0,0 +1,32 @@
1
+ ##
2
+ # In this file you can define callbacks for different aspects of the framework. Below is an example:
3
+ ##
4
+ #
5
+ # events.asterisk.before_call.each do |call|
6
+ # # This simply logs the extension for all calls going through this Adhearsion app.
7
+ # extension = call.variables[:extension]
8
+ # ahn_log "Got a new call with extension #{extension}"
9
+ # end
10
+ #
11
+ ##
12
+ # Asterisk Manager Interface example:
13
+ #
14
+ # events.asterisk.manager_interface.each do |event|
15
+ # ahn_log.events event.inspect
16
+ # end
17
+ #
18
+ # This assumes you gave :events => true to the config.asterisk.enable_ami method in config/startup.rb
19
+ #
20
+ ##
21
+ # Here is a list of the events included by default:
22
+ #
23
+ # - events.asterisk.manager_interface
24
+ # - events.after_initialized
25
+ # - events.shutdown
26
+ # - events.asterisk.before_call
27
+ # - events.asterisk.failed_call
28
+ # - events.asterisk.call_hangup
29
+ #
30
+ #
31
+ # Note: events are mostly for components to register and expose to you.
32
+ ##
data/bin/jahn CHANGED
@@ -29,4 +29,14 @@ require 'adhearsion/cli'
29
29
  # file from an app.
30
30
  # require 'adhearsion/jruby'
31
31
 
32
+ abort(<<-MESSAGE) unless RUBY_PLATFORM =~ /java/
33
+ You must use jahn with JRuby! Are you running this script after installing Adhearsion with non-JRuby RubyGems?
34
+
35
+ If you don't want to install Adhearsion with the JRuby version of RubyGems, try running jahn directly with an absolute path:
36
+
37
+ #{File.expand_path(__FILE__)} start your_app_name
38
+
39
+ Note: You must have the jruby executable in your $PATH and $JRUBY_HOME set.
40
+ MESSAGE
41
+
32
42
  Adhearsion::CLI::AhnCommand.execute!
@@ -0,0 +1,51 @@
1
+ # This is a file which shows you how to use the Asterisk Manager Interface library in a standalone Ruby script.
2
+
3
+ PATH_TO_ADHEARSION = File.join(File.dirname(__FILE__), "/../..")
4
+
5
+ MANAGER_CONNECTION_INFORMATION = {
6
+ :host => "10.0.1.97",
7
+ :username => "jicksta",
8
+ :password => "roflcopter",
9
+ :events => true
10
+ }
11
+
12
+ require 'rubygems'
13
+ begin
14
+ require 'adhearsion'
15
+ rescue LoadError
16
+ begin
17
+ require File.join(PATH_TO_ADHEARSION, "/lib/adhearsion")
18
+ rescue LoadError
19
+ abort "Could not find Adhearsion! Please update the PATH_TO_ADHEARSION constant in this file"
20
+ end
21
+ end
22
+
23
+ require 'adhearsion/voip/asterisk/manager_interface'
24
+
25
+ # If you'd like to see the AMI protocol data, change this to :debug
26
+ Adhearsion::Logging.logging_level = :warn
27
+
28
+ # This makes addressing the ManagerInterface class a little cleaner
29
+ include Adhearsion::VoIP::Asterisk::Manager
30
+
31
+ # Let's instantiate a new ManagerInterface object and have it automatically connect using the Hash we defined above.
32
+ interface = ManagerInterface.connect MANAGER_CONNECTION_INFORMATION
33
+
34
+ # Send an AMI action with our new ManagerInterface object. This will return an Array of SIPPeer events.
35
+ sip_peers = interface.send_action "SIPPeers"
36
+
37
+ # Pretty-print the SIP peers on the server
38
+
39
+ if sip_peers.any?
40
+ sip_peers.each do |peer|
41
+ # Uncomment the following line to view all the headers for each peer.
42
+ # p peer.headers
43
+
44
+ peer_name = peer.headers["ObjectName"]
45
+ peer_status = peer.headers["Status"]
46
+
47
+ puts "#{peer_name}: #{peer_status}"
48
+ end
49
+ else
50
+ puts "This Asterisk server has no SIP peers!"
51
+ end
@@ -3,29 +3,35 @@ STDERR.puts "WARNING: You are running Adhearsion in an unsupported
3
3
  version of Ruby (Ruby #{RUBY_VERSION} #{RUBY_RELEASE_DATE})!
4
4
  Please upgrade to at least Ruby v1.8.5." if RUBY_VERSION < "1.8.5"
5
5
 
6
- module Adhearsion
7
- # Sets up the Gem require path.
8
- AHN_INSTALL_DIR = File.expand_path(File.dirname(__FILE__) + "/..")
9
- CONFIG = {}
10
- end
11
-
12
6
  $: << File.expand_path(File.dirname(__FILE__))
13
7
 
14
8
  require 'rubygems'
9
+
15
10
  require 'adhearsion/version'
16
11
  require 'adhearsion/voip/call'
17
12
  require 'adhearsion/voip/dial_plan'
18
13
  require 'adhearsion/voip/asterisk/special_dial_plan_managers'
19
- require 'adhearsion/core_extensions/all'
20
- require 'adhearsion/blank_slate'
21
- require 'adhearsion/hooks'
14
+ require 'adhearsion/foundation/all'
15
+ require 'adhearsion/events_support'
22
16
  require 'adhearsion/logging'
17
+ require 'adhearsion/component_manager'
23
18
  require 'adhearsion/initializer/configuration'
24
19
  require 'adhearsion/initializer'
25
- require 'adhearsion/initializer/paths'
26
20
  require 'adhearsion/voip/dsl/numerical_string'
27
21
  require 'adhearsion/voip/dsl/dialplan/parser'
28
22
  require 'adhearsion/voip/commands'
29
23
  require 'adhearsion/voip/asterisk/commands'
30
24
  require 'adhearsion/voip/dsl/dialing_dsl'
31
- require 'adhearsion/voip/call_routing'
25
+ require 'adhearsion/voip/call_routing'
26
+
27
+ module Adhearsion
28
+ # Sets up the Gem require path.
29
+ AHN_INSTALL_DIR = File.expand_path(File.dirname(__FILE__) + "/..")
30
+ AHN_CONFIG = Configuration.new
31
+
32
+ ##
33
+ # This Array holds all the Threads whose life matters. Adhearsion will not exit until all of these have died.
34
+ #
35
+ IMPORTANT_THREADS = []
36
+
37
+ end
@@ -6,16 +6,26 @@ module Adhearsion
6
6
  USAGE = <<USAGE
7
7
  Usage:
8
8
  ahn create /path/to/directory
9
- ahn start [daemon] [directory]
9
+ ahn start [daemon] [/path/to/directory]
10
10
  ahn version|-v|--v|-version|--version
11
11
  ahn help|-h|--h|--help|-help
12
-
13
- Under development:
14
- ahn create:projectname /path/to/directory
12
+
13
+ ahn enable component COMPONENT_NAME
14
+ ahn disable component COMPONENT_NAME
15
+ ahn create component COMPONENT_NAME
15
16
  USAGE
16
17
 
17
18
  def self.execute!
18
19
  CommandHandler.send(*parse_arguments)
20
+ rescue CommandHandler::CLIException => error
21
+ fail_and_print_usage error
22
+ end
23
+
24
+ ##
25
+ # Provides a small abstraction of Kernel::abort().
26
+ #
27
+ def self.fail_and_print_usage(error)
28
+ Kernel.abort "#{error.message}\n\n#{USAGE}"
19
29
  end
20
30
 
21
31
  def self.parse_arguments(args=ARGV.clone)
@@ -23,14 +33,14 @@ USAGE
23
33
  case action
24
34
  when /^-?-?h(elp)?$/, nil then [:help]
25
35
  when /^-?-?v(ersion)?$/ then [:version]
26
- when /^create(:([\w_.]+))?$/
27
- [:create, args.shift, $LAST_PAREN_MATCH || :default]
36
+ when "create"
37
+ [:create, *args]
28
38
  when 'start'
29
39
  pid_file_regexp = /^--pid-file=(.+)$/
30
40
  if args.size > 3
31
- raise CommandHandler::UnknownCommand, "Too many arguments supplied!" if args.size > 3
41
+ fail_and_print_usage "Too many arguments supplied!" if args.size > 3
32
42
  elsif args.size == 3
33
- raise CommandHandler::UnknownCommand, "Unrecognized final argument #{args.last}" unless args.last =~ pid_file_regexp
43
+ fail_and_print_usage "Unrecognized final argument #{args.last}" unless args.last =~ pid_file_regexp
34
44
  pid_file = args.pop[pid_file_regexp, 1]
35
45
  else
36
46
  pid_file = nil
@@ -42,11 +52,19 @@ USAGE
42
52
  elsif args.size == 1
43
53
  path, daemon = args.first, false
44
54
  else
45
- raise CommandHandler::UnknownCommand, "Invalid format for the start CLI command!"
55
+ fail_and_print_usage "Invalid format for the start CLI command!"
46
56
  end
47
57
  [:start, path, daemon, pid_file]
48
58
  when '-'
49
59
  [:start, Dir.pwd]
60
+ when "enable", "disable"
61
+ if args.size == 1
62
+ raise CommandHandler::UnknownCommand, "Must supply an argument for what you wish to #{action}"
63
+ elsif args.size == 2
64
+ [action, *args]
65
+ else
66
+ raise CommandHandler::UnknownCommand, "Too many arguments supplied!"
67
+ end
50
68
  else
51
69
  [action, *args]
52
70
  end
@@ -54,20 +72,61 @@ USAGE
54
72
 
55
73
  module CommandHandler
56
74
  class << self
57
- def create(path, project=:default)
58
- raise UnknownProject.new(project) if project != :default # TODO: Support other projects
59
- require 'rubigen'
60
- require 'rubigen/scripts/generate'
61
- source = RubiGen::PathSource.new(:application,
62
- File.join(File.dirname(__FILE__), "../../app_generators"))
63
- RubiGen::Base.reset_sources
64
- RubiGen::Base.append_sources source
65
- RubiGen::Scripts::Generate.new.run([path], :generator => 'ahn')
75
+
76
+ def create(*args)
77
+ if args.size.zero?
78
+ raise CommandHandler::UnknownCommand.new("Must specify something to create!")
79
+ elsif args.size == 1
80
+ # We're creating a project
81
+ path = args.first
82
+ require 'rubigen'
83
+ require 'rubigen/scripts/generate'
84
+ source = RubiGen::PathSource.new(:application,
85
+ File.join(File.dirname(__FILE__), "../../app_generators"))
86
+ RubiGen::Base.reset_sources
87
+ RubiGen::Base.append_sources source
88
+ RubiGen::Scripts::Generate.new.run([path], :generator => 'ahn')
89
+ elsif args.size == 2
90
+ # We're creating a feature (e.g. a component)
91
+ feature_type, component_name = args
92
+
93
+ if feature_type != "component"
94
+ # At the moment, only components can be created.
95
+ raise CommandHandler::UnknownCommand.new("Don't know how to create '#{feature_type}'")
96
+ end
97
+
98
+ if component_name !~ /^[a-z][\w_]+$/
99
+ raise CommandHandler::ComponentError.new("Component name must be lowercase alphanumeric characters " +
100
+ "and begin with a character")
101
+ end
102
+
103
+ app_path = PathString.from_application_subdirectory Dir.pwd
104
+
105
+ raise PathInvalid.new(Dir.pwd) if app_path.nil?
106
+
107
+ new_component_dir = app_path + "/components/#{component_name}"
108
+ raise ComponentError.new("Component #{component_name} already exists!") if File.exists?(new_component_dir)
109
+
110
+ # Everything's good. Let's create the component
111
+ Dir.mkdir new_component_dir
112
+ File.open(new_component_dir + "/#{component_name}.rb","w") do |file|
113
+ file.puts <<-RUBY
114
+ # See http://docs.adhearsion.com for more information on how to write components or
115
+ # look at the examples in newly-created projects.
116
+ RUBY
117
+ end
118
+ File.open(new_component_dir + "/config.yml","w") do |file|
119
+ file.puts '# You can use this file for component-specific configuration.'
120
+ end
121
+ puts "Created blank component '#{component_name}' at components/#{component_name}"
122
+ else
123
+ raise CommandHandler::UnknownCommand.new("Provided too many arguments to 'create'")
124
+ end
66
125
  end
67
126
 
68
127
  def start(path, daemon=false, pid_file=nil)
69
128
  raise PathInvalid, path unless File.exists? path + "/.ahnrc"
70
- Adhearsion::Initializer.new path, :daemon => daemon, :pid_file => pid_file
129
+ Adhearsion::Initializer.start path, :daemon => daemon, :pid_file => pid_file
71
130
  end
72
131
 
73
132
  def version
@@ -78,26 +137,84 @@ USAGE
78
137
  puts USAGE
79
138
  end
80
139
 
140
+ def enable(type, name)
141
+ case type
142
+ when "component"
143
+ app_path = PathString.from_application_subdirectory Dir.pwd
144
+ if app_path
145
+ disabled_component_path = File.join app_path, "components", "disabled", name
146
+ enabled_component_path = File.join app_path, "components", name
147
+ if File.directory? disabled_component_path
148
+ FileUtils.mv disabled_component_path, enabled_component_path
149
+ puts "Enabled component #{name}"
150
+ else
151
+ raise ComponentError.new("There is no components/disabled directory!")
152
+ end
153
+ else
154
+ raise PathInvalid.new(Dir.pwd)
155
+ end
156
+ else
157
+ raise UnknownCommand.new("enable #{type}")
158
+ end
159
+ end
160
+
161
+ def disable(type, name)
162
+ case type
163
+ when "component"
164
+ app_path = PathString.from_application_subdirectory Dir.pwd
165
+ if app_path
166
+ disabled_dir = File.join app_path, "components", "disabled"
167
+
168
+ disabled_component_path = File.join disabled_dir, name
169
+ enabled_component_path = File.join app_path, "components", name
170
+
171
+ Dir.mkdir disabled_dir unless File.directory?(disabled_dir)
172
+
173
+ if File.directory? enabled_component_path
174
+ if File.directory?(disabled_component_path)
175
+ raise ComponentError.new("There is already a disabled component at #{disabled_component_path}")
176
+ else
177
+ FileUtils.mv enabled_component_path, disabled_component_path
178
+ puts "Disabled component #{name}"
179
+ end
180
+ else
181
+ raise ComponentError.new("Could not find component #{name} at #{enabled_component_path} !")
182
+ end
183
+ else
184
+ raise PathInvalid.new(Dir.pwd)
185
+ end
186
+ else
187
+ raise UnknownCommand.new("disable #{type}")
188
+ end
189
+ end
190
+
81
191
  def method_missing(action, *args)
82
192
  raise UnknownCommand, [action, *args] * " "
83
193
  end
194
+
195
+ private
196
+
84
197
  end
85
198
 
86
- class UnknownCommand < Exception
199
+ class CLIException < Exception; end
200
+
201
+ class UnknownCommand < CLIException
87
202
  def initialize(cmd)
88
- super "Unknown command: #{cmd}\n#{USAGE}"
203
+ super "Unknown command: #{cmd}"
89
204
  end
90
205
  end
91
206
 
92
- class UnknownProject < Exception
207
+ class ComponentError < CLIException; end
208
+
209
+ class UnknownProject < CLIException
93
210
  def initialize(project)
94
211
  super "Application #{project} does not exist! Have you installed it?"
95
212
  end
96
213
  end
97
214
 
98
- class PathInvalid < Exception
215
+ class PathInvalid < CLIException
99
216
  def initialize(path)
100
- super "Directory #{path} does not contain an Adhearsion project!"
217
+ super "Directory #{path} does not belong to an Adhearsion project!"
101
218
  end
102
219
  end
103
220
  end