freeswitcher 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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/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.
@@ -48,8 +54,9 @@ module FSR
48
54
 
49
55
  # Update_session
50
56
 
51
- def update_session
57
+ def update_session(&block)
52
58
  send_data("api uuid_dump #{@session.headers[:unique_id]}\n\n")
59
+ @queue << block if block_given?
53
60
  end
54
61
 
55
62
  def next_step
@@ -60,20 +67,20 @@ module FSR
60
67
  protected
61
68
  def post_init
62
69
  @session = nil # holds the session object
63
- @stack = [] # Keep track of stack for state machine
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,26 +89,24 @@ 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
91
- @stack.pop.call unless @stack.empty?
99
+ @step += 1 if @state.include?(:initiated)
100
+ @queue.pop.call unless @queue.empty?
92
101
  receive_reply(hash_header)
93
102
  end
94
103
  else
95
- @step += 1
96
- @stack.pop.call unless @stack.empty?
104
+ @step += 1 if @state.include?(:initiated)
105
+ @queue.pop.call unless @queue.empty?
97
106
  receive_reply(session_header_and_content)
98
107
  end
99
108
  end
100
109
 
101
- def cmd(&block)
102
- @stack << block
103
- end
104
-
105
110
  end
106
111
  end
107
112
  end
@@ -0,0 +1,3 @@
1
+ module FSR
2
+ VERSION = "0.1.4"
3
+ end
@@ -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
@@ -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