quby-compiler 0.5.15 → 0.5.16

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 126c7a3a5ef150e332a888dfef85acc264a46fe4a886b2f5ef9b0a19f460b14d
4
- data.tar.gz: 9bc6fad59d9da077cca02ab744bed4497dea3764e09f82d4aaf42016f7479dbc
3
+ metadata.gz: 5fbd126d732f2ab8201f325d09041cb564138354150fbedc83f3f7e9f68a755f
4
+ data.tar.gz: df750a8516c8596596cf11a03f3ce758bdb745de607068d4b8b753ae62a95e17
5
5
  SHA512:
6
- metadata.gz: 183182375a43d83e98bb4667865c78b958fb2443d8f028ea4411b59d2caaf894825072d764753539811bc659d68a8294c284d4226b7dddfdfb56635049a60a01
7
- data.tar.gz: '066826a89ee2dd03f8dc8b76b2d284a583331e4e3181823935d9afb3e48f970a8aeeece44ca7b6bc6f479ccd3ee179b47b39a65b26e60fef5489efb8106b41d3'
6
+ metadata.gz: ddd7e5767209991dd94f35b086294903cc2616d76104cbdaf776181e661e0cefe501393033abc41674aed88f14a7849a3f275c81330c72c68384dd42cf9052a3
7
+ data.tar.gz: 1ba99e0f79496a7d64d95952739bd5fcc1f0ef6f1927c2cd5ce211c6bddd134dd37f617400dd3201344d35a58503e7b14503856c274404f41d6e90391b07ace3
data/CHANGELOG.md CHANGED
@@ -1,7 +1,16 @@
1
+ # 0.5.16
2
+
3
+ * add integer as split_to_units option, with units and conversions as attributes
4
+ * add sexp_variables to do calculations using s expressions, for now in quby2, but later in the backend.
5
+ * test that select/radio options are always numeric
6
+ * quby2.json
7
+ * add split_to_units question, that saves a integer.
8
+ * Calculate sexpr variables and allow strings to interpolate them {{calculation.some_var}}
9
+
1
10
  # 0.5.15
2
11
 
3
12
  * Add context_description to questions, to have a text item that is hidden together with the question.
4
- * quuby2.json
13
+ * quby2.json
5
14
  * Moved quby2 serialization to the serializer, compact everything, no markdown for v2 title/descriptions.
6
15
  * Sanitize Quby2 html in v2.
7
16
  * Add contextDescription to questions.
@@ -8,6 +8,7 @@ require 'quby/compiler/dsl/charting/line_chart_builder'
8
8
  require 'quby/compiler/dsl/charting/radar_chart_builder'
9
9
  require 'quby/compiler/dsl/charting/bar_chart_builder'
10
10
  require 'quby/compiler/dsl/charting/overview_chart_builder'
11
+ require 'quby/compiler/dsl/sexp_variable_builder'
11
12
 
12
13
  require_relative 'standardized_panel_generators'
13
14
 
@@ -200,6 +201,10 @@ module Quby
200
201
  end
201
202
  end
202
203
 
204
+ def sexp_variable(key, &block)
205
+ @questionnaire.add_sexp_variable(key, SexpVariableBuilder.new(key, &block).build)
206
+ end
207
+
203
208
  # variable :totaal do
204
209
  # # Plain old Ruby code here, executed in the scope of the answer
205
210
  # # variables are private to the score calculation
@@ -12,6 +12,7 @@ module Quby
12
12
  end
13
13
 
14
14
  def build
15
+ @question.after_build
15
16
  @question
16
17
  end
17
18
 
@@ -14,6 +14,16 @@ module Quby
14
14
  super
15
15
  @question = Entities::Questions::IntegerQuestion.new(key, options)
16
16
  end
17
+
18
+ # as split_to_units
19
+ def units(*values)
20
+ @question.units = values
21
+ end
22
+
23
+ # as split_to_units
24
+ def conversions(value)
25
+ @question.conversions = value
26
+ end
17
27
  end
18
28
  end
