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.

Files changed (213) hide show
  1. checksums.yaml +4 -4
  2. data/{README.txt → README.md} +2 -0
  3. data/bin/sequenceserver +255 -55
  4. data/config.ru +2 -4
  5. data/lib/sequenceserver.rb +293 -447
  6. data/lib/sequenceserver/blast.rb +464 -64
  7. data/lib/sequenceserver/database.rb +185 -19
  8. data/lib/sequenceserver/links.rb +114 -0
  9. data/lib/sequenceserver/logger.rb +27 -0
  10. data/lib/sequenceserver/sequence.rb +141 -0
  11. data/public/css/bootstrap.min.css +8 -413
  12. data/public/css/custom.css +363 -122
  13. data/public/css/font-awesome.min.css +4 -0
  14. data/public/fonts/FontAwesome.otf +0 -0
  15. data/public/fonts/fontawesome-webfont.eot +0 -0
  16. data/public/fonts/fontawesome-webfont.svg +565 -0
  17. data/public/fonts/fontawesome-webfont.ttf +0 -0
  18. data/public/fonts/fontawesome-webfont.woff +0 -0
  19. data/public/fonts/fontawesome-webfont.woff2 +0 -0
  20. data/public/js/bootstrap.min.js +11 -0
  21. data/public/js/d3.v3.min.js +5 -0
  22. data/public/js/html5shiv.min.js +4 -0
  23. data/public/js/jquery.scrollspy.js +74 -0
  24. data/public/js/jquery.t.js +353 -0
  25. data/public/js/sequence.js +2419 -0
  26. data/public/js/sequenceserver.blast.js +29 -30
  27. data/public/js/sequenceserver.js +544 -120
  28. data/public/js/underscore.min.js +6 -0
  29. data/public/js/webshims/polyfiller.js +1 -0
  30. data/public/js/webshims/shims/FlashCanvas/canvas2png.js +1 -0
  31. data/public/js/webshims/shims/FlashCanvas/flashcanvas.js +1 -0
  32. data/public/js/webshims/shims/FlashCanvas/flashcanvas.swf +0 -0
  33. data/public/js/webshims/shims/FlashCanvasPro/canvas2png.js +1 -0
  34. data/public/js/webshims/shims/FlashCanvasPro/flash10canvas.swf +0 -0
  35. data/public/js/webshims/shims/FlashCanvasPro/flash9canvas.swf +0 -0
  36. data/public/js/webshims/shims/FlashCanvasPro/flashcanvas.js +1 -0
  37. data/public/js/webshims/shims/canvas-blob.js +1 -0
  38. data/public/js/webshims/shims/color-picker.js +2 -0
  39. data/public/js/webshims/shims/combos/1.js +6 -0
  40. data/public/js/webshims/shims/combos/10.js +2 -0
  41. data/public/js/webshims/shims/combos/11.js +2 -0
  42. data/public/js/webshims/shims/combos/12.js +6 -0
  43. data/public/js/webshims/shims/combos/13.js +1 -0
  44. data/public/js/webshims/shims/combos/14.js +1 -0
  45. data/public/js/webshims/shims/combos/15.js +2 -0
  46. data/public/js/webshims/shims/combos/16.js +7 -0
  47. data/public/js/webshims/shims/combos/17.js +2 -0
  48. data/public/js/webshims/shims/combos/18.js +3 -0
  49. data/public/js/webshims/shims/combos/2.js +7 -0
  50. data/public/js/webshims/shims/combos/21.js +2 -0
  51. data/public/js/webshims/shims/combos/22.js +1 -0
  52. data/public/js/webshims/shims/combos/23.js +6 -0
  53. data/public/js/webshims/shims/combos/25.js +2 -0
  54. data/public/js/webshims/shims/combos/27.js +1 -0
  55. data/public/js/webshims/shims/combos/28.js +1 -0
  56. data/public/js/webshims/shims/combos/29.js +1 -0
  57. data/public/js/webshims/shims/combos/3.js +1 -0
  58. data/public/js/webshims/shims/combos/30.js +2 -0
  59. data/public/js/webshims/shims/combos/31.js +1 -0
  60. data/public/js/webshims/shims/combos/33.js +1 -0
  61. data/public/js/webshims/shims/combos/34.js +1 -0
  62. data/public/js/webshims/shims/combos/4.js +1 -0
  63. data/public/js/webshims/shims/combos/5.js +2 -0
  64. data/public/js/webshims/shims/combos/6.js +2 -0
  65. data/public/js/webshims/shims/combos/7.js +7 -0
  66. data/public/js/webshims/shims/combos/8.js +7 -0
  67. data/public/js/webshims/shims/combos/9.js +2 -0
  68. data/public/js/webshims/shims/combos/97.js +1 -0
  69. data/public/js/webshims/shims/combos/98.js +1 -0
  70. data/public/js/webshims/shims/combos/99.js +1 -0
  71. data/public/js/webshims/shims/details.js +1 -0
  72. data/public/js/webshims/shims/dom-extend.js +1 -0
  73. data/public/js/webshims/shims/es5.js +1 -0
  74. data/public/js/webshims/shims/es6.js +1 -0
  75. data/public/js/webshims/shims/excanvas.js +1 -0
  76. data/public/js/webshims/shims/filereader-xhr.js +1 -0
  77. data/public/js/webshims/shims/form-combat.js +1 -0
  78. data/public/js/webshims/shims/form-core.js +1 -0
  79. data/public/js/webshims/shims/form-datalist-lazy.js +1 -0
  80. data/public/js/webshims/shims/form-datalist.js +1 -0
  81. data/public/js/webshims/shims/form-fixrangechange.js +1 -0
  82. data/public/js/webshims/shims/form-inputmode.js +1 -0
  83. data/public/js/webshims/shims/form-message.js +1 -0
  84. data/public/js/webshims/shims/form-native-extend.js +1 -0
  85. data/public/js/webshims/shims/form-number-date-api.js +1 -0
  86. data/public/js/webshims/shims/form-number-date-ui.js +1 -0
  87. data/public/js/webshims/shims/form-shim-extend.js +1 -0
  88. data/public/js/webshims/shims/form-shim-extend2.js +1 -0
  89. data/public/js/webshims/shims/form-validation.js +1 -0
  90. data/public/js/webshims/shims/form-validators.js +1 -0
  91. data/public/js/webshims/shims/forms-picker.js +1 -0
  92. data/public/js/webshims/shims/geolocation.js +1 -0
  93. data/public/js/webshims/shims/i18n/formcfg-ar.js +1 -0
  94. data/public/js/webshims/shims/i18n/formcfg-ch-CN.js +1 -0
  95. data/public/js/webshims/shims/i18n/formcfg-cs.js +1 -0
  96. data/public/js/webshims/shims/i18n/formcfg-de.js +1 -0
  97. data/public/js/webshims/shims/i18n/formcfg-el.js +1 -0
  98. data/public/js/webshims/shims/i18n/formcfg-en.js +1 -0
  99. data/public/js/webshims/shims/i18n/formcfg-es.js +1 -0
  100. data/public/js/webshims/shims/i18n/formcfg-fa.js +1 -0
  101. data/public/js/webshims/shims/i18n/formcfg-fr.js +1 -0
  102. data/public/js/webshims/shims/i18n/formcfg-he.js +1 -0
  103. data/public/js/webshims/shims/i18n/formcfg-hi.js +1 -0
  104. data/public/js/webshims/shims/i18n/formcfg-hu.js +1 -0
  105. data/public/js/webshims/shims/i18n/formcfg-it.js +1 -0
  106. data/public/js/webshims/shims/i18n/formcfg-ja.js +1 -0
  107. data/public/js/webshims/shims/i18n/formcfg-lt.js +1 -0
  108. data/public/js/webshims/shims/i18n/formcfg-nl.js +1 -0
  109. data/public/js/webshims/shims/i18n/formcfg-pl.js +1 -0
  110. data/public/js/webshims/shims/i18n/formcfg-pt-BR.js +1 -0
  111. data/public/js/webshims/shims/i18n/formcfg-pt-PT.js +1 -0
  112. data/public/js/webshims/shims/i18n/formcfg-pt.js +1 -0
  113. data/public/js/webshims/shims/i18n/formcfg-ru.js +1 -0
  114. data/public/js/webshims/shims/i18n/formcfg-sv.js +1 -0
  115. data/public/js/webshims/shims/i18n/formcfg-zh-CN.js +1 -0
  116. data/public/js/webshims/shims/i18n/formcfg-zh-TW.js +1 -0
  117. data/public/js/webshims/shims/jme/alternate-media.js +1 -0
  118. data/public/js/webshims/shims/jme/base.js +1 -0
  119. data/public/js/webshims/shims/jme/controls.css +1 -0
  120. data/public/js/webshims/shims/jme/jme.eot +0 -0
  121. data/public/js/webshims/shims/jme/jme.svg +36 -0
  122. data/public/js/webshims/shims/jme/jme.ttf +0 -0
  123. data/public/js/webshims/shims/jme/jme.woff +0 -0
  124. data/public/js/webshims/shims/jme/mediacontrols-lazy.js +1 -0
  125. data/public/js/webshims/shims/jme/mediacontrols.js +1 -0
  126. data/public/js/webshims/shims/jme/playlist.js +1 -0
  127. data/public/js/webshims/shims/jpicker/images/AlphaBar.png +0 -0
  128. data/public/js/webshims/shims/jpicker/images/Bars.png +0 -0
  129. data/public/js/webshims/shims/jpicker/images/Maps.png +0 -0
  130. data/public/js/webshims/shims/jpicker/images/NoColor.png +0 -0
  131. data/public/js/webshims/shims/jpicker/images/bar-opacity.png +0 -0
  132. data/public/js/webshims/shims/jpicker/images/map-opacity.png +0 -0
  133. data/public/js/webshims/shims/jpicker/images/mappoint.gif +0 -0
  134. data/public/js/webshims/shims/jpicker/images/picker.gif +0 -0
  135. data/public/js/webshims/shims/jpicker/images/preview-opacity.png +0 -0
  136. data/public/js/webshims/shims/jpicker/images/rangearrows.gif +0 -0
  137. data/public/js/webshims/shims/jpicker/jpicker.css +1 -0
  138. data/public/js/webshims/shims/matchMedia.js +3 -0
  139. data/public/js/webshims/shims/mediacapture-picker.js +1 -0
  140. data/public/js/webshims/shims/mediacapture.js +1 -0
  141. data/public/js/webshims/shims/mediaelement-core.js +1 -0
  142. data/public/js/webshims/shims/mediaelement-debug.js +1 -0
  143. data/public/js/webshims/shims/mediaelement-jaris.js +1 -0
  144. data/public/js/webshims/shims/mediaelement-native-fix.js +1 -0
  145. data/public/js/webshims/shims/mediaelement-yt.js +1 -0
  146. data/public/js/webshims/shims/moxie/flash/Moxie.cdn.swf +0 -0
  147. data/public/js/webshims/shims/moxie/flash/Moxie.min.swf +0 -0
  148. data/public/js/webshims/shims/moxie/js/moxie-html4.js +3 -0
  149. data/public/js/webshims/shims/moxie/js/moxie-swf.js +2 -0
  150. data/public/js/webshims/shims/picture.js +1 -0
  151. data/public/js/webshims/shims/plugins/jquery.ui.position.js +11 -0
  152. data/public/js/webshims/shims/range-ui.js +1 -0
  153. data/public/js/webshims/shims/sizzle.js +11 -0
  154. data/public/js/webshims/shims/sticky.js +1 -0
  155. data/public/js/webshims/shims/styles/color-picker.png +0 -0
  156. data/public/js/webshims/shims/styles/forms-ext.css +1 -0
  157. data/public/js/webshims/shims/styles/forms-picker.css +1 -0
  158. data/public/js/webshims/shims/styles/progress.gif +0 -0
  159. data/public/js/webshims/shims/styles/progress.png +0 -0
  160. data/public/js/webshims/shims/styles/shim-ext.css +1 -0
  161. data/public/js/webshims/shims/styles/shim.css +1 -0
  162. data/public/js/webshims/shims/styles/transparent.png +0 -0
  163. data/public/js/webshims/shims/styles/widget.eot +0 -0
  164. data/public/js/webshims/shims/styles/widget.svg +12 -0
  165. data/public/js/webshims/shims/styles/widget.ttf +0 -0
  166. data/public/js/webshims/shims/styles/widget.woff +0 -0
  167. data/public/js/webshims/shims/swf/JarisFLVPlayer.swf +0 -0
  168. data/public/js/webshims/shims/swfmini-embed.js +1 -0
  169. data/public/js/webshims/shims/swfmini.js +6 -0
  170. data/public/js/webshims/shims/track-ui.js +1 -0
  171. data/public/js/webshims/shims/track.js +1 -0
  172. data/public/js/webshims/shims/url.js +1 -0
  173. data/public/js/webshims/shims/usermedia-core.js +1 -0
  174. data/public/js/webshims/shims/usermedia-shim.js +1 -0
  175. data/sequenceserver.gemspec +16 -13
  176. data/views/400.erb +28 -0
  177. data/views/500.erb +35 -19
  178. data/views/_options.erb +6 -15
  179. data/views/result.erb +218 -0
  180. data/views/search.erb +354 -151
  181. metadata +254 -62
  182. data/example.config.yml +0 -39
  183. data/lib/sequenceserver/customisation.rb +0 -60
  184. data/lib/sequenceserver/database_formatter.rb +0 -190
  185. data/lib/sequenceserver/helpers.rb +0 -136
  186. data/lib/sequenceserver/sequencehelpers.rb +0 -93
  187. data/lib/sequenceserver/sinatralikeloggerformatter.rb +0 -12
  188. data/lib/sequenceserver/version.rb +0 -9
  189. data/public/css/beige.css.css +0 -254
  190. data/public/css/bootstrap.dropdown.css +0 -29
  191. data/public/css/bootstrap.icons.css +0 -155
  192. data/public/css/bootstrap.modal.css +0 -28
  193. data/public/js/bootstrap.dropdown.js +0 -92
  194. data/public/js/bootstrap.modal.js +0 -7
  195. data/public/js/bootstrap.transition.js +0 -7
  196. data/public/js/jquery-scrollspy.js +0 -98
  197. data/public/js/jquery.activity.js +0 -10
  198. data/public/js/jquery.enablePlaceholder.min.js +0 -10
  199. data/public/js/store.min.js +0 -2
  200. data/public/sequence.html +0 -28
  201. data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta +0 -5486
  202. data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta.nhr +0 -0
  203. data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta.nin +0 -0
  204. data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta.nsq +0 -0
  205. data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta +0 -6449
  206. data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta.phr +0 -0
  207. data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta.pin +0 -0
  208. data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta.psq +0 -0
  209. data/tests/run +0 -26
  210. data/tests/test_sequencehelpers.rb +0 -77
  211. data/tests/test_sequenceserver_blast.rb +0 -60
  212. data/tests/test_ui.rb +0 -104
  213. data/tests/ui.specs.todo +0 -10
