inform-runtime 1.0.4
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.
- checksums.yaml +7 -0
- data/LICENSE +623 -0
- data/README.md +185 -0
- data/Rakefile +65 -0
- data/config/database.yml +37 -0
- data/exe/inform.rb +6 -0
- data/game/config.yml +5 -0
- data/game/example.inf +76 -0
- data/game/example.rb +90 -0
- data/game/forms/example_form.rb +2 -0
- data/game/grammar/game_grammar.inf.rb +11 -0
- data/game/languages/english.rb +2 -0
- data/game/models/example_model.rb +2 -0
- data/game/modules/example_module.rb +9 -0
- data/game/rules/example_state.rb +2 -0
- data/game/scripts/example_script.rb +2 -0
- data/game/topics/example_topic.rb +2 -0
- data/game/verbs/game_verbs.rb +15 -0
- data/game/verbs/metaverbs.rb +2028 -0
- data/lib/runtime/articles.rb +138 -0
- data/lib/runtime/builtins.rb +359 -0
- data/lib/runtime/color.rb +145 -0
- data/lib/runtime/command.rb +470 -0
- data/lib/runtime/config.rb +48 -0
- data/lib/runtime/context.rb +78 -0
- data/lib/runtime/daemon.rb +266 -0
- data/lib/runtime/database.rb +500 -0
- data/lib/runtime/events.rb +771 -0
- data/lib/runtime/experimental/handler_dsl.rb +175 -0
- data/lib/runtime/game.rb +74 -0
- data/lib/runtime/game_loader.rb +132 -0
- data/lib/runtime/grammar_parser.rb +553 -0
- data/lib/runtime/helpers.rb +177 -0
- data/lib/runtime/history.rb +45 -0
- data/lib/runtime/inflector.rb +195 -0
- data/lib/runtime/io.rb +174 -0
- data/lib/runtime/kernel.rb +450 -0
- data/lib/runtime/library.rb +59 -0
- data/lib/runtime/library_loader.rb +135 -0
- data/lib/runtime/link.rb +158 -0
- data/lib/runtime/logging.rb +197 -0
- data/lib/runtime/mixins.rb +570 -0
- data/lib/runtime/module.rb +202 -0
- data/lib/runtime/object.rb +761 -0
- data/lib/runtime/options.rb +104 -0
- data/lib/runtime/persistence.rb +292 -0
- data/lib/runtime/plurals.rb +60 -0
- data/lib/runtime/prototype.rb +307 -0
- data/lib/runtime/publication.rb +92 -0
- data/lib/runtime/runtime.rb +321 -0
- data/lib/runtime/session.rb +202 -0
- data/lib/runtime/stdlib.rb +604 -0
- data/lib/runtime/subscription.rb +47 -0
- data/lib/runtime/tag.rb +287 -0
- data/lib/runtime/tree.rb +204 -0
- data/lib/runtime/version.rb +24 -0
- data/lib/runtime/world_tree.rb +69 -0
- data/lib/runtime.rb +35 -0
- metadata +199 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: false
|
|
3
|
+
|
|
4
|
+
# Copyright Nels Nelson 2008-2023 but freely usable (see license)
|
|
5
|
+
#
|
|
6
|
+
# This file is part of the Inform Runtime.
|
|
7
|
+
#
|
|
8
|
+
# The Inform Runtime is free software: you can redistribute it and/or
|
|
9
|
+
# modify it under the terms of the GNU General Public License as published
|
|
10
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
# (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# The Inform Runtime is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
# GNU General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU General Public License
|
|
19
|
+
# along with the Inform Runtime. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
|
|
21
|
+
require_relative 'config'
|
|
22
|
+
require_relative 'game'
|
|
23
|
+
require_relative 'session'
|
|
24
|
+
require_relative 'io'
|
|
25
|
+
require_relative 'context'
|
|
26
|
+
require_relative 'library'
|
|
27
|
+
require_relative 'options'
|
|
28
|
+
require_relative 'publication'
|
|
29
|
+
|
|
30
|
+
# The Inform module
|
|
31
|
+
module Inform
|
|
32
|
+
# The RuntimeConstants module
|
|
33
|
+
module RuntimeConstants
|
|
34
|
+
UndefinedMainPattern = %r{undefined method 'Main' for an instance of Object}.freeze
|
|
35
|
+
Registry = Class.new(Hash)
|
|
36
|
+
LibraryRegistry = Registry.new
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# The RuntimeLibrary module
|
|
40
|
+
module RuntimeLibrary
|
|
41
|
+
def play
|
|
42
|
+
log.debug "Using runtime-managed InformLibrary play method override"
|
|
43
|
+
inform_library = Inform::Runtime.libraries[Inform::Runtime]
|
|
44
|
+
inform_library.play
|
|
45
|
+
inform_library
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The RuntimeClassMethods module
|
|
50
|
+
module RuntimeClassMethods
|
|
51
|
+
def libraries
|
|
52
|
+
Inform::Runtime::LibraryRegistry
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def default_config
|
|
56
|
+
@default_config ||= Inform::Config::DEFAULTS
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def default_game_dir_path
|
|
60
|
+
File.join(project_dir_path, Inform::Runtime.default_config[:game_dir_name])
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def support_dir_path
|
|
64
|
+
Inform::SUPPORT_DIR_PATH
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def inform_dir_path
|
|
68
|
+
Inform::INFORM_DIR_PATH
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def lib_dir_path
|
|
72
|
+
Inform::LIB_DIR_PATH
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def project_dir_path
|
|
76
|
+
Inform::PROJECT_DIR_PATH
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def database_saves_dir_path
|
|
80
|
+
File.join(Inform::Runtime.project_dir_path, '.' + Inform::Game.config[:database_name] + '_saves')
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def default_environment
|
|
84
|
+
Inform::Runtime.default_config[:environment]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def invocation_properties
|
|
88
|
+
return Inform::Runtime.instance.invocation_properties unless Inform::Runtime.instance.nil?
|
|
89
|
+
Inform::Runtime.default_config.fetch(:properties, '').split.map(&:to_sym)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def invocation_context
|
|
93
|
+
return Inform::Runtime.instance.invocation_context unless Inform::Runtime.instance.nil?
|
|
94
|
+
Struct.new(*Inform::Runtime.invocation_properties)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def language_name
|
|
98
|
+
Inform::Runtime.default_config[:language]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def main_gem_spec_executable
|
|
102
|
+
require 'rubygems'
|
|
103
|
+
current_working_dir = File.expand_path(__dir__)
|
|
104
|
+
gem_spec = Gem.loaded_specs.values.find do |s|
|
|
105
|
+
current_working_dir.start_with?(s.full_gem_path)
|
|
106
|
+
end
|
|
107
|
+
(gem_spec&.executables || []).first
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def player?(obj)
|
|
111
|
+
return false unless defined?(Inform::IO::Session) && Inform::IO::Session.respond_to?(:players)
|
|
112
|
+
Inform::IO::Session.players.include?(obj)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def init(options = Inform::Runtime.default_config)
|
|
116
|
+
Inform::Runtime.instance(options)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def instance(options = Inform::Runtime.default_config)
|
|
120
|
+
return @instance unless @instance.nil?
|
|
121
|
+
@instance_mutex.synchronize do
|
|
122
|
+
@instance ||= new(options)
|
|
123
|
+
end
|
|
124
|
+
@instance
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
# module RuntimeClassMethods
|
|
128
|
+
|
|
129
|
+
# The RuntimeInstanceMethods module
|
|
130
|
+
module RuntimeInstanceMethods
|
|
131
|
+
include RuntimeConstants
|
|
132
|
+
|
|
133
|
+
attr_accessor :main_object
|
|
134
|
+
|
|
135
|
+
def config_file_path
|
|
136
|
+
@config_file_path ||= File.join(game_path, @options[:game_config_file_name])
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def game_dir_name
|
|
140
|
+
@game_dir_name ||= @options[:game_dir_name]
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def game_path
|
|
144
|
+
@game_path ||= File.expand_path(@options[:game_path])
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def game_components
|
|
148
|
+
@game_components ||= @options.fetch(:game_components, '').split.map(&:to_sym)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def grammar_module_path
|
|
152
|
+
@grammar_module_path ||= File.join(game_path, @options[:game_grammar_module_name])
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def invocation_properties
|
|
156
|
+
@invocation_properties ||= @options.fetch(:properties, '').split.map(&:to_sym)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def invocation_context
|
|
160
|
+
@invocation_context ||= Struct.new(*invocation_properties)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def invoke_main_method
|
|
164
|
+
::Object.new.send(:Main)
|
|
165
|
+
rescue NameError => e
|
|
166
|
+
if UndefinedMainPattern.match?(e.message)
|
|
167
|
+
log.error "Main() definition is missing"
|
|
168
|
+
else
|
|
169
|
+
log.error e.message
|
|
170
|
+
e.backtrace.each { |t| log.error t } # TODO: Remove
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# TODO: Ensuring the location of the player object requires
|
|
175
|
+
# that this method be invoked before the move @player, @location
|
|
176
|
+
# operation in the InformLibrary#play method. This means that
|
|
177
|
+
# ensuring the location must take place either in the game-
|
|
178
|
+
# defined Initialise() method, or else it can happen in a
|
|
179
|
+
# provided LibraryExtension method, but in that case it would
|
|
180
|
+
# be at risk of being overridden by a game-defined Initialise()
|
|
181
|
+
# method.
|
|
182
|
+
# TODO: Try to figure out what the best approach here is. I
|
|
183
|
+
# think that if a multi-player game is to supply an entrypoint
|
|
184
|
+
# module, it will have to handle the location ensurance itself.
|
|
185
|
+
def ensure_location
|
|
186
|
+
return if inform_library.nil?
|
|
187
|
+
return if inform_library.location
|
|
188
|
+
player_obj = inform_library.player
|
|
189
|
+
location = player_obj.location
|
|
190
|
+
location ||= player_obj.spawn_point if player_obj.respond_to?(:spawn_point)
|
|
191
|
+
inform_library.location = location
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def inform_library(key = Inform::Runtime)
|
|
195
|
+
Inform::Runtime.libraries[key]
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def manage_inform_library(key = Inform::Runtime)
|
|
199
|
+
Inform::Runtime.libraries[key] ||= begin
|
|
200
|
+
inform_library = InformLibrary.new
|
|
201
|
+
if self.respond_to?(:specialized_player)
|
|
202
|
+
inform_library.selfobj = specialized_player
|
|
203
|
+
end
|
|
204
|
+
inform_library.subscribe(inform_library.selfobj)
|
|
205
|
+
inform_library
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def apply_preferences
|
|
210
|
+
return if inform_library.nil?
|
|
211
|
+
player_obj = inform_library.player
|
|
212
|
+
preferences = player_obj.&:preferences
|
|
213
|
+
return if preferences.nil? || !preferences.respond_to?(:properties)
|
|
214
|
+
preferences.properties.each_pair do |key, value|
|
|
215
|
+
variable = :"@#{key}"
|
|
216
|
+
Parser.initial_state.delete(variable)
|
|
217
|
+
inform_library.instance_variable_set(variable, value)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def manage_privileges
|
|
222
|
+
Inform::Object.include(Inform::Privileges)
|
|
223
|
+
return unless @options[:admin]
|
|
224
|
+
give inform_library.player, :admin
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def read_eval_print_loop
|
|
228
|
+
loop do
|
|
229
|
+
prompt
|
|
230
|
+
inform_library.send(inform_library.inform(read))
|
|
231
|
+
end
|
|
232
|
+
rescue Interrupt => e
|
|
233
|
+
$stdout.puts "\n#{e.class.name}"
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def play_game
|
|
237
|
+
log.debug "#{self}#play_game"
|
|
238
|
+
invoke_main_method
|
|
239
|
+
# ensure_location
|
|
240
|
+
apply_preferences
|
|
241
|
+
manage_privileges
|
|
242
|
+
read_eval_print_loop
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def quit
|
|
246
|
+
$stdout.print "[Hit enter to exit.]"
|
|
247
|
+
$stdin.getc
|
|
248
|
+
# Curses.getch
|
|
249
|
+
# Curses.close_screen
|
|
250
|
+
exit
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def to_s
|
|
254
|
+
"#<#{self.class.name}:#{object_id}>"
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def inspect
|
|
258
|
+
to_s
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
# module RuntimeInstanceMethods
|
|
262
|
+
|
|
263
|
+
# The Inform::Privileges module to implement privilege
|
|
264
|
+
module Privileges
|
|
265
|
+
def admin?
|
|
266
|
+
self.has?(:admin)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def builder?
|
|
270
|
+
admin? || self.has?(:builder)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# The Runtime class loads a game and plays it in a REPL session.
|
|
275
|
+
class Runtime
|
|
276
|
+
include Inform::RuntimeConstants
|
|
277
|
+
include Inform::RuntimeInstanceMethods
|
|
278
|
+
include Inform::Game::Loader
|
|
279
|
+
include Inform::Library::Loader
|
|
280
|
+
include Inform::IO
|
|
281
|
+
include Inform::Publisher
|
|
282
|
+
|
|
283
|
+
class << self
|
|
284
|
+
include Inform::RuntimeClassMethods
|
|
285
|
+
include Inform::Library::ClassMethods
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
@default_config = nil
|
|
289
|
+
@instance = nil
|
|
290
|
+
@instance_mutex = Mutex.new
|
|
291
|
+
|
|
292
|
+
private_class_method :new
|
|
293
|
+
|
|
294
|
+
def initialize(options = Inform::Runtime.default_config)
|
|
295
|
+
@options = options
|
|
296
|
+
Inform::Game.init(@options)
|
|
297
|
+
Inform::Context.set(invocation_context)
|
|
298
|
+
Inform.initialize_persistence_layer
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
# class Runtime
|
|
302
|
+
|
|
303
|
+
# The App module
|
|
304
|
+
module App
|
|
305
|
+
include Inform::Options
|
|
306
|
+
|
|
307
|
+
def main(args = parse_arguments)
|
|
308
|
+
Logging.log_level = args[:log_level]
|
|
309
|
+
runtime = Inform::Runtime.init(args)
|
|
310
|
+
runtime.load_game
|
|
311
|
+
runtime.play_game
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
# module Inform
|
|
316
|
+
|
|
317
|
+
if defined?(ZCODE_ONLY)
|
|
318
|
+
# Simulate compiling with Z-code only compiler
|
|
319
|
+
WORDSIZE = (2**((0.size * 8) - 2) - 1)
|
|
320
|
+
end
|
|
321
|
+
# defined?(ZCODE_ONLY)
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: false
|
|
3
|
+
|
|
4
|
+
# Copyright Nels Nelson 2008-2023 but freely usable (see license)
|
|
5
|
+
#
|
|
6
|
+
# This file is part of the Inform Runtime.
|
|
7
|
+
#
|
|
8
|
+
# The Inform Runtime is free software: you can redistribute it and/or
|
|
9
|
+
# modify it under the terms of the GNU General Public License as published
|
|
10
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
# (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# The Inform Runtime is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
# GNU General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU General Public License
|
|
19
|
+
# along with the Inform Runtime. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
|
|
21
|
+
require 'set'
|
|
22
|
+
|
|
23
|
+
# The SessionManagementMethods module
|
|
24
|
+
module SessionManagementMethods
|
|
25
|
+
Registry = Class.new(Hash)
|
|
26
|
+
SessionsByChannel = Registry.new
|
|
27
|
+
|
|
28
|
+
def add_promiscuous_states(*states)
|
|
29
|
+
Promiscuous.merge states.flatten
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def sessions_by_channel
|
|
33
|
+
SessionsByChannel
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def register(channel, session)
|
|
37
|
+
sessions_by_channel[channel] = session
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def unregister(channel)
|
|
41
|
+
sessions_by_channel.delete(channel)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def [](channel)
|
|
45
|
+
sessions_by_channel[channel]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def []=(channel, session)
|
|
49
|
+
sessions_by_channel[channel] = session
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def include?(channel)
|
|
53
|
+
sessions_by_channel.include?(channel)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def get(channel)
|
|
57
|
+
sessions_by_channel[channel] ||= Session.new(channel)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
# module SessionManagementMethods
|
|
61
|
+
|
|
62
|
+
# The SessionStateManagementMethods module
|
|
63
|
+
module SessionStateManagementMethods
|
|
64
|
+
def [](key)
|
|
65
|
+
@session_data[key.to_sym]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def []=(key, value)
|
|
69
|
+
@session_data[key.to_sym] = value
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def include?(key)
|
|
73
|
+
keys.include?(key)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def keys
|
|
77
|
+
@session_data.keys
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def values
|
|
81
|
+
@session_data.values
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def delete(key)
|
|
85
|
+
@session_data.delete(key)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
module Inform
|
|
90
|
+
module IO
|
|
91
|
+
# The Session class
|
|
92
|
+
# TODO: Refactor method implementations into modules
|
|
93
|
+
class Session
|
|
94
|
+
# These states accept any input, including no input
|
|
95
|
+
Promiscuous = Set.new
|
|
96
|
+
include SessionStateManagementMethods
|
|
97
|
+
|
|
98
|
+
class << self
|
|
99
|
+
include SessionManagementMethods
|
|
100
|
+
end
|
|
101
|
+
attr_accessor :channel, :state, :status
|
|
102
|
+
attr_reader :last_good_state, :last_activity, :previous, :inbound, :outbound, :settings
|
|
103
|
+
|
|
104
|
+
def initialize(channel = nil)
|
|
105
|
+
init_channel_session(channel) unless channel.nil?
|
|
106
|
+
@status = nil
|
|
107
|
+
@last_good_state = @state = default if respond_to? :default
|
|
108
|
+
@session_data = {}
|
|
109
|
+
@settings = {}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def init_channel_session(channel)
|
|
113
|
+
Session[channel] = self
|
|
114
|
+
@channel = channel
|
|
115
|
+
@inbound = Inbound.new(self)
|
|
116
|
+
@outbound = Outbound.new(self)
|
|
117
|
+
@last_activity = Time.now
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def receive(message)
|
|
121
|
+
return if message.nil? # It is okay if the message parameter is an empty string
|
|
122
|
+
return disconnected if @channel.nil?
|
|
123
|
+
return disconnected unless @channel.isActive() && @channel.isOpen()
|
|
124
|
+
process(sanitize(message))
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def update(machine = self)
|
|
128
|
+
safely_progress(machine)
|
|
129
|
+
after_update if self.respond_to?(:after_update)
|
|
130
|
+
@last_good_state = @state unless @state == :confused
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def after_update
|
|
134
|
+
# Override for application implementations
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def sanitize(message)
|
|
138
|
+
message.to_s.strip.scan(/[[:print:]]/).join
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def session
|
|
142
|
+
self
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
ExitCommandPattern = /^(exit|quit|q)$/i.freeze
|
|
146
|
+
|
|
147
|
+
def exit_command?(message)
|
|
148
|
+
!Promiscuous.include?(@state) && ExitCommandPattern.match?(message)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def process(message)
|
|
152
|
+
return disconnect if exit_command?(message)
|
|
153
|
+
@buffer = message
|
|
154
|
+
@last_activity = Time.now
|
|
155
|
+
@status = :active
|
|
156
|
+
if valid_state?
|
|
157
|
+
update
|
|
158
|
+
else
|
|
159
|
+
@inbound.publish @buffer
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
respond
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def respond
|
|
166
|
+
flush_io
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def safely_progress(machine = self)
|
|
170
|
+
@state = machine.send(@state)
|
|
171
|
+
raise 'Bad state machine implementation: nil state' if @state.nil?
|
|
172
|
+
rescue StandardError => e
|
|
173
|
+
log.error "Error updating state: #{e.message}", e
|
|
174
|
+
log.warn "Reverting to last known good state #{@last_good_state}"
|
|
175
|
+
@state = @last_good_state
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def valid_state?(machine = self)
|
|
179
|
+
case @state
|
|
180
|
+
when String, Symbol
|
|
181
|
+
machine.respond_to? @state
|
|
182
|
+
else
|
|
183
|
+
false
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def confused
|
|
188
|
+
println "The session is in an unknown state: #{@state}"
|
|
189
|
+
println "The last known good session state was: #{@last_good_state}"
|
|
190
|
+
println "Reverting the session to its last known good state, for better or worse."
|
|
191
|
+
prompt
|
|
192
|
+
@last_good_state
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
# class Session
|
|
196
|
+
|
|
197
|
+
# The Connection class
|
|
198
|
+
class Connection; end
|
|
199
|
+
end
|
|
200
|
+
# module IO
|
|
201
|
+
end
|
|
202
|
+
# module Inform
|