quby-compiler 0.5.29 → 0.5.31
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 +4 -4
- data/CHANGELOG.md +9 -0
- data/lib/quby/compiler/dsl/questionnaire_builder.rb +2 -2
- data/lib/quby/compiler/dsl/questions/select_question_builder.rb +22 -1
- data/lib/quby/compiler/dsl/sexp_variable_builder.rb +26 -11
- data/lib/quby/compiler/entities/fields.rb +1 -1
- data/lib/quby/compiler/entities/question.rb +13 -1
- data/lib/quby/compiler/entities/question_optgroup.rb +35 -0
- data/lib/quby/compiler/entities/sexp_variable.rb +36 -8
- data/lib/quby/compiler/entities/sexp_variables.rb +16 -0
- data/lib/quby/compiler/entities/visibility_rule.rb +1 -1
- data/lib/quby/compiler/entities.rb +1 -0
- data/lib/quby/compiler/outputs/quby_frontend_v1_serializer.rb +1 -1
- data/lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb +8 -3
- data/lib/quby/compiler/outputs/roqua_serializer.rb +1 -1
- data/lib/quby/compiler/services/definition_validator.rb +5 -5
- data/lib/quby/compiler/version.rb +1 -1
- metadata +3 -3
- data/lib/quby/compiler/dsl/merge(values(:v_1, :v_2), values(v_4).rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a6013111357cf3e8e7e55045da2b06aeec8040b66034709091357ed43fb2eff6
|
4
|
+
data.tar.gz: eac3c3ce3e2d961336fe7b230d9f299a3fc0065cac2695e87019ba12ad9d60ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6ecb2bf5bdfaad1a377466e14d8c9cf39cf7d761144c0fa4c203e42ecfdeb46b5495005a029274c6bfe1baabf0e2bb526de080b1e2170a35f9ea3c5e973439a9
|
7
|
+
data.tar.gz: 45da18a37f6878de89bab06f3c9d87fa0ab52cb8ef767a796689d8ca8b9fd6511e9a9672b4095eba5220d93a8105b035a949a418e530f9fe73d0d4dc187defd1
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
# 0.5.31
|
2
|
+
|
3
|
+
* Fix validator not looking inside optgroups.
|
4
|
+
|
5
|
+
# 0.5.30
|
6
|
+
|
7
|
+
* Added support for optgroups under select questions for quby2 (flattened for roqua/quby jsons)
|
8
|
+
* Added comparing and boolean logic to sexp_variables. Add type to sexp_variables.
|
9
|
+
|
1
10
|
# 0.5.29
|
2
11
|
|
3
12
|
* quby2.json: force scale questions to horizontal (so quby2 doesn't have to anymore)
|
@@ -212,8 +212,8 @@ module Quby
|
|
212
212
|
end
|
213
213
|
end
|
214
214
|
|
215
|
-
def sexp_variable(key, &block)
|
216
|
-
@questionnaire.add_sexp_variable(key, SexpVariableBuilder.new(key, &block).build)
|
215
|
+
def sexp_variable(key, type: :number, &block)
|
216
|
+
@questionnaire.add_sexp_variable(key, SexpVariableBuilder.new(key, type:, &block).build)
|
217
217
|
end
|
218
218
|
|
219
219
|
# variable :totaal do
|
@@ -5,12 +5,33 @@ module Quby
|
|
5
5
|
module DSL
|
6
6
|
module Questions
|
7
7
|
class SelectQuestionBuilder < Base
|
8
|
-
include MultipleChoice
|
9
8
|
include CompareVisibilityRule
|
10
9
|
|
11
10
|
def initialize(key, **options, &block)
|
12
11
|
super
|
13
12
|
@question = Entities::Questions::SelectQuestion.new(key, options)
|
13
|
+
@current_optgroup = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def optgroup(key, label: , &block)
|
17
|
+
raise "Cannot nest optgroups" if @current_optgroup
|
18
|
+
@question.options << @current_optgroup = Entities::QuestionOptgroup.new(key, label:)
|
19
|
+
instance_eval(&block)
|
20
|
+
@current_optgroup = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def option(key, options = {}, &block)
|
24
|
+
question_option = Entities::QuestionOption.new(key, @question, options)
|
25
|
+
if @questionnaire.key_in_use?(question_option.input_key) || @question.key_in_use?(question_option.input_key)
|
26
|
+
fail "#{questionnaire.key}:#{@question.key}:#{question_option.key}: " \
|
27
|
+
"A question or option with input key #{question_option.input_key} is already defined."
|
28
|
+
end
|
29
|
+
|
30
|
+
if @current_optgroup
|
31
|
+
@current_optgroup.options << question_option
|
32
|
+
else
|
33
|
+
@question.options << question_option
|
34
|
+
end
|
14
35
|
end
|
15
36
|
end
|
16
37
|
end
|
@@ -7,10 +7,11 @@ module Quby::Compiler
|
|
7
7
|
# sum(number_values(:v_1, :v_2))
|
8
8
|
# end
|
9
9
|
class ::SexpVariableBuilder
|
10
|
-
attr_reader :calculation, :key
|
10
|
+
attr_reader :calculation, :key, :type
|
11
11
|
|
12
|
-
def initialize(key, &block)
|
12
|
+
def initialize(key, type:, &block)
|
13
13
|
@key = key
|
14
|
+
@type = type
|
14
15
|
@calculation = instance_eval(&block)
|
15
16
|
end
|
16
17
|
|
@@ -32,25 +33,39 @@ module Quby::Compiler
|
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
36
|
+
%i[eq not_eq gt gteq lt lteq].each do |op|
|
37
|
+
define_method(op) do |left:, right:|
|
38
|
+
Entities::SexpVariables::Comparer.new(op:, left: wrap_scalars(left), right: wrap_scalars(right))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
%i[all some none].each do |op|
|
43
|
+
define_method(op) do |*values|
|
44
|
+
Entities::SexpVariables::BooleanReducer.new(op:, values:)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
35
48
|
def round(value)
|
36
49
|
Entities::SexpVariables::NumberMethod.new(op: :round, value: value)
|
37
50
|
end
|
38
51
|
|
39
52
|
def build
|
40
|
-
Entities::SexpVariable.new(key:, calculation:)
|
53
|
+
Entities::SexpVariable.new(key:, calculation:, type:)
|
41
54
|
end
|
42
55
|
|
43
56
|
private
|
44
57
|
|
45
58
|
def wrap_and_flatten(values)
|
46
|
-
values.flat_map {
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
59
|
+
values.flat_map { wrap_scalars(_1)}
|
60
|
+
end
|
61
|
+
|
62
|
+
def wrap_scalars(value)
|
63
|
+
case value
|
64
|
+
when Numeric
|
65
|
+
Entities::SexpVariables::Number.new(op: :number, value: value)
|
66
|
+
else
|
67
|
+
value
|
68
|
+
end
|
54
69
|
end
|
55
70
|
end
|
56
71
|
end
|
@@ -46,7 +46,7 @@ module Quby
|
|
46
46
|
@question_hash[question.key] = question
|
47
47
|
@input_keys.merge(new_input_keys)
|
48
48
|
@answer_keys.merge(new_answer_keys)
|
49
|
-
question.
|
49
|
+
question.all_options.each do |option|
|
50
50
|
@option_hash[option.input_key] = option
|
51
51
|
end
|
52
52
|
end
|
@@ -45,6 +45,7 @@ module Quby
|
|
45
45
|
|
46
46
|
# Multiple-choice questions have options to choose from
|
47
47
|
attr_accessor :options
|
48
|
+
attr_accessor :opt_groups
|
48
49
|
|
49
50
|
# string question that is displayed after the title of this question.
|
50
51
|
attr_accessor :title_question
|
@@ -246,10 +247,21 @@ module Quby
|
|
246
247
|
answer_keys
|
247
248
|
else
|
248
249
|
# Some options don't have a key (inner_title), they are stripped
|
249
|
-
|
250
|
+
all_options.map { |opt| opt.input_key }.compact
|
250
251
|
end
|
251
252
|
end
|
252
253
|
|
254
|
+
def all_options
|
255
|
+
options.flat_map { |option|
|
256
|
+
case option
|
257
|
+
when Quby::Compiler::Entities::QuestionOptgroup
|
258
|
+
option.options
|
259
|
+
else
|
260
|
+
option
|
261
|
+
end
|
262
|
+
}
|
263
|
+
end
|
264
|
+
|
253
265
|
def key_in_use?(k)
|
254
266
|
claimed_keys.include?(k) ||
|
255
267
|
options.any? { |option| option.key_in_use?(k) }
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quby
|
4
|
+
module Compiler
|
5
|
+
module Entities
|
6
|
+
class QuestionOptgroup
|
7
|
+
include ActiveModel::Validations
|
8
|
+
include ::Quby::InspectExcept.new(:@question, :@questions)
|
9
|
+
|
10
|
+
attr_reader :key
|
11
|
+
attr_reader :label
|
12
|
+
attr_reader :options
|
13
|
+
|
14
|
+
def initialize(key, label:)
|
15
|
+
@key = key
|
16
|
+
@label = label
|
17
|
+
@options = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def key_in_use?(key)
|
21
|
+
@options.any? { _1.key_in_use?(key) }
|
22
|
+
end
|
23
|
+
|
24
|
+
# We don't allow subquestions on select questions
|
25
|
+
def questions
|
26
|
+
[]
|
27
|
+
end
|
28
|
+
|
29
|
+
def path_str
|
30
|
+
"#{question.path_str}:Optgroup:#{key}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -2,25 +2,53 @@ require 'quby/compiler/entities/sexp_variables'
|
|
2
2
|
|
3
3
|
module Quby::Compiler::Entities
|
4
4
|
class SexpVariable
|
5
|
-
attr_reader :calculation, :key
|
5
|
+
attr_reader :calculation, :key, :type
|
6
6
|
|
7
|
-
def initialize(key:, calculation:)
|
7
|
+
def initialize(key:, calculation:, type:)
|
8
8
|
@key = key
|
9
|
+
@type = type
|
9
10
|
@calculation = calculation
|
10
11
|
end
|
11
12
|
|
12
13
|
# Called by DefinitionValidator.
|
13
|
-
def validate(questionnaire)
|
14
|
-
|
14
|
+
def validate(questionnaire, calculation: self.calculation)
|
15
|
+
validate_node(calculation, questionnaire:, type:)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def validate_node(node, questionnaire:, type:)
|
21
|
+
validate_type(node, type:)
|
22
|
+
|
23
|
+
case node
|
15
24
|
when SexpVariables::NumberValue
|
16
|
-
validate_question_exist(questionnaire,
|
17
|
-
validate_value_is_number(questionnaire,
|
25
|
+
validate_question_exist(questionnaire, node)
|
26
|
+
validate_value_is_number(questionnaire, node)
|
18
27
|
when SexpVariables::StringValue
|
19
|
-
validate_question_exist(questionnaire,
|
20
|
-
validate_value_is_string(questionnaire,
|
28
|
+
validate_question_exist(questionnaire, node)
|
29
|
+
validate_value_is_string(questionnaire, node)
|
30
|
+
when SexpVariables::NumberReducer
|
31
|
+
calculation.values.each do |value|
|
32
|
+
validate_node(value, questionnaire:, type: :number)
|
33
|
+
end
|
34
|
+
when SexpVariables::BooleanReducer
|
35
|
+
calculation.values.each do |value|
|
36
|
+
validate_node(value, questionnaire:, type: :boolean)
|
37
|
+
end
|
38
|
+
when SexpVariables::Comparer
|
39
|
+
validate_node(node.left, questionnaire:, type: :number)
|
40
|
+
validate_node(node.right, questionnaire:, type: :number)
|
21
41
|
end
|
22
42
|
end
|
23
43
|
|
44
|
+
def validate_type(node, type:)
|
45
|
+
return if type == :number && SexpVariables::NumericType === node
|
46
|
+
return if type == :boolean && SexpVariables::BooleanType === node
|
47
|
+
return if type == :string && SexpVariables::StringValue === node
|
48
|
+
|
49
|
+
fail "sexp_variable #{key} has invalid type #{node.class} for expected type #{type}."
|
50
|
+
end
|
51
|
+
|
24
52
|
def validate_question_exist(questionnaire, sexp)
|
25
53
|
return if questionnaire.question_hash.key?(sexp.key)
|
26
54
|
|
@@ -8,8 +8,11 @@ module Quby::Compiler::Entities
|
|
8
8
|
class Number < Base; end
|
9
9
|
class NumberReducer < Base; end
|
10
10
|
class NumberMethod < Base; end
|
11
|
+
class BooleanReducer < Base; end
|
12
|
+
class Comparer < Base; end
|
11
13
|
|
12
14
|
NumericType = Number | NumberReducer | NumberMethod | NumberValue
|
15
|
+
BooleanType = BooleanReducer | Comparer
|
13
16
|
|
14
17
|
class NumberValue
|
15
18
|
attribute :op, Quby::Types::Symbol.enum(:number_value)
|
@@ -32,6 +35,19 @@ module Quby::Compiler::Entities
|
|
32
35
|
attribute :values, Quby::Types::Array.of(NumericType)
|
33
36
|
end
|
34
37
|
|
38
|
+
# returning a Boolean
|
39
|
+
class BooleanReducer < Base
|
40
|
+
attribute :op, Quby::Types::Symbol.enum(:all, :some, :none)
|
41
|
+
attribute :values, Quby::Types::Array.of(Base)
|
42
|
+
end
|
43
|
+
|
44
|
+
# returning a Boolean
|
45
|
+
class Comparer < Base
|
46
|
+
attribute :op, Quby::Types::Symbol.enum(:eq, :not_eq, :gt, :gteq, :lt, :lteq)
|
47
|
+
attribute :left, NumericType
|
48
|
+
attribute :right, NumericType
|
49
|
+
end
|
50
|
+
|
35
51
|
# returning a Number
|
36
52
|
class NumberMethod
|
37
53
|
attribute :op, Quby::Types::Symbol.enum(:round)
|
@@ -5,6 +5,7 @@ require 'quby/compiler/entities/definition'
|
|
5
5
|
require 'quby/compiler/entities/questionnaire'
|
6
6
|
require 'quby/compiler/entities/version'
|
7
7
|
require 'quby/compiler/entities/question_option'
|
8
|
+
require 'quby/compiler/entities/question_optgroup'
|
8
9
|
require 'quby/compiler/entities/item'
|
9
10
|
require 'quby/compiler/entities/score_calculation'
|
10
11
|
require 'quby/compiler/entities/anonymous_conditions'
|
@@ -183,7 +183,7 @@ module Quby
|
|
183
183
|
# Also adds the title question under the last option.
|
184
184
|
# TODO old dsl put it there, remove after quby gem uses title_question
|
185
185
|
def options_as_json(question)
|
186
|
-
question.
|
186
|
+
question.all_options.map { |option| option_as_json(option) }.tap do |options_json|
|
187
187
|
if question.title_question
|
188
188
|
options_json.last[:questions] << question_as_json(question.title_question)
|
189
189
|
end
|
@@ -173,7 +173,7 @@ module Quby
|
|
173
173
|
{
|
174
174
|
**base_question(question),
|
175
175
|
children: children(question),
|
176
|
-
placeholder: question.
|
176
|
+
placeholder: question.all_options.find { _1.placeholder }&.description,
|
177
177
|
}.compact
|
178
178
|
end
|
179
179
|
|
@@ -245,7 +245,12 @@ module Quby
|
|
245
245
|
|
246
246
|
def children(question)
|
247
247
|
question.options.map.with_index { |child, idx|
|
248
|
-
if child.
|
248
|
+
if child.is_a?(Quby::Compiler::Entities::QuestionOptgroup)
|
249
|
+
{ type: 'optgroup',
|
250
|
+
key: child.key,
|
251
|
+
options: child.options.map { option_as_json(_1) }
|
252
|
+
}
|
253
|
+
elsif child.inner_title
|
249
254
|
inner_title_as_json(child, idx)
|
250
255
|
elsif child.placeholder
|
251
256
|
nil # placeholder attr on question.
|
@@ -274,7 +279,7 @@ module Quby
|
|
274
279
|
value: option.question.type != :check_box && option.value,
|
275
280
|
label:,
|
276
281
|
description:,
|
277
|
-
questions: option.question.type != :select
|
282
|
+
questions: option.question.type != :select ? option.questions.map{ question(_1) } : nil,
|
278
283
|
hidden: option.hidden.presence,
|
279
284
|
viewId: option.view_id
|
280
285
|
}.compact
|
@@ -239,7 +239,7 @@ module Quby
|
|
239
239
|
def options_for(question)
|
240
240
|
return nil if question.options.empty?
|
241
241
|
|
242
|
-
question.
|
242
|
+
question.all_options.reject { |option| option.inner_title || option.placeholder }
|
243
243
|
.map { |option|
|
244
244
|
{
|
245
245
|
key: option.key,
|
@@ -106,7 +106,7 @@ module Quby
|
|
106
106
|
end
|
107
107
|
|
108
108
|
def validate_question_options(questionnaire, question)
|
109
|
-
question.
|
109
|
+
question.all_options.each do |option|
|
110
110
|
validate_item(option)
|
111
111
|
to_be_hidden_questions_exist_and_not_subquestion?(questionnaire, option, msg_base: option.path_str)
|
112
112
|
to_be_shown_questions_exist_and_not_subquestion?(questionnaire, option, msg_base: option.path_str)
|
@@ -296,7 +296,7 @@ scores_schema tables to the resulting seed."
|
|
296
296
|
|
297
297
|
def validate_subquestion_absence_in_select(question)
|
298
298
|
return unless question.type == :select
|
299
|
-
question.
|
299
|
+
question.all_options.each do |option|
|
300
300
|
unless option.questions.empty?
|
301
301
|
fail "Question '#{question.key}' of type ':select' may not include other questions."
|
302
302
|
end
|
@@ -304,7 +304,7 @@ scores_schema tables to the resulting seed."
|
|
304
304
|
end
|
305
305
|
|
306
306
|
def validate_placeholder_options_nil_values(question)
|
307
|
-
question.
|
307
|
+
question.all_options.each do |question_option|
|
308
308
|
if question_option.placeholder && question_option.value.present?
|
309
309
|
fail "#{question.key}:#{question_option.key}: Placeholder options should not have values defined."
|
310
310
|
end
|
@@ -314,7 +314,7 @@ scores_schema tables to the resulting seed."
|
|
314
314
|
def validate_values_unique(question)
|
315
315
|
return if question.type == :check_box || question.allow_duplicate_option_values
|
316
316
|
|
317
|
-
question.
|
317
|
+
question.all_options.each_with_object([]) do |question_option, seen_values|
|
318
318
|
next if question_option.placeholder || question_option.inner_title
|
319
319
|
|
320
320
|
fail "#{question.key}:#{question_option.key}: Has no option value defined." if question_option.value.blank?
|
@@ -344,7 +344,7 @@ scores_schema tables to the resulting seed."
|
|
344
344
|
questionnaire.questions.each do |question|
|
345
345
|
Entities::Question::MARKDOWN_ATTRIBUTES.each do |attr|
|
346
346
|
validate_markdown(question.send(attr), "#{question.key}.#{attr}")
|
347
|
-
question.
|
347
|
+
question.all_options.each do |option|
|
348
348
|
Entities::QuestionOption::MARKDOWN_ATTRIBUTES.each do |option_attr|
|
349
349
|
validate_markdown(option.send(option_attr), "#{question.key}:#{option.key}.#{option_attr}")
|
350
350
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quby-compiler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.31
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marten Veldthuis
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-09-17 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: activemodel
|
@@ -151,7 +151,6 @@ files:
|
|
151
151
|
- lib/quby/compiler/dsl/charting/radar_chart_builder.rb
|
152
152
|
- lib/quby/compiler/dsl/helpers.rb
|
153
153
|
- lib/quby/compiler/dsl/info_block_builder.rb
|
154
|
-
- lib/quby/compiler/dsl/merge(values(:v_1, :v_2), values(v_4).rb
|
155
154
|
- lib/quby/compiler/dsl/panel_builder.rb
|
156
155
|
- lib/quby/compiler/dsl/question_builder.rb
|
157
156
|
- lib/quby/compiler/dsl/questionnaire_builder.rb
|
@@ -188,6 +187,7 @@ files:
|
|
188
187
|
- lib/quby/compiler/entities/outcome_table.rb
|
189
188
|
- lib/quby/compiler/entities/panel.rb
|
190
189
|
- lib/quby/compiler/entities/question.rb
|
190
|
+
- lib/quby/compiler/entities/question_optgroup.rb
|
191
191
|
- lib/quby/compiler/entities/question_option.rb
|
192
192
|
- lib/quby/compiler/entities/questionnaire.rb
|
193
193
|
- lib/quby/compiler/entities/questions/checkbox_question.rb
|
@@ -1,16 +0,0 @@
|
|
1
|
-
merge(values(:v_1, :v_2), values(v_4).recode(1 => 3)).sum
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
divide(value(:v_2), multiply(value(:v_1), value(:v_1)))
|
6
|
-
|
7
|
-
w / l * l
|
8
|
-
|
9
|
-
|
10
|
-
sexp_variable :key1 do
|
11
|
-
foo = values(:v_1, :v_2)
|
12
|
-
bar = recode(foo, 1 => 3)
|
13
|
-
bla = merge(foo, bar)
|
14
|
-
sum(bla)
|
15
|
-
|
16
|
-
end
|