asker-tool 2.7.2 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 04fea4738c097db67a060121841ec075ce611ceceec3c31fb49819cd03924db0
4
- data.tar.gz: 89e0185e69dac3e5f66dec08331de9c64dfe49801a33cf587293d93f03163263
3
+ metadata.gz: a70a91ae23f461cc6740e7938555a205ca50fa9f7b910a6af4e4d6b873ff3ba1
4
+ data.tar.gz: 79a7f6dccf7faea3bc97b7e21b2bc5680d52b887b0aaa3eef52a6c0302f9d5ed
5
5
  SHA512:
6
- metadata.gz: 13c0dbaceeb204e6b8263f15ba5ccd240a8c3ec7909575a6ace921d8bf8f74554c673d350bf02dc14aca22edd935d5e1f820d74714fddaaf4a33fce93c38a26a
7
- data.tar.gz: 9698ad8f849a346b0e9abb86a6be4e64c17922c0e22a35daa423dda2c2b08d679c6ae5d8edc2d3edbb7f64c26026c2f7dd1d66e0538187244d0e2f8d66f8fdee
6
+ metadata.gz: d87d28ecb0899a1f37fe791e0a08057e4b5dbba966202dc2111b339d34e4cc063a000b87436fa5e864320d366c245216123bffed9b9b7848c838a95fb3b9f845
7
+ data.tar.gz: 9fe5684a838726523be099358f604e5ed5838c083a1043577cb9e445cba32e733f89e0dc98c4fa2408f37bab1909f523211a20d5820dd9743565f0435a11df87
@@ -0,0 +1,36 @@
1
+ require_relative "../../logger"
2
+
3
+ class Customizer
4
+ def initialize(problem)
5
+ @problem = problem
6
+ end
7
+
8
+ def call(text:, custom:, type: nil)
9
+ output = text.clone
10
+ custom.each_pair { |oldvalue, newvalue| output.gsub!(oldvalue, newvalue) }
11
+
12
+ if type.nil?
13
+ return output
14
+ elsif type == "formula"
15
+ begin
16
+ return eval(output).to_s
17
+ rescue SyntaxError => e
18
+ Logger.error "Problem.name = #{@problem.name}"
19
+ Logger.error "Customizer: Wrong formula '#{text}' or wrong values '#{output}'"
20
+ Logger.error e.to_s
21
+ exit 1
22
+ end
23
+ else
24
+ Logger.error "Customizer: Wrong answer type (#{type})"
25
+ exit 1
26
+ end
27
+ end
28
+
29
+ def min(*args)
30
+ args.min
31
+ end
32
+
33
+ def max(*args)
34
+ args.max
35
+ end
36
+ end
@@ -1,252 +1,22 @@
1
1
  require_relative "../../lang/lang_factory"
2
2
  require_relative "../question"
3
+ require_relative "stage_answers"
4
+ require_relative "stage_steps"
3
5
 
4
6
  class ProblemAI
5
7
  attr_accessor :problem
6
8
 
7
9
  def call(problem)
8
10
  @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}-01pa1true"
69
- q.text = lang.text_for(:pa1, desc, asktext, correct_answer)
70
- q.good = "TRUE"
71
- @questions << q
72
11
 
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
12
+ questions = StageAnswers.new(@problem).make_questions
13
+ @problem.stats[:answer] = questions.size
14
+ @problem.questions = questions
80
15
 
81
- # Question boolean => true
82
- if incorrect_answers.size > 0
83
- q = Question.new(:boolean)
84
- q.name = "#{name}-#{counter}-02pa1false"
85
- q.text = lang.text_for(:pa1, desc, asktext, incorrect_answers.first)
86
- q.good = "FALSE"
87
- @questions << q
88
- end
16
+ questions = StageSteps.new(@problem).make_questions
17
+ @problem.stats[:steps] = questions.size
18
+ @problem.questions += questions
89
19
 
