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

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.
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