asker-tool 2.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README.md +53 -0
- data/bin/asker +4 -0
- data/docs/changelog/v2.1.md +99 -0
- data/docs/commands.md +15 -0
- data/docs/contributions.md +18 -0
- data/docs/history.md +40 -0
- data/docs/idea.md +44 -0
- data/docs/inputs/README.md +39 -0
- data/docs/inputs/code.md +69 -0
- data/docs/inputs/concepts.md +142 -0
- data/docs/inputs/jedi.md +68 -0
- data/docs/inputs/tables.md +112 -0
- data/docs/inputs/templates.md +87 -0
- data/docs/install/README.md +38 -0
- data/docs/install/manual.md +26 -0
- data/docs/install/scripts.md +26 -0
- data/docs/revise/asker-file.md +41 -0
- data/docs/revise/buenas-practicas/01-convocatoria.md +30 -0
- data/docs/revise/buenas-practicas/02-formulario.md +35 -0
- data/docs/revise/buenas-practicas/03-descripcion.md +63 -0
- data/docs/revise/buenas-practicas/04-resultados.md +17 -0
- data/docs/revise/buenas-practicas/05-reproducir.md +10 -0
- data/docs/revise/ejemplos/01/README.md +27 -0
- data/docs/revise/ejemplos/02/README.md +31 -0
- data/docs/revise/ejemplos/03/README.md +31 -0
- data/docs/revise/ejemplos/04/README.md +37 -0
- data/docs/revise/ejemplos/05/README.md +25 -0
- data/docs/revise/ejemplos/06/README.md +43 -0
- data/docs/revise/ejemplos/README.md +11 -0
- data/docs/revise/projects.md +74 -0
- data/lib/asker.rb +103 -0
- data/lib/asker/ai/ai.rb +70 -0
- data/lib/asker/ai/ai_calculate.rb +55 -0
- data/lib/asker/ai/concept_ai.rb +49 -0
- data/lib/asker/ai/question.rb +58 -0
- data/lib/asker/ai/stages/base_stage.rb +16 -0
- data/lib/asker/ai/stages/main.rb +8 -0
- data/lib/asker/ai/stages/stage_b.rb +87 -0
- data/lib/asker/ai/stages/stage_d.rb +160 -0
- data/lib/asker/ai/stages/stage_f.rb +156 -0
- data/lib/asker/ai/stages/stage_i.rb +140 -0
- data/lib/asker/ai/stages/stage_s.rb +52 -0
- data/lib/asker/ai/stages/stage_t.rb +170 -0
- data/lib/asker/application.rb +30 -0
- data/lib/asker/checker.rb +356 -0
- data/lib/asker/cli.rb +85 -0
- data/lib/asker/code/ai/base_code_ai.rb +48 -0
- data/lib/asker/code/ai/code_ai_factory.rb +26 -0
- data/lib/asker/code/ai/javascript_code_ai.rb +167 -0
- data/lib/asker/code/ai/python_code_ai.rb +167 -0
- data/lib/asker/code/ai/ruby_code_ai.rb +169 -0
- data/lib/asker/code/ai/sql_code_ai.rb +69 -0
- data/lib/asker/code/code.rb +53 -0
- data/lib/asker/data/column.rb +62 -0
- data/lib/asker/data/concept.rb +183 -0
- data/lib/asker/data/data_field.rb +87 -0
- data/lib/asker/data/row.rb +93 -0
- data/lib/asker/data/table.rb +96 -0
- data/lib/asker/data/template.rb +65 -0
- data/lib/asker/data/world.rb +53 -0
- data/lib/asker/exporter/code_gift_exporter.rb +35 -0
- data/lib/asker/exporter/code_screen_exporter.rb +45 -0
- data/lib/asker/exporter/concept_ai_gift_exporter.rb +33 -0
- data/lib/asker/exporter/concept_ai_screen_exporter.rb +115 -0
- data/lib/asker/exporter/concept_ai_yaml_exporter.rb +33 -0
- data/lib/asker/exporter/concept_doc_exporter.rb +21 -0
- data/lib/asker/exporter/concept_screen_exporter.rb +25 -0
- data/lib/asker/exporter/main.rb +9 -0
- data/lib/asker/files/config.ini +40 -0
- data/lib/asker/formatter/code_string_formatter.rb +16 -0
- data/lib/asker/formatter/concept_doc_formatter.rb +37 -0
- data/lib/asker/formatter/concept_string_formatter.rb +66 -0
- data/lib/asker/formatter/question_gift_formatter.rb +65 -0
- data/lib/asker/formatter/question_hash_formatter.rb +40 -0
- data/lib/asker/formatter/question_moodlexml_formatter.rb +71 -0
- data/lib/asker/formatter/rb2haml_formatter.rb +26 -0
- data/lib/asker/lang/lang.rb +42 -0
- data/lib/asker/lang/lang_factory.rb +19 -0
- data/lib/asker/lang/text_actions.rb +150 -0
- data/lib/asker/loader/code_loader.rb +53 -0
- data/lib/asker/loader/content_loader.rb +101 -0
- data/lib/asker/loader/directory_loader.rb +58 -0
- data/lib/asker/loader/file_loader.rb +33 -0
- data/lib/asker/loader/image_url_loader.rb +61 -0
- data/lib/asker/loader/input_loader.rb +24 -0
- data/lib/asker/loader/project_loader.rb +71 -0
- data/lib/asker/logger.rb +21 -0
- data/lib/asker/project.rb +170 -0
- metadata +261 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
|
2
|
+
require_relative '../../lang/lang_factory'
|
3
|
+
require_relative '../../ai/question'
|
4
|
+
require_relative 'base_code_ai'
|
5
|
+
|
6
|
+
class SQLCodeAI < BaseCodeAI
|
7
|
+
def initialize(data_object)
|
8
|
+
@data_object = data_object
|
9
|
+
@lines = data_object.lines
|
10
|
+
@lang = LangFactory.instance.get('sql')
|
11
|
+
@num = 0
|
12
|
+
@questions = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def make_comment_error
|
16
|
+
error_lines = []
|
17
|
+
questions = []
|
18
|
+
@lines.each_with_index do |line,index|
|
19
|
+
if line.include?('//')
|
20
|
+
lines = clone_array @lines
|
21
|
+
lines[index].sub!('//','').strip!
|
22
|
+
|
23
|
+
q = Question.new(:short)
|
24
|
+
q.name = "#{name}-#{num}-code1uncomment"
|
25
|
+
q.text = @lang.text_for(:code1,lines_to_html(lines))
|
26
|
+
q.shorts << (index+1)
|
27
|
+
q.feedback = 'Comment symbol removed'
|
28
|
+
questions << q
|
29
|
+
elsif line.strip.size>0
|
30
|
+
lines = clone_array @lines
|
31
|
+
lines[index]='// ' + lines[index]
|
32
|
+
|
33
|
+
q = Question.new(:short)
|
34
|
+
q.name = "#{name}-#{num}-code1comment"
|
35
|
+
q.text = @lang.text_for(:code1,lines_to_html(lines))
|
36
|
+
q.shorts << (index+1)
|
37
|
+
q.feedback = 'Comment symbol added'
|
38
|
+
questions << q
|
39
|
+
end
|
40
|
+
end
|
41
|
+
questions
|
42
|
+
end
|
43
|
+
|
44
|
+
def make_keyword_error
|
45
|
+
error_lines = []
|
46
|
+
questions = []
|
47
|
+
|
48
|
+
@lang.mistakes.each_pair do |key,values|
|
49
|
+
v = values.split(',')
|
50
|
+
v.each do |value|
|
51
|
+
@lines.each_with_index do |line,index|
|
52
|
+
error_lines << index if line.include?(key.to_s)
|
53
|
+
end
|
54
|
+
|
55
|
+
error_lines.each do |index|
|
56
|
+
lines = clone_array @lines
|
57
|
+
lines[index].sub!(key.to_s, value)
|
58
|
+
q = Question.new(:short)
|
59
|
+
q.name = "#{name}-#{num}-code1keyword"
|
60
|
+
q.text = @lang.text_for(:code1,lines_to_html(lines))
|
61
|
+
q.shorts << (index+1)
|
62
|
+
q.feedback = "Keyword error: '#{value}' must be '#{key}'"
|
63
|
+
questions << q
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
questions
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require_relative 'ai/code_ai_factory'
|
2
|
+
require_relative '../project'
|
3
|
+
require_relative '../formatter/code_string_formatter'
|
4
|
+
|
5
|
+
# Contains code data input
|
6
|
+
class Code
|
7
|
+
attr_reader :dirname, :filename, :type
|
8
|
+
attr_accessor :process, :features
|
9
|
+
attr_reader :lines, :questions
|
10
|
+
|
11
|
+
def initialize(dirname, filename, type)
|
12
|
+
@dirname = dirname
|
13
|
+
@filename = filename
|
14
|
+
@type = type
|
15
|
+
@filepath = File.join(@dirname, @filename)
|
16
|
+
@process = false
|
17
|
+
@features = []
|
18
|
+
@lines = load(@filepath)
|
19
|
+
@questions = []
|
20
|
+
@code_ai = CodeAIFactory.get(self)
|
21
|
+
end
|
22
|
+
|
23
|
+
def process?
|
24
|
+
@process
|
25
|
+
end
|
26
|
+
|
27
|
+
def make_questions
|
28
|
+
return unless process?
|
29
|
+
@questions += @code_ai.make_questions
|
30
|
+
end
|
31
|
+
|
32
|
+
def lines_to_s(lines)
|
33
|
+
out = ''
|
34
|
+
lines.each_with_index do |line, index|
|
35
|
+
out << format("%2d| #{line}\n", (index + 1))
|
36
|
+
end
|
37
|
+
out
|
38
|
+
end
|
39
|
+
|
40
|
+
def debug
|
41
|
+
out = CodeStringFormatter.to_s(self)
|
42
|
+
p = Project.instance
|
43
|
+
p.verbose out
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def load(filepath)
|
49
|
+
return if filepath.nil?
|
50
|
+
content = File.read(filepath)
|
51
|
+
content.split("\n")
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
# Contain data information for every column
|
5
|
+
# Params:
|
6
|
+
# * +pRow+ - Parent row for this column
|
7
|
+
# * +index+ - Sequence order (Integer)
|
8
|
+
# * +xml_data+ - XML input data
|
9
|
+
class Column
|
10
|
+
attr_reader :row, :index, :id, :raw, :lang, :type, :simple
|
11
|
+
|
12
|
+
def initialize(row, index, xml_data)
|
13
|
+
@row = row
|
14
|
+
@index = index
|
15
|
+
@id = @row.id + '.' + @index.to_s
|
16
|
+
@raw = ''
|
17
|
+
@lang = @row.langs[@index]
|
18
|
+
@type = @row.types[@index]
|
19
|
+
@simple = { lang: true, type: true }
|
20
|
+
read_data_from_xml(xml_data)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_html
|
24
|
+
case @type
|
25
|
+
when 'text'
|
26
|
+
return @raw
|
27
|
+
when 'image_url'
|
28
|
+
return "<img src=\"#{raw}\" alt\=\"image\">"
|
29
|
+
when 'textfile_path'
|
30
|
+
return "<pre>#{raw}</pre>"
|
31
|
+
else
|
32
|
+
return "ERROR type #{@type}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def read_data_from_xml(xml_data)
|
39
|
+
raise '[ERROR] Column XML data with elements!' if xml_data.elements.count.positive?
|
40
|
+
|
41
|
+
@raw = xml_data.text.strip.to_s
|
42
|
+
|
43
|
+
# read attributes from XML input data
|
44
|
+
if xml_data.attributes['lang']
|
45
|
+
code = xml_data.attributes['lang'].strip
|
46
|
+
if code != @lang.code
|
47
|
+
@lang = LangFactory.instance.get(code)
|
48
|
+
@simple[:lang] = false
|
49
|
+
@row.simple_off(:lang)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
if xml_data.attributes['type']
|
54
|
+
type = xml_data.attributes['type'].strip
|
55
|
+
if type != @type.to_s
|
56
|
+
@type = type
|
57
|
+
@simple[:type] = false
|
58
|
+
@row.simple_off(:type)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rainbow'
|
4
|
+
require 'rexml/document'
|
5
|
+
|
6
|
+
require_relative '../project'
|
7
|
+
require_relative '../lang/lang_factory'
|
8
|
+
require_relative 'table'
|
9
|
+
require_relative 'data_field'
|
10
|
+
|
11
|
+
# Store Concept information
|
12
|
+
class Concept
|
13
|
+
attr_reader :id, :lang, :context
|
14
|
+
attr_reader :names, :type, :filename
|
15
|
+
attr_reader :data
|
16
|
+
attr_accessor :process
|
17
|
+
|
18
|
+
@@id = 0
|
19
|
+
|
20
|
+
def initialize(xml_data, filename, lang_code = 'en', context = [])
|
21
|
+
@@id += 1
|
22
|
+
@id = @@id
|
23
|
+
|
24
|
+
@filename = filename
|
25
|
+
@process = false
|
26
|
+
@lang = LangFactory.instance.get(lang_code)
|
27
|
+
|
28
|
+
if context.class == Array
|
29
|
+
@context = context
|
30
|
+
elsif context.nil?
|
31
|
+
@context = []
|
32
|
+
else
|
33
|
+
@context = context.split(',')
|
34
|
+
@context.collect!(&:strip)
|
35
|
+
end
|
36
|
+
@names = ['concept.' + @id.to_s]
|
37
|
+
@type = 'text'
|
38
|
+
|
39
|
+
@data = {}
|
40
|
+
@data[:tags] = []
|
41
|
+
@data[:texts] = []
|
42
|
+
@data[:images] = [] # TODO: By now, We'll treat images separated from texts
|
43
|
+
@data[:textfile_paths] = [] # TODO: By now, We'll treat this separated from texts
|
44
|
+
@data[:tables] = []
|
45
|
+
@data[:neighbors] = []
|
46
|
+
@data[:reference_to] = []
|
47
|
+
@data[:referenced_by] = []
|
48
|
+
|
49
|
+
read_data_from_xml(xml_data)
|
50
|
+
end
|
51
|
+
|
52
|
+
def name(option = :raw)
|
53
|
+
DataField.new(@names[0], @id, @type).get(option)
|
54
|
+
end
|
55
|
+
|
56
|
+
def text
|
57
|
+
@data[:texts][0] || '...'
|
58
|
+
end
|
59
|
+
|
60
|
+
def process?
|
61
|
+
@process
|
62
|
+
end
|
63
|
+
|
64
|
+
def try_adding_neighbor(other)
|
65
|
+
p = calculate_nearness_to_concept(other)
|
66
|
+
return if p.zero?
|
67
|
+
|
68
|
+
@data[:neighbors] << { concept: other, value: p }
|
69
|
+
# Sort neighbors list
|
70
|
+
@data[:neighbors].sort! { |a, b| a[:value] <=> b[:value] }
|
71
|
+
@data[:neighbors].reverse!
|
72
|
+
end
|
73
|
+
|
74
|
+
def calculate_nearness_to_concept(other)
|
75
|
+
weights = Project.instance.formula_weights
|
76
|
+
|
77
|
+
li_max1 = @context.count
|
78
|
+
li_max2 = @data[:tags].count
|
79
|
+
li_max3 = @data[:tables].count
|
80
|
+
|
81
|
+
lf_alike1 = lf_alike2 = lf_alike3 = 0.0
|
82
|
+
|
83
|
+
# check if exists this items from concept1 into concept2
|
84
|
+
@context.each { |i| lf_alike1 += 1.0 unless other.context.index(i).nil? }
|
85
|
+
@data[:tags].each { |i| lf_alike2 += 1.0 unless other.tags.index(i).nil? }
|
86
|
+
@data[:tables].each { |i| lf_alike3 += 1.0 unless other.tables.index(i).nil? }
|
87
|
+
|
88
|
+
lf_alike = (lf_alike1 * weights[0] + lf_alike2 * weights[1] + lf_alike3 * weights[2])
|
89
|
+
li_max = (li_max1 * weights[0] + li_max2 * weights[1] + li_max3 * weights[2])
|
90
|
+
(lf_alike * 100.0 / li_max)
|
91
|
+
end
|
92
|
+
|
93
|
+
def try_adding_references(other)
|
94
|
+
reference_to = 0
|
95
|
+
@data[:tags].each { |i| reference_to += 1 unless other.names.index(i.downcase).nil? }
|
96
|
+
@data[:texts].each do |t|
|
97
|
+
text = t.clone
|
98
|
+
text.split(' ').each do |word|
|
99
|
+
reference_to += 1 unless other.names.index(word.downcase).nil?
|
100
|
+
end
|
101
|
+
end
|
102
|
+
if reference_to.positive?
|
103
|
+
@data[:reference_to] << other.name
|
104
|
+
other.data[:referenced_by] << name
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def method_missing(method)
|
109
|
+
@data[method]
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def read_data_from_xml(xml_data)
|
115
|
+
xml_data.elements.each do |i|
|
116
|
+
case i.name
|
117
|
+
when 'names'
|
118
|
+
process_names(i)
|
119
|
+
when 'tags'
|
120
|
+
process_tags(i)
|
121
|
+
when 'context'
|
122
|
+
# DEPRECATED: Don't use xml tag <context>
|
123
|
+
# instead define it as attibute of root xml tag
|
124
|
+
process_context(i)
|
125
|
+
when 'text'
|
126
|
+
# DEPRECATED: Use xml tag <def> instead of <text>
|
127
|
+
process_text(i)
|
128
|
+
when 'def'
|
129
|
+
process_def(i)
|
130
|
+
when 'table'
|
131
|
+
@data[:tables] << Table.new(self, i)
|
132
|
+
else
|
133
|
+
text = " [ERROR] <#{i.name}> unkown XML attribute for concept #{name}"
|
134
|
+
msg = Rainbow(text).color(:red)
|
135
|
+
Project.instance.verbose msg
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def process_names(value)
|
141
|
+
@names = []
|
142
|
+
j = value.text.split(',')
|
143
|
+
j.each { |k| @names << k.strip }
|
144
|
+
@type = value.attributes['type'].strip if value.attributes['type']
|
145
|
+
end
|
146
|
+
|
147
|
+
def process_tags(value)
|
148
|
+
raise '[Error] tags label empty!' if value.text.nil? || value.text.size.zero?
|
149
|
+
@data[:tags] = value.text.split(',')
|
150
|
+
@data[:tags].collect!(&:strip)
|
151
|
+
end
|
152
|
+
|
153
|
+
def process_context(value)
|
154
|
+
msg = ' │ ' + Rainbow(' [DEPRECATED] Concept ').yellow
|
155
|
+
msg += Rainbow(name).yellow.bright
|
156
|
+
msg += Rainbow(' move <context> tag info to <map>.').yellow
|
157
|
+
Project.instance.verbose msg
|
158
|
+
@context = value.text.split(',')
|
159
|
+
@context.collect!(&:strip)
|
160
|
+
end
|
161
|
+
|
162
|
+
def process_text(value)
|
163
|
+
msg = ' │ ' + Rainbow(' [DEPRECATED] Concept ').yellow
|
164
|
+
msg += Rainbow(name).yellow.bright
|
165
|
+
msg += Rainbow(' replace <text> tag by <def>.').yellow
|
166
|
+
Project.instance.verbose msg
|
167
|
+
@data[:texts] << value.text.strip
|
168
|
+
end
|
169
|
+
|
170
|
+
def process_def(value)
|
171
|
+
case value.attributes['type']
|
172
|
+
when 'image'
|
173
|
+
msg = "[DEBUG] Concept#read_xml: image #{Rainbow(value.text).bright}"
|
174
|
+
Project.instance.verbose Rainbow(msg).yellow
|
175
|
+
when 'image_url'
|
176
|
+
@data[:images] << value.text.strip
|
177
|
+
when 'textfile_path'
|
178
|
+
@data[:textfile_paths] << value.text.strip
|
179
|
+
else
|
180
|
+
@data[:texts] << value.text.strip
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Contain data information for every field
|
4
|
+
# Params:
|
5
|
+
# * +data+ - Data (Text). This is the field content
|
6
|
+
# * +id+ - Identifier (Integer)
|
7
|
+
# * +type+ - May be "text", "textfile_path", "textfile_url" or "image_url"
|
8
|
+
class DataField
|
9
|
+
attr_reader :id, :type
|
10
|
+
|
11
|
+
def initialize(data, id, type)
|
12
|
+
@data = data
|
13
|
+
@id = id.to_i # TODO: revise where it comes from? Is it unique value?
|
14
|
+
@type = type.to_sym
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(option = :raw)
|
18
|
+
case @type
|
19
|
+
when :text
|
20
|
+
return get_text(option)
|
21
|
+
when :textfile_path
|
22
|
+
return get_textfile_path(option)
|
23
|
+
when :textfile_url
|
24
|
+
return get_textfile_url(option)
|
25
|
+
when :image_url
|
26
|
+
return get_image_url(option)
|
27
|
+
end
|
28
|
+
raise ".get: data=#{@data}, type=#{@type}, option=#{option}"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def get_text(option)
|
34
|
+
return to_screen(@data) if option == :screen
|
35
|
+
|
36
|
+
@data
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_textfile_path(option)
|
40
|
+
case option
|
41
|
+
when :raw
|
42
|
+
return @data
|
43
|
+
when :id
|
44
|
+
return "textfile_path.#{@id}"
|
45
|
+
when :decorated
|
46
|
+
content = File.new(@data).read
|
47
|
+
return "<pre>\n#{content}</pre>"
|
48
|
+
when :screen
|
49
|
+
return to_screen(@data)
|
50
|
+
end
|
51
|
+
raise ".get_textfile_path: data=#{@data}, type=#{@type}, option=#{option}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_textfile_url(option)
|
55
|
+
case option
|
56
|
+
when :raw
|
57
|
+
return @data
|
58
|
+
when :id
|
59
|
+
return "textfile_url.#{@id}"
|
60
|
+
when :decorated
|
61
|
+
return "<a href=\"#{@data}\">Textfile URL</a>"
|
62
|
+
when :screen
|
63
|
+
return to_screen(@data)
|
64
|
+
end
|
65
|
+
raise ".get_textfile_url: data=#{@data}, type=#{@type}, option=#{option}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_image_url(option)
|
69
|
+
case option
|
70
|
+
when :raw
|
71
|
+
return @data
|
72
|
+
when :id
|
73
|
+
return "image_url.#{@id}"
|
74
|
+
when :decorated
|
75
|
+
return "<img src=\"#{@data}\" alt=\"image\">"
|
76
|
+
when :screen
|
77
|
+
return to_screen(@data)
|
78
|
+
end
|
79
|
+
raise ".get_image_url: data=#{@data}, type=#{@type}, option=#{option}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_screen(text)
|
83
|
+
return text[0, 7] + '...' + text[-15, 15] if text.size > 25
|
84
|
+
|
85
|
+
text
|
86
|
+
end
|
87
|
+
end
|