adhearsion 2.3.5 → 2.4.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -1
  3. data/CHANGELOG.md +14 -0
  4. data/Gemfile +2 -0
  5. data/README.markdown +21 -2
  6. data/adhearsion.gemspec +5 -4
  7. data/features/cli_plugin.feature +41 -0
  8. data/features/cli_start.feature +12 -4
  9. data/features/step_definitions/cli_steps.rb +12 -0
  10. data/features/support/env.rb +1 -1
  11. data/features/support/utils.rb +0 -1
  12. data/lib/adhearsion.rb +4 -1
  13. data/lib/adhearsion/call.rb +92 -22
  14. data/lib/adhearsion/call_controller.rb +19 -15
  15. data/lib/adhearsion/call_controller/dial.rb +157 -25
  16. data/lib/adhearsion/call_controller/menu_dsl/menu_builder.rb +8 -0
  17. data/lib/adhearsion/call_controller/output/async_player.rb +1 -1
  18. data/lib/adhearsion/call_controller/output/formatter.rb +1 -1
  19. data/lib/adhearsion/call_controller/output/player.rb +1 -1
  20. data/lib/adhearsion/calls.rb +2 -0
  21. data/lib/adhearsion/cli_commands.rb +3 -163
  22. data/lib/adhearsion/cli_commands/ahn_command.rb +141 -0
  23. data/lib/adhearsion/cli_commands/plugin_command.rb +74 -0
  24. data/lib/adhearsion/cli_commands/thor_errors.rb +36 -0
  25. data/lib/adhearsion/console.rb +14 -6
  26. data/lib/adhearsion/generators/app/templates/spec/call_controllers/simon_game_spec.rb +36 -36
  27. data/lib/adhearsion/generators/controller/templates/spec/controller_spec.rb +1 -1
  28. data/lib/adhearsion/generators/plugin/templates/plugin-template.gemspec.tt +0 -1
  29. data/lib/adhearsion/generators/plugin/templates/spec/plugin-template/controller_methods_spec.rb.tt +1 -1
  30. data/lib/adhearsion/generators/plugin/templates/spec/spec_helper.rb.tt +0 -1
  31. data/lib/adhearsion/logging.rb +5 -1
  32. data/lib/adhearsion/outbound_call.rb +16 -0
  33. data/lib/adhearsion/punchblock_plugin.rb +0 -2
  34. data/lib/adhearsion/punchblock_plugin/initializer.rb +7 -12
  35. data/lib/adhearsion/version.rb +1 -1
  36. data/spec/adhearsion/call_controller/dial_spec.rb +785 -32
  37. data/spec/adhearsion/call_controller/menu_dsl/menu_builder_spec.rb +10 -0
  38. data/spec/adhearsion/call_controller/output/async_player_spec.rb +1 -1
  39. data/spec/adhearsion/call_controller/output/player_spec.rb +1 -1
  40. data/spec/adhearsion/call_controller/output_spec.rb +3 -3
  41. data/spec/adhearsion/call_controller/record_spec.rb +1 -1
  42. data/spec/adhearsion/call_controller_spec.rb +13 -9
  43. data/spec/adhearsion/call_spec.rb +216 -51
  44. data/spec/adhearsion/calls_spec.rb +1 -1
  45. data/spec/adhearsion/console_spec.rb +20 -9
  46. data/spec/adhearsion/outbound_call_spec.rb +40 -6
  47. data/spec/adhearsion/punchblock_plugin/initializer_spec.rb +9 -21
  48. data/spec/adhearsion/punchblock_plugin_spec.rb +1 -1
  49. data/spec/adhearsion/router_spec.rb +1 -1
  50. data/spec/spec_helper.rb +11 -15
  51. data/spec/support/call_controller_test_helpers.rb +2 -2
  52. data/spec/support/punchblock_mocks.rb +2 -2
  53. metadata +41 -16
