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