90
- # Question choice NONE
91
- if incorrect_answers.size > 2
92
- q = Question.new(:choice)
93
- q.name = "#{name}-#{counter}-03pa2-choice-none"
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}-04pa2choice"
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}-05pa2choice"
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}-06pa2short"
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}-07ps3short-ok"
157
- q.text = lang.text_for(:ps3, desc, asktext, lines_to_s(steps))
158
- q.shorts << 0
159
- @questions << q
160
-
161
- # Question steps ordering
162
- if steps.size > 3
163
- q = Question.new(:ordering)
164
- q.name = "#{name}-#{counter}-08ps6ordering"
165
- q.text = lang.text_for(:ps6, desc, asktext, lines_to_s(steps))
166
- steps.each { |step| q.ordering << step }
167
- @questions << q
168
- end
169
-
170
- # Question steps hide
171
- if steps.size > 3
172
- (0...(steps.size)).each do |index|
173
- q = Question.new(:short)
174
- q.name = "#{name}-#{counter}-09ps7short-hide"
175
- hide_steps = steps.clone
176
- hide_steps[index] = hide(steps[index])
177
- q.text = lang.text_for(:ps7, desc, asktext, lines_to_s(hide_steps))
178
- q.shorts << steps[index]
179
- @questions << q
180
- end
181
- end
182
-
183
- # Using diferents wrong steps sequences
184
- max = steps.size - 1
185
- (0..max).each do |index|
186
- change = rand(max + 1)
187
- bads = steps.clone
188
-
189
- minor = index
190
- major = change
191
- if minor > major
192
- minor, major = major, minor
193
- elsif minor == major
194
- next
195
- end
196
- bads[minor], bads[major] = bads[major], bads[minor]
197
-
198
- # Question steps error
199
- q = Question.new(:short)
200
- q.name = "#{name}-#{counter}-10ps3short-error"
201
- q.text = lang.text_for(:ps3, desc, asktext, lines_to_s(bads))
202
- q.shorts << minor + 1
203
- q.feedback = lang.text_for(:ps4, minor + 1, major + 1)
204
- @questions << q
205
- end
206
-
207
- # Match questions
208
- indexes = (0..(steps.size - 1)).to_a.shuffle
209
- (0..(steps.size - 4)).each do |first|
210
- incomplete_steps = steps.clone
211
- incomplete_steps[indexes[first]] = "?"
212
- incomplete_steps[indexes[first + 1]] = "?"
213
- incomplete_steps[indexes[first + 2]] = "?"
214
- incomplete_steps[indexes[first + 3]] = "?"
215
-
216
- q = Question.new(:match)
217
- q.name = "#{name}-#{counter}-11ps5match"
218
- q.text = lang.text_for(:ps5, desc, asktext, lines_to_s(incomplete_steps))
219
- q.matching << [steps[indexes[first]], (indexes[first] + 1).to_s]
220
- q.matching << [steps[indexes[first + 1]], (indexes[first + 1] + 1).to_s]
221
- q.matching << [steps[indexes[first + 2]], (indexes[first + 2] + 1).to_s]
222
- q.matching << [steps[indexes[first + 3]], (indexes[first + 3] + 1).to_s]
223
- q.matching << ["", lang.text_for(:error)]
224
- @questions << q
225
-
226
- q = Question.new(:ddmatch)
227
- q.name = "#{name}-#{counter}-12ps5ddmatch"
228
- q.text = lang.text_for(:ps5, desc, asktext, lines_to_s(incomplete_steps))
229
- q.matching << [(indexes[first] + 1).to_s, steps[indexes[first]]]
230
- q.matching << [(indexes[first + 1] + 1).to_s, steps[indexes[first + 1]]]
231
- q.matching << [(indexes[first + 2] + 1).to_s, steps[indexes[first + 2]]]
232
- q.matching << [(indexes[first + 3] + 1).to_s, steps[indexes[first + 3]]]
233
- q.matching << ["", lang.text_for(:error)]
234
- @questions << q
235
- end
236
- end
237
- end
238
- end
239
-
240
- def hide(text)
241
- output = []
242
- text.chars do |c|
243
- output << if c == " "
244
- c
245
- else
246
- "?"
247
- end
248
- end
249
- output.join
250
- end
20
+ @problem
251
21
  end
252
22
  end
