Skip to content

Commit

Permalink
Merge 1d2ceb3 into 64a8431
Browse files Browse the repository at this point in the history
  • Loading branch information
sfgeorge committed Jan 6, 2017
2 parents 64a8431 + 1d2ceb3 commit 77f1ab5
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 27 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# [develop](https://github.com/benlangfeld/ruby_speech)
* Feature: Permit percentage rate values for prosody tags
* Bugfix: Rulerefs referenced n-levels deep under Rulerefs should be expanded.
* Bugfix: Optimize performance of built-in number DTMF grammar

# [2.3.2](https://github.com/benlangfeld/ruby_speech/compare/v2.3.1...v2.3.2) - [2014-04-21](https://rubygems.org/gems/ruby_speech/versions/2.3.2)
* Bugfix: String nodes should take non-strings and cast to a string (`#to_s`)
Expand Down
29 changes: 18 additions & 11 deletions lib/ruby_speech/grxml/builtins.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ def self.date(options = nil)
# @option options [#to_i] :maxlength Maximum length for the string of digits.
# @option options [#to_i] :length Absolute length for the string of digits.
#
# @return [RubySpeech::GRXML::Grammar] a grammar for interpreting a boolean response.
# @return [RubySpeech::GRXML::Grammar] a grammar for interpreting an integer
# response.
#
# @raise [ArgumentError] if any of the length attributes logically conflict
#
Expand Down Expand Up @@ -118,22 +119,28 @@ def self.currency(options = nil)
def self.number(options = nil)
RubySpeech::GRXML.draw mode: :dtmf, root: 'number' do
rule id: 'number', scope: 'public' do
item repeat: '0-' do
ruleref uri: '#digit'
one_of do
item { ruleref uri: '#less_than_one' }
item { ruleref uri: '#one_or_more' }
end
end

rule id: 'less_than_one' do
item { '*' }
item { ruleref uri: '#digit_series' }
end

rule id: 'one_or_more' do
item { ruleref uri: '#digit_series' }
item repeat: '0-1' do
item { '*' }
item repeat: '0-' do
ruleref uri: '#digit'
end
item(repeat: '0-1') { ruleref uri: '#digit_series' }
end
end

rule id: 'digit' do
one_of do
0.upto(9) { |d| item { d.to_s } }
end
end
rule(id: 'digit_series') { item(repeat: '1-') { ruleref uri: '#digit' } }

rule(id: 'digit') { one_of { 0.upto(9) { |d| item { d.to_s } } } }
end
end

