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 =

Verbs that collect input from the user.

%i[ask confirm].freeze
DISPLAY_VERBS =

Verbs that only display content and auto-advance.

%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.



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

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.



41
42
43
# File 'lib/inquirex/node.rb', line 41

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:



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
193
194
# File 'lib/inquirex/node.rb', line 163

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



80
81
82
# File 'lib/inquirex/node.rb', line 80

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)



85
86
87
# File 'lib/inquirex/node.rb', line 85

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:



102
103
104
# File 'lib/inquirex/node.rb', line 102

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)



110
111
112
113
# File 'lib/inquirex/node.rb', line 110

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)


119
120
121
122
123
# File 'lib/inquirex/node.rb', line 119

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)


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
155
156
# File 'lib/inquirex/node.rb', line 129

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:



93
94
95
# File 'lib/inquirex/node.rb', line 93

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