@@ -0,0 +1,104 @@
1
+ require_relative "../question"
2
+ require_relative "stage_base"
3
+
4
+ class StageAnswers < StageBase
5
+ def make_questions
6
+ name = @problem.name
7
+ lang = @problem.lang
8
+ questions = []
9
+
10
+ @customs.each do |custom|
11
+ desc = customize(text: @problem.desc, custom: custom)
12
+
13
+ @problem.asks.each do |ask|
14
+ next if ask[:text].nil?
15
+ asktext = customize(text: ask[:text], custom: custom)
16
+ next if ask[:answer].nil?
17
+
18
+ correct_answer = customize(
19
+ text: ask[:answer],
20
+ custom: custom,
21
+ type: ask[:answer_type]
22
+ )
23
+
24
+ # Question boolean => true
25
+ q = Question.new(:boolean)
26
+ q.name = "#{name}-#{counter}-01pa1true"
27
+ q.text = lang.text_for(:pa1, desc, asktext, correct_answer)
28
+ q.good = "TRUE"
29
+ questions << q
30
+
31
+ # Locate incorrect answers
32
+ incorrect_answers = []
33
+ @customs.each do |aux|
34
+ next if aux == custom
35
+ incorrect = customize(
36
+ text: ask[:answer],
37
+ custom: aux,
38
+ type: ask[:answer_type]
39
+ )
40
+ incorrect_answers << incorrect if incorrect != correct_answer
41
+ end
42
+
43
+ # Question boolean => true
44
+ if incorrect_answers.size > 0
45
+ q = Question.new(:boolean)
46
+ q.name = "#{name}-#{counter}-02pa1false"
47
+ q.text = lang.text_for(:pa1, desc, asktext, incorrect_answers.first)
48
+ q.good = "FALSE"
49
+ questions << q
50
+ end
51
+
52
+ # Question choice NONE
53
+ if incorrect_answers.size > 2
54
+ q = Question.new(:choice)
55
+ q.name = "#{name}-#{counter}-03pa2-choice-none"
56
+ q.text = lang.text_for(:pa2, desc, asktext)
57
+ q.good = lang.text_for(:none)
58
+ incorrect_answers.shuffle!
59
+ q.bads << incorrect_answers[0]
60
+ q.bads << incorrect_answers[1]
61
+ q.bads << incorrect_answers[2]
62
+ q.feedback = "Correct answer is #{correct_answer}."
63
+ questions << q
64
+ end
65
+
66
+ # Question choice OK
67
+ if incorrect_answers.size > 2
68
+ q = Question.new(:choice)
69
+ q.name = "#{name}-#{counter}-04pa2choice"
70
+ q.text = lang.text_for(:pa2, desc, asktext)
71
+ q.good = correct_answer
72
+ incorrect_answers.shuffle!
73
+ q.bads << incorrect_answers[0]
74
+ q.bads << incorrect_answers[1]
75
+ q.bads << incorrect_answers[2]
76
+ q.feedback = "Correct answer is #{correct_answer}."
77
+ questions << q
78
+ end
79
+
80
+ if incorrect_answers.size > 1
81
+ q = Question.new(:choice)
82
+ q.name = "#{name}-#{counter}-05pa2choice"
83
+ q.text = lang.text_for(:pa2, desc, asktext)
84
+ q.good = correct_answer
85
+ incorrect_answers.shuffle!
86
+ q.bads << incorrect_answers[0]
87
+ q.bads << incorrect_answers[1]
88
+ q.bads << lang.text_for(:none)
89
+ q.feedback = "Correct answer is #{correct_answer}."
90
+ questions << q
91
+ end
92
+
93
+ # Question short
94
+ q = Question.new(:short)
95
+ q.name = "#{name}-#{counter}-06pa2short"
96
+ q.text = lang.text_for(:pa2, desc, asktext)
97
+ q.shorts << correct_answer
98
+ q.feedback = "Correct answer is #{correct_answer}."
99
+ questions << q
100
+ end
101
+ end
102
+ questions
103
+ end
104
+ end
@@ -0,0 +1,34 @@
1
+ require_relative "../../logger"
2
+ require_relative "customizer"
3
+
4
+ class StageBase
5
+ def initialize(problem)
6
+ @problem = problem
7
+ @customs = get_customs(@problem)
8
+ @customizer = Customizer.new(@problem)
9
+ @counter = @problem.questions.size
10
+ end
11
+
12
+ def counter
13
+ @counter += 1
14
+ end
15
+
16
+ def customize(...)
17
+ @customizer.call(...)
18
+ end
19
+
20
+ private
21
+
22
+ def get_customs(problem)
23
+ return [] if problem.cases.nil?
24
+
25
+ customs = []
26
+ vars = problem.varnames
27
+ problem.cases.each do |acase|
28
+ custom = {}
29
+ vars.each_with_index { |varname, index| custom[varname] = acase[index] }
30
+ customs << custom
31
+ end
32
+ customs
33
+ end
34
+ end
@@ -0,0 +1,121 @@
1
+ require_relative "../question"
2
+ require_relative "stage_base"
3
+
4
+ class StageSteps < StageBase
5
+ def make_questions
6
+ name = @problem.name
7
+ lang = @problem.lang
8
+ questions = []
9
+
10
+ @customs.each do |custom|
11
+ desc = customize(text: @problem.desc, custom: custom)
12
+
13
+ @problem.asks.each do |ask|
14
+ next if ask[:text].nil?
15
+ asktext = customize(text: ask[:text], custom: custom)
16
+ next if ask[:steps].nil? || ask[:steps].empty?
17
+ steps = ask[:steps].map { |step| customize(text: step, custom: custom) }
18
+
19
+ # Question steps ok
20
+ q = Question.new(:short)
21
+ q.name = "#{name}-#{counter}-07ps3short-ok"
22
+ q.text = lang.text_for(:ps3, desc, asktext, lines_to_s(steps))
23
+ q.shorts << 0
24
+ questions << q
25
+
26
+ # Question steps ordering
27
+ if steps.size > 3
28
+ q = Question.new(:ordering)
29
+ q.name = "#{name}-#{counter}-08ps6ordering"
30
+ q.text = lang.text_for(:ps6, desc, asktext, lines_to_s(steps))
31
+ steps.each { |step| q.ordering << step }
32
+ questions << q
33
+ end
34
+
35
+ # Question steps hide
36
+ if steps.size > 3
37
+ (0...(steps.size)).each do |index|
38
+ q = Question.new(:short)
39
+ q.name = "#{name}-#{counter}-09ps7short-hide"
40
+ hide_steps = steps.clone
41
+ hide_steps[index] = hide(steps[index])
42
+ q.text = lang.text_for(:ps7, desc, asktext, lines_to_s(hide_steps))
43
+ q.shorts << steps[index]
44
+ questions << q
45
+ end
46
+ end
47
+
48
+ # Using diferents wrong steps sequences
49
+ indexes = (0...(steps.size)).to_a
50
+ combinations = indexes.combination(2).to_a
51
+
52
+ combinations.each do |minor, major|
53
+ bads = steps.clone
54
+ bads[minor], bads[major] = bads[major], bads[minor]
55
+
56
+ # Question steps error
57
+ q = Question.new(:short)
58
+ q.name = "#{name}-#{counter}-10ps3short-error"
59
+ q.text = lang.text_for(:ps3, desc, asktext, lines_to_s(bads))
60
+ q.shorts << minor + 1
61
+ q.feedback = lang.text_for(:ps4, minor + 1, major + 1)
62
+ questions << q
63
+ end
64
+
65
+ # Match questions
66
+ indexes = (0..(steps.size - 1)).to_a.shuffle
67
+ (0..(steps.size - 4)).each do |first|
68
+ incomplete_steps = steps.clone
69
+ incomplete_steps[indexes[first]] = "?"
70
+ incomplete_steps[indexes[first + 1]] = "?"
71
+ incomplete_steps[indexes[first + 2]] = "?"
72
+ incomplete_steps[indexes[first + 3]] = "?"
73
+
74
+ q = Question.new(:match)
75
+ q.name = "#{name}-#{counter}-11ps5match"
76
+ q.text = lang.text_for(:ps5, desc, asktext, lines_to_s(incomplete_steps))
77
+ q.matching << [steps[indexes[first]], (indexes[first] + 1).to_s]
78
+ q.matching << [steps[indexes[first + 1]], (indexes[first + 1] + 1).to_s]
79
+ q.matching << [steps[indexes[first + 2]], (indexes[first + 2] + 1).to_s]
80
+ q.matching << [steps[indexes[first + 3]], (indexes[first + 3] + 1).to_s]
81
+ q.matching << ["", lang.text_for(:error)]
82
+ questions << q
83
+
84
+ q = Question.new(:ddmatch)
85
+ q.name = "#{name}-#{counter}-12ps5ddmatch"
86
+ q.text = lang.text_for(:ps5, desc, asktext, lines_to_s(incomplete_steps))
87
+ q.matching << [(indexes[first] + 1).to_s, steps[indexes[first]]]
88
+ q.matching << [(indexes[first + 1] + 1).to_s, steps[indexes[first + 1]]]
89
+ q.matching << [(indexes[first + 2] + 1).to_s, steps[indexes[first + 2]]]
90
+ q.matching << [(indexes[first + 3] + 1).to_s, steps[indexes[first + 3]]]
91
+ q.matching << ["", lang.text_for(:error)]
92
+ questions << q
93
+ end
94
+ end
95
+ end
96
+ questions
97
+ end
98
+
99
+ private
100
+
101
+ def hide(text)
102
+ output = []
103
+ text.chars do |c|
104
+ output << if c == " "
105
+ c
106
+ else
107
+ "?"
108
+ end
109
+ end
110
+ output.join
111
+ end
112
+
113
+ def lines_to_s(lines)
114
+ output = ""
115
+ lines.each_with_index do |line, index|
116
+ output << "%2d: #{line}\n" % (index + 1)
117
+ end
118
+ output
119
+ end
120
+
121
+ end
@@ -1,5 +1,6 @@
1
1
  require "singleton"