@@ -1,29 +1,195 @@
1
+ require 'find'
1
2
  require 'digest/md5'
3
+ require 'forwardable'
4
+
5
+ require 'sequenceserver/sequence'
2
6
 
3
7
  module SequenceServer
4
- class Database < Struct.new("Database", :name, :title, :type)
5
- def to_s
6
- "#{type}: #{title} #{name}"
7
- end
8
8
 
9
- # Its not very meaningful to compare Database objects, however,
10
- # we still add the 'spaceship' operator to be able to sort the
11
- # databases by 'title', or 'name' for better visual presentation.
12
- #
13
- # We use 'title' for comparison, while relying on 'name' as fallback.
14
- #
15
- # Trying to sort a list of dbs with 'title' set only for some of them
16
- # will obviously produce unpredictable sorting order.
17
- def <=>(other)
18
- if self.title and other.title
19
- self.title <=> other.title
20
- else
21
- self.name <=> other.name
9
+ # Captures a directory containing FASTA files and BLAST databases.
10
+ #
11
+ # Formatting a FASTA for use with BLAST+ will create 3 or 6 files,
12
+ # collectively referred to as a BLAST database.
13
+ #
14
+ # It is important that formatted BLAST database files have the same dirname and
15
+ # basename as the source FASTA for SequenceServer to be able to tell formatted
16
+ # FASTA from unformatted. And that FASTA files be formatted with `parse_seqids`
17
+ # option of `makeblastdb` for sequence retrieval to work.
18
+ #
19
+ # SequenceServer will always place BLAST database files alongside input FASTA,
20
+ # and use `parse_seqids` option of `makeblastdb` to format databases.
21
+ class Database < Struct.new(:name, :title, :type, :nsequences, :ncharacters, :updated_on)
22
+
23
+ class << self
24
+
25
+ include Enumerable
26
+
27
+ extend Forwardable
28
+
29
+ def_delegators SequenceServer, :config, :logger
30
+
31
+ def collection
32
+ @collection ||= {}
33
+ end
34
+
35
+ private :collection
36
+
37
+ def <<(database)
38
+ collection[database.id] = database
39
+ end
40
+
41
+ def [](ids)
42
+ ids = Array ids
43
+ collection.values_at(*ids)
44
+ end
45
+
46
+ def ids
47
+ collection.keys
48
+ end
49
+
50
+ def all
51
+ collection.values
52
+ end
53
+
54
+ def each(&block)
55
+ all.each(&block)
56
+ end
57
+
58
+ def include?(path)
59
+ collection.include? Digest::MD5.hexdigest path
60
+ end
61
+
62
+ def group_by(&block)
63
+ all.group_by(&block)
64
+ end
65
+
66
+ # Intended to be used only for testing.
67
+ def first
68
+ all.first
69
+ end
70
+
71
+ # Recurisvely scan `database_dir` for blast databases.
72
+ def scan_databases_dir
73
+ database_dir = config[:database_dir]
74
+ list = %x|blastdbcmd -recursive -list #{database_dir} -list_outfmt "%f %t %p %n %l %d" 2>&1|
75
+ list.each_line do |line|
76
+ name = line.split(' ')[0]
77
+ next if multipart_database_name?(name)
78
+ self << Database.new(*line.split(' '))
79
+ end
80
+ end
81
+
82
+ # Recursively scan `database_dir` for un-formatted FASTA and format them
83
+ # for use with BLAST+.
84
+ def make_blast_databases
85
+ unformatted_fastas.each do |file, sequence_type|
86
+ make_blast_database(file, sequence_type)
87
+ end
88
+ end
89
+
90
+ # Returns an Array of FASTA files that may require formatting, and the type
91
+ # of sequence contained in each FASTA.
92
+ #
93
+ # > unformatted_fastas
94
+ # => [['/foo/bar.fasta', :nulceotide], ...]
95
+ def unformatted_fastas
96
+ list = []
97
+ database_dir = config[:database_dir]
98
+ Find.find database_dir do |file|
99
+ next if File.directory?(file)
100
+ next if Database.include? file
101
+ if probably_fasta? file
102
+ sequence_type = guess_sequence_type_in_fasta file
103
+ if [:protein, :nucleotide].include?(sequence_type)
104
+ list << [file, sequence_type]
105
+ end
106
+ end
107
+ end
108
+ list
109
+ end
110
+
111
+ # Create BLAST database, given FASTA file and sequence type in FASTA file.
112
+ def make_blast_database(file, type)
113
+ puts "FASTA file: #{file}"
114
+ puts "FASTA type: #{type}"
115
+
116
+ print "Proceed? [y/n] (Default: y): "
117
+ response = STDIN.gets.to_s.strip
118
+
119
+ unless response.match(/n/i)
120
+ default_title = make_db_title(File.basename(file))
121
+ print "Enter a database title or will use '#{default_title}': "
122
+ title = STDIN.gets.to_s
123
+ title = default_title if title.strip.empty?
124
+
125
+ `makeblastdb -parse_seqids -hash_index \
126
+ -in #{file} -dbtype #{type.to_s.slice(0,4)} -title "#{title}"`
127
+ end
128
+ end
129
+
130
+ # Returns true if the database name appears to be a multi-part database name.
131
+ #
132
+ # e.g.
133
+ # /home/ben/pd.ben/sequenceserver/db/nr.00 => yes
134
+ # /home/ben/pd.ben/sequenceserver/db/nr => no
135
+ # /home/ben/pd.ben/sequenceserver/db/img3.5.finished.faa.01 => yes
136
+ def multipart_database_name?(db_name)
137
+ !(db_name.match(/.+\/\S+\d{2}$/).nil?)
138
+ end
139
+
140
+ # Returns true if first character of the file is '>'.
141
+ def probably_fasta?(file)
142
+ File.read(file, 1) == '>'
143
+ end
144
+
145
+ # Suggests improved titles when generating database names from files
146
+ # for improved apperance and readability in web interface.
147
+ # For example:
148
+ # Cobs1.4.proteins.fasta -> Cobs 1.4 proteins
149
+ # S_invicta.xx.2.5.small.nucl.fa -> S invicta xx 2.5 small nucl
150
+ def make_db_title(db_name)
151
+ db_name.gsub!('"', "'")
152
+ # removes .fasta like extension names
153
+ db_name.gsub!(File.extname(db_name), '')
154
+ # replaces _ with ' ',
155
+ db_name.gsub!(/(_)/, ' ')
156
+ # replaces '.' with ' ' when no numbers are on either side,
157
+ db_name.gsub!(/(?<![0-9])\.(?![0-9])/, ' ')
158
+ # preserves version numbers
159
+ db_name.gsub!(/\W*(\d+([.-]\d+)+)\W*/, ' \1 ')
160
+ db_name
161
+ end
162
+
163
+ # Guess whether FASTA file contains protein or nucleotide sequences based
164
+ # on first 32768 characters.
165
+ #
166
+ # NOTE: 2^15 == 32786. Approximately 546 lines, assuming 60 characters on
167
+ # each line.
168
+ def guess_sequence_type_in_fasta(file)
169
+ sample = File.read(file, 32768)
170
+ sequences = sample.split(/^>.+$/).delete_if { |seq| seq.empty? }
171
+ sequence_types = sequences.map {|seq| Sequence.guess_type(seq)}.uniq.compact
172
+ (sequence_types.length == 1) && sequence_types.first
22
173
  end
