Class: Inquirex::Engine

Inherits:
Object
  • Object
show all
Defined in:
lib/inquirex/engine.rb,
lib/inquirex/engine/state_serializer.rb

Overview

Runtime session that drives flow navigation. Holds the definition, collected answers, and current position in the flow graph.

Collecting steps (ask, confirm): call engine.answer(value) Display steps (say, header, btw, warning): call engine.advance

Validates each answer via an optional Validation::Adapter, then advances using node transitions. Skips steps whose skip_if rule evaluates to true.

Defined Under Namespace

Modules: StateSerializer

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(definition, validator: Validation::NullAdapter.new) ⇒ Engine

Returns a new instance of Engine.

Parameters:

  • definition (Definition)

    the flow to run

  • validator (Validation::Adapter) (defaults to: Validation::NullAdapter.new)

    optional (default: NullAdapter)



34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/inquirex/engine.rb', line 34

def initialize(definition, validator: Validation::NullAdapter.new)
  @definition = definition
  @answers = {}
  @history = []
  @current_step_id = definition.start_step_id
  @validator = validator
  @totals = init_totals
  @completion_metadata = nil
  @after_completion_hooks = []
  @completion_hook_errors = []
  @history << @current_step_id
  skip_display_steps_if_needed
end

Instance Attribute Details

#answersObject (readonly)

Returns the value of attribute answers.



13
14
15
# File 'lib/inquirex/engine.rb', line 13

def answers
  @answers
end

#completion_hook_errorsArray<StandardError> (readonly)

Exceptions raised by after_completion hooks, in the order they were raised. Hooks are isolated from one another, so a raising hook is recorded here rather than propagated — callers that care can inspect this after the flow finishes. Empty when every hook succeeded.

Returns:

  • (Array<StandardError>)


30
31
32
# File 'lib/inquirex/engine.rb', line 30

def completion_hook_errors
  @completion_hook_errors
end

#completion_metadataCompletionMetadata?

Metadata describing how and where the flow was completed. Rendering front-ends (TTY, web, chat widget) attach a rich version from an after_completion hook; when no hook provides one, the engine stamps a minimal core version the moment the flow finishes. Only :engine and :engine_version are required members; everything else is free-form.

Returns:



22
23
24
# File 'lib/inquirex/engine.rb', line 22

def 
  @completion_metadata
end

#current_step_idObject (readonly)

Returns the value of attribute current_step_id.



13
14
15
# File 'lib/inquirex/engine.rb', line 13

def current_step_id
  @current_step_id
end

#definitionObject (readonly)

Returns the value of attribute definition.



13
14
15
# File 'lib/inquirex/engine.rb', line 13

def definition
  @definition
end

#historyObject (readonly)

Returns the value of attribute history.



13
14
15
# File 'lib/inquirex/engine.rb', line 13

def history
  @history
end

#totalsObject (readonly)

Returns the value of attribute totals.



13
14
15
# File 'lib/inquirex/engine.rb', line 13

def totals
  @totals
end

Class Method Details

.from_state(definition, state_hash, validator: Validation::NullAdapter.new) ⇒ Engine

Rebuilds an Engine from a previously saved state.

Parameters:

  • definition (Definition)

    same definition used when state was captured

  • state_hash (Hash)

    hash with state keys (may be strings from JSON)

  • validator (Validation::Adapter) (defaults to: Validation::NullAdapter.new)

Returns:



186
187
188
189
190
191
# File 'lib/inquirex/engine.rb', line 186

def self.from_state(definition, state_hash, validator: Validation::NullAdapter.new)
  state = StateSerializer.symbolize_state(state_hash)
  engine = allocate
  engine.send(:restore_state, definition, state, validator)
  engine
end

Instance Method Details

#advanceObject

Advances past the current non-collecting step (say/header/btw/warning).

Raises:



91
92
93
94
95
# File 'lib/inquirex/engine.rb', line 91

def advance
  raise Errors::AlreadyFinishedError, "Flow is already finished" if finished?

  advance_step
end

#after_completion {|Engine| ... } ⇒ Engine

Registers a hook to run when the flow finishes. The block receives the engine; front-ends typically use it to attach a rich completion_metadata (host, user, ips, terminal, ...). Optional — after all hooks run, the engine fills in a minimal CompletionMetadata when none of them provided one. Registering on an already-finished engine invokes the block immediately.