@@ -0,0 +1,141 @@
1
+ # encoding: utf-8
2
+
3
+ module Adhearsion
4
+ module CLI
5
+ class AhnCommand < Thor
6
+ map %w(-h --h -help --help) => :help
7
+ map %w(-v --v -version --version) => :version
8
+ map %w(-) => :start
9
+
10
+ register ::Adhearsion::CLI::PluginCommand, 'plugin', 'plugin <command>', 'Plugin Tasks.'
11
+
12
+ check_unknown_options!
13
+
14
+ def self.exit_on_failure?
15
+ true
16
+ end
17
+
18
+ desc "create /path/to/directory", "Create a new Adhearsion application under the given path"
19
+ def create(path)
20
+ require 'adhearsion/generators/app/app_generator'
21
+ Generators::AppGenerator.start
22
+ end
23
+
24
+ desc "generate [generator_name] arguments", Generators.help
25
+ def generate(generator_name = nil, *args)
26
+ if generator_name
27
+ Generators.invoke generator_name
28
+ else
29
+ help 'generate'
30
+ end
31
+ end
32
+
33
+ desc "version", "Shows Adhearsion version"
34
+ def version
35
+ say "Adhearsion v#{Adhearsion::VERSION}"
36
+ exit 0
37
+ end
38
+
39
+ desc "start </path/to/directory>", "Start the Adhearsion server in the foreground with a console"
40
+ method_option :noconsole, type: :boolean, aliases: %w{--no-console}
41
+ def start(path = nil)
42
+ start_app path, options[:noconsole] ? :simple : :console
43
+ end
44
+
45
+ desc "daemon </path/to/directory>", "Start the Adhearsion server in the background"
46
+ method_option :pidfile, :type => :string, :aliases => %w(--pid-file)
47
+ def daemon(path = nil)
48
+ start_app path, :daemon, options[:pidfile]
49
+ end
50
+
51
+ desc "stop </path/to/directory>", "Stop a running Adhearsion server"
52
+ method_option :pidfile, :type => :string, :aliases => %w(--pid-file)
53
+ def stop(path = nil)
54
+ execute_from_app_dir! path
55
+
56
+ pid_file = if options[:pidfile]
57
+ File.exists?(File.expand_path(options[:pidfile])) ?
58
+ options[:pidfile] :
59
+ File.join(path, options[:pidfile])
60
+ else
61
+ path = Dir.pwd
62
+ File.join path, Adhearsion::Initializer::DEFAULT_PID_FILE_NAME
63
+ end
64
+ pid_file = File.expand_path pid_file
65
+
66
+ begin
67
+ pid = File.read(pid_file).to_i
68
+ rescue
69
+ raise PIDReadError, pid_file
70
+ end
71
+
72
+ raise PIDReadError, pid_file if pid.nil?
73
+
74
+ say "Stopping Adhearsion server at #{path} with pid #{pid}"
75
+ waiting_timeout = Time.now + 15
76
+ begin
77
+ ::Process.kill "TERM", pid
78
+ sleep 0.25 while process_exists?(pid) && Time.now < waiting_timeout
79
+ ::Process.kill "KILL", pid
80
+ rescue Errno::ESRCH
81
+ end
82
+
83
+ File.delete pid_file if File.exists? pid_file
84
+ end
85
+
86
+ desc "restart </path/to/directory>", "Restart the Adhearsion server"
87
+ method_option :pidfile, :type => :string, :aliases => %w(--pid-file)
88
+ def restart(path = nil)
89
+ execute_from_app_dir! path
90
+ begin
91
+ invoke :stop
92
+ rescue PIDReadError => e
93
+ puts e.message
94
+ end
95
+ invoke :daemon
96
+ end
97
+
98
+ protected
99
+
100
+ def start_app(path, mode, pid_file = nil)
101
+ execute_from_app_dir! path
102
+ say "Starting Adhearsion server at #{Dir.pwd}"
103
+ Adhearsion::Initializer.start :mode => mode, :pid_file => pid_file
104
+ end
105
+
106
+ def execute_from_app_dir!(path)
107
+ return if in_app? and running_script_ahn?
108
+
109
+ path ||= Dir.pwd if in_app?
110
+
111
+ raise PathRequired, ARGV[0] if path.nil? or path.empty?
112
+
113
+ Dir.chdir path do
114
+ raise PathInvalid, path unless ScriptAhnLoader.in_ahn_application?
115
+ args = ARGV.dup
116
+ args[1] = '.'
117
+ ScriptAhnLoader.exec_script_ahn! args
118
+ end
119
+ end
120
+
121
+ def running_script_ahn?
122
+ $0.to_s == "script/ahn"
123
+ end
124
+
125
+ def in_app?
126
+ ScriptAhnLoader.in_ahn_application? or ScriptAhnLoader.in_ahn_application_subdirectory?
127
+ end
128
+
129
+ def process_exists?(pid = nil)
130
+ # FIXME: Raise some error here
131
+ return false if pid.nil?
132
+ `ps -p #{pid} | sed -e '1d'`.strip.empty?
133
+ end
134
+
135
+ def method_missing(action, *args)
136
+ help
137
+ raise UnknownCommand, [action, *args] * " "
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,74 @@
1
+ # encoding: utf-8
2
+
3
+ module Adhearsion
4
+ module CLI
5
+ class PluginCommand < Thor
6
+
7
+ namespace :plugin
8
+
9
+ desc "create_github_hook", "Creates ahnhub hook to track github commits"
10
+ def create_github_hook
11
+ get_github_vals
12
+ generate_github_webhook
13
+ end
14
+
15
+ desc "create_rubygem_hook", "Creates ahnhub hook to track rubygem updates"
16
+ def create_rubygem_hook
17
+ get_rubygem_vals
18
+
19
+ puts `curl -H 'Authorization:#{ENV['RUBYGEM_AUTH']}' \
20
+ -F 'gem_name=#{ENV['RUBYGEM_NAME']}' \
21
+ -F 'url=http://www.ahnhub.com/gem' \
22
+ https://rubygems.org/api/v1/web_hooks/fire`
23
+ end
24
+
25
+ desc "create_ahnhub_hooks", "Creates ahnhub hooks for both a rubygem and github repo"
26
+ def create_ahnhub_hooks
27
+ create_github_hooks
28
+ create_rubygem_hooks
29
+ end
30
+
31
+ protected
32
+
33
+ def get_rubygem_vals
34
+ ENV['RUBYGEM_NAME'] ||= ask "What's the rubygem name?"
35
+ ENV['RUBYGEM_AUTH'] ||= ask "What's your authorization key for Rubygems?"
36
+ end
37
+
38
+ def get_github_vals
39
+ ENV['GITHUB_USERNAME'] ||= ask "What's your github username?"
40
+ ENV['GITHUB_PASSWORD'] ||= ask "What's your github password?"
41
+ ENV['GITHUB_REPO'] ||= ask "Please enter the owner and repo (for example, 'adhearsion/new-plugin'): "
42
+ end
43
+
44
+ def github_repo_owner
45
+ ENV['GITHUB_REPO'].split('/')[0]
46
+ end
47
+
48
+ def github_repo_name
49
+ ENV['GITHUB_REPO'].split('/')[1]
50
+ end
51
+
52
+ def generate_github_webhook
53
+ require 'net/http'
54
+
55
+ uri = URI("https://api.github.com/repos/#{github_repo_owner}/#{github_repo_name}/hooks")
56
+ req = Net::HTTP::Post.new(uri.to_s)
57
+
58
+ req.basic_auth ENV['GITHUB_USERNAME'], ENV['GITHUB_PASSWORD']
59
+ req.body = {
60
+ name: "web",
61
+ active: true,
62
+ events: ["push", "pull_request"],
63
+ config: {url: "http://ahnhub.com/github"}
64
+ }.to_json
65
+
66
+ req["content-type"] = "application/json"
67
+ Net::HTTP.start(uri.host, uri.port, :use_ssl => true) do |http|
68
+ response = http.request(req)
69
+ puts response.body
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ module Adhearsion
4
+ module CLI
5
+ class UnknownCommand < Thor::Error
6
+ def initialize(cmd)
7
+ super "Unknown command: #{cmd}"
8
+ end
9
+ end
10
+
11
+ class PathRequired < Thor::Error
12
+ def initialize(cmd)
13
+ super "A valid path is required for #{cmd}, unless run from an Adhearson app directory"
14
+ end
15
+ end
16
+
17
+ class UnknownGeneratorError < Thor::Error
18
+ def initialize(gentype)
19
+ puts "Please specify generator to use (#{Adhearsion::Generators.mappings.keys.join(", ")})"
20
+ super "Unknown command: #{gentype}"
21
+ end
22
+ end
23
+
24
+ class PathInvalid < Thor::Error
25
+ def initialize(path)
26
+ super "Directory #{path} does not belong to an Adhearsion project!"
27
+ end
28
+ end
29
+
30
+ class PIDReadError < Thor::Error
31
+ def initialize(path)
32
+ super "Could not read pid from the file #{path}"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -32,13 +32,13 @@ module Adhearsion
32
32
  def run