Expand Down
14 changes: 11 additions & 3 deletions lib/ruby_speech/grxml/grammar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,17 @@ def inline
# @return self
#
def inline!
xpath("//ns:ruleref", :ns => GRXML_NAMESPACE).each do |ref|
rule = rule_with_id ref[:uri].sub(/^#/, '')
ref.swap rule.dup.children
loop do
rule = nil
xpath('//ns:ruleref', ns: GRXML_NAMESPACE).each do |ref|
rule = rule_with_id ref[:uri].sub(/^#/, '')
unless rule
raise ArgumentError,
"The Ruleref '#{ref[:uri]}' is referenced but not defined"
end
ref.swap rule.dup.children
end
break unless rule
end

query = "./ns:rule[@id!='#{root}']"
Expand Down
35 changes: 22 additions & 13 deletions spec/ruby_speech/grxml/builtins_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,22 +119,31 @@
it { should not_match('#') }
end

describe "number" do
describe 'number' do
subject(:grammar) { described_class.number }

it { should match('0').and_interpret_as('dtmf-0') }
it { should match('123').and_interpret_as('dtmf-1 dtmf-2 dtmf-3') }
it { should match('1*01').and_interpret_as('dtmf-1 dtmf-star dtmf-0 dtmf-1') }
it { should match('01*00').and_interpret_as('dtmf-0 dtmf-1 dtmf-star dtmf-0 dtmf-0') }
it { should match('100000000000*00').and_interpret_as('dtmf-1 dtmf-0 dtmf-0 dtmf-0 dtmf-0 dtmf-0 dtmf-0 dtmf-0 dtmf-0 dtmf-0 dtmf-0 dtmf-0 dtmf-star dtmf-0 dtmf-0') }
it { should match('0*08').and_interpret_as('dtmf-0 dtmf-star dtmf-0 dtmf-8') }
it { should match('*59').and_interpret_as('dtmf-star dtmf-5 dtmf-9') }
it { should match('0*0').and_interpret_as('dtmf-0 dtmf-star dtmf-0') }
it { should match('10*5').and_interpret_as('dtmf-1 dtmf-0 dtmf-star dtmf-5') }
it { should match('123*').and_interpret_as('dtmf-1 dtmf-2 dtmf-3 dtmf-star') }
it { should match('123*2342').and_interpret_as('dtmf-1 dtmf-2 dtmf-3 dtmf-star dtmf-2 dtmf-3 dtmf-4 dtmf-2') }
it { should match('0').and_interpret_as 'dtmf-0' }
it { should match('123').and_interpret_as 'dtmf-1 dtmf-2 dtmf-3' }
it { should match('1*01').and_interpret_as dtmf_seq %w(1 star 0 1) }
it { should match('01*00').and_interpret_as dtmf_seq %w(0 1 star 0 0) }
it do
should match('100000000000*00')
.and_interpret_as dtmf_seq %w(1 0 0 0 0 0 0 0 0 0 0 0 star 0 0)
end
it { should match('0*08').and_interpret_as dtmf_seq %w(0 star 0 8) }
it { should match('*59').and_interpret_as 'dtmf-star dtmf-5 dtmf-9' }
it { should match('0*0').and_interpret_as 'dtmf-0 dtmf-star dtmf-0' }
it { should match('10*5').and_interpret_as dtmf_seq %w(1 0 star 5) }
it { should match('123*').and_interpret_as dtmf_seq %w(1 2 3 star) }
it do
should match('123*2342').and_interpret_as dtmf_seq %w(1 2 3 star 2 3 4 2)
end

it { should not_match('#') }
it { should potentially_match '*' }

it { should not_match '#' }
it { should not_match '**' }
it { should not_match '0123*456*789' }
end

describe "phone" do
Expand Down
94 changes: 94 additions & 0 deletions spec/ruby_speech/grxml/grammar_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,100 @@ module GRXML
grammar.inline!.should == inline_grammar
grammar.should == inline_grammar
end

context 'nested' do
let :expected_doc do
RubySpeech::GRXML.draw mode: :dtmf, root: 'main' do
rule id: :main, scope: 'public' do
string "How about an oatmeal cookie? You'll feel better."
end
end
end

context '1 level deep' do
subject do
RubySpeech::GRXML.draw mode: :dtmf, root: 'main' do
rule id: :main, scope: 'public' do
ruleref uri: '#rabbit_hole2'
end
rule id: 'rabbit_hole2' do
string "How about an oatmeal cookie? You'll feel better."
end
end.inline
end

it { should eq expected_doc }
end

context '2 levels deep' do
subject do
RubySpeech::GRXML.draw mode: :dtmf, root: 'main' do
rule id: :main, scope: 'public' do
ruleref uri: '#rabbit_hole2'
end
rule id: 'rabbit_hole2' do
ruleref uri: '#rabbit_hole3'
end
rule id: 'rabbit_hole3' do
string "How about an oatmeal cookie? You'll feel better."
end
end.inline
end

it { should eq expected_doc }
end

context '3 levels deep' do
subject do
RubySpeech::GRXML.draw mode: :dtmf, root: 'main' do
rule id: :main, scope: 'public' do
ruleref uri: '#rabbit_hole2'
end
rule id: 'rabbit_hole2' do
ruleref uri: '#rabbit_hole3'
end
rule id: 'rabbit_hole3' do
ruleref uri: '#rabbit_hole4'
end
rule id: 'rabbit_hole4' do
string "How about an oatmeal cookie? You'll feel better."
end
end.inline
end

it { should eq expected_doc }
end

context 'in a self-referencial infinite loop' do
subject do
RubySpeech::GRXML.draw mode: :dtmf, root: 'main' do
rule id: :main, scope: 'public' do
ruleref uri: '#paradox'
end
rule id: 'paradox' do
ruleref uri: '#paradox'
end
end.inline
end

pending 'should raise an Exception'
end

context 'with an invalid-reference' do
subject do
RubySpeech::GRXML.draw mode: :dtmf, root: 'main' do
rule id: :main, scope: 'public' do
ruleref uri: '#lost'
end
end.inline
end

it 'should raise a descriptive exception' do
message = "The Ruleref '#lost' is referenced but not defined"
expect { subject }.to raise_error ArgumentError, message
end
end
end
end

describe "#tokenize!" do
Expand Down
14 changes: 14 additions & 0 deletions spec/support/dtmf_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# encoding: utf-8
# frozen_string_literal: true

#
# Convert a simple DTMF string from "1 star 2" to "dtmf-1 dtmf-star dtmf-2".
#
# @param [Array] sequence A set of DTMF keys, such as `%w(1 star 2)`.
#
# @return [String] A string with "dtmf-" prefixed for each DTMF element.
# Example: "dtmf-1 dtmf-star dtmf-2".
#
def dtmf_seq(sequence)
sequence.map { |d| "dtmf-#{d}" }.join ' '
end

0 comments on commit 77f1ab5

Please sign in to comment.