Class: Inquirex::Node

Inherits:
Object
  • Object
show all
Defined in:
lib/inquirex/node.rb

Overview

A single step in the flow. The verb determines the role of this node:

Collecting verbs (user provides input):

:ask     — typed question with one of the 11 data types
:confirm — yes/no boolean gate (shorthand for ask with type :boolean)

Display verbs (no input, auto-advance):

:say     — informational message
:header  — section heading or title card
:btw     — admonition, sidebar, or contextual note
:warning — important alert

Constant Summary collapse

VERBS =

Valid DSL verbs and which ones collect input from the user.

%i[ask say header btw warning confirm].freeze
COLLECTING_VERBS =
%i[ask confirm].freeze
DISPLAY_VERBS =
%i[say header btw warning].freeze
TYPES =

Valid data types for :ask steps.

%i[
  string text integer decimal currency boolean
  enum multi_enum date email phone
].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(id:, verb:, type: nil, question: nil, text: nil, options: nil, transitions: [], skip_if: nil, default: nil, widget_hints: nil, accumulations: []) ⇒ Node

Returns a new instance of Node.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/inquirex/node.rb', line 52

def initialize(id:,
  verb:,
  type: nil,
  question: nil,
  text: nil,
  options: nil,
  transitions: [],
  skip_if: nil,
  default: nil,
  widget_hints: nil,
  accumulations: [])
  @id = id.to_sym
  @verb = verb.to_sym
  @type = type&.to_sym
  @question = question
  @text = text
  @transitions = transitions.freeze
  @skip_if = skip_if
  @default = default
  @widget_hints = widget_hints&.freeze
  @accumulations = accumulations.freeze
  extract_options(options)
  freeze
end

Instance Attribute Details

#accumulationsObject (readonly)

Returns the value of attribute accumulations.



39
40
41
# File 'lib/inquirex/node.rb', line 39

def accumulations
  @accumulations
end

#defaultObject? (readonly)

default value (pre-fill, user can change)

Returns:

  • (Object, nil)

    the current value of default



27
28
29
# File 'lib/inquirex/node.rb', line 27

def default
  @default
end

#idSymbol (readonly)

unique step identifier

Returns:

  • (Symbol)

    the current value of id



27
28
29
# File 'lib/inquirex/node.rb', line 27

def id
  @id
end

#option_labelsHash? (readonly)

key => display label mapping

Returns:

  • (Hash, nil)

    the current value of option_labels



27
28
29
# File 'lib/inquirex/node.rb', line 27

def option_labels
  @option_labels
end

#optionsArray? (readonly)

option keys for :enum/:multi_enum steps

Returns:

  • (Array, nil)

    the current value of options



27
28
29
# File 'lib/inquirex/node.rb', line 27

def options
  @options
end

#questionString? (readonly)

prompt text for collecting steps

Returns:

  • (String, nil)

    the current value of question



27
28
29
# File 'lib/inquirex/node.rb', line 27

def question
  @question
end

#skip_ifRules::Base? (readonly)

rule to skip this step entirely

Returns:



27
28
29
# File 'lib/inquirex/node.rb', line 27

def skip_if
  @skip_if
end

#textString? (readonly)

display text for non-collecting steps

Returns:

  • (String, nil)

    the current value of text



27
28
29
# File 'lib/inquirex/node.rb', line 27

def text
  @text
end

#transitionsArray<Transition> (readonly)

ordered conditional next-step edges

Returns:

  • (Array<Transition>)

    the current value of transitions



27
28
29
# File 'lib/inquirex/node.rb', line 27

def transitions
  @transitions
end

#typeSymbol? (readonly)

input type for :ask/:confirm (nil for display verbs)

Returns:

  • (Symbol, nil)

    the current value of type



27
28
29
# File 'lib/inquirex/node.rb', line 27

def type
  @type
end

#verbSymbol (readonly)

DSL verb (:ask, :say, :header, :btw, :warning, :confirm)

Returns:

  • (Symbol)

    the current value of verb



27
28
29
# File 'lib/inquirex/node.rb', line 27

def verb
  @verb
end

#widget_hintsHash{Symbol => WidgetHint}? (readonly)

rendering hints per target

Returns:

  • (Hash{Symbol => WidgetHint}, nil)

    the current value of widget_hints



27
28
29
# File 'lib/inquirex/node.rb', line 27

def widget_hints
  @widget_hints
end

Class Method Details

.from_h(id, hash) ⇒ Node

Deserializes a node from a plain Hash.

