bougyman-freeswitcher 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,155 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{freeswitcher}
5
+ s.version = "0.1.4"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Jayson Vaughn", "Michael Fellinger", "Kevin Berry", "TJ Vanderpoel"]
9
+ s.date = %q{2009-05-11}
10
+ s.description = %q{========================================================= FreeSWITCHeR Copyright (c) 2009 The Rubyists (Jayson Vaughn, Tj Vanderpoel, Michael Fellinger, Kevin Berry) Distributed under the terms of the MIT License. ========================================================== ABOUT ----- A ruby library for interacting with the "FreeSWITCH" (http://www.freeswitch.org) opensource telephony platform REQUIREMENTS ------------ - ruby (>= 1.8) - eventmachine (If you wish to use Outbound and Inbound listener) USAGE ----- An Outbound Event Listener Example that reads and returns DTMF input: -------------------------------------------------------------------- Simply just create a subclass of FSR::Listner::Outbound and all new calls/sessions will invoke the "session_initiated" callback method. * NOTE: FSR uses blocks within the 'session_inititated' method to ensure that the next "freeswich command" is not executed until the previous "Freeswitch command" has finished. This is kicked off by "answer do" require 'fsr' require 'fsr/listener/outbound' class OutboundDemo < FSR::Listener::Outbound def session_initiated exten = @session.headers[:caller_caller_id_number] FSR::Log.info "*** Answering incoming call from #{exten}" answer do FSR::Log.info "***Reading DTMF from #{exten}" read("/home/freeswitch/freeswitch/sounds/music/8000/sweet.wav", 4, 10, "input", 7000) do FSR::Log.info "*** Updating session for #{exten}" update_session do FSR::Log.info "***Success, grabbed #{@session.headers[:variable_input].strip} from #{exten}" hangup #Hangup the call end end end end end FSR.start_oes! OutboundDemo, :port => 8084, :host => "127.0.0.1" An Inbound Event Socket Listener example using FreeSWITCHeR's hook system: -------------------------------------------------------------------------- require 'pp' require 'fsr' require "fsr/listener/inbound" # EXAMPLE 1 # This adds a hook on CHANNEL_CREATE events. You can also create a method to handle the event you're after. See the next example FSL::Inbound.add_event_hook(:CHANNEL_CREATE) {|event| FSR::Log.info "*** [#{event.content[:unique_id]}] Channel created - greetings from the hook!" } # EXAMPLE 2 # Define a method to handle CHANNEL_HANGUP events. def custom_channel_hangup_handler(event) FSR::Log.info "*** [#{event.content[:unique_id]}] Channel hangup. The event:" pp event end # This adds a hook for EXAMPLE 2 FSL::Inbound.add_event_hook(:CHANNEL_HANGUP) {|event| custom_channel_hangup_handler(event) } # Start FSR Inbound Listener FSR.start_ies!(FSL::Inbound, :host => "localhost", :port => 8021) An Inbound Event Socket Listener example using the on_event callback method instead of hooks: --------------------------------------------------------------------------------------------- require 'pp' require 'fsr' require "fsr/listener/inbound" class IesDemo < FSR::Listener::Inbound def on_event(event) pp event.headers pp event.content[:event_name] end end FSR.start_ies!(IesDemo, :host => "localhost", :port => 8021) An example of using FSR::CommandSocket to originate a new call in irb: ---------------------------------------------------------------------- irb(main):001:0> require 'fsr' => true irb(main):002:0> FSR.load_all_commands => [:sofia, :originate] irb(main):003:0> sock = FSR::CommandSocket.new => #<FSR::CommandSocket:0xb7a89104 @server="127.0.0.1", @socket=#<TCPSocket:0xb7a8908c>, @port="8021", @auth="ClueCon"> irb(main):007:0> sock.originate(:target => 'sofia/gateway/carlos/8179395222', :endpoint => FSR::App::Bridge.new("user/bougyman")).run => {"Job-UUID"=>"732075a4-7dd5-4258-b124-6284a82a5ae7", "body"=>"", "Content-Type"=>"command/reply", "Reply-Text"=>"+OK Job-UUID: 732075a4-7dd5-4258-b124-6284a82a5ae7"} SUPPORT ------- Home page at http://code.rubyists.com/projects/fs #rubyists on FreeNode}
11
+ s.email = %q{FreeSWITCHeR@rubyists.com}
12
+ s.files = [".gitignore", "AUTHORS", "CHANGELOG", "License.txt", "MANIFEST", "NEWS", "README", "Rakefile", "examples/bin/cmd_demo.rb", "examples/bin/dtmf_test.rb", "examples/bin/ies_demo.rb", "examples/bin/ies_demo_with_hook.rb", "examples/bin/oes_demo.rb", "examples/dtmf_test.rb", "examples/ies_demo.rb", "examples/ies_demo_with_hook.rb", "examples/inbound_event_socket.rb", "examples/inbound_socket_events.rb", "examples/outbound_event_socket.rb", "freeswitcher.gemspec", "lib/fsr.rb", "lib/fsr/app.rb", "lib/fsr/app/answer.rb", "lib/fsr/app/bridge.rb", "lib/fsr/app/conference.rb", "lib/fsr/app/fifo.rb", "lib/fsr/app/fs_break.rb", "lib/fsr/app/fs_sleep.rb", "lib/fsr/app/hangup.rb", "lib/fsr/app/limit.rb", "lib/fsr/app/log.rb", "lib/fsr/app/play_and_get_digits.rb", "lib/fsr/app/playback.rb", "lib/fsr/app/read.rb", "lib/fsr/app/set.rb", "lib/fsr/app/speak.rb", "lib/fsr/app/transfer.rb", "lib/fsr/app/uuid_dump.rb", "lib/fsr/app/uuid_getvar.rb", "lib/fsr/app/uuid_setvar.rb", "lib/fsr/cmd.rb", "lib/fsr/cmd/calls.rb", "lib/fsr/cmd/fsctl.rb", "lib/fsr/cmd/originate.rb", "lib/fsr/cmd/sofia.rb", "lib/fsr/cmd/sofia/profile.rb", "lib/fsr/cmd/sofia/status.rb", "lib/fsr/cmd/sofia_contact.rb", "lib/fsr/cmd/status.rb", "lib/fsr/command_socket.rb", "lib/fsr/database.rb", "lib/fsr/database/call_limit.rb", "lib/fsr/database/core.rb", "lib/fsr/database/sofia_reg_external.rb", "lib/fsr/database/sofia_reg_internal.rb", "lib/fsr/database/voicemail_default.rb", "lib/fsr/event_socket.rb", "lib/fsr/fake_socket.rb", "lib/fsr/listener.rb", "lib/fsr/listener/header_and_content_response.rb", "lib/fsr/listener/inbound.rb", "lib/fsr/listener/inbound/event.rb", "lib/fsr/listener/outbound.rb", "lib/fsr/listener/outbound.rb.orig", "lib/fsr/model/call.rb", "lib/fsr/version.rb", "tasks/authors.rake", "tasks/bacon.rake", "tasks/changelog.rake", "tasks/copyright.rake", "tasks/gem.rake", "tasks/gem_installer.rake", "tasks/install_dependencies.rake", "tasks/manifest.rake", "tasks/rcov.rake", "tasks/release.rake", "tasks/reversion.rake", "tasks/setup.rake", "tasks/spec.rake", "tasks/yard.rake", "spec/helper.rb", "spec/fsr/app.rb", "spec/fsr/app/bridge.rb", "spec/fsr/app/conference.rb", "spec/fsr/app/fifo.rb", "spec/fsr/app/hangup.rb", "spec/fsr/app/limit.rb", "spec/fsr/app/log.rb", "spec/fsr/app/play_and_get_digits.rb", "spec/fsr/app/playback.rb", "spec/fsr/app/set.rb", "spec/fsr/app/transfer.rb", "spec/fsr/cmd.rb", "spec/fsr/cmd/calls.rb", "spec/fsr/cmd/originate.rb", "spec/fsr/cmd/sofia.rb", "spec/fsr/cmd/sofia/profile.rb", "spec/fsr/listener.rb", "spec/fsr/listener/inbound.rb", "spec/fsr/listener/outbound.rb", "spec/fsr/loading.rb"]
13
+ s.homepage = %q{http://code.rubyists.com/projects/fs}
14
+ s.post_install_message = %q{=========================================================
15
+ FreeSWITCHeR
16
+ Copyright (c) 2009 The Rubyists (Jayson Vaughn, Tj Vanderpoel, Michael Fellinger, Kevin Berry)
17
+ Distributed under the terms of the MIT License.
18
+ ==========================================================
19
+
20
+ ABOUT
21
+ -----
22
+ A ruby library for interacting with the "FreeSWITCH" (http://www.freeswitch.org) opensource telephony platform
23
+
24
+ REQUIREMENTS
25
+ ------------
26
+ - ruby (>= 1.8)
27
+ - eventmachine (If you wish to use Outbound and Inbound listener)
28
+
29
+ USAGE
30
+ -----
31
+
32
+ An Outbound Event Listener Example that reads and returns DTMF input:
33
+ --------------------------------------------------------------------
34
+
35
+ Simply just create a subclass of FSR::Listner::Outbound and all
36
+ new calls/sessions will invoke the "session_initiated" callback method.
37
+
38
+ * NOTE: FSR uses blocks within the 'session_inititated' method to ensure
39
+ that the next "freeswich command" is not executed until the previous
40
+ "Freeswitch command" has finished. This is kicked off by "answer do"
41
+
42
+ require 'fsr'
43
+ require 'fsr/listener/outbound'
44
+
45
+ class OutboundDemo < FSR::Listener::Outbound
46
+
47
+ def session_initiated
48
+ exten = @session.headers[:caller_caller_id_number]
49
+ FSR::Log.info "*** Answering incoming call from #{exten}"
50
+
51
+ answer do
52
+ FSR::Log.info "***Reading DTMF from #{exten}"
53
+ read("/home/freeswitch/freeswitch/sounds/music/8000/sweet.wav", 4, 10, "input", 7000) do
54
+ FSR::Log.info "*** Updating session for #{exten}"
55
+ update_session do
56
+ FSR::Log.info "***Success, grabbed #{@session.headers[:variable_input].strip} from #{exten}"
57
+ hangup #Hangup the call
58
+ end
59
+ end
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+
66
+ FSR.start_oes! OutboundDemo, :port => 8084, :host => "127.0.0.1"
67
+
68
+
69
+ An Inbound Event Socket Listener example using FreeSWITCHeR's hook system:
70
+ --------------------------------------------------------------------------
71
+
72
+ require 'pp'
73
+ require 'fsr'
74
+ require "fsr/listener/inbound"
75
+
76
+ # EXAMPLE 1
77
+ # This adds a hook on CHANNEL_CREATE events. You can also create a method to handle the event you're after. See the next example
78
+ FSL::Inbound.add_event_hook(:CHANNEL_CREATE) {|event| FSR::Log.info "*** [#{event.content[:unique_id]}] Channel created - greetings from the hook!" }
79
+
80
+ # EXAMPLE 2
81
+ # Define a method to handle CHANNEL_HANGUP events.
82
+ def custom_channel_hangup_handler(event)
83
+ FSR::Log.info "*** [#{event.content[:unique_id]}] Channel hangup. The event:"
84
+ pp event
85
+ end
86
+
87
+ # This adds a hook for EXAMPLE 2
88
+ FSL::Inbound.add_event_hook(:CHANNEL_HANGUP) {|event| custom_channel_hangup_handler(event) }
89
+
90
+
91
+ # Start FSR Inbound Listener
92
+ FSR.start_ies!(FSL::Inbound, :host => "localhost", :port => 8021)
93
+
94
+
95
+ An Inbound Event Socket Listener example using the on_event callback method instead of hooks:
96
+ ---------------------------------------------------------------------------------------------
97
+
98
+ require 'pp'
99
+ require 'fsr'
100
+ require "fsr/listener/inbound"
101
+
102
+
103
+ class IesDemo < FSR::Listener::Inbound
104
+
105
+ def on_event(event)
106
+ pp event.headers
107
+ pp event.content[:event_name]
108
+ end
109
+
110
+ end
111
+
112
+ FSR.start_ies!(IesDemo, :host => "localhost", :port => 8021)
113
+
114
+
115
+ An example of using FSR::CommandSocket to originate a new call in irb:
116
+ ----------------------------------------------------------------------
117
+
118
+ irb(main):001:0> require 'fsr'
119
+ => true
120
+
121
+ irb(main):002:0> FSR.load_all_commands
122
+ => [:sofia, :originate]
123
+
124
+ irb(main):003:0> sock = FSR::CommandSocket.new
125
+ => #<FSR::CommandSocket:0xb7a89104 @server="127.0.0.1", @socket=#<TCPSocket:0xb7a8908c>, @port="8021", @auth="ClueCon">
126
+
127
+ irb(main):007:0> sock.originate(:target => 'sofia/gateway/carlos/8179395222', :endpoint => FSR::App::Bridge.new("user/bougyman")).run
128
+ => {"Job-UUID"=>"732075a4-7dd5-4258-b124-6284a82a5ae7", "body"=>"", "Content-Type"=>"command/reply", "Reply-Text"=>"+OK Job-UUID: 732075a4-7dd5-4258-b124-6284a82a5ae7"}
129
+
130
+
131
+
132
+ SUPPORT
133
+ -------
134
+ Home page at http://code.rubyists.com/projects/fs
135
+ #rubyists on FreeNode
136
+ }
137
+ s.require_paths = ["lib"]
138
+ s.rubyforge_project = %q{freeswitcher}
139
+ s.rubygems_version = %q{1.3.1}
140
+ s.summary = %q{A library for interacting with the "FreeSWITCH":http://freeswitch.org telephony platform}
141
+ s.test_files = ["spec/fsr/app.rb", "spec/fsr/app/bridge.rb", "spec/fsr/app/conference.rb", "spec/fsr/app/fifo.rb", "spec/fsr/app/hangup.rb", "spec/fsr/app/limit.rb", "spec/fsr/app/log.rb", "spec/fsr/app/play_and_get_digits.rb", "spec/fsr/app/playback.rb", "spec/fsr/app/set.rb", "spec/fsr/app/transfer.rb", "spec/fsr/cmd.rb", "spec/fsr/cmd/calls.rb", "spec/fsr/cmd/originate.rb", "spec/fsr/cmd/sofia.rb", "spec/fsr/cmd/sofia/profile.rb", "spec/fsr/listener.rb", "spec/fsr/listener/inbound.rb", "spec/fsr/listener/outbound.rb", "spec/fsr/loading.rb"]
142
+
143
+ if s.respond_to? :specification_version then
144
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
145
+ s.specification_version = 2
146
+
147
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
148
+ s.add_runtime_dependency(%q<eventmachine>, [">= 0"])
149
+ else
150
+ s.add_dependency(%q<eventmachine>, [">= 0"])
151
+ end
152
+ else
153
+ s.add_dependency(%q<eventmachine>, [">= 0"])
154
+ end
155
+ end
data/lib/fsr.rb CHANGED
@@ -3,11 +3,15 @@ require 'socket'
3
3
  require 'pathname'
4
4
  require 'pp'
5
5
 
6
+ # Author:: TJ Vanderpoel (mailto:bougy.man@gmail.com)
7
+ # Copyright:: Copyright (c) 2009 The Rubyists (Jayson Vaughn, TJ Vanderpoel, Michael Fellinger, Kevin Berry)
8
+ # License:: Distributes under the terms of the MIT License http://www.opensource.org/licenses/mit-license.php
9
+
10
+ ## This module declares the namespace under which the freeswitcher framework
11
+ ## Any constants will be defined here, as well as methods for loading commands and applications
6
12
  module FSR
7
13
  # Global configuration options
8
- #
9
- VERSION = '0.1.3'
10
- FS_INSTALL_PATHS = ["/usr/local/freeswitch", "/opt/freeswitch", "/usr/freeswitch"]
14
+ FS_INSTALL_PATHS = ["/usr/local/freeswitch", "/opt/freeswitch", "/usr/freeswitch", "/home/freeswitch/freeswitch"]
11
15
  DEFAULT_CALLER_ID_NUMBER = '8675309'
12
16
  DEFAULT_CALLER_ID_NAME = "FSR"
13
17
 
@@ -48,8 +52,13 @@ module FSR
48
52
  end
49
53
 
50
54
  # Method to start EM for Inbound Event Socket
55
+ # @see FSR::Listener::Inbound
56
+ # @param [FSR::Listener::Inbound] klass An Inbound Listener class, to be started by EM.run
57
+ # @param [::Hash] args A hash of options, may contain
58
+ # <tt>:host [String]</tt> The host/ip to bind to (Default: "localhost")
59
+ # <tt>:port [Integer]</tt> the port to listen on (Default: 8021)
51
60
  def self.start_ies!(klass, args = {})
52
- port = args[:port] || "8021"
61
+ port = args[:port] || 8021
53
62
  host = args[:host] || "localhost"
54
63
  EM.run do
55
64
  EventMachine::connect(host, port, klass)
@@ -83,4 +92,5 @@ module FSR
83
92
  FS_CONFIG_PATH = FS_DB_PATH = nil
84
93
  end
85
94
  end
95
+ require "fsr/version"
86
96
 
@@ -13,7 +13,13 @@ module FSR
13
13
  include FSR::App
14
14
 
15
15
  # Redefine the FSR::App methods to wrap sendmsg around them
16
- SENDMSG_METHOD_DEFINITION = "def %s(*args, &block); sendmsg super; end"
16
+ SENDMSG_METHOD_DEFINITION = [
17
+ "def %s(*args, &block)",
18
+ " sendmsg super",
19
+ " @queue << block if block_given?",
20
+ "end"
21
+ ].join("\n")
22
+
17
23
  APPLICATIONS.each { |app, obj| module_eval(SENDMSG_METHOD_DEFINITION % app.to_s) }
18
24
 
19
25
  # session_initiated is called when a @session is first created.
@@ -31,7 +37,6 @@ module FSR
31
37
  # @param reply This HeaderAndContent instance will have the channel variables
32
38
  # in #content, if the session has been updated
33
39
  def receive_reply(reply)
34
- FSR::Log.warn "#{self.class.name}#receive_reply not overwritten"
35
40
  FSR::Log.debug reply.inspect
36
41
  end
37
42
 
@@ -49,8 +54,9 @@ module FSR
49
54
 
50
55
  # Update_session
51
56
 
52
- def update_session
57
+ def update_session(&block)
53
58
  send_data("api uuid_dump #{@session.headers[:unique_id]}\n\n")
59
+ @queue << block if block_given?
54
60
  end
55
61
 
56
62
  def next_step
@@ -61,19 +67,20 @@ module FSR
61
67
  protected
62
68
  def post_init
63
69
  @session = nil # holds the session object
70
+ @queue = [] # Keep track of queue for state machine
64
71
  send_data("connect\n\n")
65
72
  FSR::Log.debug "Accepting connections."
66
73
  end
67
74
 
68
75
  # receive_request is called each time data is received by the event machine
69
- # it will manipulate the received data into either a new session or a reply,
70
- # to be picked up by #session_initiated or #receive_reply.
71
- # If your listener is listening for events, this will also renew your @session
72
- # each time you receive a CHANNEL_DATA event.
76
+ # it will manipulate the received data into either a new session or a reply,
77
+ # to be picked up by #session_initiated or #receive_reply.
78
+ # If your listener is listening for events, this will also renew your @session
79
+ # each time you receive a CHANNEL_DATA event.
73
80
  # @param header The header of the request, as passed by HeaderAndContentProtocol
74
81
  # @param content The content of the request, as passed by HeaderAndContentProtocol
75
82
  #
76
- # @returns HeaderAndContentResponse
83
+ # @return [HeaderAndContentResponse] An EventMachine HeaderAndContentResponse
77
84
  def receive_request(header, content)
78
85
  hash_header = headers_2_hash(header)
79
86
  hash_content = headers_2_hash(content)
@@ -82,16 +89,20 @@ module FSR
82
89
  if @session.nil?
83
90
  @session = session_header_and_content
84
91
  @step = 0
92
+ @state = [:uninitiated]
85
93
  session_initiated
94
+ @state << :initiated
86
95
  elsif session_header_and_content.content[:event_name] # If content includes an event_name, it must be a response from an api command
87
96
  if session_header_and_content.content[:event_name].to_s.match(/CHANNEL_DATA/i) # Anytime we see CHANNEL_DATA event, we want to update our @session
88
97
  session_header_and_content = HeaderAndContentResponse.new({:headers => hash_header.merge(hash_content.strip_value_newlines), :content => {}})
89
98
  @session = session_header_and_content
90
- @step += 1
99
+ @step += 1 if @state.include?(:initiated)
100
+ @queue.pop.call unless @queue.empty?
91
101
  receive_reply(hash_header)
92
102
  end
93
103
  else
94
- @step += 1
104
+ @step += 1 if @state.include?(:initiated)
105
+ @queue.pop.call unless @queue.empty?
95
106
  receive_reply(session_header_and_content)
96
107
  end
97
108
  end
@@ -0,0 +1,3 @@
1
+ module FSR
2
+ VERSION = "0.1.4"
3
+ end
data/spec/helper.rb CHANGED
@@ -1,12 +1,17 @@
1
1
  begin
2
2
  require 'bacon'
3
3
  rescue LoadError
4
- puts <<-EOS
5
- To run these tests you must install bacon.
6
- Quick and easy install for gem:
7
- gem install bacon
8
- EOS
9
- exit(0)
4
+ begin
5
+ require "rubygems"
6
+ require "bacon"
7
+ rescue LoadError
8
+ puts <<-EOS
9
+ To run these tests you must install bacon.
10
+ Quick and easy install for gem:
11
+ gem install bacon
12
+ EOS
13
+ exit(0)
14
+ end
10
15
  end
11
16
 
12
17
  Bacon.summary_on_exit
@@ -0,0 +1,30 @@
1
+ # Once git has a fix for the glibc in handling .mailmap and another fix for
2
+ # allowing empty mail address to be mapped in .mailmap we won't have to handle
3
+ # them manually.
4
+
5
+ desc 'Update AUTHORS'
6
+ task :authors do
7
+ authors = Hash.new(0)
8
+
9
+ `git shortlog -nse`.scan(/(\d+)\s(.+)\s<(.*)>$/) do |count, name, email|
10
+ case name
11
+ when "bougyman"
12
+ name, email = "TJ Vanderpoel", "bougy.man@gmail.com"
13
+ when /riscfuture/i
14
+ name, email = "Tim Morgan", "riscfuture@gmail.com"
15
+ when "Michael Fellinger m.fellinger@gmail.com"
16
+ name, email = "Michael Fellinger", "m.fellinger@gmail.com"
17
+ end
18
+
19
+ authors[[name, email]] += count.to_i
20
+ end
21
+
22
+ File.open('AUTHORS', 'w+') do |io|
23
+ io.puts "Following persons have contributed to #{GEMSPEC.name}."
24
+ io.puts '(Sorted by number of submitted patches, then alphabetically)'
25
+ io.puts ''
26
+ authors.sort_by{|(n,e),c| [-c, n.downcase] }.each do |(name, email), count|
27
+ io.puts("%6d %s <%s>" % [count, name, email])
28
+ end
29
+ end
30
+ end
data/tasks/bacon.rake ADDED
@@ -0,0 +1,66 @@
1
+ desc 'Run all bacon specs with pretty output'
2
+ task :bacon => :install_dependencies do
3
+ require 'open3'
4
+ require 'scanf'
5
+ require 'matrix'
6
+
7
+ specs = PROJECT_SPECS
8
+
9
+ some_failed = false
10
+ specs_size = specs.size
11
+ len = specs.map{|s| s.size }.sort.last
12
+ total_tests = total_assertions = total_failures = total_errors = 0
13
+ totals = Vector[0, 0, 0, 0]
14
+
15
+ red, yellow, green = "\e[31m%s\e[0m", "\e[33m%s\e[0m", "\e[32m%s\e[0m"
16
+ left_format = "%4d/%d: %-#{len + 11}s"
17
+ spec_format = "%d specifications (%d requirements), %d failures, %d errors"
18
+
19
+ specs.each_with_index do |spec, idx|
20
+ print(left_format % [idx + 1, specs_size, spec])
21
+
22
+ Open3.popen3(RUBY, spec) do |sin, sout, serr|
23
+ out = sout.read.strip
24
+ err = serr.read.strip
25
+
26
+ # this is conventional, see spec/innate/state/fiber.rb for usage
27
+ if out =~ /^Bacon::Error: (needed .*)/
28
+ puts(yellow % ("%6s %s" % ['', $1]))
29
+ else
30
+ total = nil
31
+
32
+ out.each_line do |line|
33
+ scanned = line.scanf(spec_format)
34
+
35
+ next unless scanned.size == 4
36
+
37
+ total = Vector[*scanned]
38
+ break
39
+ end
40
+
41
+ if total
42
+ totals += total
43
+ tests, assertions, failures, errors = total_array = total.to_a
44
+
45
+ if tests > 0 && failures + errors == 0
46
+ puts((green % "%6d passed") % tests)
47
+ else
48
+ some_failed = true
49
+ puts(red % " failed")
50
+ puts out unless out.empty?
51
+ puts err unless err.empty?
52
+ end
53
+ else
54
+ some_failed = true
55
+ puts(red % " failed")
56
+ puts out unless out.empty?
57
+ puts err unless err.empty?
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ total_color = some_failed ? red : green
64
+ puts(total_color % (spec_format % totals.to_a))
65
+ exit 1 if some_failed
66
+ end
@@ -0,0 +1,19 @@
1
+ require 'time'
2
+ desc 'update changelog'
3
+ task :changelog do
4
+ File.open('CHANGELOG', 'w+') do |changelog|
5
+ `git log -z --abbrev-commit`.split("\0").each do |commit|
6
+ next if commit =~ /^Merge: \d*/
7
+ ref, author, time, _, title, _, message = commit.split("\n", 7)
8
+ ref = ref[/commit ([0-9a-f]+)/, 1]
9
+ author = author[/Author: (.*)/, 1].strip
10
+ time = Time.parse(time[/Date: (.*)/, 1]).utc
11
+ title.strip!
12
+
13
+ changelog.puts "[#{ref} | #{time}] #{author}"
14
+ changelog.puts '', " * #{title}"
15
+ changelog.puts '', message.rstrip if message
16
+ changelog.puts
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ desc "add copyright to all .rb files in the distribution"
2
+ task :copyright do
3
+ ignore = File.readlines('License.txt').
4
+ select{|line| line.strip!; File.exist?(line)}.
5
+ map{|file| File.expand_path(file)}
6
+
7
+ puts "adding copyright to files that don't have it currently"
8
+ puts PROJECT_COPYRIGHT
9
+ puts
10
+
11
+ Dir['{lib,test}/**/*{.rb}'].each do |file|
12
+ file = File.expand_path(file)
13
+ next if ignore.include? file
14
+ lines = File.readlines(file).map{|l| l.chomp}
15
+ unless lines.first(PROJECT_COPYRIGHT.size) == PROJECT_COPYRIGHT
16
+ puts "#{file} seems to need attention, first 4 lines:"
17
+ puts lines[0..3]
18
+ puts
19
+ end
20
+ end
21
+ end