2
2
  require "inifile"
3
+ require "rainbow"
3
4
  require_relative "logger"
4
5
  require_relative "version"
5
6
 
@@ -359,9 +359,9 @@ class CheckHamlData
359
359
  if find_parent(index) != :ask
360
360
  @outputs[index][:state] = :err
361
361
  @outputs[index][:msg] = "Parent(ask) not found!"
362
- elsif !line.match(/^\s\s\s\s\s\s%answer\s/)
362
+ elsif !line.match(/^\s\s\s\s\s\s%answer[\s|\{type:]/)
363
363
  @outputs[index][:state] = :err
364
- @outputs[index][:msg] = "Write 6 spaces before %answer"
364
+ @outputs[index][:msg] = "Write 6 spaces before %answer, then space or {type:"
365
365
  end
366
366
  end
367
367
 
data/lib/asker/cli.rb CHANGED
@@ -45,6 +45,7 @@ class CLI < Thor
45
45
  end
46
46
 
47
47
  map ["--check"] => "check"
48
+ option :color, type: :boolean
48
49
  desc "check FILEPATH", "Check HAML input file syntax"
49
50
  long_desc <<-LONGDESC
50
51
 
@@ -64,6 +65,7 @@ class CLI < Thor
64
65
  end
65
66
 
66
67
  map ["f", "-f", "--file"] => "file"
68
+ option :color, type: :boolean
67
69
  desc "[file] FILEPATH", "Build output files, from HAML/XML input file."
68
70
  long_desc <<-LONGDESC
69
71
 
@@ -1,7 +1,7 @@
1
1
  require "rexml/document"
2
2
 
3
3
  require_relative "../lang/lang_factory"
4
- require_relative "../loader/embedded_file"
4
+ require_relative "../loader/embedded_file/loader"
5
5
  require_relative "../logger"
6
6
  require_relative "table"
7
7
  require_relative "data_field"
@@ -173,10 +173,10 @@ class Concept
173
173
  case value.attributes["type"]
174
174
  when "image_url"
175
175
  # Link with remote image
176
- @data[:images] << EmbeddedFile.load(value.text.strip, File.dirname(@filename))
176
+ @data[:images] << EmbeddedFile::Loader.new.call(value.text.strip, File.dirname(@filename))
177
177
  when "file"
178
178
  # Load local images and text files
179
- @data[:images] << EmbeddedFile.load(value.text.strip, File.dirname(@filename))
179
+ @data[:images] << EmbeddedFile::Loader.new.call(value.text.strip, File.dirname(@filename))
180
180
  when nil
181
181
  if value.text.nil?
182
182
  Logger.warn "Concept: def/text empty!"
@@ -1,4 +1,5 @@
1
1
  require_relative "../logger"
2
+ require_relative "../lang/lang_factory"
2
3
 
3
4
  class Problem
4
5
  attr_accessor :lang
@@ -10,12 +11,13 @@ class Problem
10
11
  attr_accessor :descs
11
12
  attr_accessor :asks
12
13
  attr_accessor :questions
14
+ attr_accessor :stats
13
15
 
14
16
  @@id = 0
15
17
  def initialize
16
18
  @@id += 1
17
19
  @id = @@id
18
- @lang = nil
20
+ @lang = LangFactory.instance.get("en")
19
21
  @context = nil
20
22
  @process = false
21
23
  @filename = "?"
@@ -24,6 +26,7 @@ class Problem
24
26
  @descs = []
25
27
  @asks = []
26
28
  @questions = []
29
+ @stats = { answer: 0, steps: 0}
27
30
  end
28
31
 
29
32
  def self.from(values)
@@ -46,7 +49,8 @@ class Problem
46
49
  end
47
50
 
48
51
  def name
49
- "problem#{@id}"
52
+ firstword = @descs[0]&.strip&.split(" ")&.first&.downcase || "problem"
53
+ "#{firstword}#{@id}"
50
54
  end
51
55
 
52
56
  def validate
@@ -59,6 +63,8 @@ class Problem
59
63
  private
60
64
 
61
65
  def validate_varnames
66
+ return if @varnames.nil?
67
+
62
68
  if !@varnames.size.zero? && @cases.size.zero?
63
69
  Logger.warn "Problem: No problem/varnames defined with no problem/case"
64
70
  end
@@ -75,6 +81,8 @@ class Problem
75
81
  end
76
82
 
77
83
  def validate_cases
84
+ return if @cases.nil?
85
+
78
86
  @cases.each do |acase|
79
87
  if acase.size != @varnames.size
80
88
  Logger.error "Problem: problem/cases size not equal to problem/varnames size"
@@ -11,7 +11,8 @@ class ProjectData
11
11
  end
12
12
 
13
13
  def reset
14
- @default = {inputbasedir: FileUtils.pwd,
14
+ # @default = {inputbasedir: FileUtils.pwd,
15
+ @default = {inputbasedir: Dir.pwd,
15
16
  stages: {d: true, b: true, f: true, i: true, s: true, t: true},
16
17
  threshold: 0.5,
17
18
  outputdir: "output",
@@ -1,11 +1,11 @@
1
1
  require "terminal-table"
2
2
  require_relative "../logger"
3
3
 
4
- module CodeDisplayer
4
+ class CodeDisplayer
5
5
  ##
6
6
  # Show all "code" data on screen
7
7
  # @param codes (Array) List of "code" data
8
- def self.show(codes)
8
+ def call(codes)
9
9
  return if codes.nil? || codes.size.zero?
10
10
 
11
11
  total_c = total_q = total_e = 0
@@ -10,7 +10,7 @@ class ConceptAIDisplayer
10
10
  ##
11
11
  # Display ConceptAI stat on screen
12
12
  # @param concepts_ai (Array)
13
- def self.show(concepts_ai)
13
+ def call(concepts_ai)
14
14
  stages = Application.instance.config["questions"]["stages"]
15
15
  # Create table HEAD
16
16
  screen_table = Terminal::Table.new do |st|
@@ -80,7 +80,9 @@ class ConceptAIDisplayer
80
80
  Logger.info "#{screen_table}\n"
81
81
  end
82
82
 
83
- private_class_method def self.export_excluded_questions(screen_table, concepts_ai)
83
+ private
84
+
85
+ def export_excluded_questions(screen_table, concepts_ai)
84
86
  # Create table BODY
85
87
  total = {}
86
88
  total[:q] = total[:c] = 0
@@ -114,7 +116,7 @@ class ConceptAIDisplayer
114
116
  total[:ss], total[:st]]
115
117
  end
116
118
 
117
- private_class_method def self.export_notes
119
+ def export_notes
118
120
  exclude_questions = Application.instance.config["questions"]["exclude"].to_s
119
121
  renderer = ERB.new(File.read(File.join(File.dirname(__FILE__), "concept_ai_displayer.erb")))
120
122
  Logger.info Rainbow(renderer.result(binding)).white
@@ -2,11 +2,11 @@ require_relative "../application"
2
2
  require_relative "../formatter/concept_string_formatter"
3
3
  require_relative "../logger"
4
4
 
5
- module ConceptDisplayer
5
+ class ConceptDisplayer
6
6
  ##
7
7
  # Show concepts on screen
8
8
  # @param concepts (Array) List of concept data
9
- def self.show(concepts)
9
+ def call(concepts)
10
10
  return if concepts.nil? || concepts.size.zero?
11
11
 
12
12
  show_mode = Application.instance.config["global"]["show_mode"]
@@ -1,16 +1,16 @@
1
1
  require "terminal-table"
2
2
  require_relative "../logger"
3
3
 
4
- module ProblemDisplayer
4
+ class ProblemDisplayer
5
5
  ##
6
6
  # Show all "problem" data on screen
7
7
  # @param problems (Array) List of "problems" data
8
- def self.show(problems)
8
+ def call(problems)
9
9
  return if problems.nil? || problems.size.zero?
10
10
 
11
- total_p = total_q = total_e = 0
11
+ total_p = total_q = total_e = total_a = total_s = 0
12
12
  my_screen_table = Terminal::Table.new do |st|
13
- st << %w[Problem Desc Questions Entries xFactor]
13
+ st << %w[Problem Desc Questions Entries xFactor a s]
14
14
  st << :separator
15
15
  end
16
16
 
@@ -23,14 +23,19 @@ module ProblemDisplayer
23
23
  e += 1 if !ask[:answer].nil?
24
24
  end
25
25
 
26
+ desc = Rainbow(problem.desc[0, 24]).green
26
27
  q = problem.questions.size
27
28
  factor = "Unknown"
28
29
  factor = (q.to_f / e).round(2).to_s unless e.zero?
29
- desc = Rainbow(problem.desc[0, 24]).green
30
- my_screen_table.add_row [problem.name, desc, q, e, factor]
30
+ a = problem.stats[:answer]
31
+ s = problem.stats[:steps]
32
+
33
+ my_screen_table.add_row [problem.name, desc, q, e, factor, a, s]
31
34
  total_p += 1
32
35
  total_q += q
33
36
  total_e += e
37
+ total_a += a
38
+ total_s += s
34
39
  end
35
40
  return unless total_p.positive?
36
41
 
@@ -38,7 +43,9 @@ module ProblemDisplayer
38
43
  my_screen_table.add_row [Rainbow("TOTAL = #{total_p}").bright, "",
39
44
  Rainbow(total_q.to_s).bright,
40
45
  Rainbow(total_e.to_s).bright,
41
- Rainbow((total_q / total_e.to_f).round(2)).bright]
46
+ Rainbow((total_q / total_e.to_f).round(2)).bright,
47
+ Rainbow(total_a.to_s).bright,
48
+ Rainbow(total_s.to_s).bright]
42
49
  Logger.verboseln Rainbow("\n[INFO] Showing PROBLEMs statistics").white
43
50
  Logger.verboseln my_screen_table.to_s
44
51
  end
@@ -10,8 +10,8 @@ module StatsDisplayer
10
10
  def self.show(data)
11
11
  return unless Application.instance.config["global"]["show_mode"]
12
12
 
13
- ConceptAIDisplayer.show(data[:concepts_ai])
14
- CodeDisplayer.show(data[:codes_ai])
15
- ProblemDisplayer.show(data[:problems])
13
+ ConceptAIDisplayer.new.call(data[:concepts_ai])
14
+ CodeDisplayer.new.call(data[:codes_ai])
15
+ ProblemDisplayer.new.call(data[:problems])
16
16
  end
17
17
  end
@@ -3,7 +3,6 @@
3
3
  require "rainbow"
4
4
  require "terminal-table"
5
5
 
6
- # Define methods to transform Concept into String
7
6
  module ConceptStringFormatter
8
7
  ##
9
8
  # Formatter Concept to String
@@ -0,0 +1,103 @@
1
+ require "base64"
2
+ require_relative "../../logger"
3
+ require_relative "type"
4
+
5
+ module EmbeddedFile
6
+ # Methods to load embedded files defined into asker input data file
7
+ # Examples:
8
+ # def line with :type = :image_url used to link external file as https://..."
9
+ # def line with :type = :file used to load local file as image.png or file.txt"
10
+ class Loader
11
+ ##
12
+ # @param value (String)
13
+ # @param localdir (String) Input file base folder
14
+ # @return Hash
15
+ def call(value, localdir)
16
+ filepath = File.join(localdir, value)
17
+ type = EmbebbedFile::Type.new.for(value, localdir)
18
+
19
+ case type
20
+ when :image
21
+ return load_image(value, localdir)
22
+ when :image_url
23
+ return load_image_url(value, localdir)
24
+ when :audio
25
+ return load_audio(value, localdir)
26
+ when :audio_url
27
+ return load_audio_url(value, localdir)
28
+ when :video
29
+ return load_video(value, localdir)
30
+ when :video_url
31
+ return load_video_url(value, localdir)
32
+ end
33
+
34
+ {text: "<pre>#{File.read(filepath)}</pre>", file: :none, type: :text}
35
+ end
36
+
37
+ def load_audio(value, localdir)
38
+ filepath = File.join(localdir, value)
39
+ output = {}
40
+ output[:text] = '<audio controls><source src="@@PLUGINFILE@@/' + File.basename(filepath) \
41
+ + '">Your browser does not support the audio tag.</audio>'
42
+ output[:file] = '<file name="' + File.basename(filepath) \
43
+ + '" path="/" encoding="base64">' \
44
+ + Base64.strict_encode64(File.open(filepath, "rb").read) \
45
+ + "</file>"
46
+ output[:type] = :audio
47
+ output
48
+ end
49
+
50
+ def load_audio_url(value, localdir)
51
+ {
52
+ text: "<audio src=\"#{value}\" controls></audio>",
53
+ file: :none,
54
+ type: :url
55
+ }
56
+ end
57
+
58
+ def load_image(value, localdir)
59
+ filepath = File.join(localdir, value)
60
+ output = {}
61
+ output[:text] = '<img src="@@PLUGINFILE@@/' + File.basename(filepath) \
62
+ + '" alt="imagen" class="img-responsive atto_image_button_text-bottom">'
63
+ output[:file] = '<file name="' + File.basename(filepath) \
64
+ + '" path="/" encoding="base64">' \
65
+ + Base64.strict_encode64(File.open(filepath, "rb").read) \
66
+ + "</file>"
67
+ output[:type] = :image
68
+ output
69
+ end
70
+
71
+ def load_image_url(value, localdir)
72
+ {
73
+ text: "<img src=\"#{value}\" alt=\"image\" width=\"400\" height=\"300\">",
74
+ file: :none,
75
+ type: :url
76
+ }
77
+ end
78
+
79
+ def load_video(value, localdir)
80
+ filepath = File.join(localdir, value)
81
+ output = {}
82
+ output[:text] = '<video controls><source src="@@PLUGINFILE@@/' \
83
+ + File.basename(filepath) \
84
+ + '"/>Your browser does not support the video tag.</video>'
85
+ output[:file] = '<file name="' \
86
+ + File.basename(filepath) \
87
+ + '" path="/" encoding="base64">' \
88
+ + Base64.strict_encode64(File.open(filepath, "rb").read) \
89
+ + "</file>"
90
+ output[:type] = :video
91
+ output
92
+ end
93
+
94
+ def load_video_url(value, localdir)
95
+ {
96
+ text: "<video controls width=\"400\" height=\"300\">" \
97
+ + "<source src=\"#{value}\"/></video>",
98
+ file: :none,
99
+ type: :url
100
+ }
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,51 @@
1
+
2
+ module EmbebbedFile
3
+ class Type
4
+ def for(value, localdir)
5
+ if is_url? value
6
+ return :image_url if is_image? value
7
+ return :audio_url if is_audio? value
8
+ return :video_url if is_video? value
9
+
10
+ Logger.error "EmbebbedFile::Type.for: Unknown URL type (#{value})"
11
+ exit 1
12
+ end
13
+
14
+ filepath = File.join(localdir, value)
15
+ unless File.exist?(filepath)
16
+ Logger.error "EmbeddedFile::Type.for: File does not exist (#{filepath})"
17
+ exit 1
18
+ end
19
+
20
+ return :image if is_image? value
21
+ return :audio if is_audio? value
22
+ return :video if is_video? value
23
+
24
+ :text
25
+ end
26
+
27
+ def is_audio?(filename)
28
+ extens = [".mp3", ".ogg", ".wav"]
29
+ extens.each { |ext| return true if filename.downcase.end_with?(ext) }
30
+ false
31
+ end
32
+
33
+ def is_image?(filename)
34
+ extens = [".jpg", ".jpeg", ".png"]
35
+ extens.each { |ext| return true if filename.downcase.end_with?(ext) }
36
+ false
37
+ end
38
+
39
+ def is_video?(filename)
40
+ extens = [".mp4", ".ogv"]
41
+ extens.each { |ext| return true if filename.downcase.end_with?(ext) }
42
+ false
43
+ end
44
+
45
+ def is_url?(value)
46
+ return true if value.start_with?("https://", "http://")
47
+
48
+ false
49
+ end
50
+ end
51
+ end
@@ -64,6 +64,8 @@ class ProblemLoader
64
64
  ask[:text] = i.text
65
65
  elsif i.name == "answer"
66
66
  ask[:answer] = i.text
67
+ type = i.attributes["type"]
68
+ ask[:answer_type] = type unless type.nil?
67
69
  elsif i.name == "step"
68
70
  ask[:steps] << i.text
69
71
  else
data/lib/asker/start.rb CHANGED
@@ -9,7 +9,7 @@ require_relative "loader/input_loader"
9
9
  class Start
10
10
  def call(filepath)
11
11
  project_data, data = load_input(filepath)
12
- ConceptDisplayer.show(data[:concepts])
12
+ ConceptDisplayer.new.call(data[:concepts])
13
13
  create_output(project_data, data)
14
14
  end
15
15
 
data/lib/asker/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  class Asker
2
- VERSION = "2.7.2"
2
+ VERSION = "2.8.0"
3
3
  NAME = "asker"
4
4
  GEM = "asker-tool"
5
5
  CONFIGFILE = "asker.ini"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asker-tool
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.2
4
+ version: 2.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Vargas Ruiz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-25 00:00:00.000000000 Z
11
+ date: 2023-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: haml
@@ -117,7 +117,11 @@ files:
117
117
  - lib/asker/ai/code/ruby_code_ai.rb
118
118
  - lib/asker/ai/code/sql_code_ai.rb
119
119
  - lib/asker/ai/concept_ai.rb
120
+ - lib/asker/ai/problem/customizer.rb
120
121
  - lib/asker/ai/problem/problem_ai.rb
122
+ - lib/asker/ai/problem/stage_answers.rb
123
+ - lib/asker/ai/problem/stage_base.rb
124
+ - lib/asker/ai/problem/stage_steps.rb
121
125
  - lib/asker/ai/question.rb
122
126
  - lib/asker/ai/stages/base_stage.rb
123
127
  - lib/asker/ai/stages/main.rb
@@ -210,7 +214,8 @@ files:
210
214
  - lib/asker/loader/code_loader.rb
211
215
  - lib/asker/loader/content_loader.rb
212
216
  - lib/asker/loader/directory_loader.rb
213
- - lib/asker/loader/embedded_file.rb
217
+ - lib/asker/loader/embedded_file/loader.rb
218
+ - lib/asker/loader/embedded_file/type.rb
214
219
  - lib/asker/loader/file_loader.rb
215
220
  - lib/asker/loader/haml_loader.rb
216
221
  - lib/asker/loader/image_url_loader.rb
@@ -1,133 +0,0 @@
1
- require "base64"
2
- require_relative "../logger"
3
-
4
- # Methods to load embedded files defined into asker input data file
5
- # Example:
6
- # * def line with :type = :image_url used to link external file as https://..."
7
- # * def line with :type = :file used to load local file as image.png or file.txt"
8
- module EmbeddedFile
9
- ##
10
- # @param value (String)
11
- # @param localdir (String) Input file base folder
12
- # @return Hash
13
- def self.load(value, localdir)
14
- return load_image(value, localdir) if is_image? value
15
- return load_audio(value, localdir) if is_audio? value
16
- return load_video(value, localdir) if is_video? value
17
-
18
- if is_url? value
19
- Logger.error "EmbebbedFile: Unknown URL type (#{value})"
20
- exit 1
21
- end
22
-
23
- filepath = File.join(localdir, value)
24
- unless File.exist?(filepath)
25
- Logger.error "EmbeddedFile: File does not exist (#{filepath})"
26
- exit 1
27
- end
28
-
29
- # Suposse that filename is TEXT file
30
- {text: "<pre>#{File.read(filepath)}</pre>", file: :none, type: :text}
31
- end
32
-
33
- def self.is_audio?(filename)
34
- extens = [".mp3", ".ogg", ".wav"]
35
- extens.each { |ext| return true if filename.downcase.end_with?(ext) }
36
- false
37
- end
38
-
39
- def self.is_image?(filename)
40
- extens = [".jpg", ".jpeg", ".png"]
41
- extens.each { |ext| return true if filename.downcase.end_with?(ext) }
42
- false
43
- end
44
-
45
- def self.is_video?(filename)
46
- extens = [".mp4", ".ogv"]
47
- extens.each { |ext| return true if filename.downcase.end_with?(ext) }
48
- false
49
- end
50
-
51
- def self.is_url?(value)
52
- return true if value.start_with?("https://", "http://")
53
-
54
- false
55
- end
56
-
57
- def self.load_audio(value, localdir)
58
- output = {text: :error, file: :none, type: :audio}
59
-
60
- if is_url? value
61
- output[:text] = "<audio src=\"#{value}\" controls></audio>"
62
- output[:file] = :none
63
- output[:type] = :url
64
- return output
65
- end
66
-
67
- filepath = File.join(localdir, value)
68
- unless File.exist?(filepath)
69
- Logger.error "EmbebbedFile: Audio file no exists (#{filepath})"
70
- exit 1
71
- end
72
- output[:text] = '<audio controls><source src="@@PLUGINFILE@@/' + File.basename(filepath) \
73
- + '">Your browser does not support the audio tag.</audio>'
74
- output[:file] = '<file name="' + File.basename(filepath) \
75
- + '" path="/" encoding="base64">' \
76
- + Base64.strict_encode64(File.open(filepath, "rb").read) \
77
- + "</file>"
78
- output[:type] = :audio
79
- output
80
- end
81
-
82
- def self.load_image(value, localdir)
83
- output = {text: :error, file: :none, type: :image}
84
-
85
- if is_url? value
86
- output[:text] = "<img src=\"#{value}\" alt=\"image\" width=\"400\" height=\"300\">"
87
- output[:file] = :none
88
- output[:type] = :url
89
- return output
90
- end
91
-
92
- filepath = File.join(localdir, value)
93
- unless File.exist?(filepath)
94
- Logger.error "EmbeddedFile: Unknown file (#{filepath})"
95
- exit 1
96
- end
97
- output[:text] = '<img src="@@PLUGINFILE@@/' + File.basename(filepath) \
98
- + '" alt="imagen" class="img-responsive atto_image_button_text-bottom">'
99
- output[:file] = '<file name="' + File.basename(filepath) \
100
- + '" path="/" encoding="base64">' \
101
- + Base64.strict_encode64(File.open(filepath, "rb").read) \
102
- + "</file>"
103
- output[:type] = :image
104
- output
105
- end
106
-
107
- def self.load_video(value, localdir)
108
- output = {text: :error, file: :none, type: :video}
109
- if is_url? value
110
- output[:text] = "<video controls width=\"400\" height=\"300\">" \
111
- + "<source src=\"#{value}\"/></video>"
112
- output[:file] = :none
113
- output[:type] = :url
114
- return output
115
- end
116
-
117
- filepath = File.join(localdir, value)
118
- unless File.exist?(filepath)
119
- Logger.error "Unknown file (#{filepath})"
120
- exit 1
121
- end
122
- output[:text] = '<video controls><source src="@@PLUGINFILE@@/' \
123
- + File.basename(filepath) \
124
- + '"/>Your browser does not support the video tag.</video>'
125
- output[:file] = '<file name="' \
126
- + File.basename(filepath) \
127
- + '" path="/" encoding="base64">' \
128
- + Base64.strict_encode64(File.open(filepath, "rb").read) \
129
- + "</file>"
130
- output[:type] = :video
131
- output
132
- end
133
- end