inquirex 0.5.0 → 0.6.1

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +88 -6
  4. data/lib/.DS_Store +0 -0
  5. data/lib/inquirex/accumulator.rb +29 -0
  6. data/lib/inquirex/actions/action.rb +68 -0
  7. data/lib/inquirex/actions/base.rb +41 -0
  8. data/lib/inquirex/actions/custom.rb +31 -0
  9. data/lib/inquirex/actions/outbox.rb +57 -0
  10. data/lib/inquirex/actions/runner.rb +52 -0
  11. data/lib/inquirex/actions/send_email.rb +174 -0
  12. data/lib/inquirex/actions/template.rb +95 -0
  13. data/lib/inquirex/actions/webhook.rb +139 -0
  14. data/lib/inquirex/actions.rb +57 -0
  15. data/lib/inquirex/answers.rb +13 -0
  16. data/lib/inquirex/completion_metadata.rb +82 -0
  17. data/lib/inquirex/definition.rb +67 -5
  18. data/lib/inquirex/dsl/action_builder.rb +53 -0
  19. data/lib/inquirex/dsl/flow_builder.rb +49 -6
  20. data/lib/inquirex/engine/state_serializer.rb +7 -4
  21. data/lib/inquirex/engine.rb +136 -6
  22. data/lib/inquirex/errors.rb +5 -0
  23. data/lib/inquirex/graph/mermaid_exporter.rb +2 -0
  24. data/lib/inquirex/node.rb +2 -0
  25. data/lib/inquirex/rules/all.rb +12 -0
  26. data/lib/inquirex/rules/any.rb +11 -0
  27. data/lib/inquirex/rules/base.rb +6 -0
  28. data/lib/inquirex/rules/contains.rb +12 -0
  29. data/lib/inquirex/rules/equals.rb +12 -0
  30. data/lib/inquirex/rules/greater_than.rb +12 -0
  31. data/lib/inquirex/rules/less_than.rb +12 -0
  32. data/lib/inquirex/rules/not_empty.rb +12 -0
  33. data/lib/inquirex/validation/adapter.rb +1 -0
  34. data/lib/inquirex/validation/null_adapter.rb +5 -0
  35. data/lib/inquirex/version.rb +1 -1
  36. data/lib/inquirex/widget_registry.rb +1 -0
  37. data/lib/inquirex.rb +13 -0
  38. metadata +30 -18
  39. data/.relaxed_rubocop.yml +0 -153
  40. data/.rub +0 -0
  41. data/.ruby-version +0 -1
  42. data/.secrets.baseline +0 -127
  43. data/Brewfile +0 -7
  44. data/Rakefile +0 -36
  45. data/docs/badges/coverage_badge.svg +0 -21
  46. data/examples/01_readme_example.rb +0 -55
  47. data/examples/02_readme_mermaid.png +0 -0
  48. data/examples/02_readme_mermaid.rb +0 -36
  49. data/examples/README.md +0 -33
  50. data/justfile +0 -70
  51. data/lefthook.yml +0 -35
  52. data/sig/inquirex.rbs +0 -4
@@ -12,6 +12,32 @@ module Inquirex
12
12
  class Engine
13
13
  attr_reader :definition, :answers, :history, :current_step_id, :totals
14
14
 
15
+ # Metadata describing how and where the flow was completed. Rendering
16
+ # front-ends (TTY, web, chat widget) attach a rich version from an
17
+ # after_completion hook; when no hook provides one, the engine stamps a
18
+ # minimal core version the moment the flow finishes. Only :engine and
19
+ # :engine_version are required members; everything else is free-form.
20
+ #
21
+ # @return [CompletionMetadata, nil] nil until the flow finishes
22
+ attr_accessor :completion_metadata
23
+
24
+ # Answer suggestions produced by prefill! for multi-select steps, keyed by
25
+ # step id. A suggestion pre-populates the step's choices in a renderer but
26
+ # — unlike an answer — never satisfies skip_if rules: multi-select
27
+ # extraction is treated as a hint the user confirms and may extend, not a
28
+ # deterministic fact. Cleared per step once the user answers it.
29
+ #
30
+ # @return [Hash{Symbol => Array}]
31
+ attr_reader :suggestions
32
+
33
+ # Exceptions raised by after_completion hooks, in the order they were
34
+ # raised. Hooks are isolated from one another, so a raising hook is
35
+ # recorded here rather than propagated — callers that care can inspect
36
+ # this after the flow finishes. Empty when every hook succeeded.
37
+ #
38
+ # @return [Array<StandardError>]
39
+ attr_reader :completion_hook_errors
40
+
15
41
  # @param definition [Definition] the flow to run