Parameters:

  • id (Symbol, String)

    step id

  • hash (Hash)

    node attributes

Returns:



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/inquirex/node.rb', line 161

def self.from_h(id, hash)
  verb = hash["verb"] || hash[:verb]
  type = hash["type"] || hash[:type]
  question = hash["question"] || hash[:question]
  text = hash["text"] || hash[:text]
  raw_options = hash["options"] || hash[:options]
  transitions_data = hash["transitions"] || hash[:transitions] || []
  skip_if_data = hash["skip_if"] || hash[:skip_if]
  default = hash["default"] || hash[:default]
  widget_data = hash["widget"] || hash[:widget]
  accumulate_data = hash["accumulate"] || hash[:accumulate]

  transitions = transitions_data.map { |t| Transition.from_h(t) }
  skip_if = skip_if_data ? Rules::Base.from_h(skip_if_data) : nil
  options = deserialize_options(raw_options)
  widget_hints = deserialize_widget_hints(widget_data)
  accumulations = deserialize_accumulations(accumulate_data)

  new(
    id:,
    verb:,
    type:,
    question:,
    text:,
    options:,
    transitions:,
    skip_if:,
    default:,
    widget_hints:,
    accumulations:
  )
end

Instance Method Details

#collecting?Boolean

Returns true if this step collects input from the user.

Returns:

  • (Boolean)

    true if this step collects input from the user



78
79
80
# File 'lib/inquirex/node.rb', line 78

def collecting?
  COLLECTING_VERBS.include?(@verb)
end

#display?Boolean

Returns true if this step only displays content (no input).

Returns:

  • (Boolean)

    true if this step only displays content (no input)



83
84
85
# File 'lib/inquirex/node.rb', line 83

def display?
  DISPLAY_VERBS.include?(@verb)
end

#effective_widget_hint_for(target: :desktop) ⇒ WidgetHint?

Returns the explicit hint for the target, falling back to the registry default for this node’s type when no explicit hint is set.

Parameters:

  • target (Symbol) (defaults to: :desktop)

    e.g. :desktop, :mobile, :tty

Returns:



100
101
102
# File 'lib/inquirex/node.rb', line 100

def effective_widget_hint_for(target: :desktop)
  widget_hint_for(target:) || WidgetRegistry.default_hint_for(@type, context: target)
end

#next_step_id(answers) ⇒ Symbol?

Resolves the next step id from current answers by evaluating transitions in order.

Parameters:

  • answers (Hash)

    current answer state

Returns:

  • (Symbol, nil)

    id of the next step, or nil if no transition matches (flow end)



108
109
110
111
# File 'lib/inquirex/node.rb', line 108

def next_step_id(answers)
  match = @transitions.find { |t| t.applies?(answers) }
  match&.target
end

#skip?(answers) ⇒ Boolean

Whether this step should be skipped given current answers.

Parameters:

  • answers (Hash)

Returns:

  • (Boolean)


117
118
119
120
121
# File 'lib/inquirex/node.rb', line 117

def skip?(answers)
  return false if @skip_if.nil?

  @skip_if.evaluate(answers)
end

#to_hHash

Serializes the node to a plain Hash for JSON output. Lambda defaults/compute are stripped (server-side only).

Returns:

  • (Hash)


127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/inquirex/node.rb', line 127

def to_h
  hash = { "verb" => @verb.to_s }

  if collecting?
    hash["type"] = @type.to_s if @type
    hash["question"] = @question if @question
    hash["options"] = serialize_options if @options
    hash["skip_if"] = @skip_if.to_h if @skip_if
    hash["default"] = @default unless @default.nil? || @default.is_a?(Proc)
  elsif @text
    hash["text"] = @text
  end

  hash["transitions"] = @transitions.map(&:to_h) unless @transitions.empty?

  if @widget_hints && !@widget_hints.empty?
    hash["widget"] = @widget_hints.transform_keys(&:to_s)
                                  .transform_values(&:to_h)
  end

  unless @accumulations.empty?
    hash["accumulate"] = @accumulations.to_h do |acc|
      [acc.target.to_s, acc.to_h]
    end
  end

  hash
end

#widget_hint_for(target: :desktop) ⇒ WidgetHint?

Returns the explicit widget hint for the given target, or nil.

Parameters:

  • target (Symbol) (defaults to: :desktop)

    e.g. :desktop, :mobile, :tty

Returns:



91
92
93
# File 'lib/inquirex/node.rb', line 91

def widget_hint_for(target: :desktop)
  @widget_hints&.fetch(target.to_sym, nil)
end