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
  @outbound = 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

#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



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

def after_update
  # Override for application implementations
end

#confusedObject



357
358
359
360
361
362
363
# File 'lib/story_teller/session.rb', line 357

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



279
280
281
282
283
284
# File 'lib/story_teller/session.rb', line 279

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

#drain_outputObject



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

def drain_output
  tidy_output(@outbound.drain)
end

#exit_command?(message) ⇒ Boolean

Returns:

  • (Boolean)


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

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

#expose_to(machine) ⇒ Object



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

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

#init_channel_session(channel) ⇒ Object



271
272
273
274
275
276
277
# File 'lib/story_teller/session.rb', line 271

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

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

Raises:

  • (ArgumentError)


322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/story_teller/session.rb', line 322

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



286
287
288
289
290
291
# File 'lib/story_teller/session.rb', line 286

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



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

def reset_output
  @outbound.reset
end

#respondObject



335
336
337
# File 'lib/story_teller/session.rb', line 335

def respond
  drain_output
end

#safely_progress(machine = self) ⇒ Object



339
340
341
342
343
344
345
346
# File 'lib/story_teller/session.rb', line 339

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



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

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

#sessionObject



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

def session
  self
end

#tidy_output(value, columns = word_wrap_column) ⇒ Object



217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/story_teller/session.rb', line 217

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 = output.gsub(/^\n+/, "\n")
  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



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

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)


348
349
350
351
352
353
354
355
# File 'lib/story_teller/session.rb', line 348

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



260
261
262
263
264
265
# File 'lib/story_teller/session.rb', line 260

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



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/story_teller/session.rb', line 233

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



209
210
211
# File 'lib/story_teller/session.rb', line 209

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