33
33
  set_prompt
34
34
  Pry.config.command_prefix = "%"
35
- if libedit?
36
- logger.error "Cannot start. You are running Adhearsion on Ruby with libedit. You must use readline for the console to work."
37
- else
35
+ if jruby? || cruby_with_readline?
38
36
  logger.info "Launching Adhearsion Console"
39
37
  @pry_thread = Thread.current
40
38
  pry
41
39
  logger.info "Adhearsion Console exiting"
40
+ else
41
+ logger.error "Unable to launch Adhearsion Console: This version of Ruby is using libedit. You must use readline for the console to work."
42
42
  end
43
43
  end
44
44
 
@@ -65,6 +65,10 @@ module Adhearsion
65
65
  Adhearsion.active_calls
66
66
  end
67
67
 
68
+ def originate(*args, &block)
69
+ Adhearsion::OutboundCall.originate(*args, &block)
70
+ end
71
+
68
72
  def take(call = nil)
69
73
  case call
70
74
  when Call
@@ -101,16 +105,20 @@ module Adhearsion
101
105
  pry
102
106
  end
103
107
 
104
- def libedit?
108
+ def cruby_with_readline?
105
109
  begin
106
110
  # If NotImplemented then this might be libedit
107
111
  Readline.emacs_editing_mode
