quby-compiler 0.5.28 → 0.5.30
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 +10 -1
- 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 +12 -4
- data/lib/quby/compiler/outputs/roqua_serializer.rb +1 -1
- 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: 8a6a65058f64b3e68de9d841447cb82483d112ebf3bb79ea081b8d70369568af
|
4
|
+
data.tar.gz: '03487522a9cfe65ffeeddab48f4edb64b6c3f8fa08c5e2b1d5563bcad1ec4de2'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 916e3ba77d76eac79f56c7c6ebf785fac439e12decfbdc3a9b8b60649fa5759cf1dc41df999cf30f122c6a0287800b7f33f309e7bc761eb9fab81ab2a1f63476
|
7
|
+
data.tar.gz: 5c010854d019d877a7c9e85986e022229fe78b85f45721cd1adaa535b8023bd73926fd804e927aa19d825262bef8952811d2a7a4aa82988d4cf49e0286711f09
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,15 @@
|
|
1
|
+
# 0.5.30
|
2
|
+
|
3
|
+
* Added support for optgroups under select questions for quby2 (flattened for roqua/quby jsons)
|
4
|
+
* Added comparing and boolean logic to sexp_variables. Add type to sexp_variables.
|
5
|
+
|
6
|
+
# 0.5.29
|
7
|
+
|
8
|
+
* quby2.json: force scale questions to horizontal (so quby2 doesn't have to anymore)
|
9
|
+
|
1
10
|
# 0.5.28
|
2
11
|
|
3
|
-
* roqua.json: Don't parse markdown in title/descriptions for
|
12
|
+
* roqua.json: Don't parse markdown in title/descriptions for layout_version :v2
|
4
13
|
|
5
14
|
# 0.5.27
|
6
15
|
|
@@ -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
|
@@ -163,14 +163,17 @@ module Quby
|
|
163
163
|
end
|
164
164
|
|
165
165
|
def scale_question(question)
|
166
|
-
|
166
|
+
{
|
167
|
+
**radio_question(question),
|
168
|
+
presentation: :horizontal
|
169
|
+
}
|
167
170
|
end
|
168
171
|
|
169
172
|
def select_question(question)
|
170
173
|
{
|
171
174
|
**base_question(question),
|
172
175
|
children: children(question),
|
173
|
-
placeholder: question.
|
176
|
+
placeholder: question.all_options.find { _1.placeholder }&.description,
|
174
177
|
}.compact
|
175
178
|
end
|
176
179
|
|
@@ -242,7 +245,12 @@ module Quby
|
|
242
245
|
|
243
246
|
def children(question)
|
244
247
|
question.options.map.with_index { |child, idx|
|
245
|
-
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
|
246
254
|
inner_title_as_json(child, idx)
|
247
255
|
elsif child.placeholder
|
248
256
|
nil # placeholder attr on question.
|
@@ -271,7 +279,7 @@ module Quby
|
|
271
279
|
value: option.question.type != :check_box && option.value,
|
272
280
|
label:,
|
273
281
|
description:,
|
274
|
-
questions: option.question.type != :select
|
282
|
+
questions: option.question.type != :select ? option.questions.map{ question(_1) } : nil,
|
275
283
|
hidden: option.hidden.presence,
|
276
284
|
viewId: option.view_id
|
277
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,
|
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.30
|
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-15 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
|