asker-tool 2.6.0 → 2.7.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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -5
  3. data/lib/asker/ai/ai.rb +6 -6
  4. data/lib/asker/ai/ai_calculate.rb +3 -3
  5. data/lib/asker/ai/code/base_code_ai.rb +5 -30
  6. data/lib/asker/ai/code/code_ai_factory.rb +6 -12
  7. data/lib/asker/ai/code/javascript_code_ai.rb +33 -34
  8. data/lib/asker/ai/code/python_code_ai.rb +35 -36
  9. data/lib/asker/ai/code/ruby_code_ai.rb +33 -33
  10. data/lib/asker/ai/code/sql_code_ai.rb +20 -21
  11. data/lib/asker/ai/concept_ai.rb +12 -22
  12. data/lib/asker/ai/problem/problem_ai.rb +226 -0
  13. data/lib/asker/ai/question.rb +34 -45
  14. data/lib/asker/ai/stages/base_stage.rb +7 -7
  15. data/lib/asker/ai/stages/stage_b.rb +62 -28
  16. data/lib/asker/ai/stages/stage_d.rb +10 -10
  17. data/lib/asker/ai/stages/stage_f.rb +17 -17
  18. data/lib/asker/ai/stages/stage_i.rb +8 -18
  19. data/lib/asker/ai/stages/stage_s.rb +28 -26
  20. data/lib/asker/ai/stages/stage_t.rb +40 -51
  21. data/lib/asker/application.rb +15 -14
  22. data/lib/asker/check_input/check_haml_data.rb +52 -51
  23. data/lib/asker/check_input/check_table.rb +17 -20
  24. data/lib/asker/check_input.rb +10 -23
  25. data/lib/asker/cli.rb +43 -24
  26. data/lib/asker/data/code.rb +10 -9
  27. data/lib/asker/data/column.rb +21 -17
  28. data/lib/asker/data/concept.rb +24 -37
  29. data/lib/asker/data/data_field.rb +2 -2
  30. data/lib/asker/data/problem.rb +112 -0
  31. data/lib/asker/data/project_data.rb +11 -15
  32. data/lib/asker/data/row.rb +25 -23
  33. data/lib/asker/data/table.rb +25 -46
  34. data/lib/asker/data/template.rb +7 -7
  35. data/lib/asker/data/world.rb +3 -3
  36. data/lib/asker/{formatter → deprecated}/question_moodlexml_formatter.rb +19 -21
  37. data/lib/asker/displayer/code_displayer.rb +10 -10
  38. data/lib/asker/displayer/concept_ai_displayer.erb +1 -1
  39. data/lib/asker/displayer/concept_ai_displayer.rb +17 -17
  40. data/lib/asker/displayer/concept_displayer.rb +4 -2
  41. data/lib/asker/displayer/problem_displayer.rb +45 -0
  42. data/lib/asker/displayer/stats_displayer.rb +7 -12
  43. data/lib/asker/exporter/code_gift_exporter.rb +2 -2
  44. data/lib/asker/exporter/concept_ai_gift_exporter.rb +4 -4
  45. data/lib/asker/exporter/concept_ai_yaml_exporter.rb +7 -7
  46. data/lib/asker/exporter/concept_doc_exporter.rb +5 -5
  47. data/lib/asker/exporter/data_gift_exporter.rb +14 -15
  48. data/lib/asker/exporter/data_moodle_exporter.rb +51 -20
  49. data/lib/asker/exporter/output_file_exporter.rb +9 -8
  50. data/lib/asker/exporter/problem_gift_exporter.rb +30 -0
  51. data/lib/asker/files/language/ca/templates.yaml +6 -0
  52. data/lib/asker/files/language/du/templates.yaml +6 -0
  53. data/lib/asker/files/language/en/templates.yaml +7 -1
  54. data/lib/asker/files/language/es/templates.yaml +6 -0
  55. data/lib/asker/files/language/fr/templates.yaml +6 -0
  56. data/lib/asker/formatter/code_string_formatter.rb +5 -5
  57. data/lib/asker/formatter/concept_doc_formatter.rb +3 -3
  58. data/lib/asker/formatter/concept_string_formatter.rb +6 -6
  59. data/lib/asker/formatter/moodle/ddmatch.erb +40 -0
  60. data/lib/asker/formatter/moodle/gapfill.erb +57 -0
  61. data/lib/asker/formatter/moodle/ordering.erb +41 -0
  62. data/lib/asker/formatter/question_gift_formatter.rb +41 -14
  63. data/lib/asker/formatter/question_hash_formatter.rb +5 -6
  64. data/lib/asker/formatter/question_moodle_formatter.rb +14 -7
  65. data/lib/asker/formatter/rb2haml_formatter.rb +8 -7
  66. data/lib/asker/lang/lang.rb +16 -16
  67. data/lib/asker/lang/lang_factory.rb +13 -16
  68. data/lib/asker/lang/text_actions.rb +20 -18
  69. data/lib/asker/loader/code_loader.rb +10 -22
  70. data/lib/asker/loader/content_loader.rb +42 -49
  71. data/lib/asker/loader/directory_loader.rb +13 -16
  72. data/lib/asker/loader/embedded_file.rb +14 -14
  73. data/lib/asker/loader/file_loader.rb +5 -4
  74. data/lib/asker/loader/haml_loader.rb +4 -3
  75. data/lib/asker/loader/image_url_loader.rb +6 -5
  76. data/lib/asker/loader/input_loader.rb +24 -10
  77. data/lib/asker/loader/problem_loader.rb +88 -0
  78. data/lib/asker/loader/project_loader.rb +5 -12
  79. data/lib/asker/logger.rb +19 -10
  80. data/lib/asker/skeleton.rb +19 -35
  81. data/lib/asker/start.rb +44 -0
  82. data/lib/asker/version.rb +1 -1
  83. data/lib/asker.rb +7 -52
  84. metadata +12 -6
  85. data/lib/asker/ai/code/problem_code_ai.rb +0 -176
  86. data/lib/asker/exporter/code_moodle_exporter.rb +0 -15
  87. data/lib/asker/exporter/concept_ai_moodle_exporter.rb +0 -15
