sequenceserver 0.8.9 → 1.0.0.pre.1
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.txt → README.md} +2 -0
- data/bin/sequenceserver +255 -55
- data/config.ru +2 -4
- data/lib/sequenceserver.rb +293 -447
- data/lib/sequenceserver/blast.rb +464 -64
- data/lib/sequenceserver/database.rb +185 -19
- data/lib/sequenceserver/links.rb +114 -0
- data/lib/sequenceserver/logger.rb +27 -0
- data/lib/sequenceserver/sequence.rb +141 -0
- data/public/css/bootstrap.min.css +8 -413
- data/public/css/custom.css +363 -122
- data/public/css/font-awesome.min.css +4 -0
- data/public/fonts/FontAwesome.otf +0 -0
- data/public/fonts/fontawesome-webfont.eot +0 -0
- data/public/fonts/fontawesome-webfont.svg +565 -0
- data/public/fonts/fontawesome-webfont.ttf +0 -0
- data/public/fonts/fontawesome-webfont.woff +0 -0
- data/public/fonts/fontawesome-webfont.woff2 +0 -0
- data/public/js/bootstrap.min.js +11 -0
- data/public/js/d3.v3.min.js +5 -0
- data/public/js/html5shiv.min.js +4 -0
- data/public/js/jquery.scrollspy.js +74 -0
- data/public/js/jquery.t.js +353 -0
- data/public/js/sequence.js +2419 -0
- data/public/js/sequenceserver.blast.js +29 -30
- data/public/js/sequenceserver.js +544 -120
- data/public/js/underscore.min.js +6 -0
- data/public/js/webshims/polyfiller.js +1 -0
- data/public/js/webshims/shims/FlashCanvas/canvas2png.js +1 -0
- data/public/js/webshims/shims/FlashCanvas/flashcanvas.js +1 -0
- data/public/js/webshims/shims/FlashCanvas/flashcanvas.swf +0 -0
- data/public/js/webshims/shims/FlashCanvasPro/canvas2png.js +1 -0
- data/public/js/webshims/shims/FlashCanvasPro/flash10canvas.swf +0 -0
- data/public/js/webshims/shims/FlashCanvasPro/flash9canvas.swf +0 -0
- data/public/js/webshims/shims/FlashCanvasPro/flashcanvas.js +1 -0
- data/public/js/webshims/shims/canvas-blob.js +1 -0
- data/public/js/webshims/shims/color-picker.js +2 -0
- data/public/js/webshims/shims/combos/1.js +6 -0
- data/public/js/webshims/shims/combos/10.js +2 -0
- data/public/js/webshims/shims/combos/11.js +2 -0
- data/public/js/webshims/shims/combos/12.js +6 -0
- data/public/js/webshims/shims/combos/13.js +1 -0
- data/public/js/webshims/shims/combos/14.js +1 -0
- data/public/js/webshims/shims/combos/15.js +2 -0
- data/public/js/webshims/shims/combos/16.js +7 -0
- data/public/js/webshims/shims/combos/17.js +2 -0
- data/public/js/webshims/shims/combos/18.js +3 -0
- data/public/js/webshims/shims/combos/2.js +7 -0
- data/public/js/webshims/shims/combos/21.js +2 -0
- data/public/js/webshims/shims/combos/22.js +1 -0
- data/public/js/webshims/shims/combos/23.js +6 -0
- data/public/js/webshims/shims/combos/25.js +2 -0
- data/public/js/webshims/shims/combos/27.js +1 -0
- data/public/js/webshims/shims/combos/28.js +1 -0
- data/public/js/webshims/shims/combos/29.js +1 -0
- data/public/js/webshims/shims/combos/3.js +1 -0
- data/public/js/webshims/shims/combos/30.js +2 -0
- data/public/js/webshims/shims/combos/31.js +1 -0
- data/public/js/webshims/shims/combos/33.js +1 -0
- data/public/js/webshims/shims/combos/34.js +1 -0
- data/public/js/webshims/shims/combos/4.js +1 -0
- data/public/js/webshims/shims/combos/5.js +2 -0
- data/public/js/webshims/shims/combos/6.js +2 -0
- data/public/js/webshims/shims/combos/7.js +7 -0
- data/public/js/webshims/shims/combos/8.js +7 -0
- data/public/js/webshims/shims/combos/9.js +2 -0
- data/public/js/webshims/shims/combos/97.js +1 -0
- data/public/js/webshims/shims/combos/98.js +1 -0
- data/public/js/webshims/shims/combos/99.js +1 -0
- data/public/js/webshims/shims/details.js +1 -0
- data/public/js/webshims/shims/dom-extend.js +1 -0
- data/public/js/webshims/shims/es5.js +1 -0
- data/public/js/webshims/shims/es6.js +1 -0
- data/public/js/webshims/shims/excanvas.js +1 -0
- data/public/js/webshims/shims/filereader-xhr.js +1 -0
- data/public/js/webshims/shims/form-combat.js +1 -0
- data/public/js/webshims/shims/form-core.js +1 -0
- data/public/js/webshims/shims/form-datalist-lazy.js +1 -0
- data/public/js/webshims/shims/form-datalist.js +1 -0
- data/public/js/webshims/shims/form-fixrangechange.js +1 -0
- data/public/js/webshims/shims/form-inputmode.js +1 -0
- data/public/js/webshims/shims/form-message.js +1 -0
- data/public/js/webshims/shims/form-native-extend.js +1 -0
- data/public/js/webshims/shims/form-number-date-api.js +1 -0
- data/public/js/webshims/shims/form-number-date-ui.js +1 -0
- data/public/js/webshims/shims/form-shim-extend.js +1 -0
- data/public/js/webshims/shims/form-shim-extend2.js +1 -0
- data/public/js/webshims/shims/form-validation.js +1 -0
- data/public/js/webshims/shims/form-validators.js +1 -0
- data/public/js/webshims/shims/forms-picker.js +1 -0
- data/public/js/webshims/shims/geolocation.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-ar.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-ch-CN.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-cs.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-de.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-el.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-en.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-es.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-fa.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-fr.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-he.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-hi.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-hu.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-it.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-ja.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-lt.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-nl.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-pl.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-pt-BR.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-pt-PT.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-pt.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-ru.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-sv.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-zh-CN.js +1 -0
- data/public/js/webshims/shims/i18n/formcfg-zh-TW.js +1 -0
- data/public/js/webshims/shims/jme/alternate-media.js +1 -0
- data/public/js/webshims/shims/jme/base.js +1 -0
- data/public/js/webshims/shims/jme/controls.css +1 -0
- data/public/js/webshims/shims/jme/jme.eot +0 -0
- data/public/js/webshims/shims/jme/jme.svg +36 -0
- data/public/js/webshims/shims/jme/jme.ttf +0 -0
- data/public/js/webshims/shims/jme/jme.woff +0 -0
- data/public/js/webshims/shims/jme/mediacontrols-lazy.js +1 -0
- data/public/js/webshims/shims/jme/mediacontrols.js +1 -0
- data/public/js/webshims/shims/jme/playlist.js +1 -0
- data/public/js/webshims/shims/jpicker/images/AlphaBar.png +0 -0
- data/public/js/webshims/shims/jpicker/images/Bars.png +0 -0
- data/public/js/webshims/shims/jpicker/images/Maps.png +0 -0
- data/public/js/webshims/shims/jpicker/images/NoColor.png +0 -0
- data/public/js/webshims/shims/jpicker/images/bar-opacity.png +0 -0
- data/public/js/webshims/shims/jpicker/images/map-opacity.png +0 -0
- data/public/js/webshims/shims/jpicker/images/mappoint.gif +0 -0
- data/public/js/webshims/shims/jpicker/images/picker.gif +0 -0
- data/public/js/webshims/shims/jpicker/images/preview-opacity.png +0 -0
- data/public/js/webshims/shims/jpicker/images/rangearrows.gif +0 -0
- data/public/js/webshims/shims/jpicker/jpicker.css +1 -0
- data/public/js/webshims/shims/matchMedia.js +3 -0
- data/public/js/webshims/shims/mediacapture-picker.js +1 -0
- data/public/js/webshims/shims/mediacapture.js +1 -0
- data/public/js/webshims/shims/mediaelement-core.js +1 -0
- data/public/js/webshims/shims/mediaelement-debug.js +1 -0
- data/public/js/webshims/shims/mediaelement-jaris.js +1 -0
- data/public/js/webshims/shims/mediaelement-native-fix.js +1 -0
- data/public/js/webshims/shims/mediaelement-yt.js +1 -0
- data/public/js/webshims/shims/moxie/flash/Moxie.cdn.swf +0 -0
- data/public/js/webshims/shims/moxie/flash/Moxie.min.swf +0 -0
- data/public/js/webshims/shims/moxie/js/moxie-html4.js +3 -0
- data/public/js/webshims/shims/moxie/js/moxie-swf.js +2 -0
- data/public/js/webshims/shims/picture.js +1 -0
- data/public/js/webshims/shims/plugins/jquery.ui.position.js +11 -0
- data/public/js/webshims/shims/range-ui.js +1 -0
- data/public/js/webshims/shims/sizzle.js +11 -0
- data/public/js/webshims/shims/sticky.js +1 -0
- data/public/js/webshims/shims/styles/color-picker.png +0 -0
- data/public/js/webshims/shims/styles/forms-ext.css +1 -0
- data/public/js/webshims/shims/styles/forms-picker.css +1 -0
- data/public/js/webshims/shims/styles/progress.gif +0 -0
- data/public/js/webshims/shims/styles/progress.png +0 -0
- data/public/js/webshims/shims/styles/shim-ext.css +1 -0
- data/public/js/webshims/shims/styles/shim.css +1 -0
- data/public/js/webshims/shims/styles/transparent.png +0 -0
- data/public/js/webshims/shims/styles/widget.eot +0 -0
- data/public/js/webshims/shims/styles/widget.svg +12 -0
- data/public/js/webshims/shims/styles/widget.ttf +0 -0
- data/public/js/webshims/shims/styles/widget.woff +0 -0
- data/public/js/webshims/shims/swf/JarisFLVPlayer.swf +0 -0
- data/public/js/webshims/shims/swfmini-embed.js +1 -0
- data/public/js/webshims/shims/swfmini.js +6 -0
- data/public/js/webshims/shims/track-ui.js +1 -0
- data/public/js/webshims/shims/track.js +1 -0
- data/public/js/webshims/shims/url.js +1 -0
- data/public/js/webshims/shims/usermedia-core.js +1 -0
- data/public/js/webshims/shims/usermedia-shim.js +1 -0
- data/sequenceserver.gemspec +16 -13
- data/views/400.erb +28 -0
- data/views/500.erb +35 -19
- data/views/_options.erb +6 -15
- data/views/result.erb +218 -0
- data/views/search.erb +354 -151
- metadata +254 -62
- data/example.config.yml +0 -39
- data/lib/sequenceserver/customisation.rb +0 -60
- data/lib/sequenceserver/database_formatter.rb +0 -190
- data/lib/sequenceserver/helpers.rb +0 -136
- data/lib/sequenceserver/sequencehelpers.rb +0 -93
- data/lib/sequenceserver/sinatralikeloggerformatter.rb +0 -12
- data/lib/sequenceserver/version.rb +0 -9
- data/public/css/beige.css.css +0 -254
- data/public/css/bootstrap.dropdown.css +0 -29
- data/public/css/bootstrap.icons.css +0 -155
- data/public/css/bootstrap.modal.css +0 -28
- data/public/js/bootstrap.dropdown.js +0 -92
- data/public/js/bootstrap.modal.js +0 -7
- data/public/js/bootstrap.transition.js +0 -7
- data/public/js/jquery-scrollspy.js +0 -98
- data/public/js/jquery.activity.js +0 -10
- data/public/js/jquery.enablePlaceholder.min.js +0 -10
- data/public/js/store.min.js +0 -2
- data/public/sequence.html +0 -28
- data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta +0 -5486
- data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta.nhr +0 -0
- data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta.nin +0 -0
- data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta.nsq +0 -0
- data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta +0 -6449
- data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta.phr +0 -0
- data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta.pin +0 -0
- data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta.psq +0 -0
- data/tests/run +0 -26
- data/tests/test_sequencehelpers.rb +0 -77
- data/tests/test_sequenceserver_blast.rb +0 -60
- data/tests/test_ui.rb +0 -104
- data/tests/ui.specs.todo +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b4b6422d0e86b47690118cf32a0633609fd8e545
|
4
|
+
data.tar.gz: 01dda58ef4ab7bac5c889508361be848482cfc75
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c469b959e1fa91763766a7584b2b8ba6950c057d68319cb5ffc7e7dffe8b903bdff88037264ff9a6e71a6b2914a8c733e061b0c065c6934af6f41e633f01e889
|
7
|
+
data.tar.gz: 6cc6dcf46bbf6e4d93437e284102083de62fb30e35e18f0a6f63461dfbaa27f1fe9e7a019b2bcab48ca84d67b787598dc7fde277dcb9ffaad9c507f64e76b89c
|
data/{README.txt → README.md}
RENAMED
@@ -2,4 +2,6 @@ Thanks for downloading SequenceServer!
|
|
2
2
|
|
3
3
|
Documentation available at http://www.sequenceserver.com
|
4
4
|
|
5
|
+
[![build status](https://secure.travis-ci.org/yannickwurm/sequenceserver.png?branch=master)](https://travis-ci.org/yannickwurm/sequenceserver)
|
6
|
+
|
5
7
|
-- Yannick Wurm, Ben Woodcroft, Anurag Priyam
|
data/bin/sequenceserver
CHANGED
@@ -1,82 +1,282 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
require 'readline'
|
3
|
+
require 'slop'
|
2
4
|
|
3
|
-
|
4
|
-
require 'bundler/setup'
|
5
|
-
require 'optparse'
|
6
|
-
require 'sequenceserver'
|
5
|
+
ENV['RACK_ENV'] ||= 'production'
|
7
6
|
|
8
|
-
#
|
9
|
-
|
10
|
-
begin
|
11
|
-
# parse command line till first non-option, removing parsed options from ARGV
|
12
|
-
OptionParser.new do |opts|
|
13
|
-
opts.banner =<<BANNER
|
7
|
+
# display name for tools like `ps`
|
8
|
+
$PROGRAM_NAME = 'sequenceserver'
|
14
9
|
|
10
|
+
begin
|
11
|
+
Slop.parse!(:strict => true, :help => true) do
|
12
|
+
banner <<BANNER
|
15
13
|
SUMMARY
|
16
14
|
|
17
|
-
|
15
|
+
custom, local, BLAST server
|
18
16
|
|
19
17
|
USAGE
|
20
18
|
|
21
|
-
|
19
|
+
sequenceserver [options]
|
22
20
|
|
23
|
-
|
21
|
+
Example:
|
24
22
|
|
25
|
-
|
26
|
-
|
23
|
+
# launch SequenceServer with the given config file
|
24
|
+
$ sequenceserver -c ~/.sequenceserver.ants.conf
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
# use the bundled database formatter utility to prepare databases for use
|
27
|
+
# with SequenceServer
|
28
|
+
$ sequenceserver -m
|
31
29
|
|
32
30
|
DESCRIPTION
|
33
31
|
|
34
|
-
|
35
|
-
|
32
|
+
SequenceServer lets you rapidly set up a BLAST+ server with an intuitive user
|
33
|
+
interface for use locally or over the web.
|
36
34
|
|
37
|
-
|
35
|
+
If BLAST+ is not installed on your system, SequenceServer will offer to install
|
36
|
+
BLAST+ for you.
|
38
37
|
|
39
|
-
|
40
|
-
prepare BLAST databases for use with SequenceServer
|
38
|
+
You should only ever have to point it to a directory of FASTA files.
|
41
39
|
|
42
|
-
|
40
|
+
In a given directory, SequenceServer is able to tell FASTA files that are yet
|
41
|
+
to be formatted for use with BLAST+ and format them, and FASTA files that are
|
42
|
+
already formatted for use with BLAST+, heuristically skipping all other files
|
43
|
+
in the directory. Directories are scanned recursively. Type of sequences in a
|
44
|
+
FASTA file is detected automagically. `parse_seqids` option of `makeblastdb` is
|
45
|
+
used to create BLAST+ databases.
|
46
|
+
BANNER
|
43
47
|
|
44
|
-
|
48
|
+
on 'c', 'config_file=',
|
49
|
+
'Use the given configuration file',
|
50
|
+
:argument => true
|
45
51
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
end
|
52
|
+
on 'b', 'bin=',
|
53
|
+
'Load BLAST+ binaries from this directory',
|
54
|
+
:argument => true
|
50
55
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
56
|
+
on 'd', 'database_dir=',
|
57
|
+
'Read FASTA and BLAST database from this directory',
|
58
|
+
:argument => true
|
59
|
+
|
60
|
+
on 'n', 'num_threads=',
|
61
|
+
'Number of threads to use to run a BLAST search',
|
62
|
+
:argument => true
|
63
|
+
#:as => Integer
|
64
|
+
|
65
|
+
on 'r', 'require=',
|
66
|
+
'Load extension from this file',
|
67
|
+
:argument => true
|
68
|
+
|
69
|
+
on 'h', 'host=',
|
70
|
+
'Host to run SequenceServer on',
|
71
|
+
:argument => true
|
72
|
+
|
73
|
+
on 'p', 'port=',
|
74
|
+
'Port to run SequenceServer on',
|
75
|
+
:argument => true
|
76
|
+
|
77
|
+
on 's', 'set',
|
78
|
+
'Set configuration value in default or given config file'
|
79
|
+
|
80
|
+
on 'm', 'make-blast-databases',
|
81
|
+
'Create BLAST databases'
|
82
|
+
|
83
|
+
on 'l', 'list_databases',
|
84
|
+
'List BLAST databases'
|
85
|
+
|
86
|
+
on 'u', 'list-unformatted-fastas',
|
87
|
+
'List unformatted FASTA files'
|
88
|
+
|
89
|
+
on 'i', 'interactive',
|
90
|
+
'Run SequenceServer in interactive mode'
|
91
|
+
|
92
|
+
on 'D', 'devel',
|
93
|
+
'Start SequenceServer in development mode'
|
94
|
+
|
95
|
+
on '-v', '--version',
|
96
|
+
'Print version number of SequenceServer that will be loaded'
|
97
|
+
|
98
|
+
on '-h', '--help',
|
99
|
+
'Display this help message'
|
100
|
+
|
101
|
+
run do
|
102
|
+
if version?
|
103
|
+
gemspec_file = File.join(File.dirname(__FILE__), '..', 'sequenceserver.gemspec')
|
104
|
+
gemspec = eval File.read gemspec_file
|
105
|
+
puts gemspec.version
|
106
|
+
exit
|
107
|
+
end
|
108
|
+
|
109
|
+
ENV['RACK_ENV'] = 'development' if devel?
|
110
|
+
# Save the state of user terminal
|
111
|
+
stty_save = `stty -g`.chomp
|
112
|
+
# Exit gracefully on SIGINT
|
113
|
+
trap('INT') do
|
114
|
+
puts ''
|
115
|
+
puts 'Aborted.'
|
116
|
+
system('stty', stty_save)
|
117
|
+
exit
|
118
|
+
end
|
119
|
+
|
120
|
+
require 'sequenceserver'
|
121
|
+
begin
|
122
|
+
opts = to_h.delete_if{|k, v| v.nil?}
|
123
|
+
opts.delete :set
|
124
|
+
SequenceServer.init opts
|
125
|
+
rescue SystemExit => e
|
126
|
+
# The aim of following error recovery scenarios is to guide user to a
|
127
|
+
# working SequenceServer installation. We expect to land following
|
128
|
+
# error scenarios either when creating a new SequenceServer (first
|
129
|
+
# time or later), or updating config values using -s CLI option.
|
130
|
+
|
131
|
+
# Try error recovery now.
|
132
|
+
case e.status
|
133
|
+
when SequenceServer::EXIT_CONFIG_FILE_NOT_FOUND
|
134
|
+
# Create an empty configuration file to make SequenceServer happy.
|
135
|
+
File.open(SequenceServer.config_file, 'w') {}
|
136
|
+
puts " Created."
|
137
|
+
redo
|
138
|
+
when SequenceServer::EXIT_BLAST_NOT_INSTALLED,
|
139
|
+
SequenceServer::EXIT_BLAST_NOT_COMPATIBLE
|
140
|
+
|
141
|
+
# Set a flag so that if we recovered from error resulting config can be
|
142
|
+
# saved. Config will be saved unless invoked with -b option.
|
143
|
+
fetch_option(:set).value = !bin?
|
144
|
+
|
145
|
+
# Ask user if she already has BLAST+ downloaded or offer to download
|
146
|
+
# BLAST+ for her.
|
147
|
+
puts
|
148
|
+
print 'Do you have BLAST+ downloaded already? [y/n] (Default: n): '
|
149
|
+
response = STDIN.gets.to_s.strip
|
150
|
+
if response.match(/^[y]$/i)
|
151
|
+
response = Readline.readline('Where? ').to_s.strip
|
152
|
+
unless File.basename(response) == 'bin'
|
153
|
+
fetch_option(:bin).value = File.join(response, 'bin')
|
154
|
+
end
|
155
|
+
redo
|
156
|
+
end
|
157
|
+
|
158
|
+
puts
|
159
|
+
print "Install BLAST+? [y/n] (Default: y): "
|
160
|
+
response = STDIN.gets.to_s.strip
|
161
|
+
unless response.match(/^[n]$/i)
|
162
|
+
puts
|
163
|
+
puts "*** Installing BLAST+."
|
164
|
+
puts "RUBY_PLATFORM #{RUBY_PLATFORM}"
|
165
|
+
|
166
|
+
version = SequenceServer::MINIMUM_BLAST_VERSION
|
167
|
+
url = case RUBY_PLATFORM
|
168
|
+
when /i686-linux/ # 32 bit Linux
|
169
|
+
"ftp://ftp.ncbi.nlm.nih.gov/blast/executables/blast+/#{version.chop}/ncbi-blast-#{version}-ia32-linux.tar.gz"
|
170
|
+
when /x86_64-linux/ # 64 bit Linux
|
171
|
+
"ftp://ftp.ncbi.nlm.nih.gov/blast/executables/blast+/#{version.chop}/ncbi-blast-#{version}-x64-linux.tar.gz"
|
172
|
+
when /darwin/ # Mac
|
173
|
+
"ftp://ftp.ncbi.nlm.nih.gov/blast/executables/blast+/#{version.chop}/ncbi-blast-#{version}-universal-macosx.tar.gz"
|
174
|
+
else
|
175
|
+
puts <<ERR
|
176
|
+
------------------------------------------------------------------------
|
177
|
+
FAILED!! to install NCBI BLAST+.
|
178
|
+
|
179
|
+
We currently support Linux and Mac only (both 32 and 64 bit). If you
|
180
|
+
believe you are running a supported platform, please open a support
|
181
|
+
ticket titled "#{RUBY_PLATFORM}" at:
|
182
|
+
|
183
|
+
https://github.com/yannickwurm/sequenceserver/issues
|
184
|
+
------------------------------------------------------------------------
|
185
|
+
|
186
|
+
ERR
|
187
|
+
end
|
188
|
+
|
189
|
+
archive = File.join('/tmp', File.basename(url))
|
190
|
+
system "wget -c #{url} -O #{archive} && mkdir -p ~/.sequenceserver && tar xvf #{archive} -C ~/.sequenceserver"
|
191
|
+
unless $?.success?
|
192
|
+
puts "*** Failed to install BLAST+."
|
193
|
+
puts " You may need to download BLAST+ from - "
|
194
|
+
puts " http://www.ncbi.nlm.nih.gov/blast/Blast.cgi?CMD=Web&PAGE_TYPE=BlastDocs&DOC_TYPE=Download"
|
195
|
+
puts " Please ensure that you download BLAST+ version
|
196
|
+
#{SequenceServer::MINIMUM_BLAST_VERSION} or higher."
|
197
|
+
exit SequenceServer::EXIT_BLAST_INSTALLATION_FAILED
|
198
|
+
end
|
199
|
+
fetch_option(:bin).value = "~/.sequenceserver/ncbi-blast-#{version}/bin/"
|
200
|
+
redo
|
201
|
+
else
|
202
|
+
exit e.status unless set?
|
203
|
+
end
|
204
|
+
when SequenceServer::EXIT_NO_SEQUENCE_DIR
|
205
|
+
# Ask user for the directory containing sequences or BLAST+
|
206
|
+
# databases.
|
207
|
+
|
208
|
+
# Set a flag so that if we recovered from error resulting config can be
|
209
|
+
# saved. Config will be saved unless invoked with -d option.
|
210
|
+
fetch_option(:set).value = !database_dir?
|
211
|
+
|
212
|
+
puts
|
213
|
+
prompt = 'Where are the sequences you would like to search?' \
|
214
|
+
' (Default: current dir) '
|
215
|
+
response = Readline.readline(prompt).to_s.strip
|
216
|
+
fetch_option(:database_dir).value = response
|
217
|
+
redo
|
218
|
+
when SequenceServer::EXIT_NO_BLAST_DATABASE
|
219
|
+
unless list_databases? or list_unformatted_fastas? or make_blast_databases?
|
220
|
+
# Offer user to format the FASTA files.
|
221
|
+
puts
|
222
|
+
print 'Create databases? [y/n] (Default: y): '
|
223
|
+
response = STDIN.gets.to_s.strip
|
224
|
+
unless response.match(/^[n]$/i)
|
225
|
+
puts
|
226
|
+
puts "*** Creating databases."
|
227
|
+
SequenceServer::Database.make_blast_databases
|
228
|
+
redo
|
229
|
+
else
|
230
|
+
exit e.status unless set?
|
231
|
+
end
|
232
|
+
end
|
233
|
+
else
|
234
|
+
exit e.status
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
if interactive?
|
239
|
+
SequenceServer.irb
|
240
|
+
exit
|
241
|
+
end
|
242
|
+
|
243
|
+
if list_databases?
|
244
|
+
puts SequenceServer::Database.all
|
245
|
+
exit
|
246
|
+
end
|
247
|
+
|
248
|
+
if list_unformatted_fastas? or make_blast_databases?
|
249
|
+
unformatted_fastas = SequenceServer::Database.unformatted_fastas
|
250
|
+
if unformatted_fastas.empty?
|
251
|
+
puts "All FASTA files in #{SequenceServer[:database_dir]} are formatted."
|
252
|
+
exit
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
if list_unformatted_fastas?
|
257
|
+
puts unformatted_fastas
|
258
|
+
exit
|
259
|
+
end
|
260
|
+
|
261
|
+
if make_blast_databases?
|
262
|
+
SequenceServer::Database.make_blast_databases
|
263
|
+
exit
|
264
|
+
end
|
265
|
+
|
266
|
+
if set?
|
267
|
+
SequenceServer.send :write_config_file
|
268
|
+
exit
|
269
|
+
end
|
270
|
+
|
271
|
+
if fetch_option(:set).value
|
272
|
+
SequenceServer.send :write_config_file
|
273
|
+
end
|
274
|
+
|
275
|
+
SequenceServer.run
|
71
276
|
end
|
72
277
|
end
|
73
|
-
rescue
|
278
|
+
rescue Slop::Error => e
|
74
279
|
puts e
|
75
280
|
puts "Run '#{$0} -h' for help with command line options."
|
76
281
|
exit
|
77
282
|
end
|
78
|
-
|
79
|
-
# display name for tools like `ps`
|
80
|
-
$PROGRAM_NAME = 'sequenceserver'
|
81
|
-
|
82
|
-
SequenceServer::App.run!
|
data/config.ru
CHANGED
data/lib/sequenceserver.rb
CHANGED
@@ -1,527 +1,373 @@
|
|
1
|
-
# sequenceserver.rb
|
2
|
-
|
3
|
-
require 'sinatra/base'
|
4
1
|
require 'yaml'
|
5
|
-
require 'logger'
|
6
2
|
require 'fileutils'
|
7
|
-
require '
|
3
|
+
require 'sinatra/base'
|
4
|
+
require 'thin'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
require 'sequenceserver/logger'
|
8
|
+
require 'sequenceserver/sequence'
|
9
|
+
require 'sequenceserver/database'
|
8
10
|
require 'sequenceserver/blast'
|
9
|
-
require 'sequenceserver/sequencehelpers'
|
10
|
-
require 'sequenceserver/sinatralikeloggerformatter'
|
11
|
-
require 'sequenceserver/customisation'
|
12
|
-
require 'sequenceserver/version'
|
13
11
|
|
14
|
-
# Helper module - initialize the blast server.
|
15
12
|
module SequenceServer
|
16
|
-
class App < Sinatra::Base
|
17
|
-
include Helpers
|
18
|
-
include SequenceHelpers
|
19
|
-
include SequenceServer::Customisation
|
20
|
-
|
21
|
-
# Basic configuration settings for app.
|
22
|
-
configure do
|
23
|
-
# enable some builtin goodies
|
24
|
-
enable :session, :logging
|
25
13
|
|
26
|
-
|
27
|
-
|
14
|
+
# Use a fixed minimum version of BLAST+
|
15
|
+
MINIMUM_BLAST_VERSION = '2.2.30+'
|
28
16
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
17
|
+
# Use the following exit codes, or 1.
|
18
|
+
EXIT_BLAST_NOT_INSTALLED = 2
|
19
|
+
EXIT_BLAST_NOT_COMPATIBLE = 3
|
20
|
+
EXIT_NO_BLAST_DATABASE = 4
|
21
|
+
EXIT_BLAST_INSTALLATION_FAILED = 5
|
22
|
+
EXIT_CONFIG_FILE_NOT_FOUND = 6
|
23
|
+
EXIT_NO_SEQUENCE_DIR = 7
|
34
24
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
# test suite.
|
40
|
-
set :test_database, File.join(root, 'tests', 'database')
|
25
|
+
class << self
|
26
|
+
def environment
|
27
|
+
ENV['RACK_ENV']
|
28
|
+
end
|
41
29
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
# copy it and make necessary changes to get started.
|
46
|
-
set :example_config_file, File.join(root, 'example.config.yml')
|
30
|
+
def verbose?
|
31
|
+
@verbose ||= (environment == 'development')
|
32
|
+
end
|
47
33
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
set :config_file, Proc.new{ File.expand_path('~/.sequenceserver.conf') }
|
34
|
+
def root
|
35
|
+
File.dirname(File.dirname(__FILE__))
|
36
|
+
end
|
52
37
|
|
53
|
-
|
54
|
-
|
38
|
+
def logger
|
39
|
+
@logger ||= Logger.new(STDERR, verbose?)
|
55
40
|
end
|
56
41
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
42
|
+
def init(config = {})
|
43
|
+
@config_file = config.delete(:config_file) || '~/.sequenceserver.conf'
|
44
|
+
@config_file = File.expand_path(config_file)
|
45
|
+
assert_file_present('config file', config_file, EXIT_CONFIG_FILE_NOT_FOUND)
|
46
|
+
|
47
|
+
@config = {
|
48
|
+
:num_threads => 1,
|
49
|
+
:port => 4567,
|
50
|
+
:host => 'localhost'
|
51
|
+
}.update(parse_config_file.merge(config))
|
52
|
+
|
53
|
+
if @config[:bin]
|
54
|
+
@config[:bin] = File.expand_path @config[:bin]
|
55
|
+
assert_dir_present 'bin dir', @config[:bin]
|
56
|
+
export_bin_dir
|
57
|
+
end
|
66
58
|
|
67
|
-
|
68
|
-
#
|
69
|
-
# A default of 'nil' is indicative of blast binaries being present in
|
70
|
-
# system PATH.
|
71
|
-
set :bin, Proc.new{ File.expand_path(config['bin']) rescue nil }
|
59
|
+
assert_blast_installed_and_compatible
|
72
60
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
# directory of the running app.
|
77
|
-
set :database, Proc.new{ File.expand_path(config['database']) rescue test_database }
|
61
|
+
assert_dir_present 'database dir', @config[:database_dir], EXIT_NO_SEQUENCE_DIR
|
62
|
+
@config[:database_dir] = File.expand_path(@config[:database_dir])
|
63
|
+
assert_blast_databases_present_in_database_dir
|
78
64
|
|
79
|
-
|
80
|
-
set :port, Proc.new{ (config['port'] or 4567).to_i }
|
65
|
+
Database.scan_databases_dir
|
81
66
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
# as a higher value may cause BLAST+ to crash if it was not compiled with
|
86
|
-
# threading support.
|
87
|
-
set :num_threads, Proc.new{ (config['num_threads'] or 1).to_i }
|
88
|
-
end
|
67
|
+
@config[:num_threads] = Integer(@config[:num_threads])
|
68
|
+
assert_num_threads_valid @config[:num_threads]
|
69
|
+
logger.debug("Will use #{@config[:num_threads]} threads to run BLAST.")
|
89
70
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
71
|
+
if @config[:require]
|
72
|
+
@config[:require] = File.expand_path @config[:require]
|
73
|
+
assert_file_present 'extension file', @config[:require]
|
74
|
+
require @config[:require]
|
75
|
+
end
|
76
|
+
|
77
|
+
# We don't validate port and host settings. If SequenceServer is run
|
78
|
+
# self-hosted, bind will fail on incorrect values. If SequenceServer
|
79
|
+
# is run via Apache+Passenger, we don't need to worry.
|
96
80
|
|
97
|
-
|
98
|
-
set :databases, {}
|
81
|
+
self
|
99
82
|
end
|
100
83
|
|
101
|
-
|
102
|
-
|
84
|
+
attr_reader :config_file, :config
|
85
|
+
|
86
|
+
def [](key)
|
87
|
+
config[key]
|
103
88
|
end
|
104
89
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
90
|
+
# Run SequenceServer as a self-hosted server using Thin webserver.
|
91
|
+
def run
|
92
|
+
url = "http://#{config[:host]}:#{config[:port]}"
|
93
|
+
server = Thin::Server.new(config[:host], config[:port], :signals => false) do
|
94
|
+
use Rack::CommonLogger
|
95
|
+
run SequenceServer
|
109
96
|
end
|
110
|
-
|
111
|
-
|
97
|
+
server.silent = true
|
98
|
+
server.backend.start do
|
99
|
+
puts "** SequenceServer is ready."
|
100
|
+
puts " Go to #{url} in your browser and start BLASTing!"
|
101
|
+
puts " Press CTRL+C to quit."
|
102
|
+
[:INT, :TERM].each do |sig|
|
103
|
+
trap sig do
|
104
|
+
server.stop!
|
105
|
+
puts
|
106
|
+
puts "** Thank you for using SequenceServer :)."
|
107
|
+
puts " Please cite: "
|
108
|
+
puts " Priyam, Woodcroft, Rai & Wurm,"
|
109
|
+
puts " SequenceServer (in prep)."
|
110
|
+
end
|
111
|
+
end
|
112
112
|
end
|
113
|
+
rescue
|
114
|
+
puts "** Oops! There was an error."
|
115
|
+
puts " Is SequenceServer already accessible at #{url}?"
|
116
|
+
puts " Try running SequenceServer on another port, like so: sequenceserver -p 4570."
|
113
117
|
end
|
114
118
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
119
|
+
# Rack-interface.
|
120
|
+
#
|
121
|
+
# Inject our logger in the env and dispatch request to our
|
122
|
+
# controller.
|
123
|
+
def call(env)
|
124
|
+
env['rack.logger'] = logger
|
125
|
+
App.call(env)
|
126
|
+
end
|
122
127
|
|
123
|
-
|
124
|
-
|
125
|
-
|
128
|
+
# Run SequenceServer interactively.
|
129
|
+
def irb
|
130
|
+
ARGV.clear
|
131
|
+
require 'irb'
|
132
|
+
IRB.setup nil
|
133
|
+
IRB.conf[:MAIN_CONTEXT] = IRB::Irb.new.context
|
134
|
+
require 'irb/ext/multi-irb'
|
135
|
+
IRB.irb nil, self
|
136
|
+
end
|
126
137
|
|
127
|
-
|
128
|
-
handler = detect_rack_handler
|
129
|
-
handler_name = handler.name.gsub(/.*::/, '')
|
138
|
+
private
|
130
139
|
|
131
|
-
|
132
|
-
|
140
|
+
def parse_config_file
|
141
|
+
logger.debug("Reading configuration file: #{config_file}.")
|
142
|
+
config = YAML.load_file(config_file) || {}
|
133
143
|
|
134
|
-
|
135
|
-
|
136
|
-
puts "== To install Thin: [sudo] gem install thin"
|
137
|
-
end
|
144
|
+
# Symbolize hash keys
|
145
|
+
config = config.inject({}){|c, e| c[e.first.to_sym] = e.last; c}
|
138
146
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
147
|
+
# The newer config file version replaces the older database key with
|
148
|
+
# database_dir. If an older version is found, we auto-migrate it to
|
149
|
+
# newer one.
|
150
|
+
config[:database_dir] ||= config.delete(:database)
|
151
|
+
config
|
152
|
+
rescue ArgumentError => error
|
153
|
+
puts "*** Error in config file: #{error}."
|
154
|
+
puts " YAML is white space sensitive. Is your config file properly indented?"
|
155
|
+
exit 1
|
156
|
+
end
|
145
157
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
rescue Errno::EADDRINUSE, RuntimeError => e
|
150
|
-
puts "\n== Failed to start SequenceServer."
|
151
|
-
puts "== Is SequenceServer already running at: #{url}"
|
158
|
+
def write_config_file
|
159
|
+
File.open(SequenceServer.config_file, 'w') do |f|
|
160
|
+
f.puts(config.delete_if{|k, v| v.nil?}.to_yaml)
|
152
161
|
end
|
162
|
+
end
|
153
163
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
"\n== Sequenceserver: BLAST searching made easy." unless handler_name =~/cgi/i
|
164
|
+
# Export NCBI BLAST+ bin dir to PATH environment variable.
|
165
|
+
def export_bin_dir
|
166
|
+
bin_dir = config[:bin]
|
167
|
+
if bin_dir
|
168
|
+
unless ENV['PATH'].split(':').include? bin_dir
|
169
|
+
ENV['PATH'] = "#{bin_dir}:#{ENV['PATH']}"
|
170
|
+
end
|
162
171
|
end
|
172
|
+
end
|
163
173
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
# empty config file
|
172
|
-
unless config
|
173
|
-
log.warn("Empty configuration file: #{config_file} - will assume default settings")
|
174
|
-
self.config = {}
|
175
|
-
end
|
174
|
+
def assert_file_present desc, file, exit_code = 1
|
175
|
+
unless file and File.exists? File.expand_path file
|
176
|
+
puts "*** Couldn't find #{desc}: #{file}."
|
177
|
+
exit exit_code
|
178
|
+
end
|
179
|
+
end
|
176
180
|
|
177
|
-
|
178
|
-
self.binaries = scan_blast_executables(bin).freeze
|
181
|
+
alias assert_dir_present assert_file_present
|
179
182
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
183
|
+
def assert_blast_installed_and_compatible
|
184
|
+
unless command? 'blastdbcmd'
|
185
|
+
puts "*** Could not find BLAST+ binaries."
|
186
|
+
exit EXIT_BLAST_NOT_INSTALLED
|
187
|
+
end
|
188
|
+
version = %x|blastdbcmd -version|.split[1]
|
189
|
+
unless version >= MINIMUM_BLAST_VERSION
|
190
|
+
puts "*** Your BLAST+ version #{version} is outdated."
|
191
|
+
puts " SequenceServer needs NCBI BLAST+ version #{MINIMUM_BLAST_VERSION} or higher."
|
192
|
+
exit EXIT_BLAST_NOT_COMPATIBLE
|
193
|
+
end
|
194
|
+
end
|
187
195
|
|
188
|
-
|
189
|
-
|
190
|
-
|
196
|
+
def assert_blast_databases_present_in_database_dir
|
197
|
+
database_dir = config[:database_dir]
|
198
|
+
out = %x|blastdbcmd -recursive -list #{database_dir}|
|
199
|
+
if out.empty?
|
200
|
+
puts "*** Could not find BLAST databases in '#{database_dir}'."
|
201
|
+
exit EXIT_NO_BLAST_DATABASE
|
202
|
+
elsif out.match(/BLAST Database error/) or not $?.success?
|
203
|
+
puts "*** Error obtaining BLAST databases."
|
204
|
+
puts " Tried: #{find_dbs_command}"
|
205
|
+
puts " Error:"
|
206
|
+
out.strip.split("\n").each do |l|
|
207
|
+
puts " #{l}"
|
191
208
|
end
|
192
|
-
|
193
|
-
|
194
|
-
exit
|
195
|
-
rescue ArgumentError => error
|
196
|
-
log.fatal("Error in config.yml: #{error}")
|
197
|
-
puts "YAML is white space sensitive. Is your config.yml properly indented?"
|
198
|
-
exit
|
199
|
-
rescue Errno::ENOENT # config file not found
|
200
|
-
log.info('Configuration file not found')
|
201
|
-
FileUtils.cp(example_config_file, config_file)
|
202
|
-
log.info("Generated a dummy configuration file: #{config_file}")
|
203
|
-
puts "\nPlease edit #{config_file} to indicate the location of your BLAST databases and run SequenceServer again."
|
204
|
-
exit
|
209
|
+
puts " Please could you report this to 'https://groups.google.com/forum/#!forum/sequenceserver'?"
|
210
|
+
exit EXIT_BLAST_DATABASE_ERROR
|
205
211
|
end
|
212
|
+
end
|
206
213
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
214
|
+
def assert_num_threads_valid num_threads
|
215
|
+
unless num_threads > 0
|
216
|
+
puts "*** Can't use #{num_threads} number of threads."
|
217
|
+
puts " Number of threads should be greater than or equal to 1."
|
218
|
+
exit 1
|
219
|
+
end
|
220
|
+
if num_threads > 256
|
221
|
+
logger.warn "*** Number of threads set at #{num_threads} is unusually high."
|
215
222
|
end
|
223
|
+
rescue
|
224
|
+
puts "*** Number of threads should be a number."
|
225
|
+
exit 1
|
216
226
|
end
|
217
227
|
|
218
|
-
|
219
|
-
|
228
|
+
|
229
|
+
# Return `true` if the given command exists and is executable.
|
230
|
+
def command?(command)
|
231
|
+
system("which #{command} > /dev/null 2>&1")
|
220
232
|
end
|
221
233
|
|
222
|
-
|
223
|
-
pass if params.empty?
|
234
|
+
end
|
224
235
|
|
225
|
-
|
226
|
-
|
227
|
-
# If a required parameter is missing, SequnceServer returns 'Bad Request
|
228
|
-
# (400)' error.
|
229
|
-
#
|
230
|
-
# See Twitter's [Error Codes & Responses][1] page for reference.
|
231
|
-
#
|
232
|
-
# [1]: https://dev.twitter.com/docs/error-codes-responses
|
236
|
+
# Controller.
|
237
|
+
class App < Sinatra::Base
|
233
238
|
|
234
|
-
|
235
|
-
|
236
|
-
|
239
|
+
# See
|
240
|
+
# http://www.sinatrarb.com/configuration.html
|
241
|
+
configure do
|
242
|
+
# We don't need Rack::MethodOverride. Let's avoid the overhead.
|
243
|
+
disable :method_override
|
244
|
+
|
245
|
+
# Ensure exceptions never leak out of the app. Exceptions raised within
|
246
|
+
# the app must be handled by the app. We do this by attaching error
|
247
|
+
# blocks to exceptions we know how to handle and attaching to Exception
|
248
|
+
# as fallback.
|
249
|
+
disable :show_exceptions, :raise_errors
|
250
|
+
|
251
|
+
# Make it a policy to dump to 'rack.errors' any exception raised by the
|
252
|
+
# app so that error handlers don't have to do it themselves. But for it
|
253
|
+
# to always work, Exceptions defined by us should not respond to `code`
|
254
|
+
# or http_status` methods. Error blocks errors must explicitly set http
|
255
|
+
# status, if needed, by calling `status` method.
|
256
|
+
# method.
|
257
|
+
enable :dump_errors
|
258
|
+
|
259
|
+
# We don't want Sinatra do setup any loggers for us. We will use our own.
|
260
|
+
set :logging, nil
|
261
|
+
|
262
|
+
# Public, and views directory will be found here.
|
263
|
+
set :root, lambda { SequenceServer.root }
|
264
|
+
end
|
237
265
|
|
238
|
-
|
239
|
-
|
240
|
-
|
266
|
+
# See
|
267
|
+
# http://www.sinatrarb.com/intro.html#Mime%20Types
|
268
|
+
configure do
|
269
|
+
mime_type :fasta, 'text/fasta'
|
270
|
+
end
|
241
271
|
|
242
|
-
|
243
|
-
|
272
|
+
helpers do
|
273
|
+
# Render an anchor element from the given Hash.
|
274
|
+
#
|
275
|
+
# See links.rb for example of a Hash object that will be rendered.
|
276
|
+
def a(link)
|
277
|
+
return unless link[:title] and link[:url]
|
278
|
+
_a = ["<a"]
|
279
|
+
_a << "href=#{link[:url]}"
|
280
|
+
_a << "class=\"#{link[:class]}\"" if link[:class]
|
281
|
+
_a << "target=\"_blank\"" if absolute? link[:url]
|
282
|
+
_a << '>'
|
283
|
+
_a << "<i class=\"fa #{link[:icon]}\"></i>" if link[:icon]
|
284
|
+
_a << link[:title]
|
285
|
+
_a << "</a>"
|
286
|
+
_a.join("\n")
|
244
287
|
end
|
245
288
|
|
246
|
-
#
|
247
|
-
|
248
|
-
|
249
|
-
blast_methods = %w|blastn blastp blastx tblastn tblastx|
|
250
|
-
unless blast_methods.include?(params[:method])
|
251
|
-
halt 400, "Unknown BLAST method: #{params[:method]}."
|
289
|
+
# Is the given URI absolute? (or relative?)
|
290
|
+
def absolute?(uri)
|
291
|
+
URI.parse(uri).absolute?
|
252
292
|
end
|
253
293
|
|
254
|
-
#
|
255
|
-
|
256
|
-
|
257
|
-
rescue ArgumentError => error
|
258
|
-
halt 400, "Advanced parameters invalid: #{error}"
|
294
|
+
# Formats score (a float) to two decimal places.
|
295
|
+
def prettify_score(score)
|
296
|
+
'%.2f' % score
|
259
297
|
end
|
260
298
|
|
261
|
-
#
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
post '/' do
|
269
|
-
method = params['method']
|
270
|
-
databases = params[:databases]
|
271
|
-
sequence = params[:sequence]
|
272
|
-
advanced_opts = params['advanced']
|
273
|
-
|
274
|
-
# evaluate empty sequence as nil, otherwise as fasta
|
275
|
-
sequence = sequence.empty? ? nil : to_fasta(sequence)
|
276
|
-
|
277
|
-
# blastn implies blastn, not megablast; but let's not interfere if a user
|
278
|
-
# specifies `task` herself
|
279
|
-
if method == 'blastn' and not advanced_opts =~ /task/
|
280
|
-
advanced_opts << ' -task blastn '
|
299
|
+
# Formats evalue (a float expressed in scientific notation) to "a x b^c".
|
300
|
+
def prettify_evalue(evalue)
|
301
|
+
evalue.to_s.sub(/(\d*\.\d*)e?([+-]\d*)?/) do
|
302
|
+
s = '%.3f' % Regexp.last_match[1]
|
303
|
+
s << " × 10<sup>#{Regexp.last_match[2]}</sup>" if Regexp.last_match[2]
|
304
|
+
s
|
305
|
+
end
|
281
306
|
end
|
307
|
+
end
|
282
308
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
# run blast and log
|
290
|
-
blast = Blast.new(method, sequence, databases.join(' '), advanced_opts)
|
291
|
-
blast.run!
|
292
|
-
settings.log.info('Ran: ' + blast.command)
|
309
|
+
# For any request that hits the app in development mode, log incoming
|
310
|
+
# params.
|
311
|
+
before do
|
312
|
+
logger.debug params
|
313
|
+
end
|
293
314
|
|
294
|
-
|
295
|
-
|
296
|
-
|
315
|
+
# Render the search form.
|
316
|
+
get '/' do
|
317
|
+
erb :search, :locals => {:databases => Database.group_by(&:type)}
|
318
|
+
end
|
297
319
|
|
298
|
-
|
320
|
+
# BLAST search!
|
321
|
+
post '/' do
|
322
|
+
erb :result, :locals => {:report => BLAST.run(params)}
|
299
323
|
end
|
300
324
|
|
301
|
-
# get '/get_sequence/?
|
325
|
+
# get '/get_sequence/?sequence_ids=sequence_ids&database_ids=retreival_databases[&download=fasta]'
|
302
326
|
#
|
303
327
|
# Use whitespace to separate entries in sequence_ids (all other chars exist
|
304
328
|
# in identifiers) and retreival_databases (we don't allow whitespace in a
|
305
329
|
# database's name, so it's safe).
|
306
330
|
get '/get_sequence/' do
|
307
|
-
|
308
|
-
|
309
|
-
retrieval_databases = params[:db].split(/\s/)
|
310
|
-
|
311
|
-
settings.log.info("Looking for: '#{sequenceids.join(', ')}' in '#{retrieval_databases.join(', ')}'")
|
312
|
-
|
313
|
-
# the results do not indicate which database a hit is from.
|
314
|
-
# Thus if several databases were used for blasting, we must check them all
|
315
|
-
# if it works, refactor with "inject" or "collect"?
|
316
|
-
found_sequences = ''
|
317
|
-
|
318
|
-
retrieval_databases.each do |database| # we need to populate this session variable from the erb.
|
319
|
-
sequence = sequence_from_blastdb(sequenceids, database)
|
320
|
-
if sequence.empty?
|
321
|
-
settings.log.debug("'#{sequenceids.join(', ')}' not found in #{database}")
|
322
|
-
else
|
323
|
-
found_sequences += sequence
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
|
-
found_sequences_count = found_sequences.count('>')
|
328
|
-
|
329
|
-
out = ''
|
330
|
-
# just in case, checking we found right number of sequences
|
331
|
-
if found_sequences_count != sequenceids.length
|
332
|
-
out <<<<HEADER
|
333
|
-
<h1>ERROR: incorrect number of sequences found.</h1>
|
334
|
-
<p>Dear user,</p>
|
331
|
+
sequence_ids = params[:sequence_ids].split(/\s/)
|
332
|
+
database_ids = params[:database_ids].split(/\s/)
|
335
333
|
|
336
|
-
|
337
|
-
<em>#{found_sequences_count > sequenceids.length ? 'more' : 'less'}</em>
|
338
|
-
sequence than expected.</strong></p>
|
334
|
+
sequences = Sequence.from_blastdb(sequence_ids, database_ids)
|
339
335
|
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
with the following identifiers: <code>#{sequenceids.join(', ')}</code>,
|
346
|
-
from the following databases: <code>#{retrieval_databases.join(', ')}</code>.
|
347
|
-
But we found #{found_sequences_count} sequence#{found_sequences_count> 1 ? 's' : ''}.
|
348
|
-
</p>
|
349
|
-
|
350
|
-
<p>If sequences were retrieved, you can find them below (but some may be incorrect, so be careful!).</p>
|
351
|
-
<hr/>
|
352
|
-
HEADER
|
353
|
-
end
|
354
|
-
|
355
|
-
out << "<pre><code>#{found_sequences}</pre></code>"
|
356
|
-
out
|
357
|
-
end
|
358
|
-
|
359
|
-
# Ensure a '>sequence_identifier\n' at the start of a sequence.
|
360
|
-
#
|
361
|
-
# An empty query line appears in the blast report if the leading
|
362
|
-
# '>sequence_identifier\n' in the sequence is missing. We prepend
|
363
|
-
# the input sequence with user info in such case.
|
364
|
-
#
|
365
|
-
# > to_fasta("acgt")
|
366
|
-
# => 'Submitted_By_127.0.0.1_at_110214-15:33:34\nacgt'
|
367
|
-
def to_fasta(sequence)
|
368
|
-
sequence.lstrip!
|
369
|
-
if sequence[0,1] != '>'
|
370
|
-
ip = request.ip.to_s
|
371
|
-
time = Time.now.strftime("%y%m%d-%H:%M:%S")
|
372
|
-
sequence.insert(0, ">Submitted_By_#{ip}_at_#{time}\n")
|
373
|
-
end
|
374
|
-
return sequence
|
375
|
-
end
|
376
|
-
|
377
|
-
def format_blast_results(result, databases)
|
378
|
-
# Constructing the result in an Array and then calling Array#join is much faster than
|
379
|
-
# building up a String and using +=, as older versions of SeqServ did.
|
380
|
-
formatted_results = []
|
381
|
-
|
382
|
-
@all_retrievable_ids = []
|
383
|
-
string_of_used_databases = databases.join(' ')
|
384
|
-
blast_database_number = 0
|
385
|
-
line_number = 0
|
386
|
-
started_query = false
|
387
|
-
finished_database_summary = false
|
388
|
-
finished_alignments = false
|
389
|
-
reference_string = ''
|
390
|
-
database_summary_string = ''
|
391
|
-
result.each do |line|
|
392
|
-
line_number += 1
|
393
|
-
next if line_number <= 5 #skip the first 5 lines
|
394
|
-
|
395
|
-
# Add the reference to the end, not the start, of the blast result
|
396
|
-
if line_number >= 7 and line_number <= 15
|
397
|
-
reference_string += line
|
398
|
-
next
|
399
|
-
end
|
400
|
-
|
401
|
-
if !finished_database_summary and line_number > 15
|
402
|
-
database_summary_string += line
|
403
|
-
finished_database_summary = true if line.match(/total letters/)
|
404
|
-
next
|
405
|
-
end
|
406
|
-
|
407
|
-
# Remove certain lines from the output
|
408
|
-
skipped_lines = [/^<\/BODY>/,/^<\/HTML>/,/^<\/PRE>/]
|
409
|
-
skip = false
|
410
|
-
skipped_lines.each do |skippy|
|
411
|
-
# $stderr.puts "`#{line}' matches #{skippy}?"
|
412
|
-
if skippy.match(line)
|
413
|
-
skip = true
|
414
|
-
# $stderr.puts 'yes'
|
415
|
-
else
|
416
|
-
# $stderr.puts 'no'
|
417
|
-
end
|
336
|
+
if params[:download]
|
337
|
+
file_name = "sequenceserver_#{sequence_ids.first}.fasta"
|
338
|
+
file = Tempfile.new file_name
|
339
|
+
sequences.each do |sequence|
|
340
|
+
file.puts sequence.fasta
|
418
341
|
end
|
419
|
-
|
420
|
-
|
421
|
-
# Remove the javascript inclusion
|
422
|
-
line.gsub!(/^<script src=\"blastResult.js\"><\/script>/, '')
|
423
|
-
|
424
|
-
if line.match(/^>/) # If line to possibly replace
|
425
|
-
# Reposition the anchor to the end of the line, so that it both still works and
|
426
|
-
# doesn't interfere with the diagnostic space at the beginning of the line.
|
427
|
-
#
|
428
|
-
# There are two cases:
|
429
|
-
#
|
430
|
-
# database formatted _with_ -parse_seqids
|
431
|
-
line.gsub!(/^>(.+)(<a.*><\/a>)(.*)/, '>\1\3\2')
|
432
|
-
#
|
433
|
-
# database formatted _without_ -parse_seqids
|
434
|
-
line.gsub!(/^>(<a.*><\/a>)(.*)/, '>\2\1')
|
435
|
-
|
436
|
-
# get hit coordinates -- useful for linking to genome browsers
|
437
|
-
hit_length = result[line_number..-1].index{|l| l =~ />lcl|Lambda/}
|
438
|
-
hit_coordinates = result[line_number, hit_length].grep(/Sbjct/).
|
439
|
-
map(&:split).map{|l| [l[1], l[-1]]}.flatten.map(&:to_i).minmax
|
440
|
-
|
441
|
-
# Create the hyperlink (if required)
|
442
|
-
formatted_results << construct_sequence_hyperlink_line(line, databases, hit_coordinates)
|
443
|
-
else
|
444
|
-
# Surround each query's result in <div> tags so they can be coloured by CSS
|
445
|
-
if matches = line.match(/^<b>Query=<\/b> (.*)/) # If starting a new query, then surround in new <div> tag, and finish the last one off
|
446
|
-
line = "<div class=\"resultn\" id=\"#{matches[1]}\">\n<h3>Query= #{matches[1]}</h3><pre>"
|
447
|
-
unless blast_database_number == 0
|
448
|
-
line = "</pre></div>\n#{line}"
|
449
|
-
end
|
450
|
-
blast_database_number += 1
|
451
|
-
elsif line.match(/^ Database: /) and !finished_alignments
|
452
|
-
formatted_results << "</div>\n<pre>#{database_summary_string}\n\n"
|
453
|
-
finished_alignments = true
|
454
|
-
end
|
455
|
-
formatted_results << line
|
456
|
-
end
|
457
|
-
end
|
458
|
-
formatted_results << "</pre>"
|
459
|
-
|
460
|
-
link_to_fasta_of_all = "/get_sequence/?id=#{@all_retrievable_ids.join(' ')}&db=#{string_of_used_databases}"
|
461
|
-
# #dbs must be sep by ' '
|
462
|
-
retrieval_text = @all_retrievable_ids.empty? ? '' : "<a href='#{url(link_to_fasta_of_all)}'>FASTA of #{@all_retrievable_ids.length} retrievable hit(s)</a>"
|
463
|
-
|
464
|
-
"<h2>Results</h2>"+
|
465
|
-
retrieval_text +
|
466
|
-
"<br/><br/>" +
|
467
|
-
formatted_results.join +
|
468
|
-
"<br/>" +
|
469
|
-
"<pre>#{reference_string.strip}</pre>"
|
470
|
-
end
|
471
|
-
|
472
|
-
def construct_sequence_hyperlink_line(line, databases, hit_coordinates)
|
473
|
-
matches = line.match(/^>(.+)/)
|
474
|
-
sequence_id = matches[1]
|
475
|
-
|
476
|
-
link = nil
|
477
|
-
|
478
|
-
# If a custom sequence hyperlink method has been defined,
|
479
|
-
# use that.
|
480
|
-
options = {
|
481
|
-
:sequence_id => sequence_id,
|
482
|
-
:databases => databases,
|
483
|
-
:hit_coordinates => hit_coordinates
|
484
|
-
}
|
485
|
-
|
486
|
-
# First precedence: construct the whole line to be customised
|
487
|
-
if self.respond_to?(:construct_custom_sequence_hyperlinking_line)
|
488
|
-
settings.log.debug("Using custom hyperlinking line creator with sequence #{options.inspect}")
|
489
|
-
link_line = construct_custom_sequence_hyperlinking_line(options)
|
490
|
-
unless link_line.nil?
|
491
|
-
return link_line
|
492
|
-
end
|
493
|
-
end
|
494
|
-
|
495
|
-
# If we have reached here, custom construction of the
|
496
|
-
# whole line either wasn't defined, or returned nil
|
497
|
-
# (indicating failure)
|
498
|
-
if self.respond_to?(:construct_custom_sequence_hyperlink)
|
499
|
-
settings.log.debug("Using custom hyperlink creator with sequence #{options.inspect}")
|
500
|
-
link = construct_custom_sequence_hyperlink(options)
|
501
|
-
else
|
502
|
-
settings.log.debug("Using standard hyperlink creator with sequence `#{options.inspect}'")
|
503
|
-
link = construct_standard_sequence_hyperlink(options)
|
504
|
-
end
|
505
|
-
|
506
|
-
# Return the BLAST output line with the link in it
|
507
|
-
if link.nil?
|
508
|
-
settings.log.debug('No link added link for: `'+ sequence_id +'\'')
|
509
|
-
return line
|
342
|
+
file.close
|
343
|
+
send_file file.path, :type => :fasta, :filename => file_name
|
510
344
|
else
|
511
|
-
|
512
|
-
|
345
|
+
{
|
346
|
+
:sequence_ids => sequence_ids,
|
347
|
+
:databases => Database[database_ids].map(&:title),
|
348
|
+
:sequences => sequences.map {|s| s.info}
|
349
|
+
}.to_json
|
513
350
|
end
|
351
|
+
end
|
514
352
|
|
353
|
+
# This error block will only ever be hit if the user gives us a funny
|
354
|
+
# sequence or incorrect advanced parameter. Well, we could hit this block
|
355
|
+
# if someone is playing around with our HTTP API too.
|
356
|
+
error BLAST::ArgumentError do
|
357
|
+
status 400
|
358
|
+
error = env['sinatra.error']
|
359
|
+
erb :'400', :locals => {:error => error}
|
515
360
|
end
|
516
361
|
|
517
|
-
#
|
518
|
-
#
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
362
|
+
# This will catch any unhandled error and some very special errors. Ideally
|
363
|
+
# we will never hit this block. If we do, there's a bug in SequenceServer
|
364
|
+
# or something really weird going on. If we hit this error block we show
|
365
|
+
# the stacktrace to the user requesting them to post the same to our Google
|
366
|
+
# Group.
|
367
|
+
error Exception, BLAST::RuntimeError do
|
368
|
+
status 500
|
369
|
+
error = env['sinatra.error']
|
370
|
+
erb :'500', :locals => {:error => error}
|
525
371
|
end
|
526
372
|
end
|
527
373
|
end
|