inquirex-ui 0.2.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 +7 -0
- data/.relaxed_rubocop.yml +153 -0
- data/.ruby-version +1 -0
- data/.secrets.baseline +127 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +360 -0
- data/Rakefile +36 -0
- data/exe/inquirex-ui +3 -0
- data/justfile +38 -0
- data/lefthook.yml +35 -0
- data/lib/inquirex/ui/dsl/flow_builder.rb +21 -0
- data/lib/inquirex/ui/dsl/step_builder.rb +63 -0
- data/lib/inquirex/ui/node.rb +108 -0
- data/lib/inquirex/ui/version.rb +7 -0
- data/lib/inquirex/ui/widget_hint.rb +37 -0
- data/lib/inquirex/ui/widget_registry.rb +84 -0
- data/lib/inquirex/ui.rb +74 -0
- data/sig/inquirex/ui.rbs +6 -0
- metadata +165 -0
data/Rakefile
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rspec/core/rake_task"
|
|
5
|
+
require "timeout"
|
|
6
|
+
require "yard"
|
|
7
|
+
|
|
8
|
+
def shell(*args)
|
|
9
|
+
puts "running: #{args.join(" ")}"
|
|
10
|
+
system(args.join(" "))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
task :clean do
|
|
14
|
+
shell("rm -rf pkg/ tmp/ coverage/ doc/ ")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
task gem: [:build] do
|
|
18
|
+
shell("gem install pkg/*")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
task permissions: [:clean] do
|
|
22
|
+
shell("chmod -v o+r,g+r * */* */*/* */*/*/* */*/*/*/* */*/*/*/*/*")
|
|
23
|
+
shell("find . -type d -exec chmod o+x,g+x {} \\;")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
task build: :permissions
|
|
27
|
+
|
|
28
|
+
YARD::Rake::YardocTask.new(:doc) do |t|
|
|
29
|
+
t.files = %w[lib/**/*.rb exe/*.rb - README.md LICENSE.txt CHANGELOG.md]
|
|
30
|
+
t.options.unshift("--title", '"FlowEngine — DSL + AST for buildiong complex flows in Ruby."')
|
|
31
|
+
t.after = -> { exec("open doc/index.html") } if RUBY_PLATFORM =~ /darwin/
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
35
|
+
|
|
36
|
+
task default: :spec
|
data/exe/inquirex-ui
ADDED
data/justfile
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
set shell := ["bash", "-lc"]
|
|
2
|
+
|
|
3
|
+
rbenv := 'eval "$(rbenv init -)"'
|
|
4
|
+
|
|
5
|
+
[no-exit-message]
|
|
6
|
+
recipes:
|
|
7
|
+
@just --choose
|
|
8
|
+
|
|
9
|
+
# Sync all dependencies
|
|
10
|
+
install:
|
|
11
|
+
{{rbenv}} && bin/setup
|
|
12
|
+
|
|
13
|
+
# Lint and reformat files
|
|
14
|
+
lint-fix *args:
|
|
15
|
+
{{rbenv}} && bundle exec rubocop -a
|
|
16
|
+
|
|
17
|
+
alias format := lint-fix
|
|
18
|
+
|
|
19
|
+
# Lint and reformat files
|
|
20
|
+
lint:
|
|
21
|
+
{{rbenv}} && bundle exec rubocop
|
|
22
|
+
|
|
23
|
+
# Run all the tests
|
|
24
|
+
test *args:
|
|
25
|
+
{{rbenv}} && ENVIRONMENT=test bundle exec rspec {{args}}
|
|
26
|
+
|
|
27
|
+
# Run tests with coverage
|
|
28
|
+
test-coverage *args:
|
|
29
|
+
ENVIRONMENT=test COVERAGE=true bundle exec rspec
|
|
30
|
+
|
|
31
|
+
clean:
|
|
32
|
+
#!/usr/bin/env bash
|
|
33
|
+
find . -name .DS_Store -delete -print || true
|
|
34
|
+
rm -rf tmp/*
|
|
35
|
+
|
|
36
|
+
# Run all lefthook pre-commit hooks
|
|
37
|
+
ci:
|
|
38
|
+
{{rbenv}} && lefthook run pre-commit --all-files
|
data/lefthook.yml
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
output:
|
|
2
|
+
- summary
|
|
3
|
+
- failure
|
|
4
|
+
|
|
5
|
+
pre-commit:
|
|
6
|
+
parallel: true
|
|
7
|
+
jobs:
|
|
8
|
+
- name: lint
|
|
9
|
+
run: bundle exec rubocop -c .rubocop.yml {staged_files}
|
|
10
|
+
glob: "*.{rb,Gemfile}"
|
|
11
|
+
stage_fixed: true
|
|
12
|
+
|
|
13
|
+
- name: check for conflict markers and whitespace issues
|
|
14
|
+
run: git --no-pager diff --check
|
|
15
|
+
|
|
16
|
+
# If tests take >1 second, move this (or just the long-running tests) to pre-push.
|
|
17
|
+
- name: run tests
|
|
18
|
+
run: just test
|
|
19
|
+
|
|
20
|
+
- name: fix rubocop formatting issues
|
|
21
|
+
run: bundle exec rubocop -a {staged_files}
|
|
22
|
+
glob: "*.{rb,Gemfile,gemspec}"
|
|
23
|
+
stage_fixed: true
|
|
24
|
+
|
|
25
|
+
- name: spell check
|
|
26
|
+
run: codespell {staged_files}
|
|
27
|
+
glob: "*.{rb,md,gemspec}"
|
|
28
|
+
|
|
29
|
+
- name: format markdown
|
|
30
|
+
run: mdformat {staged_files}
|
|
31
|
+
glob: "*.md"
|
|
32
|
+
stage_fixed: true
|
|
33
|
+
|
|
34
|
+
- name: scan for secrets
|
|
35
|
+
run: detect-secrets-hook --baseline .secrets.baseline
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Inquirex
|
|
4
|
+
module UI
|
|
5
|
+
module DSL
|
|
6
|
+
# Extends Inquirex::DSL::FlowBuilder to use UI::DSL::StepBuilder for all steps,
|
|
7
|
+
# ensuring every node produced by Inquirex::UI.define carries widget hints.
|
|
8
|
+
class FlowBuilder < Inquirex::DSL::FlowBuilder
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
# Overrides the parent's add_step to instantiate UI::DSL::StepBuilder
|
|
12
|
+
# instead of the base StepBuilder, so widget hints are available in blocks.
|
|
13
|
+
def add_step(id, verb, &block)
|
|
14
|
+
builder = UI::DSL::StepBuilder.new(verb)
|
|
15
|
+
builder.instance_eval(&block) if block
|
|
16
|
+
@nodes[id.to_sym] = builder.build(id)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Inquirex
|
|
4
|
+
module UI
|
|
5
|
+
module DSL
|
|
6
|
+
# Extends Inquirex::DSL::StepBuilder with widget rendering hint methods.
|
|
7
|
+
# Used by FlowBuilder whenever a step block is evaluated inside Inquirex::UI.define.
|
|
8
|
+
#
|
|
9
|
+
# Additional DSL methods:
|
|
10
|
+
# widget :radio_group, columns: 2 # desktop/default hint
|
|
11
|
+
# widget_mobile :dropdown # mobile-specific hint
|
|
12
|
+
class StepBuilder < Inquirex::DSL::StepBuilder
|
|
13
|
+
def initialize(verb)
|
|
14
|
+
super
|
|
15
|
+
@widget_hints = {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Sets a rendering hint for the given target context.
|
|
19
|
+
# Call once per target. Recognized targets: :desktop, :mobile (and any
|
|
20
|
+
# future targets adapters may define).
|
|
21
|
+
#
|
|
22
|
+
# @param target [Symbol] rendering context (default: :desktop)
|
|
23
|
+
# @param type [Symbol] widget type (see WidgetRegistry::WIDGET_TYPES)
|
|
24
|
+
# @param opts [Hash] widget-specific options (e.g. columns: 2)
|
|
25
|
+
#
|
|
26
|
+
# @example
|
|
27
|
+
# widget target: :desktop, type: :radio_group, columns: 2
|
|
28
|
+
# widget target: :mobile, type: :dropdown
|
|
29
|
+
def widget(type:, target: :desktop, **opts)
|
|
30
|
+
@widget_hints[target.to_sym] = WidgetHint.new(type:, options: opts)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Builds a UI::Node with widget hints (explicit or registry defaults).
|
|
34
|
+
#
|
|
35
|
+
# @param id [Symbol]
|
|
36
|
+
# @return [UI::Node]
|
|
37
|
+
def build(id)
|
|
38
|
+
effective_type = resolve_type
|
|
39
|
+
hints = @widget_hints.dup
|
|
40
|
+
|
|
41
|
+
# Fill in registry defaults for any targets not explicitly set.
|
|
42
|
+
%i[desktop mobile].each do |target|
|
|
43
|
+
hints[target] ||= WidgetRegistry.default_hint_for(effective_type, context: target)
|
|
44
|
+
end
|
|
45
|
+
hints.compact!
|
|
46
|
+
|
|
47
|
+
UI::Node.new(
|
|
48
|
+
id:,
|
|
49
|
+
verb: @verb,
|
|
50
|
+
type: effective_type,
|
|
51
|
+
question: @question,
|
|
52
|
+
text: @text,
|
|
53
|
+
options: @options,
|
|
54
|
+
transitions: @transitions,
|
|
55
|
+
skip_if: @skip_if,
|
|
56
|
+
default: @default,
|
|
57
|
+
widget_hints: hints.empty? ? nil : hints
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Inquirex
|
|
4
|
+
module UI
|
|
5
|
+
# Enriched node that extends Inquirex::Node with widget rendering hints.
|
|
6
|
+
# Hints are stored as a Hash keyed by target context (e.g. :desktop, :mobile).
|
|
7
|
+
# Additional targets can be introduced by adapters without changing this class.
|
|
8
|
+
#
|
|
9
|
+
# The hints are included in JSON serialization and consumed by frontend adapters
|
|
10
|
+
# (JS widget, TTY renderer, etc.). This class does NOT render anything itself.
|
|
11
|
+
#
|
|
12
|
+
# @attr_reader widget_hints [Hash{Symbol => WidgetHint}, nil]
|
|
13
|
+
# Map of target → hint. nil for display nodes (say/header/btw/warning).
|
|
14
|
+
class Node < Inquirex::Node
|
|
15
|
+
attr_reader :widget_hints
|
|
16
|
+
|
|
17
|
+
# @param widget_hints [Hash{Symbol => WidgetHint}, nil]
|
|
18
|
+
# @param kwargs [Hash] all other params forwarded to Inquirex::Node
|
|
19
|
+
def initialize(widget_hints: nil, **)
|
|
20
|
+
# Set widget attr BEFORE calling super — super calls freeze.
|
|
21
|
+
@widget_hints = widget_hints&.freeze
|
|
22
|
+
super(**)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Returns the explicit hint for the given target, or nil when not set.
|
|
26
|
+
#
|
|
27
|
+
# @param target [Symbol] e.g. :desktop, :mobile
|
|
28
|
+
# @return [WidgetHint, nil]
|
|
29
|
+
def widget_hint_for(target: :desktop)
|
|
30
|
+
widget_hints&.fetch(target.to_sym, nil)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Returns the explicit hint for the given target, falling back to the
|
|
34
|
+
# registry default for this node's type when no explicit hint is set.
|
|
35
|
+
#
|
|
36
|
+
# @param target [Symbol] e.g. :desktop, :mobile
|
|
37
|
+
# @return [WidgetHint, nil]
|
|
38
|
+
def effective_widget_hint_for(target: :desktop)
|
|
39
|
+
widget_hint_for(target:) || WidgetRegistry.default_hint_for(type, context: target)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Serializes to a plain Hash. When widget_hints are present, they are nested
|
|
43
|
+
# under the "widget" key as { "desktop" => {...}, "mobile" => {...} }.
|
|
44
|
+
#
|
|
45
|
+
# @return [Hash]
|
|
46
|
+
def to_h
|
|
47
|
+
hash = super
|
|
48
|
+
if widget_hints && !widget_hints.empty?
|
|
49
|
+
hash["widget"] = widget_hints.transform_keys(&:to_s)
|
|
50
|
+
.transform_values(&:to_h)
|
|
51
|
+
end
|
|
52
|
+
hash
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Deserializes from a plain Hash (string or symbol keys).
|
|
56
|
+
# Parses the nested "widget" target map in addition to all base Node fields.
|
|
57
|
+
#
|
|
58
|
+
# @param id [Symbol, String]
|
|
59
|
+
# @param hash [Hash]
|
|
60
|
+
# @return [UI::Node]
|
|
61
|
+
def self.from_h(id, hash)
|
|
62
|
+
widget_data = hash["widget"] || hash[:widget]
|
|
63
|
+
|
|
64
|
+
widget_hints =
|
|
65
|
+
if widget_data.is_a?(Hash) && widget_data.any? { |_, v| v.is_a?(Hash) }
|
|
66
|
+
# New format: { "desktop" => { "type" => "...", ... }, ... }
|
|
67
|
+
widget_data.each_with_object({}) do |(target, hint_hash), acc|
|
|
68
|
+
acc[target.to_sym] = WidgetHint.from_h(hint_hash)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
verb = hash["verb"] || hash[:verb]
|
|
73
|
+
type = hash["type"] || hash[:type]
|
|
74
|
+
question = hash["question"] || hash[:question]
|
|
75
|
+
text = hash["text"] || hash[:text]
|
|
76
|
+
raw_options = hash["options"] || hash[:options]
|
|
77
|
+
transitions_data = hash["transitions"] || hash[:transitions] || []
|
|
78
|
+
skip_if_data = hash["skip_if"] || hash[:skip_if]
|
|
79
|
+
default = hash["default"] || hash[:default]
|
|
80
|
+
|
|
81
|
+
transitions = transitions_data.map { |t| Inquirex::Transition.from_h(t) }
|
|
82
|
+
skip_if = skip_if_data ? Inquirex::Rules::Base.from_h(skip_if_data) : nil
|
|
83
|
+
options = deserialize_options(raw_options)
|
|
84
|
+
|
|
85
|
+
new(
|
|
86
|
+
id:,
|
|
87
|
+
verb:,
|
|
88
|
+
type:,
|
|
89
|
+
question:,
|
|
90
|
+
text:,
|
|
91
|
+
options:,
|
|
92
|
+
transitions:,
|
|
93
|
+
skip_if:,
|
|
94
|
+
default:,
|
|
95
|
+
widget_hints:
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private_class_method def self.deserialize_options(raw)
|
|
100
|
+
return nil if raw.nil?
|
|
101
|
+
return raw unless raw.is_a?(Array)
|
|
102
|
+
return raw if raw.empty?
|
|
103
|
+
|
|
104
|
+
raw.to_h { |o| [(o["value"] || o[:value]).to_s, (o["label"] || o[:label]).to_s] }
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Inquirex
|
|
4
|
+
module UI
|
|
5
|
+
# Immutable rendering hint attached to a step node.
|
|
6
|
+
# Carries a widget type (e.g. :radio_group) and an optional options hash
|
|
7
|
+
# (e.g. { columns: 2 }). Framework-agnostic — consumed by the JS widget,
|
|
8
|
+
# TTY adapter, or any other frontend renderer.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# hint = WidgetHint.new(type: :radio_group, options: { columns: 2 })
|
|
12
|
+
# hint.to_h # => { "type" => "radio_group", "columns" => 2 }
|
|
13
|
+
WidgetHint = Data.define(:type, :options) do
|
|
14
|
+
def initialize(type:, options: {})
|
|
15
|
+
super(type: type.to_sym, options: options.transform_keys(&:to_sym).freeze)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Serializes to a flat hash: type key + option keys merged in.
|
|
19
|
+
#
|
|
20
|
+
# @return [Hash<String, Object>]
|
|
21
|
+
def to_h
|
|
22
|
+
{ "type" => type.to_s }.merge(options.transform_keys(&:to_s))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Deserializes from a plain hash (string or symbol keys).
|
|
26
|
+
#
|
|
27
|
+
# @param hash [Hash]
|
|
28
|
+
# @return [WidgetHint]
|
|
29
|
+
def self.from_h(hash)
|
|
30
|
+
type = hash["type"] || hash[:type]
|
|
31
|
+
options = hash.reject { |k, _| k.to_s == "type" }
|
|
32
|
+
.transform_keys(&:to_sym)
|
|
33
|
+
new(type:, options:)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Inquirex
|
|
4
|
+
module UI
|
|
5
|
+
# Canonical mapping from Inquirex data types to default widget types per rendering context.
|
|
6
|
+
#
|
|
7
|
+
# Desktop defaults lean toward richer controls (radio groups, checkbox groups).
|
|
8
|
+
# Mobile defaults lean toward compact controls (dropdowns, toggles).
|
|
9
|
+
#
|
|
10
|
+
# Adapters (TTY, JS, Rails) use this to pick an appropriate renderer when
|
|
11
|
+
# the DSL author has not specified an explicit `widget` hint.
|
|
12
|
+
module WidgetRegistry
|
|
13
|
+
# All recognized widget type symbols. Adapters may not support every widget;
|
|
14
|
+
# they should fall back gracefully to a simpler one.
|
|
15
|
+
WIDGET_TYPES = %i[
|
|
16
|
+
text_input
|
|
17
|
+
textarea
|
|
18
|
+
number_input
|
|
19
|
+
currency_input
|
|
20
|
+
toggle
|
|
21
|
+
yes_no_buttons
|
|
22
|
+
yes_no
|
|
23
|
+
radio_group
|
|
24
|
+
dropdown
|
|
25
|
+
checkbox_group
|
|
26
|
+
multi_select_dropdown
|
|
27
|
+
select
|
|
28
|
+
multi_select
|
|
29
|
+
enum_select
|
|
30
|
+
multiline
|
|
31
|
+
mask
|
|
32
|
+
slider
|
|
33
|
+
date_picker
|
|
34
|
+
email_input
|
|
35
|
+
phone_input
|
|
36
|
+
].freeze
|
|
37
|
+
|
|
38
|
+
# Default widget per data type and rendering context.
|
|
39
|
+
# Each context key (:desktop, :mobile, :tty, …) maps to a widget type symbol.
|
|
40
|
+
# Adapters consult this table when no explicit `widget` hint was given.
|
|
41
|
+
#
|
|
42
|
+
# TTY defaults align with tty-prompt methods:
|
|
43
|
+
# text_input → prompt.ask
|
|
44
|
+
# multiline → prompt.multiline
|
|
45
|
+
# number_input → prompt.ask (with numeric conversion)
|
|
46
|
+
# yes_no → prompt.yes?
|
|
47
|
+
# select → prompt.select
|
|
48
|
+
# multi_select → prompt.multi_select
|
|
49
|
+
DEFAULTS = {
|
|
50
|
+
string: { desktop: :text_input, mobile: :text_input, tty: :text_input },
|
|
51
|
+
text: { desktop: :textarea, mobile: :textarea, tty: :multiline },
|
|
52
|
+
integer: { desktop: :number_input, mobile: :number_input, tty: :number_input },
|
|
53
|
+
decimal: { desktop: :number_input, mobile: :number_input, tty: :number_input },
|
|
54
|
+
currency: { desktop: :currency_input, mobile: :currency_input, tty: :number_input },
|
|
55
|
+
boolean: { desktop: :toggle, mobile: :yes_no_buttons, tty: :yes_no },
|
|
56
|
+
enum: { desktop: :radio_group, mobile: :dropdown, tty: :select },
|
|
57
|
+
multi_enum: { desktop: :checkbox_group, mobile: :checkbox_group, tty: :multi_select },
|
|
58
|
+
date: { desktop: :date_picker, mobile: :date_picker, tty: :text_input },
|
|
59
|
+
email: { desktop: :email_input, mobile: :email_input, tty: :text_input },
|
|
60
|
+
phone: { desktop: :phone_input, mobile: :phone_input, tty: :text_input }
|
|
61
|
+
}.freeze
|
|
62
|
+
|
|
63
|
+
# Returns the default widget type for a given data type and context.
|
|
64
|
+
#
|
|
65
|
+
# @param type [Symbol, String, nil] Inquirex data type (e.g. :enum, :integer)
|
|
66
|
+
# @param context [Symbol] :desktop or :mobile (default: :desktop)
|
|
67
|
+
# @return [Symbol, nil] widget type symbol, or nil if type is unknown
|
|
68
|
+
def self.default_for(type, context: :desktop)
|
|
69
|
+
DEFAULTS.dig(type&.to_sym, context)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Returns a WidgetHint with the default widget for the given type + context,
|
|
73
|
+
# or nil when no default exists (e.g. for display verbs with no type).
|
|
74
|
+
#
|
|
75
|
+
# @param type [Symbol, String, nil]
|
|
76
|
+
# @param context [Symbol]
|
|
77
|
+
# @return [WidgetHint, nil]
|
|
78
|
+
def self.default_hint_for(type, context: :desktop)
|
|
79
|
+
widget_type = default_for(type, context:)
|
|
80
|
+
widget_type ? WidgetHint.new(type: widget_type) : nil
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
data/lib/inquirex/ui.rb
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "inquirex"
|
|
4
|
+
require_relative "ui/version"
|
|
5
|
+
require_relative "ui/widget_hint"
|
|
6
|
+
require_relative "ui/widget_registry"
|
|
7
|
+
require_relative "ui/node"
|
|
8
|
+
require_relative "ui/dsl/step_builder"
|
|
9
|
+
require_relative "ui/dsl/flow_builder"
|
|
10
|
+
|
|
11
|
+
module Inquirex
|
|
12
|
+
# UI enrichment layer for Inquirex flows.
|
|
13
|
+
#
|
|
14
|
+
# Extends the core DSL with `widget` and `widget_mobile` rendering hints.
|
|
15
|
+
# Every node built via Inquirex::UI.define carries a WidgetHint describing
|
|
16
|
+
# the preferred UI control for that step's data type.
|
|
17
|
+
#
|
|
18
|
+
# Widget hints are:
|
|
19
|
+
# - Framework-agnostic (no HTML/JS generated here)
|
|
20
|
+
# - Included in JSON serialization
|
|
21
|
+
# - Consumed by adapters: inquirex-tty, inquirex-js, inquirex-rails
|
|
22
|
+
#
|
|
23
|
+
# When no explicit hint is provided, WidgetRegistry supplies a sensible default
|
|
24
|
+
# for each data type × rendering context (desktop / mobile).
|
|
25
|
+
#
|
|
26
|
+
# @example
|
|
27
|
+
# definition = Inquirex::UI.define id: "tax-2025" do
|
|
28
|
+
# start :filing_status
|
|
29
|
+
#
|
|
30
|
+
# ask :filing_status do
|
|
31
|
+
# type :enum
|
|
32
|
+
# question "What is your filing status?"
|
|
33
|
+
# options single: "Single", married_jointly: "Married Filing Jointly"
|
|
34
|
+
# widget target: :desktop, type: :radio_group, columns: 2
|
|
35
|
+
# widget target: :mobile, type: :dropdown
|
|
36
|
+
# transition to: :next_step
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# ask :income do
|
|
40
|
+
# type :currency
|
|
41
|
+
# question "Estimated annual income?"
|
|
42
|
+
# # No widget call — WidgetRegistry provides :currency_input by default
|
|
43
|
+
# transition to: :done
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# say :done do
|
|
47
|
+
# text "All done."
|
|
48
|
+
# end
|
|
49
|
+
# end
|
|
50
|
+
#
|
|
51
|
+
# definition.step(:filing_status).widget_hint_for(target: :desktop)
|
|
52
|
+
# # => #<data Inquirex::UI::WidgetHint type=:radio_group, options={columns: 2}>
|
|
53
|
+
#
|
|
54
|
+
# definition.step(:income).effective_widget_hint_for(target: :desktop)
|
|
55
|
+
# # => #<data Inquirex::UI::WidgetHint type=:currency_input, options={}>
|
|
56
|
+
#
|
|
57
|
+
# definition.to_json
|
|
58
|
+
# # => '{"start":"filing_status","steps":{"filing_status":{...,"widget":{"type":"radio_group","columns":2},...}}}'
|
|
59
|
+
#
|
|
60
|
+
module UI
|
|
61
|
+
# Builds an immutable Definition where every node is a UI::Node with widget hints.
|
|
62
|
+
# Accepts the same arguments as Inquirex.define.
|
|
63
|
+
#
|
|
64
|
+
# @param id [String, nil] optional flow identifier
|
|
65
|
+
# @param version [String] semver (default: "1.0.0")
|
|
66
|
+
# @yield context of UI::DSL::FlowBuilder
|
|
67
|
+
# @return [Inquirex::Definition]
|
|
68
|
+
def self.define(id: nil, version: "1.0.0", &)
|
|
69
|
+
builder = UI::DSL::FlowBuilder.new(id:, version:)
|
|
70
|
+
builder.instance_eval(&)
|
|
71
|
+
builder.build
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
data/sig/inquirex/ui.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: inquirex-ui
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Konstantin Gredeskoul
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: inquirex
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.1.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.1.0
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rspec
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rspec-its
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '2.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '2.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rubocop
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '1.21'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '1.21'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rubocop-rspec
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: simplecov
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0.22'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0.22'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: yard
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0.9'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0.9'
|
|
110
|
+
description: Enriches Inquirex flow definitions with framework-agnostic widget rendering
|
|
111
|
+
metadata. Adds `widget` and `widget_mobile` DSL verbs that attach WidgetHint objects
|
|
112
|
+
to nodes. Consumed by inquirex-tty, inquirex-js, and inquirex-rails adapters. Produces
|
|
113
|
+
enriched JSON — no HTML or JavaScript generated.
|
|
114
|
+
email:
|
|
115
|
+
- kigster@gmail.com
|
|
116
|
+
executables:
|
|
117
|
+
- inquirex-ui
|
|
118
|
+
extensions: []
|
|
119
|
+
extra_rdoc_files: []
|
|
120
|
+
files:
|
|
121
|
+
- ".relaxed_rubocop.yml"
|
|
122
|
+
- ".ruby-version"
|
|
123
|
+
- ".secrets.baseline"
|
|
124
|
+
- CHANGELOG.md
|
|
125
|
+
- LICENSE.txt
|
|
126
|
+
- README.md
|
|
127
|
+
- Rakefile
|
|
128
|
+
- exe/inquirex-ui
|
|
129
|
+
- justfile
|
|
130
|
+
- lefthook.yml
|
|
131
|
+
- lib/inquirex/ui.rb
|
|
132
|
+
- lib/inquirex/ui/dsl/flow_builder.rb
|
|
133
|
+
- lib/inquirex/ui/dsl/step_builder.rb
|
|
134
|
+
- lib/inquirex/ui/node.rb
|
|
135
|
+
- lib/inquirex/ui/version.rb
|
|
136
|
+
- lib/inquirex/ui/widget_hint.rb
|
|
137
|
+
- lib/inquirex/ui/widget_registry.rb
|
|
138
|
+
- sig/inquirex/ui.rbs
|
|
139
|
+
homepage: https://github.com/inquirex/inquirex-ui
|
|
140
|
+
licenses:
|
|
141
|
+
- MIT
|
|
142
|
+
metadata:
|
|
143
|
+
allowed_push_host: https://rubygems.org
|
|
144
|
+
homepage_uri: https://github.com/inquirex/inquirex-ui
|
|
145
|
+
source_code_uri: https://github.com/inquirex/inquirex-ui
|
|
146
|
+
changelog_uri: https://github.com/inquirex/inquirex-ui/blob/main/CHANGELOG.md
|
|
147
|
+
rubygems_mfa_required: 'true'
|
|
148
|
+
rdoc_options: []
|
|
149
|
+
require_paths:
|
|
150
|
+
- lib
|
|
151
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
152
|
+
requirements:
|
|
153
|
+
- - ">="
|
|
154
|
+
- !ruby/object:Gem::Version
|
|
155
|
+
version: 4.0.0
|
|
156
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
157
|
+
requirements:
|
|
158
|
+
- - ">="
|
|
159
|
+
- !ruby/object:Gem::Version
|
|
160
|
+
version: '0'
|
|
161
|
+
requirements: []
|
|
162
|
+
rubygems_version: 4.0.10
|
|
163
|
+
specification_version: 4
|
|
164
|
+
summary: Widget rendering hints for Inquirex flow definitions
|
|
165
|
+
test_files: []
|