16
42
  # @param validator [Validation::Adapter] optional (default: NullAdapter)
17
43
  def initialize(definition, validator: Validation::NullAdapter.new)
@@ -21,6 +47,10 @@ module Inquirex
21
47
  @current_step_id = definition.start_step_id
22
48
  @validator = validator
23
49
  @totals = init_totals
50
+ @completion_metadata = nil
51
+ @after_completion_hooks = []
52
+ @completion_hook_errors = []
53
+ @suggestions = {}
24
54
  @history << @current_step_id
25
55
  skip_display_steps_if_needed
26
56
  end
@@ -61,6 +91,7 @@ module Inquirex
61
91
  raise Errors::ValidationError, "Validation failed: #{result.errors.join(", ")}" unless result.valid?
62
92
 
63
93
  @answers[@current_step_id] = value
94
+ @suggestions.delete(@current_step_id)
64
95
  apply_accumulations(current_step, value)
65
96
  advance_step
66
97
  end
@@ -95,24 +126,85 @@ module Inquirex
95
126
  next if value.respond_to?(:empty?) && value.empty?
96
127
 
97
128
  sym = key.to_sym
98
- @answers[sym] = value unless @answers.key?(sym)
129
+ if multi_select_step?(sym)
130
+ # Multi-select extraction is a hint, not a fact: the user may have
131
+ # more selections in mind than the text revealed. Record it as a
132
+ # suggestion so renderers pre-check the choices while the question
133
+ # is still asked; skip_if rules see no answer and do not fire.
134
+ @suggestions[sym] = Array(value) unless @answers.key?(sym)
135
+ else
136
+ @answers[sym] = value unless @answers.key?(sym)
137
+ end
99
138
  end
100
139
  skip_if_needed unless finished?
101
140
  @answers
102
141
  end
103
142
 
143
+ # The prefill suggestion for a step, or nil when none was recorded.
144
+ #
145
+ # @param step_id [Symbol, String] step id
146
+ # @return [Array, nil] suggested selections for a multi-select step
147
+ def suggestion_for(step_id)
148
+ @suggestions[step_id.to_sym]
149
+ end
150
+
151
+ # Registers a hook to run when the flow finishes. The block receives the
152
+ # engine; front-ends typically use it to attach a rich
153
+ # completion_metadata (host, user, ips, terminal, ...). Optional — after
154
+ # all hooks run, the engine fills in a minimal CompletionMetadata when
155
+ # none of them provided one. Registering on an already-finished engine
156
+ # invokes the block immediately.
157
+ #
158
+ # Any number of hooks may be registered; they run in registration order.
159
+ # Each is isolated from the others — a hook that raises a StandardError
160
+ # has it recorded in #completion_hook_errors, and the remaining hooks
161
+ # still run. Non-StandardError exceptions (Interrupt, SignalException)
162
+ # propagate, as they should.
163
+ #
164
+ # @example Stamp renderer-specific completion metadata
165
+ # engine.after_completion do |eng|
166
+ # eng.completion_metadata = Inquirex::CompletionMetadata.new(
167
+ # engine: "inquirex-tty", engine_version: "0.5.0", hostname: Socket.gethostname
168
+ # )
169
+ # end
170
+ #
171
+ # @yield [Engine] the engine, at completion time
172
+ # @return [Engine] self
173
+ def after_completion(&block)
174
+ raise ArgumentError, "after_completion requires a block" unless block
175
+
176
+ @after_completion_hooks << block
177
+ if finished?
178
+ invoke_completion_hook(block)
179
+ ensure_completion_metadata
180
+ end
181
+ self
182
+ end
183
+
104
184
  # Serializable state snapshot for persistence or resumption.
