quby-compiler 0.5.14 → 0.5.15

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35bdecff443479812941ad2ced1e6585228fb03881c4ba9c48af3468b48c544f
4
- data.tar.gz: 3cf5faea78f741c241c0f57d873480fcc3b028aa91942ff020e9970eaf332530
3
+ metadata.gz: 126c7a3a5ef150e332a888dfef85acc264a46fe4a886b2f5ef9b0a19f460b14d
4
+ data.tar.gz: 9bc6fad59d9da077cca02ab744bed4497dea3764e09f82d4aaf42016f7479dbc
5
5
  SHA512:
6
- metadata.gz: d0b431d1f495544c5541bc5b2e5ceb57ece1d6d5782cabf6bfa80b9d7bb251a33e9b13764ebc6f0ac726523883eab6ee04585d4e4b102cd10ee419a3466cf1e7
7
- data.tar.gz: 4f22422b8789dbad63cb5cc57550582971ef47c2958b829bae72e9d140081a4378797534c5d5ae7a9888f562f3cfd87ad87bac6cc0c620032610264cc55b46e2
6
+ metadata.gz: 183182375a43d83e98bb4667865c78b958fb2443d8f028ea4411b59d2caaf894825072d764753539811bc659d68a8294c284d4226b7dddfdfb56635049a60a01
7
+ data.tar.gz: '066826a89ee2dd03f8dc8b76b2d284a583331e4e3181823935d9afb3e48f970a8aeeece44ca7b6bc6f479ccd3ee179b47b39a65b26e60fef5489efb8106b41d3'
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ # 0.5.15
2
+
3
+ * Add context_description to questions, to have a text item that is hidden together with the question.
4
+ * quuby2.json
5
+ * Moved quby2 serialization to the serializer, compact everything, no markdown for v2 title/descriptions.
6
+ * Sanitize Quby2 html in v2.
7
+ * Add contextDescription to questions.
8
+
1
9
  # 0.5.14
2
10
 
3
11
  * Support for activemodel 7.1 and 7.2
@@ -27,6 +27,10 @@ module Quby
27
27
  @question.description = value
28
28
  end
29
29
 
30
+ def context_description(value)
31
+ @question.context_description = value
32
+ end
33
+
30
34
  def presentation(value)
31
35
  @question.presentation = value
32
36
  end
@@ -16,10 +16,6 @@ module Quby
16
16
  @items = options[:items] || []
17
17
  end
18
18
 
19
- def as_json(options = {})
20
- super.merge(title: title, index: index, items: json_items)
21
- end
22
-
23
19
  def index
24
20
  @questionnaire.panels.index(self)
25
21
  end
@@ -44,20 +40,6 @@ module Quby
44
40
  end
45
41
  end
46
42
 
47
- def json_items
48
- items.map do |item|
49
- case item
50
- when Text
51
- { type: 'html', html: item.html }
52
- when Question
53
- next if item.table # things inside a table are added to the table, AND ALSO to the panel. skip them.
54
- { type: 'question', key: item.key }
55
- when Table
56
- { type: "table" }
57
- end
58
- end.compact
59
- end
60
-
61
43
  def validations
62
44
  vals = {}
63
45
  items.each do |item|
@@ -6,6 +6,8 @@ module Quby
6
6
  module Compiler
7
7
  module Entities
8
8
  class Question < Item
9
+ include ::Quby::InspectExcept.new(*%i[@dependencies @options @questionnaire @table @validations @parent])
10
+
9
11
  MARKDOWN_ATTRIBUTES = %w(description title).freeze
10
12
 
11
13
  set_callback :after_dsl_enhance, :expand_depends_on_input_keys
@@ -18,6 +20,7 @@ module Quby
18
20
  attr_accessor :title
19
21
  attr_accessor :context_free_title
20
22
  attr_accessor :description
23
+ attr_accessor :context_description
21
24
 
22
25
  attr_accessor :labels
23
26
 
@@ -229,30 +232,6 @@ module Quby
229
232
  options.length > 0 && type != :select ? options.length : @col_span
230
233
  end
231
234
 