19
29
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'quby/compiler/entities/sexp_variable'
4
+ module Quby::Compiler
5
+ module DSL
6
+ # sexp_variable :myvar do
7
+ # sum(number_values(:v_1, :v_2))
8
+ # end
9
+ class ::SexpVariableBuilder
10
+ attr_reader :calculation, :key
11
+
12
+ def initialize(key, &block)
13
+ @key = key
14
+ @calculation = instance_eval(&block)
15
+ end
16
+
17
+ def string_value(key)
18
+ Entities::SexpVariables::StringValue.new(op: :string_value, key: key)
19
+ end
20
+
21
+ def number_value(key)
22
+ Entities::SexpVariables::NumberValue.new(op: :number_value, key: key)
23
+ end
24
+
25
+ def number_values(*keys)
26
+ keys.map { |key| number_value(key) }
27
+ end
28
+
29
+ %i[sum subtract multiply divide].each do |op|
30
+ define_method(op) do |*values|
31
+ Entities::SexpVariables::NumberReducer.new(op:, values: wrap_and_flatten(values))
32
+ end
33
+ end
34
+
35
+ def round(value)
36
+ Entities::SexpVariables::NumberMethod.new(op: :round, value: value)
37
+ end
38
+
39
+ def build
40
+ Entities::SexpVariable.new(key:, calculation:)
41
+ end
42
+
43
+ private
44
+
45
+ def wrap_and_flatten(values)
46
+ values.flat_map { |value|
47
+ case value
48
+ when Numeric
49
+ Entities::SexpVariables::Number.new(op: :number, value: value)
50
+ else
51
+ value
52
+ end
53
+ }
54
+ end
55
+ end
56
+ end
57
+ end
@@ -208,6 +208,10 @@ module Quby
208
208
  end
209
209
  # rubocop:enable CyclomaticComplexity, Metrics/MethodLength
210
210
 
211
+ # called after DSL has instance_evalled everything within the question block.
212
+ def after_build
213
+ end
214
+
211
215
  def context_free_title_or_title
212
216
  context_free_title || title
213
217
  end
@@ -11,6 +11,7 @@ module Quby
11
11
 
12
12
  attr_reader :key
13
13
  attr_reader :value
14
+ validates :value, numericality: {allow_nil: true} # nil for checkbox questions.
14
15
  attr_reader :description, :context_free_description
15
16
  attr_reader :questions
16
17
  # for scale/radio/checbox questions, piece of of html that is rendered between the options
@@ -53,6 +53,7 @@ module Quby
53
53
  @outcome_tables = []
54
54
  @check_score_keys_consistency = true
55
55
  @lookup_tables = {}
56
+ @sexp_variables = {}
56
57
  @versions = []
57
58
  @seeds_patch = {}
58
59
  @anonymous_conditions = Entities::AnonymousConditions.new
@@ -102,6 +103,7 @@ module Quby
102
103
 
103
104
  attr_accessor :outcome_tables
104
105
  attr_accessor :score_schemas
106
+ attr_accessor :sexp_variables
105
107
  attr_accessor :lookup_tables
106
108
  attr_accessor :anonymous_conditions
107
109
 
@@ -362,6 +364,10 @@ module Quby
362
364
  end
363
365
  end
364
366
 
367
+ def add_sexp_variable(key, sexp_variable)
368
+ sexp_variables[key] = sexp_variable
369
+ end
370
+
365
371
  def add_outcome_table(outcome_table_options)
366
372
  outcome_tables << OutcomeTable.new(**outcome_table_options, questionnaire: self)
367
373
  end
@@ -0,0 +1,58 @@
1
+ module Quby::Compiler::Entities::Questions::Concerns
2
+ module SplitToUnits
3
+ extend ActiveSupport::Concern
4
+ DEFAULT_SPLIT_TO_UNIT_CONVERSIONS = {
5
+ minutes: {
6
+ hours: 60,
7
+ days: 1440,
8
+ weeks: 10080
9
+ },
10
+ seconds: {
11
+ minutes: 60,
12
+ hours: 3600,
13
+ days: 86400
14
+ },
15
+ m: {
16
+ km: 1000
17
+ },
18
+ cm: {
19
+ m: 100
20
+ },
21
+ mm: {
22
+ cm: 10,
23
+ m: 1000
24
+ },
25
+ g: {
26
+ kg: 1000
27
+ }
28
+ }.freeze
29
+
30
+ included do
31
+ attr_accessor :units, :conversions
32
+
33
+ validates :units, :conversions, presence: true, if: -> { as == :split_to_units }
34
+ validate :validate_split_to_units, if: -> { as == :split_to_units }
35
+ end
36
+
37
+ def after_build
38
+ super
39
+ return unless as == :split_to_units
40
+ @unit = units&.last
41
+ (@conversions ||= {}).reverse_merge!(default_split_to_units_conversions)
42
+ end
43
+
44
+ def default_split_to_units_conversions
45
+ (DEFAULT_SPLIT_TO_UNIT_CONVERSIONS[unit] || {}).slice(*units)
46
+ end
47
+
48
+ def validate_split_to_units
49
+ return unless units.present? && conversions.present?
50
+
51
+ (units - [unit]).each do |unit_to_convert|
52
+ if conversions[unit_to_convert].nil?
53
+ errors.add(:conversions, "should contain a conversion for unit #{unit_to_convert}")
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'concerns/slider'
4
+ require_relative 'concerns/split_to_units'
4
5
 
