genevalidatorapp 1.4.13 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/Rakefile +5 -1
- data/bin/genevalidatorapp +227 -73
- data/config.ru +1 -1
- data/genevalidatorapp.gemspec +7 -6
- data/lib/genevalidatorapp.rb +247 -0
- data/lib/{GeneValidatorApp → genevalidatorapp}/config.rb +7 -6
- data/lib/{GeneValidatorApp → genevalidatorapp}/database.rb +8 -13
- data/lib/genevalidatorapp/exceptions.rb +162 -0
- data/lib/{GeneValidatorApp → genevalidatorapp}/genevalidator.rb +52 -58
- data/lib/{GeneValidatorApp → genevalidatorapp}/logger.rb +0 -0
- data/lib/genevalidatorapp/routes.rb +81 -0
- data/lib/genevalidatorapp/server.rb +63 -0
- data/lib/{GeneValidatorApp → genevalidatorapp}/version.rb +1 -1
- data/public/{web_files → src}/css/bootstrap1.min.css +0 -0
- data/public/{web_files → src}/css/custom.css +8 -13
- data/public/{web_files → src}/css/custom.min.css +0 -0
- data/public/{web_files → src}/css/font-awesome.min.css +0 -0
- data/public/{web_files → src}/js/bionode-seq.min.js +0 -0
- data/public/{web_files → src}/js/bootstrap.min.js +0 -0
- data/public/{web_files → src}/js/d3.v3.min.js +0 -0
- data/public/{web_files → src}/js/genevalidator.js +44 -49
- data/public/{web_files → src}/js/jquery.cookie.min.js +0 -0
- data/public/{web_files → src}/js/jquery.min.js +0 -0
- data/public/{web_files → src}/js/jquery.tablesorter.min.js +0 -0
- data/public/{web_files → src}/js/jquery.validate.min.js +0 -0
- data/public/src/js/plots.js +814 -0
- data/public/web_files/css/GV_compiled_css.min.css +15 -0
- data/public/web_files/js/GV_compiled_js.min.js +34 -0
- data/spec/app_spec.rb +1 -1
- data/spec/database_spec.rb +2 -2
- data/views/index.slim +1 -1
- data/views/layout.slim +15 -24
- data/views/results.slim +54 -0
- metadata +39 -35
- data/lib/GeneValidatorApp.rb +0 -321
- data/public/web_files/css/bootstrap.min.css +0 -7
- data/public/web_files/js/genevalidator.min.js +0 -1
- data/public/web_files/js/plots.js +0 -744
- data/public/web_files/js/plots.min.js +0 -1
@@ -36,6 +36,7 @@ module GeneValidatorApp
|
|
36
36
|
# Write config data to config file.
|
37
37
|
def write_config_file
|
38
38
|
return unless config_file
|
39
|
+
|
39
40
|
File.open(config_file, 'w') do |f|
|
40
41
|
f.puts(data.delete_if { |_, v| v.nil? }.to_yaml)
|
41
42
|
end
|
@@ -71,10 +72,11 @@ module GeneValidatorApp
|
|
71
72
|
# Default configuration data.
|
72
73
|
def defaults
|
73
74
|
{
|
74
|
-
:num_threads
|
75
|
-
:port
|
76
|
-
:host
|
77
|
-
:
|
75
|
+
:num_threads => 1,
|
76
|
+
:port => 4567,
|
77
|
+
:host => '0.0.0.0',
|
78
|
+
:gv_app_dir => '~/.genevalidatorapp/',
|
79
|
+
:max_characters => 'undefined'
|
78
80
|
}
|
79
81
|
end
|
80
82
|
|
@@ -82,5 +84,4 @@ module GeneValidatorApp
|
|
82
84
|
'~/.genevalidatorapp.conf'
|
83
85
|
end
|
84
86
|
end
|
85
|
-
|
86
|
-
end
|
87
|
+
end
|
@@ -3,15 +3,7 @@ require 'digest/md5'
|
|
3
3
|
require 'forwardable'
|
4
4
|
|
5
5
|
module GeneValidatorApp
|
6
|
-
#
|
7
|
-
#
|
8
|
-
# It is important that formatted BLAST database files have the same dirname and
|
9
|
-
# basename as the source FASTA for GeneValidatorApp to be able to tell formatted
|
10
|
-
# FASTA from unformatted. And that FASTA files be formatted with `parse_seqids`
|
11
|
-
# option of `makeblastdb` for sequence retrieval to work.
|
12
|
-
#
|
13
|
-
# GeneValidatorApp will always place BLAST database files alongside input FASTA,
|
14
|
-
# and use `parse_seqids` option of `makeblastdb` to format databases.
|
6
|
+
# class on the BLAST databases
|
15
7
|
class Database < Struct.new(:name, :title, :type)
|
16
8
|
class << self
|
17
9
|
extend Forwardable
|
@@ -77,16 +69,19 @@ module GeneValidatorApp
|
|
77
69
|
# Recurisvely scan `database_dir` for blast databases.
|
78
70
|
def scan_databases_dir
|
79
71
|
database_dir = config[:database_dir]
|
80
|
-
|
72
|
+
cmd = "blastdbcmd -recursive -list #{database_dir} -list_outfmt" \
|
73
|
+
' "%p::%f::%t"'
|
74
|
+
list = `#{cmd} 2>&1`
|
81
75
|
list.each_line do |line|
|
82
|
-
type, name, title = line.split('
|
76
|
+
type, name, title = line.split('::', 3)
|
83
77
|
next if multipart_database_name?(name)
|
84
|
-
next unless type.downcase == 'protein'
|
78
|
+
next unless type.downcase == 'protein'
|
85
79
|
self << Database.new(name, title, type)
|
86
80
|
end
|
87
81
|
end
|
88
82
|
|
89
|
-
# Returns true if the database name appears to be a multi-part database
|
83
|
+
# Returns true if the database name appears to be a multi-part database
|
84
|
+
# name.
|
90
85
|
#
|
91
86
|
# e.g.
|
92
87
|
# /home/ben/pd.ben/sequenceserver/db/nr.00 => yes
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# This file defines all possible exceptions that can be thrown by
|
2
|
+
# GeneValidatorApp on startup.
|
3
|
+
#
|
4
|
+
# Exceptions only ever inform another entity (downstream code or users) of an
|
5
|
+
# issue. Exceptions may or may not be recoverable.
|
6
|
+
#
|
7
|
+
# Error classes should be seen as: the error code (class name), human readable
|
8
|
+
# message (to_s method), and necessary attributes to act on the error.
|
9
|
+
#
|
10
|
+
# We define as many error classes as needed to be precise about the issue, thus
|
11
|
+
# making it easy for downstream code (bin/genevalidatorapp or config.ru) to act
|
12
|
+
# on them.
|
13
|
+
|
14
|
+
module GeneValidatorApp
|
15
|
+
# Error in config file.
|
16
|
+
class CONFIG_FILE_ERROR < StandardError
|
17
|
+
def initialize(ent, err)
|
18
|
+
@ent = ent
|
19
|
+
@err = err
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :ent, :err
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
<<MSG
|
26
|
+
Error reading config file: #{ent}.
|
27
|
+
#{err}
|
28
|
+
MSG
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
## ENOENT ##
|
33
|
+
|
34
|
+
# Name borrowed from standard Errno::ENOENT, this class serves as a template
|
35
|
+
# for defining errors that mean "expected to find <entity> at <path>, but
|
36
|
+
# didn't".
|
37
|
+
#
|
38
|
+
# ENOENT is raised if and only if an entity was set, either using CLI or
|
39
|
+
# config file. For instance, it's compulsory to set database_dir. But ENOENT
|
40
|
+
# is not raised if database_dir is not set. ENOENT is raised if database_dir
|
41
|
+
# was set, but does not exist.
|
42
|
+
class ENOENT < StandardError
|
43
|
+
def initialize(des, ent)
|
44
|
+
@des = des
|
45
|
+
@ent = ent
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_reader :des, :ent
|
49
|
+
|
50
|
+
def to_s
|
51
|
+
"Could not find #{des}: #{ent}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Raised if bin dir set, but does not exist.
|
56
|
+
class BIN_DIR_NOT_FOUND < ENOENT
|
57
|
+
def initialize(ent)
|
58
|
+
super 'bin dir', ent
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Raised if database dir set, but does not exist.
|
63
|
+
class DATABASE_DIR_NOT_FOUND < ENOENT
|
64
|
+
def initialize(ent)
|
65
|
+
super 'database dir', ent
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Raised if extension file set, but does not exist.
|
70
|
+
class EXTENSION_FILE_NOT_FOUND < ENOENT
|
71
|
+
def initialize(ent)
|
72
|
+
super 'extension file', ent
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
## NUM THREADS ##
|
77
|
+
|
78
|
+
# Raised if num_threads set by the user is incorrect.
|
79
|
+
class NUM_THREADS_INCORRECT < StandardError
|
80
|
+
def to_s
|
81
|
+
'Number of threads should be a number greater than or equal to 1.'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
## BLAST NOT INSTALLED OR NOT COMPATIBLE ##
|
86
|
+
|
87
|
+
# Raised if SequenceServer could not locate NCBI BLAST+ installation on
|
88
|
+
# user's system.
|
89
|
+
class BLAST_NOT_INSTALLED < StandardError
|
90
|
+
def to_s
|
91
|
+
'Could not locate BLAST+ binaries.'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Raised if SequenceServer could not successfully execute 'blastp -version'
|
96
|
+
# on user's system (see #141).
|
97
|
+
class BLAST_NOT_EXECUTABLE < StandardError
|
98
|
+
def to_s
|
99
|
+
'Error executing BLAST+ binaries.'
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Raised if SequenceServer determined NCBI BLAST+ present on the user's
|
104
|
+
# system but not meeting SequenceServer's minimum version requirement.
|
105
|
+
class BLAST_NOT_COMPATIBLE < StandardError
|
106
|
+
def initialize(version)
|
107
|
+
@version = version
|
108
|
+
end
|
109
|
+
|
110
|
+
attr_reader :version
|
111
|
+
|
112
|
+
def to_s
|
113
|
+
<<MSG
|
114
|
+
Your BLAST+ version #{version} is outdated.
|
115
|
+
SequenceServer needs NCBI BLAST+ version #{MINIMUM_BLAST_VERSION} or higher.
|
116
|
+
MSG
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
## BLAST+ DATABASE RELATED ##
|
121
|
+
|
122
|
+
# Raised if 'database_dir' not set.
|
123
|
+
class DATABASE_DIR_NOT_SET < StandardError
|
124
|
+
def to_s
|
125
|
+
'Database dir not set.'
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Raised if not even one BLAST+ database was found in database_dir.
|
130
|
+
class NO_BLAST_DATABASE_FOUND < StandardError
|
131
|
+
def initialize(database_dir)
|
132
|
+
@database_dir = database_dir
|
133
|
+
end
|
134
|
+
|
135
|
+
attr_reader :database_dir
|
136
|
+
|
137
|
+
def to_s
|
138
|
+
"Could not find BLAST+ databases in: #{database_dir}."
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Raised if there was an error determining BLAST+ databases in database_dir.
|
143
|
+
class BLAST_DATABASE_ERROR < StandardError
|
144
|
+
def initialize(cmd, out)
|
145
|
+
@cmd = cmd
|
146
|
+
@out = out
|
147
|
+
end
|
148
|
+
|
149
|
+
attr_reader :cmd, :out
|
150
|
+
|
151
|
+
def to_s
|
152
|
+
<<MSG
|
153
|
+
Error obtaining BLAST databases.
|
154
|
+
Tried: #{cmd}
|
155
|
+
Error:
|
156
|
+
#{out.strip}
|
157
|
+
|
158
|
+
Please could you report this to 'https://groups.google.com/forum/#!forum/sequenceserver'?
|
159
|
+
MSG
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -2,6 +2,7 @@ require 'forwardable'
|
|
2
2
|
require 'bio'
|
3
3
|
require 'fileutils'
|
4
4
|
require 'genevalidator'
|
5
|
+
require 'json'
|
5
6
|
|
6
7
|
module GeneValidatorApp
|
7
8
|
# Module that runs GeneValidator
|
@@ -24,7 +25,7 @@ module GeneValidatorApp
|
|
24
25
|
class << self
|
25
26
|
extend Forwardable
|
26
27
|
|
27
|
-
def_delegators GeneValidatorApp, :config, :logger
|
28
|
+
def_delegators GeneValidatorApp, :config, :logger, :public_dir
|
28
29
|
|
29
30
|
attr_reader :gv_dir, :tmp_gv_dir, :input_file, :xml_file, :raw_seq,
|
30
31
|
:unique_id, :params
|
@@ -33,7 +34,7 @@ module GeneValidatorApp
|
|
33
34
|
def init(url, params)
|
34
35
|
create_unique_id
|
35
36
|
create_subdir_in_main_tmpdir
|
36
|
-
|
37
|
+
create_soft_link_from_tmpdir_to_gv_dir
|
37
38
|
@params = params
|
38
39
|
validate_params
|
39
40
|
obtain_db_path
|
@@ -45,7 +46,9 @@ module GeneValidatorApp
|
|
45
46
|
def run
|
46
47
|
write_seq_to_file
|
47
48
|
run_genevalidator
|
48
|
-
|
49
|
+
copy_json_folder
|
50
|
+
(@params[:result_link]) ? @url : output_json_file_path
|
51
|
+
parse_output_json
|
49
52
|
end
|
50
53
|
|
51
54
|
private
|
@@ -53,7 +56,7 @@ module GeneValidatorApp
|
|
53
56
|
# Creates a unique run ID (based on time),
|
54
57
|
def create_unique_id
|
55
58
|
@unique_id = Time.new.strftime('%Y-%m-%d_%H-%M-%S_%L-%N')
|
56
|
-
@gv_tmpdir = GeneValidatorApp.
|
59
|
+
@gv_tmpdir = File.join(GeneValidatorApp.temp_dir, unique_id)
|
57
60
|
ensure_unique_id
|
58
61
|
end
|
59
62
|
|
@@ -62,7 +65,7 @@ module GeneValidatorApp
|
|
62
65
|
def ensure_unique_id
|
63
66
|
while File.exist?(@gv_tmpdir)
|
64
67
|
@unique_id = create_unique_id
|
65
|
-
@gv_tmpdir = GeneValidatorApp.
|
68
|
+
@gv_tmpdir = File.join(GeneValidatorApp.temp_dir, unique_id)
|
66
69
|
end
|
67
70
|
logger.debug("Unique ID = #{@unique_id}")
|
68
71
|
end
|
@@ -74,8 +77,9 @@ module GeneValidatorApp
|
|
74
77
|
end
|
75
78
|
|
76
79
|
# Create the Tmp Dir and the create a soft link to it.
|
77
|
-
def
|
78
|
-
@gv_dir = GeneValidatorApp.public_dir
|
80
|
+
def create_soft_link_from_tmpdir_to_gv_dir
|
81
|
+
@gv_dir = File.join(GeneValidatorApp.public_dir, 'GeneValidator',
|
82
|
+
@unique_id)
|
79
83
|
logger.debug("Local GV dir = #{@gv_dir}")
|
80
84
|
FileUtils.ln_s "#{@gv_tmpdir}", "#{@gv_dir}"
|
81
85
|
end
|
@@ -84,6 +88,7 @@ module GeneValidatorApp
|
|
84
88
|
# Only important if POST request is sent via API - Web APP also validates
|
85
89
|
# all params via Javascript.
|
86
90
|
def validate_params
|
91
|
+
logger.debug("Input Paramaters: #{@params}")
|
87
92
|
check_seq_param_present
|
88
93
|
check_seq_length
|
89
94
|
check_validations_param_present
|
@@ -92,23 +97,20 @@ module GeneValidatorApp
|
|
92
97
|
|
93
98
|
# Simply asserts whether that the seq param is present
|
94
99
|
def check_seq_param_present
|
95
|
-
|
96
|
-
|
97
|
-
end
|
100
|
+
return if @params[:seq]
|
101
|
+
fail ArgumentError, 'No input sequence provided.'
|
98
102
|
end
|
99
103
|
|
100
104
|
def check_seq_length
|
101
|
-
return unless config[:max_characters]
|
102
|
-
|
103
|
-
|
104
|
-
end
|
105
|
+
return unless config[:max_characters] != 'undefined'
|
106
|
+
return if @params[:seq].length < config[:max_characters]
|
107
|
+
fail ArgumentError, 'The input sequence is too long.'
|
105
108
|
end
|
106
109
|
|
107
110
|
# Asserts whether the validations param are specified
|
108
111
|
def check_validations_param_present
|
109
|
-
|
110
|
-
|
111
|
-
end
|
112
|
+
return if @params[:validations]
|
113
|
+
fail ArgumentError, 'No validations specified'
|
112
114
|
end
|
113
115
|
|
114
116
|
# Asserts whether the database parameter is present
|
@@ -124,7 +126,7 @@ module GeneValidatorApp
|
|
124
126
|
|
125
127
|
# Writes the input sequences to a file with the sub_dir in the temp_dir
|
126
128
|
def write_seq_to_file
|
127
|
-
@input_fasta_file = @gv_tmpdir
|
129
|
+
@input_fasta_file = File.join(@gv_tmpdir, 'input_file.fa')
|
128
130
|
logger.debug("Writing input seqs to: '#{@input_fasta_file}'")
|
129
131
|
ensure_unix_line_ending
|
130
132
|
ensure_fasta_valid
|
@@ -145,7 +147,8 @@ module GeneValidatorApp
|
|
145
147
|
unique_queries = {}
|
146
148
|
sequence = @params[:seq].lstrip
|
147
149
|
if sequence[0] != '>'
|
148
|
-
sequence.insert(0,
|
150
|
+
sequence.insert(0, '>Submitted:'\
|
151
|
+
"#{Time.now.strftime('%H:%M-%B_%d_%Y')}\n")
|
149
152
|
end
|
150
153
|
sequence.gsub!(/^\>(\S+)/) do |s|
|
151
154
|
if unique_queries.key?(s)
|
@@ -163,8 +166,7 @@ module GeneValidatorApp
|
|
163
166
|
# empty
|
164
167
|
def assert_input_file_present
|
165
168
|
unless File.exist?(@input_fasta_file) || File.zero?(@input_fasta_file)
|
166
|
-
fail
|
167
|
-
' file.'
|
169
|
+
fail 'GeneValidatorApp was unable to create the input file.'
|
168
170
|
end
|
169
171
|
end
|
170
172
|
|
@@ -180,62 +182,54 @@ module GeneValidatorApp
|
|
180
182
|
|
181
183
|
# Runs GeneValidator
|
182
184
|
def run_genevalidator
|
183
|
-
opts = set_up_gv_opts
|
184
|
-
logger.debug("Running GeneValidator with options: #{opts}")
|
185
185
|
create_gv_log_file
|
186
186
|
run_gv
|
187
|
-
|
187
|
+
assert_json_output_file_produced
|
188
188
|
rescue SystemExit
|
189
|
-
raise
|
189
|
+
raise 'GeneValidator failed to run properly'
|
190
190
|
end
|
191
191
|
|
192
192
|
def run_gv
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
def set_up_gv_opts
|
200
|
-
{
|
201
|
-
validations: @params[:validations],
|
202
|
-
db: @db,
|
203
|
-
num_threads: config[:num_threads],
|
204
|
-
fast: true,
|
205
|
-
input_fasta_file: @input_fasta_file.to_s
|
206
|
-
}
|
193
|
+
cmd = "genevalidator -v '#{@params[:validations].join(', ')}'" \
|
194
|
+
" -d #{@db} -n #{config[:num_threads]} #{@input_fasta_file}"
|
195
|
+
logger.debug("GV command: $ #{cmd}")
|
196
|
+
log_file = (logger.debug?) ? '' : "> #{@gv_log_file} 2>&1"
|
197
|
+
`#{cmd} #{log_file}`
|
207
198
|
end
|
208
199
|
|
209
200
|
def create_gv_log_file
|
210
|
-
@gv_log_file = (@gv_tmpdir
|
201
|
+
@gv_log_file = File.join(@gv_tmpdir, 'log_file.txt')
|
211
202
|
logger.debug("Log file: #{@gv_log_file}")
|
212
203
|
end
|
213
204
|
|
214
205
|
# Assets whether the results file is produced by GeneValidator.
|
215
|
-
def
|
216
|
-
@
|
217
|
-
|
218
|
-
|
219
|
-
' output file.'
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
# Reads the GV output table file.
|
224
|
-
# Updates links to the plots with relative links to plot jsons.
|
225
|
-
def produce_table_html
|
226
|
-
orig_plots_dir = 'files/json/input_file.fa_'
|
227
|
-
local_plots_dir = Pathname.new('GeneValidator') + @unique_id +
|
228
|
-
'input_file.fa.html/files/json/input_file.fa_'
|
229
|
-
full_html = IO.binread(@table_file)
|
230
|
-
full_html.gsub(/#{orig_plots_dir}/, local_plots_dir.to_s).gsub(
|
231
|
-
'#Place_external_results_link_here', @url)
|
206
|
+
def assert_json_output_file_produced
|
207
|
+
@json_file = File.join(@gv_dir, 'input_file.fa.json')
|
208
|
+
return if File.exist?(@json_file)
|
209
|
+
fail 'GeneValidator did not produce the required output file.'
|
232
210
|
end
|
233
211
|
|
234
212
|
# Reuturns the URL of the results page.
|
235
213
|
def produce_result_url_link(url)
|
236
|
-
url.gsub(/input/, '').gsub(
|
214
|
+
url.gsub(/input/, '').gsub(%r{/*$}, '') +
|
237
215
|
"/GeneValidator/#{@unique_id}/input_file.fa.html/results.html"
|
238
216
|
end
|
217
|
+
|
218
|
+
def parse_output_json
|
219
|
+
json_contents = File.read(output_json_file_path)
|
220
|
+
JSON.parse(json_contents)
|
221
|
+
end
|
222
|
+
|
223
|
+
def output_json_file_path
|
224
|
+
"#{@input_fasta_file}.json"
|
225
|
+
end
|
226
|
+
|
227
|
+
def copy_json_folder
|
228
|
+
json_dir = File.join("#{@input_fasta_file}.html", 'files/json')
|
229
|
+
web_dir_json = File.join(public_dir, 'web_files/json')
|
230
|
+
logger.debug("Moving JSON files from #{json_dir} to #{web_dir_json}")
|
231
|
+
FileUtils.cp_r(json_dir, web_dir_json)
|
232
|
+
end
|
239
233
|
end
|
240
234
|
end
|
241
235
|
end
|