sequenceserver 1.0.0.pre.2 → 1.0.0.pre.3
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/Gemfile +1 -1
- data/README.md +18 -4
- data/bin/sequenceserver +219 -124
- data/lib/sequenceserver.rb +156 -153
- data/lib/sequenceserver/blast.rb +163 -432
- data/lib/sequenceserver/blast/exceptions.rb +27 -0
- data/lib/sequenceserver/blast/hit.rb +32 -0
- data/lib/sequenceserver/blast/hsp.rb +260 -0
- data/lib/sequenceserver/blast/query.rb +28 -0
- data/lib/sequenceserver/blast/report.rb +123 -0
- data/lib/sequenceserver/config.rb +94 -0
- data/lib/sequenceserver/database.rb +89 -49
- data/lib/sequenceserver/exceptions.rb +154 -0
- data/lib/sequenceserver/links.rb +1 -1
- data/lib/sequenceserver/logger.rb +5 -7
- data/lib/sequenceserver/sequence.rb +40 -39
- data/public/css/bootstrap.min.css +5 -7
- data/public/css/custom.css +28 -27
- data/public/dist/css/sequenceserver.min.css +1 -0
- data/public/dist/css/sequenceserver.min.css.gz +0 -0
- data/public/dist/fonts/FontAwesome.otf +0 -0
- data/public/dist/fonts/fontawesome-webfont.eot +0 -0
- data/public/dist/fonts/fontawesome-webfont.svg +565 -0
- data/public/dist/fonts/fontawesome-webfont.ttf +0 -0
- data/public/dist/fonts/fontawesome-webfont.woff +0 -0
- data/public/dist/fonts/fontawesome-webfont.woff2 +0 -0
- data/public/dist/js/sequenceserver.min.js +12 -0
- data/public/dist/js/sequenceserver.min.js.gz +0 -0
- data/public/dist/js/shims/FlashCanvas/canvas2png.js +1 -0
- data/public/dist/js/shims/FlashCanvas/flashcanvas.js +1 -0
- data/public/dist/js/shims/FlashCanvas/flashcanvas.swf +0 -0
- data/public/dist/js/shims/FlashCanvasPro/canvas2png.js +1 -0
- data/public/dist/js/shims/FlashCanvasPro/flash10canvas.swf +0 -0
- data/public/dist/js/shims/FlashCanvasPro/flash9canvas.swf +0 -0
- data/public/dist/js/shims/FlashCanvasPro/flashcanvas.js +1 -0
- data/public/dist/js/shims/canvas-blob.js +1 -0
- data/public/dist/js/shims/color-picker.js +2 -0
- data/public/dist/js/shims/combos/1.js +6 -0
- data/public/dist/js/shims/combos/10.js +2 -0
- data/public/dist/js/shims/combos/11.js +2 -0
- data/public/dist/js/shims/combos/12.js +6 -0
- data/public/dist/js/shims/combos/13.js +1 -0
- data/public/dist/js/shims/combos/14.js +1 -0
- data/public/dist/js/shims/combos/15.js +2 -0
- data/public/dist/js/shims/combos/16.js +7 -0
- data/public/dist/js/shims/combos/17.js +2 -0
- data/public/dist/js/shims/combos/18.js +3 -0
- data/public/dist/js/shims/combos/2.js +7 -0
- data/public/dist/js/shims/combos/21.js +2 -0
- data/public/dist/js/shims/combos/22.js +1 -0
- data/public/dist/js/shims/combos/23.js +6 -0
- data/public/dist/js/shims/combos/25.js +2 -0
- data/public/dist/js/shims/combos/27.js +1 -0
- data/public/dist/js/shims/combos/28.js +1 -0
- data/public/dist/js/shims/combos/29.js +1 -0
- data/public/dist/js/shims/combos/3.js +1 -0
- data/public/dist/js/shims/combos/30.js +2 -0
- data/public/dist/js/shims/combos/31.js +1 -0
- data/public/dist/js/shims/combos/33.js +1 -0
- data/public/dist/js/shims/combos/34.js +1 -0
- data/public/dist/js/shims/combos/4.js +1 -0
- data/public/dist/js/shims/combos/5.js +2 -0
- data/public/dist/js/shims/combos/6.js +2 -0
- data/public/dist/js/shims/combos/7.js +7 -0
- data/public/dist/js/shims/combos/8.js +7 -0
- data/public/dist/js/shims/combos/9.js +2 -0
- data/public/dist/js/shims/combos/97.js +1 -0
- data/public/dist/js/shims/combos/98.js +1 -0
- data/public/dist/js/shims/combos/99.js +1 -0
- data/public/dist/js/shims/details.js +1 -0
- data/public/dist/js/shims/dom-extend.js +1 -0
- data/public/dist/js/shims/es5.js +1 -0
- data/public/dist/js/shims/es6.js +1 -0
- data/public/dist/js/shims/excanvas.js +1 -0
- data/public/dist/js/shims/filereader-xhr.js +1 -0
- data/public/dist/js/shims/form-combat.js +1 -0
- data/public/dist/js/shims/form-core.js +1 -0
- data/public/dist/js/shims/form-datalist-lazy.js +1 -0
- data/public/dist/js/shims/form-datalist.js +1 -0
- data/public/dist/js/shims/form-fixrangechange.js +1 -0
- data/public/dist/js/shims/form-inputmode.js +1 -0
- data/public/dist/js/shims/form-message.js +1 -0
- data/public/dist/js/shims/form-native-extend.js +1 -0
- data/public/dist/js/shims/form-number-date-api.js +1 -0
- data/public/dist/js/shims/form-number-date-ui.js +1 -0
- data/public/dist/js/shims/form-shim-extend.js +1 -0
- data/public/dist/js/shims/form-shim-extend2.js +1 -0
- data/public/dist/js/shims/form-validation.js +1 -0
- data/public/dist/js/shims/form-validators.js +1 -0
- data/public/dist/js/shims/forms-picker.js +1 -0
- data/public/dist/js/shims/geolocation.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-ar.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-ch-CN.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-cs.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-de.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-el.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-en.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-es.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-fa.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-fr.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-he.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-hi.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-hu.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-it.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-ja.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-lt.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-nl.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-pl.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-pt-BR.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-pt-PT.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-pt.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-ru.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-sv.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-zh-CN.js +1 -0
- data/public/dist/js/shims/i18n/formcfg-zh-TW.js +1 -0
- data/public/dist/js/shims/jme/alternate-media.js +1 -0
- data/public/dist/js/shims/jme/base.js +1 -0
- data/public/dist/js/shims/jme/controls.css +1 -0
- data/public/dist/js/shims/jme/jme.eot +0 -0
- data/public/dist/js/shims/jme/jme.svg +36 -0
- data/public/dist/js/shims/jme/jme.ttf +0 -0
- data/public/dist/js/shims/jme/jme.woff +0 -0
- data/public/dist/js/shims/jme/mediacontrols-lazy.js +1 -0
- data/public/dist/js/shims/jme/mediacontrols.js +1 -0
- data/public/dist/js/shims/jme/playlist.js +1 -0
- data/public/dist/js/shims/jpicker/images/AlphaBar.png +0 -0
- data/public/dist/js/shims/jpicker/images/Bars.png +0 -0
- data/public/dist/js/shims/jpicker/images/Maps.png +0 -0
- data/public/dist/js/shims/jpicker/images/NoColor.png +0 -0
- data/public/dist/js/shims/jpicker/images/bar-opacity.png +0 -0
- data/public/dist/js/shims/jpicker/images/map-opacity.png +0 -0
- data/public/dist/js/shims/jpicker/images/mappoint.gif +0 -0
- data/public/dist/js/shims/jpicker/images/picker.gif +0 -0
- data/public/dist/js/shims/jpicker/images/preview-opacity.png +0 -0
- data/public/dist/js/shims/jpicker/images/rangearrows.gif +0 -0
- data/public/dist/js/shims/jpicker/jpicker.css +1 -0
- data/public/dist/js/shims/matchMedia.js +3 -0
- data/public/dist/js/shims/mediacapture-picker.js +1 -0
- data/public/dist/js/shims/mediacapture.js +1 -0
- data/public/dist/js/shims/mediaelement-core.js +1 -0
- data/public/dist/js/shims/mediaelement-debug.js +1 -0
- data/public/dist/js/shims/mediaelement-jaris.js +1 -0
- data/public/dist/js/shims/mediaelement-native-fix.js +1 -0
- data/public/dist/js/shims/mediaelement-yt.js +1 -0
- data/public/dist/js/shims/moxie/flash/Moxie.cdn.swf +0 -0
- data/public/dist/js/shims/moxie/flash/Moxie.min.swf +0 -0
- data/public/dist/js/shims/moxie/js/moxie-html4.js +3 -0
- data/public/dist/js/shims/moxie/js/moxie-swf.js +2 -0
- data/public/dist/js/shims/picture.js +1 -0
- data/public/dist/js/shims/plugins/jquery.ui.position.js +11 -0
- data/public/dist/js/shims/range-ui.js +1 -0
- data/public/dist/js/shims/sizzle.js +11 -0
- data/public/dist/js/shims/sticky.js +1 -0
- data/public/dist/js/shims/styles/color-picker.png +0 -0
- data/public/dist/js/shims/styles/forms-ext.css +1 -0
- data/public/dist/js/shims/styles/forms-picker.css +1 -0
- data/public/dist/js/shims/styles/progress.gif +0 -0
- data/public/dist/js/shims/styles/progress.png +0 -0
- data/public/dist/js/shims/styles/shim-ext.css +1 -0
- data/public/dist/js/shims/styles/shim.css +1 -0
- data/public/dist/js/shims/styles/transparent.png +0 -0
- data/public/dist/js/shims/styles/widget.eot +0 -0
- data/public/dist/js/shims/styles/widget.svg +12 -0
- data/public/dist/js/shims/styles/widget.ttf +0 -0
- data/public/dist/js/shims/styles/widget.woff +0 -0
- data/public/dist/js/shims/swf/JarisFLVPlayer.swf +0 -0
- data/public/dist/js/shims/swfmini-embed.js +1 -0
- data/public/dist/js/shims/swfmini.js +6 -0
- data/public/dist/js/shims/track-ui.js +1 -0
- data/public/dist/js/shims/track.js +1 -0
- data/public/dist/js/shims/url.js +1 -0
- data/public/dist/js/shims/usermedia-core.js +1 -0
- data/public/dist/js/shims/usermedia-shim.js +1 -0
- data/public/dist/js/webshims/shims/FlashCanvas/canvas2png.js +1 -0
- data/public/dist/js/webshims/shims/FlashCanvas/flashcanvas.js +1 -0
- data/public/dist/js/webshims/shims/FlashCanvas/flashcanvas.swf +0 -0
- data/public/dist/js/webshims/shims/FlashCanvasPro/canvas2png.js +1 -0
- data/public/dist/js/webshims/shims/FlashCanvasPro/flash10canvas.swf +0 -0
- data/public/dist/js/webshims/shims/FlashCanvasPro/flash9canvas.swf +0 -0
- data/public/dist/js/webshims/shims/FlashCanvasPro/flashcanvas.js +1 -0
- data/public/dist/js/webshims/shims/canvas-blob.js +1 -0
- data/public/dist/js/webshims/shims/color-picker.js +2 -0
- data/public/dist/js/webshims/shims/combos/1.js +6 -0
- data/public/dist/js/webshims/shims/combos/10.js +2 -0
- data/public/dist/js/webshims/shims/combos/11.js +2 -0
- data/public/dist/js/webshims/shims/combos/12.js +6 -0
- data/public/dist/js/webshims/shims/combos/13.js +1 -0
- data/public/dist/js/webshims/shims/combos/14.js +1 -0
- data/public/dist/js/webshims/shims/combos/15.js +2 -0
- data/public/dist/js/webshims/shims/combos/16.js +7 -0
- data/public/dist/js/webshims/shims/combos/17.js +2 -0
- data/public/dist/js/webshims/shims/combos/18.js +3 -0
- data/public/dist/js/webshims/shims/combos/2.js +7 -0
- data/public/dist/js/webshims/shims/combos/21.js +2 -0
- data/public/dist/js/webshims/shims/combos/22.js +1 -0
- data/public/dist/js/webshims/shims/combos/23.js +6 -0
- data/public/dist/js/webshims/shims/combos/25.js +2 -0
- data/public/dist/js/webshims/shims/combos/27.js +1 -0
- data/public/dist/js/webshims/shims/combos/28.js +1 -0
- data/public/dist/js/webshims/shims/combos/29.js +1 -0
- data/public/dist/js/webshims/shims/combos/3.js +1 -0
- data/public/dist/js/webshims/shims/combos/30.js +2 -0
- data/public/dist/js/webshims/shims/combos/31.js +1 -0
- data/public/dist/js/webshims/shims/combos/33.js +1 -0
- data/public/dist/js/webshims/shims/combos/34.js +1 -0
- data/public/dist/js/webshims/shims/combos/4.js +1 -0
- data/public/dist/js/webshims/shims/combos/5.js +2 -0
- data/public/dist/js/webshims/shims/combos/6.js +2 -0
- data/public/dist/js/webshims/shims/combos/7.js +7 -0
- data/public/dist/js/webshims/shims/combos/8.js +7 -0
- data/public/dist/js/webshims/shims/combos/9.js +2 -0
- data/public/dist/js/webshims/shims/combos/97.js +1 -0
- data/public/dist/js/webshims/shims/combos/98.js +1 -0
- data/public/dist/js/webshims/shims/combos/99.js +1 -0
- data/public/dist/js/webshims/shims/details.js +1 -0
- data/public/dist/js/webshims/shims/dom-extend.js +1 -0
- data/public/dist/js/webshims/shims/es5.js +1 -0
- data/public/dist/js/webshims/shims/es6.js +1 -0
- data/public/dist/js/webshims/shims/excanvas.js +1 -0
- data/public/dist/js/webshims/shims/filereader-xhr.js +1 -0
- data/public/dist/js/webshims/shims/form-combat.js +1 -0
- data/public/dist/js/webshims/shims/form-core.js +1 -0
- data/public/dist/js/webshims/shims/form-datalist-lazy.js +1 -0
- data/public/dist/js/webshims/shims/form-datalist.js +1 -0
- data/public/dist/js/webshims/shims/form-fixrangechange.js +1 -0
- data/public/dist/js/webshims/shims/form-inputmode.js +1 -0
- data/public/dist/js/webshims/shims/form-message.js +1 -0
- data/public/dist/js/webshims/shims/form-native-extend.js +1 -0
- data/public/dist/js/webshims/shims/form-number-date-api.js +1 -0
- data/public/dist/js/webshims/shims/form-number-date-ui.js +1 -0
- data/public/dist/js/webshims/shims/form-shim-extend.js +1 -0
- data/public/dist/js/webshims/shims/form-shim-extend2.js +1 -0
- data/public/dist/js/webshims/shims/form-validation.js +1 -0
- data/public/dist/js/webshims/shims/form-validators.js +1 -0
- data/public/dist/js/webshims/shims/forms-picker.js +1 -0
- data/public/dist/js/webshims/shims/geolocation.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-ar.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-ch-CN.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-cs.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-de.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-el.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-en.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-es.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-fa.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-fr.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-he.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-hi.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-hu.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-it.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-ja.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-lt.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-nl.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-pl.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-pt-BR.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-pt-PT.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-pt.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-ru.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-sv.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-zh-CN.js +1 -0
- data/public/dist/js/webshims/shims/i18n/formcfg-zh-TW.js +1 -0
- data/public/dist/js/webshims/shims/jme/alternate-media.js +1 -0
- data/public/dist/js/webshims/shims/jme/base.js +1 -0
- data/public/dist/js/webshims/shims/jme/controls.css +1 -0
- data/public/dist/js/webshims/shims/jme/jme.eot +0 -0
- data/public/dist/js/webshims/shims/jme/jme.svg +36 -0
- data/public/dist/js/webshims/shims/jme/jme.ttf +0 -0
- data/public/dist/js/webshims/shims/jme/jme.woff +0 -0
- data/public/dist/js/webshims/shims/jme/mediacontrols-lazy.js +1 -0
- data/public/dist/js/webshims/shims/jme/mediacontrols.js +1 -0
- data/public/dist/js/webshims/shims/jme/playlist.js +1 -0
- data/public/dist/js/webshims/shims/jpicker/images/AlphaBar.png +0 -0
- data/public/dist/js/webshims/shims/jpicker/images/Bars.png +0 -0
- data/public/dist/js/webshims/shims/jpicker/images/Maps.png +0 -0
- data/public/dist/js/webshims/shims/jpicker/images/NoColor.png +0 -0
- data/public/dist/js/webshims/shims/jpicker/images/bar-opacity.png +0 -0
- data/public/dist/js/webshims/shims/jpicker/images/map-opacity.png +0 -0
- data/public/dist/js/webshims/shims/jpicker/images/mappoint.gif +0 -0
- data/public/dist/js/webshims/shims/jpicker/images/picker.gif +0 -0
- data/public/dist/js/webshims/shims/jpicker/images/preview-opacity.png +0 -0
- data/public/dist/js/webshims/shims/jpicker/images/rangearrows.gif +0 -0
- data/public/dist/js/webshims/shims/jpicker/jpicker.css +1 -0
- data/public/dist/js/webshims/shims/matchMedia.js +3 -0
- data/public/dist/js/webshims/shims/mediacapture-picker.js +1 -0
- data/public/dist/js/webshims/shims/mediacapture.js +1 -0
- data/public/dist/js/webshims/shims/mediaelement-core.js +1 -0
- data/public/dist/js/webshims/shims/mediaelement-debug.js +1 -0
- data/public/dist/js/webshims/shims/mediaelement-jaris.js +1 -0
- data/public/dist/js/webshims/shims/mediaelement-native-fix.js +1 -0
- data/public/dist/js/webshims/shims/mediaelement-yt.js +1 -0
- data/public/dist/js/webshims/shims/moxie/flash/Moxie.cdn.swf +0 -0
- data/public/dist/js/webshims/shims/moxie/flash/Moxie.min.swf +0 -0
- data/public/dist/js/webshims/shims/moxie/js/moxie-html4.js +3 -0
- data/public/dist/js/webshims/shims/moxie/js/moxie-swf.js +2 -0
- data/public/dist/js/webshims/shims/picture.js +1 -0
- data/public/dist/js/webshims/shims/plugins/jquery.ui.position.js +11 -0
- data/public/dist/js/webshims/shims/range-ui.js +1 -0
- data/public/dist/js/webshims/shims/sizzle.js +11 -0
- data/public/dist/js/webshims/shims/sticky.js +1 -0
- data/public/dist/js/webshims/shims/styles/color-picker.png +0 -0
- data/public/dist/js/webshims/shims/styles/forms-ext.css +1 -0
- data/public/dist/js/webshims/shims/styles/forms-picker.css +1 -0
- data/public/dist/js/webshims/shims/styles/progress.gif +0 -0
- data/public/dist/js/webshims/shims/styles/progress.png +0 -0
- data/public/dist/js/webshims/shims/styles/shim-ext.css +1 -0
- data/public/dist/js/webshims/shims/styles/shim.css +1 -0
- data/public/dist/js/webshims/shims/styles/transparent.png +0 -0
- data/public/dist/js/webshims/shims/styles/widget.eot +0 -0
- data/public/dist/js/webshims/shims/styles/widget.svg +12 -0
- data/public/dist/js/webshims/shims/styles/widget.ttf +0 -0
- data/public/dist/js/webshims/shims/styles/widget.woff +0 -0
- data/public/dist/js/webshims/shims/swf/JarisFLVPlayer.swf +0 -0
- data/public/dist/js/webshims/shims/swfmini-embed.js +1 -0
- data/public/dist/js/webshims/shims/swfmini.js +6 -0
- data/public/dist/js/webshims/shims/track-ui.js +1 -0
- data/public/dist/js/webshims/shims/track.js +1 -0
- data/public/dist/js/webshims/shims/url.js +1 -0
- data/public/dist/js/webshims/shims/usermedia-core.js +1 -0
- data/public/dist/js/webshims/shims/usermedia-shim.js +1 -0
- data/public/js/bootstrap.min.js +3 -8
- data/public/js/jquery-ui.min.js +6 -0
- data/public/js/jquery.min.js +4 -0
- data/public/js/jquery.t.js +4 -4
- data/public/js/sequenceserver.blast.js +20 -18
- data/public/js/sequenceserver.js +116 -74
- data/sequenceserver.gemspec +20 -16
- data/views/400.erb +2 -1
- data/views/500.erb +6 -1
- data/views/result.erb +38 -18
- data/views/search.erb +49 -32
- metadata +389 -11
- data/public/img/glyphicons-halflings-white.png +0 -0
- data/public/img/glyphicons-halflings.png +0 -0
- data/public/js/jquery-ui.js +0 -14987
- data/public/js/jquery.js +0 -5
- data/public/js/jquery.scrollspy.js +0 -74
- data/public/sequence.min.js +0 -1
data/lib/sequenceserver/blast.rb
CHANGED
@@ -1,8 +1,14 @@
|
|
1
1
|
require 'forwardable'
|
2
2
|
require 'tempfile'
|
3
|
+
require 'English'
|
3
4
|
require 'ox'
|
4
5
|
|
5
6
|
require 'sequenceserver/links'
|
7
|
+
require 'sequenceserver/blast/exceptions'
|
8
|
+
require 'sequenceserver/blast/report'
|
9
|
+
require 'sequenceserver/blast/query'
|
10
|
+
require 'sequenceserver/blast/hit'
|
11
|
+
require 'sequenceserver/blast/hsp'
|
6
12
|
|
7
13
|
module SequenceServer
|
8
14
|
# Simple wrapper around BLAST+ search algorithms.
|
@@ -10,478 +16,203 @@ module SequenceServer
|
|
10
16
|
# `BLAST::ArgumentError` and `BLAST::RuntimeError` signal errors encountered
|
11
17
|
# when attempting a BLAST search.
|
12
18
|
module BLAST
|
13
|
-
|
14
|
-
#
|
15
|
-
# ArgumentError is raised when BLAST+'s exit status is 1; see [1].
|
16
|
-
class ArgumentError < ArgumentError
|
17
|
-
end
|
18
|
-
|
19
|
-
# To signal internal errors.
|
20
|
-
#
|
21
|
-
# RuntimeError is raised when BLAST+'s exits status is one of 2, 3, 4, or
|
22
|
-
# 255; see [1]. These are rare, infrastructure errors, used internally,
|
23
|
-
# and of concern only to the admins/developers.
|
24
|
-
class RuntimeError < RuntimeError
|
25
|
-
def initialize(status, message)
|
26
|
-
@status = status
|
27
|
-
@message = message
|
28
|
-
end
|
29
|
-
|
30
|
-
attr_reader :status, :message
|
31
|
-
|
32
|
-
def to_s
|
33
|
-
"#{status}, #{message}"
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
# Capture results per query of a BLAST search.
|
38
|
-
# @member [String] number
|
39
|
-
# @member [String] def
|
40
|
-
# @member [Fixnum] len
|
41
|
-
# @member [Array(Hit)] hits
|
42
|
-
Query = Struct.new(:number, :def, :len, :hits) do
|
43
|
-
def initialize(*args)
|
44
|
-
args[0] = args[0].to_i
|
45
|
-
args[1] = "Query_#{args[0]}" if args[1] == 'No definition line'
|
46
|
-
args[2] = args[2].to_i
|
47
|
-
@id, *rest = args[1].split
|
48
|
-
@title = rest.join(' ')
|
49
|
-
super
|
50
|
-
end
|
51
|
-
|
52
|
-
def sort_hits_by_evalue!
|
53
|
-
@hits = hits.sort_by(&:evalue)
|
54
|
-
end
|
55
|
-
|
56
|
-
attr_reader :id, :title
|
57
|
-
|
58
|
-
alias_method :length, :len
|
59
|
-
end
|
60
|
-
|
61
|
-
# Hit Object to store all the hits per Query.
|
62
|
-
# @member [Fixnum] number
|
63
|
-
# @member [String] id
|
64
|
-
# @member [String] def
|
65
|
-
# @member [String] accession
|
66
|
-
# @member [Fixnum] len
|
67
|
-
# @member [HSP] hsp
|
68
|
-
Hit = Struct.new(:number, :id, :title, :accession, :len, :hsps) do
|
69
|
-
def initialize(*args)
|
70
|
-
args[0] = args[0].to_i
|
71
|
-
args[2] = '' if args[2] == 'No definition line'
|
72
|
-
args[4] = args[4].to_i
|
73
|
-
super
|
74
|
-
end
|
75
|
-
|
76
|
-
alias_method :length, :len
|
77
|
-
|
78
|
-
# Hit evalue is the minimum evalue of all HSP(s).
|
79
|
-
def evalue
|
80
|
-
hsps.map(&:evalue).min
|
81
|
-
end
|
82
|
-
|
83
|
-
# Hit score is the sum of bit scores of all HSP(s).
|
84
|
-
def score
|
85
|
-
hsps.map(&:bit_score).reduce(:+)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
# Structure to hold the HSP information about each hit. For more
|
90
|
-
# information, check the link contained in the references section at the
|
91
|
-
# end of the file.
|
92
|
-
HSP = Struct.new(:number, :bit_score, :score, :evalue, :qstart, :qend,
|
93
|
-
:sstart, :send, :qframe, :sframe, :identity, :positives,
|
94
|
-
:gaps, :len, :qseq, :sseq, :midline) do
|
19
|
+
ERROR_LINE = /\(CArgException.*\)\s(.*)/
|
95
20
|
|
96
|
-
|
97
|
-
FLOAT_ARGS = [1, 3]
|
21
|
+
ALGORITHMS = %w(blastn blastp blastx tblastn tblastx)
|
98
22
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
+
class << self
|
39
|
+
extend Forwardable
|
40
|
+
|
41
|
+
def_delegators SequenceServer, :config, :logger
|
42
|
+
|
43
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
44
|
+
# rubocop:disable Metrics/MethodLength
|
45
|
+
def run(params)
|
46
|
+
pre_process params
|
47
|
+
validate_blast_params params
|
48
|
+
|
49
|
+
# Compile parameters for BLAST search into a shell executable command.
|
50
|
+
#
|
51
|
+
# BLAST method to use.
|
52
|
+
method = params[:method]
|
53
|
+
#
|
54
|
+
# BLAST+ expects query sequence as a file.
|
55
|
+
qfile = Tempfile.new('sequenceserver_query')
|
56
|
+
qfile.puts(params[:sequence])
|
57
|
+
qfile.close
|
58
|
+
#
|
59
|
+
# Retrieve database objects from database id.
|
60
|
+
databases = Database[params[:databases]]
|
61
|
+
#
|
62
|
+
# Concatenate other blast options.
|
63
|
+
options = params[:advanced].to_s.strip + defaults
|
64
|
+
#
|
65
|
+
# blastn implies blastn, not megablast; but let's not interfere if a
|
66
|
+
# user specifies `task` herself.
|
67
|
+
options << ' -task blastn' if method == 'blastn' && !(options =~ /task/)
|
68
|
+
|
69
|
+
# Run BLAST search.
|
70
|
+
#
|
71
|
+
# Command to execute.
|
72
|
+
command = "#{method} -db '#{databases.map(&:name).join(' ')}'" \
|
73
|
+
" -query '#{qfile.path}' #{options}"
|
74
|
+
#
|
75
|
+
# Debugging log.
|
76
|
+
logger.debug("Executing: #{command}")
|
77
|
+
#
|
78
|
+
# Temporary files to capture stdout and stderr.
|
79
|
+
rfile = Tempfile.new('sequenceserver_blast_result')
|
80
|
+
efile = Tempfile.new('sequenceserver_blast_error')
|
81
|
+
[rfile, efile].each(&:close)
|
82
|
+
#
|
83
|
+
# Execute.
|
84
|
+
system("#{command} > #{rfile.path} 2> #{efile.path}")
|
85
|
+
|
86
|
+
# Capture error.
|
87
|
+
status = $CHILD_STATUS.exitstatus
|
88
|
+
case status
|
89
|
+
when 1 # error in query sequence or options; see [1]
|
90
|
+
efile.open
|
91
|
+
|
92
|
+
# Most of the time BLAST+ generates a verbose error message with
|
93
|
+
# details we don't require. So we parse out the relevant lines.
|
94
|
+
error = efile.each_line do |l|
|
95
|
+
break Regexp.last_match[1] if l.match(ERROR_LINE)
|
96
|
+
end
|
103
97
|
|
104
|
-
|
105
|
-
|
98
|
+
# But sometimes BLAST+ returns the exact/relevant error message.
|
99
|
+
# Trying to parse such messages returns nil, and we use the error
|
100
|
+
# message from BLAST+ as it is.
|
101
|
+
error = efile.rewind && efile.read unless error.is_a? String
|
102
|
+
|
103
|
+
efile.close
|
104
|
+
fail ArgumentError, error
|
105
|
+
when 2, 3, 4, 255 # see [1]
|
106
|
+
efile.open
|
107
|
+
error = efile.read
|
108
|
+
efile.close
|
109
|
+
fail RuntimeError.new(status, error)
|
106
110
|
end
|
107
111
|
|
108
|
-
|
109
|
-
end
|
110
|
-
|
111
|
-
alias_method :length, :len
|
112
|
-
|
113
|
-
end
|
114
|
-
|
115
|
-
# Captures BLAST results from BLAST+'s XML output.
|
116
|
-
class Report
|
117
|
-
|
118
|
-
include Links
|
119
|
-
|
120
|
-
# Expects a File object and Database objects used to BLAST against.
|
121
|
-
#
|
122
|
-
# Parses the XML file into an intermediate representation (ir) and
|
123
|
-
# constructs an object model from that.
|
124
|
-
#
|
125
|
-
# NOTE:
|
126
|
-
# Databases param is optional for test suite.
|
127
|
-
def initialize(rfile, databases = nil)
|
128
|
-
ir = node_to_array Ox.parse(rfile.read).root
|
129
|
-
|
130
|
-
@program = ir[0]
|
131
|
-
@program_version = ir[1]
|
132
|
-
@querydb = Array databases
|
133
|
-
@parameters = {
|
134
|
-
:matrix => ir[7][0],
|
135
|
-
:evalue => ir[7][1],
|
136
|
-
:gapopen => ir[7][2],
|
137
|
-
:gapextend => ir[7][3],
|
138
|
-
:filters => ir[7][4]
|
139
|
-
}
|
140
|
-
|
141
|
-
ir[8].each_with_index do |n, i|
|
142
|
-
@stats ||= n[5][0]
|
143
|
-
@queries ||= []
|
144
|
-
@queries.push(Query.new(n[0], n[2], n[3], []))
|
145
|
-
|
146
|
-
# Ensure a hit object is received. No hits, returns a newline. Note
|
147
|
-
# that checking to "\n" doesn't work since n[4] = ["\n"]
|
148
|
-
if n[4] == ["\n"]
|
149
|
-
@queries[i][:hits] = []
|
150
|
-
else
|
151
|
-
n[4].each_with_index do |hits, j|
|
152
|
-
@queries[i][:hits].push(Hit.new(hits[0], hits[1], hits[2],
|
153
|
-
hits[3], hits[4], []))
|
154
|
-
hits[5].each do |hsp|
|
155
|
-
@queries[i][:hits][j][:hsps].push(HSP.new(*hsp))
|
156
|
-
end
|
157
|
-
end
|
158
|
-
@queries[i].sort_hits_by_evalue!
|
159
|
-
end
|
160
|
-
end
|
112
|
+
Report.new(File.basename(rfile), databases)
|
161
113
|
end
|
162
114
|
|
163
|
-
|
164
|
-
|
165
|
-
# :nodoc:
|
166
|
-
# params are defaults provided by BLAST or user input to tweak the
|
167
|
-
# result. stats are computed metrics provided by BLAST.
|
168
|
-
#
|
169
|
-
# BLAST+ doesn't list all input params (like word_size) in the XML
|
170
|
-
# output. Only matrix, evalue, gapopen, gapextend, and filters.
|
171
|
-
attr_reader :params, :stats
|
115
|
+
def format(params)
|
116
|
+
validate_format_params params
|
172
117
|
|
173
|
-
|
118
|
+
rfile = create_file_path params['report']
|
119
|
+
ofmt = OUTFMT[params['format']]
|
120
|
+
type = params['type']
|
174
121
|
|
175
|
-
|
122
|
+
oname = "seqserv_#{type}_#{Time.now.strftime('%H%M')}.#{ofmt[1]}"
|
123
|
+
ofile = Tempfile.new oname
|
176
124
|
|
177
|
-
|
125
|
+
command = "blast_formatter -archive '#{rfile}' -out #{ofile.path}" \
|
126
|
+
" -outfmt '#{ofmt[0]} #{params['specifiers']}' 2> /dev/null"
|
178
127
|
|
179
|
-
|
180
|
-
|
181
|
-
hsp.evalue.to_s.sub(/(\d*\.\d*)e?([+-]\d*)?/) do
|
182
|
-
s = '%.3f' % Regexp.last_match[1]
|
183
|
-
s << " × 10<sup>#{Regexp.last_match[2]}</sup>" if Regexp.last_match[2]
|
184
|
-
s
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
def identity_fraction(hsp)
|
189
|
-
"#{hsp.identity}/#{hsp.length}"
|
190
|
-
end
|
128
|
+
logger.debug("Executing: #{command}")
|
129
|
+
system command
|
191
130
|
|
192
|
-
|
193
|
-
|
131
|
+
{
|
132
|
+
:filepath => ofile.path,
|
133
|
+
:filename => oname,
|
134
|
+
:type => ofmt[1]
|
135
|
+
}
|
194
136
|
end
|
137
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
138
|
+
# rubocop:enable Metrics/MethodLength
|
195
139
|
|
196
|
-
def
|
197
|
-
|
140
|
+
def pre_process(params)
|
141
|
+
params[:sequence].strip! unless params[:sequence].nil?
|
198
142
|
end
|
199
143
|
|
200
|
-
def
|
201
|
-
|
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
|
202
152
|
end
|
203
153
|
|
204
|
-
def
|
205
|
-
|
154
|
+
def validate_blast_params(params)
|
155
|
+
validate_blast_method params[:method]
|
156
|
+
validate_blast_sequences params[:sequence]
|
157
|
+
validate_blast_databases params[:databases]
|
158
|
+
validate_blast_options params[:advanced]
|
206
159
|
end
|
207
160
|
|
208
|
-
def
|
209
|
-
"
|
161
|
+
def defaults
|
162
|
+
" -outfmt 11 -num_threads #{config[:num_threads]}"
|
210
163
|
end
|
211
164
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
# query and subject sequences. Since each amino acid is encoded using
|
217
|
-
# three nucl. referred to as codons, necessary value is multiplied
|
218
|
-
# to determine the coordinates.
|
219
|
-
|
220
|
-
# blastn and blastp search the nucleotide and protein databases using
|
221
|
-
# nucleotide and protein queries respectively.
|
222
|
-
qframe_unit = 1
|
223
|
-
sframe_unit = 1
|
224
|
-
# tblastn searches translated nucleotide database using a protein query
|
225
|
-
if @program == 'tblastn'
|
226
|
-
sframe_unit = 3
|
227
|
-
# blastx searches protein database using a translated nucleotide query,
|
228
|
-
elsif @program == 'blastx'
|
229
|
-
qframe_unit = 3
|
230
|
-
# tblastx searches translated nucleotide database using a translated
|
231
|
-
# nucleotide query.
|
232
|
-
elsif @program == 'tblastx'
|
233
|
-
qframe_unit = 3
|
234
|
-
sframe_unit = 3
|
235
|
-
end
|
236
|
-
|
237
|
-
qframe_sign = hsp.qframe >= 0 ? 1 : -1
|
238
|
-
sframe_sign = hsp.sframe >= 0 ? 1 : -1
|
239
|
-
|
240
|
-
chars = 60
|
241
|
-
lines = (hsp.length / chars.to_f).ceil
|
242
|
-
width = [hsp.qend, hsp.send, hsp.qstart,
|
243
|
-
hsp.sstart].map(&:to_s).map(&:length).max
|
244
|
-
|
245
|
-
# blastn results are inconsistent with the other methods as it
|
246
|
-
# automatically reverse the start and end coordinates (based on
|
247
|
-
# frame), while for others it has to be inferred.
|
248
|
-
if @program != 'blastn'
|
249
|
-
nqseq = hsp.qframe >= 0 ? hsp.qstart : hsp.qend
|
250
|
-
nsseq = hsp.sframe >= 0 ? hsp.sstart : hsp.send
|
251
|
-
else
|
252
|
-
nqseq = hsp.qstart
|
253
|
-
nsseq = hsp.sstart
|
254
|
-
end
|
255
|
-
|
256
|
-
s = ''
|
257
|
-
(1..lines).each do |i|
|
258
|
-
lqstart = nqseq
|
259
|
-
lqseq = hsp.qseq[chars * (i - 1), chars]
|
260
|
-
nqseq += (lqseq.length - lqseq.count('-')) * qframe_unit * qframe_sign
|
261
|
-
lqend = nqseq - qframe_sign
|
262
|
-
s << "Query %#{width}d #{lqseq} #{lqend}\n" % lqstart
|
263
|
-
|
264
|
-
lmseq = hsp.midline[chars * (i - 1), chars]
|
265
|
-
s << "#{' ' * (width + 8)} #{lmseq}\n"
|
266
|
-
|
267
|
-
lsstart = nsseq
|
268
|
-
lsseq = hsp.sseq[chars * (i - 1), chars]
|
269
|
-
nsseq += (lsseq.length - lsseq.count('-')) * sframe_unit * sframe_sign
|
270
|
-
lsend = nsseq - sframe_sign
|
271
|
-
s << "Subject %#{width}d #{lsseq} #{lsend}\n" % lsstart
|
272
|
-
|
273
|
-
s << "\n" unless i == lines
|
274
|
-
end
|
275
|
-
s
|
165
|
+
def validate_blast_method(method)
|
166
|
+
return true if ALGORITHMS.include? method
|
167
|
+
fail ArgumentError, 'BLAST algorithm should be one of:' \
|
168
|
+
" #{ALGORITHMS.join(', ')}."
|
276
169
|
end
|
277
170
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
'Score' => "#{'%.2f' % hsp[:bit_score]} (#{hsp[:score]})",
|
282
|
-
'E value' => "#{pretty_evalue hsp}",
|
283
|
-
'Identities' => "#{identity_fraction hsp} " \
|
284
|
-
"(#{identity_percentage hsp}%)",
|
285
|
-
'Gaps' => "#{gaps_fraction hsp} (#{gaps_percentage hsp}%)"
|
286
|
-
}
|
287
|
-
|
288
|
-
if @program == 'blastp'
|
289
|
-
hsp_stats['Positives'] = "#{positives_fraction hsp}" \
|
290
|
-
"(#{positives_percentage hsp}%)"
|
291
|
-
elsif @program == 'blastx'
|
292
|
-
hsp_stats['Query frame'] = "#{hsp[:qframe]}"
|
293
|
-
elsif @program == 'tblastn'
|
294
|
-
hsp_stats['Hit frame'] = "#{hsp[:sframe]}"
|
295
|
-
elsif @program == 'tblastx'
|
296
|
-
hsp_stats['Positives'] = "#{positives_fraction hsp}" \
|
297
|
-
"(#{positives_percentage hsp}%)"
|
298
|
-
hsp_stats['Frame'] = "#{hsp[:qframe]}/#{hsp[:sframe]}"
|
299
|
-
elsif @program == 'blastn'
|
300
|
-
hsp_stats['Strand'] = "#{hsp[:qframe] > 0 ? '+' : '-'}/" \
|
301
|
-
"#{hsp[:sframe] > 0 ? '+' : '-'}"
|
302
|
-
end
|
303
|
-
|
304
|
-
hsp_stats
|
171
|
+
def validate_blast_sequences(sequences)
|
172
|
+
return true if sequences.is_a?(String) && !sequences.empty?
|
173
|
+
fail ArgumentError, 'Sequences should be a non-empty string.'
|
305
174
|
end
|
306
175
|
|
307
|
-
def
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
176
|
+
def validate_blast_databases(database_ids)
|
177
|
+
ids = Database.ids
|
178
|
+
return true if database_ids.is_a?(Array) && !database_ids.empty? &&
|
179
|
+
(ids & database_ids).length == database_ids.length
|
180
|
+
fail ArgumentError, 'Database id should be one of:' \
|
181
|
+
" #{ids.join("\n")}."
|
312
182
|
end
|
313
183
|
|
314
|
-
#
|
315
|
-
#
|
316
|
-
# NOTE: This function may return more than one database object for
|
317
|
-
# a single sequence id.
|
184
|
+
# Advanced options are specified by the user. Here they are checked for
|
185
|
+
# interference with SequenceServer operations.
|
318
186
|
#
|
319
|
-
#
|
320
|
-
def
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
private
|
325
|
-
|
326
|
-
PARSEABLE_AS_ARRAY = %w(Parameters BlastOutput_param Iteration_stat
|
327
|
-
Statistics Iteration_hits BlastOutput_iterations
|
328
|
-
Iteration Hit Hit_hsps Hsp)
|
329
|
-
|
330
|
-
def node_to_array(element)
|
331
|
-
element.nodes.map {|n| node_to_value n}
|
332
|
-
end
|
187
|
+
# Raise ArgumentError if an error has occurred.
|
188
|
+
def validate_blast_options(options)
|
189
|
+
return true if !options || (options.is_a?(String) &&
|
190
|
+
options.strip.empty?)
|
333
191
|
|
334
|
-
|
335
|
-
|
336
|
-
return node if node.is_a?(String)
|
337
|
-
|
338
|
-
if PARSEABLE_AS_ARRAY.include? node.name
|
339
|
-
value = node_to_array(node)
|
340
|
-
else
|
341
|
-
value = first_text(node)
|
192
|
+
unless allowed_chars.match(options)
|
193
|
+
fail ArgumentError, 'Invalid characters detected in options.'
|
342
194
|
end
|
343
|
-
value
|
344
|
-
end
|
345
|
-
|
346
|
-
def first_text(node)
|
347
|
-
node.nodes.find {|n| n.is_a? String}
|
348
|
-
end
|
349
|
-
end
|
350
|
-
|
351
|
-
ERROR_LINE = /\(CArgException.*\)\s(.*)/
|
352
|
-
|
353
|
-
ALGORITHMS = %w(blastn blastp blastx tblastn tblastx)
|
354
|
-
|
355
|
-
extend self
|
356
|
-
|
357
|
-
extend Forwardable
|
358
|
-
|
359
|
-
def_delegators SequenceServer, :config, :logger
|
360
|
-
|
361
|
-
def run(params)
|
362
|
-
pre_process params
|
363
|
-
validate_blast_params params
|
364
195
|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
method = params[:method]
|
369
|
-
#
|
370
|
-
# BLAST+ expects query sequence as a file.
|
371
|
-
qfile = Tempfile.new('sequenceserver_query')
|
372
|
-
qfile.puts(params[:sequence])
|
373
|
-
qfile.close
|
374
|
-
#
|
375
|
-
# Retrieve database objects from database id.
|
376
|
-
databases = Database[params[:databases]]
|
377
|
-
#
|
378
|
-
# Concatenate other blast options.
|
379
|
-
options = params[:advanced].to_s.strip + defaults
|
380
|
-
#
|
381
|
-
# blastn implies blastn, not megablast; but let's not interfere if a user
|
382
|
-
# specifies `task` herself.
|
383
|
-
if method == 'blastn' and not options =~ /task/
|
384
|
-
options << ' -task blastn'
|
385
|
-
end
|
386
|
-
|
387
|
-
# Run BLAST search.
|
388
|
-
#
|
389
|
-
# Command to execute.
|
390
|
-
command = "#{method} -db '#{databases.map(&:name).join(' ')}'" \
|
391
|
-
" -query '#{qfile.path}' #{options}"
|
392
|
-
#
|
393
|
-
# Debugging log.
|
394
|
-
logger.debug("Executing: #{command}")
|
395
|
-
#
|
396
|
-
# Temporary files to capture stdout and stderr.
|
397
|
-
rfile = Tempfile.new('sequenceserver_blast_result')
|
398
|
-
efile = Tempfile.new('sequenceserver_blast_error')
|
399
|
-
[rfile, efile].each(&:close)
|
400
|
-
#
|
401
|
-
# Execute.
|
402
|
-
system("#{command} > #{rfile.path} 2> #{efile.path}")
|
403
|
-
|
404
|
-
# Capture error.
|
405
|
-
status = $?.exitstatus
|
406
|
-
case status
|
407
|
-
when 1 # error in query sequence or options; see [1]
|
408
|
-
efile.open
|
409
|
-
|
410
|
-
# Most of the time BLAST+ generates a verbose error message with
|
411
|
-
# details we don't require. So we parse out the relevant lines.
|
412
|
-
error = efile.each_line do |l|
|
413
|
-
break Regexp.last_match[1] if l.match(ERROR_LINE)
|
196
|
+
if disallowed_options.match(options)
|
197
|
+
failedopt = Regexp.last_match[0]
|
198
|
+
fail ArgumentError, "Option \"#{failedopt}\" is prohibited."
|
414
199
|
end
|
415
200
|
|
416
|
-
|
417
|
-
# Trying to parse such messages returns nil, and we use the error
|
418
|
-
# message from BLAST+ as it is.
|
419
|
-
error = efile.rewind && efile.read unless error.is_a? String
|
420
|
-
|
421
|
-
efile.close
|
422
|
-
raise ArgumentError, error
|
423
|
-
when 2, 3, 4, 255 # see [1]
|
424
|
-
efile.open
|
425
|
-
error = efile.read
|
426
|
-
efile.close
|
427
|
-
raise RuntimeError.new(status, error)
|
201
|
+
true
|
428
202
|
end
|
429
203
|
|
430
|
-
#
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
def pre_process(params)
|
435
|
-
unless params[:sequence].nil?
|
436
|
-
params[:sequence].strip!
|
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
|
437
208
|
end
|
438
|
-
end
|
439
|
-
|
440
|
-
def validate_blast_params(params)
|
441
|
-
validate_blast_method params[:method]
|
442
|
-
validate_blast_sequences params[:sequence]
|
443
|
-
validate_blast_databases params[:databases]
|
444
|
-
validate_blast_options params[:advanced]
|
445
|
-
end
|
446
|
-
|
447
|
-
def defaults
|
448
|
-
" -outfmt 5 -num_threads #{config[:num_threads]}"
|
449
|
-
end
|
450
|
-
|
451
|
-
def validate_blast_method(method)
|
452
|
-
return true if ALGORITHMS.include? method
|
453
|
-
raise ArgumentError, "BLAST algorithm should be one of:" \
|
454
|
-
" #{ALGORITHMS.join(', ')}."
|
455
|
-
end
|
456
209
|
|
457
|
-
|
458
|
-
|
459
|
-
raise ArgumentError, 'Sequences should be a non-empty string.'
|
460
|
-
end
|
461
|
-
|
462
|
-
def validate_blast_databases(database_ids)
|
463
|
-
ids = Database.ids
|
464
|
-
return true if database_ids.is_a?(Array) && !database_ids.empty? &&
|
465
|
-
(ids & database_ids).length == database_ids.length
|
466
|
-
raise ArgumentError, "Database id should be one of:" \
|
467
|
-
" #{ids.join("\n")}."
|
468
|
-
end
|
469
|
-
|
470
|
-
# Advanced options are specified by the user. Here they are checked for
|
471
|
-
# interference with SequenceServer operations.
|
472
|
-
# raise ArgumentError if an error has occurred, else return without value
|
473
|
-
def validate_blast_options(options)
|
474
|
-
return true if !options || (options.is_a?(String) && options.strip.empty?)
|
475
|
-
|
476
|
-
unless options =~ /\A[a-z0-9\-_\. ']*\Z/i
|
477
|
-
raise ArgumentError, 'Invalid characters detected in options.'
|
210
|
+
def allowed_chars
|
211
|
+
/\A[a-z0-9\-_\. ']*\Z/i
|
478
212
|
end
|
479
213
|
|
480
|
-
disallowed_options
|
481
|
-
|
482
|
-
if options =~ /#{o}/i
|
483
|
-
raise ArgumentError, "Option \"#{o}\" is prohibited."
|
484
|
-
end
|
214
|
+
def disallowed_options
|
215
|
+
/-out|-html|-outfmt|-db|-query/i
|
485
216
|
end
|
486
217
|
end
|
487
218
|
end
|