asker-tool 2.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +674 -0
  3. data/README.md +53 -0
  4. data/bin/asker +4 -0
  5. data/docs/changelog/v2.1.md +99 -0
  6. data/docs/commands.md +15 -0
  7. data/docs/contributions.md +18 -0
  8. data/docs/history.md +40 -0
  9. data/docs/idea.md +44 -0
  10. data/docs/inputs/README.md +39 -0
  11. data/docs/inputs/code.md +69 -0
  12. data/docs/inputs/concepts.md +142 -0
  13. data/docs/inputs/jedi.md +68 -0
  14. data/docs/inputs/tables.md +112 -0
  15. data/docs/inputs/templates.md +87 -0
  16. data/docs/install/README.md +38 -0
  17. data/docs/install/manual.md +26 -0
  18. data/docs/install/scripts.md +26 -0
  19. data/docs/revise/asker-file.md +41 -0
  20. data/docs/revise/buenas-practicas/01-convocatoria.md +30 -0
  21. data/docs/revise/buenas-practicas/02-formulario.md +35 -0
  22. data/docs/revise/buenas-practicas/03-descripcion.md +63 -0
  23. data/docs/revise/buenas-practicas/04-resultados.md +17 -0
  24. data/docs/revise/buenas-practicas/05-reproducir.md +10 -0
  25. data/docs/revise/ejemplos/01/README.md +27 -0
  26. data/docs/revise/ejemplos/02/README.md +31 -0
  27. data/docs/revise/ejemplos/03/README.md +31 -0
  28. data/docs/revise/ejemplos/04/README.md +37 -0
  29. data/docs/revise/ejemplos/05/README.md +25 -0
  30. data/docs/revise/ejemplos/06/README.md +43 -0
  31. data/docs/revise/ejemplos/README.md +11 -0
  32. data/docs/revise/projects.md +74 -0
  33. data/lib/asker.rb +103 -0
  34. data/lib/asker/ai/ai.rb +70 -0
  35. data/lib/asker/ai/ai_calculate.rb +55 -0
  36. data/lib/asker/ai/concept_ai.rb +49 -0
  37. data/lib/asker/ai/question.rb +58 -0
  38. data/lib/asker/ai/stages/base_stage.rb +16 -0
  39. data/lib/asker/ai/stages/main.rb +8 -0
  40. data/lib/asker/ai/stages/stage_b.rb +87 -0
  41. data/lib/asker/ai/stages/stage_d.rb +160 -0
  42. data/lib/asker/ai/stages/stage_f.rb +156 -0
  43. data/lib/asker/ai/stages/stage_i.rb +140 -0
  44. data/lib/asker/ai/stages/stage_s.rb +52 -0
  45. data/lib/asker/ai/stages/stage_t.rb +170 -0
  46. data/lib/asker/application.rb +30 -0
  47. data/lib/asker/checker.rb +356 -0
  48. data/lib/asker/cli.rb +85 -0
  49. data/lib/asker/code/ai/base_code_ai.rb +48 -0
  50. data/lib/asker/code/ai/code_ai_factory.rb +26 -0
  51. data/lib/asker/code/ai/javascript_code_ai.rb +167 -0
  52. data/lib/asker/code/ai/python_code_ai.rb +167 -0
  53. data/lib/asker/code/ai/ruby_code_ai.rb +169 -0
  54. data/lib/asker/code/ai/sql_code_ai.rb +69 -0
  55. data/lib/asker/code/code.rb +53 -0
  56. data/lib/asker/data/column.rb +62 -0
  57. data/lib/asker/data/concept.rb +183 -0
  58. data/lib/asker/data/data_field.rb +87 -0
  59. data/lib/asker/data/row.rb +93 -0
  60. data/lib/asker/data/table.rb +96 -0
  61. data/lib/asker/data/template.rb +65 -0
  62. data/lib/asker/data/world.rb +53 -0
  63. data/lib/asker/exporter/code_gift_exporter.rb +35 -0
  64. data/lib/asker/exporter/code_screen_exporter.rb +45 -0
  65. data/lib/asker/exporter/concept_ai_gift_exporter.rb +33 -0
  66. data/lib/asker/exporter/concept_ai_screen_exporter.rb +115 -0
  67. data/lib/asker/exporter/concept_ai_yaml_exporter.rb +33 -0
  68. data/lib/asker/exporter/concept_doc_exporter.rb +21 -0
  69. data/lib/asker/exporter/concept_screen_exporter.rb +25 -0
  70. data/lib/asker/exporter/main.rb +9 -0
  71. data/lib/asker/files/config.ini +40 -0
  72. data/lib/asker/formatter/code_string_formatter.rb +16 -0
  73. data/lib/asker/formatter/concept_doc_formatter.rb +37 -0
  74. data/lib/asker/formatter/concept_string_formatter.rb +66 -0
  75. data/lib/asker/formatter/question_gift_formatter.rb +65 -0
  76. data/lib/asker/formatter/question_hash_formatter.rb +40 -0
  77. data/lib/asker/formatter/question_moodlexml_formatter.rb +71 -0
  78. data/lib/asker/formatter/rb2haml_formatter.rb +26 -0
  79. data/lib/asker/lang/lang.rb +42 -0
  80. data/lib/asker/lang/lang_factory.rb +19 -0
  81. data/lib/asker/lang/text_actions.rb +150 -0
  82. data/lib/asker/loader/code_loader.rb +53 -0
  83. data/lib/asker/loader/content_loader.rb +101 -0
  84. data/lib/asker/loader/directory_loader.rb +58 -0
  85. data/lib/asker/loader/file_loader.rb +33 -0
  86. data/lib/asker/loader/image_url_loader.rb +61 -0
  87. data/lib/asker/loader/input_loader.rb +24 -0
  88. data/lib/asker/loader/project_loader.rb +71 -0
  89. data/lib/asker/logger.rb +21 -0
  90. data/lib/asker/project.rb +170 -0
  91. metadata +261 -0
