quby-compiler 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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