232
- def as_json(options = {})
233
- # rubocop:disable SymbolName
234
- super.merge(
235
- key: key,
236
- title: Quby::Compiler::MarkdownParser.new(title).to_html,
237
- description: Quby::Compiler::MarkdownParser.new(description).to_html,
238
- type: type,
239
- size: size.presence && Integer(size), # 2022-11: 4k string and 7k integer
240
- hidden: hidden?,
241
- displayModes: display_modes,
242
- defaultInvisible: default_invisible,
243
- viewSelector: view_selector,
244
- parentKey: parent&.key,
245
- parentOptionKey: parent_option_key,
246
- deselectable: deselectable,
247
- presentation: presentation,
248
- as: as || type, # default to type so typescript can narrow on it.
249
- questionGroup: question_group
250
- ).tap do |json|
251
- json[:unit] = unit if %i[integer float string].include?(type) && as != :slider
252
- json[:showValues] = [true, :all].include?(show_values) if %i[radio scale].include?(type) && show_values
253
- end
254
- end
255
-
256
235
  def title_question?
257
236
  presentation == :next_to_title
258
237
  end
@@ -5,6 +5,7 @@ module Quby
5
5
  module Entities
6
6
  class QuestionOption
7
7
  include ActiveModel::Validations
8
+ include ::Quby::InspectExcept.new(:@question, :@questions)
8
9
 
9
10
  MARKDOWN_ATTRIBUTES = %w(description).freeze
10
11
 
@@ -53,40 +54,6 @@ module Quby
53
54
  @questions.each { |q| return true if q.key_in_use?(k) }
54
55
  false
55
56
  end
56
-
57
- def as_json(options = {})
58
- if inner_title
59
- inner_title_as_json(options)
60
- elsif placeholder
61
- nil # placeholder attr on question.
62
- else
63
- option_as_json(options)
64
- end
65
- end
66
-
67
- def inner_title_as_json(options = {})
68
- {
69
- type: 'html',
70
- key: SecureRandom.uuid,
71
- html: Quby::Compiler::MarkdownParser.new(description).to_html
72
- }
73
- end
74
-
75
- def option_as_json(options = {})
76
- {
77
- type: 'option',
78
- key: key,
79
- value: value,
80
- description: question.type == :select \
81
- ? description
82
- : Quby::Compiler::MarkdownParser.new(description).to_html,
83
- questions: questions,
84
- hidesQuestions: hides_questions,
85
- showsQuestions: shows_questions,
86
- hidden: hidden,
87
- viewId: view_id
88
- }
89
- end
90
57
  end
91
58
  end
92
59
  end
@@ -17,6 +17,7 @@ module Quby
17
17
  class Questionnaire
18
18
  extend ActiveModel::Naming
19
19
  include ActiveModel::Validations
20
+ include ::Quby::InspectExcept.new(*%i[@attributes @charts @fields @flags @panels @questions @sourcecode @score_calculations @score_schemas @textvars])
20
21
 
21
22
  class ValidationError < StandardError; end
22
23
  class UnknownInputKey < ValidationError; end
@@ -58,14 +58,6 @@ module Quby
58
58
  # Some options don't have a key (inner_title), they are stripped.
59
59
  options.map { |opt| opt.input_key }.compact
60
60
  end
61
-
62
- def as_json(options = {})
63
- super.tap do |json|
64
- json[:children] = @options.as_json
65
- json[:checkAllOption] = check_all_option if check_all_option.present?
66
- json[:uncheckAllOption] = uncheck_all_option if uncheck_all_option.present?
67
- end
68
- end
69
61
  end
70
62
  end
71
63
  end
@@ -5,19 +5,5 @@ module Quby::Compiler::Entities::Questions::Concerns
5
5
  included do
6
6
  validates :minimum, :maximum, presence: true, if: -> { as == :slider }
7
7
  end
8
-
9
- def as_json(options = {})
10
- if as == :slider
11
- super.merge(
12
- step: step,
13
- defaultPosition: default_position.is_a?(Numeric) ? default_position : minimum,
14
- startThumbHidden: default_position == :hidden,
15
- valueTooltip: input_data[:value_tooltip] || false,
16
- labels: labels
17
- )
18
- else
19
- super
20
- end
21
- end
22
8
  end