108
- false
109
- rescue NotImplementedError
110
112
  true
113
+ rescue NotImplementedError
114
+ false
111
115
  end
112
116
  end
113
117
 
118
+ def jruby?
119
+ defined? JRUBY_VERSION
120
+ end
121
+
114
122
  private
115
123
 
116
124
  def set_prompt
@@ -9,56 +9,56 @@ describe SimonGame do
9
9
  let(:long_response) { OpenStruct.new(:response => "55555") }
10
10
  let(:long_number) { "55555" }
11
11
 
12
- let(:mock_call) { mock 'Call' }
12
+ let(:mock_call) { double 'Call' }
13
13
  subject { SimonGame.new(mock_call) }
14
14
 
15
- describe "#random_number" do
15
+ describe "#random_number" do
16
16
 
17
17
  before { subject.stub!(:rand).and_return(example_number) }
18
18
 
19
- it "generates a random number" do
20
- subject.random_number.should eq example_number
19
+ it "generates a random number" do
20
+ subject.random_number.should eq example_number
21
21
  end
22
22
  end
23
23
 
24
- describe "#update_number" do
24
+ describe "#update_number" do
25
25
 
26
26
  before { subject.number = "123" }
27
27
  before { subject.stub!(:random_number).and_return "4" }
28
28
 
29
- it "adds a digit to the end of the number" do
29
+ it "adds a digit to the end of the number" do
30
30
  subject.update_number
31
31
  subject.number.should eq "1234"
32
32
  end
33
33
  end
34
34
 
35
- describe "#collect_attempt" do
35
+ describe "#collect_attempt" do
36
+
37
+ context "when the @number is 1 digit long" do
36
38
 
37
- context "when the @number is 1 digit long" do
38
-
39
39
  before { subject.number = "3" }
40
40
 
