sequenceserver 2.0.0.rc8 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/sequenceserver +22 -30
- data/lib/sequenceserver/api_errors.rb +5 -1
- data/lib/sequenceserver/blast/constants.rb +1 -1
- data/lib/sequenceserver/blast/hit.rb +5 -16
- data/lib/sequenceserver/blast/job.rb +9 -18
- data/lib/sequenceserver/blast/report.rb +5 -3
- data/lib/sequenceserver/config.rb +4 -1
- data/lib/sequenceserver/database.rb +69 -9
- data/lib/sequenceserver/job.rb +1 -1
- data/lib/sequenceserver/makeblastdb.rb +40 -45
- data/lib/sequenceserver/routes.rb +4 -0
- data/lib/sequenceserver/version.rb +1 -1
- data/lib/sequenceserver.rb +15 -11
- data/public/config.js +143 -142
- data/public/css/fonts.css +23 -22
- data/public/css/grapher.css +598 -594
- data/public/css/sequenceserver.css +86 -24
- data/public/css/sequenceserver.min.css +2 -2
- data/public/js/alignment_exporter.js +14 -14
- data/public/js/databases_tree.js +215 -0
- data/public/js/download_fasta.js +1 -1
- data/public/js/hit.js +6 -2
- data/public/js/hits_overview.js +1 -1
- data/public/js/length_distribution.js +5 -5
- data/public/js/query.js +4 -7
- data/public/js/report.js +12 -24
- data/public/js/search.js +21 -2
- data/public/js/sidebar.js +4 -4
- data/public/js/svgExporter.js +12 -12
- data/public/js/visualisation_helpers.js +4 -5
- data/public/sequenceserver-report.min.js +11 -11
- data/public/sequenceserver-search.min.js +15 -11
- data/public/vendor/github/vakata/jstree@3.3.8/LICENSE-MIT +22 -0
- data/public/vendor/github/vakata/jstree@3.3.8/README.md +663 -0
- data/public/vendor/github/vakata/jstree@3.3.8/bower.json +33 -0
- data/public/vendor/github/vakata/jstree@3.3.8/component.json +28 -0
- data/public/vendor/github/vakata/jstree@3.3.8/composer.json +46 -0
- data/public/vendor/github/vakata/jstree@3.3.8/demo/README.md +2 -0
- data/public/vendor/github/vakata/jstree@3.3.8/demo/basic/index.html +146 -0
- data/public/vendor/github/vakata/jstree@3.3.8/demo/basic/root.json +1 -0
- data/public/vendor/github/vakata/jstree@3.3.8/dist/jstree.js +8612 -0
- data/public/vendor/github/vakata/jstree@3.3.8/dist/jstree.min.js +6 -0
- data/public/vendor/github/vakata/jstree@3.3.8/dist/themes/default/32px.png +0 -0
- data/public/vendor/github/vakata/jstree@3.3.8/dist/themes/default/40px.png +0 -0
- data/public/vendor/github/vakata/jstree@3.3.8/dist/themes/default/style.css +1102 -0
- data/public/vendor/github/vakata/jstree@3.3.8/dist/themes/default/style.min.css +1 -0
- data/public/vendor/github/vakata/jstree@3.3.8/dist/themes/default/throbber.gif +0 -0
- data/public/vendor/github/vakata/jstree@3.3.8/dist/themes/default-dark/32px.png +0 -0
- data/public/vendor/github/vakata/jstree@3.3.8/dist/themes/default-dark/40px.png +0 -0
- data/public/vendor/github/vakata/jstree@3.3.8/dist/themes/default-dark/style.css +1146 -0
- data/public/vendor/github/vakata/jstree@3.3.8/dist/themes/default-dark/style.min.css +1 -0
- data/public/vendor/github/vakata/jstree@3.3.8/dist/themes/default-dark/throbber.gif +0 -0
- data/public/vendor/github/vakata/jstree@3.3.8/gruntfile.js +242 -0
- data/public/vendor/github/vakata/jstree@3.3.8/jstree.jquery.json +28 -0
- data/public/vendor/github/vakata/jstree@3.3.8/package.json +58 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/intro.js +14 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/jstree.changed.js +69 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/jstree.checkbox.js +976 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/jstree.conditionalselect.js +38 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/jstree.contextmenu.js +661 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/jstree.dnd.js +669 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/jstree.js +4931 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/jstree.massload.js +137 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/jstree.search.js +421 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/jstree.sort.js +74 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/jstree.state.js +138 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/jstree.types.js +372 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/jstree.unique.js +164 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/jstree.wholerow.js +122 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/misc.js +656 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/outro.js +1 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/sample.js +93 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/themes/base.less +93 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/themes/default/32px.png +0 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/themes/default/40px.png +0 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/themes/default/style.css +1102 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/themes/default/style.less +22 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/themes/default/throbber.gif +0 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/themes/default-dark/32px.png +0 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/themes/default-dark/40px.png +0 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/themes/default-dark/style.css +1146 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/themes/default-dark/style.less +50 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/themes/default-dark/throbber.gif +0 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/themes/main.less +77 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/themes/mixins.less +104 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/themes/responsive.less +67 -0
- data/public/vendor/github/vakata/jstree@3.3.8/src/vakata-jstree.js +38 -0
- data/public/vendor/github/vakata/jstree@3.3.8/test/unit/index.html +16 -0
- data/public/vendor/github/vakata/jstree@3.3.8/test/unit/libs/qunit.css +244 -0
- data/public/vendor/github/vakata/jstree@3.3.8/test/unit/libs/qunit.js +2212 -0
- data/public/vendor/github/vakata/jstree@3.3.8/test/unit/test.js +11 -0
- data/public/vendor/github/vakata/jstree@3.3.8/test/visual/desktop/index.html +44 -0
- data/public/vendor/github/vakata/jstree@3.3.8/test/visual/mobile/index.html +42 -0
- data/public/vendor/github/vakata/jstree@3.3.8/test/visual/screenshots/desktop/desktop.png +0 -0
- data/public/vendor/github/vakata/jstree@3.3.8/test/visual/screenshots/desktop/home.png +0 -0
- data/public/vendor/github/vakata/jstree@3.3.8/test/visual/screenshots/mobile/home.png +0 -0
- data/public/vendor/github/vakata/jstree@3.3.8/test/visual/screenshots/mobile/mobile.png +0 -0
- data/public/vendor/github/vakata/jstree@3.3.8.js +3 -0
- data/public/vendor/system-csp-production.js +3 -3
- data/public/vendor/system-csp-production.js.map +1 -1
- data/public/vendor/system-csp-production.src.js +146 -140
- data/public/vendor/system-polyfills.js.map +1 -1
- data/public/vendor/system-polyfills.src.js +1 -0
- data/public/vendor/system.js +3 -3
- data/public/vendor/system.js.map +1 -1
- data/public/vendor/system.src.js +4771 -2383
- data/views/_options.erb +21 -0
- data/views/layout.erb +17 -18
- metadata +102 -43
- data/bin/chromedriver +0 -0
- data/bin/geckodriver +0 -0
- data/public/shims/form-core.js +0 -3
- data/public/shims/form-validation.js +0 -3
- data/public/shims/plugins/jquery.ui.position.js +0 -13
- data/public/shims/styles/shim.css +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 25c2424857780517682276d21694f8ab0c3594772b82f08dcdca1934dcf23fd9
|
4
|
+
data.tar.gz: 3e27329d829825b7a3a73f14ebdecad514648381dbba914cfca6ee63564c795a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5137bf6118ee0044a0b8d5b1979f525686d7dd1698d6c795682d39a6c9d8247410104441fb642a585e6df9a18ad827336c8f61215f9e3c8c68fb387e0b6e2f41
|
7
|
+
data.tar.gz: e7ec5cebd2595b3a5f90cb750ce583161821e1be22dfcf14b1ba1fbe11dda440105795ac04b06caeb456c0f78e55db50d730704db8972667ca8d7acae5170692
|
data/bin/sequenceserver
CHANGED
@@ -308,34 +308,6 @@ begin
|
|
308
308
|
exit! unless set?
|
309
309
|
end
|
310
310
|
end
|
311
|
-
rescue SequenceServer::INCOMPATIBLE_BLAST_DATABASES => e
|
312
|
-
unless list_databases? || make_blast_databases?
|
313
|
-
# Print error raised.
|
314
|
-
puts
|
315
|
-
puts e
|
316
|
-
|
317
|
-
# Offer user to format the FASTA files.
|
318
|
-
database_dir = SequenceServer.config[:database_dir]
|
319
|
-
puts
|
320
|
-
puts <<~MSG
|
321
|
-
Incompatible databases can cause BLAST searches and other features of
|
322
|
-
SequenceServer to fail unexpectedly.
|
323
|
-
You can view incompatible databases and choose to reformat them below.
|
324
|
-
Alternatively, please remove them from databases directory.
|
325
|
-
|
326
|
-
View incompatible databases? [y/n] (Default: y).
|
327
|
-
MSG
|
328
|
-
puts
|
329
|
-
print '>> '
|
330
|
-
response = STDIN.gets.to_s.strip
|
331
|
-
unless response =~ /^[n]$/i
|
332
|
-
reformatted = SequenceServer.makeblastdb.reformat
|
333
|
-
exit! if reformatted.empty? && !set?
|
334
|
-
redo unless set?
|
335
|
-
else
|
336
|
-
exit! unless set?
|
337
|
-
end
|
338
|
-
end
|
339
311
|
rescue SequenceServer::ENOENT,
|
340
312
|
SequenceServer::CONFIG_FILE_ERROR,
|
341
313
|
SequenceServer::BLAST_DATABASE_ERROR,
|
@@ -382,8 +354,28 @@ begin
|
|
382
354
|
|
383
355
|
if make_blast_databases?
|
384
356
|
if SequenceServer.makeblastdb.scan
|
385
|
-
|
386
|
-
|
357
|
+
puts
|
358
|
+
puts <<~MSG
|
359
|
+
SequenceServer has scanned your databases directory and will now offer
|
360
|
+
to convert FASTA files into BLAST databases. It will also offer to
|
361
|
+
reformat any old-format BLAST databases and those created without
|
362
|
+
the -parse_seqids option of makeblastdb (-parse_seqids option is
|
363
|
+
required for sequence retrieval to correctly work).
|
364
|
+
|
365
|
+
Note that reformatting process can be slow large BLAST databases and
|
366
|
+
fail if sequence identifiers are longer than 50 characters. While we
|
367
|
+
exepect the reformatting process to work in most other cases, things
|
368
|
+
can inevitably go wrong. Thus, please back up your databases before
|
369
|
+
reformatting and post any issues to our Google Group/GitHub so that
|
370
|
+
we can all learn from it.
|
371
|
+
|
372
|
+
Proceed? [y/n] (Default: y).
|
373
|
+
MSG
|
374
|
+
puts
|
375
|
+
print '>> '
|
376
|
+
response = STDIN.gets.to_s.strip
|
377
|
+
SequenceServer.makeblastdb.run unless response =~ /^[n]$/i
|
378
|
+
else
|
387
379
|
puts "All FASTA files in #{SequenceServer.config[:database_dir]} " \
|
388
380
|
'are formatted.'
|
389
381
|
end
|
@@ -41,7 +41,11 @@ module SequenceServer
|
|
41
41
|
def message
|
42
42
|
<<~MSG
|
43
43
|
Looks like there's a problem with one of the query sequences, selected
|
44
|
-
databases, or advanced parameters.
|
44
|
+
databases, or advanced parameters. Details of the error are included
|
45
|
+
below. Please ask on our
|
46
|
+
<a href="https://github.com/wurmlab/sequenceserver/issues" target="_blank">issue tracker</a>
|
47
|
+
or on our <a href="https://groups.google.com/g/sequenceserver">forum</a> if you are
|
48
|
+
not sure what the error message means, or if the error message is just a number.
|
45
49
|
MSG
|
46
50
|
end
|
47
51
|
|
@@ -21,8 +21,8 @@ module SequenceServer
|
|
21
21
|
# representation.
|
22
22
|
def to_json(*args)
|
23
23
|
# List all attributes that we want to send to the browser.
|
24
|
-
properties = %i[number id accession title length
|
25
|
-
qcovs sciname
|
24
|
+
properties = %i[number id accession title length total_score
|
25
|
+
qcovs sciname hsps links]
|
26
26
|
properties.inject({}) { |h, k| h[k] = send(k); h }.to_json(*args)
|
27
27
|
end
|
28
28
|
|
@@ -71,23 +71,12 @@ module SequenceServer
|
|
71
71
|
# client.
|
72
72
|
###
|
73
73
|
|
74
|
-
# Returns the sum of scores of all HSPs.
|
75
|
-
|
74
|
+
# Returns the sum of scores of all HSPs. Displayed in the tabular summary
|
75
|
+
# of hits in the HTML report. Should probably be calculated in browser?
|
76
|
+
def total_score
|
76
77
|
hsps.map(&:score).reduce(:+)
|
77
78
|
end
|
78
79
|
|
79
|
-
# Returns the sum of identity of all HSPs divided by sum of length of all
|
80
|
-
# HSPs (expressed as percentage).
|
81
|
-
def identity
|
82
|
-
hsps.map(&:identity).reduce(:+) * 100 / hsps.map(&:length).reduce(:+)
|
83
|
-
end
|
84
|
-
|
85
|
-
# Returns the minimum evalue of all HSPs of the Hit. This is shown in the
|
86
|
-
# tabular overview of hits in the HTML report.
|
87
|
-
def evalue
|
88
|
-
hsps.first.evalue
|
89
|
-
end
|
90
|
-
|
91
80
|
private
|
92
81
|
|
93
82
|
# Returns the report object that this hit is a part of. This is used to
|
@@ -60,28 +60,19 @@ module SequenceServer
|
|
60
60
|
|
61
61
|
# Handle error. See [1].
|
62
62
|
case exitstatus
|
63
|
-
when 1
|
64
|
-
# Error in query
|
63
|
+
when 1..2
|
64
|
+
# 1: Error in query sequences or options.
|
65
|
+
# 2: Error in BLAST databases.
|
65
66
|
error = IO.foreach(stderr).grep(ERROR_LINE).join
|
66
67
|
error = File.read(stderr) if error.empty?
|
67
|
-
fail InputError, error
|
68
|
-
when 2
|
69
|
-
fail InputError, <<~MSG
|
70
|
-
BLAST signalled a problem with the databases that you searched.
|
71
|
-
|
72
|
-
Most likely one or more of your databases were created using an
|
73
|
-
older version of BLAST. Please consider recreating the databases
|
74
|
-
using BLAST #{BLAST_VERSION}.
|
75
|
-
|
76
|
-
As a temporary solution, you can try searching one database at a time.
|
77
|
-
MSG
|
68
|
+
fail InputError, "(#{exitstatus}) #{error}"
|
78
69
|
when 4
|
79
70
|
# Out of memory. User can retry with a shorter search, so raising
|
80
71
|
# InputError here instead of SystemError.
|
81
72
|
fail InputError, <<~MSG
|
82
|
-
Ran out of memory. Please try a smaller query,
|
73
|
+
Ran out of memory. Please try a smaller query, fewer and smaller
|
83
74
|
databases, or limiting the output by using advanced options.
|
84
|
-
|
75
|
+
MSG
|
85
76
|
when 6
|
86
77
|
# Error creating output files. It can't be a permission issue as that
|
87
78
|
# would have been caught while creating job directory. But we can run
|
@@ -98,7 +89,7 @@ module SequenceServer
|
|
98
89
|
fail SystemError, <<~MSG
|
99
90
|
BLAST failed abruptly (exit status: #{exitstatus}). Most likely there is a
|
100
91
|
problem with the BLAST+ binaries.
|
101
|
-
|
92
|
+
MSG
|
102
93
|
end
|
103
94
|
end
|
104
95
|
# rubocop:enable Metrics/CyclomaticComplexity
|
@@ -155,7 +146,7 @@ module SequenceServer
|
|
155
146
|
end
|
156
147
|
|
157
148
|
def allowed_chars
|
158
|
-
/\A[a-z0-9\-_\. ']*\Z/i
|
149
|
+
/\A[a-z0-9\-_\. ',]*\Z/i
|
159
150
|
end
|
160
151
|
|
161
152
|
def disallowed_options
|
@@ -167,4 +158,4 @@ end
|
|
167
158
|
|
168
159
|
# References
|
169
160
|
# ----------
|
170
|
-
# [1]: http://www.ncbi.nlm.nih.gov/books/NBK1763/
|
161
|
+
# [1]: http://www.ncbi.nlm.nih.gov/books/NBK1763/ (Appendices)
|
@@ -25,6 +25,7 @@ module SequenceServer
|
|
25
25
|
def initialize(job)
|
26
26
|
super do
|
27
27
|
@queries = []
|
28
|
+
@querydb = job.databases
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
@@ -42,7 +43,8 @@ module SequenceServer
|
|
42
43
|
}.update(search_id: job.id,
|
43
44
|
submitted_at: job.submitted_at.utc,
|
44
45
|
imported_xml: !!job.imported_xml_file,
|
45
|
-
seqserv_version: SequenceServer::VERSION
|
46
|
+
seqserv_version: SequenceServer::VERSION,
|
47
|
+
non_parse_seqids: !!job.databases&.any?(&:non_parse_seqids?)).to_json
|
46
48
|
end
|
47
49
|
|
48
50
|
private
|
@@ -80,13 +82,12 @@ module SequenceServer
|
|
80
82
|
# Get database information (title and type) from job yaml or from XML.
|
81
83
|
# Sets `querydb` and `dbtype` attributes.
|
82
84
|
def extract_db_info(ir)
|
83
|
-
if
|
85
|
+
if @querydb.empty?
|
84
86
|
@querydb = ir[3].split.map do |path|
|
85
87
|
{ title: File.basename(path) }
|
86
88
|
end
|
87
89
|
@dbtype = dbtype_from_program
|
88
90
|
else
|
89
|
-
@querydb = job.databases
|
90
91
|
@dbtype = @querydb.first.type
|
91
92
|
end
|
92
93
|
end
|
@@ -176,6 +177,7 @@ module SequenceServer
|
|
176
177
|
def parse_xml(xml)
|
177
178
|
node_to_array Ox.parse(xml).root
|
178
179
|
rescue Ox::ParseError
|
180
|
+
fail 'Error parsing XML file' if job.imported_xml_file
|
179
181
|
fail InputError, <<~MSG
|
180
182
|
BLAST generated incorrect XML output. This can happen if sequence ids in your
|
181
183
|
databases are not unique across all files. As a temporary workaround, you can
|
@@ -99,6 +99,7 @@ module SequenceServer
|
|
99
99
|
{
|
100
100
|
host: '0.0.0.0',
|
101
101
|
port: 4567,
|
102
|
+
databases_widget: 'classic',
|
102
103
|
options: {
|
103
104
|
blastn: ['-task blastn', '-evalue 1e-5'],
|
104
105
|
blastp: ['-evalue 1e-5'],
|
@@ -106,7 +107,9 @@ module SequenceServer
|
|
106
107
|
tblastx: ['-evalue 1e-5'],
|
107
108
|
tblastn: ['-evalue 1e-5']
|
108
109
|
},
|
109
|
-
num_threads: 1
|
110
|
+
num_threads: 1,
|
111
|
+
num_jobs: 1,
|
112
|
+
job_lifetime: 43200
|
110
113
|
}
|
111
114
|
end
|
112
115
|
end
|
@@ -19,14 +19,14 @@ module SequenceServer
|
|
19
19
|
# SequenceServer will always place BLAST database files alongside input FASTA,
|
20
20
|
# and use `parse_seqids` option of `makeblastdb` to format databases.
|
21
21
|
Database = Struct.new(:name, :title, :type, :nsequences, :ncharacters,
|
22
|
-
:updated_on) do
|
22
|
+
:updated_on, :format, :categories) do
|
23
23
|
|
24
24
|
extend Forwardable
|
25
25
|
|
26
26
|
def_delegators SequenceServer, :config, :sys
|
27
27
|
|
28
28
|
def initialize(*args)
|
29
|
-
args[2].downcase!
|
29
|
+
args[2].downcase! # type
|
30
30
|
args.each(&:freeze)
|
31
31
|
super
|
32
32
|
|
@@ -34,6 +34,7 @@ module SequenceServer
|
|
34
34
|
end
|
35
35
|
|
36
36
|
attr_reader :id
|
37
|
+
alias path name
|
37
38
|
|
38
39
|
def retrieve(accession, coords = nil)
|
39
40
|
cmd = "blastdbcmd -db #{name} -entry '#{accession}'"
|
@@ -48,10 +49,36 @@ module SequenceServer
|
|
48
49
|
nil
|
49
50
|
end
|
50
51
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
52
|
+
# Returns true if the database contains the given sequence id.
|
53
|
+
# Returns false otherwise.
|
54
|
+
def include?(id)
|
55
|
+
cmd = "blastdbcmd -entry '#{id}' -db #{name}"
|
56
|
+
sys(cmd, path: config[:bin]) rescue false
|
57
|
+
end
|
58
|
+
|
59
|
+
def v4?
|
60
|
+
format == '4'
|
61
|
+
end
|
62
|
+
|
63
|
+
def v5?
|
64
|
+
format == '5'
|
65
|
+
end
|
66
|
+
|
67
|
+
# Return true if the database was _not_ created using the -parse_seqids
|
68
|
+
# option of makeblastdb.
|
69
|
+
def non_parse_seqids?
|
70
|
+
return if alias?
|
71
|
+
case format
|
72
|
+
when '5'
|
73
|
+
(%w[nog nos pog pos] & extensions).length != 2
|
74
|
+
when '4'
|
75
|
+
(%w[nog nsd nsi pod psd psi] & extensions).length != 3
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns true if the database was created using blastdb_aliastool.
|
80
|
+
def alias?
|
81
|
+
(%w[nal pal] & extensions).length == 1 && extensions.count == 1
|
55
82
|
end
|
56
83
|
|
57
84
|
def ==(other)
|
@@ -65,6 +92,16 @@ module SequenceServer
|
|
65
92
|
def to_json(*args)
|
66
93
|
to_h.update(id: id).to_json(*args)
|
67
94
|
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def extensions
|
99
|
+
# The glob pattern used here is quite relaxed. This is to capture
|
100
|
+
# multipart databases as well. It is possible that non-blast-database
|
101
|
+
# extensions may also be picked. However, that shouldn't be a problem
|
102
|
+
# as we only check whether certain required extensions are present or not.
|
103
|
+
@extensions ||= Dir["#{path}*{n,p}*"].map { |p| p.split('.').last }.sort.uniq
|
104
|
+
end
|
68
105
|
end
|
69
106
|
|
70
107
|
# Model Database's eigenclass as a collection of Database objects.
|
@@ -80,9 +117,8 @@ module SequenceServer
|
|
80
117
|
@collection ||= {}
|
81
118
|
end
|
82
119
|
|
83
|
-
def collection=(
|
84
|
-
|
85
|
-
db = Database.new(*db_attrs)
|
120
|
+
def collection=(databases)
|
121
|
+
databases.each do |db|
|
86
122
|
collection[db.id] = db
|
87
123
|
end
|
88
124
|
end
|
@@ -102,6 +138,30 @@ module SequenceServer
|
|
102
138
|
collection.values
|
103
139
|
end
|
104
140
|
|
141
|
+
def tree
|
142
|
+
all.each_with_object({}) do |db, data|
|
143
|
+
data[db.type] ||= []
|
144
|
+
use_parent = '#'
|
145
|
+
db.categories.each_with_index do |entry, index|
|
146
|
+
parent = index.zero? ? '#' : db.categories[0..(index - 1)].join('-')
|
147
|
+
use_id = db.categories[0..index].join('-')
|
148
|
+
element = { id: use_id, parent: parent, text: entry }
|
149
|
+
data[db.type] << element unless data[db.type].include?(element)
|
150
|
+
use_parent = use_id
|
151
|
+
end
|
152
|
+
|
153
|
+
data[db.type] <<
|
154
|
+
{
|
155
|
+
id: db.id,
|
156
|
+
parent: use_parent,
|
157
|
+
text: db.title,
|
158
|
+
icon: 'glyphicon glyphicon-file'
|
159
|
+
}
|
160
|
+
|
161
|
+
yield(db, data[db.type].last) if block_given?
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
105
165
|
def each(&block)
|
106
166
|
all.each(&block)
|
107
167
|
end
|
data/lib/sequenceserver/job.rb
CHANGED
@@ -11,17 +11,6 @@ module SequenceServer
|
|
11
11
|
# makeblastdb.scan && makeblastdb.run
|
12
12
|
#
|
13
13
|
class MAKEBLASTDB
|
14
|
-
# We want V5 databases created using -parse_seqids for proper function of
|
15
|
-
# SequenceServer. This means each database should be comprised of at least 9
|
16
|
-
# files with the following extensions. Databases created by us will have two
|
17
|
-
# additional files with the extensions nhd and nhi, or phd and phi, due to
|
18
|
-
# the use of -hash_index option. Finally, multipart databases will have one
|
19
|
-
# additional file with the extension nal or pal.
|
20
|
-
REQUIRED_EXTENSIONS = {
|
21
|
-
'nucleotide' => %w{ndb nhr nin nog nos not nsq ntf nto}.freeze,
|
22
|
-
'protein' => %w{pdb phr pin pog pos pot psq ptf pto}.freeze
|
23
|
-
}
|
24
|
-
|
25
14
|
extend Forwardable
|
26
15
|
|
27
16
|
def_delegators SequenceServer, :config, :sys
|
@@ -70,18 +59,12 @@ module SequenceServer
|
|
70
59
|
# Returns true if the databases directory contains one or more incompatible
|
71
60
|
# databases.
|
72
61
|
#
|
73
|
-
# Note that it is okay to only use V4 databases or only V5 databases.
|
62
|
+
# Note that it is okay to only use V4 databases or only V5 databases.
|
74
63
|
# Incompatibility arises when they are mixed.
|
75
64
|
def any_incompatible?
|
76
|
-
return false if @
|
77
|
-
|
78
|
-
|
79
|
-
# path is common to both and sufficient for our needs.
|
80
|
-
to_reformat = @fastas_to_reformat.map(&:first)
|
81
|
-
formatted = @formatted_fastas.map(&:first)
|
82
|
-
# Check that they are not equal. Using intersection operator ensures
|
83
|
-
# comparison even if their order differs.
|
84
|
-
formatted & to_reformat != formatted
|
65
|
+
return false if @formatted_fastas.all? { |ff| ff.v4? || ff.alias? }
|
66
|
+
return false if @formatted_fastas.all? { |ff| ff.v5? || ff.alias? }
|
67
|
+
true
|
85
68
|
end
|
86
69
|
|
87
70
|
# Runs makeblastdb on each file in `@fastas_to_format` and
|
@@ -109,8 +92,8 @@ module SequenceServer
|
|
109
92
|
# Make the intent clear as well as ensure the program won't crash if
|
110
93
|
# we accidentally call reformat before calling scan.
|
111
94
|
return unless @fastas_to_reformat
|
112
|
-
@fastas_to_reformat.select do |path, title, type|
|
113
|
-
make_blast_database('reformat', path, title, type)
|
95
|
+
@fastas_to_reformat.select do |path, title, type, non_parse_seqids|
|
96
|
+
make_blast_database('reformat', path, title, type, non_parse_seqids)
|
114
97
|
end
|
115
98
|
end
|
116
99
|
|
@@ -120,34 +103,36 @@ module SequenceServer
|
|
120
103
|
# formatted. Adds to @formatted_fastas.
|
121
104
|
def determine_formatted_fastas
|
122
105
|
blastdbcmd.each_line do |line|
|
123
|
-
path,
|
106
|
+
path, *rest = line.chomp.split("\t")
|
124
107
|
next if multipart_database_name?(path)
|
125
|
-
|
108
|
+
rest << get_categories(path)
|
109
|
+
@formatted_fastas << Database.new(path, *rest)
|
126
110
|
end
|
127
111
|
end
|
128
112
|
|
129
113
|
# Determines which FASTA files in the database directory require
|
130
114
|
# reformatting. Adds to @fastas_to_format.
|
131
115
|
def determine_fastas_to_reformat
|
132
|
-
@formatted_fastas.each do |
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
@fastas_to_reformat << [path, title, type]
|
116
|
+
@formatted_fastas.each do |ff|
|
117
|
+
if ff.v4? || ff.non_parse_seqids?
|
118
|
+
@fastas_to_reformat << [ff.path, ff.title, ff.type, ff.non_parse_seqids?]
|
119
|
+
end
|
138
120
|
end
|
139
121
|
end
|
140
122
|
|
141
123
|
# Determines which FASTA files in the database directory are
|
142
124
|
# unformatted. Adds to @fastas_to_format.
|
143
125
|
def determine_unformatted_fastas
|
144
|
-
Find.find
|
126
|
+
# Add a trailing slash to database_dir - Find.find doesn't work as
|
127
|
+
# expected without the trailing slash if database_dir is a symlink
|
128
|
+
# inside a docker container.
|
129
|
+
Find.find(database_dir + '/') do |path|
|
145
130
|
next if File.directory?(path)
|
146
131
|
next unless probably_fasta?(path)
|
147
132
|
next if @formatted_fastas.any? { |f| f[0] == path }
|
148
133
|
|
149
134
|
@fastas_to_format << [path,
|
150
|
-
make_db_title(
|
135
|
+
make_db_title(path),
|
151
136
|
guess_sequence_type_in_fasta(path)]
|
152
137
|
end
|
153
138
|
end
|
@@ -157,7 +142,7 @@ module SequenceServer
|
|
157
142
|
# by `determine_formatted_fastas`.
|
158
143
|
def blastdbcmd
|
159
144
|
cmd = "blastdbcmd -recursive -list #{config[:database_dir]}" \
|
160
|
-
' -list_outfmt "%f %t %p %n %l %d"'
|
145
|
+
' -list_outfmt "%f %t %p %n %l %d %v"'
|
161
146
|
out, err = sys(cmd, path: config[:bin])
|
162
147
|
errpat = /BLAST Database error/
|
163
148
|
fail BLAST_DATABASE_ERROR.new(cmd, err) if err.match(errpat)
|
@@ -167,10 +152,11 @@ module SequenceServer
|
|
167
152
|
end
|
168
153
|
|
169
154
|
# Create BLAST database, given FASTA file and sequence type in FASTA file.
|
170
|
-
def make_blast_database(action, file, title, type)
|
155
|
+
def make_blast_database(action, file, title, type, non_parse_seqids = false)
|
171
156
|
return unless make_blast_database?(action, file, type)
|
172
157
|
title = confirm_database_title(title)
|
173
|
-
|
158
|
+
extract_fasta(file) unless File.exist?(file)
|
159
|
+
taxonomy = taxid_map(file, non_parse_seqids) || taxid
|
174
160
|
_make_blast_database(file, type, title, taxonomy)
|
175
161
|
end
|
176
162
|
|
@@ -200,11 +186,10 @@ module SequenceServer
|
|
200
186
|
|
201
187
|
# Check if a '.taxid_map.txt' file exists. If not, try getting it
|
202
188
|
# using blastdbcmd.
|
203
|
-
def taxid_map(db)
|
189
|
+
def taxid_map(db, non_parse_seqids)
|
190
|
+
return if non_parse_seqids
|
204
191
|
taxid_map = db.sub(/#{File.extname(db)}$/, '.taxid_map.txt')
|
205
|
-
|
206
|
-
extract_taxid_map(db, taxid_map)
|
207
|
-
end
|
192
|
+
extract_taxid_map(db, taxid_map) if !File.exist?(taxid_map)
|
208
193
|
"-taxid_map #{taxid_map}" if !File.zero?(taxid_map)
|
209
194
|
end
|
210
195
|
|
@@ -213,22 +198,23 @@ module SequenceServer
|
|
213
198
|
# Using 0 as taxid is equivalent to not setting taxid for the database
|
214
199
|
# that will be created.
|
215
200
|
def taxid
|
201
|
+
default = 0
|
216
202
|
print 'Enter taxid (optional): '
|
217
203
|
user_response = STDIN.gets.strip
|
218
|
-
"-taxid #{user_response
|
204
|
+
"-taxid #{user_response.empty? && default || Integer(user_response)}"
|
219
205
|
rescue ArgumentError # presumably from call to Interger()
|
220
206
|
puts 'taxid should be a number'
|
221
207
|
retry
|
222
208
|
end
|
223
209
|
|
224
210
|
def _make_blast_database(file, type, title, taxonomy)
|
225
|
-
|
226
|
-
|
227
|
-
"-dbtype #{type.to_s.slice(0, 4)} -title '#{title}'" \
|
211
|
+
cmd = "makeblastdb -parse_seqids -hash_index -in '#{file}'" \
|
212
|
+
" -dbtype #{type.to_s.slice(0, 4)} -title '#{title}'" \
|
228
213
|
" #{taxonomy}"
|
229
214
|
out, err = sys(cmd, path: config[:bin])
|
230
215
|
puts out.strip
|
231
216
|
puts err.strip
|
217
|
+
return true
|
232
218
|
rescue CommandFailed => e
|
233
219
|
puts <<~MSG
|
234
220
|
Could not create BLAST database for: #{file}
|
@@ -278,8 +264,16 @@ module SequenceServer
|
|
278
264
|
!(db_name.match(%r{.+/\S+\.\d{2,3}$}).nil?)
|
279
265
|
end
|
280
266
|
|
267
|
+
def get_categories(path)
|
268
|
+
path
|
269
|
+
.gsub(config[:database_dir], '') # remove database_dir from path
|
270
|
+
.split('/')
|
271
|
+
.reject(&:empty?)[0..-2] # the first entry might be '' if database_dir does not end with /
|
272
|
+
end
|
273
|
+
|
281
274
|
# Returns true if first character of the file is '>'.
|
282
275
|
def probably_fasta?(file)
|
276
|
+
return false unless file.match(/((cds)|(fasta)|(fna)|(pep)|(cdna)|(fa)|(prot)|(fas)|(genome)|(nuc)|(dna)|(nt))$/i)
|
283
277
|
File.read(file, 1) == '>'
|
284
278
|
end
|
285
279
|
|
@@ -288,7 +282,8 @@ module SequenceServer
|
|
288
282
|
# For example:
|
289
283
|
# Cobs1.4.proteins.fasta -> Cobs 1.4 proteins
|
290
284
|
# S_invicta.xx.2.5.small.nucl.fa -> S invicta xx 2.5 small nucl
|
291
|
-
def make_db_title(
|
285
|
+
def make_db_title(path)
|
286
|
+
db_name = File.basename(path)
|
292
287
|
db_name.tr!('"', "'")
|
293
288
|
# removes .fasta like extension names
|
294
289
|
db_name.gsub!(File.extname(db_name), '')
|
@@ -79,6 +79,10 @@ module SequenceServer
|
|
79
79
|
options: SequenceServer.config[:options]
|
80
80
|
}
|
81
81
|
|
82
|
+
if SequenceServer.config[:databases_widget] == 'tree'
|
83
|
+
searchdata.update(tree: Database.tree)
|
84
|
+
end
|
85
|
+
|
82
86
|
# If a job_id is specified, update searchdata from job meta data (i.e.,
|
83
87
|
# query, pre-selected databases, advanced options used). Query is only
|
84
88
|
# updated if params[:query] is not specified.
|
data/lib/sequenceserver.rb
CHANGED
@@ -5,11 +5,7 @@ require 'resolv'
|
|
5
5
|
# Top level module / namespace.
|
6
6
|
module SequenceServer
|
7
7
|
# The default version of BLAST that will be downloaded and configured for use.
|
8
|
-
BLAST_VERSION = '2.
|
9
|
-
# The minimum version of BLAST that SequenceServer is happy to run with. This
|
10
|
-
# is for compatiblity with older database formats. Users will download BLAST
|
11
|
-
# themselves.
|
12
|
-
MIN_BLAST_VERSION = '2.9.0+'.freeze
|
8
|
+
BLAST_VERSION = '2.12.0+'.freeze
|
13
9
|
|
14
10
|
# Default location of configuration file.
|
15
11
|
DEFAULT_CONFIG_FILE = '~/.sequenceserver.conf'.freeze
|
@@ -78,9 +74,9 @@ module SequenceServer
|
|
78
74
|
Thread.abort_on_exception = true if development?
|
79
75
|
|
80
76
|
# Now locate binaries, scan databases directory, require any plugin files.
|
77
|
+
load_extension
|
81
78
|
init_binaries
|
82
79
|
init_database
|
83
|
-
load_extension
|
84
80
|
|
85
81
|
# The above methods validate bin dir, database dir, and path to plugin
|
86
82
|
# files. Port and host settings don't need to be validated: if running
|
@@ -141,7 +137,8 @@ module SequenceServer
|
|
141
137
|
puts " - http://#{ip_address}:#{config[:port]}"
|
142
138
|
puts " - http://#{hostname}:#{config[:port]}" if hostname
|
143
139
|
puts ' To share your setup with anyone in the world, ask your IT team'
|
144
|
-
puts ' for a public IP address
|
140
|
+
puts ' for a public IP address or consider the SequenceServer cloud'
|
141
|
+
puts ' hosting service: https://sequenceserver.com/cloud'
|
145
142
|
puts ' To disable sharing, set :host: key in config file to 127.0.0.1'
|
146
143
|
puts ' and restart server.'
|
147
144
|
end
|
@@ -205,12 +202,19 @@ module SequenceServer
|
|
205
202
|
|
206
203
|
makeblastdb.scan
|
207
204
|
fail NO_BLAST_DATABASE_FOUND, config[:database_dir] if !makeblastdb.any_formatted?
|
208
|
-
fail INCOMPATIBLE_BLAST_DATABASES, config[:database_dir] if makeblastdb.any_incompatible?
|
209
205
|
|
210
206
|
Database.collection = makeblastdb.formatted_fastas
|
211
207
|
Database.each do |database|
|
212
|
-
logger.debug
|
213
|
-
|
208
|
+
logger.debug "Found #{database.type} database '#{database.title}' at '#{database.path}'"
|
209
|
+
if database.non_parse_seqids?
|
210
|
+
logger.warn "Database '#{database.title}' was created without using the" \
|
211
|
+
' -parse_seqids option of makeblastdb. FASTA download will' \
|
212
|
+
" not work correctly (path: '#{database.path}')."
|
213
|
+
elsif database.v4?
|
214
|
+
logger.warn "Database '#{database.title}' is of older format. Mixing" \
|
215
|
+
' old and new format databases can be problematic' \
|
216
|
+
"(path: '#{database.path}')."
|
217
|
+
end
|
214
218
|
end
|
215
219
|
end
|
216
220
|
|
@@ -245,7 +249,7 @@ module SequenceServer
|
|
245
249
|
end
|
246
250
|
version = out.split[1]
|
247
251
|
fail BLAST_NOT_INSTALLED_OR_NOT_EXECUTABLE if version.empty?
|
248
|
-
fail BLAST_NOT_COMPATIBLE, version unless is_compatible(version,
|
252
|
+
fail BLAST_NOT_COMPATIBLE, version unless is_compatible(version, BLAST_VERSION)
|
249
253
|
end
|
250
254
|
|
251
255
|
def server_url
|