Class: StoryTeller::IO::Session

Inherits:
Object
  • Object
show all
Extended by:
SessionManagementMethods
Includes:
SessionStateManagementMethods
Defined in:
lib/story_teller/session.rb

Overview

The Session class TODO: Refactor method implementations into modules rubocop: disable Metrics/ClassLength

Constant Summary collapse

Promiscuous =

These states accept any input, including no input

Set.new
ExitCommandPattern =
/^(exit|quit|q)$/i.freeze

Constants included from SessionManagementMethods

SessionManagementMethods::SessionsByChannel, SessionManagementMethods::SessionsByPlayer

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from SessionManagementMethods

[], []=, add_promiscuous_states, channels, get, include?, of, players, register, register_player, release_player, sessions_by_channel, sessions_by_player, unregister

Methods included from SessionStateManagementMethods

#[], #[]=, #delete, #include?, #keys, #values

Constructor Details

#initialize(channel = nil, machine: nil, player: nil, state: :playing, settings: {}) ⇒ Session

rubocop: disable Metrics/MethodLength



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/story_teller/session.rb', line 190

def initialize(channel = nil, machine: nil, player: nil, state: :playing, settings: {})
  @channel = channel
  @machine = machine
  @player = nil
  @state = state
  @last_good_state = state
  @session_data = {}
  @settings = settings
  @status = nil
  @last_activity = Time.now
  self.output = OutputBuffer.new

  self.class.register(channel, self) unless channel.nil?
  control(player) unless player.nil?
end

Instance Attribute Details

#channelObject

Returns the value of attribute channel.



183
184
185
# File 'lib/story_teller/session.rb', line 183

def channel
  @channel
end

#inboundObject (readonly)

Returns the value of attribute inbound.



184
185
186
# File 'lib/story_teller/session.rb', line 184

def inbound
  @inbound
end

#last_activityObject (readonly)

Returns the value of attribute last_activity.



184
185
186
# File 'lib/story_teller/session.rb', line 184

def last_activity
  @last_activity
end

#last_good_stateObject (readonly)

Returns the value of attribute last_good_state.



184
185
186
# File 'lib/story_teller/session.rb', line 184

def last_good_state
  @last_good_state
end

#machineObject

Returns the value of attribute machine.



183
184
185
# File 'lib/story_teller/session.rb', line 183

def machine
  @machine
end

#outboundObject (readonly)

Returns the value of attribute outbound.



184
185
186
# File 'lib/story_teller/session.rb', line 184

def outbound
  @outbound
end

#outputObject

Returns the value of attribute output.



184
185
186
# File 'lib/story_teller/session.rb', line 184

def output
  @output
end

#playerObject

Returns the value of attribute player.



183
184
185
# File 'lib/story_teller/session.rb', line 183

def player
  @player
end

#previousObject (readonly)

Returns the value of attribute previous.



184
185
186
# File 'lib/story_teller/session.rb', line 184

def previous
  @previous
end

#settingsObject (readonly) Also known as: preferences

Returns the value of attribute settings.



184
185
186
# File 'lib/story_teller/session.rb', line 184

def settings
  @settings
end

#stateObject

Returns the value of attribute state.



183
184
185
# File 'lib/story_teller/session.rb', line 183

def state
  @state
end

#statusObject

Returns the value of attribute status.



183
184
185
# File 'lib/story_teller/session.rb', line 183

def status
  @status
end

Instance Method Details

#after_updateObject



304
305
306
# File 'lib/story_teller/session.rb', line 304

def after_update
  # Override for application implementations
end

#confusedObject



362
363
364
365
366
367
368
# File 'lib/story_teller/session.rb', line 362

def confused
  println "The session is in an unknown state: #{@state}"
  println "The last known good session state was: #{@last_good_state}"
  println "Reverting the session to its last known good state, for better or worse."
  prompt
  @last_good_state
end

#control(player) ⇒ Object



284
285
286
287
288
289
# File 'lib/story_teller/session.rb', line 284

def control(player)
  self.class.release_player(@player, self)
  @player = player
  self.class.register_player(player, self) unless player.nil?
  self
end

#drain_outputObject



218
219
220
# File 'lib/story_teller/session.rb', line 218

def drain_output
  tidy_output(@outbound.drain)
end

#exit_command?(message) ⇒ Boolean

Returns:

  • (Boolean)


323
324
325
# File 'lib/story_teller/session.rb', line 323

def exit_command?(message)
  !Promiscuous.include?(@state) && ExitCommandPattern.match?(message)
end

#expose_to(machine) ⇒ Object



