asker-tool 2.7.2 → 2.8.0

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