105
185
  #
106
186
  # @return [Hash]
107
187
  def to_state
108
188
  {
109
- current_step_id: @current_step_id,
110
- answers: @answers,
111
- history: @history,
112
- totals: @totals
189
+ current_step_id: @current_step_id,
190
+ answers: @answers,
191
+ history: @history,
192
+ totals: @totals,
193
+ suggestions: @suggestions,
194
+ completion_metadata: @completion_metadata&.to_h
113
195
  }
114
196
  end
115
197
 
198
+ # The collected answers with the completion metadata (when a renderer
199
+ # attached one) merged in under the :completion_metadata key.
200
+ #
201
+ # @return [Hash]
202
+ def answers_with_metadata
203
+ return @answers if @completion_metadata.nil?
204
+
205
+ @answers.merge(completion_metadata: @completion_metadata.to_h)
206
+ end
207
+
116
208
  # Rebuilds an Engine from a previously saved state.
117
209
  #
118
210
  # @param definition [Definition] same definition used when state was captured
@@ -135,6 +227,19 @@ module Inquirex
135
227
  @answers = state[:answers] || {}
136
228
  @history = state[:history] || []
137
229
  @totals = state[:totals] || init_totals
230
+ @completion_metadata = CompletionMetadata.from_h(state[:completion_metadata])
231
+ @after_completion_hooks = []
232
+ @completion_hook_errors = []
233
+ @suggestions = state[:suggestions] || {}
234
+ end
235
+
236
+ # Whether +step_id+ names a multi-select step in the definition. Unknown
237
+ # ids (e.g. an extract schema field that matches no step) are not.
238
+ #
239
+ # @param step_id [Symbol]
240
+ # @return [Boolean]
241
+ def multi_select_step?(step_id)
242
+ @definition.step_ids.include?(step_id) && @definition.step(step_id).type == :multi_enum
138
243
  end
139
244
 
140
245
  def init_totals
@@ -154,12 +259,37 @@ module Inquirex
154
259
  node = @definition.step(@current_step_id)
155
260
  next_id = node.next_step_id(@answers)
156
261
  @current_step_id = next_id
157
- return unless next_id
262
+ return run_after_completion_hooks unless next_id
158
263
 
159
264
  @history << next_id
160
265
  skip_if_needed
161
266
  end
162
267
 
268
+ # Fires the moment the flow finishes: runs registered after_completion
269
+ # hooks in registration order, then guarantees completion_metadata exists
270
+ # — hooks may attach a rich version; absent that (including when the hook
271
+ # meant to supply it raised), a minimal core-stamped one is used.
272
+ def run_after_completion_hooks
273
+ @after_completion_hooks.each { |hook| invoke_completion_hook(hook) }
274
+ ensure_completion_metadata
275
+ nil
276
+ end
277
+
278
+ # Runs one hook in isolation. A StandardError is recorded rather than
279
+ # propagated so that one misbehaving hook cannot silently cancel the
280
+ # hooks registered after it, nor abort the completion of the flow itself.
281
+ def invoke_completion_hook(hook)
282
+ hook.call(self)
283
+ rescue StandardError => e
284
+ @completion_hook_errors << e
285
+ end
286
+
287
+ def ensure_completion_metadata
288
+ return if @completion_metadata
289
+
290
+ @completion_metadata = CompletionMetadata.new(engine: "inquirex", engine_version: VERSION)
291
+ end
292
+
163
293
  # Auto-skips the current step if its skip_if rule is satisfied.
164
294
  def skip_if_needed
