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 +22 -0
- data/bin/james +19 -0
- data/lib/james.rb +18 -0
- data/lib/james/builtin/main_dialogue.rb +25 -0
- data/lib/james/controller.rb +116 -0
- data/lib/james/dialogue_api.rb +30 -0
- data/lib/james/dialogue_internals.rb +61 -0
- data/lib/james/dialogues.rb +58 -0
- data/lib/james/inputs/audio.rb +40 -0
- data/lib/james/inputs/base.rb +23 -0
- data/lib/james/inputs/terminal.rb +22 -0
- data/lib/james/main_dialogue.rb +15 -0
- data/lib/james/outputs/audio.rb +20 -0
- data/lib/james/outputs/terminal.rb +17 -0
- data/lib/james/state_api.rb +86 -0
- data/lib/james/state_internals.rb +32 -0
- data/lib/james/timer.rb +14 -0
- data/lib/james/visitor.rb +72 -0
- data/lib/james/visitors.rb +37 -0
- data/lib/old/main.rb +30 -0
- data/lib/old/main_dialogue.rb +47 -0
- data/spec/integration/test_dialogue_spec.rb +83 -0
- data/spec/lib/james/dialogue_spec.rb +76 -0
- data/spec/lib/james/state_spec.rb +127 -0
- data/spec/lib/james/visitor_spec.rb +86 -0
- data/spec/lib/james/visitors_spec.rb +52 -0
- metadata +31 -6
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,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,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
|
data/lib/james/timer.rb
ADDED
@@ -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
|
-
-
|
9
|
-
version: 0.0.
|
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-
|
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
|