@@ -16,29 +16,29 @@ class RubyCodeAI < BaseCodeAI
16
16
  questions = []
17
17
  # error_lines = []
18
18
  @lines.each_with_index do |line, index|
19
- if line.strip.start_with?('#')
19
+ if line.strip.start_with?("#")
20
20
  lines = clone_array @lines
21
- lines[index].sub!('#','').strip!
21
+ lines[index].sub!("#", "").strip!
22
22
 
23
23
  q = Question.new(:short)
24
24
  q.name = "#{name}-#{num}-uncomment"
25
- q.text = @lang.text_for(:code1,lines_to_html(lines))
26
- q.shorts << (index+1)
27
- q.feedback = 'Comment symbol removed'
25
+ q.text = @lang.text_for(:code1, lines_to_html(lines))
26
+ q.shorts << (index + 1)
27
+ q.feedback = "Comment symbol removed"
28
28
  questions << q
29
- elsif line.strip.size>0
29
+ elsif line.strip.size > 0
30
30
  lines = clone_array @lines
31
- lines[index]='# ' + lines[index]
31
+ lines[index] = "# " + lines[index]
32
32
 
33
33
  q = Question.new(:short)
34
34
  q.name = "#{name}-#{num}-comment"
35
- q.text = @lang.text_for(:code1,lines_to_html(lines))
36
- q.shorts << (index+1)
37
- q.feedback = 'Comment symbol added'
35
+ q.text = @lang.text_for(:code1, lines_to_html(lines))
36
+ q.shorts << (index + 1)
37
+ q.feedback = "Comment symbol added"
38
38
  questions << q
39
39
  end
40
40
  end
41
- questions.shuffle[0,@lines.size/@reduce]
41
+ questions.shuffle[0, @lines.size / @reduce]
42
42
  end
43
43
 
44
44
  ##
@@ -47,7 +47,7 @@ class RubyCodeAI < BaseCodeAI
47
47
  questions = []