23
9
  end
@@ -53,19 +53,6 @@ module Quby
53
53
  send("#{component}_key").to_sym
54
54
  end
55
55
  end
56
-
57
- def as_json(options = {})
58
- super.merge(
59
- type: 'date_parts',
60
- as: 'date_parts',
61
- dateParts: components.map { |component|
62
- {
63
- part: component,
64
- key: send("#{component}_key")
65
- }
66
- }
67
- )
68
- end
69
56
  end
70
57
  end
71
58
  end
@@ -8,10 +8,6 @@ module Quby
8
8
  def hidden?
9
9
  true
10
10
  end
11
-
12
- def as_json(options = {})
13
- super.merge(options: @options.as_json)
14
- end
15
11
  end
16
12
  end
17
13
  end
@@ -9,13 +9,6 @@ module Quby
9
9
  class FloatQuestion < Question
10
10
  include Concerns::Slider
11
11
 
12
- def as_json(options = {})
13
- super.merge(
14
- minimum: minimum,
15
- maximum: maximum
16
- )
17
- end
18
-
19
12
  def size
20
13
  @size || 30
21
14
  end
@@ -9,13 +9,6 @@ module Quby
9
9
  class IntegerQuestion < Question
10
10
  include Concerns::Slider
11
11
 
12
- def as_json(options = {})
13
- super.merge(
14
- minimum: minimum,
15
- maximum: maximum
16
- )
17
- end
18
-
19
12
  def size
20
13
  @size || 30
21
14
  end
@@ -5,9 +5,6 @@ module Quby
5
5
  module Entities
6
6
  module Questions
7
7
  class RadioQuestion < Question
8
- def as_json(options = {})
9
- super.merge(children: @options.as_json)
10
- end
11
8
  end
12
9
  end
13
10
  end
@@ -5,12 +5,6 @@ module Quby
5
5
  module Entities
6
6
  module Questions
7
7
  class SelectQuestion < Question
8
- def as_json(options = {})
9
- super.merge(
10
- children: @options.as_json.compact, # for now just options, but we'll add optgroups later.
11
- placeholder: @options.find { _1.placeholder }&.description # nil for no placeholder
12
- )
13
- end
14
8
  end
15
9
  end
16
10
  end
@@ -5,12 +5,6 @@ module Quby
5
5
  module Entities
6
6
  module Questions
7
7
  class StringQuestion < Question
8
- def as_json(options = {})
9
- super.merge(autocomplete: @autocomplete)
10
- super.merge(autocomplete: @autocomplete).tap do |json|
11
- json[:setsTextvar] = sets_textvar if sets_textvar
12
- end
13
- end
14
8
  end
15
9
  end
16
10
  end
@@ -5,9 +5,6 @@ module Quby
5
5
  module Entities
6
6
  module Questions
7
7
  class TextQuestion < Question
8
- def as_json(options = {})
9
- super.merge(autocomplete: @autocomplete, lines: lines)
10
- end
11
8
  end
12
9
  end
13
10
  end
@@ -19,12 +19,202 @@ module Quby
19
19
  description: description,
20
20
  shortDescription: short_description,
21
21
  defaultAnswerValue: Services::TransformQuby1ValuesIntoQuby2Values.run!(@questionnaire, default_answer_value),
22
- panels: panels,
23
- questions: fields.question_hash.as_json,
24
- flags: flags,
22
+ panels: panels.map { panel(_1) },
23
+ questions: questions,
25
24
  textvars: textvars,
26
25
  validations: validations,
