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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.gitlab-ci.yml +5 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Dockerfile +11 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +133 -0
- data/LICENSE.txt +21 -0
- data/README.md +44 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/rspec +29 -0
- data/bin/setup +8 -0
- data/config/locales/de.yml +58 -0
- data/config/locales/en.yml +57 -0
- data/config/locales/nl.yml +57 -0
- data/config/locales/rails-i18n/README.md +4 -0
- data/config/locales/rails-i18n/de.yml +223 -0
- data/config/locales/rails-i18n/en.yml +216 -0
- data/config/locales/rails-i18n/nl.yml +214 -0
- data/exe/quby-compile +56 -0
- data/lib/quby/array_attribute_valid_validator.rb +15 -0
- data/lib/quby/attribute_valid_validator.rb +14 -0
- data/lib/quby/compiler.rb +50 -0
- data/lib/quby/compiler/dsl.rb +29 -0
- data/lib/quby/compiler/dsl/base.rb +20 -0
- data/lib/quby/compiler/dsl/calls_custom_methods.rb +29 -0
- data/lib/quby/compiler/dsl/charting/bar_chart_builder.rb +14 -0
- data/lib/quby/compiler/dsl/charting/chart_builder.rb +95 -0
- data/lib/quby/compiler/dsl/charting/line_chart_builder.rb +34 -0
- data/lib/quby/compiler/dsl/charting/overview_chart_builder.rb +31 -0
- data/lib/quby/compiler/dsl/charting/radar_chart_builder.rb +14 -0
- data/lib/quby/compiler/dsl/helpers.rb +53 -0
- data/lib/quby/compiler/dsl/panel_builder.rb +80 -0
- data/lib/quby/compiler/dsl/question_builder.rb +40 -0
- data/lib/quby/compiler/dsl/questionnaire_builder.rb +279 -0
- data/lib/quby/compiler/dsl/questions/base.rb +180 -0
- data/lib/quby/compiler/dsl/questions/checkbox_question_builder.rb +20 -0
- data/lib/quby/compiler/dsl/questions/date_question_builder.rb +18 -0
- data/lib/quby/compiler/dsl/questions/deprecated_question_builder.rb +18 -0
- data/lib/quby/compiler/dsl/questions/float_question_builder.rb +21 -0
- data/lib/quby/compiler/dsl/questions/integer_question_builder.rb +21 -0
- data/lib/quby/compiler/dsl/questions/radio_question_builder.rb +20 -0
- data/lib/quby/compiler/dsl/questions/select_question_builder.rb +18 -0
- data/lib/quby/compiler/dsl/questions/string_question_builder.rb +20 -0
- data/lib/quby/compiler/dsl/questions/text_question_builder.rb +22 -0
- data/lib/quby/compiler/dsl/score_builder.rb +22 -0
- data/lib/quby/compiler/dsl/score_schema_builder.rb +53 -0
- data/lib/quby/compiler/dsl/standardized_panel_generators.rb +33 -0
- data/lib/quby/compiler/dsl/table_builder.rb +48 -0
- data/lib/quby/compiler/entities.rb +38 -0
- data/lib/quby/compiler/entities/charting/bar_chart.rb +17 -0
- data/lib/quby/compiler/entities/charting/chart.rb +101 -0
- data/lib/quby/compiler/entities/charting/charts.rb +42 -0
- data/lib/quby/compiler/entities/charting/line_chart.rb +38 -0
- data/lib/quby/compiler/entities/charting/overview_chart.rb +20 -0
- data/lib/quby/compiler/entities/charting/plottable.rb +20 -0
- data/lib/quby/compiler/entities/charting/radar_chart.rb +17 -0
- data/lib/quby/compiler/entities/definition.rb +26 -0
- data/lib/quby/compiler/entities/fields.rb +119 -0
- data/lib/quby/compiler/entities/flag.rb +55 -0
- data/lib/quby/compiler/entities/item.rb +40 -0
- data/lib/quby/compiler/entities/lookup_tables.rb +71 -0
- data/lib/quby/compiler/entities/outcome_table.rb +31 -0
- data/lib/quby/compiler/entities/panel.rb +82 -0
- data/lib/quby/compiler/entities/question.rb +365 -0
- data/lib/quby/compiler/entities/question_option.rb +96 -0
- data/lib/quby/compiler/entities/questionnaire.rb +440 -0
- data/lib/quby/compiler/entities/questions/checkbox_question.rb +82 -0
- data/lib/quby/compiler/entities/questions/date_question.rb +84 -0
- data/lib/quby/compiler/entities/questions/deprecated_question.rb +19 -0
- data/lib/quby/compiler/entities/questions/float_question.rb +15 -0
- data/lib/quby/compiler/entities/questions/integer_question.rb +15 -0
- data/lib/quby/compiler/entities/questions/radio_question.rb +19 -0
- data/lib/quby/compiler/entities/questions/select_question.rb +19 -0
- data/lib/quby/compiler/entities/questions/string_question.rb +15 -0
- data/lib/quby/compiler/entities/questions/text_question.rb +15 -0
- data/lib/quby/compiler/entities/score_calculation.rb +35 -0
- data/lib/quby/compiler/entities/score_schema.rb +25 -0
- data/lib/quby/compiler/entities/subscore_schema.rb +23 -0
- data/lib/quby/compiler/entities/table.rb +143 -0
- data/lib/quby/compiler/entities/text.rb +71 -0
- data/lib/quby/compiler/entities/textvar.rb +23 -0
- data/lib/quby/compiler/entities/validation.rb +17 -0
- data/lib/quby/compiler/entities/version.rb +23 -0
- data/lib/quby/compiler/entities/visibility_rule.rb +71 -0
- data/lib/quby/compiler/instance.rb +72 -0
- data/lib/quby/compiler/output.rb +13 -0
- data/lib/quby/compiler/outputs.rb +4 -0
- data/lib/quby/compiler/outputs/quby_frontend_v1_serializer.rb +362 -0
- data/lib/quby/compiler/outputs/quby_frontend_v2_serializer.rb +15 -0
- data/lib/quby/compiler/outputs/roqua_serializer.rb +108 -0
- data/lib/quby/compiler/outputs/seed_serializer.rb +34 -0
- data/lib/quby/compiler/services/definition_validator.rb +330 -0
- data/lib/quby/compiler/services/quby_proxy.rb +405 -0
- data/lib/quby/compiler/services/seed_diff.rb +116 -0
- data/lib/quby/compiler/services/text_transformation.rb +30 -0
- data/lib/quby/compiler/version.rb +5 -0
- data/lib/quby/markdown_parser.rb +38 -0
- data/lib/quby/range_categories.rb +38 -0
- data/lib/quby/settings.rb +86 -0
- data/lib/quby/text_transformation.rb +26 -0
- data/lib/quby/type_validator.rb +12 -0
- data/quby-compiler.gemspec +39 -0
- 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,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
|