316
317
318
319
# File 'lib/story_teller/session.rb', line 316

def expose_to(machine)
  machine.instance_variable_set(:@buffer, @buffer)
  machine.instance_variable_set(:@session, self)
end

#init_channel_session(channel) ⇒ Object



275
276
277
278
279
280
281
282
# File 'lib/story_teller/session.rb', line 275

def init_channel_session(channel)
  Session[channel] = self
  @channel = channel
  @inbound = Inbound.new(self) if defined?(Inbound)
  @output = OutputBuffer.new
  @outbound = @output
  @last_activity = Time.now
end

#process(message, machine = @machine) ⇒ Object

Raises:

  • (ArgumentError)


327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/story_teller/session.rb', line 327

def process(message, machine = @machine)
  raise ArgumentError, 'Session machine is required' if machine.nil?

  @machine = machine
  @buffer = sanitize(message)
  @last_activity = Time.now
  @status = :active

  expose_to(machine)
  update(machine)
  drain_output
end

#receive(message) ⇒ Object



291
292
293
294
295
296
# File 'lib/story_teller/session.rb', line 291

def receive(message)
  return if message.nil? # It is okay if the message parameter is an empty string
  return disconnected if @channel.nil?
  return disconnected unless @channel.isActive() && @channel.isOpen()
  process(sanitize(message))
end

#reset_outputObject



271
272
273
# File 'lib/story_teller/session.rb', line 271

def reset_output
  @outbound.reset
end

#respondObject



340
341
342
# File 'lib/story_teller/session.rb', line 340

def respond
  drain_output
end

#safely_progress(machine = self) ⇒ Object



344
345
346
347
348
349
350
351
# File 'lib/story_teller/session.rb', line 344

def safely_progress(machine = self)
  @state = machine.public_send(@state)
  raise 'Bad state machine implementation: nil state' if @state.nil?
rescue StandardError => e
  log.error "Error updating state: #{e.message}", e
  log.warn "Reverting to last known good state #{@last_good_state}"
  @state = @last_good_state
end

#sanitize(message) ⇒ Object



308
309
310
# File 'lib/story_teller/session.rb', line 308

def sanitize(message)
  message.to_s.strip.scan(/[[:print:]]/).join
end

#sessionObject



312
313
314
# File 'lib/story_teller/session.rb', line 312

def session
  self
end

#tidy_output(value, columns = word_wrap_column) ⇒ Object



222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/story_teller/session.rb', line 222

def tidy_output(value, columns = word_wrap_column)
  output = value.to_s
  return '' if output.empty?

  columns = columns.to_i
  return output if columns <= 0

  output.each_line.map do |line|
    newline = line.end_with?("\n") ? "\n" : ''
    wrap_line(line.chomp, columns) + newline
  end.join
end

#update(machine = @machine) ⇒ Object



298
299
300
301
302
# File 'lib/story_teller/session.rb', line 298

def update(machine = @machine)
  safely_progress(machine)
  after_update if self.respond_to?(:after_update)
  @last_good_state = @state unless @state == :confused
end

#valid_state?(machine = self) ⇒ Boolean

Returns:

  • (Boolean)


353
354
355
356
357
358
359
360
# File 'lib/story_teller/session.rb', line 353

def valid_state?(machine = self)
  case @state
  when String, Symbol
    machine.respond_to?(@state)
  else
    false
  end
end

#word_wrap_columnObject

rubocop: enable Metrics/AbcSize rubocop: enable Metrics/MethodLength



264
265
266
267
268
269
# File 'lib/story_teller/session.rb', line 264

def word_wrap_column
  value = preferences[:word_wrap]
  return 76 if value.nil?

  value.to_i
end

#wrap_line(line, columns) ⇒ Object

rubocop: disable Metrics/AbcSize rubocop: disable Metrics/MethodLength



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/story_teller/session.rb', line 237

def wrap_line(line, columns)
  return line if line.length <= columns

  indentation = line[/\A[ \t]*/].to_s
  words = line.strip.split(/[ \t]+/)
  return line if words.empty?

  lines = []
  current = indentation.dup

  words.each do |word|
    candidate = current == indentation ? "#{current}#{word}" : "#{current} #{word}"

    if candidate.length <= columns || current == indentation
      current = candidate
    else
      lines << current.rstrip
      current = "#{indentation}#{word}"
    end
  end

  lines << current.rstrip
  lines.join("\n")
end

#write_output(value) ⇒ Object



214
215
216
# File 'lib/story_teller/session.rb', line 214

def write_output(value)
  @output.write(value)
end