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,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'quby/compiler/entities'
4
+
5
+ module Quby
6
+ module Compiler
7
+ module DSL
8
+ class ChartBuilder
9
+ # rubocop:disable AccessorMethodName
10
+ def self.set_chart_class(chart_class)
11
+ @chart_class = chart_class
12
+ end
13
+ # rubocop:enable AccessorMethodName
14
+
15
+ def self.chart_class
16
+ @chart_class
17
+ end
18
+
19
+ def initialize(questionnaire, key, options = {})
20
+ @chart = self.class.chart_class.new(key, options)
21
+ @questionnaire = questionnaire
22
+ end
23
+
24
+ def title(title)
25
+ @chart.title = title
26
+ end
27
+
28
+ def range(range)
29
+ @chart.y_range = range
30
+ end
31
+
32
+ def tick_interval(tick_interval)
33
+ @chart.tick_interval = tick_interval
34
+ end
35
+
36
+ def y_categories(y_categories)
37
+ @chart.y_categories = y_categories
38
+ end
39
+
40
+ def y_range_categories(*y_range_categories)
41
+ @chart.y_range_categories = RangeCategories.new(*y_range_categories).as_range_hash
42
+ end
43
+
44
+ def plotband(from, to, color)
45
+ @chart.plotbands << {from: from, to: to, color: color}
46
+ end
47
+
48
+ def plotline(value, color)
49
+ @chart.plotlines << {value: value, color: color}
50
+ end
51
+
52
+ def plot(key, options = {})
53
+ unless plottable = @questionnaire.find_plottable(key)
54
+ fail "Questionnaire #{@questionnaire.key} chart #{@chart.key} references unknown score or question #{key}"
55
+ end
56
+
57
+ configure_options plottable, options
58
+
59
+ @chart.plottables << Entities::Charting::Plottable.new(plottable.key, options)
60
+ end
61
+
62
+ def chart_type(type)
63
+ @chart.chart_type = type
64
+ end
65
+
66
+ def build(&block)
67
+ instance_eval(&block)
68
+ validate!
69
+ @chart
70
+ end
71
+
72
+ def validate!
73
+ if @chart.y_categories.present? && @chart.y_range.present? &&
74
+ @chart.y_range != (0..(@chart.y_categories.count - 1))
75
+ fail ArgumentError, 'Y_categories size and range do not match'
76
+ end
77
+
78
+ true
79
+ end
80
+
81
+ private
82
+
83
+ def configure_options(plottable, options)
84
+ case plottable
85
+ when Entities::ScoreCalculation
86
+ options.reverse_merge! plottable.options
87
+ when Entities::Question
88
+ options[:label] ||= plottable.title
89
+ end
90
+ options[:questionnaire_key] = @questionnaire.key
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'quby/compiler/entities/charting/line_chart'
4
+ require_relative 'chart_builder'
5
+
6
+ module Quby
7
+ module Compiler
8
+ module DSL
9
+ class LineChartBuilder < ChartBuilder
10
+ set_chart_class(Entities::Charting::LineChart)
11
+
12
+ def y_axis_label(label)
13
+ @chart.y_label = label
14
+ end
15
+
16
+ def tonality(value)
17
+ @chart.tonality = value
18
+ end
19
+
20
+ def baseline(value)
21
+ @chart.baseline = value
22
+ end
23
+
24
+ def clinically_relevant_change(value)
25
+ @chart.clinically_relevant_change = value
26
+ end
27
+
28
+ def validate!
29
+ fail "Chart #{@chart.key} has no range specified" unless @chart.y_range
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ require 'quby/compiler/entities/charting/overview_chart'
2
+ require_relative 'chart_builder'
3
+
4
+ module Quby
5
+ module Compiler
6
+ module DSL
7
+ class OverviewChartBuilder < ChartBuilder
8
+ set_chart_class(Entities::Charting::OverviewChart)
9
+
10
+ def initialize(questionnaire, options = {})
11
+ @questionnaire = questionnaire
12
+ @chart = self.class.chart_class.new
13
+ end
14
+
15
+ def subscore(key)
16
+ @chart.subscore = key
17
+ end
18
+
19
+ def y_max(value)
20
+ @chart.y_max = value
21
+ end
22
+
23
+ def validate!
24
+ fail ArgumentError, "subscore not specified" unless @chart.subscore.present?
25
+ fail ArgumentError, "y_max not specified" unless @chart.y_max.present?
26
+ true
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'quby/compiler/entities/charting/radar_chart'
4
+ require_relative 'chart_builder'
5
+
6
+ module Quby
7
+ module Compiler
8
+ module DSL
9
+ class RadarChartBuilder < ChartBuilder
10
+ set_chart_class(Entities::Charting::RadarChart)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quby
4
+ module Compiler
5
+ module DSL
6
+ module Helpers
7
+ include ActionView::Helpers::TagHelper
8
+ include ActionView::Helpers::OutputSafetyHelper
9
+
10
+ def image_tag(source, options = {})
11
+ tag.img(options.reverse_merge(src: source, alt: image_alt(source)))
12
+ end
13
+
14
+ # Copied from ActionController::Base.helpers.image_alt, because it will be removed from Rails 6.0, but we want
15
+ # to keep using this functionality
16
+ def image_alt(source)
17
+ File.basename(source, ".*").sub(/-[[:xdigit:]]{32,64}\z/, "").tr("-_", " ").capitalize
18
+ end
19
+
20
+ def check_question_keys_uniqueness(key, options, questionnaire)
21
+ keys = QuestionBuilder.build(key, options).claimed_keys
22
+ if keys.any? { |k| questionnaire.key_in_use? k }
23
+ fail "#{questionnaire.key}:#{key}: A question or option with input key #{key} is already defined."
24
+ end
25
+ end
26
+
27
+ def video_tag(*urls, poster: nil, autoplay: false, loop: false)
28
+ # assume the file extension is the video format
29
+ # otherwise, the host's declared mime type is used to figure out compatibility
30
+ # which is usually wrong
31
+ sources = urls.map { |url| [url, url.match(/[^\.]*\z/)&.[](0).downcase] }
32
+ sources.each { |_url, ext| raise 'unknown video url file extension' unless %w[mp4 webm].include?(ext) }
33
+
34
+ options = {
35
+ width: '100%',
36
+ preload: 'none',
37
+ poster: poster,
38
+ loop: loop,
39
+ autoplay: autoplay,
40
+ muted: autoplay,
41
+ controls: ''
42
+ }
43
+
44
+ content = sources.map { |url, ext| tag.source src: url, type: "video/#{ext}" }
45
+ .append(I18n.t('video_not_supported'))
46
+ .yield_self(&tag.method(:safe_join))
47
+
48
+ content_tag(:video, content, options)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'quby/compiler/entities'
4
+
5
+ module Quby
6
+ module Compiler
7
+ module DSL
8
+ class PanelBuilder < Base
9
+ attr_reader :title
10
+ attr_reader :questionnaire
11
+
12
+ def initialize(title, options = {})
13
+ @panel = Entities::Panel.new(options.merge(title: title, items: []))
14
+ @default_question_options = options[:default_question_options] || {}
15
+ @questionnaire = options[:questionnaire]
16
+ @custom_methods = options[:custom_methods] || {}
17
+ end
18
+
19
+ def build
20
+ @panel
21
+ end
22
+
23
+ def title(value)
24
+ @panel.title = value
25
+ end
26
+
27
+ def text(value, options = {})
28
+ @panel.items << Entities::Text.new(value.to_s, options)
29
+ end
30
+
31
+ def html(value)
32
+ @panel.items << Entities::Text.new('', html_content: value.to_s)
33
+ end
34
+
35
+ def raw_html(value)
36
+ @panel.items << Entities::Text.new('', raw_content: value.to_s)
37
+ end
38
+
39
+ def video(*urls, **options)
40
+ video_html = video_tag *urls, **options
41
+ @panel.items << Entities::Text.new('', raw_content: video_html)
42
+ end
43
+
44
+ def default_question_options(options = {})
45
+ @default_question_options = @default_question_options.merge(options)
46
+ end
47
+
48
+ def question(key, options = {}, &block)
49
+ options = @default_question_options.merge(options).merge(questionnaire: @panel.questionnaire)
50
+
51
+ check_question_keys_uniqueness key, options, @questionnaire
52
+
53
+ question = QuestionBuilder.build(key, options, &block)
54
+
55
+ @questionnaire.register_question(question)
56
+ @panel.items << question
57
+ end
58
+
59
+ def table(options = {}, &block)
60
+ table_builder = TableBuilder.new(@panel, options.merge(questionnaire: @panel.questionnaire,
61
+ default_question_options: @default_question_options,
62
+ custom_methods: @custom_methods))
63
+ table_builder.instance_eval(&block) if block
64
+ end
65
+
66
+ def method_missing(method_sym, *args, &block)
67
+ if @custom_methods.key? method_sym
68
+ instance_exec(*args, &@custom_methods[method_sym])
69
+ else
70
+ super
71
+ end
72
+ end
73
+
74
+ def respond_to_missing?(method_name, include_private = false)
75
+ @custom_methods.key?(method_name) || super
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'quby/compiler/entities'
4
+
5
+ require 'quby/compiler/dsl/questions/base'
6
+ require 'quby/compiler/dsl/questions/checkbox_question_builder'
7
+ require 'quby/compiler/dsl/questions/date_question_builder'
8
+ require 'quby/compiler/dsl/questions/deprecated_question_builder'
9
+ require 'quby/compiler/dsl/questions/float_question_builder'
10
+ require 'quby/compiler/dsl/questions/integer_question_builder'
11
+ require 'quby/compiler/dsl/questions/radio_question_builder'
12
+ require 'quby/compiler/dsl/questions/select_question_builder'
13
+ require 'quby/compiler/dsl/questions/string_question_builder'
14
+ require 'quby/compiler/dsl/questions/text_question_builder'
15
+
16
+ module Quby
17
+ module Compiler
18
+ module DSL
19
+ module QuestionBuilder
20
+ include Helpers
21
+ BUILDERS = {
22
+ 'string' => Questions::StringQuestionBuilder,
23
+ 'textarea' => Questions::TextQuestionBuilder,
24
+ 'integer' => Questions::IntegerQuestionBuilder,
25
+ 'float' => Questions::FloatQuestionBuilder,
26
+ 'radio' => Questions::RadioQuestionBuilder,
27
+ 'scale' => Questions::RadioQuestionBuilder,
28
+ 'select' => Questions::SelectQuestionBuilder,
29
+ 'check_box' => Questions::CheckboxQuestionBuilder,
30
+ 'date' => Questions::DateQuestionBuilder,
31
+ 'hidden' => Questions::DeprecatedQuestionBuilder
32
+ }
33
+
34
+ def self.build(key, options = {}, &block)
35
+ BUILDERS.fetch(options.fetch(:type).to_s).build(key, options, &block)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,279 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'quby/compiler/dsl/panel_builder'
4
+ require 'quby/compiler/dsl/table_builder'
5
+ require 'quby/compiler/dsl/question_builder'
6
+ require 'quby/compiler/dsl/score_builder'
7
+ require 'quby/compiler/dsl/charting/line_chart_builder'
8
+ require 'quby/compiler/dsl/charting/radar_chart_builder'
9
+ require 'quby/compiler/dsl/charting/bar_chart_builder'
10
+ require 'quby/compiler/dsl/charting/overview_chart_builder'
11
+
12
+ require_relative 'standardized_panel_generators'
13
+
14
+ module Quby
15
+ module Compiler
16
+ module DSL
17
+ class QuestionnaireBuilder
18
+ prepend CallsCustomMethods
19
+ include StandardizedPanelGenerators
20
+ include Helpers
21
+
22
+ def initialize(target_instance, lookup_tables: nil)
23
+ @questionnaire = target_instance
24
+ @lookup_tables = lookup_tables
25
+ @default_question_options = {}
26
+ @custom_methods = {}
27
+ end
28
+
29
+ def leave_page_alert(text)
30
+ @questionnaire.leave_page_alert = text
31
+ end
32
+
33
+ def key(key)
34
+ # no-op, key is now passed in to Questionnaire constructor
35
+ end
36
+
37
+ def roqua_keys(*keys)
38
+ @questionnaire.roqua_keys = keys
39
+ end
40
+
41
+ def seeds_patch(value)
42
+ @questionnaire.seeds_patch = value
43
+ end
44
+
45
+ def do_not_check_key_clashes
46
+ @questionnaire.check_key_clashes = false
47
+ end
48
+
49
+ def do_not_check_score_keys_consistency
50
+ @questionnaire.check_score_keys_consistency = false
51
+ end
52
+
53
+ def do_not_validate_html
54
+ @questionnaire.validate_html = false
55
+ end
56
+
57
+ def title(title)
58
+ @questionnaire.title = title
59
+ end
60
+
61
+ def description(description)
62
+ @questionnaire.description = description
63
+ end
64
+
65
+ def outcome_description(description)
66
+ @questionnaire.outcome_description = description
67
+ end
68
+
69
+ def short_description(description)
70
+ @questionnaire.short_description = description
71
+ end
72
+
73
+ def sbg_key(sbg_key)
74
+ @questionnaire.sbg_key = sbg_key
75
+ end
76
+
77
+ def sbg_domain(sbg_code, outcome:, from: nil, till: nil, sbg_key: nil, primary: false)
78
+ @questionnaire.sbg_domains << {
79
+ sbg_code: sbg_code,
80
+ from: from,
81
+ till: till,
82
+ outcome: outcome,
83
+ sbg_key: sbg_key || @questionnaire.sbg_key,
84
+ primary: primary
85
+ }
86
+ end
87
+
88
+ def abortable
89
+ @questionnaire.abortable = true
90
+ end
91
+
92
+ def allow_hotkeys(type = :all)
93
+ @questionnaire.allow_hotkeys = type
94
+ end
95
+
96
+ def license(type, licensor: nil)
97
+ @questionnaire.license = type
98
+ @questionnaire.licensor = licensor
99
+ end
100
+
101
+ def language(language)
102
+ @questionnaire.language = language
103
+ end
104
+
105
+ def respondent_types(*respondent_types)
106
+ @questionnaire.respondent_types = respondent_types
107
+ end
108
+
109
+ def tags(*tags)
110
+ @questionnaire.tags = tags
111
+ end
112
+
113
+ def version(number, release_notes:, regenerate_outcome: false, deactivate_answers: false)
114
+ @questionnaire.versions << Entities::Version.new(
115
+ number: number,
116
+ release_notes: release_notes,
117
+ regenerate_outcome: regenerate_outcome,
118
+ deactivate_answers: deactivate_answers
119
+ )
120
+ end
121
+
122
+ def outcome_regeneration_requested_at(timestamp)
123
+ @questionnaire.outcome_regeneration_requested_at = timestamp
124
+ end
125
+
126
+ def deactivate_answers_requested_at(timestamp)
127
+ @questionnaire.deactivate_answers_requested_at = timestamp
128
+ end
129
+
130
+ def enable_previous_questionnaire_button
131
+ @questionnaire.enable_previous_questionnaire_button = true
132
+ end
133
+
134
+ def renderer_version(version)
135
+ @questionnaire.renderer_version = version
136
+ end
137
+
138
+ def css(value)
139
+ @questionnaire.extra_css += value
140
+ end
141
+
142
+ def allow_switch_to_bulk(value=true)
143
+ @questionnaire.allow_switch_to_bulk = value
144
+ end
145
+
146
+ def default_answer_value(value)
147
+ @questionnaire.default_answer_value = value
148
+ end
149
+
150
+ def panel(title = nil, options = {}, &block)
151
+ panel = PanelBuilder.build(title, options.merge(default_panel_options), &block)
152
+ @questionnaire.add_panel(panel)
153
+ end
154
+
155
+ def custom_method(key, &block)
156
+ if PanelBuilder.new(nil, custom_methods: @custom_methods).respond_to? key
157
+ fail 'Custom method trying to override existing method'
158
+ end
159
+ @custom_methods[key] = block
160
+ end
161
+
162
+ def import_lookup_tree(key)
163
+ @questionnaire.lookup_tables[key] = @lookup_tables.fetch(key)
164
+ end
165
+
166
+ def add_lookup_tree(key, levels:, tree:)
167
+ @questionnaire.lookup_tables[key] = {levels: levels, tree: tree}
168
+ end
169
+
170
+ def default_question_options(options = {})
171
+ @default_question_options.merge!(options)
172
+ end
173
+
174
+ # Short-circuit the question command to perform an implicit panel
175
+ def question(key, options = {}, &block)
176
+ panel(nil, default_panel_options) do
177
+ question(key, @default_question_options.merge(options).merge(questionnaire: @questionnaire), &block)
178
+ end
179
+ end
180
+
181
+ # Short-circuit the text command to perform an implicit panel
182
+ def text(value, options = {})
183
+ panel(nil, default_panel_options) do
184
+ text(value, options)
185
+ end
186
+ end
187
+
188
+ # Short-circuit the table command to perform an implicit panel
189
+ def table(options = {}, &block)
190
+ panel(nil, default_panel_options) do
191
+ table(options, &block)
192
+ end
193
+ end
194
+
195
+ # variable :totaal do
196
+ # # Plain old Ruby code here, executed in the scope of the answer
197
+ # # variables are private to the score calculation
198
+ # q01 + q02 + q03
199
+ # end
200
+ def variable(key, options = {}, &block)
201
+ @questionnaire.add_score_calculation ScoreBuilder.new(key, options, &block).build
202
+ end
203
+
204
+ def score(key, options = {}, &block)
205
+ @questionnaire.errors.add "Score #{key}", 'misses label in score call' if options[:label].blank?
206
+ schema = options.delete(:schema)
207
+ score_schema(key, options[:label], schema) if schema.present?
208
+ variable(key, options.reverse_merge(score: true), &block)
209
+ end
210
+
211
+ def score_schema(key, label, options = nil, &block)
212
+ if block
213
+ schema, calculations = ScoreSchemaBuilder.build(key: key, label: label, &block)
214
+ @questionnaire.add_score_schema schema
215
+ calculations.each do |calculation|
216
+ @questionnaire.add_score_calculation calculation
217
+ end
218
+ else
219
+ @questionnaire.add_score_schema Entities::ScoreSchema.new key: key,
220
+ label: label,
221
+ subscore_schemas: options
222
+ end
223
+ end
224
+
225
+ def attention(options = {}, &block)
226
+ variable(:attention, options.reverse_merge(action: true), &block)
227
+ end
228
+
229
+ def alarm(options = {}, &block)
230
+ variable(:alarm, options.reverse_merge(action: true), &block)
231
+ end
232
+
233
+ def completion(options = {}, &block)
234
+ variable(:completion, options.reverse_merge(completion: true), &block)
235
+ end
236
+
237
+ def overview_chart(*args, &block)
238
+ raise "Cannot define more than one overview chart" if @questionnaire.charts.overview.present?
239
+ builder = OverviewChartBuilder.new(@questionnaire, *args)
240
+ @questionnaire.charts.overview = builder.build(&block)
241
+ end
242
+
243
+ def line_chart(*args, &block)
244
+ builder = LineChartBuilder.new(@questionnaire, *args)
245
+ @questionnaire.add_chart(builder.build(&block))
246
+ end
247
+
248
+ def bar_chart(*args, &block)
249
+ builder = BarChartBuilder.new(@questionnaire, *args)
250
+ @questionnaire.add_chart(builder.build(&block))
251
+ end
252
+
253
+ def radar_chart(*args, &block)
254
+ builder = RadarChartBuilder.new(@questionnaire, *args)
255
+ @questionnaire.add_chart(builder.build(&block))
256
+ end
257
+
258
+ def flag(flag_options)
259
+ @questionnaire.add_flag flag_options
260
+ end
261
+
262
+ def textvar(textvar_options)
263
+ @questionnaire.add_textvar textvar_options
264
+ end
265
+
266
+ def outcome_table(table_options)
267
+ @questionnaire.add_outcome_table table_options
268
+ end
269
+
270
+ private
271
+
272
+ def default_panel_options
273
+ {questionnaire: @questionnaire, default_question_options: @default_question_options,
274
+ custom_methods: @custom_methods}
275
+ end
276
+ end
277
+ end
278
+ end
279
+ end