Class: Inquirex::Engine
- Inherits:
-
Object
- Object
- Inquirex::Engine
- 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
-
#answers ⇒ Object
readonly
Returns the value of attribute answers.
-
#completion_hook_errors ⇒ Array<StandardError>
readonly
Exceptions raised by after_completion hooks, in the order they were raised.
-
#completion_metadata ⇒ CompletionMetadata?
Metadata describing how and where the flow was completed.
-
#current_step_id ⇒ Object
readonly
Returns the value of attribute current_step_id.
-
#definition ⇒ Object
readonly
Returns the value of attribute definition.
-
#history ⇒ Object
readonly
Returns the value of attribute history.
-
#suggestions ⇒ Hash{Symbol => Array}
readonly
Answer suggestions produced by prefill! for multi-select steps, keyed by step id.
-
#totals ⇒ Object
readonly
Returns the value of attribute totals.
Class Method Summary collapse
-
.from_state(definition, state_hash, validator: Validation::NullAdapter.new) ⇒ Engine
Rebuilds an Engine from a previously saved state.
Instance Method Summary collapse
-
#advance ⇒ Object
Advances past the current non-collecting step (say/header/btw/warning).
-
#after_completion {|Engine| ... } ⇒ Engine
Registers a hook to run when the flow finishes.
-
#answer(value) ⇒ Object
Submits an answer for the current collecting step (ask/confirm).
-
#answers_with_metadata ⇒ Hash
The collected answers with the completion metadata (when a renderer attached one) merged in under the :completion_metadata key.
-
#current_step ⇒ Node?
Current step node, or nil if flow is finished.
-
#finished? ⇒ Boolean
True when there is no current step (flow ended).
-
#initialize(definition, validator: Validation::NullAdapter.new) ⇒ Engine
constructor
A new instance of Engine.
-
#prefill!(hash) ⇒ Hash
Merges a hash of { step_id => value } into the top-level answers without clobbering answers the user has already provided.
-
#suggestion_for(step_id) ⇒ Array?
The prefill suggestion for a step, or nil when none was recorded.
-
#to_state ⇒ Hash
Serializable state snapshot for persistence or resumption.
-
#total(name) ⇒ Numeric
Convenience accessor for a single accumulator's running total.
Constructor Details
#initialize(definition, validator: Validation::NullAdapter.new) ⇒ Engine
Returns a new instance of Engine.
43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/inquirex/engine.rb', line 43 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 = [] @suggestions = {} @history << @current_step_id skip_display_steps_if_needed end |
Instance Attribute Details
#answers ⇒ Object (readonly)
Returns the value of attribute answers.
13 14 15 |
# File 'lib/inquirex/engine.rb', line 13 def answers @answers end |
#completion_hook_errors ⇒ Array<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.
39 40 41 |
# File 'lib/inquirex/engine.rb', line 39 def completion_hook_errors @completion_hook_errors end |
#completion_metadata ⇒ CompletionMetadata?
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.
22 23 24 |
# File 'lib/inquirex/engine.rb', line 22 def @completion_metadata end |
#current_step_id ⇒ Object (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 |
#definition ⇒ Object (readonly)
Returns the value of attribute definition.
13 14 15 |
# File 'lib/inquirex/engine.rb', line 13 def definition @definition end |
#history ⇒ Object (readonly)
Returns the value of attribute history.
13 14 15 |
# File 'lib/inquirex/engine.rb', line 13 def history @history end |
#suggestions ⇒ Hash{Symbol => Array} (readonly)
Answer suggestions produced by prefill! for multi-select steps, keyed by step id. A suggestion pre-populates the step's choices in a renderer but — unlike an answer — never satisfies skip_if rules: multi-select extraction is treated as a hint the user confirms and may extend, not a deterministic fact. Cleared per step once the user answers it.
31 32 33 |
# File 'lib/inquirex/engine.rb', line 31 def suggestions @suggestions end |
#totals ⇒ Object (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.
214 215 216 217 218 219 |
# File 'lib/inquirex/engine.rb', line 214 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
#advance ⇒ Object
Advances past the current non-collecting step (say/header/btw/warning).
102 103 104 105 106 |
# File 'lib/inquirex/engine.rb', line 102 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.
173 174 175 176 177 178 179 180 181 182 |
# File 'lib/inquirex/engine.rb', line 173 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.
85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/inquirex/engine.rb', line 85 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 @suggestions.delete(@current_step_id) apply_accumulations(current_step, value) advance_step end |
#answers_with_metadata ⇒ Hash
The collected answers with the completion metadata (when a renderer attached one) merged in under the :completion_metadata key.
202 203 204 205 206 |
# File 'lib/inquirex/engine.rb', line 202 def return @answers if @completion_metadata.nil? @answers.merge(completion_metadata: @completion_metadata.to_h) end |
#current_step ⇒ Node?
Returns current step node, or nil if flow is finished.
67 68 69 70 71 |
# File 'lib/inquirex/engine.rb', line 67 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).
74 75 76 |
# File 'lib/inquirex/engine.rb', line 74 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.
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/inquirex/engine.rb', line 121 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 if multi_select_step?(sym) # Multi-select extraction is a hint, not a fact: the user may have # more selections in mind than the text revealed. Record it as a # suggestion so renderers pre-check the choices while the question # is still asked; skip_if rules see no answer and do not fire. @suggestions[sym] = Array(value) unless @answers.key?(sym) else @answers[sym] = value unless @answers.key?(sym) end end skip_if_needed unless finished? @answers end |
#suggestion_for(step_id) ⇒ Array?
The prefill suggestion for a step, or nil when none was recorded.
147 148 149 |
# File 'lib/inquirex/engine.rb', line 147 def suggestion_for(step_id) @suggestions[step_id.to_sym] end |
#to_state ⇒ Hash
Serializable state snapshot for persistence or resumption.
187 188 189 190 191 192 193 194 195 196 |
# File 'lib/inquirex/engine.rb', line 187 def to_state { current_step_id: @current_step_id, answers: @answers, history: @history, totals: @totals, suggestions: @suggestions, completion_metadata: @completion_metadata&.to_h } end |
#total(name) ⇒ Numeric
Convenience accessor for a single accumulator's running total.
62 63 64 |
# File 'lib/inquirex/engine.rb', line 62 def total(name) @totals[name.to_sym] || 0 end |