@@ -0,0 +1,93 @@
1
+
2
+ require_relative 'column'
3
+
4
+ class Row
5
+ attr_reader :table, :index, :id
6
+ attr_reader :langs, :types, :raws, :columns
7
+ attr_reader :simple
8
+
9
+ def initialize( table, index, xml_data )
10
+ @table = table
11
+ @index = index
12
+ @id = @table.id + "." + @index.to_s
13
+ @langs = @table.langs
14
+ @types = @table.types
15
+ @raws = []
16
+ @columns = []
17
+ @simple = { :lang => true, :type => true }
18
+ read_data_from_xml(xml_data)
19
+ end
20
+
21
+ def simple_off(option)
22
+ @simple[option]=false
23
+ @table.simple_off(option)
24
+ end
25
+
26
+ private
27
+
28
+ def read_data_from_xml(pXMLdata)
29
+ if pXMLdata.elements.count==0 then
30
+ build_row_with_1_column(pXMLdata)
31
+ else
32
+ build_row_with_N_columns(pXMLdata)
33
+ end
34
+
35
+ raise "[ERROR] Row: #{pXMLdata}" if @columns.size!=@table.fields.size
36
+ end
37
+
38
+ def build_row_with_1_column(pXMLdata)
39
+ # When row tag only has text, we add this text as one value array
40
+ # This is usefull for tables with only one columns
41
+ @columns = [ Column.new( self, @raws.size, pXMLdata) ]
42
+ @raws = [ pXMLdata.text.strip.to_s ]
43
+
44
+ #read attributes from XML data
45
+ if pXMLdata.attributes['lang'] then
46
+ code = pXMLdata.attributes['lang'].strip
47
+ if code != @langs[0].code then
48
+ @langs = [ LangFactory.instance.get(code) ]
49
+ @simple[:lang]= false
50
+ @table.simple_off(:lang)
51
+ end
52
+ end
53
+
54
+ if pXMLdata.attributes['type'] then
55
+ type = pXMLdata.attributes['type'].strip
56
+ if type != @types[0] then
57
+ @types = [ type ]
58
+ @simple[:type]= false
59
+ @table.simple_off(:type)
60
+ end
61
+ end
62
+ end
63
+
64
+ def build_row_with_N_columns(pXMLdata)
65
+ pXMLdata.elements.each do |i|
66
+ case i.name
67
+ when 'lang'
68
+ j = i.text.split(",")
69
+ codes = @langs.map {|i| i.code }
70
+
71
+ if j.join(",")!=codes.join(",")
72
+ @langs = []
73
+ j.each { |k| @langs << LangFactory.instance.get(k.strip.to_s) }
74
+ @simple[:lang]=false
75
+ @table.simple_off(:lang)
76
+ end
77
+ when 'type'
78
+ j = i.text.split(",")
79
+ if j.join(",")!=@types.join(",") then
80
+ @types = []
81
+ j.each { |k| @types << k.strip.to_s }
82
+ @simple[:type]=false
83
+ @table.simple_off(:type)
84
+ end
85
+ when 'col' # When row tag has several columns, we add every value to the array
86
+ #Column Objects
87
+ @columns << Column.new( self, @raws.size, i)
88
+ @raws << i.text.to_s
89
+ end
90
+ end
91
+ end
92
+
93
+ end
@@ -0,0 +1,96 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative 'row'
4
+ require_relative 'template'
5
+
6
+ # Contains data table information
7
+ class Table
8
+ attr_reader :name, :id
9
+ attr_reader :fields, :langs, :types, :sequence
10
+ attr_reader :datarows, :rows
11
+ attr_reader :simple
12
+
13
+ def initialize(concept, xml_data)
14
+ @concept = concept
15
+
16
+ # read attributes from XML data
17
+ t = xml_data.attributes['fields'].to_s.strip.split(',')
18
+ t.each { |i| i.strip! }
19
+ @fields = t || []
20
+ @types = ['text'] * @fields.size
21
+ @langs = [@concept.lang] * @fields.size
22
+
23
+ @name = ''
24
+ @fields.each { |i| @name=@name + '$' + i.to_s.strip.downcase}
25
+ @id = @concept.name.to_s + '.' + @name
26
+ @simple = { lang: true, type: true }
27
+
28
+ @sequence = []
29
+ if xml_data.attributes['sequence']
30
+ t = xml_data.attributes['sequence'].to_s || ""
31
+ @sequence = t.split(",")
32
+ # puts "[DEPRECATED] sequence attr on table <#{@name}>"
33
+ end
34
+
35
+ @datarows = [] #DEV experiment replace row data with row objects
36
+ read_data_from_xml(xml_data)
37
+ @rows = @datarows.map { |r| r.raws }
38
+ end
39
+
40
+ def to_s
41
+ @name.to_s
42
+ end
43
+
44
+ def sequence?
45
+ @sequence.size > 0
46
+ end
47
+
48
+ def types(index = :all)
49
+ @types = (['text'] * @fields.size) if @types.nil?
50
+ return @types if index == :all
51
+ @types[index]
52
+ end
53
+
54
+ def simple_off(option)
55
+ @simple[option] = false
56
+ end
57
+
58
+ private
59
+
60
+ def read_data_from_xml(xml_data)
61
+ xml_data.elements.each do |i|
62
+ case i.name
63
+ when 'lang'
64
+ j = i.text.split(',')
65
+ codes = @langs.map(&:code)
66
+
67
+ if j.join(',') != codes.join(',')
68
+ simple_off(:lang)
69
+ @langs = []
70
+ j.each do |k|
71
+ if k.strip == '*' || k.strip == ''
72
+ @langs << @concept.lang
73
+ else
74
+ @langs << LangFactory.instance.get(k.strip.to_s)
75
+ end
76
+ end
77
+ end
78
+ when 'row'
79
+ @datarows << Row.new(self, @datarows.size, i)
80
+ when 'sequence'
81
+ @sequence = i.text.split(',')
82
+ when 'template'
83
+ @datarows += Template.new(self, @datarows.size, i).datarows
84
+ when 'type'
85
+ j = i.text.split(',')
86
+ if j.join(',') != @types.join(',')
87
+ simple_off(:type)
88
+ @types = []
89
+ j.each { |k| @types << k.strip.to_s }
90
+ end
91
+ else
92
+ puts Rainbow("[ERROR] concept/table#xml_data with #{i.name}").red.bright
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,65 @@
1
+
2
+ require 'rexml/document'
3
+ require_relative 'row'
4
+
5
+ # This class process "template" tag used by Tables
6
+ class Template
7
+ attr_reader :datarows
8
+
9
+ def initialize(table, index, xml)
10
+ @mode = :simple
11
+ vars = load_vars_from(xml)
12
+ template = load_template_from(xml)
13
+ data_string = apply_vars_to_template(vars, template)
14
+ @datarows = read_rows_from(table, index, data_string)
15
+ end
16
+
17
+ def load_vars_from(xml)
18
+ vars = {}
19
+ v = xml.attributes
20
+ v.keys.each do |i|
21
+ if i == 'mode'
22
+ @mode = v[i].to_sym
23
+ else
24
+ vars[i] = v[i].split(',')
25
+ end
26
+ end
27
+ # fill_vars_values(vars,mode)
28
+ vars
29
+ end
30
+
31
+ def fill_vars_values(vars, mode)
32
+ # create sizes array
33
+ end
34
+
35
+ def load_template_from(xml)
36
+ template = ''
37
+ xml.elements.each { |i| template << i.to_s + "\n" }
38
+ template
39
+ end
40
+
41
+ def apply_vars_to_template(vars, template)
42
+ output = ''
43
+ return if vars.size.zero?
44
+ max = vars.first[1].size
45
+ (1..max).each do |index|
46
+ t = template.dup
47
+ vars.each_pair { |k, v| t.gsub!(k, v[index - 1]) }
48
+ output += t
49
+ end
50
+ output
51
+ end
52
+
53
+ def read_rows_from(table, index, data_string)
54
+ datarows = []
55
+ data = "<template>\n#{data_string}\n</template>"
56
+ xml = REXML::Document.new(data)
57
+ xml.root.elements.each do |i|
58
+ if i.name == 'row'
59
+ datarows << Row.new(table, index, i)
60
+ index += 1
61
+ end
62
+ end
63
+ datarows
64
+ end
65
+ end
@@ -0,0 +1,53 @@
1
+
2
+ require_relative '../loader/image_url_loader'
3
+ require_relative '../project'
4
+
5
+ class World
6
+ attr_reader :concepts, :filenames, :contexts, :image_urls
7
+
8
+ def initialize(concepts, show_progress=true)
9
+ find_neighbors_for_every_concept(concepts)
10
+
11
+ @concepts = {}
12
+ @filenames = []
13
+ @contexts = []
14
+ @image_urls = {}
15
+
16
+ concepts.each do |c|
17
+ if c.process
18
+ @concepts[c.name] = c
19
+ @filenames << c.filename
20
+ @contexts << c.context
21
+ end
22
+ end
23
+ @filenames.uniq!
24
+ @contexts.uniq!
25
+
26
+ threads = []
27
+ concepts.each do |c|
28
+ print('.') if show_progress
29
+ # puts "[DEBUG] #{c.name}\n"
30
+ # filter = [ c.name.clone ] + c.context.clone
31
+ filter = c.name.clone
32
+ threads << Thread.new { @image_urls[c.name] = ImageUrlLoader::load(filter) }
33
+ end
34
+ @contexts.each do |filter|
35
+ print('.') if show_progress
36
+ threads << Thread.new { @image_urls[ filter.join('.').to_s ] = ImageUrlLoader::load(filter) }
37
+ end
38
+ threads.each { |t| t.join } # wait for all threads to finish
39
+ print("\n") if show_progress
40
+ end
41
+
42
+ def find_neighbors_for_every_concept(concepts)
43
+ concepts.each do |i|
44
+ concepts.each do |j|
45
+ if (i.id!=j.id) then
46
+ i.try_adding_neighbor(j)
47
+ i.try_adding_references(j)
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../project'
4
+ require_relative '../formatter/question_gift_formatter'
5
+
6
+ # UNDER DEVELOPMENT
7
+ # Use to export questions from Code to gift format
8
+ module CodeGiftExporter
9
+ def self.export_all(codes)
10
+ codes.each do |code|
11
+ code.make_questions
12
+ export code
13
+ end
14
+ end
15
+
16
+ def self.export(code)
17
+ return false unless code.process?
18
+
19
+ file = Project.instance.outputfile
20
+ file.write head(code)
21
+
22
+ code.questions.each do |question|
23
+ file.write QuestionGiftFormatter.to_s(question)
24
+ end
25
+ true
26
+ end
27
+
28
+ def self.head(code)
29
+ s = "\n"
30
+ s += '// ' + '=' * 50 + "\n"
31
+ s += "// Code #{code.type}: #{code.filename} (#{code.questions.size})\n"
32
+ s += '// ' + '=' * 50 + "\n"
33
+ s
34
+ end
35
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'terminal-table'
4
+
5
+ # Export Code into Screen
6
+ module CodeScreenExporter
7
+ def self.export_all(codes)
8
+ project = Project.instance
9
+ return if project.show_mode == :none || codes.nil? || codes.size.zero?
10
+
11
+ total_c = total_q = total_e = 0
12
+ my_screen_table = Terminal::Table.new do |st|
13
+ st << %w[Filename Type Questions Lines xFactor]
14
+ st << :separator
15
+ end
16
+
17
+ codes.each do |code|
18
+ next unless code.process?
19
+
20
+ e = code.lines.size
21
+ q = code.questions.size
22
+ factor = 'Unkown'
23
+ factor = (q.to_f / e).round(2).to_s unless e.zero?
24
+ my_screen_table.add_row [Rainbow(File.basename(code.filename)).green,
25
+ code.type,
26
+ q,
27
+ e,
28
+ factor]
29
+ total_c += 1
30
+ total_q += q
31
+ total_e += e
32
+ end
33
+
34
+ my_screen_table.add_separator
35
+ my_screen_table.add_row [Rainbow("TOTAL = #{total_c}").bright,
36
+ ' ',
37
+ Rainbow(total_q.to_s).bright,
38
+ Rainbow(total_e.to_s).bright,
39
+ Rainbow((total_q / total_e.to_f).round(2)).bright]
40
+ return unless total_c.positive?
41
+
42
+ project.verboseln "\n[INFO] Showing CODE statistics"
43
+ project.verboseln my_screen_table.to_s
44
+ end
45
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative '../project'
4
+ require_relative '../formatter/question_gift_formatter'
5
+
6
+ # Use to export data from ConceptIA to gift format
7
+ module ConceptAIGiftExporter
8
+ def self.export_all(concepts_ai)
9
+ concepts_ai.each { |concept_ai| export(concept_ai) }
10
+ end
11
+
12
+ def self.export(concept_ai)
13
+ return unless concept_ai.process?
14
+
15
+ file = Project.instance.outputfile
16
+ file.write head(concept_ai.name)
17
+
18
+ stages = Project.instance.stages
19
+ stages.each_key do |stage|
20
+ concept_ai.questions[stage].each do |question|
21
+ file.write(QuestionGiftFormatter.to_s(question))
22
+ end
23
+ end
24
+ end
25
+
26
+ def self.head(name)
27
+ s = "\n"
28
+ s += '// ' + '=' * 50 + "\n"
29
+ s += "// Concept name: #{name}\n"
30
+ s += '// ' + '=' * 50 + "\n"
31
+ s
32
+ end
33
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'terminal-table'
4
+
5
+ # Show ConceptAI info on screen
6
+ class ConceptAIScreenExporter
7
+ def self.export_all(concepts_ai)
8
+ @concepts_ai = concepts_ai
9
+ project = Project.instance
10
+ return if project.show_mode == :none
11
+
12
+ # Create table HEAD
13
+ screen_table = Terminal::Table.new do |st|
14
+ st << ['Concept','Questions','Entries','xFactor',
15
+ 'd','b','f','i','s','t']
16
+ st << :separator
17
+ end
18
+
19
+ # Create table BODY
20
+ total = {}
21
+ total[:q] = total[:e] = total[:c] = 0
22
+ total[:sd] = total[:sb] = total[:sf] = 0
23
+ total[:si] = total[:ss] = total[:st] = 0
24
+
25
+ @concepts_ai.each do |concept_ai|
26
+ if concept_ai.process?
27
+ e = concept_ai.texts.size
28
+ concept_ai.tables.each { |t| e += t.fields.size * t.rows.size }
29
+
30
+ sd = concept_ai.questions[:d].size
31
+ sb = concept_ai.questions[:b].size
32
+ sf = concept_ai.questions[:f].size
33
+ si = concept_ai.questions[:i].size
34
+ ss = concept_ai.questions[:s].size
35
+ st = concept_ai.questions[:t].size
36
+ t = sd + sb + sf + si + ss + st
37
+
38
+ if e == 0
39
+ factor = 'Unkown'
40
+ else
41
+ factor = (t.to_f/e.to_f).round(2).to_s
42
+ end
43
+ screen_table.add_row [Rainbow(concept_ai.name(:screen)).green.bright,
44
+ t, e, factor, sd, sb, sf, si, ss, st]
45
+
46
+ total[:q] += t ; total[:e] += e; total[:c] += 1
47
+ total[:sd] += sd; total[:sb] += sb; total[:sf] += sf
48
+ total[:si] += si; total[:ss] += ss; total[:st] += st
49
+ end
50
+ end
51
+ return if total[:c] == 0 # No concepts to be process?
52
+
53
+ # Add row with excluded questions
54
+ export_excluded_questions(screen_table, @concepts_ai)
55
+
56
+ # Create table TAIL
57
+ screen_table.add_separator
58
+ screen_table.add_row [Rainbow("TOTAL = #{total[:c]}").bright,
59
+ Rainbow(total[:q].to_s).bright,
60
+ Rainbow(total[:e].to_s).bright,
61
+ Rainbow((total[:q].to_f/total[:e].to_f).round(2)).bright,
62
+ total[:sd], total[:sb], total[:sf],
63
+ total[:si], total[:ss], total[:st]]
64
+ export_notes
65
+ project.verbose screen_table.to_s + "\n"
66
+ end
67
+
68
+ def self.export_excluded_questions(screen_table, concepts_ai)
69
+ # Create table BODY
70
+ total = {}
71
+ total[:q] = total[:c] = 0
72
+ total[:sd] = total[:sb] = total[:sf] = 0
73
+ total[:si] = total[:ss] = total[:st] = 0
74
+
75
+ concepts_ai.each do |concept_ai|
76
+ if concept_ai.process?
77
+ sd = concept_ai.excluded_questions[:d].size
78
+ sb = concept_ai.excluded_questions[:b].size
79
+ sf = concept_ai.excluded_questions[:f].size
80
+ si = concept_ai.excluded_questions[:i].size
81
+ ss = concept_ai.excluded_questions[:s].size
82
+ st = concept_ai.excluded_questions[:t].size
83
+ t = sd + sb + sf + si + ss + st
84
+
85
+ total[:q] += t ; total[:c] += 1
86
+ total[:sd] += sd; total[:sb] += sb; total[:sf] += sf
87
+ total[:si] += si; total[:ss] += ss; total[:st] += st
88
+ end
89
+ end
90
+ screen_table.add_row [Rainbow('Excluded questions').yellow.bright,
91
+ total[:q], '-', '-',
92
+ total[:sd], total[:sb],
93
+ total[:sf], total[:si],
94
+ total[:ss], total[:st]]
95
+ end
96
+
97
+ def self.export_notes
98
+ p = Project.instance
99
+ p.verbose "\n[INFO] Showing CONCEPT statistics\n"
100
+ p.verbose ' * Exclude questions: ' +
101
+ Application.instance.config['questions']['exclude'].to_s
102
+ p.verbose ' * Annotations:'
103
+ p.verbose ' ├── (d) Definitions <= Concept.def'
104
+ p.verbose ' ├── (b) Table Matching <= ' \
105
+ 'Concept.table.rows.columns'
106
+ p.verbose ' ├── (f) Tables 1 Field <= Concept.table.fields.size==1'
107
+ p.verbose ' ├── (i) Images URL <= ' \
108
+ "Concept.def{:type => 'image_url'}"
109
+ p.verbose ' ├── (s) Sequences <= ' \
110
+ "Concept.table{:sequence => '...'}"
111
+ p.verbose ' └── (t) Table Rows&Cols <= ' \
112
+ 'Concept.table.rows.columns'
113
+ p.verbose "\n"
114
+ end
115
+ end