48
48
  empty_lines = []
49
49
  used_lines = []
50
- @lines.each_with_index do |line,index|
50
+ @lines.each_with_index do |line, index|
51
51
  if line.strip.size.zero?
52
52
  empty_lines << index
53
53
  else
@@ -57,28 +57,28 @@ class RubyCodeAI < BaseCodeAI
57
57
 
58
58
  used_lines.each do |index|
59
59
  lines = clone_array(@lines)
60
- lines.insert(index, ' ' * (rand(4).to_i + 1))
60
+ lines.insert(index, " " * (rand(4).to_i + 1))
61
61
  if @lines.size < 4 || rand(2) == 0
62
62
  q = Question.new(:short)
63
63
  q.name = "#{name}-#{num}-codeok"
64
- q.text = @lang.text_for(:code1,lines_to_html(lines))
65
- q.shorts << '0'
66
- q.feedback = 'Code is OK'
64
+ q.text = @lang.text_for(:code1, lines_to_html(lines))
65
+ q.shorts << "0"
66
+ q.feedback = "Code is OK"
67
67
  questions << q
68
68
  else
69
69
  q = Question.new(:choice)
70
70
  q.name = "#{name}-#{num}-codeok"
71
- q.text = @lang.text_for(:code2,lines_to_html(lines))
71
+ q.text = @lang.text_for(:code2, lines_to_html(lines))
72
72
  others = (1..@lines.size).to_a.shuffle!
73
- q.good = '0'
73
+ q.good = "0"
74
74
  q.bads << others[0].to_s
75
75
  q.bads << others[1].to_s
76
76
  q.bads << others[2].to_s
77
- q.feedback = 'Code is OK'
77
+ q.feedback = "Code is OK"
78
78
  end
79
79
  end
80
80
 
81
- questions.shuffle[0,@lines.size/@reduce]
81
+ questions.shuffle[0, @lines.size / @reduce]
82
82
  end
83
83
 
84
84
  ##
@@ -86,13 +86,13 @@ class RubyCodeAI < BaseCodeAI
86
86
  def make_syntax_error
87
87
  questions = []
88
88
 
89
- @lang.mistakes.each_pair do |key,values|
89
+ @lang.mistakes.each_pair do |key, values|
90
90
  error_lines = []
91
- @lines.each_with_index do |line,index|
91
+ @lines.each_with_index do |line, index|
92
92
  error_lines << index if line.include?(key.to_s)
93
93
  end
94
94
 
95
- v = values.split(',')
95
+ v = values.split(",")
96
96
  v.each do |value|
97
97
  error_lines.each do |index|
98
98
  lines = clone_array(@lines)
@@ -100,15 +100,15 @@ class RubyCodeAI < BaseCodeAI
100
100
  if @lines.size < 4 || rand(2) == 0
101
101
  q = Question.new(:short)
102
102
  q.name = "#{name}-#{num}-syntaxerror"
103
- q.text = @lang.text_for(:code1,lines_to_html(lines))
104
- q.shorts << (index+1)
103
+ q.text = @lang.text_for(:code1, lines_to_html(lines))
104
+ q.shorts << (index + 1)
105
105
  q.feedback = "Syntax error: '#{value}' must be '#{key}'"
106
106
  else
107
107
  q = Question.new(:choice)
108
108
  q.name = "#{name}-#{num}-syntaxerror"
109
- q.text = @lang.text_for(:code2,lines_to_html(lines))
109
+ q.text = @lang.text_for(:code2, lines_to_html(lines))
110
110
  others = (1..@lines.size).to_a.shuffle!
111
- others.delete(index+1)
111
+ others.delete(index + 1)
112
112
  q.good = (index + 1).to_s
113
113
  q.bads << others[0].to_s
114
114
  q.bads << others[1].to_s
@@ -119,7 +119,7 @@ class RubyCodeAI < BaseCodeAI
119
119
  end
120
120
  end
121
121
  end
122
- questions.shuffle[0,@lines.size/@reduce]
122
+ questions.shuffle[0, @lines.size / @reduce]
123
123
  end
124
124
 