41
- it "asks for a 1 digits number" do
41
+ it "asks for a 1 digits number" do
42
42
  subject.should_receive(:ask).with("3", :limit => 1).and_return(example_response)
43
43
  subject.collect_attempt
44
- end
44
+ end
45
45
  end
46
46
 
47
- context "when the @number is 5 digits long" do
48
-
47
+ context "when the @number is 5 digits long" do
48
+
49
49
  before { subject.number = long_number }
50
50
 
51
- it "asks for a 5 digits number" do
51
+ it "asks for a 5 digits number" do
52
52
  subject.should_receive(:ask).with(long_number, :limit => 5).and_return(long_response)
53
53
  subject.collect_attempt
54
- end
54
+ end
55
55
  end
56
56
 
57
57
  context "sets @attempt" do
58
58
 
59
59
  before { subject.number = "12345" }
60
60
 
61
- it "based on the user's response" do
61
+ it "based on the user's response" do
62
62
  subject.should_receive(:ask).with("12345", :limit => 5).and_return(long_response)
63
63
  subject.collect_attempt
64
64
  subject.attempt.should eq long_number
@@ -66,67 +66,67 @@ describe SimonGame do
66
66
  end
67
67
  end
68
68
 
69
- describe "#attempt_correct?" do
70
-
69
+ describe "#attempt_correct?" do
70
+
71
71
  before { subject.number = "7" }
72
72
 
73
- context "with a good attempt" do
73
+ context "with a good attempt" do
74
74
 
75
75
  before { subject.attempt = "7" }
76
76
 
77
- it "returns true" do
77
+ it "returns true" do
78
78
  subject.attempt_correct?.should be_true
79
79
  end
80
- end
80
+ end
81
81
 
82
- context "with a bad attempt" do
82
+ context "with a bad attempt" do
83
83
 
84
84
  before { subject.attempt = "9" }
85
85
 
86
- it "returns true" do
86
+ it "returns true" do
87
87
  subject.attempt_correct?.should be_false
88
88
  end
89
89
  end
90
90
  end
91
91
 
92
- describe "#verify_attempt" do
93
- context "when the user is a good guesser" do
92
+ describe "#verify_attempt" do
93
+ context "when the user is a good guesser" do
94
94
 
95
95
  before { subject.stub!(:attempt_correct?).and_return true }
96
96
 
97
- it "congradulates them" do
97
+ it "congradulates them" do
98
98
  subject.should_receive(:speak).with('good')
99
99
  subject.verify_attempt
100
100
  end
101
- end
101
+ end
102
102
 
103
- context "when the user guesses wrong" do
103
+ context "when the user guesses wrong" do
104
104
 
105
105
  before { subject.number = "12345" }
106
106
  before { subject.attempt = "12346" }
107
107
 
108
- it "congradulates them" do
108
+ it "congradulates them" do
109
109
  subject.should_receive(:speak).with('4 times wrong, try again smarty')
110
110
  subject.verify_attempt
111
111
  end
112
112
  end
113
113
  end
114
114
 
115
- describe "#reset" do
115
+ describe "#reset" do
116
116
 
117
117
  before { subject.reset }
118
118
 
119
- it "sets @number" do
119
+ it "sets @number" do
120
120
  subject.number.should eq ''
121
121
  end
122
122
 
123
- it "sets @attempt" do
123
+ it "sets @attempt" do
124
124
  subject.attempt.should eq ''
125
125
  end
126
126
  end
127
127
 
128
- describe "#run" do
129
- it "loops the loop" do
128
+ describe "#run" do
129
+ it "loops the loop" do
130
130
  subject.should_receive :answer
131
131
  subject.should_receive :reset
132
132
 
@@ -134,9 +134,9 @@ describe SimonGame do
134
134
  subject.should_receive :collect_attempt
135
135
  subject.should_receive(:verify_attempt).and_throw :rspec_loop_stop
136
136
 
137
- catch :rspec_loop_stop do
137
+ catch :rspec_loop_stop do
138
138
  subject.run
139
139
  end
140
140
  end
141
141
  end
142
- end
142
+ end