sequenceserver 1.0.0.pre.3 → 1.0.0.pre.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sequenceserver might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +47 -4
- data/lib/sequenceserver.rb +3 -177
- data/lib/sequenceserver/blast.rb +4 -59
- data/lib/sequenceserver/blast/constants.rb +34 -0
- data/lib/sequenceserver/blast/formatter.rb +69 -0
- data/lib/sequenceserver/blast/hit.rb +33 -12
- data/lib/sequenceserver/blast/hsp.rb +4 -6
- data/lib/sequenceserver/blast/query.rb +6 -8
- data/lib/sequenceserver/blast/report.rb +94 -77
- data/lib/sequenceserver/config.rb +1 -0
- data/lib/sequenceserver/exceptions.rb +1 -1
- data/lib/sequenceserver/links.rb +14 -21
- data/lib/sequenceserver/routes.rb +163 -0
- data/lib/sequenceserver/search.rb +19 -0
- data/lib/sequenceserver/sequence.rb +135 -30
- data/public/css/custom.css +17 -8
- data/public/dist/css/sequenceserver.min.css +1 -1
- data/public/dist/css/sequenceserver.min.css.gz +0 -0
- data/public/dist/js/sequenceserver.min.js +1 -1
- data/public/dist/js/sequenceserver.min.js.gz +0 -0
- data/public/js/sequenceserver.js +66 -32
- data/sequenceserver.gemspec +1 -1
- data/views/result.erb +8 -8
- data/views/search.erb +12 -3
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9ed1609668685537118f3ff44bb18bda69cbdf8b
|
4
|
+
data.tar.gz: d2b5aa23928abb0ed230747e789b45440b9927fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2b7d392d183377a918b4841685adc34fe26997b73eda7f5129273064bf8bae30929448ea8fd4818508728957460cc20c299b375ba644405060ccbed432ac5fa
|
7
|
+
data.tar.gz: bdca19b354e1b70113d2c77fc99699c4150e30e61e8fe5a82195d8e8033ad826a82ea7fdd9fde69a0528197d4aa739648f2bfd0d31f81d2da25dd7b32e520bfc
|
data/README.md
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
[![build status](https://secure.travis-ci.org/yannickwurm/sequenceserver.png?branch=master)](https://travis-ci.org/yannickwurm/sequenceserver)
|
2
|
-
[![
|
3
|
-
[![
|
4
|
-
[![
|
2
|
+
[![code climate](https://codeclimate.com/github/yannickwurm/sequenceserver/badges/gpa.svg)](https://codeclimate.com/github/yannickwurm/sequenceserver)
|
3
|
+
[![coverage](https://codeclimate.com/github/yannickwurm/sequenceserver/badges/coverage.svg)](https://codeclimate.com/github/yannickwurm/sequenceserver)
|
4
|
+
[![gem version](https://badge.fury.io/rb/sequenceserver.svg)](http://rubygems.org/gems/sequenceserver)
|
5
|
+
[![total downloads](http://ruby-gem-downloads-badge.herokuapp.com/sequenceserver?type=total&color=brightgreen)](http://rubygems.org/gems/sequenceserver)
|
6
|
+
|
7
|
+
[![gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/yannickwurm/sequenceserver)
|
5
8
|
|
6
9
|
# SequenceServer - BLAST searching made easy!
|
7
10
|
|
@@ -10,7 +13,47 @@ interface for use locally or over the web.
|
|
10
13
|
|
11
14
|
## Installation
|
12
15
|
|
13
|
-
Please see http://www.sequenceserver.com
|
16
|
+
Please see http://www.sequenceserver.com.
|
17
|
+
|
18
|
+
## Contribute
|
19
|
+
|
20
|
+
You will need Ruby and NodeJS and respective package managers (RubyGems and
|
21
|
+
npm) for development.
|
22
|
+
|
23
|
+
##### Get source code.
|
24
|
+
```
|
25
|
+
git clone https://github.com/yannickwurm/sequenceserver
|
26
|
+
cd sequenceserver
|
27
|
+
```
|
28
|
+
|
29
|
+
##### Install dependencies.
|
30
|
+
###### Ruby
|
31
|
+
```
|
32
|
+
gem install bundler && bundle
|
33
|
+
```
|
34
|
+
|
35
|
+
###### Node
|
36
|
+
```
|
37
|
+
npm install
|
38
|
+
```
|
39
|
+
|
40
|
+
#### Run, test, lint
|
41
|
+
```
|
42
|
+
# Launch SequenceServer in development mode.
|
43
|
+
bundle exec bin/sequenceserver -D
|
44
|
+
|
45
|
+
# Run RSpec, Capybara, and RuboCop.
|
46
|
+
rake
|
47
|
+
|
48
|
+
# Run bootlint, csslint, jshint
|
49
|
+
npm run-script cop
|
50
|
+
|
51
|
+
# Minify JS and CSS
|
52
|
+
npm run-script build
|
53
|
+
```
|
54
|
+
|
55
|
+
SequenceServer runs in production mode by default. Minified JS and CSS are
|
56
|
+
picked in production mode only.
|
14
57
|
|
15
58
|
## Contributors
|
16
59
|
|
data/lib/sequenceserver.rb
CHANGED
@@ -1,16 +1,14 @@
|
|
1
|
-
require 'yaml'
|
2
1
|
require 'English'
|
3
|
-
require 'fileutils'
|
4
|
-
require 'sinatra/base'
|
5
2
|
require 'thin'
|
6
|
-
require 'json'
|
7
3
|
|
8
4
|
require 'sequenceserver/exceptions'
|
9
5
|
require 'sequenceserver/config'
|
10
6
|
require 'sequenceserver/logger'
|
7
|
+
require 'sequenceserver/search'
|
11
8
|
require 'sequenceserver/sequence'
|
12
9
|
require 'sequenceserver/database'
|
13
10
|
require 'sequenceserver/blast'
|
11
|
+
require 'sequenceserver/routes'
|
14
12
|
|
15
13
|
# Top level module / namespace.
|
16
14
|
module SequenceServer
|
@@ -90,7 +88,7 @@ module SequenceServer
|
|
90
88
|
# controller.
|
91
89
|
def call(env)
|
92
90
|
env['rack.logger'] = logger
|
93
|
-
|
91
|
+
Routes.call(env)
|
94
92
|
end
|
95
93
|
|
96
94
|
# Run SequenceServer interactively.
|
@@ -201,176 +199,4 @@ module SequenceServer
|
|
201
199
|
system("which #{command} > /dev/null 2>&1")
|
202
200
|
end
|
203
201
|
end
|
204
|
-
|
205
|
-
# Controller.
|
206
|
-
class App < Sinatra::Base
|
207
|
-
# See
|
208
|
-
# http://www.sinatrarb.com/configuration.html
|
209
|
-
configure do
|
210
|
-
# We don't need Rack::MethodOverride. Let's avoid the overhead.
|
211
|
-
disable :method_override
|
212
|
-
|
213
|
-
# Ensure exceptions never leak out of the app. Exceptions raised within
|
214
|
-
# the app must be handled by the app. We do this by attaching error
|
215
|
-
# blocks to exceptions we know how to handle and attaching to Exception
|
216
|
-
# as fallback.
|
217
|
-
disable :show_exceptions, :raise_errors
|
218
|
-
|
219
|
-
# Make it a policy to dump to 'rack.errors' any exception raised by the
|
220
|
-
# app so that error handlers don't have to do it themselves. But for it
|
221
|
-
# to always work, Exceptions defined by us should not respond to `code`
|
222
|
-
# or http_status` methods. Error blocks errors must explicitly set http
|
223
|
-
# status, if needed, by calling `status` method.
|
224
|
-
# method.
|
225
|
-
enable :dump_errors
|
226
|
-
|
227
|
-
# We don't want Sinatra do setup any loggers for us. We will use our own.
|
228
|
-
set :logging, nil
|
229
|
-
|
230
|
-
# Public, and views directory will be found here.
|
231
|
-
set :root, lambda { SequenceServer.root }
|
232
|
-
end
|
233
|
-
|
234
|
-
# See
|
235
|
-
# http://www.sinatrarb.com/intro.html#Mime%20Types
|
236
|
-
configure do
|
237
|
-
mime_type :fasta, 'text/fasta'
|
238
|
-
mime_type :xml, 'text/xml'
|
239
|
-
mime_type :tsv, 'text/tsv'
|
240
|
-
end
|
241
|
-
|
242
|
-
configure :production do
|
243
|
-
set :public_folder,
|
244
|
-
lambda { File.join SequenceServer.root, 'public', 'dist' }
|
245
|
-
end
|
246
|
-
|
247
|
-
helpers do
|
248
|
-
# Render an anchor element from the given Hash.
|
249
|
-
#
|
250
|
-
# See links.rb for example of a Hash object that will be rendered.
|
251
|
-
def a(link)
|
252
|
-
return unless link[:title] && link[:url]
|
253
|
-
a = ['<a']
|
254
|
-
a << "href=#{link[:url]}"
|
255
|
-
a << "class=\"#{link[:class]}\"" if link[:class]
|
256
|
-
a << "target=\"_blank\"" if absolute? link[:url]
|
257
|
-
a << '>'
|
258
|
-
a << "<i class=\"fa #{link[:icon]}\"></i>" if link[:icon]
|
259
|
-
a << link[:title]
|
260
|
-
a << '</a>'
|
261
|
-
a.join("\n")
|
262
|
-
end
|
263
|
-
|
264
|
-
# Is the given URI absolute? (or relative?)
|
265
|
-
def absolute?(uri)
|
266
|
-
URI.parse(uri).absolute?
|
267
|
-
end
|
268
|
-
|
269
|
-
# Prettify given data.
|
270
|
-
def prettify(data)
|
271
|
-
return prettify_tuple(data) if tuple? data
|
272
|
-
return prettify_float(data) if data.is_a? Float
|
273
|
-
data
|
274
|
-
end
|
275
|
-
|
276
|
-
# Formats float as "a.bcd" or "a x b^c". The latter if float is
|
277
|
-
# scientific notation. Former otherwise.
|
278
|
-
def prettify_float(float)
|
279
|
-
float.to_s.sub(/(\d*\.\d*)e?([+-]\d*)?/) do
|
280
|
-
base = Regexp.last_match[1]
|
281
|
-
power = Regexp.last_match[2]
|
282
|
-
s = format '%.2f', base
|
283
|
-
s << " × 10<sup>#{power}</sup>" if power
|
284
|
-
s
|
285
|
-
end
|
286
|
-
end
|
287
|
-
|
288
|
-
# Formats an array of two elements as "first (last)".
|
289
|
-
def prettify_tuple(tuple)
|
290
|
-
"#{tuple.first} (#{tuple.last})"
|
291
|
-
end
|
292
|
-
|
293
|
-
# Is the given value a tuple? (array of length two).
|
294
|
-
def tuple?(data)
|
295
|
-
return true if data.is_a?(Array) && data.length == 2
|
296
|
-
end
|
297
|
-
end
|
298
|
-
|
299
|
-
# For any request that hits the app in development mode, log incoming
|
300
|
-
# params.
|
301
|
-
before do
|
302
|
-
logger.debug params
|
303
|
-
end
|
304
|
-
|
305
|
-
# Render the search form.
|
306
|
-
get '/' do
|
307
|
-
erb :search, :locals => { :databases => Database.group_by(&:type) }
|
308
|
-
end
|
309
|
-
|
310
|
-
# BLAST search!
|
311
|
-
post '/' do
|
312
|
-
erb :result, :locals => { :report => BLAST.run(params) }
|
313
|
-
end
|
314
|
-
|
315
|
-
# @params sequence_ids: whitespace separated list of sequence ids to
|
316
|
-
# retrieve
|
317
|
-
# @params database_ids: whitespace separated list of database ids to
|
318
|
-
# retrieve the sequence from.
|
319
|
-
# @params download: whether to return raw response or initiate file
|
320
|
-
# download
|
321
|
-
#
|
322
|
-
# Use whitespace to separate entries in sequence_ids (all other chars exist
|
323
|
-
# in identifiers) and retreival_databases (we don't allow whitespace in a
|
324
|
-
# database's name, so it's safe).
|
325
|
-
get '/get_sequence/' do
|
326
|
-
sequence_ids = params[:sequence_ids].split(/\s/)
|
327
|
-
database_ids = params[:database_ids].split(/\s/)
|
328
|
-
|
329
|
-
sequences = Sequence.from_blastdb(sequence_ids, database_ids)
|
330
|
-
|
331
|
-
if params[:download]
|
332
|
-
file_name = "sequenceserver_#{sequence_ids.first}.fasta"
|
333
|
-
file = Tempfile.new file_name
|
334
|
-
sequences.each do |sequence|
|
335
|
-
file.puts sequence.fasta
|
336
|
-
end
|
337
|
-
file.close
|
338
|
-
send_file file.path, :type => :fasta, :filename => file_name
|
339
|
-
else
|
340
|
-
{
|
341
|
-
:sequence_ids => sequence_ids,
|
342
|
-
:databases => Database[database_ids].map(&:title),
|
343
|
-
:sequences => sequences.map(&:info)
|
344
|
-
}.to_json
|
345
|
-
end
|
346
|
-
end
|
347
|
-
|
348
|
-
get '/get_report/' do
|
349
|
-
ofile = BLAST.format(params)
|
350
|
-
|
351
|
-
send_file ofile[:filepath],
|
352
|
-
:filename => ofile[:filename],
|
353
|
-
:type => ofile[:type].to_sym
|
354
|
-
end
|
355
|
-
|
356
|
-
# This error block will only ever be hit if the user gives us a funny
|
357
|
-
# sequence or incorrect advanced parameter. Well, we could hit this block
|
358
|
-
# if someone is playing around with our HTTP API too.
|
359
|
-
error BLAST::ArgumentError do
|
360
|
-
status 400
|
361
|
-
error = env['sinatra.error']
|
362
|
-
erb :'400', :locals => { :error => error }
|
363
|
-
end
|
364
|
-
|
365
|
-
# This will catch any unhandled error and some very special errors. Ideally
|
366
|
-
# we will never hit this block. If we do, there's a bug in SequenceServer
|
367
|
-
# or something really weird going on. If we hit this error block we show
|
368
|
-
# the stacktrace to the user requesting them to post the same to our Google
|
369
|
-
# Group.
|
370
|
-
error Exception, BLAST::RuntimeError do
|
371
|
-
status 500
|
372
|
-
error = env['sinatra.error']
|
373
|
-
erb :'500', :locals => { :error => error }
|
374
|
-
end
|
375
|
-
end
|
376
202
|
end
|
data/lib/sequenceserver/blast.rb
CHANGED
@@ -5,6 +5,8 @@ require 'ox'
|
|
5
5
|
|
6
6
|
require 'sequenceserver/links'
|
7
7
|
require 'sequenceserver/blast/exceptions'
|
8
|
+
require 'sequenceserver/blast/constants'
|
9
|
+
require 'sequenceserver/blast/formatter'
|
8
10
|
require 'sequenceserver/blast/report'
|
9
11
|
require 'sequenceserver/blast/query'
|
10
12
|
require 'sequenceserver/blast/hit'
|
@@ -16,25 +18,6 @@ module SequenceServer
|
|
16
18
|
# `BLAST::ArgumentError` and `BLAST::RuntimeError` signal errors encountered
|
17
19
|
# when attempting a BLAST search.
|
18
20
|
module BLAST
|
19
|
-
ERROR_LINE = /\(CArgException.*\)\s(.*)/
|
20
|
-
|
21
|
-
ALGORITHMS = %w(blastn blastp blastx tblastn tblastx)
|
22
|
-
|
23
|
-
OUTFMT = {
|
24
|
-
'pairwise' => [0, 'txt'],
|
25
|
-
'qa_identity' => [1, 'txt'],
|
26
|
-
'qa_no_identity' => [2, 'txt'],
|
27
|
-
'fqa_identity' => [3, 'txt'],
|
28
|
-
'fqa_no_identity' => [4, 'txt'],
|
29
|
-
'xml' => [5, 'xml'],
|
30
|
-
'tsv' => [6, 'tsv'],
|
31
|
-
'tsv_commented' => [7, 'tsv'],
|
32
|
-
'asn_text' => [8, 'asn'],
|
33
|
-
'asn_binary' => [9, 'asn'],
|
34
|
-
'csv' => [10, 'csv'],
|
35
|
-
'archive' => [11, 'txt']
|
36
|
-
} # See [1]
|
37
|
-
|
38
21
|
class << self
|
39
22
|
extend Forwardable
|
40
23
|
|
@@ -109,30 +92,8 @@ module SequenceServer
|
|
109
92
|
fail RuntimeError.new(status, error)
|
110
93
|
end
|
111
94
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
def format(params)
|
116
|
-
validate_format_params params
|
117
|
-
|
118
|
-
rfile = create_file_path params['report']
|
119
|
-
ofmt = OUTFMT[params['format']]
|
120
|
-
type = params['type']
|
121
|
-
|
122
|
-
oname = "seqserv_#{type}_#{Time.now.strftime('%H%M')}.#{ofmt[1]}"
|
123
|
-
ofile = Tempfile.new oname
|
124
|
-
|
125
|
-
command = "blast_formatter -archive '#{rfile}' -out #{ofile.path}" \
|
126
|
-
" -outfmt '#{ofmt[0]} #{params['specifiers']}' 2> /dev/null"
|
127
|
-
|
128
|
-
logger.debug("Executing: #{command}")
|
129
|
-
system command
|
130
|
-
|
131
|
-
{
|
132
|
-
:filepath => ofile.path,
|
133
|
-
:filename => oname,
|
134
|
-
:type => ofmt[1]
|
135
|
-
}
|
95
|
+
Search << rfile
|
96
|
+
Report.new(File.basename(rfile.path), databases)
|
136
97
|
end
|
137
98
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
138
99
|
# rubocop:enable Metrics/MethodLength
|
@@ -141,16 +102,6 @@ module SequenceServer
|
|
141
102
|
params[:sequence].strip! unless params[:sequence].nil?
|
142
103
|
end
|
143
104
|
|
144
|
-
def validate_format_params(params)
|
145
|
-
return true if params.include?('report') &&
|
146
|
-
params.include?('format') &&
|
147
|
-
File.exist?(create_file_path params['report'])
|
148
|
-
fail ArgumentError, <<MSG
|
149
|
-
Incorrect request parameters. Please ensure that requested file name is
|
150
|
-
correct and the file type is either xml or tsv.
|
151
|
-
MSG
|
152
|
-
end
|
153
|
-
|
154
105
|
def validate_blast_params(params)
|
155
106
|
validate_blast_method params[:method]
|
156
107
|
validate_blast_sequences params[:sequence]
|
@@ -201,12 +152,6 @@ MSG
|
|
201
152
|
true
|
202
153
|
end
|
203
154
|
|
204
|
-
# Returns filename if path exists otherwise returns a path to tmp dir.
|
205
|
-
def create_file_path(filename)
|
206
|
-
return File.join(Dir.tmpdir, filename) unless File.exist? filename
|
207
|
-
filename
|
208
|
-
end
|
209
|
-
|
210
155
|
def allowed_chars
|
211
156
|
/\A[a-z0-9\-_\. ']*\Z/i
|
212
157
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module SequenceServer
|
2
|
+
# Define constanst used by BLAST module.
|
3
|
+
module BLAST
|
4
|
+
ERROR_LINE = /\(CArgException.*\)\s(.*)/
|
5
|
+
|
6
|
+
ALGORITHMS = %w(blastn blastp blastx tblastn tblastx)
|
7
|
+
|
8
|
+
OUTFMT_SPECIFIERS = %w(qseqid qgi qacc sseqid sallseqid sgi sallgi sacc
|
9
|
+
sallacc qstart qend sstart send qseq sseq evalue
|
10
|
+
bitscore score length length pident nident
|
11
|
+
mismatch positive gapopen gaps ppos frames
|
12
|
+
qframe hframe btop staxids sscinames scomnames
|
13
|
+
sblastnames sskingdoms stitle salltitles sstrand
|
14
|
+
qcovs qcovhsp)
|
15
|
+
OUTFMT = {
|
16
|
+
'pairwise' => [0, :txt],
|
17
|
+
'qa' => [1, :txt],
|
18
|
+
'qa_no_identity' => [2, :txt],
|
19
|
+
'fqa' => [3, :txt],
|
20
|
+
'fqa_no_identity' => [4, :txt],
|
21
|
+
'xml' => [5, :xml],
|
22
|
+
'std_tsv' => [7, :tsv],
|
23
|
+
'full_tsv' => [7, :tsv, OUTFMT_SPECIFIERS],
|
24
|
+
'asn_text' => [8, :asn],
|
25
|
+
'asn_binary' => [9, :asn],
|
26
|
+
'csv' => [10, :csv],
|
27
|
+
'archive' => [11, :txt]
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# References
|
33
|
+
# ----------
|
34
|
+
# [1]: http://www.ncbi.nlm.nih.gov/books/NBK1763/
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module SequenceServer
|
4
|
+
module BLAST
|
5
|
+
# Formats BLAST+ archive file format into other file formats.
|
6
|
+
class Formatter
|
7
|
+
class << self
|
8
|
+
alias_method :run, :new
|
9
|
+
end
|
10
|
+
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
def_delegators SequenceServer, :logger
|
14
|
+
|
15
|
+
def initialize(search_id, type)
|
16
|
+
@archive_file = get_archive_file search_id
|
17
|
+
@format, @mime, @specifiers = OUTFMT[type]
|
18
|
+
@type = type
|
19
|
+
|
20
|
+
validate && run
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :archive_file, :type
|
24
|
+
|
25
|
+
attr_reader :format, :mime, :specifiers
|
26
|
+
|
27
|
+
def file
|
28
|
+
@file ||= Tempfile.new filename
|
29
|
+
end
|
30
|
+
|
31
|
+
def filename
|
32
|
+
@filename ||=
|
33
|
+
"sequenceserver-#{type}_report.#{mime}"
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def run
|
39
|
+
command =
|
40
|
+
"blast_formatter -archive '#{archive_file}'" \
|
41
|
+
" -outfmt '#{format} #{specifiers}'" \
|
42
|
+
" -out '#{file.path}' 2> /dev/null"
|
43
|
+
logger.debug("Executing: #{command}")
|
44
|
+
system command
|
45
|
+
end
|
46
|
+
|
47
|
+
def validate
|
48
|
+
return true if archive_file && format &&
|
49
|
+
File.exist?(archive_file)
|
50
|
+
fail ArgumentError, <<MSG
|
51
|
+
Incorrect request parameters. Please ensure that requested file name is
|
52
|
+
correct and the file type is either xml or tsv.
|
53
|
+
MSG
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns filename if path exists otherwise returns a path to tmp dir.
|
57
|
+
def get_archive_file(file)
|
58
|
+
return unless file
|
59
|
+
return file.path if file.respond_to? :path
|
60
|
+
return file if File.exist? file
|
61
|
+
File.join Dir.tmpdir, file
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# References
|
68
|
+
# ----------
|
69
|
+
# [1]: http://www.ncbi.nlm.nih.gov/books/NBK1763/
|