sant0sk1-adhearsion 0.7.999

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. data/LICENSE +456 -0
  2. data/README.txt +5 -0
  3. data/Rakefile +75 -0
  4. data/adhearsion.gemspec +136 -0
  5. data/app_generators/ahn/USAGE +5 -0
  6. data/app_generators/ahn/ahn_generator.rb +77 -0
  7. data/app_generators/ahn/templates/.ahnrc +36 -0
  8. data/app_generators/ahn/templates/README +8 -0
  9. data/app_generators/ahn/templates/Rakefile +18 -0
  10. data/app_generators/ahn/templates/components/simon_game/configuration.rb +0 -0
  11. data/app_generators/ahn/templates/components/simon_game/lib/simon_game.rb +61 -0
  12. data/app_generators/ahn/templates/components/simon_game/test/test_helper.rb +14 -0
  13. data/app_generators/ahn/templates/components/simon_game/test/test_simon_game.rb +31 -0
  14. data/app_generators/ahn/templates/config/startup.rb +53 -0
  15. data/app_generators/ahn/templates/dialplan.rb +4 -0
  16. data/bin/ahn +28 -0
  17. data/bin/ahnctl +68 -0
  18. data/bin/jahn +32 -0
  19. data/lib/adhearsion.rb +32 -0
  20. data/lib/adhearsion/blank_slate.rb +5 -0
  21. data/lib/adhearsion/cli.rb +106 -0
  22. data/lib/adhearsion/component_manager.rb +277 -0
  23. data/lib/adhearsion/core_extensions/all.rb +9 -0
  24. data/lib/adhearsion/core_extensions/array.rb +0 -0
  25. data/lib/adhearsion/core_extensions/custom_daemonizer.rb +45 -0
  26. data/lib/adhearsion/core_extensions/global.rb +1 -0
  27. data/lib/adhearsion/core_extensions/hash.rb +0 -0
  28. data/lib/adhearsion/core_extensions/metaprogramming.rb +17 -0
  29. data/lib/adhearsion/core_extensions/numeric.rb +4 -0
  30. data/lib/adhearsion/core_extensions/proc.rb +0 -0
  31. data/lib/adhearsion/core_extensions/publishable.rb +73 -0
  32. data/lib/adhearsion/core_extensions/relationship_properties.rb +40 -0
  33. data/lib/adhearsion/core_extensions/string.rb +26 -0
  34. data/lib/adhearsion/core_extensions/thread.rb +13 -0
  35. data/lib/adhearsion/core_extensions/thread_safety.rb +7 -0
  36. data/lib/adhearsion/core_extensions/time.rb +0 -0
  37. data/lib/adhearsion/distributed/gateways/dbus_gateway.rb +0 -0
  38. data/lib/adhearsion/distributed/gateways/osa_gateway.rb +0 -0
  39. data/lib/adhearsion/distributed/gateways/rest_gateway.rb +9 -0
  40. data/lib/adhearsion/distributed/gateways/soap_gateway.rb +9 -0
  41. data/lib/adhearsion/distributed/gateways/xmlrpc_gateway.rb +9 -0
  42. data/lib/adhearsion/distributed/peer_finder.rb +0 -0
  43. data/lib/adhearsion/distributed/remote_cli.rb +0 -0
  44. data/lib/adhearsion/events_support.rb +26 -0
  45. data/lib/adhearsion/hooks.rb +57 -0
  46. data/lib/adhearsion/host_definitions.rb +63 -0
  47. data/lib/adhearsion/initializer.rb +246 -0
  48. data/lib/adhearsion/initializer/asterisk.rb +59 -0
  49. data/lib/adhearsion/initializer/configuration.rb +236 -0
  50. data/lib/adhearsion/initializer/database.rb +49 -0
  51. data/lib/adhearsion/initializer/drb.rb +25 -0
  52. data/lib/adhearsion/initializer/freeswitch.rb +22 -0
  53. data/lib/adhearsion/initializer/rails.rb +40 -0
  54. data/lib/adhearsion/logging.rb +92 -0
  55. data/lib/adhearsion/tasks.rb +15 -0
  56. data/lib/adhearsion/tasks/database.rb +5 -0
  57. data/lib/adhearsion/tasks/generating.rb +20 -0
  58. data/lib/adhearsion/tasks/lint.rb +4 -0
  59. data/lib/adhearsion/tasks/testing.rb +37 -0
  60. data/lib/adhearsion/version.rb +9 -0
  61. data/lib/adhearsion/voip/asterisk.rb +10 -0
  62. data/lib/adhearsion/voip/asterisk/agi_server.rb +81 -0
  63. data/lib/adhearsion/voip/asterisk/ami.rb +147 -0
  64. data/lib/adhearsion/voip/asterisk/ami/actions.rb +238 -0
  65. data/lib/adhearsion/voip/asterisk/ami/machine.rb +871 -0
  66. data/lib/adhearsion/voip/asterisk/ami/machine.rl +109 -0
  67. data/lib/adhearsion/voip/asterisk/ami/parser.rb +262 -0
  68. data/lib/adhearsion/voip/asterisk/commands.rb +1284 -0
  69. data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
  70. data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +101 -0
  71. data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
  72. data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
  73. data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
  74. data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
  75. data/lib/adhearsion/voip/call.rb +436 -0
  76. data/lib/adhearsion/voip/call_routing.rb +64 -0
  77. data/lib/adhearsion/voip/commands.rb +9 -0
  78. data/lib/adhearsion/voip/constants.rb +39 -0
  79. data/lib/adhearsion/voip/conveniences.rb +18 -0
  80. data/lib/adhearsion/voip/dial_plan.rb +207 -0
  81. data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
  82. data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
  83. data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
  84. data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
  85. data/lib/adhearsion/voip/dsl/dialplan/parser.rb +71 -0
  86. data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
  87. data/lib/adhearsion/voip/dsl/numerical_string.rb +117 -0
  88. data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
  89. data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
  90. data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
  91. data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
  92. data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
  93. data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
  94. data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
  95. data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +58 -0
  96. data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
  97. metadata +167 -0