125
125
  ##
@@ -129,7 +129,7 @@ class RubyCodeAI < BaseCodeAI
129
129
  # error_lines = []
130
130
  @lines.each_with_index do |line, index|
131
131
  # Search Variable assignment
132
- m = /\s*(\w*)\s*\=\w*/.match(line)
132
+ m = /\s*(\w*)\s*=\w*/.match(line)
133
133
  i = []
134
134
  unless m.nil?
135
135
  varname = (m.values_at 1)[0]
@@ -152,22 +152,22 @@ class RubyCodeAI < BaseCodeAI
152
152
  q.name = "#{name}-#{num}-variable"
153
153
  q.text = @lang.text_for(:code1, lines_to_html(lines))
154
154
  q.shorts << (index + 1)
155
- q.feedback = "Variable error! Swapped lines #{(index+1)} with #{(k+1)}"
155
+ q.feedback = "Variable error! Swapped lines #{index + 1} with #{k + 1}"
156
156
  else
157
157
  q = Question.new(:choice)
158
158
  q.name = "#{name}-#{num}-variable"
159
159
  q.text = @lang.text_for(:code2, lines_to_html(lines))
160
160
  others = (1..@lines.size).to_a.shuffle!
161
- others.delete(index+1)
161
+ others.delete(index + 1)
162
162
  q.good = (index + 1).to_s
163
163
  q.bads << others[0].to_s
164
164
  q.bads << others[1].to_s
165
165
  q.bads << others[2].to_s
166
- q.feedback = "Variable error! Swapped lines #{(index+1)} with #{(k+1)}"
166
+ q.feedback = "Variable error! Swapped lines #{index + 1} with #{k + 1}"
167
167
  end
168
168
  questions << q
169
169
  end
170
170
  end
171
- questions.shuffle[0,@lines.size/@reduce]
171
+ questions.shuffle[0, @lines.size / @reduce]
172
172
  end
173
173
  end
@@ -1,36 +1,35 @@
1
-
2
- require_relative '../../lang/lang_factory'
3
- require_relative '../../ai/question'
4
- require_relative 'base_code_ai'
1
+ require_relative "../../lang/lang_factory"
2
+ require_relative "../../ai/question"
3
+ require_relative "base_code_ai"
5
4
 
6
5
  class SQLCodeAI < BaseCodeAI
7
6
  def initialize(code)
8
- @lang = LangFactory.instance.get('sql')
7
+ @lang = LangFactory.instance.get("sql")
9
8
  super code
10
9
  end
11
10
 
12
11
  def make_comment_error
13
12
  questions = []
14
- @lines.each_with_index do |line,index|
15
- if line.include?('//')
13
+ @lines.each_with_index do |line, index|
14
+ if line.include?("//")
16
15
  lines = clone_array @lines
17
- lines[index].sub!('//','').strip!
16
+ lines[index].sub!("//", "").strip!
18
17
 
19
18
  q = Question.new(:short)
20
19
  q.name = "#{name}-#{num}-code1uncomment"
21
- q.text = @lang.text_for(:code1,lines_to_html(lines))
22
- q.shorts << (index+1)
23
- q.feedback = 'Comment symbol removed'
20
+ q.text = @lang.text_for(:code1, lines_to_html(lines))
21
+ q.shorts << (index + 1)
22
+ q.feedback = "Comment symbol removed"
24
23
  questions << q
25
- elsif line.strip.size>0
24
+ elsif line.strip.size > 0
26
25
  lines = clone_array @lines
27
- lines[index]='// ' + lines[index]
26
+ lines[index] = "// " + lines[index]
28
27
 
29
28
  q = Question.new(:short)
30
29
  q.name = "#{name}-#{num}-code1comment"
31
- q.text = @lang.text_for(:code1,lines_to_html(lines))
32
- q.shorts << (index+1)
33
- q.feedback = 'Comment symbol added'
30
+ q.text = @lang.text_for(:code1, lines_to_html(lines))
31
+ q.shorts << (index + 1)
32
+ q.feedback = "Comment symbol added"
34
33
  questions << q
35
34
  end
