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,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