inquirex 0.6.1 → 0.7.0
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 +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +59 -0
- data/lib/inquirex/dsl/step_builder.rb +23 -0
- data/lib/inquirex/engine/state_serializer.rb +1 -0
- data/lib/inquirex/engine.rb +81 -4
- data/lib/inquirex/errors.rb +4 -0
- data/lib/inquirex/node.rb +18 -0
- data/lib/inquirex/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 67e5f1389368dbb7416aa60a83a7cf613997e72d728d4fb10bd8290741c83901
|
|
4
|
+
data.tar.gz: 105ed41192f4208d7a856e2c12edae0403343f19af45faeae7d557e5bd7048ef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9b2a1379ab0b37450c4621b03068939e63a1559dc5dec0d91de03d3819ba765d93f1c2587313738f5bd93bf0a9bbdfb8de5061f715d563407a49b306d7819d6d
|
|
7
|
+
data.tar.gz: 66313dc2a02d2ee469c8076e752ca73b5cd15b857ae475f91e566eea0f18f1756f8dfdddb87b9e66ce7fbde58a5871fc30fcd56bf30f9d812f3dc3abbfae4883
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.7.0] - 2026-07-21
|
|
4
|
+
|
|
5
|
+
- `required false` DSL builder method on collecting steps (`ask`, `confirm`): marks a question as optional so widgets render a small Skip control. Default remains `required true`, so every existing flow is unchanged.
|
|
6
|
+
- `Node#required?` predicate; step JSON gains `"required": false` (omitted when true), round-tripping through `to_json` / `from_json`.
|
|
7
|
+
- `Engine#skip` — user-initiated skip of the current optional step: records the step's `default` (when declared) into the answers and accumulators exactly as if answered, marks the step id in the new `Engine#skipped` list, and advances through transitions like `#answer`. Raises `Errors::RequiredStepError` (new) on a required step, `NonCollectingStepError` on a display step.
|
|
8
|
+
- Skip without a default writes no answers entry (a missing key and `nil` branch identically in rules; the "declined" signal lives in `skipped`).
|
|
9
|
+
- `Engine#skipped` / `Engine#skipped?(step_id)` distinguish default-by-skip values from user-provided answers; the list survives `to_state` / `from_state` (string→symbol normalized) and is merged into `Engine#answers_with_metadata` under `:skipped`.
|
|
10
|
+
- Steps elided automatically by `skip_if` rules are NOT marked as skipped — auto-elision is flow logic, `#skip` is a user action (see docs/design/required-and-skip.md).
|
|
11
|
+
|
|
3
12
|
## [0.6.0] - 2026-07-19
|
|
4
13
|
|
|
5
14
|
- Post-completion `action` DSL verb with an extensible effect registry
|
data/README.md
CHANGED
|
@@ -173,6 +173,7 @@ engine.finished? # => true
|
|
|
173
173
|
- `text "..."` for display steps
|
|
174
174
|
- `options [...]` or `options key: "Label"` for enum-style inputs
|
|
175
175
|
- `default value` or `default { |answers| ... }`
|
|
176
|
+
- `required false` (marks a question optional — renderers show a Skip control; see [Optional Questions and Skipping](#optional-questions-and-skipping))
|
|
176
177
|
- `skip_if rule`
|
|
177
178
|
- `transition to: :next_step, if_rule: rule, requires_server: false`
|
|
178
179
|
- `compute { |answers| ... }` (accepted by the DSL as a server-side hook; currently omitted from runtime JSON)
|
|
@@ -432,6 +433,7 @@ Behavior:
|
|
|
432
433
|
|
|
433
434
|
- Use `answer(value)` on collecting steps
|
|
434
435
|
- Use `advance` on display steps
|
|
436
|
+
- Use `skip` on optional (`required false`) collecting steps the user declines (see [Optional Questions and Skipping](#optional-questions-and-skipping))
|
|
435
437
|
- Use `finished?` to detect completion
|
|
436
438
|
- Use `total(:price)` / `totals` to read running totals
|
|
437
439
|
- Use `to_state` / `.from_state` for persistence/resume (totals included)
|
|
@@ -462,6 +464,61 @@ Pass a custom adapter to the engine:
|
|
|
462
464
|
engine = Inquirex::Engine.new(definition, validator: my_validator)
|
|
463
465
|
```
|
|
464
466
|
|
|
467
|
+
## Optional Questions and Skipping
|
|
468
|
+
|
|
469
|
+
Every collecting step is **required by default** — the flow will not advance without an answer. Declare `required false` to make a question optional: renderers (the JS widget, the TTY adapter, the qualified.at wizard) show a small **Skip** control next to the input, and a user who declines the question moves on via `Engine#skip`.
|
|
470
|
+
|
|
471
|
+
```ruby
|
|
472
|
+
ask :dependents do
|
|
473
|
+
type :integer
|
|
474
|
+
question "How many dependents?"
|
|
475
|
+
required false # renders a Skip control
|
|
476
|
+
default 0 # what a skip records into the answers
|
|
477
|
+
transition to: :adult_path, if_rule: greater_than(:dependents, 0)
|
|
478
|
+
transition to: :done
|
|
479
|
+
end
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
When the user skips:
|
|
483
|
+
|
|
484
|
+
- **With a `default`** — the default is recorded into `answers[:dependents]` and contributes to [accumulators](#accumulators) exactly as if the user had submitted it. Transition rules evaluate against the default, so branching stays deterministic.
|
|
485
|
+
- **Without a `default`** — no answers entry is written at all (not even `nil`). Rules read a missing key as `nil`, so branching behaves as if the question were unanswered.
|
|
486
|
+
- Either way the step id lands in `engine.skipped`, which is how a defaulted-by-skip value is **distinguished from an answer the user actually provided**:
|
|
487
|
+
|
|
488
|
+
```ruby
|
|
489
|
+
engine.skip # user pressed Skip on :dependents
|
|
490
|
+
engine.answers[:dependents] # => 0 (the default)
|
|
491
|
+
engine.skipped # => [:dependents]
|
|
492
|
+
engine.skipped?(:dependents) # => true
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
Guard rails:
|
|
496
|
+
|
|
497
|
+
- `skip` on a **required** step raises `Errors::RequiredStepError`
|
|
498
|
+
- `skip` on a **display** step raises `Errors::NonCollectingStepError` (use `advance`)
|
|
499
|
+
- `skip` after the flow finished raises `Errors::AlreadyFinishedError`
|
|
500
|
+
|
|
501
|
+
The `skipped` list survives `to_state` / `.from_state` round-trips (string keys from JSON are normalized back to symbols), and `engine.answers_with_metadata` merges it into the answers under `:skipped` — so post-completion actions (webhook payloads, email templates) and API consumers see which values were defaults-by-skip.
|
|
502
|
+
|
|
503
|
+
On the wire, step JSON carries `"required": false` (omitted when true, like other defaults):
|
|
504
|
+
|
|
505
|
+
```json
|
|
506
|
+
{
|
|
507
|
+
"dependents": {
|
|
508
|
+
"verb": "ask",
|
|
509
|
+
"type": "integer",
|
|
510
|
+
"question": "How many dependents?",
|
|
511
|
+
"default": 0,
|
|
512
|
+
"required": false,
|
|
513
|
+
"transitions": [{ "to": "done" }]
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
> [!NOTE]
|
|
519
|
+
>
|
|
520
|
+
> **`skip_if` is not the same thing.** A `skip_if` rule is *flow logic*: the definition elides the step from the path automatically, no default kicks in, and the step is **not** added to `engine.skipped` — it was never presented, so the user cannot have declined it. `Engine#skip` is a *user action* on a question that was presented. See [`docs/design/required-and-skip.md`](docs/design/required-and-skip.md) for the full design.
|
|
521
|
+
|
|
465
522
|
## Completion Metadata
|
|
466
523
|
|
|
467
524
|
When a flow finishes, the engine guarantees a `CompletionMetadata` — an OpenStruct describing how the answers were collected. Only `engine` and `engine_version` are required members; rendering front-ends attach richer environment details from an `after_completion` hook:
|
|
@@ -571,6 +628,7 @@ Important serialization details:
|
|
|
571
628
|
- Rule objects and accumulator shapes serialize and deserialize cleanly
|
|
572
629
|
- Proc/lambda defaults are stripped from JSON
|
|
573
630
|
- `requires_server: true` transition flag is preserved
|
|
631
|
+
- `required: false` step flag is preserved (omitted when true, the default)
|
|
574
632
|
- Snake-case theme keys are converted to camelCase on serialization to match the JS widget contract
|
|
575
633
|
|
|
576
634
|
## Answers Wrapper
|
|
@@ -615,6 +673,7 @@ Common exceptions under `Inquirex::Errors`:
|
|
|
615
673
|
- `AlreadyFinishedError`
|
|
616
674
|
- `ValidationError`
|
|
617
675
|
- `NonCollectingStepError`
|
|
676
|
+
- `RequiredStepError`
|
|
618
677
|
|
|
619
678
|
## Development
|
|
620
679
|
|
|
@@ -17,6 +17,7 @@ module Inquirex
|
|
|
17
17
|
@transitions = []
|
|
18
18
|
@skip_if = nil
|
|
19
19
|
@default = nil
|
|
20
|
+
@required = true
|
|
20
21
|
@compute = nil
|
|
21
22
|
@widget_hints = {}
|
|
22
23
|
@accumulations = []
|
|
@@ -114,12 +115,33 @@ module Inquirex
|
|
|
114
115
|
|
|
115
116
|
# Sets a default value for this step (shown pre-filled; user can change it).
|
|
116
117
|
# Can be a static value or a proc receiving collected answers so far.
|
|
118
|
+
# On a `required false` step the default is also what Engine#skip records
|
|
119
|
+
# into the answers when the user skips the question.
|
|
117
120
|
#
|
|
118
121
|
# @param value [Object, Proc]
|
|
119
122
|
def default(value = nil, &block)
|
|
120
123
|
@default = block || value
|
|
121
124
|
end
|
|
122
125
|
|
|
126
|
+
# Declares whether the user must answer this step (true by default).
|
|
127
|
+
# `required false` marks the question as optional: renderers show a small
|
|
128
|
+
# Skip control, and Engine#skip records the step's default (when one is
|
|
129
|
+
# declared) into the answers while marking the step as skipped.
|
|
130
|
+
#
|
|
131
|
+
# @example An optional question with a skip default
|
|
132
|
+
# ask :dependents do
|
|
133
|
+
# type :integer
|
|
134
|
+
# question "How many dependents?"
|
|
135
|
+
# required false
|
|
136
|
+
# default 0
|
|
137
|
+
# transition to: :done
|
|
138
|
+
# end
|
|
139
|
+
#
|
|
140
|
+
# @param value [Boolean] false to make the step skippable
|
|
141
|
+
def required(value = true)
|
|
142
|
+
@required = value
|
|
143
|
+
end
|
|
144
|
+
|
|
123
145
|
# Registers a compute block: auto-calculates a value from answers, not shown to user.
|
|
124
146
|
# The computed value is stored server-side only and stripped from JSON serialization.
|
|
125
147
|
#
|
|
@@ -143,6 +165,7 @@ module Inquirex
|
|
|
143
165
|
transitions: @transitions,
|
|
144
166
|
skip_if: @skip_if,
|
|
145
167
|
default: @default,
|
|
168
|
+
required: @required,
|
|
146
169
|
widget_hints: resolve_widget_hints,
|
|
147
170
|
accumulations: @accumulations
|
|
148
171
|
)
|
|
@@ -12,6 +12,7 @@ module Inquirex
|
|
|
12
12
|
answers: ->(v) { symbolize_answers(v) },
|
|
13
13
|
totals: ->(v) { symbolize_answers(v) },
|
|
14
14
|
suggestions: ->(v) { symbolize_answers(v) },
|
|
15
|
+
skipped: ->(v) { Array(v).map { |e| e&.to_sym } },
|
|
15
16
|
completion_metadata: ->(v) { symbolize_answers(v) }
|
|
16
17
|
}.freeze
|
|
17
18
|
|
data/lib/inquirex/engine.rb
CHANGED
|
@@ -6,6 +6,7 @@ module Inquirex
|
|
|
6
6
|
#
|
|
7
7
|
# Collecting steps (ask, confirm): call engine.answer(value)
|
|
8
8
|
# Display steps (say, header, btw, warning): call engine.advance
|
|
9
|
+
# Optional steps (declared `required false`): engine.skip is also allowed
|
|
9
10
|
#
|
|
10
11
|
# Validates each answer via an optional Validation::Adapter, then advances using
|
|
11
12
|
# node transitions. Skips steps whose skip_if rule evaluates to true.
|
|
@@ -30,6 +31,15 @@ module Inquirex
|
|
|
30
31
|
# @return [Hash{Symbol => Array}]
|
|
31
32
|
attr_reader :suggestions
|
|
32
33
|
|
|
34
|
+
# Step ids the user explicitly skipped via #skip, in the order they were
|
|
35
|
+
# skipped. Distinguishes default-by-skip values in answers from values the
|
|
36
|
+
# user actually provided. Steps elided automatically by their skip_if rule
|
|
37
|
+
# are NOT listed here — they were never presented, so the user cannot have
|
|
38
|
+
# declined them.
|
|
39
|
+
#
|
|
40
|
+
# @return [Array<Symbol>]
|
|
41
|
+
attr_reader :skipped
|
|
42
|
+
|
|
33
43
|
# Exceptions raised by after_completion hooks, in the order they were
|
|
34
44
|
# raised. Hooks are isolated from one another, so a raising hook is
|
|
35
45
|
# recorded here rather than propagated — callers that care can inspect
|
|
@@ -51,6 +61,7 @@ module Inquirex
|
|
|
51
61
|
@after_completion_hooks = []
|
|
52
62
|
@completion_hook_errors = []
|
|
53
63
|
@suggestions = {}
|
|
64
|
+
@skipped = []
|
|
54
65
|
@history << @current_step_id
|
|
55
66
|
skip_display_steps_if_needed
|
|
56
67
|
end
|
|
@@ -105,6 +116,53 @@ module Inquirex
|
|
|
105
116
|
advance_step
|
|
106
117
|
end
|
|
107
118
|
|
|
119
|
+
# Skips the current optional collecting step at the user's request — the
|
|
120
|
+
# engine-side handler for a widget's Skip button. Only steps declared with
|
|
121
|
+
# `required false` may be skipped.
|
|
122
|
+
#
|
|
123
|
+
# When the step has a default, the default is recorded into the answers and
|
|
124
|
+
# contributes to accumulators exactly as if the user had submitted it; the
|
|
125
|
+
# step id lands in #skipped so consumers can tell the value apart from one
|
|
126
|
+
# the user actually provided. Without a default, no answers entry is
|
|
127
|
+
# written (rules read a missing key as nil, so branching is unaffected).
|
|
128
|
+
# Advances through transitions exactly like #answer.
|
|
129
|
+
#
|
|
130
|
+
# @example Optional question, skipped by the user
|
|
131
|
+
# engine.current_step.required? # => false
|
|
132
|
+
# engine.skip
|
|
133
|
+
# engine.answers[:dependents] # => 0 (the step's default)
|
|
134
|
+
# engine.skipped # => [:dependents]
|
|
135
|
+
#
|
|
136
|
+
# @return [void]
|
|
137
|
+
# @raise [Errors::AlreadyFinishedError] if the flow has already finished
|
|
138
|
+
# @raise [Errors::NonCollectingStepError] if the current step is a display verb
|
|
139
|
+
# @raise [Errors::RequiredStepError] if the current step is required
|
|
140
|
+
def skip
|
|
141
|
+
raise Errors::AlreadyFinishedError, "Flow is already finished" if finished?
|
|
142
|
+
raise Errors::NonCollectingStepError, "Step #{@current_step_id} is a display step; use #advance instead" \
|
|
143
|
+
unless current_step.collecting?
|
|
144
|
+
raise Errors::RequiredStepError, "Step #{@current_step_id} is required and cannot be skipped" \
|
|
145
|
+
if current_step.required?
|
|
146
|
+
|
|
147
|
+
node = current_step
|
|
148
|
+
default = resolve_default(node)
|
|
149
|
+
unless default.nil?
|
|
150
|
+
@answers[@current_step_id] = default
|
|
151
|
+
apply_accumulations(node, default)
|
|
152
|
+
end
|
|
153
|
+
@skipped << @current_step_id unless @skipped.include?(@current_step_id)
|
|
154
|
+
@suggestions.delete(@current_step_id)
|
|
155
|
+
advance_step
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Whether the user explicitly skipped the given step via #skip.
|
|
159
|
+
#
|
|
160
|
+
# @param step_id [Symbol, String] step id
|
|
161
|
+
# @return [Boolean]
|
|
162
|
+
def skipped?(step_id)
|
|
163
|
+
@skipped.include?(step_id.to_sym)
|
|
164
|
+
end
|
|
165
|
+
|
|
108
166
|
# Merges a hash of { step_id => value } into the top-level answers without
|
|
109
167
|
# clobbering answers the user has already provided. Used by LLM clarify
|
|
110
168
|
# steps to populate downstream answers from free-text extraction so that
|
|
@@ -191,18 +249,23 @@ module Inquirex
|
|
|
191
249
|
history: @history,
|
|
192
250
|
totals: @totals,
|
|
193
251
|
suggestions: @suggestions,
|
|
252
|
+
skipped: @skipped,
|
|
194
253
|
completion_metadata: @completion_metadata&.to_h
|
|
195
254
|
}
|
|
196
255
|
end
|
|
197
256
|
|
|
198
257
|
# The collected answers with the completion metadata (when a renderer
|
|
199
|
-
# attached one) merged in under the :completion_metadata key
|
|
258
|
+
# attached one) merged in under the :completion_metadata key, and the
|
|
259
|
+
# user-skipped step ids (when any) under the :skipped key — so
|
|
260
|
+
# post-completion actions and API consumers can tell default-by-skip
|
|
261
|
+
# values apart from answers the user actually provided.
|
|
200
262
|
#
|
|
201
263
|
# @return [Hash]
|
|
202
264
|
def answers_with_metadata
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
@
|
|
265
|
+
extra = {}
|
|
266
|
+
extra[:completion_metadata] = @completion_metadata.to_h if @completion_metadata
|
|
267
|
+
extra[:skipped] = @skipped.dup unless @skipped.empty?
|
|
268
|
+
extra.empty? ? @answers : @answers.merge(extra)
|
|
206
269
|
end
|
|
207
270
|
|
|
208
271
|
# Rebuilds an Engine from a previously saved state.
|
|
@@ -231,6 +294,7 @@ module Inquirex
|
|
|
231
294
|
@after_completion_hooks = []
|
|
232
295
|
@completion_hook_errors = []
|
|
233
296
|
@suggestions = state[:suggestions] || {}
|
|
297
|
+
@skipped = state[:skipped] || []
|
|
234
298
|
end
|
|
235
299
|
|
|
236
300
|
# Whether +step_id+ names a multi-select step in the definition. Unknown
|
|
@@ -255,6 +319,19 @@ module Inquirex
|
|
|
255
319
|
end
|
|
256
320
|
end
|
|
257
321
|
|
|
322
|
+
# The step's default as a concrete value: a Proc default (server-side only,
|
|
323
|
+
# stripped from JSON) is called with the answers collected so far, exactly
|
|
324
|
+
# as a renderer pre-filling the field would resolve it.
|
|
325
|
+
#
|
|
326
|
+
# @param node [Node]
|
|
327
|
+
# @return [Object, nil]
|
|
328
|
+
def resolve_default(node)
|
|
329
|
+
default = node.default
|
|
330
|
+
return default unless default.is_a?(Proc)
|
|
331
|
+
|
|
332
|
+
default.arity.zero? ? default.call : default.call(@answers)
|
|
333
|
+
end
|
|
334
|
+
|
|
258
335
|
def advance_step
|
|
259
336
|
node = @definition.step(@current_step_id)
|
|
260
337
|
next_id = node.next_step_id(@answers)
|
data/lib/inquirex/errors.rb
CHANGED
|
@@ -25,6 +25,10 @@ module Inquirex
|
|
|
25
25
|
# Use Engine#advance for non-collecting steps.
|
|
26
26
|
class NonCollectingStepError < EngineError; end
|
|
27
27
|
|
|
28
|
+
# Raised when Engine#skip is called on a step that is required (the default).
|
|
29
|
+
# Only steps declared with `required false` may be skipped by the user.
|
|
30
|
+
class RequiredStepError < EngineError; end
|
|
31
|
+
|
|
28
32
|
# Raised when serializing or deserializing a Definition to/from JSON fails.
|
|
29
33
|
class SerializationError < Error; end
|
|
30
34
|
|
data/lib/inquirex/node.rb
CHANGED
|
@@ -23,6 +23,8 @@ module Inquirex
|
|
|
23
23
|
# @attr_reader transitions [Array<Transition>] ordered conditional next-step edges
|
|
24
24
|
# @attr_reader skip_if [Rules::Base, nil] rule to skip this step entirely
|
|
25
25
|
# @attr_reader default [Object, nil] default value (pre-fill, user can change)
|
|
26
|
+
# @attr_reader required [Boolean] whether the user must answer (true by default);
|
|
27
|
+
# `required false` steps render a Skip control and accept Engine#skip
|
|
26
28
|
# @attr_reader widget_hints [Hash{Symbol => WidgetHint}, nil] rendering hints per target
|
|
27
29
|
class Node
|
|
28
30
|
# Valid DSL verbs and which ones collect input from the user.
|
|
@@ -48,6 +50,7 @@ module Inquirex
|
|
|
48
50
|
:transitions,
|
|
49
51
|
:skip_if,
|
|
50
52
|
:default,
|
|
53
|
+
:required,
|
|
51
54
|
:widget_hints,
|
|
52
55
|
:accumulations
|
|
53
56
|
|
|
@@ -60,6 +63,7 @@ module Inquirex
|
|
|
60
63
|
transitions: [],
|
|
61
64
|
skip_if: nil,
|
|
62
65
|
default: nil,
|
|
66
|
+
required: true,
|
|
63
67
|
widget_hints: nil,
|
|
64
68
|
accumulations: [])
|
|
65
69
|
@id = id.to_sym
|
|
@@ -70,6 +74,7 @@ module Inquirex
|
|
|
70
74
|
@transitions = transitions.freeze
|
|
71
75
|
@skip_if = skip_if
|
|
72
76
|
@default = default
|
|
77
|
+
@required = required ? true : false
|
|
73
78
|
@widget_hints = widget_hints&.freeze
|
|
74
79
|
@accumulations = accumulations.freeze
|
|
75
80
|
extract_options(options)
|
|
@@ -86,6 +91,15 @@ module Inquirex
|
|
|
86
91
|
DISPLAY_VERBS.include?(@verb)
|
|
87
92
|
end
|
|
88
93
|
|
|
94
|
+
# Whether the user must answer this step. True by default; steps declared
|
|
95
|
+
# with `required false` render a Skip control and accept Engine#skip.
|
|
96
|
+
# Only meaningful for collecting steps.
|
|
97
|
+
#
|
|
98
|
+
# @return [Boolean]
|
|
99
|
+
def required?
|
|
100
|
+
@required
|
|
101
|
+
end
|
|
102
|
+
|
|
89
103
|
# Returns the explicit widget hint for the given target, or nil.
|
|
90
104
|
#
|
|
91
105
|
# @param target [Symbol] e.g. :desktop, :mobile, :tty
|
|
@@ -135,6 +149,7 @@ module Inquirex
|
|
|
135
149
|
hash["options"] = serialize_options if @options
|
|
136
150
|
hash["skip_if"] = @skip_if.to_h if @skip_if
|
|
137
151
|
hash["default"] = @default unless @default.nil? || @default.is_a?(Proc)
|
|
152
|
+
hash["required"] = false unless @required
|
|
138
153
|
elsif @text
|
|
139
154
|
hash["text"] = @text
|
|
140
155
|
end
|
|
@@ -169,6 +184,8 @@ module Inquirex
|
|
|
169
184
|
transitions_data = hash["transitions"] || hash[:transitions] || []
|
|
170
185
|
skip_if_data = hash["skip_if"] || hash[:skip_if]
|
|
171
186
|
default = hash["default"] || hash[:default]
|
|
187
|
+
# Fetch chain (not ||) so an explicit false survives; absent key means required.
|
|
188
|
+
required = hash.fetch("required") { hash.fetch(:required, true) }
|
|
172
189
|
widget_data = hash["widget"] || hash[:widget]
|
|
173
190
|
accumulate_data = hash["accumulate"] || hash[:accumulate]
|
|
174
191
|
|
|
@@ -188,6 +205,7 @@ module Inquirex
|
|
|
188
205
|
transitions:,
|
|
189
206
|
skip_if:,
|
|
190
207
|
default:,
|
|
208
|
+
required:,
|
|
191
209
|
widget_hints:,
|
|
192
210
|
accumulations:
|
|
193
211
|
)
|
data/lib/inquirex/version.rb
CHANGED