36
35
  end
@@ -41,10 +40,10 @@ class SQLCodeAI < BaseCodeAI
41
40
  error_lines = []
42
41
  questions = []
43
42
 
44
- @lang.mistakes.each_pair do |key,values|
45
- v = values.split(',')
43
+ @lang.mistakes.each_pair do |key, values|
44
+ v = values.split(",")
46
45
  v.each do |value|
47
- @lines.each_with_index do |line,index|
46
+ @lines.each_with_index do |line, index|
48
47
  error_lines << index if line.include?(key.to_s)
49
48
  end
50
49
 
@@ -53,8 +52,8 @@ class SQLCodeAI < BaseCodeAI
53
52
  lines[index].sub!(key.to_s, value)
54
53
  q = Question.new(:short)
55
54
  q.name = "#{name}-#{num}-code1keyword"
56
- q.text = @lang.text_for(:code1,lines_to_html(lines))
57
- q.shorts << (index+1)
55
+ q.text = @lang.text_for(:code1, lines_to_html(lines))
56
+ q.shorts << (index + 1)
58
57
  q.feedback = "Keyword error: '#{value}' must be '#{key}'"
59
58
  questions << q
60
59
  end
@@ -1,15 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../lang/lang'
4
- require_relative 'ai'
5
- require_relative 'question'
6
-
7
- # ConceptAI: Add more info to every Concept instance.
8
- # Encapsulating AI data => questions
9
- # * concept
10
- # * questions
11
- # * num
12
- # * random_image_for
3
+ require_relative "../lang/lang"
4
+ require_relative "ai"
5
+ require_relative "question"
6
+
7
+ # Add more info to every Concept instance.
8
+ # Encapsulating AI data => questions
13
9
  class ConceptAI
14
10
  include AI
15
11
 
@@ -17,22 +13,17 @@ class ConceptAI
17
13
  attr_reader :questions
18
14
  attr_reader :excluded_questions
19
15
 
20
- ##
21
- # Initialize ConcepAI
22
- # @param concept (Concept)
23
- # @param world (World)
24
16
  def initialize(concept, world)
25
17
  @concept = concept
26
18
  @world = world
27
- @questions = { d: [], b: [], f: [], i: [], s: [], t: [] }
28
- @excluded_questions = { d: [], b: [], f: [], i: [], s: [], t: [] }
19
+ @questions = {d: [], b: [], f: [], i: [], s: [], t: []}
20
+ @excluded_questions = {d: [], b: [], f: [], i: [], s: [], t: []}
29
21
  @num = 0 # Add a unique number to every question
30
22
  make_questions
31
23
  end
32
24
 
33
- ##
34
- # Generates and return new "num" value
35
25
  def num
26
+ # Generates and return new "num" value
36
27
  @num += 1
37
28
  @num.to_s
38
29
  end
@@ -43,15 +34,14 @@ class ConceptAI
43
34
  @concept.send(method, *args, &block)
44
35
  end
45
36
 
46
- ##
47
- # Generates random image URL
48
37
  def random_image_for(_conceptname)
49
- return '' if rand <= ProjectData.instance.get(:threshold)
38
+ # Generates random image URL
39
+ return "" if rand <= ProjectData.instance.get(:threshold)
50
40
 
51
41
  keys = @world.image_urls.keys
52
42
  keys.shuffle!
53
43
  values = @world.image_urls[keys[0]] # keys[0] could be conceptname
54
- return '' if values.nil?
44
+ return "" if values.nil?
55
45
 
56
46
  values.shuffle!
57
47
  "<img src=\"#{values[0]}\" alt=\"image\"><br/>"