27
- visibilityRules: visibility_rules
26
+ visibilityRules: visibility_rules.as_json
27
+ }
28
+ end
29
+
30
+ def panel(panel)
31
+ {
32
+ title: panel.title,
33
+ items: panel.items.map { panel_item(_1) }.compact,
34
+ }.compact
35
+ end
36
+
37
+ def panel_item(item)
38
+ case item
39
+ when Quby::Compiler::Entities::Text
40
+ { type: 'html', html: handle_html(item.html, type: :prose, v1_markdown: false) }
41
+ when Quby::Compiler::Entities::Question
42
+ return if item.table # things inside a table are added to the table, AND ALSO to the panel. skip them.
43
+ { type: 'question', key: item.key }
44
+ when Quby::Compiler::Entities::Table
45
+ { type: "table" }
46
+ end
47
+ end
48
+
49
+ def questions
50
+ fields.question_hash \
51
+ .to_h { |k, question| [k, question(question)] } \
52
+ .compact
53
+ end
54
+
55
+ def question(question)
56
+ send(:"#{question_type(question)}_question", question)
57
+ end
58
+
59
+ def check_box_question(question)
60
+ {
61
+ **base_question(question),
62
+ children: children(question),
63
+ checkAllOption: question.check_all_option,
64
+ uncheckAllOption: question.uncheck_all_option,
65
+ }.compact
66
+ end
67
+
68
+ def date_parts_question(question)
69
+ {
70
+ **base_question(question),
71
+ dateParts: question.components.map { |component|
72
+ {
73
+ part: component,
74
+ key: question.send("#{component}_key"),
75
+ }
76
+ },
77
+ }.compact
78
+ end
79
+
80
+ # deprecated
81
+ def hidden_question(question)
82
+ nil
83
+ end
84
+
85
+ def float_question(question)
86
+ integer_question(question)
87
+ end
88
+
89
+ def integer_question(question)
90
+ {
91
+ **base_question(question),
92
+ **slider_question(question),
93
+ minimum: question.minimum,
94
+ maximum: question.maximum,
95
+ size: size(question),
96
+ unit: question.as != :slider && question.unit,
97
+ }.compact
98
+ end
99
+
100
+ def radio_question(question)
101
+ {
102
+ **base_question(question),
103
+ children: children(question),
104
+ showValues: [true, :all].include?(question.show_values),
105
+ }.compact
106
+ end
107
+
108
+ def scale_question(question)
109
+ radio_question(question)
110
+ end
111
+
112
+ def select_question(question)
113
+ {
114
+ **base_question(question),
115
+ children: children(question),
116
+ placeholder: question.options.find { _1.placeholder }&.description,
117
+ }.compact
118
+ end
119
+
120
+ def string_question(question)
121
+ {
122
+ **base_question(question),
123
+ setsTextvar: question.sets_textvar,
124
+ size: size(question),
125
+ unit: question.as != :slider && question.unit,
126
+ }.compact
127
+ end
128
+
129
+ def textarea_question(question)
130
+ {
131
+ **base_question(question),
132
+ autocomplete: question.autocomplete,
133
+ lines: question.lines,
134
+ }.compact
135
+ end
136
+
137
+ def base_question(question)
138
+ {
139
+ key: question.key,
140
+ title: handle_html(question.title),
141
+ description: handle_html(question.description, type: :question_description),
142
+ contextDescription: handle_html(question.context_description, type: :prose, v1_markdown: false),
143
+ type: question_type(question),
144
+ hidden: question.hidden?,
145
+ displayModes: question.display_modes,
146
+ viewSelector: question.view_selector,
147
+ parentKey: question.parent&.key,
148
+ parentOptionKey: question.parent_option_key,
149
+ deselectable: question.deselectable,
150
+ presentation: question.presentation,
151
+ as: question.as || question_type(question), # default to type so typescript can narrow on it.
152
+ questionGroup: question.question_group,
153
+ }
154
+ end
155
+
156
+ def slider_question(question)
157
+ return {} unless question.as == :slider
158
+
159
+ {
160
+ step: question.step,
161
+ defaultPosition: question.default_position.is_a?(Numeric) ? question.default_position : question.minimum,
162
+ startThumbHidden: question.default_position == :hidden,
163
+ valueTooltip: question.input_data[:value_tooltip] || false,
164
+ labels: question.labels,
165
+ }
166
+ end
167
+
168
+ def question_type(question)
169
+ {
170
+ date: 'date_parts',
171
+ }[question.type] || question.type
172
+ end
173
+
174
+ def size(question)
175
+ question.size.presence && Integer(question.size) # 2022-11: 4k string and 7k integer
176
+ end
177
+
178
+ def children(question)
179
+ question.options.map { |child|
180
+ if child.inner_title
181
+ inner_title_as_json(child)
182
+ elsif child.placeholder
183
+ nil # placeholder attr on question.
184
+ else
185
+ option_as_json(child)
186
+ end
187
+ }.compact
188
+ end
189
+
190
+ def inner_title_as_json(option)
191
+ {
192
+ type: 'html',
193
+ key: SecureRandom.uuid,
194
+ html: handle_html(option.description)
195
+ }
196
+ end
197
+
198
+ def option_as_json(option)
199
+ {
200
+ type: 'option',
201
+ key: option.key,
202
+ value: option.question.type != :check_box && option.value,
203
+ description: option.question.type == :select ? option.description : handle_html(option.description),
204
+ questions: option.question.type != :select && option.questions.map{ question(_1) },
205
+ viewId: option.view_id
206
+ }.compact
207
+ end
208
+
209
+ def textvars
210
+ @questionnaire.textvars.to_h { |key, textvar|
211
+ [
212
+ key,
213
+ {
214
+ key: textvar.key,
215
+ default: textvar.default,
216
+ }
217
+ ]
28
218
  }
