reviser 0.0.1.1.pre.beta → 0.0.2.rc1
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/Gemfile +18 -0
- data/README.md +46 -0
- data/ext/valgrind.rb +21 -18
- data/ext/web_validators.rb +132 -0
- data/lang/HTML.yml +2 -14
- data/lib/component.rb +41 -39
- data/lib/components/archiver.rb +105 -101
- data/lib/components/checker.rb +46 -52
- data/lib/components/extractors.rb +121 -120
- data/lib/components/generator.rb +40 -37
- data/lib/components/generators.rb +113 -109
- data/lib/components/organiser.rb +165 -153
- data/lib/config.rb +53 -35
- data/lib/criteria/code_analysis.rb +54 -0
- data/lib/criteria/compilation.rb +42 -0
- data/lib/criteria/execution.rb +78 -0
- data/lib/exec.rb +109 -94
- data/lib/helpers/criteria.rb +152 -154
- data/lib/helpers/git.rb +23 -21
- data/lib/helpers/project.rb +198 -19
- data/lib/helpers/system.rb +50 -39
- data/lib/loggers/logger.rb +39 -30
- data/lib/loggers/modes.rb +118 -54
- data/lib/reviser.rb +63 -41
- data/res/css/style_logs.css +166 -0
- data/res/css/web_validators/css-base.css +733 -0
- data/res/css/web_validators/css-results.css +257 -0
- data/res/css/web_validators/html-base.css +746 -0
- data/res/css/web_validators/html-results.css +489 -0
- data/res/labys/labfich11.txt +19 -0
- data/res/labys/test.txt +3 -0
- data/res/labys/yoda.txt +19 -0
- data/res/scss/style_logs.scss +134 -0
- data/type/JavaProject.yml +18 -0
- data/type/Pendu.yml +22 -0
- data/type/Web.yml +23 -0
- metadata +144 -10
- data/ext/html_validator.rb +0 -21
- data/lib/helpers/code_analysis.rb +0 -64
- data/lib/helpers/compilation.rb +0 -40
- data/lib/helpers/execution.rb +0 -83
- data/lib/project.rb +0 -155
data/lib/helpers/criteria.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require_relative '../config'
|
2
|
-
|
3
1
|
# Manage criteria and labels.
|
4
2
|
|
5
3
|
# @example Call a criterion (in the config File):
|
@@ -11,176 +9,176 @@ require_relative '../config'
|
|
11
9
|
# @author Yann Prono
|
12
10
|
# @author Renan Strauss
|
13
11
|
#
|
14
|
-
module
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
12
|
+
module Reviser
|
13
|
+
module Helpers
|
14
|
+
# This module enables to
|
15
|
+
# imports automaticlly all modules for the analysis
|
16
|
+
#
|
17
|
+
# Convention over configuration !
|
18
|
+
# A analysis module contains the word 'tool' in its filename.
|
19
|
+
# You also have the possibility to put code in the ext folder.
|
20
|
+
#
|
21
|
+
# @example Call a criterion during analysis (in the config File):
|
22
|
+
# criteria:
|
23
|
+
# - :count_lines
|
24
|
+
# - :list_files
|
25
|
+
# - :<method>: <custom label>
|
26
|
+
#
|
27
|
+
# In the last item of the list, the custom label will overwrite the label
|
28
|
+
# in labels.yml if it exist.
|
29
|
+
#
|
30
|
+
module Criteria
|
31
|
+
|
32
|
+
# Where I am ?
|
33
|
+
PWD = File.dirname __FILE__
|
34
|
+
# Path of extensions
|
35
|
+
EXT = File.join File.dirname(File.dirname(PWD)), 'ext'
|
36
|
+
|
37
|
+
attr_reader :criteria
|
38
|
+
attr_reader :output
|
39
|
+
|
40
|
+
# All criterias available.
|
41
|
+
# :criterion => Name of the module
|
42
|
+
@criteria
|
43
|
+
|
44
|
+
# :criterion => label of criterion
|
45
|
+
@output
|
46
|
+
|
47
|
+
# Enable to call a specified method.
|
48
|
+
# @param meth [String] Method to call.
|
49
|
+
# @return results of the method.
|
50
|
+
def call meth
|
51
|
+
if @criteria.key? meth
|
52
|
+
@logger.h1(Logger::INFO, "Include methods of #{@criteria[meth]}") unless respond_to? meth
|
53
|
+
self.class.send(:include, @criteria[meth]) unless respond_to? meth
|
54
|
+
|
55
|
+
send meth
|
56
|
+
else
|
57
|
+
nil
|
58
|
+
end
|
59
59
|
end
|
60
|
-
end
|
61
60
|
|
62
61
|
|
63
|
-
|
62
|
+
protected
|
64
63
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
64
|
+
# Get all criteria which can be used.
|
65
|
+
# @return [Array] all criteria
|
66
|
+
def all
|
67
|
+
@criteria.keys.map &:to_sym
|
68
|
+
end
|
70
69
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
70
|
+
# from Cfg file to symbols
|
71
|
+
# @param criterion The criteria
|
72
|
+
# @param module_name The name of the module.
|
73
|
+
def populate criterion, module_name
|
74
|
+
raise "Criterion '#{criterion}' is already defined in #{@criteria[criterion.to_sym]} (#{criterion}/#{module_name}).\nPlease change the name of the method in one of modules." if @criteria.has_key? criterion.to_sym
|
75
|
+
@criteria[criterion.to_sym] = module_name
|
76
|
+
end
|
78
77
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
file_module.split('_').each {|s| s.capitalize! }.join('')
|
103
|
-
end
|
78
|
+
# Load all of modules available for the analysis
|
79
|
+
# @param directory Directory where search of modules is done.
|
80
|
+
# @param regex regex to find name of modules.
|
81
|
+
def load directory, regex = '*'
|
82
|
+
@logger.h2 Logger::INFO, "Modules of #{directory}"
|
83
|
+
modules = Dir[File.join(directory, regex)]
|
84
|
+
|
85
|
+
namespace = directory == EXT && 'Reviser::Extensions' || 'Reviser::Criteria'
|
86
|
+
modules.each do |m|
|
87
|
+
require_relative m
|
88
|
+
ext = File.extname m
|
89
|
+
module_name = Object.const_get "#{namespace}::#{camelize(File.basename(m,ext))}", false
|
90
|
+
@logger.h3 Logger::INFO, "Load #{module_name}"
|
91
|
+
methods = module_name.instance_methods false
|
92
|
+
methods.each { |method| populate(method, module_name) }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Gets the name of module
|
97
|
+
# @param file_module Name of the file module.
|
98
|
+
def camelize file_module
|
99
|
+
file_module.split('_').each {|s| s.capitalize! }.join('')
|
100
|
+
end
|
104
101
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
102
|
+
# Load labels given by the user.
|
103
|
+
# If the label doesn't exist, it will created with the name of the method.
|
104
|
+
# @param key Key of criteria in config file
|
105
|
+
def load_labels key
|
106
|
+
labels = Labels.load
|
107
|
+
|
108
|
+
if Cfg.has_key?(key) && Cfg[key].respond_to?('each')
|
109
|
+
Cfg[key].each do |meth|
|
110
|
+
if meth.respond_to?('each') && meth.respond_to?('[]')
|
111
|
+
label = meth[meth.keys[0]]
|
112
|
+
@logger.h2(Logger::ERROR, "Undefined label for #{meth.keys[0]}, check your config file") if label == nil
|
113
|
+
label = create_label(meth.keys[0]) if label == nil
|
114
|
+
@output[meth.keys[0].to_sym] = label
|
115
|
+
else
|
116
|
+
label = (labels.respond_to?('[]') && labels.key?(meth.to_sym)) ? labels[meth.to_sym] : create_label(meth)
|
117
|
+
@output[meth.to_sym] = label
|
118
|
+
end
|
121
119
|
end
|
122
120
|
end
|
123
121
|
end
|
124
|
-
end
|
125
122
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
123
|
+
# Create label for a method.
|
124
|
+
# @param meth [String] method linked to the label
|
125
|
+
# @return [String] Renamed Label inspired of the name of the method
|
126
|
+
def create_label meth
|
127
|
+
@logger.h2 Logger::ERROR, "Create label for #{meth}. You should custom your label (see 'reviser add')"
|
128
|
+
meth.to_s.split('_').each {|s| s.capitalize! }.join(' ')
|
129
|
+
end
|
132
130
|
end
|
133
|
-
end
|
134
|
-
|
135
|
-
|
136
|
-
# Manage all actions for adding, updating or getting labels of Reviser.
|
137
|
-
# A label is a a group of words, describing the associated criterion (method).
|
138
|
-
#
|
139
|
-
# @example
|
140
|
-
# criterion => label
|
141
|
-
# all_files => all files of project
|
142
|
-
#
|
143
|
-
# known Labels are in the labels.yml file.
|
144
|
-
#
|
145
|
-
# @author Yann Prono
|
146
|
-
class Labels
|
147
|
-
|
148
|
-
# Current directory of this file
|
149
|
-
PWD = File.dirname __FILE__
|
150
131
|
|
151
|
-
# Path of label.yml file
|
152
|
-
LABELS = File.join(File.dirname(File.dirname(PWD)), 'labels.yml')
|
153
132
|
|
133
|
+
# Manage all actions for adding, updating or getting labels of Reviser.
|
134
|
+
# A label is a a group of words, describing the associated criterion (method).
|
154
135
|
#
|
155
|
-
#
|
156
|
-
#
|
157
|
-
#
|
158
|
-
#
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
136
|
+
# @example
|
137
|
+
# criterion => label
|
138
|
+
# all_files => all files of project
|
139
|
+
#
|
140
|
+
# known Labels are in the labels.yml file.
|
141
|
+
#
|
142
|
+
# @author Yann Prono
|
143
|
+
class Labels
|
144
|
+
|
145
|
+
# Current directory of this file
|
146
|
+
PWD = File.dirname __FILE__
|
147
|
+
|
148
|
+
# Path of label.yml file
|
149
|
+
LABELS = File.join(File.dirname(File.dirname(PWD)), 'labels.yml')
|
150
|
+
|
151
|
+
#
|
152
|
+
# Enable to associate a label to a criterion (method).
|
153
|
+
# The label will be saved in the 'labels.yml' file
|
154
|
+
# @param meth Method to link.
|
155
|
+
# @param label Label to link with the method.
|
156
|
+
def self.add meth, label
|
157
|
+
res = "Create"
|
158
|
+
labels = YAML.load File.open(LABELS)
|
159
|
+
if labels.respond_to? '[]'
|
160
|
+
res = "Update" if labels.key? meth
|
161
|
+
labels[meth] = label
|
162
|
+
File.open(LABELS, 'w') { |f| f.write labels.to_yaml }
|
163
|
+
end
|
164
|
+
res
|
166
165
|
end
|
167
|
-
res
|
168
|
-
end
|
169
166
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
167
|
+
# @return Hash all known labels by reviser.
|
168
|
+
# :criterion => label
|
169
|
+
def self.load
|
170
|
+
Labels.populate(YAML.load(File.open(LABELS)))
|
171
|
+
end
|
175
172
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
173
|
+
def self.populate hash
|
174
|
+
labels = {}
|
175
|
+
if hash.respond_to?('each')
|
176
|
+
hash.each do |meth, label|
|
177
|
+
labels[meth.to_sym] = label
|
178
|
+
end
|
181
179
|
end
|
180
|
+
labels
|
182
181
|
end
|
183
|
-
|
184
|
-
|
185
|
-
end
|
182
|
+
end
|
183
|
+
end
|
186
184
|
end
|
data/lib/helpers/git.rb
CHANGED
@@ -6,32 +6,34 @@
|
|
6
6
|
#
|
7
7
|
require 'git'
|
8
8
|
|
9
|
-
module
|
10
|
-
module
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
module Reviser
|
10
|
+
module Helpers
|
11
|
+
module Git
|
12
|
+
# method which initialize a git repository
|
13
|
+
def git_init
|
14
|
+
@git = ::Git.init
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
# method which allows the user to add something on the repository
|
18
|
+
def git_add
|
19
|
+
@git.add(:all=>true)
|
20
|
+
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
# method for displaying a message when the repository is configured
|
23
|
+
def git_commit
|
24
|
+
@git.commit_all('initialization of git repertory')
|
25
|
+
end
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
def git_push
|
28
|
+
@git.push
|
29
|
+
end
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
# method which allows the user to see the differences between two last commits
|
32
|
+
# I have to know the current commit and the last but how ?
|
33
|
+
# and do a diff between these 2 commits.
|
34
|
+
def git_diff
|
34
35
|
|
36
|
+
end
|
35
37
|
end
|
36
38
|
end
|
37
39
|
end
|
data/lib/helpers/project.rb
CHANGED
@@ -5,35 +5,214 @@
|
|
5
5
|
# @author Renan Strauss
|
6
6
|
# @author Yann Prono
|
7
7
|
#
|
8
|
-
module
|
9
|
-
module
|
8
|
+
module Reviser
|
9
|
+
module Helpers
|
10
|
+
module Project
|
11
|
+
#
|
12
|
+
# For interpreted languages
|
13
|
+
# We only check for missing files
|
14
|
+
#
|
15
|
+
def prepare
|
16
|
+
missing_files.empty? && 'None' || res
|
17
|
+
end
|
18
|
+
|
19
|
+
# Check if the project has all files needed
|
20
|
+
def missing_files
|
21
|
+
return [] unless Cfg =~ :required_files
|
22
|
+
|
23
|
+
dir = Dir['*']
|
24
|
+
|
25
|
+
#
|
26
|
+
# Check if there is any regexp
|
27
|
+
# If it's the case, if any file
|
28
|
+
# matches, we delete the entry
|
29
|
+
# for diff to work properly
|
30
|
+
#
|
31
|
+
Cfg[:required_files].each_with_index do |e, i|
|
32
|
+
if dir.any? { |f| (e.respond_to?(:match)) && (e =~ f) }
|
33
|
+
Cfg[:required_files].delete_at i
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Cfg[:required_files] - dir
|
38
|
+
end
|
39
|
+
|
10
40
|
#
|
11
|
-
#
|
12
|
-
# We only check for missing files
|
41
|
+
# @return all the files in the project's folder
|
13
42
|
#
|
14
|
-
def
|
15
|
-
|
43
|
+
def files
|
44
|
+
Dir.glob("**/*").select { |f| (File.file?(f)) }
|
16
45
|
end
|
17
46
|
|
18
|
-
|
19
|
-
|
20
|
-
|
47
|
+
def sources
|
48
|
+
files.select { |f| Cfg[:extension].include? File.extname(f) }
|
49
|
+
end
|
21
50
|
|
22
|
-
dir = Dir['*']
|
23
51
|
|
52
|
+
# This modules is used to scan the name of project
|
53
|
+
# in order to get all students who worked.
|
54
|
+
# This analysis uses regex of convention given by teachers (config file).
|
24
55
|
#
|
25
|
-
#
|
26
|
-
# If it's the case, if any file
|
27
|
-
# matches, we delete the entry
|
28
|
-
# for diff to work properly
|
56
|
+
# @author Yann Prono
|
29
57
|
#
|
30
|
-
|
31
|
-
|
32
|
-
|
58
|
+
|
59
|
+
module Naming
|
60
|
+
|
61
|
+
# Dictionnary for regex in config file
|
62
|
+
SYMBOLS = {
|
63
|
+
:group => 'GROUP',
|
64
|
+
:firstname => 'FIRSTN',
|
65
|
+
:name => 'NAME',
|
66
|
+
:user => 'USER',
|
67
|
+
:lambda => 'LAMBDA'
|
68
|
+
}
|
69
|
+
|
70
|
+
# Regex to associate, depending the used word in Cfg
|
71
|
+
REGEX = {
|
72
|
+
:group => '([A-Za-z0-9]{3,4})',
|
73
|
+
:firstname => '([A-Za-z\-]+)',
|
74
|
+
:name => '([A-Za-z]+)',
|
75
|
+
:user => '([^_]*)',
|
76
|
+
:lambda => '[a-zA-Z0-9 _]*'
|
77
|
+
}
|
78
|
+
|
79
|
+
|
80
|
+
# Get formatter written in the config file
|
81
|
+
# and count occurences of each word in the dictionnary SYMBOLS.
|
82
|
+
# @return [Hash] sym => count.
|
83
|
+
#
|
84
|
+
def analyze_formatter
|
85
|
+
regex = Cfg[:projects_names]
|
86
|
+
# Foreach known symbols
|
87
|
+
SYMBOLS.each do |k, _|
|
88
|
+
# Get numbers of occurences of the word k in regex
|
89
|
+
matches = regex.scan(SYMBOLS[k]).size
|
90
|
+
# the word K => number of occurences
|
91
|
+
@count_patterns[k] = matches if matches > 0
|
92
|
+
end
|
33
93
|
end
|
34
|
-
end
|
35
94
|
|
36
|
-
|
95
|
+
|
96
|
+
# Analyze et get all informations
|
97
|
+
# that could be useful in the name of the
|
98
|
+
# directory project.
|
99
|
+
# @param entry [String] name of directory to analysis.
|
100
|
+
#
|
101
|
+
def format entry
|
102
|
+
ext = File.extname entry
|
103
|
+
entry = File.basename entry, ext
|
104
|
+
|
105
|
+
analyze_formatter if @count_patterns.empty?
|
106
|
+
|
107
|
+
group = check_entry_name entry
|
108
|
+
generate_label group
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
# Generate new name of project.
|
113
|
+
# @param infos [Hash] All informations used for generate a name for the directory.
|
114
|
+
# @return [String] the formatted name for directory project
|
115
|
+
#
|
116
|
+
def generate_label infos
|
117
|
+
unless infos.empty?
|
118
|
+
label = ''
|
119
|
+
infos.reject { |k| k == :group }.each { |_, v|
|
120
|
+
if v.respond_to?('each')
|
121
|
+
v.each { |data|
|
122
|
+
label += data +' '
|
123
|
+
}
|
124
|
+
else
|
125
|
+
label += v + ' '
|
126
|
+
end
|
127
|
+
}
|
128
|
+
# Inject group of project before name : group/name
|
129
|
+
label = infos.key?(:group) && File.join(infos[:group], label) || label
|
130
|
+
label
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# I'm not pround of this method ...
|
135
|
+
# associate to a symbol, his position in the regex
|
136
|
+
# @example NAME_FIRSTN
|
137
|
+
# will give : {
|
138
|
+
# 1 => :name,
|
139
|
+
# 2 => :firstname
|
140
|
+
#}
|
141
|
+
def get_position regex
|
142
|
+
res = {}
|
143
|
+
SYMBOLS.each do |k,v|
|
144
|
+
regex.scan(v) do |_|
|
145
|
+
res[$~.offset(0)[0]] = k
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
res = (res.sort_by { |k, _| k }).to_h
|
150
|
+
tmp = {}
|
151
|
+
|
152
|
+
index = 1
|
153
|
+
res.each do |_,v|
|
154
|
+
tmp[index] = v
|
155
|
+
index += 1
|
156
|
+
end
|
157
|
+
tmp
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
# Apply regex of user on the entry name
|
162
|
+
# and try to get all interested matched values.
|
163
|
+
def check_entry_name entry
|
164
|
+
regex = Cfg[:projects_names]
|
165
|
+
# who work on the current project (entry) ?
|
166
|
+
position = get_position regex
|
167
|
+
|
168
|
+
@count_patterns.each do |k, _|
|
169
|
+
regex = regex.gsub SYMBOLS[k], REGEX[k]
|
170
|
+
end
|
171
|
+
|
172
|
+
# Apply created regex
|
173
|
+
entry.match Regexp.new(regex)
|
174
|
+
pos = 1
|
175
|
+
infos = {}
|
176
|
+
|
177
|
+
# Get matched values
|
178
|
+
begin
|
179
|
+
tmp = eval "$#{pos}"
|
180
|
+
if tmp != nil && tmp != ''
|
181
|
+
tmp = tmp.delete '_'
|
182
|
+
infos.has_key?(position[pos]) && infos[position[pos]] << tmp || infos[position[pos]] = [tmp]
|
183
|
+
end
|
184
|
+
pos += 1
|
185
|
+
end while pos <= position.size
|
186
|
+
|
187
|
+
if infos.empty?
|
188
|
+
infos[:unknown] = entry
|
189
|
+
end
|
190
|
+
sort_infos infos
|
191
|
+
infos
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
# Put all datas found in respective variables (students, groups, teams ...).
|
196
|
+
# @param infos [Hash] Informations found by regex.
|
197
|
+
def sort_infos infos
|
198
|
+
if infos.has_key?(:name)
|
199
|
+
infos[:name].respond_to?('each') && infos[:name].each { |n| @students << n } || @students << infos[:name]
|
200
|
+
@binoms << infos[:name]
|
201
|
+
end
|
202
|
+
if infos.has_key?(:group)
|
203
|
+
infos[:group] = infos[:group][0].upcase
|
204
|
+
sym_group = infos[:group].to_sym
|
205
|
+
@projects_per_group[sym_group] = @projects_per_group.key?(sym_group) && @projects_per_group[sym_group] + 1 || 1
|
206
|
+
end
|
207
|
+
|
208
|
+
@unknown << infos[:unknown] if infos.key? :unknown
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
def ask entry
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
37
216
|
end
|
38
217
|
end
|
39
218
|
end
|