Any number of hooks may be registered; they run in registration order. Each is isolated from the others — a hook that raises a StandardError has it recorded in #completion_hook_errors, and the remaining hooks still run. Non-StandardError exceptions (Interrupt, SignalException) propagate, as they should.

Examples:

Stamp renderer-specific completion metadata

engine.after_completion do |eng|
  eng. = Inquirex::CompletionMetadata.new(
    engine: "inquirex-tty", engine_version: "0.5.0", hostname: Socket.gethostname
  )
end

Yields:

  • (Engine)

    the engine, at completion time

Returns:

Raises:

  • (ArgumentError)


146
147
148
149
150
151
152
153
154
155
# File 'lib/inquirex/engine.rb', line 146

def after_completion(&block)
  raise ArgumentError, "after_completion requires a block" unless block

  @after_completion_hooks << block
  if finished?
    invoke_completion_hook(block)
    
  end
  self
end

#answer(value) ⇒ Object

Submits an answer for the current collecting step (ask/confirm). Validates, stores, and advances to the next step.

Parameters:

  • value (Object)

    user's answer for the current step

Raises:



75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/inquirex/engine.rb', line 75

def answer(value)
  raise Errors::AlreadyFinishedError, "Flow is already finished" if finished?
  raise Errors::NonCollectingStepError, "Step #{@current_step_id} is a display step; use #advance instead" \
    unless current_step.collecting?

  result = @validator.validate(current_step, value)
  raise Errors::ValidationError, "Validation failed: #{result.errors.join(", ")}" unless result.valid?

  @answers[@current_step_id] = value
  apply_accumulations(current_step, value)
  advance_step
end

#answers_with_metadataHash

The collected answers with the completion metadata (when a renderer attached one) merged in under the :completion_metadata key.

Returns:

  • (Hash)


174
175
176
177
178
# File 'lib/inquirex/engine.rb', line 174

def 
  return @answers if @completion_metadata.nil?

  @answers.merge(completion_metadata: @completion_metadata.to_h)
end

#current_stepNode?

Returns current step node, or nil if flow is finished.

Returns:

  • (Node, nil)

    current step node, or nil if flow is finished



57
58
59
60
61
# File 'lib/inquirex/engine.rb', line 57

def current_step
  return nil if finished?

  @definition.step(@current_step_id)
end

#finished?Boolean

Returns true when there is no current step (flow ended).

Returns:

  • (Boolean)

    true when there is no current step (flow ended)



64
65
66
# File 'lib/inquirex/engine.rb', line 64

def finished?
  @current_step_id.nil?
end

#prefill!(hash) ⇒ Hash

Merges a hash of { step_id => value } into the top-level answers without clobbering answers the user has already provided. Used by LLM clarify steps to populate downstream answers from free-text extraction so that skip_if not_empty(:id) rules on later steps will fire.

Nil/empty values in the hash are ignored so that "unknown" LLM outputs don't spuriously satisfy not_empty rules.

If the engine's current step becomes skippable as a result of the prefill, it auto-advances past it.

Parameters:

  • hash (Hash)

    answers keyed by step id

Returns:

  • (Hash)

    the updated answers



110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/inquirex/engine.rb', line 110

def prefill!(hash)
  return @answers unless hash.is_a?(Hash)

  hash.each do |key, value|
    next if value.nil?
    next if value.respond_to?(:empty?) && value.empty?

    sym = key.to_sym
    @answers[sym] = value unless @answers.key?(sym)
  end
  skip_if_needed unless finished?
  @answers
end

#to_stateHash

Serializable state snapshot for persistence or resumption.

Returns:

  • (Hash)


160
161
162
163
164
165
166
167
168
# File 'lib/inquirex/engine.rb', line 160

def to_state
  {
    current_step_id:     @current_step_id,
    answers:             @answers,
    history:             @history,
    totals:              @totals,
    completion_metadata: @completion_metadata&.to_h
  }
end

#total(name) ⇒ Numeric

Convenience accessor for a single accumulator's running total.

Parameters:

  • name (Symbol)

    accumulator name (e.g. :price)

Returns:

  • (Numeric)


52
53
54
# File 'lib/inquirex/engine.rb', line 52

def total(name)
  @totals[name.to_sym] || 0
end