@@ -0,0 +1,226 @@
1
+ require_relative "../../lang/lang_factory"
2
+ require_relative "../question"
3
+
4
+ class ProblemAI
5
+ attr_accessor :problem
6
+
7
+ def call(problem)
8
+ @problem = problem
9
+ make_questions
10
+ @problem
11
+ end
12
+
13
+ def make_questions
14
+ @counter = 0
15
+ @questions = []
16
+ @customs = get_customs(@problem)
17
+ make_questions_with_aswers
18
+ make_questions_with_steps
19
+ @problem.questions = @questions
20
+ end
21
+
22
+ private
23
+
24
+ def counter
25
+ @counter += 1
26
+ end
27
+
28
+ def customize(text:, custom:)
29
+ output = text.clone
30
+ custom.each_pair { |oldvalue, newvalue| output.gsub!(oldvalue, newvalue) }
31
+ output
32
+ end
33
+
34
+ def get_customs(problem)
35
+ customs = []
36
+ vars = problem.varnames
37
+ problem.cases.each do |acase|
38
+ custom = {}
39
+ vars.each_with_index { |varname, index| custom[varname] = acase[index] }
40
+ customs << custom
41
+ end
42
+ customs
43
+ end
44
+
45
+ def lines_to_s(lines)
46
+ output = ""
47
+ lines.each_with_index do |line, index|
48
+ output << "%2d: #{line}\n" % (index + 1)
49
+ end
50
+ output
51
+ end
52
+
53
+ def make_questions_with_aswers
54
+ name = @problem.name
55
+ lang = @problem.lang
56
+
57
+ @customs.each do |custom|
58
+ desc = customize(text: @problem.desc, custom: custom)
59
+
60
+ @problem.asks.each do |ask|
61
+ next if ask[:text].nil?
62
+ asktext = customize(text: ask[:text], custom: custom)
63
+ next if ask[:answer].nil?
64
+ correct_answer = customize(text: ask[:answer], custom: custom)
65
+
66
+ # Question boolean => true
67
+ q = Question.new(:boolean)
68
+ q.name = "#{name}-#{counter}-pa1true1"
69
+ q.text = lang.text_for(:pa1, desc, asktext, correct_answer)
70
+ q.good = "TRUE"
71
+ @questions << q
72
+
73
+ # Locate incorrect answers
74
+ incorrect_answers = []
75
+ @customs.each do |aux|
76
+ next if aux == custom
77
+ incorrect = customize(text: ask[:answer], custom: aux)
78
+ incorrect_answers << incorrect if incorrect != correct_answer
79
+ end
80
+
81
+ # Question boolean => true
82
+ if incorrect_answers.size > 0
83
+ q = Question.new(:boolean)
84
+ q.name = "#{name}-#{counter}-pa2false2"
85
+ q.text = lang.text_for(:pa1, desc, asktext, incorrect_answers.first)
86
+ q.good = "FALSE"
87
+ @questions << q
88
+ end
89
+
90
+ # Question choice NONE
91
+ if incorrect_answers.size > 2
92
+ q = Question.new(:choice)
93
+ q.name = "#{name}-#{counter}-pa2-choice-none3"
94
+ q.text = lang.text_for(:pa2, desc, asktext)
95
+ q.good = lang.text_for(:none)
96
+ incorrect_answers.shuffle!
97
+ q.bads << incorrect_answers[0]
98
+ q.bads << incorrect_answers[1]
99
+ q.bads << incorrect_answers[2]
100
+ q.feedback = "Correct answer is #{correct_answer}."
101
+ @questions << q
102
+ end
103
+
104
+ # Question choice OK
105
+ if incorrect_answers.size > 2
106
+ q = Question.new(:choice)
107
+ q.name = "#{name}-#{counter}-pa2choice4"
108
+ q.text = lang.text_for(:pa2, desc, asktext)
109
+ q.good = correct_answer
110
+ incorrect_answers.shuffle!
111
+ q.bads << incorrect_answers[0]
112
+ q.bads << incorrect_answers[1]
113
+ q.bads << incorrect_answers[2]
114
+ q.feedback = "Correct answer is #{correct_answer}."
115
+ @questions << q
116
+ end
117
+
118
+ if incorrect_answers.size > 1
119
+ q = Question.new(:choice)
120
+ q.name = "#{name}-#{counter}-pa2choice5"
121
+ q.text = lang.text_for(:pa2, desc, asktext)
122
+ q.good = correct_answer
123
+ incorrect_answers.shuffle!
124
+ q.bads << incorrect_answers[0]
125
+ q.bads << incorrect_answers[1]
126
+ q.bads << lang.text_for(:none)
127
+ q.feedback = "Correct answer is #{correct_answer}."
128
+ @questions << q
129
+ end
130
+
131
+ # Question short
132
+ q = Question.new(:short)
133
+ q.name = "#{name}-#{counter}-pa2short6"
134
+ q.text = lang.text_for(:pa2, desc, asktext)
135
+ q.shorts << correct_answer
136
+ q.feedback = "Correct answer is #{correct_answer}."
137
+ @questions << q
138
+ end
139
+ end
140
+
141
+ def make_questions_with_steps
142
+ name = @problem.name
143
+ lang = @problem.lang
144
+
145
+ @customs.each do |custom|
146
+ desc = customize(text: @problem.desc, custom: custom)
147
+
148
+ @problem.asks.each do |ask|
149
+ next if ask[:text].nil?
150
+ asktext = customize(text: ask[:text], custom: custom)
151
+ next if ask[:steps].nil? || ask[:steps].empty?
152
+ steps = ask[:steps].map { |step| customize(text: step, custom: custom) }
153
+
154
+ # Question steps ok
155
+ q = Question.new(:short)
156
+ q.name = "#{name}-#{counter}-ps3short7"
157
+ q.text = lang.text_for(:ps3, desc, asktext, lines_to_s(steps))
158
+ q.shorts << 0
159
+ @questions << q
160
+
161
+ if steps.size > 3
162
+ q = Question.new(:ordering)
163
+ q.name = "#{name}-#{counter}-ps6ordering8"
164
+ q.text = lang.text_for(:ps6, desc, asktext, lines_to_s(steps))
165
+ steps.each { |step| q.ordering << step }
166
+ @questions << q
167
+ end
168
+
169
+ # Using diferents wrong steps sequences
170
+ max = steps.size - 1
171
+ (0..max).each do |index|
172
+ change = rand(max + 1)
173
+ bads = steps.clone
174
+
175
+ minor = index
176
+ major = change
177
+ if minor > major
178
+ minor, major = major, minor
179
+ elsif minor == major
180
+ next
181
+ end
182
+ bads[minor], bads[major] = bads[major], bads[minor]
183
+
184
+ # Question steps error
185
+ q = Question.new(:short)
186
+ q.name = "#{name}-#{counter}-ps3short-error9"
187
+ q.text = lang.text_for(:ps3, desc, asktext, lines_to_s(bads))
188
+ q.shorts << minor + 1
189
+ q.feedback = lang.text_for(:ps4, minor + 1, major + 1)
190
+ @questions << q
191
+ end
192
+
193
+ # Match questions
194
+ indexes = (0..(steps.size - 1)).to_a.shuffle
195
+ (0..(steps.size - 4)).each do |first|
196
+ incomplete_steps = steps.clone
197
+ incomplete_steps[indexes[first]] = "?"
198
+ incomplete_steps[indexes[first + 1]] = "?"
199
+ incomplete_steps[indexes[first + 2]] = "?"
200
+ incomplete_steps[indexes[first + 3]] = "?"
201
+
202
+ q = Question.new(:match)
203
+ q.name = "#{name}-#{counter}-ps5match10"
204
+ q.text = lang.text_for(:ps5, desc, asktext, lines_to_s(incomplete_steps))
205
+ q.matching << [steps[indexes[first]], (indexes[first] + 1).to_s]
206
+ q.matching << [steps[indexes[first + 1]], (indexes[first + 1] + 1).to_s]
207
+ q.matching << [steps[indexes[first + 2]], (indexes[first + 2] + 1).to_s]
208
+ q.matching << [steps[indexes[first + 3]], (indexes[first + 3] + 1).to_s]
209
+ q.matching << ["", lang.text_for(:error)]
210
+ @questions << q
211
+
212
+ q = Question.new(:ddmatch)
213
+ q.name = "#{name}-#{counter}-ps5ddmatch11"
214
+ q.text = lang.text_for(:ps5, desc, asktext, lines_to_s(incomplete_steps))
215
+ q.matching << [(indexes[first] + 1).to_s, steps[indexes[first]]]
216
+ q.matching << [(indexes[first + 1] + 1).to_s, steps[indexes[first + 1]]]
217
+ q.matching << [(indexes[first + 2] + 1).to_s, steps[indexes[first + 2]]]
218
+ q.matching << [(indexes[first + 3] + 1).to_s, steps[indexes[first + 3]]]
219
+ q.matching << ["", lang.text_for(:error)]
220
+ @questions << q
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
@@ -1,80 +1,69 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
3
+ require "set"
4
4
 