29
219
  end
30
220
 
@@ -40,6 +230,27 @@ module Quby
40
230
  end.as_json
41
231
  end
42
232
  end
233
+
234
+ def handle_html(html, type: :simple, v1_markdown: true)
235
+ if layout_version == :v2
236
+ case type
237
+ when :simple
238
+ html_sanitizer.sanitize(html, tags: %w[strong em sup sub br span], attributes: %w[class])
239
+ when :question_description
240
+ html_sanitizer.sanitize(html, tags: %w[strong em b i u sup sub p span br ul ol li], attributes: %w[class])
241
+ when :prose
242
+ html_sanitizer.sanitize(html, tags: %w[strong em b i u sup sub pre blockquote p span br ul ol li a h1 h2 h3 h4], attributes: %w[href class target])
243
+ end
244
+ elsif v1_markdown
245
+ Quby::Compiler::MarkdownParser.new(html).to_html
246
+ else
247
+ html
248
+ end
249
+ end
250
+
251
+ def html_sanitizer
252
+ @html_sanitize ||= Rails::HTML5::SafeListSanitizer.new
253
+ end
43
254
  end
44
255
  end
45
256
  end
@@ -1,5 +1,5 @@
1
1
  module Quby
2
2
  module Compiler
3
- VERSION = "0.5.14"
3
+ VERSION = "0.5.15"
4
4
  end
5
5
  end
data/lib/quby/compiler.rb CHANGED
@@ -15,6 +15,7 @@ module Quby
15
15
  end
16
16
  end
17
17
 
18
+ require 'quby/inspect_except'
18
19
  require 'quby/compiler/markdown_parser'
19
20
  require 'quby/range_categories'
20
21
  require 'quby/compiler/type_validator'
@@ -0,0 +1,12 @@
1
+ class Quby::InspectExcept < Module
2
+ def initialize(*excepts)
3
+ define_method :inspect do
4
+ prefix = "#<#{self.class}:0x#{self.__id__.to_s(16)}"
5
+
6
+ parts = (instance_variables - excepts).map do |var|
7
+ "#{var}=#{instance_variable_get(var).inspect}"
8
+ end
9
+ "#{prefix}\n #{parts.join(", ")}>"
10
+ end
11
+ end
12
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quby-compiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.14
4
+ version: 0.5.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marten Veldthuis
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-02-13 00:00:00.000000000 Z
10
+ date: 2025-02-27 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activemodel
@@ -220,6 +220,7 @@ files:
220
220
  - lib/quby/compiler/services/transform_quby1_values_into_quby2_values.rb
221
221
  - lib/quby/compiler/type_validator.rb
222
222
  - lib/quby/compiler/version.rb
223
+ - lib/quby/inspect_except.rb
223
224
  - lib/quby/range_categories.rb
224
225
  - lib/quby/settings.rb
225
226
  - lib/quby/text_transformation.rb