5
6
  module Quby
6
7
  module Compiler
@@ -8,6 +9,7 @@ module Quby
8
9
  module Questions
9
10
  class IntegerQuestion < Question
10
11
  include Concerns::Slider
12
+ include Concerns::SplitToUnits
11
13
 
12
14
  def size
13
15
  @size || 30
@@ -0,0 +1,50 @@
1
+ require 'quby/compiler/entities/sexp_variables'
2
+
3
+ module Quby::Compiler::Entities
4
+ class SexpVariable
5
+ attr_reader :calculation, :key
6
+
7
+ def initialize(key:, calculation:)
8
+ @key = key
9
+ @calculation = calculation
10
+ end
11
+
12
+ # Called by DefinitionValidator.
13
+ def validate(questionnaire)
14
+ case calculation
15
+ when SexpVariables::NumberValue
16
+ validate_question_exist(questionnaire, calculation)
17
+ validate_value_is_number(questionnaire, calculation)
18
+ when SexpVariables::StringValue
19
+ validate_question_exist(questionnaire, calculation)
20
+ validate_value_is_string(questionnaire, calculation)
21
+ end
22
+ end
23
+
24
+ def validate_question_exist(questionnaire, sexp)
25
+ return if questionnaire.question_hash.key?(sexp.key)
26
+
27
+ fail "sexp_variable #{key} uses nonexistent question #{sexp.key}."
28
+ end
29
+
30
+ def validate_value_is_number(questionnaire, sexp)
31
+ question = questionnaire.question_hash[sexp.key]
32
+ case question
33
+ when Questions::IntegerQuestion, Questions::FloatQuestion, Questions::SelectQuestion, Questions::RadioQuestion
34
+ return
35
+ end
36
+
37
+ fail "sexp_variable #{key} uses non-numeric question #{sexp.key} for number_value."
38
+ end
39
+
40
+ def validate_value_is_string(questionnaire, sexp)
41
+ question = questionnaire.question_hash[sexp.key]
42
+ case question
43
+ when Questions::StringQuestion, Questions::TextareaQuestion
44
+ return
45
+ end
46
+
47
+ fail "sexp_variable #{key} uses non-string question #{sexp.key} for string_value."
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,41 @@
1
+ module Quby::Compiler::Entities
2
+ module SexpVariables
3
+ class Base < Dry::Struct
4
+ end
5
+
6
+ # recursive dependencies, we so we define the classes first.
7
+ class NumberValue < Base; end
8
+ class Number < Base; end
9
+ class NumberReducer < Base; end
10
+ class NumberMethod < Base; end
11
+
12
+ NumericType = Number | NumberReducer | NumberMethod | NumberValue
13
+
14
+ class NumberValue
15
+ attribute :op, Quby::Types::Symbol.enum(:number_value)
16
+ attribute :key, Quby::Types::Symbol
17
+ end
18
+
19
+ class StringValue < Base
20
+ attribute :op, Quby::Types::Symbol.enum(:string_value)
21
+ attribute :key, Quby::Types::Symbol
22
+ end
23
+
24
+ class Number
25
+ attribute :op, Quby::Types::Symbol.enum(:value)
26
+ attribute :value, Quby::Types::Integer | Quby::Types::Float
27
+ end
28
+
29
+ # returning a Number
30
+ class NumberReducer
31
+ attribute :op, Quby::Types::Symbol.enum(:sum, :subtract, :multiply, :divide)
32
+ attribute :values, Quby::Types::Array.of(NumericType)
33
+ end
34
+
35
+ # returning a Number
36
+ class NumberMethod
37
+ attribute :op, Quby::Types::Symbol.enum(:round)
38
+ attribute :value, NumericType
39
+ end
40
+ end
41
+ end
@@ -23,7 +23,8 @@ module Quby
23
23
  questions: questions,
24
24
  textvars: textvars,
25
25
  validations: validations,
26
- visibilityRules: visibility_rules.as_json
26
+ visibilityRules: visibility_rules.as_json,
27
+ sexpVariables: sexp_variables,
27
28
  }
28
29
  end