5
- # Define Question class
6
5
  class Question
6
+ TYPES = %i[boolean choice ddmatch match ordering short]
7
+
7
8
  attr_accessor :name # Question name used as identification
8
9
  attr_accessor :comment # Comments asociated
9
- attr_accessor :text # The real text of the question
10
- attr_accessor :good # The correct answer
11
- attr_accessor :bads # Bads answers used by choice type question
12
- attr_accessor :matching # Matching answers used by match type question
13
- attr_accessor :shorts # Short answers used by short type question
14
- attr_accessor :feedback # Question feedbak
15
- attr_reader :type # Question type: :choice, :match, :boolean, :short
16
10
  attr_accessor :tags
17
11
  attr_accessor :lang # Info used when export (YAML)
18
12
  attr_accessor :encode # image base64 content used when export Moodle xml
19
13
 
20
- # Initialize object
21
- # @param type (Symbol) Question type: choice, match, boolean, short
14
+ attr_accessor :text # The real text of the question
15
+ attr_accessor :feedback # Question feedbak
16
+ attr_reader :type # Question type: ;boolean, :choice, :match, :short
17
+
18
+ attr_accessor :good # The correct answer (types: boolean, choice)
19
+ attr_accessor :bads # Bads answers (type: choice)
20
+ attr_accessor :matching # Matching answers (type: match)
21
+ attr_accessor :ordering # Steps answer (type: ordering)
22
+ attr_accessor :shorts # Short answers (type: short)
23
+
22
24
  def initialize(type = :choice)