165
295
  return if finished?
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Inquirex
4
+ # Namespace for all Inquirex exception classes.
4
5
  module Errors
5
6
  # Base exception for all Inquirex errors.
6
7
  class Error < StandardError; end
@@ -26,5 +27,9 @@ module Inquirex
26
27
 
27
28
  # Raised when serializing or deserializing a Definition to/from JSON fails.
28
29
  class SerializationError < Error; end
30
+
31
+ # Raised when a post-completion action cannot execute structurally,
32
+ # e.g. send_email is used without the mail gem installed.
33
+ class ActionError < Error; end
29
34
  end
30
35
  end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Inquirex
4
+ # Namespace for flow graph export utilities (Mermaid, ...).
4
5
  module Graph
5
6
  # Exports a flow Definition to Mermaid flowchart syntax for visualization.
6
7
  # Node labels show verb + truncated question/text; edges show condition labels.
7
8
  class MermaidExporter
9
+ # Maximum characters of a node label before it is truncated with "...".
8
10
  MAX_LABEL_LENGTH = 50
9
11
 
10
12
  attr_reader :definition
data/lib/inquirex/node.rb CHANGED
@@ -27,7 +27,9 @@ module Inquirex
27
27
  class Node
28
28
  # Valid DSL verbs and which ones collect input from the user.
29
29
  VERBS = %i[ask say header btw warning confirm].freeze
30
+ # Verbs that collect input from the user.
30
31
  COLLECTING_VERBS = %i[ask confirm].freeze
32
+ # Verbs that only display content and auto-advance.
31
33
  DISPLAY_VERBS = %i[say header btw warning].freeze
32
34
 
33
35
  # Valid data types for :ask steps.
@@ -1,29 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Inquirex
4
+ # Namespace for rule AST nodes used in transitions and visibility conditions.
4
5
  module Rules
5
6
  # Composite rule: logical AND of multiple sub-rules. All must be true.
6
7
  class All < Base
7
8
  attr_reader :rules
8
9
 
10
+ # @param rules [Array<Rules::Base>] sub-rules that must all hold
9
11
  def initialize(*rules)
10
12
  super()
11
13
  @rules = rules.flatten.freeze
12
14
  freeze
13
15
  end
14
16
 
17
+ # True when every sub-rule evaluates true (vacuously true when empty).
18
+ #
19
+ # @param answers [Hash{Symbol => Object}] answer context, step_id => value
20
+ # @return [Boolean]
15
21
  def evaluate(answers)
16
22
  @rules.all? { |rule| rule.evaluate(answers) }
17
23
  end
18
24
 
25
+ # @return [Hash{String => Object}] wire format with nested rule hashes
19
26
  def to_h
20
27
  { "op" => "all", "rules" => @rules.map(&:to_h) }
21
28
  end
22
29
 
30
+ # @return [String] human-readable form, sub-rules joined with AND
23
31
  def to_s
24
32
  "(#{@rules.join(" AND ")})"
25
33
  end
26
34
 
35
+ # Deserializes an All rule, recursively rehydrating its sub-rules.
36
+ #
37
+ # @param hash [Hash] rule hash with string or symbol keys
38
+ # @return [All]
27
39
  def self.from_h(hash)
28
40
  raw_rules = hash["rules"] || hash[:rules] || []
29
41
  new(*raw_rules.map { |r| Base.from_h(r) })
@@ -6,24 +6,35 @@ module Inquirex
6
6
  class Any < Base
7
7
  attr_reader :rules
8
8
 
9
+ # @param rules [Array<Rules::Base>] sub-rules, at least one of which must hold
9
10
  def initialize(*rules)
10
11
  super()
11
12
  @rules = rules.flatten.freeze
12
13
  freeze
13
14
  end
14
15
 
16
+ # True when at least one sub-rule evaluates true (false when empty).
17
+ #
18
+ # @param answers [Hash{Symbol => Object}] answer context, step_id => value
19
+ # @return [Boolean]
15
20
  def evaluate(answers)