23
174
  end
24
175
 
25
- def hash
26
- @hash ||= Digest::MD5.hexdigest(self.name)
176
+ def initialize(*args)
177
+ args[2].downcase! # database type
178
+ args.each(&:freeze)
179
+ super
180
+
181
+ @id = Digest::MD5.hexdigest args.first
182
+ end
183
+
184
+ attr_reader :id
185
+
186
+ def include?(accession)
187
+ out = `blastdbcmd -entry '#{accession}' -db #{name} 2> /dev/null`
188
+ not out.empty?
189
+ end
190
+
191
+ def to_s
192
+ "#{type}: #{title} #{name}"
27
193
  end
28
194
  end
29
195
  end
@@ -0,0 +1,114 @@
1
+ module SequenceServer
2
+ # Module to contain methods for generating sequence retrieval links.
3
+ module Links
4
+ require 'erb'
5
+
6
+ # Provide a method to URL encode _query parameters_. See [1].
7
+ include ERB::Util
8
+ #
9
+ alias encode url_encode
10
+
11
+ NCBI_ID_PATTERN = /gi\|(\d+)\|/
12
+
13
+ # Your custom method should have following pattern:
14
+ #
15
+ # Input
16
+ # -----
17
+ # sequence_id: Array of sequence ids
18
+ #
19
+ # Return
20
+ # ------
21
+ # The return value should be a Hash:
22
+ #
23
+ # {
24
+ # # Required. Display title.
25
+ # :title => "title",
26
+ #
27
+ # # Required. Generated url.
28
+ # :url => url,
29
+ #
30
+ # # Optional. Left-right order in which the link should appear.
31
+ # :order => num,
32
+ #
33
+ # # Optional. Classes, if any, to apply to the link.
34
+ # :class => "class1 class2",
35
+ #
36
+ # # Optional. Class name of a FontAwesome icon to use.
37
+ # :icon => "fa-icon-class"
38
+ # }
39
+ #
40
+ # If no url could be generated, return nil.
41
+ #
42
+ # Helper methods
43
+ # --------------
44
+ #
45
+ # Following helper methods are available to help with link generation.
46
+ #
47
+ # encode:
48
+ # URL encode query params.
49
+ #
50
+ # Don't use this function to encode the entire URL. Only params.
51
+ #
52
+ # e.g:
53
+ # sequence_id = encode sequence_id
54
+ # url = "http://www.ncbi.nlm.nih.gov/nucleotide/#{sequence_id}"
55
+ #
56
+ # querydb:
57
+ # Returns an array of databases that were used for BLASTing.
58
+ #
59
+ # which_blastdb:
60
+ # Returns the database from which the given hit came from.
61
+ #
62
+ # e.g:
63
+ #
64
+ # hit_database = which_blastdb sequence_id
65
+ #
66
+ # Examples:
67
+ # ---------
68
+ # See methods provided by default for an example implementation.
69
+
70
+ def sequence_viewer(sequence_id)
71
+ sequence_id = encode sequence_id
72
+ database_ids = encode querydb.map(&:id).join(' ')
73
+ url = "get_sequence/?sequence_ids=#{sequence_id}" \
74
+ "&database_ids=#{database_ids}"
75
+
76
+ {
77
+ :order => 0,
78
+ :url => url,
79
+ :title => 'Sequence',
80
+ :class => 'view-sequence',
81
+ :icon => 'fa-eye'
82
+ }
83
+ end
84
+
85
+ def fasta_download(sequence_id)
86
+ sequence_id = encode sequence_id
87
+ database_ids = encode querydb.map(&:id).join(' ')
88
+ url = "get_sequence/?sequence_ids=#{sequence_id}" \
89
+ "&database_ids=#{database_ids}&download=fasta"
90
+
91
+ {
92
+ :order => 1,
93
+ :title => 'FASTA',
94
+ :url => url,
95
+ :icon => 'fa-download'
96
+ }
97
+ end
98
+
99
+ def ncbi(sequence_id)
100
+ return nil unless sequence_id.match(NCBI_ID_PATTERN)
101
+ ncbi_id = Regexp.last_match[1]
102
+ ncbi_id = encode ncbi_id
103
+ url = "http://www.ncbi.nlm.nih.gov/#{querydb.first.typ}/#{ncbi_id}"
104
+ {
105
+ :order => 2,
106
+ :title => 'View on NCBI',
107
+ :url => url,
108
+ :icon => 'fa-external-link'
109
+ }
110
+ end
111
+ end
112
+ end
113
+
114
+ # [1]: https://stackoverflow.com/questions/2824126/whats-the-difference-between-uri-escape-and-cgi-escape
@@ -0,0 +1,27 @@
1
+ require 'logger'
2
+
3
+ module SequenceServer
4
+
5
+ class Logger < Logger
6
+
7
+ def initialize(dev, verbose = false)
8
+ super dev
9
+ self.level = verbose ? DEBUG : INFO
10
+ self.formatter = Formatter.new
11
+ end
12
+
13
+ # We change Logging format so that it is consistent with Sinatra's
14
+ class Formatter < Formatter
15
+
16
+ Format = "[%s] %s %s\n"
17
+
18
+ def initialize
19
+ self.datetime_format = "%Y-%m-%d %H:%M:%S"
20
+ end
21
+
22
+ def call(severity, time, progname, msg)
23
+ Format % [format_datetime(time), severity, msg2str(msg)]
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,141 @@
1
+ require 'forwardable'
2
+
3
+ module SequenceServer
4
+
5
+ # Provides simple sequence processing utilities via class methods. Instance
6
+ # of the class serves as a simple data object to captures sequences fetched
7
+ # from BLAST databases.
8
+ #
9
+ # NOTE:
10
+ # What all do we need to consistently construct FASTA from `blastdbcmd's`
11
+ # output?
12
+ #
13
+ # It would seem rather straightforward. But it's not.
14
+ #
15
+ # FASTA format:
16
+ #
17
+ # >defline
18
+ # actual sequence
19
+ #
20
+ # where,
21
+ #
22
+ # defline = >id title
23
+ #
24
+ # ID of a sequence fetched from nr database should look like this:
25
+ #
26
+ # sequence id -> self.seqid
27
+ # -------------
28
+ # accession -> self.accession
29
+ # ----------
30
+ # gi|322796550|gb|EFZ19024.1| -> self.id
31
+ # ---------
32
+ # gi number -> self.gi
33
+ #
34
+ # while for local databases, the id should be the exact same, as in the
35
+ # original FASTA file:
36
+ #
37
+ # SI2.2.0_06267 -> self.id == self.seqid == self.accession.
38
+ class Sequence < Struct.new(:gi, :seqid, :accession, :title, :value)
39
+
40
+ class << self
41
+
42
+ extend Forwardable
43
+
44
+ # Derive `logger` from SequenceServer module.
45
+ def_delegators SequenceServer, :logger
46
+
47
+ # Disable using `Sequence.new`. Should use `Sequence.from_blastdb`
48
+ # instead.
49
+ private :new
50
+
51
+ # Returns an Array of `Sequence` objects each capturing a sequence
52
+ # fetched from BLAST database.
53
+ def from_blastdb(accessions, database_ids)
54
+ accessions = Array accessions
55
+ database_ids = Array database_ids
56
+
57
+ accessions = accessions.join(',')
58
+ database_names = Database[database_ids].map(&:name).join(' ')
59
+
60
+ # Output of the command will be five columns TSV.
61
+ command = "blastdbcmd -outfmt '%g %i %a %t %s'" \
62
+ " -db '#{database_names}' -entry '#{accessions}'"
63
+
64
+ logger.debug("Executing: #{command}")
65
+
66
+ # Not interested in stderr.
67
+ `#{command} 2> /dev/null`.
68
+ each_line.map {|line| new(*line.chomp.split(' '))}
69
+ end
70
+
71
+ # Strips all non-letter characters. If less than 10 useable characters
72
+ # return `nil`. If at least 90% is ACGTU, returns `:nucleotide`, else
73
+ # `:protein`.
74
+ def guess_type(sequence)
75
+ # Clean the sequence: first remove non-letter characters, then
76
+ # ambiguous characters.
77
+ cleaned_sequence = sequence.gsub(/[^A-Z]/i, '').gsub(/[NX]/i, '')
78
+
79
+ return if cleaned_sequence.length < 10 # conservative
80
+
81
+ # Count putative NA in the sequence.
82
+ na_count = 0
83
+ composition = composition(cleaned_sequence)
84
+ composition.each do |character, count|
85
+ na_count = na_count + count if character.match(/[ACGTU]/i)
86
+ end
87
+
88
+ na_count > (0.9 * cleaned_sequence.length) ? :nucleotide : :protein
89
+ end
90
+
91
+ # Copied from BioRuby's `Bio::Sequence` class.
92
+ #
93
+ # > composition("asdfasdfffffasdf")
94
+ # => {"a"=>3, "d"=>3, "f"=>7, "s"=>3}
95
+ def composition(sequence_string)
96
+ count = Hash.new(0)
97
+ sequence_string.scan(/./) do |x|
98
+ count[x] += 1
99
+ end
100
+ count
101
+ end
102
+ end
103
+
104
+ def initialize(*args)
105
+ args[0] = nil if args[0] == 'N/A'
106
+ super
107
+ end
108
+
109
+ # Returns FASTA sequence id.
110
+ def id
111
+ (gi ? ['gi', gi, seqid] : [seqid]).join('|')
112
+ end
113
+
114
+ # Returns length of the sequence.
115
+ def length
116
+ value.length
117
+ end
118
+
119
+ # Returns sequence value.
120
+ def to_s
121
+ value
122
+ end
123
+
124
+ def info
125
+ {:value => value, :id => id, :title => title}
126
+ end
127
+
128
+ # Returns FASTA formatted sequence.
129
+ def fasta
130
+ chars = 60
131
+ lines = (length / chars.to_f).ceil
132
+ defline = ">#{id} #{title}"
133
+ seqlines = (1..lines).map {|i| to_s[chars * (i - 1), chars]}
134
+ [defline].concat(seqlines).join("\n")
135
+ end
136
+ end
137
+ end
138
+
139
+ # References
140
+ # ----------
141
+ # [1]: http://blast.ncbi.nlm.nih.gov/blastcgihelp.shtml