29
30
 
@@ -83,10 +84,17 @@ module Quby
83
84
  end
84
85
 
85
86
  def float_question(question)
86
- integer_question(question)
87
+ number_question(question)
87
88
  end
88
89
 
89
90
  def integer_question(question)
91
+ {
92
+ **number_question(question),
93
+ **split_to_units_question(question),
94
+ }.compact
95
+ end
96
+
97
+ def number_question(question)
90
98
  {
91
99
  **base_question(question),
92
100
  **slider_question(question),
@@ -94,8 +102,8 @@ module Quby
94
102
  maximum: question.maximum,
95
103
  size: size(question),
96
104
  unit: question.as != :slider && question.unit,
97
- }.compact
98
- end
105
+ }.compact
106
+ end
99
107
 
100
108
  def radio_question(question)
101
109
  {
@@ -165,6 +173,13 @@ module Quby
165
173
  }
166
174
  end
167
175
 
176
+ def split_to_units_question(question)
177
+ {
178
+ units: question.units,
179
+ conversions: question.conversions,
180
+ }
181
+ end
182
+
168
183
  def question_type(question)
169
184
  {
170
185
  date: 'date_parts',
@@ -26,6 +26,7 @@ module Quby
26
26
  validate_outcome_tables(questionnaire)
27
27
  validate_markdown_fields(questionnaire) if questionnaire.validate_html
28
28
  validate_raw_content_items(questionnaire) if questionnaire.validate_html
29
+ validate_sexp_variables(questionnaire)
29
30
  # Some compilation errors are Exceptions (pure syntax errors) and some StandardErrors (NameErrors)
30
31
  rescue Exception => exception # rubocop:disable Lint/RescueException
31
32
  definition.errors.add(:sourcecode, message: "Questionnaire error: #{definition.key}\n" \
@@ -364,6 +365,12 @@ scores_schema tables to the resulting seed."
364
365
  fail "#{key || html} contains invalid html: #{fragment.errors.map(&:to_s).join(', ')}."
365
366
  end
366
367
 
368
+ def validate_sexp_variables(questionnaire)
369
+ questionnaire.sexp_variables.each_value do |sexp_variable|
370
+ sexp_variable.validate(questionnaire)
371
+ end
372
+ end
373
+
367
374
  def delete_prefix(key, questionnaire)
368
375
  key.delete_prefix("#{questionnaire.key}_")
369
376
  end
@@ -1,5 +1,5 @@
1
1
  module Quby
2
2
  module Compiler
3
- VERSION = "0.5.15"
3
+ VERSION = "0.5.16"
4
4
  end
5
5
  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.15
4
+ version: 0.5.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marten Veldthuis
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-02-27 00:00:00.000000000 Z
10
+ date: 2025-03-23 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activemodel
@@ -165,6 +165,7 @@ files:
165
165
  - lib/quby/compiler/dsl/questions/text_question_builder.rb
166
166
  - lib/quby/compiler/dsl/score_builder.rb
167
167
  - lib/quby/compiler/dsl/score_schema_builder.rb
168
+ - lib/quby/compiler/dsl/sexp_variable_builder.rb
168
169
  - lib/quby/compiler/dsl/standardized_panel_generators.rb
169
170
  - lib/quby/compiler/dsl/table_builder.rb
170
171
  - lib/quby/compiler/entities.rb
@@ -188,6 +189,7 @@ files:
188
189
  - lib/quby/compiler/entities/questionnaire.rb
189
190
  - lib/quby/compiler/entities/questions/checkbox_question.rb
190
191
  - lib/quby/compiler/entities/questions/concerns/slider.rb
192
+ - lib/quby/compiler/entities/questions/concerns/split_to_units.rb
191
193
  - lib/quby/compiler/entities/questions/date_question.rb
192
194
  - lib/quby/compiler/entities/questions/deprecated_question.rb
193
195
  - lib/quby/compiler/entities/questions/float_question.rb
@@ -198,6 +200,8 @@ files:
198
200
  - lib/quby/compiler/entities/questions/text_question.rb
199
201
  - lib/quby/compiler/entities/score_calculation.rb
200
202
  - lib/quby/compiler/entities/score_schema.rb
203
+ - lib/quby/compiler/entities/sexp_variable.rb
204
+ - lib/quby/compiler/entities/sexp_variables.rb
201
205
  - lib/quby/compiler/entities/subscore_schema.rb
202
206
  - lib/quby/compiler/entities/table.rb
203
207
  - lib/quby/compiler/entities/text.rb