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 +4 -4
- data/lib/asker/ai/problem/customizer.rb +36 -0
- data/lib/asker/ai/problem/problem_ai.rb +9 -239
- data/lib/asker/ai/problem/stage_answers.rb +104 -0
- data/lib/asker/ai/problem/stage_base.rb +34 -0
- data/lib/asker/ai/problem/stage_steps.rb +121 -0
- data/lib/asker/application.rb +1 -0
- data/lib/asker/check_input/check_haml_data.rb +2 -2
- data/lib/asker/cli.rb +2 -0
- data/lib/asker/data/concept.rb +3 -3
- data/lib/asker/data/problem.rb +10 -2
- data/lib/asker/data/project_data.rb +2 -1
- data/lib/asker/displayer/code_displayer.rb +2 -2
- data/lib/asker/displayer/concept_ai_displayer.rb +5 -3
- data/lib/asker/displayer/concept_displayer.rb +2 -2
- data/lib/asker/displayer/problem_displayer.rb +14 -7
- data/lib/asker/displayer/stats_displayer.rb +3 -3
- data/lib/asker/formatter/concept_string_formatter.rb +0 -1
- data/lib/asker/loader/embedded_file/loader.rb +103 -0
- data/lib/asker/loader/embedded_file/type.rb +51 -0
- data/lib/asker/loader/problem_loader.rb +2 -0
- data/lib/asker/start.rb +1 -1
- data/lib/asker/version.rb +1 -1
- metadata +8 -3
- data/lib/asker/loader/embedded_file.rb +0 -133
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a70a91ae23f461cc6740e7938555a205ca50fa9f7b910a6af4e4d6b873ff3ba1
|
4
|
+
data.tar.gz: 79a7f6dccf7faea3bc97b7e21b2bc5680d52b887b0aaa3eef52a6c0302f9d5ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
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
|
data/lib/asker/application.rb
CHANGED
@@ -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
|
|
data/lib/asker/data/concept.rb
CHANGED
@@ -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.
|
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.
|
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!"
|
data/lib/asker/data/problem.rb
CHANGED
@@ -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 =
|
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
|
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
|
-
|
4
|
+
class CodeDisplayer
|
5
5
|
##
|
6
6
|
# Show all "code" data on screen
|
7
7
|
# @param codes (Array) List of "code" data
|
8
|
-
def
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
5
|
+
class ConceptDisplayer
|
6
6
|
##
|
7
7
|
# Show concepts on screen
|
8
8
|
# @param concepts (Array) List of concept data
|
9
|
-
def
|
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
|
-
|
4
|
+
class ProblemDisplayer
|
5
5
|
##
|
6
6
|
# Show all "problem" data on screen
|
7
7
|
# @param problems (Array) List of "problems" data
|
8
|
-
def
|
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
|
-
|
30
|
-
|
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.
|
14
|
-
CodeDisplayer.
|
15
|
-
ProblemDisplayer.
|
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
|
@@ -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
|
data/lib/asker/start.rb
CHANGED
data/lib/asker/version.rb
CHANGED
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.
|
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-
|
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
|