inquirex 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/.rub +0 -0
- data/.ruby-version +1 -0
- data/.secrets.baseline +127 -0
- data/Brewfile +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +289 -0
- data/Rakefile +36 -0
- data/exe/inquirex +3 -0
- data/justfile +38 -0
- data/lefthook.yml +35 -0
- data/lib/inquirex/answers.rb +133 -0
- data/lib/inquirex/definition.rb +109 -0
- data/lib/inquirex/dsl/flow_builder.rb +107 -0
- data/lib/inquirex/dsl/rule_helpers.rb +57 -0
- data/lib/inquirex/dsl/step_builder.rb +109 -0
- data/lib/inquirex/dsl.rb +9 -0
- data/lib/inquirex/engine/state_serializer.rb +36 -0
- data/lib/inquirex/engine.rb +128 -0
- data/lib/inquirex/errors.rb +30 -0
- data/lib/inquirex/evaluator.rb +26 -0
- data/lib/inquirex/graph/mermaid_exporter.rb +55 -0
- data/lib/inquirex/node.rb +192 -0
- data/lib/inquirex/rules/all.rb +33 -0
- data/lib/inquirex/rules/any.rb +33 -0
- data/lib/inquirex/rules/base.rb +52 -0
- data/lib/inquirex/rules/contains.rb +36 -0
- data/lib/inquirex/rules/equals.rb +35 -0
- data/lib/inquirex/rules/greater_than.rb +35 -0
- data/lib/inquirex/rules/less_than.rb +35 -0
- data/lib/inquirex/rules/not_empty.rb +37 -0
- data/lib/inquirex/transition.rb +63 -0
- data/lib/inquirex/validation/adapter.rb +36 -0
- data/lib/inquirex/validation/null_adapter.rb +13 -0
- data/lib/inquirex/version.rb +5 -0
- data/lib/inquirex.rb +105 -0
- data/sig/inquirex.rbs +4 -0
- metadata +91 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 5a1021c83f11e037c69736dd5fa9228d95af53c9f7967173dd21bf9b096d4f31
|
|
4
|
+
data.tar.gz: 5b3cec5558da5aa1f9c3850e77ff6021916b9652531862ba621b8a7ea65fd5a7
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: aba4ff0487e52177d96fdf385c3268f4ea3c888c7e0e711547ec7b78700c7b5b377d573235823a5d8fe2861553006bed2b7573cdc9c8c38c2fc480ae0af81aa9
|
|
7
|
+
data.tar.gz: d1f7ccf5705755923d240414abb3e688534a618a36f9607fd60d6a526b72886c5d7fd86d8b1b93589edf16f7627c0ac2c26be4d7d42f1a4109f9f0bf1376ee2e
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Relaxed.Ruby.Style
|
|
2
|
+
## Version 2.5
|
|
3
|
+
|
|
4
|
+
Style/Alias:
|
|
5
|
+
Enabled: false
|
|
6
|
+
StyleGuide: https://relaxed.ruby.style/#stylealias
|
|
7
|
+
|
|
8
|
+
Style/AsciiComments:
|
|
9
|
+
Enabled: false
|
|
10
|
+
StyleGuide: https://relaxed.ruby.style/#styleasciicomments
|
|
11
|
+
|
|
12
|
+
Style/BeginBlock:
|
|
13
|
+
Enabled: false
|
|
14
|
+
StyleGuide: https://relaxed.ruby.style/#stylebeginblock
|
|
15
|
+
|
|
16
|
+
Style/BlockDelimiters:
|
|
17
|
+
Enabled: false
|
|
18
|
+
StyleGuide: https://relaxed.ruby.style/#styleblockdelimiters
|
|
19
|
+
|
|
20
|
+
Style/CommentAnnotation:
|
|
21
|
+
Enabled: false
|
|
22
|
+
StyleGuide: https://relaxed.ruby.style/#stylecommentannotation
|
|
23
|
+
|
|
24
|
+
Style/Documentation:
|
|
25
|
+
Enabled: false
|
|
26
|
+
StyleGuide: https://relaxed.ruby.style/#styledocumentation
|
|
27
|
+
|
|
28
|
+
Layout/DotPosition:
|
|
29
|
+
Enabled: false
|
|
30
|
+
StyleGuide: https://relaxed.ruby.style/#layoutdotposition
|
|
31
|
+
|
|
32
|
+
Style/DoubleNegation:
|
|
33
|
+
Enabled: false
|
|
34
|
+
StyleGuide: https://relaxed.ruby.style/#styledoublenegation
|
|
35
|
+
|
|
36
|
+
Style/EndBlock:
|
|
37
|
+
Enabled: false
|
|
38
|
+
StyleGuide: https://relaxed.ruby.style/#styleendblock
|
|
39
|
+
|
|
40
|
+
Style/FormatString:
|
|
41
|
+
Enabled: false
|
|
42
|
+
StyleGuide: https://relaxed.ruby.style/#styleformatstring
|
|
43
|
+
|
|
44
|
+
Style/IfUnlessModifier:
|
|
45
|
+
Enabled: false
|
|
46
|
+
StyleGuide: https://relaxed.ruby.style/#styleifunlessmodifier
|
|
47
|
+
|
|
48
|
+
Style/Lambda:
|
|
49
|
+
Enabled: false
|
|
50
|
+
StyleGuide: https://relaxed.ruby.style/#stylelambda
|
|
51
|
+
|
|
52
|
+
Style/ModuleFunction:
|
|
53
|
+
Enabled: false
|
|
54
|
+
StyleGuide: https://relaxed.ruby.style/#stylemodulefunction
|
|
55
|
+
|
|
56
|
+
Style/MultilineBlockChain:
|
|
57
|
+
Enabled: false
|
|
58
|
+
StyleGuide: https://relaxed.ruby.style/#stylemultilineblockchain
|
|
59
|
+
|
|
60
|
+
Style/NegatedIf:
|
|
61
|
+
Enabled: false
|
|
62
|
+
StyleGuide: https://relaxed.ruby.style/#stylenegatedif
|
|
63
|
+
|
|
64
|
+
Style/NegatedWhile:
|
|
65
|
+
Enabled: false
|
|
66
|
+
StyleGuide: https://relaxed.ruby.style/#stylenegatedwhile
|
|
67
|
+
|
|
68
|
+
Style/NumericPredicate:
|
|
69
|
+
Enabled: false
|
|
70
|
+
StyleGuide: https://relaxed.ruby.style/#stylenumericpredicate
|
|
71
|
+
|
|
72
|
+
Style/ParallelAssignment:
|
|
73
|
+
Enabled: false
|
|
74
|
+
StyleGuide: https://relaxed.ruby.style/#styleparallelassignment
|
|
75
|
+
|
|
76
|
+
Style/PercentLiteralDelimiters:
|
|
77
|
+
Enabled: false
|
|
78
|
+
StyleGuide: https://relaxed.ruby.style/#stylepercentliteraldelimiters
|
|
79
|
+
|
|
80
|
+
Style/PerlBackrefs:
|
|
81
|
+
Enabled: false
|
|
82
|
+
StyleGuide: https://relaxed.ruby.style/#styleperlbackrefs
|
|
83
|
+
|
|
84
|
+
Style/Semicolon:
|
|
85
|
+
Enabled: false
|
|
86
|
+
StyleGuide: https://relaxed.ruby.style/#stylesemicolon
|
|
87
|
+
|
|
88
|
+
Style/SignalException:
|
|
89
|
+
Enabled: false
|
|
90
|
+
StyleGuide: https://relaxed.ruby.style/#stylesignalexception
|
|
91
|
+
|
|
92
|
+
Style/SingleLineBlockParams:
|
|
93
|
+
Enabled: false
|
|
94
|
+
StyleGuide: https://relaxed.ruby.style/#stylesinglelineblockparams
|
|
95
|
+
|
|
96
|
+
Style/SingleLineMethods:
|
|
97
|
+
Enabled: false
|
|
98
|
+
StyleGuide: https://relaxed.ruby.style/#stylesinglelinemethods
|
|
99
|
+
|
|
100
|
+
Layout/SpaceBeforeBlockBraces:
|
|
101
|
+
Enabled: false
|
|
102
|
+
StyleGuide: https://relaxed.ruby.style/#layoutspacebeforeblockbraces
|
|
103
|
+
|
|
104
|
+
Layout/SpaceInsideParens:
|
|
105
|
+
Enabled: false
|
|
106
|
+
StyleGuide: https://relaxed.ruby.style/#layoutspaceinsideparens
|
|
107
|
+
|
|
108
|
+
Style/SpecialGlobalVars:
|
|
109
|
+
Enabled: false
|
|
110
|
+
StyleGuide: https://relaxed.ruby.style/#stylespecialglobalvars
|
|
111
|
+
|
|
112
|
+
Style/StringLiterals:
|
|
113
|
+
Enabled: false
|
|
114
|
+
StyleGuide: https://relaxed.ruby.style/#stylestringliterals
|
|
115
|
+
|
|
116
|
+
Style/TrailingCommaInArguments:
|
|
117
|
+
Enabled: false
|
|
118
|
+
StyleGuide: https://relaxed.ruby.style/#styletrailingcommainarguments
|
|
119
|
+
|
|
120
|
+
Style/TrailingCommaInArrayLiteral:
|
|
121
|
+
Enabled: false
|
|
122
|
+
StyleGuide: https://relaxed.ruby.style/#styletrailingcommainarrayliteral
|
|
123
|
+
|
|
124
|
+
Style/TrailingCommaInHashLiteral:
|
|
125
|
+
Enabled: false
|
|
126
|
+
StyleGuide: https://relaxed.ruby.style/#styletrailingcommainhashliteral
|
|
127
|
+
|
|
128
|
+
Style/SymbolArray:
|
|
129
|
+
Enabled: false
|
|
130
|
+
StyleGuide: http://relaxed.ruby.style/#stylesymbolarray
|
|
131
|
+
|
|
132
|
+
Style/WhileUntilModifier:
|
|
133
|
+
Enabled: false
|
|
134
|
+
StyleGuide: https://relaxed.ruby.style/#stylewhileuntilmodifier
|
|
135
|
+
|
|
136
|
+
Style/WordArray:
|
|
137
|
+
Enabled: false
|
|
138
|
+
StyleGuide: https://relaxed.ruby.style/#stylewordarray
|
|
139
|
+
|
|
140
|
+
Lint/AmbiguousRegexpLiteral:
|
|
141
|
+
Enabled: false
|
|
142
|
+
StyleGuide: https://relaxed.ruby.style/#lintambiguousregexpliteral
|
|
143
|
+
|
|
144
|
+
Lint/AssignmentInCondition:
|
|
145
|
+
Enabled: false
|
|
146
|
+
StyleGuide: https://relaxed.ruby.style/#lintassignmentincondition
|
|
147
|
+
|
|
148
|
+
Layout/LineLength:
|
|
149
|
+
Enabled: false
|
|
150
|
+
|
|
151
|
+
Metrics:
|
|
152
|
+
Enabled: false
|
|
153
|
+
|
data/.rub
ADDED
|
File without changes
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
4.0.2
|
data/.secrets.baseline
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.5.0",
|
|
3
|
+
"plugins_used": [
|
|
4
|
+
{
|
|
5
|
+
"name": "ArtifactoryDetector"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"name": "AWSKeyDetector"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "AzureStorageKeyDetector"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "Base64HighEntropyString",
|
|
15
|
+
"limit": 4.5
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "BasicAuthDetector"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "CloudantDetector"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "DiscordBotTokenDetector"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "GitHubTokenDetector"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "GitLabTokenDetector"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "HexHighEntropyString",
|
|
34
|
+
"limit": 3.0
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "IbmCloudIamDetector"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "IbmCosHmacDetector"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "IPPublicDetector"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "JwtTokenDetector"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "KeywordDetector",
|
|
50
|
+
"keyword_exclude": ""
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "MailchimpDetector"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "NpmDetector"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "OpenAIDetector"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "PrivateKeyDetector"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"name": "PypiTokenDetector"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "SendGridDetector"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "SlackDetector"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "SoftlayerDetector"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name": "SquareOAuthDetector"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "StripeDetector"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"name": "TelegramBotTokenDetector"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"name": "TwilioKeyDetector"
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
"filters_used": [
|
|
90
|
+
{
|
|
91
|
+
"path": "detect_secrets.filters.allowlist.is_line_allowlisted"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
|
|
95
|
+
"min_level": 2
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"path": "detect_secrets.filters.heuristic.is_indirect_reference"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"path": "detect_secrets.filters.heuristic.is_likely_id_string"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"path": "detect_secrets.filters.heuristic.is_lock_file"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"path": "detect_secrets.filters.heuristic.is_potential_uuid"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"path": "detect_secrets.filters.heuristic.is_sequential_string"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"path": "detect_secrets.filters.heuristic.is_swagger_file"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"path": "detect_secrets.filters.heuristic.is_templated_secret"
|
|
123
|
+
}
|
|
124
|
+
],
|
|
125
|
+
"results": {},
|
|
126
|
+
"generated_at": "2026-04-13T21:29:03Z"
|
|
127
|
+
}
|
data/Brewfile
ADDED
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Konstantin Gredeskoul
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# Inquirex
|
|
2
|
+
|
|
3
|
+
`inquirex` is a pure Ruby, declarative, rules-driven questionnaire engine for building conditional intake forms, qualification wizards, and branching surveys.
|
|
4
|
+
|
|
5
|
+
It is the core gem in the Inquirex ecosystem and focuses on:
|
|
6
|
+
|
|
7
|
+
- A conversational DSL (`ask`, `say`, `header`, `btw`, `warning`, `confirm`)
|
|
8
|
+
- A serializable AST rule system (`contains`, `equals`, `greater_than`, `less_than`, `not_empty`, `all`, `any`)
|
|
9
|
+
- An immutable flow definition graph
|
|
10
|
+
- A runtime engine for stateful step traversal
|
|
11
|
+
- JSON round-trip serialization for cross-platform clients
|
|
12
|
+
- A structured `Answers` wrapper and Mermaid graph export
|
|
13
|
+
|
|
14
|
+
## Status
|
|
15
|
+
|
|
16
|
+
- Version: `0.1.0`
|
|
17
|
+
- Ruby: `>= 4.0.0` (project currently uses `4.0.2`)
|
|
18
|
+
- Test suite: `109 examples, 0 failures`
|
|
19
|
+
- Coverage: ~`92.5%` line coverage
|
|
20
|
+
|
|
21
|
+
## Why Inquirex
|
|
22
|
+
|
|
23
|
+
Many form builders hard-code branching in controllers, React components, or database callbacks. Inquirex keeps flow logic in one portable graph:
|
|
24
|
+
|
|
25
|
+
- Define once in Ruby
|
|
26
|
+
- Serialize to JSON
|
|
27
|
+
- Evaluate transitions consistently using rule AST objects
|
|
28
|
+
- Run the same flow in different frontends (web widget, terminal, etc.)
|
|
29
|
+
|
|
30
|
+
This design is the foundation for the broader ecosystem (`inquirex-ui`, `inquirex-tty`, `inquirex-js`, `inquirex-llm`, `inquirex-rails`).
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
Add to your Gemfile:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
gem "inquirex"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then install:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
bundle install
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
require "inquirex"
|
|
50
|
+
|
|
51
|
+
definition = Inquirex.define id: "tax-intake-2025", version: "1.0.0" do
|
|
52
|
+
meta title: "Tax Preparation Intake", subtitle: "Let's understand your situation"
|
|
53
|
+
start :filing_status
|
|
54
|
+
|
|
55
|
+
ask :filing_status do
|
|
56
|
+
type :enum
|
|
57
|
+
question "What is your filing status?"
|
|
58
|
+
options single: "Single", married_jointly: "Married Filing Jointly"
|
|
59
|
+
transition to: :dependents
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
ask :dependents do
|
|
63
|
+
type :integer
|
|
64
|
+
question "How many dependents?"
|
|
65
|
+
default 0
|
|
66
|
+
transition to: :business_income
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
confirm :business_income do
|
|
70
|
+
question "Do you have business income?"
|
|
71
|
+
transition to: :business_count, if_rule: equals(:business_income, true)
|
|
72
|
+
transition to: :done
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
ask :business_count do
|
|
76
|
+
type :integer
|
|
77
|
+
question "How many businesses?"
|
|
78
|
+
transition to: :done
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
say :done do
|
|
82
|
+
text "Thanks for completing the intake."
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
engine = Inquirex::Engine.new(definition)
|
|
87
|
+
|
|
88
|
+
engine.answer("single") # filing_status
|
|
89
|
+
engine.answer(2) # dependents
|
|
90
|
+
engine.answer(false) # business_income
|
|
91
|
+
engine.advance # done (display step)
|
|
92
|
+
|
|
93
|
+
engine.finished? # => true
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## DSL Overview
|
|
97
|
+
|
|
98
|
+
### Flow-level methods
|
|
99
|
+
|
|
100
|
+
- `start :step_id` sets the entry step
|
|
101
|
+
- `meta title:, subtitle:, brand:` adds optional frontend metadata
|
|
102
|
+
|
|
103
|
+
### Step verbs
|
|
104
|
+
|
|
105
|
+
- Collecting verbs: `ask`, `confirm`
|
|
106
|
+
- Display verbs: `say`, `header`, `btw`, `warning`
|
|
107
|
+
|
|
108
|
+
### Supported input types
|
|
109
|
+
|
|
110
|
+
- `:string`
|
|
111
|
+
- `:text`
|
|
112
|
+
- `:integer`
|
|
113
|
+
- `:decimal`
|
|
114
|
+
- `:currency`
|
|
115
|
+
- `:boolean`
|
|
116
|
+
- `:enum`
|
|
117
|
+
- `:multi_enum`
|
|
118
|
+
- `:date`
|
|
119
|
+
- `:email`
|
|
120
|
+
- `:phone`
|
|
121
|
+
|
|
122
|
+
### Step options
|
|
123
|
+
|
|
124
|
+
- `question "..."` for collecting steps
|
|
125
|
+
- `text "..."` for display steps
|
|
126
|
+
- `options [...]` or `options key: "Label"` for enum-style inputs
|
|
127
|
+
- `default value` or `default { |answers| ... }`
|
|
128
|
+
- `skip_if rule`
|
|
129
|
+
- `transition to: :next_step, if_rule: rule, requires_server: false`
|
|
130
|
+
- `compute { |answers| ... }` (accepted by the DSL as a server-side hook; currently omitted from runtime JSON)
|
|
131
|
+
|
|
132
|
+
## Rule System (AST, JSON-serializable)
|
|
133
|
+
|
|
134
|
+
Rule helpers available in DSL blocks:
|
|
135
|
+
|
|
136
|
+
- `contains(:income_types, "Business")`
|
|
137
|
+
- `equals(:status, "single")`
|
|
138
|
+
- `greater_than(:dependents, 0)`
|
|
139
|
+
- `less_than(:age, 18)`
|
|
140
|
+
- `not_empty(:email)`
|
|
141
|
+
- `all(rule1, rule2, ...)`
|
|
142
|
+
- `any(rule1, rule2, ...)`
|
|
143
|
+
|
|
144
|
+
Example:
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
transition to: :complex_path,
|
|
148
|
+
if_rule: all(
|
|
149
|
+
contains(:income_types, "Business"),
|
|
150
|
+
greater_than(:business_count, 2)
|
|
151
|
+
)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Runtime Engine
|
|
155
|
+
|
|
156
|
+
`Inquirex::Engine` holds runtime state:
|
|
157
|
+
|
|
158
|
+
- `current_step_id`
|
|
159
|
+
- `current_step`
|
|
160
|
+
- `answers` (raw hash)
|
|
161
|
+
- `history` (visited step IDs)
|
|
162
|
+
|
|
163
|
+
Behavior:
|
|
164
|
+
|
|
165
|
+
- Use `answer(value)` on collecting steps
|
|
166
|
+
- Use `advance` on display steps
|
|
167
|
+
- Use `finished?` to detect completion
|
|
168
|
+
- Use `to_state` / `.from_state` for persistence/resume
|
|
169
|
+
|
|
170
|
+
### Validation Adapter
|
|
171
|
+
|
|
172
|
+
Validation is pluggable:
|
|
173
|
+
|
|
174
|
+
- `Inquirex::Validation::Adapter` (abstract)
|
|
175
|
+
- `Inquirex::Validation::NullAdapter` (default, accepts everything)
|
|
176
|
+
|
|
177
|
+
Pass a custom adapter to the engine:
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
engine = Inquirex::Engine.new(definition, validator: my_validator)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Serialization
|
|
184
|
+
|
|
185
|
+
Definitions support round-trip serialization:
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
json = definition.to_json
|
|
189
|
+
restored = Inquirex::Definition.from_json(json)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Serialized structure includes:
|
|
193
|
+
|
|
194
|
+
- Flow metadata (`id`, `version`, `meta`, `start`)
|
|
195
|
+
- Steps and transitions
|
|
196
|
+
- Rule AST payloads
|
|
197
|
+
|
|
198
|
+
Important serialization details:
|
|
199
|
+
|
|
200
|
+
- Rule objects serialize and deserialize cleanly
|
|
201
|
+
- Proc/lambda defaults are stripped from JSON
|
|
202
|
+
- `requires_server: true` transition flag is preserved
|
|
203
|
+
|
|
204
|
+
## Answers Wrapper
|
|
205
|
+
|
|
206
|
+
`Inquirex::Answers` provides structured answer access:
|
|
207
|
+
|
|
208
|
+
```ruby
|
|
209
|
+
answers = Inquirex::Answers.new(
|
|
210
|
+
filing_status: "single",
|
|
211
|
+
business: { count: 3, type: "llc" }
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
answers.filing_status # => "single"
|
|
215
|
+
answers[:filing_status] # => "single"
|
|
216
|
+
answers.business.count # => 3
|
|
217
|
+
answers.dig("business.count") # => 3
|
|
218
|
+
answers.to_flat_h # => {"filing_status"=>"single", "business.count"=>3, ...}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Mermaid Export
|
|
222
|
+
|
|
223
|
+
Visualize flow graphs with Mermaid:
|
|
224
|
+
|
|
225
|
+
```ruby
|
|
226
|
+
exporter = Inquirex::Graph::MermaidExporter.new(definition)
|
|
227
|
+
puts exporter.export
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Output is `flowchart TD` syntax with:
|
|
231
|
+
|
|
232
|
+
- One node per step
|
|
233
|
+
- Conditional edge labels from rule `to_s`
|
|
234
|
+
- Truncated node content for readability
|
|
235
|
+
|
|
236
|
+
## Error Types
|
|
237
|
+
|
|
238
|
+
Common exceptions under `Inquirex::Errors`:
|
|
239
|
+
|
|
240
|
+
- `DefinitionError`
|
|
241
|
+
- `UnknownStepError`
|
|
242
|
+
- `SerializationError`
|
|
243
|
+
- `AlreadyFinishedError`
|
|
244
|
+
- `ValidationError`
|
|
245
|
+
- `NonCollectingStepError`
|
|
246
|
+
|
|
247
|
+
## Development
|
|
248
|
+
|
|
249
|
+
### Setup
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
bin/setup
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Run tests
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
bundle exec rspec
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Lint
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
bundle exec rubocop
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Useful `just` tasks
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
just install
|
|
271
|
+
just test
|
|
272
|
+
just lint
|
|
273
|
+
just lint-fix
|
|
274
|
+
just ci
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Roadmap Context
|
|
278
|
+
|
|
279
|
+
This repository is the core runtime (`inquirex`). The full ecosystem roadmap includes:
|
|
280
|
+
|
|
281
|
+
- `inquirex-ui` for rendering metadata
|
|
282
|
+
- `inquirex-tty` for terminal interaction
|
|
283
|
+
- `inquirex-js` for embeddable web widget runtime
|
|
284
|
+
- `inquirex-llm` for server-side LLM verbs
|
|
285
|
+
- `inquirex-rails` for persistence/API integration
|
|
286
|
+
|
|
287
|
+
## License
|
|
288
|
+
|
|
289
|
+
MIT. See `LICENSE.txt`.
|
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
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
|