james 0.0.1-universal-darwin-10 → 0.0.2-universal-darwin-10

Sign up to get free protection for your applications and to get access to all the features.
data/aux/james/cli.rb ADDED
@@ -0,0 +1,22 @@
1
+ require File.expand_path '../../../lib/james', __FILE__
2
+
3
+ module James
4
+
5
+ class CLI
6
+
7
+ def execute *dialogues
8
+ all_dialogues = Dir["**/*_dialog{,ue}.rb"]
9
+ all_dialogues.select! { |dialogue| dialogues.any? { |given| dialogue =~ %r{#{given}_dialog(ue)?.rb$} } } unless dialogues.empty?
10
+
11
+ puts "James: Using #{all_dialogues.join(', ')} for our conversation, Sir."
12
+
13
+ all_dialogues.each do |dialogue|
14
+ require File.expand_path dialogue, Dir.pwd
15
+ end
16
+
17
+ James.listen
18
+ end
19
+
20
+ end
21
+
22
+ end
data/bin/james ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+
4
+ # james joke phonebook quote
5
+ # OR
6
+ # james # Uses Dir.pwd
7
+ #
8
+
9
+ begin
10
+ require 'james/cli'
11
+ rescue LoadError => e
12
+ require 'rubygems'
13
+ james_path = File.expand_path '../../aux', __FILE__
14
+ $:.unshift(james_path) if File.directory?(james_path) && !$:.include?(james_path)
15
+ require 'james/cli'
16
+ end
17
+
18
+ cli = James::CLI.new
19
+ cli.execute *ARGV
data/lib/james.rb CHANGED
@@ -0,0 +1,18 @@
1
+ module James; end
2
+
3
+ require File.expand_path '../james/timer', __FILE__
4
+ require File.expand_path '../james/visitor', __FILE__
5
+ require File.expand_path '../james/dialogues', __FILE__
6
+
7
+ require File.expand_path '../james/dialogue_api', __FILE__
8
+ require File.expand_path '../james/dialogue_internals', __FILE__
9
+
10
+ require File.expand_path '../james/controller', __FILE__
11
+
12
+ module James
13
+
14
+ def self.listen
15
+ Controller.new.listen
16
+ end
17
+
18
+ end
@@ -0,0 +1,25 @@
1
+ class Bla
2
+
3
+ include James::Dialogue
4
+
5
+ hear 'James?' => :awake
6
+
7
+ state :awake do
8
+ hear 'Leave me alone, James' => :away
9
+ into { "Sir?" }
10
+ end
11
+
12
+ state :away do
13
+ hear 'Are you there, James?' => :awake,
14
+ "That's it for today, James" => :exit
15
+ into { "Goodbye, Sir" }
16
+ end
17
+
18
+ state :exit do
19
+ into do
20
+ puts "James: Exits through a side door."
21
+ Kernel.exit
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,116 @@
1
+ framework 'AppKit'
2
+
3
+ require File.expand_path '../inputs/base', __FILE__
4
+ require File.expand_path '../inputs/audio', __FILE__
5
+ require File.expand_path '../inputs/terminal', __FILE__
6
+
7
+ require File.expand_path '../outputs/audio', __FILE__
8
+ require File.expand_path '../outputs/terminal', __FILE__
9
+
10
+ module James
11
+
12
+ class Controller
13
+
14
+ attr_reader :visitor
15
+
16
+ def initialize
17
+ @visitor = initialize_dialogues.visitor
18
+ end
19
+
20
+ def applicationDidFinishLaunching notification
21
+ load_voices
22
+
23
+ start_output
24
+ start_input
25
+
26
+ visitor.enter { |text| say text }
27
+
28
+ @input.listen
29
+ end
30
+ def windowWillClose notification
31
+ puts "James is going to bed."
32
+ exit
33
+ end
34
+
35
+ # Load voices from yaml.
36
+ #
37
+ def load_voices
38
+ # yaml_voices = ''
39
+ # File.open('voices.yml') do |f| yaml_voices << f.read end
40
+ # voices = YAML.load(yaml_voices)
41
+ # @male_voice = voices['male']
42
+ # @female_voice = voices['female']
43
+ # Commented voices are Apple built-in voices. Can be changed by replacing the last part e.g.'Vicki' with e.g.'Laura'
44
+ # much better female voice from iVox:
45
+ # female: com.acapela.iVox.voice.iVoxHeather22k
46
+ # much better male voice from iVox:
47
+ # male: com.acapela.iVox.voice.iVoxRyan22k
48
+ # female: com.apple.speech.synthesis.voice.Vicki
49
+ # male: com.apple.speech.synthesis.voice.Bruce
50
+ end
51
+
52
+ def initialize_dialogues
53
+ # Create the main dialogue.
54
+ #
55
+ # Everybody hooks into this, then.
56
+ #
57
+ dialogues = Dialogues.new
58
+ dialogues.resolve
59
+ dialogues
60
+ end
61
+ # Start recognizing words.
62
+ #
63
+ def start_input
64
+ @input = Inputs::Audio.new self
65
+ end
66
+ # Start speaking.
67
+ #
68
+ def start_output
69
+ @output = Outputs::Audio.new
70
+ end
71
+
72
+ # Callback method from dialogue.
73
+ #
74
+ def say text
75
+ @output.say text
76
+ end
77
+ def hear text
78
+ @visitor.hear text do |response|
79
+ say response
80
+ end
81
+ end
82
+ def expects
83
+ @visitor.expects
84
+ end
85
+
86
+ def listen
87
+ app = NSApplication.sharedApplication
88
+ app.delegate = self
89
+
90
+ # window = NSWindow.alloc.initWithContentRect([200, 300, 300, 100],
91
+ # styleMask:NSTitledWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask,
92
+ # backing:NSBackingStoreBuffered,
93
+ # defer:false)
94
+ # window.title = 'MacRuby: The Definitive Guide'
95
+ # window.level = 3
96
+ # window.delegate = app.delegate
97
+ #
98
+ # button = NSButton.alloc.initWithFrame([80, 10, 120, 80])
99
+ # button.bezelStyle = 4
100
+ # button.title = 'Hello World!'
101
+ # button.target = app.delegate
102
+ # button.action = 'say_hello:'
103
+ #
104
+ # window.contentView.addSubview(button)
105
+ #
106
+ # window.display
107
+ # window.orderFrontRegardless
108
+
109
+ app.delegate.applicationDidFinishLaunching nil
110
+
111
+ app.run
112
+ end
113
+
114
+ end
115
+
116
+ end
@@ -0,0 +1,30 @@
1
+ module James
2
+
3
+ # A dialog(ue) can be instantiated in two ways:
4
+ #
5
+ # James.dialogue do
6
+ # # Your dialogue.
7
+ # #
8
+ # end
9
+ #
10
+ # class MyDialogue
11
+ # include James::Dialogue
12
+ #
13
+ # # Your dialogue.
14
+ # #
15
+ # end
16
+ #
17
+ module Dialogue; end
18
+
19
+ class << self
20
+
21
+ def dialogue &block
22
+ dialogue = Class.new { include Dialogue }
23
+ dialogue.class_eval &block
24
+ dialogue
25
+ end
26
+ alias dialog dialogue
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,61 @@
1
+ require File.expand_path '../state_api', __FILE__
2
+ require File.expand_path '../state_internals', __FILE__
3
+ require File.expand_path '../dialogues', __FILE__
4
+
5
+ module James
6
+
7
+ # A dialogue is just a container object
8
+ # for defining states and executing methods.
9
+ #
10
+ module Dialogue
11
+
12
+ def self.included into
13
+ into.extend ClassMethods
14
+ Dialogues << into
15
+ end
16
+
17
+ #
18
+ #
19
+ def state_for name
20
+ self.class.state_for name, self
21
+ end
22
+
23
+ module ClassMethods
24
+
25
+ # Defines the entry sentences.
26
+ #
27
+ def hear definition
28
+ define_method :entries do
29
+ definition
30
+ end
31
+ end
32
+
33
+ # Defines a state with transitions.
34
+ #
35
+ # state :name do
36
+ # # state properties (hear, into, exit) go here.
37
+ # end
38
+ #
39
+ attr_reader :states
40
+ def state name, &block
41
+ @states ||= {}
42
+ @states[name] ||= block if block_given?
43
+ end
44
+ def state_for name, instance
45
+ # Lazily wrap.
46
+ #
47
+ if states[name].respond_to?(:call)
48
+ states[name] = State.new(name, instance, &states[name])
49
+ end
50
+ states[name]
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+
57
+ # We don't care about the spelling.
58
+ #
59
+ Dialog = Dialogue
60
+
61
+ end
@@ -0,0 +1,58 @@
1
+ require File.expand_path '../visitor', __FILE__
2
+
3
+ module James
4
+
5
+ # Registers all dialogues and connects their states.
6
+ #
7
+ class Dialogues
8
+
9
+ attr_reader :initial, :dialogues
10
+
11
+ def initialize
12
+ @initial = State.new :__initial_plugin_state__, nil
13
+ @dialogues = self.class.dialogues.map &:new
14
+ end
15
+
16
+ class << self
17
+
18
+ attr_reader :dialogues
19
+
20
+ def << dialogue
21
+ @dialogues ||= []
22
+ @dialogues << dialogue
23
+ end
24
+
25
+ end
26
+
27
+ # Generate the graph for the dialogues.
28
+ #
29
+ # Hooks up the entry phrases of all dialogues
30
+ # into the main dialogue.
31
+ #
32
+ # It raises if the hook phrase of a dialogue
33
+ # is already used.
34
+ #
35
+ def resolve
36
+ # Hook dialogues into initial state.
37
+ #
38
+ resolved_entries = {}
39
+ dialogues.each do |dialogue|
40
+ dialogue.entries.each do |(phrases, state)|
41
+ resolved_entries[phrases] = state.respond_to?(:phrases) ? state : dialogue.state_for(state)
42
+ end
43
+ end
44
+
45
+ initial.hear resolved_entries
46
+ end
47
+
48
+ # Get the visitor.
49
+ #
50
+ # Initialized on the initial state.
51
+ #
52
+ def visitor
53
+ @visitor ||= Visitor.new initial
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,40 @@
1
+ module James
2
+
3
+ module Inputs
4
+
5
+ class Audio < Base
6
+
7
+ def initialize controller
8
+ super controller
9
+ @recognizer = NSSpeechRecognizer.alloc.init
10
+ @recognizer.setBlocksOtherRecognizers true
11
+ @recognizer.setListensInForegroundOnly false
12
+ recognize_new_commands
13
+ @recognizer.setDelegate self
14
+ end
15
+
16
+ def listen
17
+ @recognizer.startListening
18
+ end
19
+ def heard command
20
+ super
21
+
22
+ # Set recognizable commands.
23
+ #
24
+ recognize_new_commands
25
+ end
26
+
27
+ # Callback method from the speech interface.
28
+ #
29
+ def speechRecognizer sender, didRecognizeCommand: command
30
+ heard command
31
+ end
32
+ def recognize_new_commands
33
+ @recognizer.setCommands controller.expects
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,23 @@
1
+ module James
2
+
3
+ module Inputs
4
+
5
+ class Base
6
+
7
+ attr_reader :controller
8
+
9
+ def initialize controller
10
+ @controller = controller
11
+ end
12
+
13
+ def heard command
14
+ # Call dialogue.
15
+ #
16
+ controller.hear command
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,22 @@
1
+ module James
2
+
3
+ module Inputs
4
+
5
+ # Terminal input for silent purposes.
6
+ #
7
+ class Terminal < Base
8
+
9
+ def listen
10
+ loop do
11
+ puts %Q{What would you like? Possibilities include\n"#{controller.expects.join('", "')}"}
12
+ command = gets.chop
13
+ puts "I heard '#{command}'."
14
+ heard command if controller.expects.include? command
15
+ end
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,15 @@
1
+ module James
2
+
3
+ # MainDialogue for first visitor.
4
+ #
5
+ module MainDialogue
6
+
7
+ extend Dialogue
8
+
9
+ def self.included into
10
+ into.extend ClassMethods
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,20 @@
1
+ module James
2
+
3
+ module Outputs
4
+
5
+ class Audio
6
+
7
+
8
+ def initialize voice = nil
9
+ @output = NSSpeechSynthesizer.alloc.initWithVoice voice || 'com.apple.speech.synthesis.voice.Alex'
10
+ end
11
+
12
+ def say text
13
+ @output.startSpeakingString text
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,17 @@
1
+ module James
2
+
3
+ module Outputs
4
+
5
+ # Terminal output for silent purposes.
6
+ #
7
+ class Terminal
8
+
9
+ def say text
10
+ p text
11
+ end
12
+
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,86 @@
1
+ module James
2
+
3
+ # A state is defined in a dialogue.
4
+ #
5
+ # It has a name with which it can be targeted.
6
+ #
7
+ # A state has three methods:
8
+ # * hear: If this phrase (or one of these phrases) is heard, move to that state. Takes a hash.
9
+ # * into: A block that is called on entering.
10
+ # * exit: A block that is called on exit.
11
+ #
12
+ # Example:
13
+ # state :time do
14
+ # hear ['What time is it?', 'And now?'] => :time
15
+ # into { time = Time.now; "It is currently #{time.hour} #{time.min}." }
16
+ # exit { "And that was the time." }
17
+ # end
18
+ #
19
+ class State
20
+
21
+ attr_reader :name, :context
22
+
23
+ def initialize name, context
24
+ @name = name
25
+ @context = context
26
+
27
+ @transitions = {}
28
+
29
+ instance_eval(&Proc.new) if block_given?
30
+ end
31
+
32
+ # How do I get from this state to another?
33
+ #
34
+ # Example:
35
+ # hear 'What time is it?' => :time,
36
+ # 'What? This late?' => :yes
37
+ #
38
+ # Example for staying in the same state:
39
+ # hear 'What time is it?' # Implicitly staying.
40
+ #
41
+ # Note: We could expand on this with
42
+ # hear 'What time is it?' => lambda { Do something here with the time, returns to same state }
43
+ #
44
+ def hear transitions
45
+ transitions = { transitions => name } unless transitions.respond_to?(:to_hash)
46
+ @transitions = expand transitions
47
+ end
48
+
49
+ # Execute this block when entering this state.
50
+ #
51
+ def into &block
52
+ @into_block = block
53
+ end
54
+
55
+ # Execute this block when exiting this state.
56
+ #
57
+ def exit &block
58
+ @exit_block = block
59
+ end
60
+
61
+ # Description of self using name and transitions.
62
+ #
63
+ def to_s
64
+ "#{self.class.name}(#{name}, #{context}, #{transitions})"
65
+ end
66
+
67
+ # The naughty privates of this class.
68
+ #
69
+
70
+ # Expands a hash in the form
71
+ # * [a, b] => c to a => c, b => c
72
+ # but leaves a non-array key alone.
73
+ #
74
+ def expand transitions
75
+ results = {}
76
+ transitions.each_pair do |phrases, state_name|
77
+ [*phrases].each do |phrase|
78
+ results[phrase] = state_name
79
+ end
80
+ end
81
+ results
82
+ end
83
+
84
+ end
85
+
86
+ end
@@ -0,0 +1,32 @@
1
+ module James
2
+
3
+ class State
4
+
5
+ attr_reader :transitions
6
+
7
+ # Returns all possible phrases that lead
8
+ # away from this state.
9
+ #
10
+ def phrases
11
+ transitions.keys
12
+ end
13
+
14
+ # Returns the next state for the given phrase.
15
+ #
16
+ # It accesses the context (Dialog(ue)) to get a full object state.
17
+ #
18
+ def next_for phrase
19
+ state = self.transitions[phrase]
20
+ state.respond_to?(:phrases) ? state : context.state_for(state)
21
+ end
22
+
23
+ def __into__
24
+ @into_block && context.instance_eval(&@into_block)
25
+ end
26
+ def __exit__
27
+ @exit_block && context.instance_eval(&@exit_block)
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,14 @@
1
+ module James
2
+
3
+ class Timer
4
+
5
+ def stop
6
+
7
+ end
8
+ def restart
9
+
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,72 @@
1
+ require File.expand_path '../timer', __FILE__
2
+
3
+ module James
4
+
5
+ # The visitor knows where in the conversation we are.
6
+ #
7
+ # It also remembers where it has to go back to if
8
+ # too much time passes without input.
9
+ #
10
+ # Note: A visitor should generally be very stupid.
11
+ # Note 2: We could call this Hearing, or Ear ;)
12
+ #
13
+ class Visitor
14
+
15
+ attr_reader :initial, :timer
16
+ attr_accessor :current
17
+
18
+ # Pass in an initial state to start from.
19
+ #
20
+ def initialize initial, timer = nil
21
+ @current = initial
22
+
23
+ @initial = initial
24
+ @timer = timer || Timer.new
25
+ end
26
+
27
+ # Escapes the current state back to the initial.
28
+ #
29
+ def escape
30
+ timer.stop
31
+ self.current = initial
32
+ end
33
+
34
+ # We hear a phrase.
35
+ #
36
+ # Also used to start the whole process.
37
+ #
38
+ def enter
39
+ result = current.__into__
40
+ yield result if result && block_given?
41
+ result
42
+ end
43
+ def exit
44
+ result = current.__exit__
45
+ yield result if result && block_given?
46
+ result
47
+ end
48
+ def transition phrase
49
+ self.current = current.next_for phrase
50
+ end
51
+ def check
52
+ escape && yield("That led nowhere.") unless current
53
+ end
54
+ def hear phrase, &block
55
+ return unless hears? phrase
56
+ timer.restart
57
+ exit_text = exit &block
58
+ transition phrase
59
+ check &block
60
+ into_text = enter &block
61
+ exit_text || into_text
62
+ end
63
+ def hears? phrase
64
+ expects.include? phrase
65
+ end
66
+ def expects
67
+ current.phrases
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,37 @@
1
+ module James
2
+
3
+ # The visitors class has a number of visitors, whose
4
+ # dialogues are visited in order of preference.
5
+ #
6
+ # Why?
7
+ # Discussions have multiple points where they can be.
8
+ # (Politics, then this joke, then back again, finally "Oh, bye I have to go!")
9
+ #
10
+ # In James, though, it is much simpler.
11
+ # We just have a visitor in an entry scenario
12
+ # (James!, Sleep, Wake up, Something completely different)
13
+ # and one in any specific user-given scenario.
14
+ #
15
+ # Visitors is a proxy object for visitors.
16
+ #
17
+ class Visitors
18
+
19
+ attr_reader :visitors
20
+
21
+ def initialize *visitors
22
+ @visitors = visitors
23
+ end
24
+
25
+ def hear phrase, &block
26
+ visitors.each do |visitor|
27
+ visitor.hear phrase, &block and break
28
+ end
29
+ end
30
+
31
+ def expects
32
+ visitors.inject([]) { |expects, visitor| expects + visitor.expects }
33
+ end
34
+
35
+ end
36
+
37
+ end
data/lib/old/main.rb ADDED
@@ -0,0 +1,30 @@
1
+ # Instead of this, check for __main__.
2
+ #
3
+
4
+ require File.expand_path '../../james', __FILE__
5
+
6
+ app = NSApplication.sharedApplication
7
+ app.delegate = James::Controller.new
8
+
9
+ # window = NSWindow.alloc.initWithContentRect([200, 300, 300, 100],
10
+ # styleMask:NSTitledWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask,
11
+ # backing:NSBackingStoreBuffered,
12
+ # defer:false)
13
+ # window.title = 'MacRuby: The Definitive Guide'
14
+ # window.level = 3
15
+ # window.delegate = app.delegate
16
+ #
17
+ # button = NSButton.alloc.initWithFrame([80, 10, 120, 80])
18
+ # button.bezelStyle = 4
19
+ # button.title = 'Hello World!'
20
+ # button.target = app.delegate
21
+ # button.action = 'say_hello:'
22
+ #
23
+ # window.contentView.addSubview(button)
24
+ #
25
+ # window.display
26
+ # window.orderFrontRegardless
27
+
28
+ app.delegate.applicationDidFinishLaunching nil
29
+
30
+ app.run
@@ -0,0 +1,47 @@
1
+ module James
2
+
3
+ # This is the default main dialogue.
4
+ #
5
+ # It is a proxy to the internal dialogues.
6
+ #
7
+ class MainDialogue
8
+ include Dialogue
9
+
10
+ hear 'wake up james' => :awake
11
+
12
+ # Create Protostate with block. Then, create instance instance_evaling block.
13
+ #
14
+ state :awake do
15
+ hear 'sleep james' => :sleeping
16
+ hear 'my options?' { |the_next| the_next.phrases.join ' ' }
17
+ into { "At your service" }
18
+ exit { "Right away" }
19
+ end
20
+
21
+ state :sleeping do
22
+ hear 'wake up james' => :awake
23
+ into { "Good night, Sir" }
24
+ end
25
+
26
+ # state :awake, {
27
+ # 'sleep james' => :sleeping
28
+ # # 'james, my options?' => lambda { || } # stays in this state but executes.
29
+ # }
30
+ # state :sleeping, {
31
+ # 'wake up james' => :awake
32
+ # }
33
+ # entry 'wake up james' => :awake
34
+ #
35
+ # def enter_sleeping
36
+ # "Good night, Sir"
37
+ # end
38
+ # def enter_awake
39
+ # "At your service"
40
+ # end
41
+ # def exit_awake
42
+ # "Right away"
43
+ # end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,83 @@
1
+ # encoding: utf-8
2
+ #
3
+ require File.expand_path '../../../lib/james', __FILE__
4
+
5
+ describe 'TestDialogue' do
6
+
7
+ context 'unit' do
8
+ let(:dialogue) do
9
+
10
+ Class.new do
11
+ include James::Dialog
12
+
13
+ hear ['test1', 'test2'] => :first
14
+
15
+ state :first do
16
+ hear 'go' => :second, 'stay' => :first
17
+ end
18
+
19
+ state :second do
20
+ hear 'go' => :third, 'back' => :first
21
+ end
22
+ end.new
23
+
24
+ end
25
+ let(:visitor) do
26
+ James::Visitor.new dialogue.state_for(:first)
27
+ end
28
+
29
+ describe "integration" do
30
+ it 'works correctly' do
31
+ visitor.current.name.should == :first
32
+ visitor.hear('go') {}
33
+ visitor.current.name.should == :second
34
+ visitor.hear('back') {}
35
+ visitor.current.name.should == :first
36
+ visitor.hear('stay') {}
37
+ visitor.current.name.should == :first
38
+ end
39
+ it 'calls the entrance/exits correctly' do
40
+ dialogue.should_receive(:respond_to?).once.with(:enter_second).and_return true
41
+ dialogue.should_receive(:enter_second).once
42
+
43
+ dialogue.should_receive(:respond_to?).once.with(:exit_second).and_return true
44
+ dialogue.should_receive(:exit_second).once
45
+
46
+ dialogue.should_receive(:respond_to?).once.with(:enter_first).and_return true
47
+ dialogue.should_receive(:enter_first).once
48
+
49
+ dialogue.should_receive(:respond_to?).once.with(:exit_first).and_return true
50
+ dialogue.should_receive(:exit_first).once
51
+
52
+ visitor.hear 'go'
53
+ visitor.hear 'back'
54
+ end
55
+ end
56
+ end
57
+
58
+ # context 'integration' do
59
+ # let(:dialogue) do
60
+ # dialogue = Class.new do
61
+ # include James::Dialog
62
+ #
63
+ # hear ['test1', 'test2'] => :first
64
+ # state :first do
65
+ # hear 'go' => :second, 'stay' => :first
66
+ # end
67
+ # state :second do
68
+ # hear 'go' => :third, 'back' => :first
69
+ # end
70
+ # end.new
71
+ # end
72
+ # it 'works correctly' do
73
+ # dialogue.state.name.should == :awake
74
+ # dialogue.hear 'sleep'
75
+ # dialogue.state.name.should == :sleeping
76
+ # end
77
+ # it 'delegates correctly' do
78
+ # dialogue.state.name.should == :awake
79
+ # dialogue.hear 'test1'
80
+ # end
81
+ # end
82
+
83
+ end
@@ -0,0 +1,76 @@
1
+ # encoding: utf-8
2
+ #
3
+ require File.expand_path '../../../../lib/james/dialogue_api', __FILE__
4
+ require File.expand_path '../../../../lib/james/dialogue_internals', __FILE__
5
+
6
+ describe James::Dialogue do
7
+
8
+ it 'can haz merkin spellink' do
9
+ James::Dialogue.should == James::Dialog
10
+ end
11
+
12
+ context 'units' do
13
+ let(:dialogue) do
14
+ James.dialogue do
15
+
16
+ hear 'something' => :first
17
+
18
+ state :first do
19
+ hear 'something else' => :second
20
+ into {}
21
+ exit {}
22
+ end
23
+
24
+ state :second do
25
+ hear 'yet something else' => :first
26
+ into {}
27
+ exit {}
28
+ end
29
+
30
+ end
31
+ end
32
+ describe 'state_for' do
33
+ it 'delegates to the class, adding itself' do
34
+ new_dialogue = dialogue.new
35
+ dialogue.should_receive(:state_for).once.with :some_name, new_dialogue
36
+
37
+ new_dialogue.state_for :some_name
38
+ end
39
+ it 'returns nil on not found' do
40
+ dialogue.new.state_for(:nonexistent).should == nil
41
+ end
42
+ end
43
+ end
44
+
45
+ describe 'initialization' do
46
+ it 'can be included' do
47
+ expect do
48
+ class Test
49
+ include James::Dialogue
50
+
51
+ hear 'something' => :some_state
52
+ end
53
+ end.to_not raise_error
54
+ end
55
+
56
+ it 'can be defined' do
57
+ expect do
58
+ James.dialogue do
59
+
60
+ hear 'something' => :some_state
61
+
62
+ end
63
+ end.to_not raise_error
64
+ end
65
+ it 'can haz merkin spellink' do
66
+ expect do
67
+ James.dialog do
68
+
69
+ hear 'something' => :some_state
70
+
71
+ end
72
+ end.to_not raise_error
73
+ end
74
+ end
75
+
76
+ end
@@ -0,0 +1,127 @@
1
+ # encoding: utf-8
2
+ #
3
+ require File.expand_path '../../../../lib/james/state_api', __FILE__
4
+ require File.expand_path '../../../../lib/james/state_internals', __FILE__
5
+
6
+ describe James::State do
7
+
8
+ before(:all) do
9
+ @context = stub :context,
10
+ :inspect => 'some_context'
11
+ class << @context
12
+
13
+ def state_for name
14
+ {
15
+ :next_state1 => :some_state_object1,
16
+ :next_state2 => :some_state_object2,
17
+ :next_state3 => :some_state_object3,
18
+ }[name]
19
+ end
20
+
21
+ end
22
+ end
23
+
24
+ context 'with no transitions or into or exit' do
25
+ let(:state) do
26
+ described_class.new :some_name, @context do
27
+ # Nothing to see here.
28
+ end
29
+ end
30
+ describe 'phrases' do
31
+ it { state.phrases.should == [] }
32
+ end
33
+ describe 'to_s' do
34
+ it { state.to_s.should == 'James::State(some_name, some_context, {})' }
35
+ end
36
+ describe 'next_for' do
37
+ it { state.next_for('non-existent').should == nil }
38
+ end
39
+ describe 'expand' do
40
+ it do
41
+ state.expand([:a, :b] => 1).should == { :a => 1, :b => 1 }
42
+ end
43
+ it do
44
+ state.expand(:a => 1).should == { :a => 1 }
45
+ end
46
+ end
47
+ describe '__into__' do
48
+ it 'is called' do
49
+ state.__into__.should == nil
50
+ end
51
+ end
52
+ describe '__exit__' do
53
+ it 'is conditionally called' do
54
+ state.__exit__.should == nil
55
+ end
56
+ end
57
+ end
58
+
59
+ context 'of the context' do
60
+ let(:state) do
61
+ described_class.new :some_name, @context do
62
+ hear 'transition one' => :next_state1
63
+ into { self }
64
+ exit { self }
65
+ end
66
+ end
67
+ describe '__into__' do
68
+ it 'is called' do
69
+ state.__into__.should == @context
70
+ end
71
+ end
72
+ describe '__exit__' do
73
+ it 'is conditionally called' do
74
+ state.__exit__.should == @context
75
+ end
76
+ end
77
+ end
78
+
79
+ context 'with 1 transition and into and exit' do
80
+ let(:state) do
81
+ described_class.new :some_name, @context do
82
+ hear 'transition one' => :next_state1
83
+ into { "hi there" }
84
+ exit { "good bye" }
85
+ end
86
+ end
87
+ describe 'phrases' do
88
+ it { state.phrases.should == ['transition one'] }
89
+ end
90
+ describe 'to_s' do
91
+ it { state.to_s.should == 'James::State(some_name, some_context, {"transition one"=>:next_state1})' }
92
+ end
93
+ describe 'next_for' do
94
+ it { state.next_for('transition one').should == :some_state_object1 }
95
+ it { state.next_for('non-existent').should == nil }
96
+ end
97
+ describe '__into__' do
98
+ it 'is called' do
99
+ state.__into__.should == 'hi there'
100
+ end
101
+ end
102
+ describe '__exit__' do
103
+ it 'is conditionally called' do
104
+ state.__exit__.should == 'good bye'
105
+ end
106
+ end
107
+ end
108
+
109
+ context 'with multiple transition' do
110
+ let(:state) do
111
+ described_class.new :some_name, @context do
112
+ hear 'transition one' => :next_state1,
113
+ 'transition two' => :next_state2,
114
+ 'transition three' => :next_state3
115
+ end
116
+ end
117
+ describe 'phrases' do
118
+ it { state.phrases.should == ['transition one', 'transition two', 'transition three'] }
119
+ end
120
+ describe 'to_s' do
121
+ it { state.to_s.should == 'James::State(some_name, some_context, {"transition one"=>:next_state1, "transition two"=>:next_state2, "transition three"=>:next_state3})' }
122
+ end
123
+ it { state.next_for('transition two').should == :some_state_object2 }
124
+ it { state.next_for('non-existent').should == nil }
125
+ end
126
+
127
+ end
@@ -0,0 +1,86 @@
1
+ # encoding: utf-8
2
+ #
3
+ require File.expand_path '../../../../lib/james/visitor', __FILE__
4
+
5
+ describe James::Visitor do
6
+
7
+ let(:initial) { stub :state }
8
+ let(:timer) { stub :timer }
9
+ let(:visitor) { described_class.new initial, timer }
10
+
11
+ describe 'current' do
12
+ it { visitor.current.should == initial }
13
+ end
14
+
15
+ describe 'enter' do
16
+ it 'calls enter on the state' do
17
+ initial.should_receive(:__into__).once
18
+
19
+ visitor.enter
20
+ end
21
+ it 'returns the result' do
22
+ initial.stub! :__into__ => 'some text'
23
+
24
+ visitor.enter.should == 'some text'
25
+ end
26
+ it 'yields the result' do
27
+ initial.stub! :__into__ => 'some text'
28
+
29
+ visitor.enter do |text|
30
+ text.should == 'some text'
31
+ end
32
+ end
33
+ end
34
+
35
+ describe 'exit' do
36
+ it 'calls enter on the state' do
37
+ initial.should_receive(:__exit__).once.with
38
+
39
+ visitor.exit
40
+ end
41
+ it 'returns the result' do
42
+ initial.stub! :__exit__ => 'some text'
43
+
44
+ visitor.exit.should == 'some text'
45
+ end
46
+ it 'yields the result' do
47
+ initial.stub! :__exit__ => 'some text'
48
+
49
+ visitor.exit do |text|
50
+ text.should == 'some text'
51
+ end
52
+ end
53
+ end
54
+
55
+ describe 'transition' do
56
+ it 'sets the current state' do
57
+ initial.stub! :next_for => :some_state
58
+
59
+ visitor.transition 'some phrase'
60
+
61
+ visitor.current.should == :some_state
62
+ end
63
+ end
64
+
65
+ describe 'escape' do
66
+ it 'calls methods in order' do
67
+ timer.should_receive(:stop).once.with
68
+ visitor.should_receive(:current=).once.with initial
69
+
70
+ visitor.escape
71
+ end
72
+ end
73
+
74
+ describe 'hear' do
75
+ it 'calls methods in order' do
76
+ visitor.should_receive(:hears?).once.ordered.with(:some_phrase).and_return true
77
+ timer.should_receive(:restart).once.ordered.with
78
+ visitor.should_receive(:exit).once.ordered.with
79
+ visitor.should_receive(:transition).once.ordered.with :some_phrase
80
+ # visitor.should_receive(:check).once.ordered.with
81
+ visitor.should_receive(:enter).once.ordered.with
82
+
83
+ visitor.hear :some_phrase
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,52 @@
1
+ # encoding: utf-8
2
+ #
3
+ require File.expand_path '../../../../lib/james/visitors', __FILE__
4
+
5
+ describe James::Visitors do
6
+
7
+ let(:first) { stub :first }
8
+ let(:second) { stub :second }
9
+ let(:visitors) { described_class.new first, second }
10
+
11
+ describe 'hear' do
12
+ context 'the first has it' do
13
+ before(:each) do
14
+ first.stub! :hear => :something
15
+ second.stub! :hear => nil
16
+ end
17
+ it 'works' do
18
+ visitors.hear 'some phrase'
19
+ end
20
+ it 'calls the second never' do
21
+ first.should_receive(:hear).once.and_return true
22
+ second.should_receive(:hear).never
23
+
24
+ visitors.hear 'some phrase'
25
+ end
26
+ end
27
+ context 'the second has it' do
28
+ before(:each) do
29
+ first.stub! :hear => nil
30
+ second.stub! :hear => :something
31
+ end
32
+ it 'works' do
33
+ visitors.hear 'some phrase'
34
+ end
35
+ it 'calls the first hear first' do
36
+ first.should_receive(:hear).once.ordered.with 'some phrase'
37
+ second.should_receive(:hear).once.ordered.with 'some phrase'
38
+
39
+ visitors.hear 'some phrase'
40
+ end
41
+ end
42
+ end
43
+
44
+ describe 'expects' do
45
+ before(:each) do
46
+ first.stub! :expects => [:a, :b]
47
+ second.stub! :expects => [:c, :d, :e]
48
+ end
49
+ it { visitors.expects.should == [:a, :b, :c, :d, :e] }
50
+ end
51
+
52
+ end
metadata CHANGED
@@ -5,16 +5,16 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 1
9
- version: 0.0.1
8
+ - 2
9
+ version: 0.0.2
10
10
  platform: universal-darwin-10
11
11
  authors:
12
12
  - Florian Hanke
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2011-05-05 00:00:00 +10:00
17
- default_executable:
16
+ date: 2011-05-09 00:00:00 +10:00
17
+ default_executable: james
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: rspec
@@ -37,11 +37,31 @@ dependencies:
37
37
  description: Modular Electronic Butler. Add Dialog(ue)s to it to add more abilities
38
38
  to it.
39
39
  email: florian.hanke+james@gmail.com
40
- executables: []
40
+ executables:
41
+ - james
41
42
  extensions: []
42
43
  extra_rdoc_files: []
43
44
  files:
45
+ - lib/james/builtin/main_dialogue.rb
46
+ - lib/james/controller.rb
47
+ - lib/james/dialogue_api.rb
48
+ - lib/james/dialogue_internals.rb
49
+ - lib/james/dialogues.rb
50
+ - lib/james/inputs/audio.rb
51
+ - lib/james/inputs/base.rb
52
+ - lib/james/inputs/terminal.rb
53
+ - lib/james/main_dialogue.rb
54
+ - lib/james/outputs/audio.rb
55
+ - lib/james/outputs/terminal.rb
56
+ - lib/james/state_api.rb
57
+ - lib/james/state_internals.rb
58
+ - lib/james/timer.rb
59
+ - lib/james/visitor.rb
60
+ - lib/james/visitors.rb
44
61
  - lib/james.rb
62
+ - lib/old/main.rb
63
+ - lib/old/main_dialogue.rb
64
+ - aux/james/cli.rb
45
65
  has_rdoc: true
46
66
  homepage: http://floere.github.com/james
47
67
  licenses: []
@@ -69,4 +89,9 @@ rubygems_version: 1.3.6
69
89
  signing_key:
70
90
  specification_version: 3
71
91
  summary: 'James: Modular Electronic Butler.'
72
- test_files: []
92
+ test_files:
93
+ - spec/integration/test_dialogue_spec.rb
94
+ - spec/lib/james/dialogue_spec.rb
95
+ - spec/lib/james/state_spec.rb
96
+ - spec/lib/james/visitor_spec.rb
97
+ - spec/lib/james/visitors_spec.rb