quby-compiler 0.2.1

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.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.gitlab-ci.yml +5 -0
  4. data/.rspec +3 -0
  5. data/.ruby-version +1 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Dockerfile +11 -0
  8. data/Gemfile +8 -0
  9. data/Gemfile.lock +133 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +44 -0
  12. data/Rakefile +6 -0
  13. data/bin/console +14 -0
  14. data/bin/rspec +29 -0
  15. data/bin/setup +8 -0
  16. data/config/locales/de.yml +58 -0
  17. data/config/locales/en.yml +57 -0
  18. data/config/locales/nl.yml +57 -0
  19. data/config/locales/rails-i18n/README.md +4 -0
  20. data/config/locales/rails-i18n/de.yml +223 -0
  21. data/config/locales/rails-i18n/en.yml +216 -0
  22. data/config/locales/rails-i18n/nl.yml +214 -0
  23. data/exe/quby-compile +56 -0
  24. data/lib/quby/array_attribute_valid_validator.rb +15 -0
  25. data/lib/quby/attribute_valid_validator.rb +14 -0
  26. data/lib/quby/compiler.rb +50 -0
  27. data/lib/quby/compiler/dsl.rb +29 -0
  28. data/lib/quby/compiler/dsl/base.rb +20 -0
  29. data/lib/quby/compiler/dsl/calls_custom_methods.rb +29 -0
  30. data/lib/quby/compiler/dsl/charting/bar_chart_builder.rb +14 -0
  31. data/lib/quby/compiler/dsl/charting/chart_builder.rb +95 -0
  32. data/lib/quby/compiler/dsl/charting/line_chart_builder.rb +34 -0
  33. data/lib/quby/compiler/dsl/charting/overview_chart_builder.rb +31 -0
  34. data/lib/quby/compiler/dsl/charting/radar_chart_builder.rb +14 -0
  35. data/lib/quby/compiler/dsl/helpers.rb +53 -0
  36. data/lib/quby/compiler/dsl/panel_builder.rb +80 -0
  37. data/lib/quby/compiler/dsl/question_builder.rb +40 -0
  38. data/lib/quby/compiler/dsl/questionnaire_builder.rb +279 -0
  39. data/lib/quby/compiler/dsl/questions/base.rb +180 -0
  40. data/lib/quby/compiler/dsl/questions/checkbox_question_builder.rb +20 -0
  41. data/lib/quby/compiler/dsl/questions/date_question_builder.rb +18 -0
  42. data/lib/quby/compiler/dsl/questions/deprecated_question_builder.rb +18 -0
  43. data/lib/quby/compiler/dsl/questions/float_question_builder.rb +21 -0
  44. data/lib/quby/compiler/dsl/questions/integer_question_builder.rb +21 -0
  45. data/lib/quby/compiler/dsl/questions/radio_question_builder.rb +20 -0
  46. data/lib/quby/compiler/dsl/questions/select_question_builder.rb +18 -0
  47. data/lib/quby/compiler/dsl/questions/string_question_builder.rb +20 -0
  48. data/lib/quby/compiler/dsl/questions/text_question_builder.rb +22 -0
  49. data/lib/quby/compiler/dsl/score_builder.rb +22 -0
  50. data/lib/quby/compiler/dsl/score_schema_builder.rb +53 -0
  51. data/lib/quby/compiler/dsl/standardized_panel_generators.rb +33 -0
  52. data/lib/quby/compiler/dsl/table_builder.rb +48 -0
  53. data/lib/quby/compiler/entities.rb +38 -0
  54. data/lib/quby/compiler/entities/charting/bar_chart.rb +17 -0
  55. data/lib/quby/compiler/entities/charting/chart.rb +101 -0
  56. data/lib/quby/compiler/entities/charting/charts.rb +42 -0
  57. data/lib/quby/compiler/entities/charting/line_chart.rb +38 -0
  58. data/lib/quby/compiler/entities/charting/overview_chart.rb +20 -0
  59. data/lib/quby/compiler/entities/charting/plottable.rb +20 -0
  60. data/lib/quby/compiler/entities/charting/radar_chart.rb +17 -0
  61. data/lib/quby/compiler/entities/definition.rb +26 -0
  62. data/lib/quby/compiler/entities/fields.rb +119 -0
  63. data/lib/quby/compiler/entities/flag.rb +55 -0
  64. data/lib/quby/compiler/entities/item.rb +40 -0
  65. data/lib/quby/compiler/entities/lookup_tables.rb +71 -0
  66. data/lib/quby/compiler/entities/outcome_table.rb +31 -0
  67. data/lib/quby/compiler/entities/panel.rb +82 -0
  68. data/lib/quby/compiler/entities/question.rb +365 -0
  69. data/lib/quby/compiler/entities/question_option.rb +96 -0
  70. data/lib/quby/compiler/entities/questionnaire.rb +440 -0
  71. data/lib/quby/compiler/entities/questions/checkbox_question.rb +82 -0
  72. data/lib/quby/compiler/entities/questions/date_question.rb +84 -0
  73. data/lib/quby/compiler/entities/questions/deprecated_question.rb +19 -0
  74. data/lib/quby/compiler/entities/questions/float_question.rb +15 -0
  75. data/lib/quby/compiler/entities/questions/integer_question.rb +15 -0
  76. data/lib/quby/compiler/entities/questions/radio_question.rb +19 -0
  77. data/lib/quby/compiler/entities/questions/select_question.rb +19 -0
  78. data/lib/quby/compiler/entities/questions/string_question.rb +15 -0
  79. data/lib/quby/compiler/entities/questions/text_question.rb +15 -0
  80. data/lib/quby/compiler/entities/score_calculation.rb +35 -0
  81. data/lib/quby/compiler/entities/score_schema.rb +25 -0
  82. data/lib/quby/compiler/entities/subscore_schema.rb +23 -0
  83. data/lib/quby/compiler/entities/table.rb +143 -0
  84. data/lib/quby/compiler/entities/text.rb +71 -0
  85. data/lib/quby/compiler/entities/textvar.rb +23 -0
  86. data/lib/quby/compiler/entities/validation.rb +17 -0
  87. data/lib/quby/compiler/entities/version.rb +23 -0
  88. data/lib/quby/compiler/entities/visibility_rule.rb +71 -0
  89. data/lib/quby/compiler/instance.rb +72 -0
  90. data/lib/quby/compiler/output.rb +13 -0
  91. data/lib/quby/compiler/outputs.rb +4 -0
  92. data/lib/quby/compiler/outputs/quby_frontend_v1_serializer.rb +362 -0
  93. data/lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb +15 -0
  94. data/lib/quby/compiler/outputs/roqua_serializer.rb +108 -0
  95. data/lib/quby/compiler/outputs/seed_serializer.rb +34 -0
  96. data/lib/quby/compiler/services/definition_validator.rb +330 -0
  97. data/lib/quby/compiler/services/quby_proxy.rb +405 -0
  98. data/lib/quby/compiler/services/seed_diff.rb +116 -0
  99. data/lib/quby/compiler/services/text_transformation.rb +30 -0
  100. data/lib/quby/compiler/version.rb +5 -0
  101. data/lib/quby/markdown_parser.rb +38 -0
  102. data/lib/quby/range_categories.rb +38 -0
  103. data/lib/quby/settings.rb +86 -0
  104. data/lib/quby/text_transformation.rb +26 -0
  105. data/lib/quby/type_validator.rb +12 -0
  106. data/quby-compiler.gemspec +39 -0
  107. metadata +277 -0
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quby
4
+ module Compiler
5
+ module Entities
6
+ module Questions
7
+ class CheckboxQuestion < Question
8
+ # checkbox option that checks all other options on check
9
+ attr_accessor :check_all_option
10
+
11
+ # checkbox option that unchecks all other options on check
12
+ attr_accessor :uncheck_all_option
13
+
14
+ # checkbox option that allows to select a maximum amount of checkboxes
15
+ attr_accessor :maximum_checked_allowed
16
+
17
+ # checkbox option that forces to select a minimum amount of checkboxes
18
+ attr_accessor :minimum_checked_required
19
+
20
+ def initialize(key, options = {})
21
+ super
22
+
23
+ @check_all_option = options[:check_all_option]
24
+ @uncheck_all_option = options[:uncheck_all_option]
25
+ @maximum_checked_allowed = options[:maximum_checked_allowed]
26
+ @minimum_checked_required = options[:minimum_checked_required]
27
+
28
+ if @check_all_option
29
+ @validations << {type: :not_all_checked,
30
+ check_all_key: @check_all_option,
31
+ explanation: options[:error_explanation]}
32
+ end
33
+
34
+ if @uncheck_all_option
35
+ @validations << {type: :too_many_checked,
36
+ uncheck_all_key: @uncheck_all_option,
37
+ explanation: options[:error_explanation]}
38
+ end
39
+
40
+ if @maximum_checked_allowed
41
+ @validations << {type: :maximum_checked_allowed,
42
+ maximum_checked_value: @maximum_checked_allowed,
43
+ explanation: options[:error_explanation]}
44
+ end
45
+
46
+ if @minimum_checked_required
47
+ @validations << {type: :minimum_checked_required,
48
+ minimum_checked_value: @minimum_checked_required,
49
+ explanation: options[:error_explanation]}
50
+ end
51
+ end
52
+
53
+ def variable_descriptions
54
+ options.each_with_object(key => context_free_title) do |option, hash|
55
+ next if option.input_key.blank?
56
+ hash[option.input_key] = "#{context_free_title} - #{option.description}"
57
+ end.with_indifferent_access
58
+ end
59
+
60
+ def claimed_keys
61
+ [key]
62
+ end
63
+
64
+ def answer_keys
65
+ # Some options don't have a key (inner_title), they are stripped.
66
+ options.map { |opt| opt.input_key }.compact
67
+ end
68
+
69
+ def as_json(options = {})
70
+ super.merge(options: @options.as_json)
71
+ end
72
+
73
+ def to_codebook(questionnaire, opts = {})
74
+ options.map do |option|
75
+ option.to_codebook(questionnaire, opts)
76
+ end.compact.join("\n\n")
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quby
4
+ module Compiler
5
+ module Entities
6
+ module Questions
7
+ class DateQuestion < Question
8
+ POSSIBLE_COMPONENTS = %i( day month year hour minute )
9
+ COMPONENT_KEYS = Hash[POSSIBLE_COMPONENTS.zip %w( dd mm yyyy hh ii)]
10
+ COMPONENT_PLACEHOLDERS = Hash[POSSIBLE_COMPONENTS.zip %w( DD MM YYYY hh mm)]
11
+ DEFAULT_COMPONENTS = %i( day month year )
12
+
13
+ # For optionally giving year, month and day fields of dates their own keys
14
+ POSSIBLE_COMPONENTS.each do |component|
15
+ attr_accessor "#{component}_key".to_sym
16
+ end
17
+
18
+ # @!attribute [r] components
19
+ # @return [Array<Symbol>] date parts to show
20
+ # @!attribute [r] required_components
21
+ # @return [Array<Symbol>] date parts that are required if the question is required or partly filled out.
22
+ attr_accessor :components, :required_components, :optional_components
23
+
24
+ def initialize(key, options = {})
25
+ super
26
+
27
+ @components = options[:components] || DEFAULT_COMPONENTS
28
+ @required_components = options[:required_components] || @components
29
+ @optional_components = @components - @required_components
30
+
31
+ components.each do |component|
32
+ component_key = options[:"#{component}_key"] || "#{key}_#{COMPONENT_KEYS[component]}"
33
+ instance_variable_set("@#{component}_key", component_key.to_sym)
34
+ end
35
+
36
+ add_date_validation(options[:error_explanation])
37
+ end
38
+
39
+ def add_date_validation(explanation)
40
+ @validations << {type: :valid_date,
41
+ subtype: :"valid_date_#{components.sort.join('_')}",
42
+ explanation: explanation}
43
+ end
44
+
45
+ def claimed_keys
46
+ [key] + answer_keys
47
+ end
48
+
49
+ def answer_keys
50
+ components.map do |component|
51
+ send("#{component}_key").to_sym
52
+ end
53
+ end
54
+
55
+ def variable_descriptions
56
+ components.each_with_object(key => context_free_title) do |component, hash|
57
+ key = send("#{component}_key")
58
+ hash[key] = "#{context_free_title} (#{I18n.t component})"
59
+ end.with_indifferent_access
60
+ end
61
+
62
+ def as_json(options = {})
63
+ component_keys = components.each_with_object({}) do |component, hash|
64
+ hash["#{component}Key"] = send("#{component}_key")
65
+ end
66
+ super.merge(components: components).merge(component_keys)
67
+ end
68
+
69
+ def to_codebook(questionnaire, opts = {})
70
+ output = []
71
+ components.each do |component|
72
+ output << "#{codebook_key(send("#{component}_key"), questionnaire, opts)} " \
73
+ "#{type}_#{component} #{codebook_output_range}"
74
+ output << "\"#{title}\"" unless title.blank?
75
+ output << options.map(&:to_codebook).join("\n") unless options.blank?
76
+ output << ""
77
+ end
78
+ output.join("\n")
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quby
4
+ module Compiler
5
+ module Entities
6
+ module Questions
7
+ class DeprecatedQuestion < Question
8
+ def hidden?
9
+ true
10
+ end
11
+
12
+ def as_json(options = {})
13
+ super.merge(options: @options.as_json)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quby
4
+ module Compiler
5
+ module Entities
6
+ module Questions
7
+ class FloatQuestion < Question
8
+ def size
9
+ @size || 30
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quby
4
+ module Compiler
5
+ module Entities
6
+ module Questions
7
+ class IntegerQuestion < Question
8
+ def size
9
+ @size || 30
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quby
4
+ module Compiler
5
+ module Entities
6
+ module Questions
7
+ class RadioQuestion < Question
8
+ def as_json(options = {})
9
+ super.merge(options: @options.as_json)
10
+ end
11
+
12
+ def codebook_output_type
13
+ :radio
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quby
4
+ module Compiler
5
+ module Entities
6
+ module Questions
7
+ class SelectQuestion < Question
8
+ def as_json(options = {})
9
+ super.merge(options: @options.as_json)
10
+ end
11
+
12
+ def codebook_output_type
13
+ :radio
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quby
4
+ module Compiler
5
+ module Entities
6
+ module Questions
7
+ class StringQuestion < Question
8
+ def as_json(options = {})
9
+ super.merge(autocomplete: @autocomplete, size: @size || 30)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quby
4
+ module Compiler
5
+ module Entities
6
+ module Questions
7
+ class TextQuestion < Question
8
+ def as_json(options = {})
9
+ super.merge(autocomplete: @autocomplete, lines: lines, cols: cols)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quby
4
+ module Compiler
5
+ module Entities
6
+ class ScoreCalculation
7
+ attr_accessor :key, :label, :sbg_key, :options, :calculation
8
+
9
+ def initialize(key, options, &block)
10
+ @key = key
11
+ @label = options[:label]
12
+ @sbg_key = options[:sbg_key]
13
+ @options = options
14
+ @calculation = block
15
+ end
16
+
17
+ def score
18
+ @options[:score]
19
+ end
20
+
21
+ def completion
22
+ @options[:completion]
23
+ end
24
+
25
+ def action
26
+ @options[:action]
27
+ end
28
+
29
+ def sourcecode
30
+ options[:ruby_string] || calculation&.to_proc&.source
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ module Quby
2
+ module Compiler
3
+ module Entities
4
+ # ScoreSchema instances describe score definitions.
5
+ #
6
+ # Score definitions are blocks of ruby code that return a hash of score results based on a questionnaire
7
+ # response (answer). These schemas describe the purpose and form of the scores. Each key-value pair of the result
8
+ # hash is called a subscore.
9
+ # The :value subscore is treated as the main score result. Subscores are usually identified by their 'export_key'.
10
+ # The score value's export_key is usually set to a shortened version of the main score key.
11
+ class ScoreSchema < Dry::Struct
12
+ # The key of the corresponding score in the questionnaire definition
13
+ attribute :key, Types::Symbol
14
+ # A label describing the general purpose of the score
15
+ attribute :label, Types::String
16
+ # An array of SubscoreSchemas describing each key that can be returned in the result hash of a score.
17
+ attribute :subscore_schemas, Types::Array(Entities::SubscoreSchema)
18
+
19
+ def export_key_labels
20
+ subscore_schemas.map { |schema| [schema.export_key, schema.label] }.to_h.with_indifferent_access
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ module Quby
2
+ module Compiler
3
+ module Entities
4
+ # SubscoreSchema instances describe each key that could be returned in a score result hash
5
+ class SubscoreSchema < Dry::Struct
6
+ # The key this subscore has in the hash returned by the score
7
+ attribute :key, Types::Symbol
8
+ # The description of this subscore in the context of the score, like 'Mean', 'T-Score' or 'Interpretation'
9
+ attribute :label, Types::String
10
+ # The shortened key that will used as the field/column name for csv and oru exports,
11
+ # excluding the questionnaire key part
12
+ attribute :export_key, Types::Symbol
13
+ # [Optional] Whether this score will only be exported through oru/api/data exports, but not shown in interfaces
14
+ attribute :only_for_export?, Types::Bool
15
+ # [Optional] The key of the variable definition used to calculate the subscore result.
16
+ attribute :calculation_key?, Types::Symbol
17
+ # [Optional argument] The name of the outcome table where this subscore should be shown. Used for cases where scores
18
+ # differ in subscores too much to be shown as one table. By default, all scores end up in the `:main` table.
19
+ attribute :outcome_table, Types::Symbol.default(:main).meta(omittable: true)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'quby/compiler/entities/item'
4
+
5
+ module Quby
6
+ module Compiler
7
+ module Entities
8
+ class Table < Item
9
+ attr_accessor :columns
10
+ attr_accessor :items
11
+
12
+ attr_accessor :title
13
+ attr_accessor :description
14
+
15
+ # Whether the question options in this table should show their descriptions
16
+ attr_accessor :show_option_desc
17
+
18
+ def initialize(options = {})
19
+ @columns = options[:columns]
20
+ @title = options[:title]
21
+ @description = options[:description]
22
+ @show_option_desc = options[:show_option_desc] || false
23
+ @items = []
24
+ end
25
+
26
+ def item_table
27
+ rows
28
+ @item_table
29
+ end
30
+
31
+ # FIXME: code to be ashamed of
32
+ # rubocop:disable CyclomaticComplexity, Metrics/MethodLength
33
+ def rows
34
+ return @rows if @rows
35
+ @item_table = [[]]
36
+ @rows = [[[]]]
37
+ filled_columns = 0
38
+ filled_rows = 0
39
+ row_items = 0
40
+
41
+ skips = []
42
+ items.each do |item|
43
+
44
+ skips.delete_if do |row_span, skip_cols_at, skip_cols_length|
45
+ next true if row_span == 1
46
+ if skip_cols_length > 0 and filled_columns == skip_cols_at
47
+ filled_columns += skip_cols_length
48
+ skip_cols_length.times do
49
+ @item_table[filled_rows] << nil
50
+ @rows[filled_rows][row_items] << nil
51
+ end
52
+ if filled_columns >= columns and item != items.last
53
+ filled_rows += 1
54
+ filled_columns = 0
55
+ row_items = 0
56
+ @rows << [[]]
57
+ @item_table << []
58
+ skips.map! do |new_row_span, new_skip_cols_at, new_skip_cols_length|
59
+ [new_row_span - 1, new_skip_cols_at, new_skip_cols_length]
60
+ end
61
+ end
62
+ if filled_columns != 0 and item != items.last
63
+ row_items += 1
64
+ @rows[filled_rows] << []
65
+ end
66
+ next row_span - 1 == 1
67
+ end
68
+ end
69
+
70
+ if item.is_a?(Text) || !([:check_box, :radio, :scale].include? item.type)
71
+ if item.row_span > 1
72
+ skips << [item.row_span, filled_columns, item.col_span]
73
+ end
74
+
75
+ @item_table[filled_rows] << item
76
+ @rows[filled_rows][row_items] << item
77
+ filled_columns += item.col_span
78
+
79
+ if filled_columns >= columns and item != items.last
80
+ filled_rows += 1
81
+ filled_columns = 0
82
+ row_items = 0
83
+ @rows << [[]]
84
+ @item_table << []
85
+ end
86
+ if filled_columns != 0 and item != items.last
87
+ row_items += 1
88
+ @rows[filled_rows] << []
89
+ end
90
+
91
+ else # is :check_box, :radio or :scale question
92
+ if item.row_span > 1
93
+ skips << [item.row_span, filled_columns, item.options.length]
94
+ end
95
+
96
+ @item_table[filled_rows] << item
97
+ if item.options.length <= columns # multiple questions on one row
98
+ item.options.each do |opt|
99
+ @rows[filled_rows][row_items] << opt
100
+ filled_columns += 1
101
+
102
+ if filled_columns >= columns and item != items.last
103
+ filled_rows += 1
104
+ filled_columns = 0
105
+ row_items = 0
106
+ @rows << [[]]
107
+ @item_table << []
108
+ end
109
+ end
110
+ if filled_columns != 0 and item != items.last
111
+ row_items += 1
112
+ @rows[filled_rows] << []
113
+ end
114
+ else # one question's options split over multiple rows, ordered row wise
115
+ opt_len = item.options.length
116
+ col_len = (opt_len / columns.to_f).ceil
117
+ (0...col_len).each do |j|
118
+ (0...columns).each do |i|
119
+ break if j + i * col_len >= opt_len
120
+ @rows[filled_rows][row_items] << item.options[j + i * col_len]
121
+ filled_columns += 1
122
+ if filled_columns == columns
123
+ filled_rows += 1
124
+ filled_columns = 0
125
+ @rows << [[]]
126
+ @item_table << [item]
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
133
+ @rows
134
+ end
135
+ # rubocop:enable CyclomaticComplexity, Metrics/MethodLength
136
+
137
+ def type
138
+ "table"
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end