16
21
  @rules.any? { |rule| rule.evaluate(answers) }
17
22
  end
18
23
 
24
+ # @return [Hash{String => Object}] wire format with nested rule hashes
19
25
  def to_h
20
26
  { "op" => "any", "rules" => @rules.map(&:to_h) }
21
27
  end
22
28
 
29
+ # @return [String] human-readable form, sub-rules joined with OR
23
30
  def to_s
24
31
  "(#{@rules.join(" OR ")})"
25
32
  end
26
33
 
34
+ # Deserializes an Any rule, recursively rehydrating its sub-rules.
35
+ #
36
+ # @param hash [Hash] rule hash with string or symbol keys
37
+ # @return [Any]
27
38
  def self.from_h(hash)
28
39
  raw_rules = hash["rules"] || hash[:rules] || []
29
40
  new(*raw_rules.map { |r| Base.from_h(r) })
@@ -30,6 +30,12 @@ module Inquirex
30
30
 
31
31
  # Deserializes a rule from a plain Hash (e.g. parsed from JSON).
32
32
  #
33
+ # @example Rehydrate a rule from its wire format
34
+ # rule = Inquirex::Rules::Base.from_h(
35
+ # "op" => "equals", "field" => "filing_status", "value" => "single"
36
+ # )
37
+ # rule.evaluate(filing_status: "single") # => true
38
+ #
33
39
  # @param hash [Hash] rule hash with string or symbol keys
34
40
  # @return [Rules::Base] the deserialized rule
35
41
  # @raise [Inquirex::Errors::SerializationError] on unknown operator
@@ -7,6 +7,8 @@ module Inquirex
7
7
  class Contains < Base
8
8
  attr_reader :field, :value
9
9
 
10
+ # @param field [Symbol, String] step id whose answer is inspected
11
+ # @param value [Object] value the answer must include
10
12
  def initialize(field, value)
11
13
  super()
12
14
  @field = field.to_sym
@@ -14,18 +16,28 @@ module Inquirex
14
16
  freeze
15
17
  end
16
18
 
19
+ # True when the field's answer, coerced to an Array, includes the value.
20
+ #
21
+ # @param answers [Hash{Symbol => Object}] answer context, step_id => value
22
+ # @return [Boolean]
17
23
  def evaluate(answers)
18
24
  Array(answers[@field]).include?(@value)
19
25
  end
20
26
 
27
+ # @return [Hash{String => Object}] wire format, same shape .from_h accepts
21
28
  def to_h
22
29
  { "op" => "contains", "field" => @field.to_s, "value" => @value }
23
30
  end
24
31
 
32
+ # @return [String] human-readable form, e.g. "Business in income_types"
25
33
  def to_s
26
34
  "#{@value} in #{@field}"
27
35
  end
28
36
 
37
+ # Deserializes a Contains rule from a plain Hash.
38
+ #
39
+ # @param hash [Hash] rule hash with string or symbol keys
40
+ # @return [Contains]
29
41
  def self.from_h(hash)
30
42
  field = hash["field"] || hash[:field]
31
43
  value = hash["value"] || hash[:value]
@@ -6,6 +6,8 @@ module Inquirex
6
6
  class Equals < Base
7
7
  attr_reader :field, :value
8
8
 
9
+ # @param field [Symbol, String] step id whose answer is compared
10
+ # @param value [Object] value the answer must equal
9
11
  def initialize(field, value)
10
12
  super()
11
13
  @field = field.to_sym
@@ -13,18 +15,28 @@ module Inquirex
13
15
  freeze
14
16
  end
15
17
 
18
+ # True when the field's answer equals the value (Ruby ==).
19
+ #
20
+ # @param answers [Hash{Symbol => Object}] answer context, step_id => value
21
+ # @return [Boolean]
16
22
  def evaluate(answers)
17
23
  answers[@field] == @value
18
24
  end
19
25
 
26
+ # @return [Hash{String => Object}] wire format, same shape .from_h accepts
20
27
  def to_h
