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.
- 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
|