@@ -0,0 +1,31 @@
1
+ require File.dirname(__FILE__) + "/test_helper"
2
+
3
+ class TestSimonGame < Test::Unit::TestCase
4
+ attr_reader :game
5
+ def setup
6
+ @game = SimonGame.new
7
+ @game.call_context = mock_call_context
8
+ end
9
+
10
+ def test_game_executes_say_digits_when_asked_to_say_number
11
+ flexmock(game).should_receive(:random_number).and_return("2")
12
+ mock_call_context.should_receive(:say_digits).once.with('2')
13
+ @game.say_number
14
+ end
15
+
16
+
17
+ # Didn't get very far on this
18
+ def xtest_can_play_one_round_and_receive_the_players_score
19
+ flexmock(game).should_receive(:random_number).and_return("2")
20
+ assert_equal(3, 'x')
21
+ end
22
+
23
+ private
24
+ def mock_call_context
25
+ @mock_call_context ||= flexmock("Mock Call Context")
26
+ end
27
+
28
+ def stub_random_number
29
+
30
+ end
31
+ end
@@ -0,0 +1,53 @@
1
+ unless defined? Adhearsion
2
+ if File.exists? File.dirname(__FILE__) + "/../adhearsion/lib/adhearsion.rb"
3
+ # If you wish to freeze a copy of Adhearsion to this app, simply place a copy of Adhearsion
4
+ # into a folder named "adhearsion" within this app's main directory.
5
+ require File.dirname(__FILE__) + "/../adhearsion/lib/adhearsion.rb"
6
+ else
7
+ require 'rubygems'
8
+ gem 'adhearsion', '>= 0.7.999'
9
+ require 'adhearsion'
10
+ end
11
+ end
12
+
13
+ Adhearsion::Configuration.configure do |config|
14
+
15
+ # Supported levels (in increasing severity) -- :debug < :info < :warn < :error < :fatal
16
+ config.logging :level => :info
17
+
18
+ # Whether incoming calls be automatically answered. Defaults to true.
19
+ # config.automatically_answer_incoming_calls = false
20
+
21
+ # Whether the other end hanging up should end the call immediately. Defaults to true.
22
+ # config.end_call_on_hangup = false
23
+
24
+ # Whether to end the call immediately if an unrescued exception is caught. Defaults to true.
25
+ # config.end_call_on_error = false
26
+
27
+ # By default Asterisk is enabled with the default settings
28
+ config.enable_asterisk
29
+ # config.asterisk.enable_ami :host => "127.0.0.1", :username => "admin", :password => "password"
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
35
+
36
+ # Streamlined Rails integration! The first argument should be a relative or absolute path to
37
+ # the Rails app folder with which you're integrating. The second argument must be one of the
38
+ # the following: :development, :production, or :test.
39
+
40
+ # config.enable_rails :path => 'gui', :env => :development
41
+
42
+ # Note: You CANNOT do enable_rails and enable_database at the same time. When you enable Rails,
43
+ # it will automatically connect to same database Rails does and load the Rails app's models.
44
+
45
+ # Configure a database to use ActiveRecord-backed models. See ActiveRecord::Base.establish_connection
46
+ # for the appropriate settings here.
47
+ # config.enable_database :adapter => 'mysql',
48
+ # :username => 'joe',
49
+ # :password => 'secret',
50
+ # :host => 'db.example.org'
51
+ end
52
+
53
+ Adhearsion::Initializer.start_from_init_file(__FILE__, File.dirname(__FILE__) + "/..")
@@ -0,0 +1,4 @@
1
+ adhearsion {
2
+ simon = new_simon_game
3
+ simon.start
4
+ }
data/bin/ahn ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This is the main executable file.
4
+
5
+ # Adhearsion, open source collaboration framework
6
+ # Copyright (C) 2006,2007,2008 Jay Phillips
7
+ #
8
+ # This library is free software; you can redistribute it and/or modify it under
9
+ # the terms of the GNU Lesser General Public License as published by the Free
10
+ # Software Foundation; either version 2.1 of the License, or (at your option)
11
+ # any later version.
12
+ #
13
+ # This library is distributed in the hope that it will be useful, but WITHOUT
14
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15
+ # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
16
+ # details.
17
+ #
18
+ # You should have received a copy of the GNU Lesser General Public License along
19
+ # with this library; if not, write to the Free Software Foundation, Inc.,
20
+ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
+
22
+ $:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
23
+
24
+ require 'rubygems'
25
+ require 'adhearsion'
26
+ require 'adhearsion/cli'
27
+
28
+ Adhearsion::CLI::AhnCommand.execute!
data/bin/ahnctl ADDED
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # ahnctl - Adhearsion daemon controller
4
+ #
5
+ # Adhearsion, open source collaboration framework
6
+ # Copyright (C) 2006,2007,2008 Jay Phillips
7
+ #
8
+ # This library is free software; you can redistribute it and/or
9
+ # modify it under the terms of the GNU Lesser General Public
10
+ # License as published by the Free Software Foundation; either
11
+ # version 2.1 of the License, or (at your option) any later version.
12
+ #
13
+ # This library is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
+ # Lesser General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Lesser General Public
19
+ # License along with this library; if not, write to the Free Software
20
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
+
22
+ USAGE = "Usage: ahnctl start|stop|restart /path/to/adhearsion/app [--pid-file=/path/to/pid_file.pid]"
23
+
24
+ # Blow up if the CLI arguments are invalid
25
+ abort USAGE unless (2..3).include?(ARGV.size) && %w[start stop restart].include?(ARGV.first)
26
+
27
+ # By default, ahnctl will use the version of Adhearsion it was installed with.
28
+
29
+ pid_file = ARGV.pop if ARGV.size == 3
30
+ ahn_command = File.expand_path File.dirname(__FILE__) + "/ahn"
31
+ app_dir = File.expand_path ARGV.last
32
+
33
+ pid_file_path_regexp = /^--pid-file=(.+)$/
34
+ abort USAGE if pid_file && pid_file !~ pid_file_path_regexp
35
+
36
+ # If pid_file is not nil, let's extract the path specified.
37
+ pid_file &&= File.expand_path pid_file[pid_file_path_regexp,1]
38
+
39
+ # If there was no third argument and pid_file is still nil, let's use the default.
40
+ pid_file ||= app_dir + '/adhearsion.pid'
41
+
42
+ abort "Directory is not an Adhearsion application!" unless File.exists?(app_dir + "/.ahnrc")
43
+
44
+ def terminate(pid) `kill -s TERM #{pid} 2> /dev/null` end
45
+ def kill(pid) `kill -s KILL #{pid} 2> /dev/null` end
46
+
47
+ # Even if we're starting Adhearsion, we need to make sure to clean up after any stale
48
+ # pid files. In effect, start is the same as restart.
49
+ puts "Stopping Adhearsion app at #{app_dir}" if %w[stop restart].include? ARGV.first
50
+
51
+ if File.exists?(pid_file)
52
+ # An Adhearsion process may still be running. Let's kill the other one as cleanly as possible
53
+ pid = File.read(pid_file).to_i
54
+
55
+ # Time to spend waiting for Adhearsion to exit
56
+ waiting_timeout = Time.now + 15
57
+
58
+ terminate pid
59
+ sleep 0.25 until `ps -p #{pid} | sed -e '1d'`.strip.empty? || Time.now > waiting_timeout
60
+ kill pid
61
+
62
+ `rm -f #{pid_file}`
63
+ end
64
+
65
+ if ['start', 'restart'].include? ARGV.first
66
+ puts "Starting Adhearsion app at #{app_dir}"
67
+ `#{ahn_command} start daemon #{app_dir} --pid-file=#{pid_file}`
68
+ end
data/bin/jahn ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env jruby
2
+
3
+ # This "jahn" script is "ahn" for JRUBY!
4
+
5
+ # Adhearsion, open source collaboration framework
6
+ # Copyright (C) 2006,2007,2008 Jay Phillips
7
+ #
8
+ # This library is free software; you can redistribute it and/or modify it under
9
+ # the terms of the GNU Lesser General Public License as published by the Free
10
+ # Software Foundation; either version 2.1 of the License, or (at your option)
11
+ # any later version.
12
+ #
13
+ # This library is distributed in the hope that it will be useful, but WITHOUT
14
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15
+ # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
16
+ # details.
17
+ #
18
+ # You should have received a copy of the GNU Lesser General Public License along
19
+ # with this library; if not, write to the Free Software Foundation, Inc.,
20
+ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
+
22
+ $:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
23
+
24
+ require 'rubygems'
25
+ require 'adhearsion'
26
+ require 'adhearsion/cli'
27
+
28
+ # jahn will soon have JRuby-specific behavior such as creating a compiled .jar
29
+ # file from an app.
30
+ # require 'adhearsion/jruby'
31
+
32
+ Adhearsion::CLI::AhnCommand.execute!
data/lib/adhearsion.rb ADDED
@@ -0,0 +1,32 @@
1
+ # Check the Ruby version
2
+ STDERR.puts "WARNING: You are running Adhearsion in an unsupported
3
+ version of Ruby (Ruby #{RUBY_VERSION} #{RUBY_RELEASE_DATE})!
4
+ Please upgrade to at least Ruby v1.8.5." if RUBY_VERSION < "1.8.5"
5
+
6
+ $: << File.expand_path(File.dirname(__FILE__))
7
+
8
+ require 'rubygems'
9
+ require 'adhearsion/version'
10
+ require 'adhearsion/voip/call'
11
+ require 'adhearsion/voip/dial_plan'
12
+ require 'adhearsion/voip/asterisk/special_dial_plan_managers'
13
+ require 'adhearsion/core_extensions/all'
14
+ require 'adhearsion/blank_slate'
15
+ require 'adhearsion/hooks'
16
+ # require 'adhearsion/events_support'
17
+ require 'adhearsion/logging'
18
+ require 'adhearsion/initializer/configuration'
19
+ require 'adhearsion/initializer'
20
+ require 'adhearsion/voip/dsl/numerical_string'
21
+ require 'adhearsion/voip/dsl/dialplan/parser'
22
+ require 'adhearsion/voip/commands'
23
+ require 'adhearsion/voip/asterisk/commands'
24
+ require 'adhearsion/voip/dsl/dialing_dsl'
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
+ CONFIG = {}
31
+ AHN_CONFIG = Configuration.new
32
+ end
@@ -0,0 +1,5 @@
1
+ class BlankSlate
2
+ instance_methods.each do |method|
3
+ undef_method method unless method =~ /^__/ || method == 'instance_eval'
4
+ end
5
+ end
@@ -0,0 +1,106 @@
1
+ require 'fileutils'
2
+
3
+ module Adhearsion
4
+ module CLI
5
+ module AhnCommand
6
+ USAGE = <<USAGE
7
+ Usage:
8
+ ahn create /path/to/directory
9
+ ahn start [daemon] [directory]
10
+ ahn version|-v|--v|-version|--version
11
+ ahn help|-h|--h|--help|-help
12
+
13
+ Under development:
14
+ ahn create:projectname /path/to/directory
15
+ USAGE
16
+
17
+ def self.execute!
18
+ CommandHandler.send(*parse_arguments)
19
+ end
20
+
21
+ def self.parse_arguments(args=ARGV.clone)
22
+ action = args.shift
23
+ case action
24
+ when /^-?-?h(elp)?$/, nil then [:help]
25
+ when /^-?-?v(ersion)?$/ then [:version]
26
+ when /^create(:([\w_.]+))?$/
27
+ [:create, args.shift, $LAST_PAREN_MATCH || :default]
28
+ when 'start'
29
+ pid_file_regexp = /^--pid-file=(.+)$/
30
+ if args.size > 3
31
+ raise CommandHandler::UnknownCommand, "Too many arguments supplied!" if args.size > 3
32
+ elsif args.size == 3
33
+ raise CommandHandler::UnknownCommand, "Unrecognized final argument #{args.last}" unless args.last =~ pid_file_regexp
34
+ pid_file = args.pop[pid_file_regexp, 1]
35
+ else
36
+ pid_file = nil
37
+ end
38
+
39
+ if args.first == 'daemon' && args.size == 2
40
+ path = args.last
41
+ daemon = true
42
+ elsif args.size == 1
43
+ path, daemon = args.first, false
44
+ else
45
+ raise CommandHandler::UnknownCommand, "Invalid format for the start CLI command!"
46
+ end
47
+ [:start, path, daemon, pid_file]
48
+ when '-'
49
+ [:start, Dir.pwd]
50
+ else
51
+ [action, *args]
52
+ end
53
+ end
54
+
55
+ module CommandHandler
56
+ 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')
66
+ end
67
+
68
+ def start(path, daemon=false, pid_file=nil)
69
+ raise PathInvalid, path unless File.exists? path + "/.ahnrc"
70
+ Adhearsion::Initializer.start path, :daemon => daemon, :pid_file => pid_file
71
+ end
72
+
73
+ def version
74
+ puts "Adhearsion v#{Adhearsion::VERSION::STRING}"
75
+ end
76
+
77
+ def help
78
+ puts USAGE
79
+ end
80
+
81
+ def method_missing(action, *args)
82
+ raise UnknownCommand, [action, *args] * " "
83
+ end
84
+ end
85
+
86
+ class UnknownCommand < Exception
87
+ def initialize(cmd)
88
+ super "Unknown command: #{cmd}\n#{USAGE}"
89
+ end
90
+ end
91
+
92
+ class UnknownProject < Exception
93
+ def initialize(project)
94
+ super "Application #{project} does not exist! Have you installed it?"
95
+ end
96
+ end
97
+
98
+ class PathInvalid < Exception
99
+ def initialize(path)
100
+ super "Directory #{path} does not contain an Adhearsion project!"
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,277 @@
1
+ module Adhearsion
2
+
3
+ module Components
4
+ class Manager
5
+ attr_reader :active_components, :host_information, :started_components, :components_with_call_context
6
+ def initialize
7
+ @active_components = {}
8
+ @started_components = []
9
+ @components_with_call_context = {}
10
+ end
11
+
12
+ def [](component_name)
13
+ active_components[component_name].component_class.instance
14
+ end
15
+
16
+ def component(component_name)
17
+ active_components[component_name]
18
+ end
19
+
20
+ def has_component?(component_name)
21
+ active_components.has_key?(component_name)
22
+ end
23
+
24
+ def component_gems
25
+ @repository ||= RubygemsRepository.new
26
+ @repository.adhearsion_gems
27
+ end
28
+
29
+ def load
30
+ return unless File.exist?(AHN_ROOT.component_path)
31
+ component_directories = Dir.glob(File.join(AHN_ROOT.component_path, "*"))
32
+ component_directories.each do |component_directory|
33
+ component_name = File.basename(component_directory).to_sym
34
+ @active_components[component_name] = Component.new(self, component_name, component_directory)
35
+ end
36
+ end
37
+
38
+ def start
39
+ @active_components.keys.each do |name|
40
+ @active_components[name].start
41
+ end
42
+ end
43
+
44
+ def stop
45
+ @started_components.reverse.each do |name|
46
+ @active_components[name].stop
47
+ end
48
+ end
49
+
50
+ class RubygemsRepository
51
+ def initialize
52
+ require 'rubygems'
53
+ Gem.manage_gems
54
+ end
55
+
56
+ def adhearsion_gems
57
+ gems = {}
58
+ Gem.source_index.each {|name, spec| gems[spec.name] = spec.full_gem_path if spec.requirements.include?("adhearsion")}
59
+ gems
60
+ end
61
+ end
62
+ end
63
+
64
+ ClassToGetCallContext = Struct.new(:component_class, :instance_variable)
65
+ class ClassToGetCallContext
66
+ def instantiate_with_call_context(call_context, *args, &block)
67
+ component = component_class.allocate
68
+ component.instance_variable_set(("@"+instance_variable.to_s).to_sym, call_context)
69
+ component.send(:initialize, *args, &block)
70
+ component
71
+ end
72
+ end
73
+
74
+
75
+ #component behavior is shared across components
76
+ module Behavior
77
+ def self.included(component_class)
78
+ component_class.extend(ClassMethods)
79
+ end
80
+
81
+ def component_name
82
+ Component.name
83
+ end
84
+
85
+ def component_description
86
+ Configuration.description || Component.name
87
+ end
88
+
89
+ module ClassMethods
90
+ def add_call_context(params = {:as => :call_context})
91
+ attr_reader params[:as]
92
+ ComponentManager.components_with_call_context[name] = ClassToGetCallContext.new(self, params[:as])
93
+ end
94
+ end
95
+ end
96
+
97
+ class Component
98
+ class << self
99
+ def prepare_component_class(component_module, name)
100
+ component_class_name = name.to_s.camelize
101
+ component_module.module_eval(<<-EVAL, __FILE__, __LINE__)
102
+ class #{component_class_name}
103
+ def self.name
104
+ '#{component_class_name}'
105
+ end
106
+ include Adhearsion::Components::Behavior
107
+ end
108
+ EVAL
109
+ end
110
+ end
111
+
112
+ attr_reader :manager, :name, :path, :component_module, :component_class
113
+ def initialize(manager, name, path)
114
+ @manager = manager
115
+ @name = name
116
+ @path = path
117
+ unless File.exist?(main_file_name)
118
+ gem_path = @manager.component_gems[@name.to_s]
119
+ raise "The component '#{@name}' does not have the main file: #{main_file_name}" unless gem_path
120
+ @path = gem_path
121
+ end
122
+ @started = false
123
+ end
124
+
125
+ def start
126
+ return if @started
127
+ manager.started_components << @name
128
+ @started = true
129
+ @component_module = ComponentModule.new(self) do |component_module|
130
+ Component.prepare_component_class(component_module, @name)
131
+ component_module.load_configuration_file
132
+ end
133
+ @component_module.require(File.join("lib", @name.to_s))
134
+ end
135
+
136
+ def configuration
137
+ @component_module.const_get(:Configuration)
138
+ end
139
+
140
+ def stop
141
+ #@component_class.unload if @component_class && @component_class.respond_to?(:unload)
142
+ end
143
+
144
+ def configuration_file
145
+ File.join(path, configuration_file_name)
146
+ end
147
+
148
+ private
149
+
150
+ def main_file_name
151
+ File.join(@path, "lib", @name.to_s+".rb")
152
+ end
153
+
154
+ def configuration_file_name
155
+ "configuration.rb"
156
+ end
157
+ end
158
+
159
+ class ComponentModule < Module
160
+ # The file with which the Script was instantiated.
161
+ attr_reader :main_file
162
+
163
+ # The directory in which main_file is located, and relative to which
164
+ # #load searches for files before falling back to Kernel#load.
165
+ attr_reader :dir
166
+
167
+ # A hash that maps <tt>filename=>true</tt> for each file that has been
168
+ # required locally by the script. This has the same semantics as <tt>$"</tt>,
169
+ # alias <tt>$LOADED_FEATURES</tt>, except that it is local to this script.
170
+ attr_reader :loaded_features
171
+
172
+ class << self
173
+ alias load new
174
+ end
175
+
176
+ # Creates new Script, and loads _main_file_ in the scope of the Script. If a
177
+ # block is given, the script is passed to it before loading from the file, and
178
+ # constants can be defined as inputs to the script.
179
+ attr_reader :component
180
+ def initialize(component) # :yields: self
181
+ extend ComponentModuleMethods
182
+ @component = component
183
+ @loaded_features = {}
184
+
185
+ const_set :Component, component
186
+ const_set :Configuration, OpenStruct.new(:description => nil)
187
+
188
+ yield self if block_given?
189
+ end
190
+
191
+ def load_configuration_file
192
+ load_in_module(component.configuration_file)
193
+ end
194
+
195
+ # Loads _file_ into this Script. Searches relative to the local dir, that is,
196
+ # the dir of the file given in the original call to
197
+ # <tt>Script.load(file)</tt>, loads the file, if found, into this Script's
198
+ # scope, and returns true. If the file is not found, falls back to
199
+ # <tt>Kernel.load</tt>, which searches on <tt>$LOAD_PATH</tt>, loads the file,
200
+ # if found, into global scope, and returns true. Otherwise, raises
201
+ # <tt>LoadError</tt>.
202
+ #
203
+ # The _wrap_ argument is passed to <tt>Kernel.load</tt> in the fallback case,
204
+ # when the file is not found locally.
205
+ #
206
+ # Typically called from within the main file to load additional sub files, or
207
+ # from those sub files.
208
+
209
+ def load(file, wrap = false)
210
+ load_in_module(File.join(component.path, file))
211
+ true
212
+ rescue MissingFile
213
+ super
214
+ end
215
+
216
+ # Analogous to <tt>Kernel#require</tt>. First tries the local dir, then falls
217
+ # back to <tt>Kernel#require</tt>. Will load a given _feature_ only once.
218
+ #
219
+ # Note that extensions (*.so, *.dll) can be required in the global scope, as
220
+ # usual, but not in the local scope. (This is not much of a limitation in
221
+ # practice--you wouldn't want to load an extension more than once.) This
222
+ # implementation falls back to <tt>Kernel#require</tt> when the argument is an
223
+ # extension or is not found locally.
224
+
225
+ def require(feature)
226
+ unless @loaded_features[feature]
227
+ @loaded_features[feature] = true
228
+ file = feature
229
+ file += ".rb" unless /\.rb$/ =~ file
230
+ load_in_module(File.join(component.path, file))
231
+ end
232
+ rescue MissingFile
233
+ @loaded_features[feature] = false
234
+ super
235
+ end
236
+
237
+ # Raised by #load_in_module, caught by #load and #require.
238
+ class MissingFile < LoadError; end
239
+
240
+ # Loads _file_ in this module's context. Note that <tt>\_\_FILE\_\_</tt> and
241
+ # <tt>\_\_LINE\_\_</tt> work correctly in _file_.
242
+ # Called by #load and #require; not normally called directly.
243
+
244
+ def load_in_module(file)
245
+ module_eval(File.read(file), File.expand_path(file))
246
+ rescue Errno::ENOENT => e
247
+ if /#{file}$/ =~ e.message
248
+ raise MissingFile, e.message
249
+ else
250
+ raise
251
+ end
252
+ end
253
+
254
+ def to_s
255
+ "#<#{self.class}:#{File.basename(component.path)}>"
256
+ end
257
+
258
+ module ComponentModuleMethods
259
+ # This is so that <tt>def meth...</tt> behaves like in Ruby's top-level
260
+ # context. The implementation simply calls
261
+ # <tt>Module#module_function(name)</tt>.
262
+ def method_added(name) # :nodoc:
263
+ module_function(name)
264
+ end
265
+
266
+ def start_component_after(*others)
267
+ others.each do |component_name|
268
+ component.manager.active_components[component_name].start
269
+ end
270
+ end
271
+
272
+ end
273
+ end
274
+ end
275
+
276
+ ComponentManager = Components::Manager.new unless defined? ComponentManager
277
+ end