21
28
  { "op" => "equals", "field" => @field.to_s, "value" => @value }
22
29
  end
23
30
 
31
+ # @return [String] human-readable form, e.g. "filing_status == single"
24
32
  def to_s
25
33
  "#{@field} == #{@value}"
26
34
  end
27
35
 
36
+ # Deserializes an Equals rule from a plain Hash.
37
+ #
38
+ # @param hash [Hash] rule hash with string or symbol keys
39
+ # @return [Equals]
28
40
  def self.from_h(hash)
29
41
  field = hash["field"] || hash[:field]
30
42
  value = hash["value"] || hash[:value]
@@ -6,6 +6,8 @@ module Inquirex
6
6
  class GreaterThan < Base
7
7
  attr_reader :field, :value
8
8
 
9
+ # @param field [Symbol, String] step id whose answer is compared
10
+ # @param value [Integer] threshold the answer must exceed
9
11
  def initialize(field, value)
10
12
  super()
11
13
  @field = field.to_sym
@@ -13,18 +15,28 @@ module Inquirex
13
15
  freeze
14
16
  end
15
17
 
18
+ # True when the field's answer, coerced to an Integer, exceeds the threshold.
19
+ #
20
+ # @param answers [Hash{Symbol => Object}] answer context, step_id => value
21
+ # @return [Boolean]
16
22
  def evaluate(answers)
17
23
  answers[@field].to_i > @value
18
24
  end
19
25
 
26
+ # @return [Hash{String => Object}] wire format, same shape .from_h accepts
20
27
  def to_h
21
28
  { "op" => "greater_than", "field" => @field.to_s, "value" => @value }
22
29
  end
23
30
 
31
+ # @return [String] human-readable form, e.g. "dependents > 2"
24
32
  def to_s
25
33
  "#{@field} > #{@value}"
26
34
  end
27
35
 
36
+ # Deserializes a GreaterThan rule from a plain Hash.
37
+ #
38
+ # @param hash [Hash] rule hash with string or symbol keys
39
+ # @return [GreaterThan]
28
40
  def self.from_h(hash)
29
41
  field = hash["field"] || hash[:field]
30
42
  value = (hash["value"] || hash[:value]).to_i
@@ -6,6 +6,8 @@ module Inquirex
6
6
  class LessThan < Base
7
7
  attr_reader :field, :value
8
8
 
9
+ # @param field [Symbol, String] step id whose answer is compared
10
+ # @param value [Integer] threshold the answer must stay below
9
11
  def initialize(field, value)
10
12
  super()
11
13
  @field = field.to_sym
@@ -13,18 +15,28 @@ module Inquirex
13
15
  freeze
14
16
  end
15
17
 
18
+ # True when the field's answer, coerced to an Integer, is below the threshold.
19
+ #
20
+ # @param answers [Hash{Symbol => Object}] answer context, step_id => value
21
+ # @return [Boolean]
16
22
  def evaluate(answers)
17
23
  answers[@field].to_i < @value
18
24
  end
19
25
 
26
+ # @return [Hash{String => Object}] wire format, same shape .from_h accepts
20
27
  def to_h
21
28
  { "op" => "less_than", "field" => @field.to_s, "value" => @value }
22
29
  end
23
30
 
31
+ # @return [String] human-readable form, e.g. "dependents < 2"
24
32
  def to_s
25
33
  "#{@field} < #{@value}"
26
34
  end
27
35
 
36
+ # Deserializes a LessThan rule from a plain Hash.
37
+ #
38
+ # @param hash [Hash] rule hash with string or symbol keys
39
+ # @return [LessThan]
28
40
  def self.from_h(hash)
29
41
  field = hash["field"] || hash[:field]
30
42
  value = (hash["value"] || hash[:value]).to_i
@@ -6,12 +6,18 @@ module Inquirex
6
6
  class NotEmpty < Base
7
7
  attr_reader :field
8
8
 