23
25
  reset(type)
24
26
  end
25
27
 
26
- # Reset attributes
27
28
  # @param type (Symbol) Question type: choice, match, boolean, short
28
- # rubocop:disable Metrics/MethodLength
29
29
  def reset(type = :choice)
30
- @name = ''
31
- @comment = ''
32
- @text = ''
30
+ validate type
33
31
  @type = type
34
- @good = ''
35
- @bads = []
36
- @matching = []
37
- @shorts = []
38
- @feedback = nil
39
- shuffle_on
32
+
33
+ @name = ""
34
+ @comment = ""
40
35
  @tags = Set.new
41
36
  @lang = nil
42
37
  @encode = :none
43
- end
44
- # rubocop:enable Metrics/MethodLength
45
38
 
46
- # Set choice type
47
- def set_choice
48
- @type = :choice
49
- end
50
-
51
- # Set match type
52
- def set_match
53
- @type = :match
54
- end
55
-
56
- # Set boolean type
57
- def set_boolean
58
- @type = :boolean
59
- end
60
-
61
- # Set short type
62
- def set_short
63
- @type = :short
39
+ @text = ""
40
+ @feedback = nil
41
+ @good = ""
42
+ @bads = []
43
+ @matching = []
44
+ @ordering = []
45
+ @shorts = []
46
+ shuffle_on
64
47
  end
65
48
 
66
- # Set shuffle off
67
49
  def shuffle_off
68
50
  @shuffle = false
69
51
  end
70
52
 
71
- # Set shuffle on
72
53
  def shuffle_on
73
54
  @shuffle = true
74
55
  end
75
56
 
76
- # Return shuffle value
77
57
  def shuffle?
78
58
  @shuffle
79
59
  end
60
+
61
+ private
62
+
63
+ def validate(type)
64
+ unless TYPES.include? type
65
+ warn "[ERROR] Question type error (#{type})"
66
+ exit 1
67
+ end
68
+ end
80
69
  end