9
+ # @param field [Symbol, String] step id whose answer is checked for presence
9
10
  def initialize(field)
10
11
  super()
11
12
  @field = field.to_sym
12
13
  freeze
13
14
  end
14
15
 
16
+ # True when the field's answer is neither nil nor empty
17
+ # (empty String, Array, Hash, etc. all evaluate false).
18
+ #
19
+ # @param answers [Hash{Symbol => Object}] answer context, step_id => value
20
+ # @return [Boolean]
15
21
  def evaluate(answers)
16
22
  val = answers[@field]
17
23
  return false if val.nil?
@@ -20,14 +26,20 @@ module Inquirex
20
26
  true
21
27
  end
22
28
 
29
+ # @return [Hash{String => Object}] wire format, same shape .from_h accepts
23
30
  def to_h
24
31
  { "op" => "not_empty", "field" => @field.to_s }
25
32
  end
26
33
 
34
+ # @return [String] human-readable form, e.g. "income_types is not empty"
27
35
  def to_s
28
36
  "#{@field} is not empty"
29
37
  end
30
38
 
39
+ # Deserializes a NotEmpty rule from a plain Hash.
40
+ #
41
+ # @param hash [Hash] rule hash with string or symbol keys
42
+ # @return [NotEmpty]
31
43
  def self.from_h(hash)
32
44
  field = hash["field"] || hash[:field]
33
45
  new(field)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Inquirex
4
+ # Namespace for pluggable step-answer validation adapters.
4
5
  module Validation
5
6
  # Result of validating a step answer.
6
7
  #
@@ -5,6 +5,11 @@ module Inquirex
5
5
  # No-op validator: always accepts any input.
6
6
  # Used by default when no validation adapter is configured.
7
7
  class NullAdapter < Adapter
8
+ # Accepts any input unconditionally.
9
+ #
10
+ # @param _node [Node] the current step (ignored)
11
+ # @param _input [Object] value submitted by the user (ignored)
12
+ # @return [Result] always valid, with no errors
8
13
  def validate(_node, _input)
9
14
  Result.new(valid: true)
10
15
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Inquirex
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.1"
5
5
  end
@@ -10,6 +10,7 @@ module Inquirex
10
10
  # Adapters (TTY, JS, Rails) use this to pick an appropriate renderer when
11
11
  # the DSL author has not specified an explicit `widget` hint.
12
12
  module WidgetRegistry
13
+ # Every widget type an adapter may be asked to render, across all contexts.
13
14
  WIDGET_TYPES = %i[
14
15
  text_input
15
16
  textarea
data/lib/inquirex.rb CHANGED
@@ -24,16 +24,29 @@ require_relative "inquirex/accumulator"
24
24
  require_relative "inquirex/node"
25
25
  require_relative "inquirex/definition"
26
26
  require_relative "inquirex/answers"
27
+ require_relative "inquirex/completion_metadata"
27
28
 
28
29
  # Validation
29
30
  require_relative "inquirex/validation/adapter"
30
31
  require_relative "inquirex/validation/null_adapter"
31
32
 
33
+ # Post-completion actions
34
+ require_relative "inquirex/actions"
35
+ require_relative "inquirex/actions/template"
36
+ require_relative "inquirex/actions/outbox"
37
+ require_relative "inquirex/actions/base"
38
+ require_relative "inquirex/actions/send_email"
39
+ require_relative "inquirex/actions/webhook"
40
+ require_relative "inquirex/actions/custom"
41
+ require_relative "inquirex/actions/action"
42
+ require_relative "inquirex/actions/runner"
43
+
32
44
  # DSL
33
45
  require_relative "inquirex/dsl"
34
46
  require_relative "inquirex/dsl/rule_helpers"
35
47
  require_relative "inquirex/dsl/step_builder"
36
48
  require_relative "inquirex/dsl/flow_builder"
49
+ require_relative "inquirex/dsl/action_builder"
37
50
 
38
51
  # Engine
39
52
  require_relative "inquirex/engine/state_serializer"