genevalidatorapp 1.4.13 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/Rakefile +5 -1
  4. data/bin/genevalidatorapp +227 -73
  5. data/config.ru +1 -1
  6. data/genevalidatorapp.gemspec +7 -6
  7. data/lib/genevalidatorapp.rb +247 -0
  8. data/lib/{GeneValidatorApp → genevalidatorapp}/config.rb +7 -6
  9. data/lib/{GeneValidatorApp → genevalidatorapp}/database.rb +8 -13
  10. data/lib/genevalidatorapp/exceptions.rb +162 -0
  11. data/lib/{GeneValidatorApp → genevalidatorapp}/genevalidator.rb +52 -58
  12. data/lib/{GeneValidatorApp → genevalidatorapp}/logger.rb +0 -0
  13. data/lib/genevalidatorapp/routes.rb +81 -0
  14. data/lib/genevalidatorapp/server.rb +63 -0
  15. data/lib/{GeneValidatorApp → genevalidatorapp}/version.rb +1 -1
  16. data/public/{web_files → src}/css/bootstrap1.min.css +0 -0
  17. data/public/{web_files → src}/css/custom.css +8 -13
  18. data/public/{web_files → src}/css/custom.min.css +0 -0
  19. data/public/{web_files → src}/css/font-awesome.min.css +0 -0
  20. data/public/{web_files → src}/js/bionode-seq.min.js +0 -0
  21. data/public/{web_files → src}/js/bootstrap.min.js +0 -0
  22. data/public/{web_files → src}/js/d3.v3.min.js +0 -0
  23. data/public/{web_files → src}/js/genevalidator.js +44 -49
  24. data/public/{web_files → src}/js/jquery.cookie.min.js +0 -0
  25. data/public/{web_files → src}/js/jquery.min.js +0 -0
  26. data/public/{web_files → src}/js/jquery.tablesorter.min.js +0 -0
  27. data/public/{web_files → src}/js/jquery.validate.min.js +0 -0
  28. data/public/src/js/plots.js +814 -0
  29. data/public/web_files/css/GV_compiled_css.min.css +15 -0
  30. data/public/web_files/js/GV_compiled_js.min.js +34 -0
  31. data/spec/app_spec.rb +1 -1
  32. data/spec/database_spec.rb +2 -2
  33. data/views/index.slim +1 -1
  34. data/views/layout.slim +15 -24
  35. data/views/results.slim +54 -0
  36. metadata +39 -35
  37. data/lib/GeneValidatorApp.rb +0 -321
  38. data/public/web_files/css/bootstrap.min.css +0 -7
  39. data/public/web_files/js/genevalidator.min.js +0 -1
  40. data/public/web_files/js/plots.js +0 -744
  41. data/public/web_files/js/plots.min.js +0 -1
@@ -36,6 +36,7 @@ module GeneValidatorApp
36
36
  # Write config data to config file.
37
37
  def write_config_file
38
38
  return unless config_file
39
+
39
40
  File.open(config_file, 'w') do |f|
40
41
  f.puts(data.delete_if { |_, v| v.nil? }.to_yaml)
41
42
  end
@@ -71,10 +72,11 @@ module GeneValidatorApp
71
72
  # Default configuration data.
72
73
  def defaults
73
74
  {
74
- :num_threads => 1,
75
- :port => 4567,
76
- :host => '0.0.0.0',
77
- :web_dir => Dir.pwd
75
+ :num_threads => 1,
76
+ :port => 4567,
77
+ :host => '0.0.0.0',
78
+ :gv_app_dir => '~/.genevalidatorapp/',
79
+ :max_characters => 'undefined'
78
80
  }
79
81
  end
80
82
 
@@ -82,5 +84,4 @@ module GeneValidatorApp
82
84
  '~/.genevalidatorapp.conf'
83
85
  end
84
86
  end
85
-
86
- end
87
+ end
@@ -3,15 +3,7 @@ require 'digest/md5'
3
3
  require 'forwardable'
4
4
 
5
5
  module GeneValidatorApp
6
- # Captures a directory containing FASTA files and BLAST databases.
7
- #
8
- # It is important that formatted BLAST database files have the same dirname and
9
- # basename as the source FASTA for GeneValidatorApp to be able to tell formatted
10
- # FASTA from unformatted. And that FASTA files be formatted with `parse_seqids`
11
- # option of `makeblastdb` for sequence retrieval to work.
12
- #
13
- # GeneValidatorApp will always place BLAST database files alongside input FASTA,
14
- # and use `parse_seqids` option of `makeblastdb` to format databases.
6
+ # class on the BLAST databases
15
7
  class Database < Struct.new(:name, :title, :type)
16
8
  class << self
17
9
  extend Forwardable
@@ -77,16 +69,19 @@ module GeneValidatorApp
77
69
  # Recurisvely scan `database_dir` for blast databases.
78
70
  def scan_databases_dir
79
71
  database_dir = config[:database_dir]
80
- list = `blastdbcmd -recursive -list #{database_dir} -list_outfmt "%p %f %t" 2>&1`
72
+ cmd = "blastdbcmd -recursive -list #{database_dir} -list_outfmt" \
73
+ ' "%p::%f::%t"'
74
+ list = `#{cmd} 2>&1`
81
75
  list.each_line do |line|
82
- type, name, title = line.split(' ')
76
+ type, name, title = line.split('::', 3)
83
77
  next if multipart_database_name?(name)
84
- next unless type.downcase == 'protein' # to ensure we only have protein dbs
78
+ next unless type.downcase == 'protein'
85
79
  self << Database.new(name, title, type)
86
80
  end
87
81
  end
88
82
 
89
- # Returns true if the database name appears to be a multi-part database name.
83
+ # Returns true if the database name appears to be a multi-part database
84
+ # name.
90
85
  #
91
86
  # e.g.
92
87
  # /home/ben/pd.ben/sequenceserver/db/nr.00 => yes
@@ -0,0 +1,162 @@
1
+ # This file defines all possible exceptions that can be thrown by
2
+ # GeneValidatorApp on startup.
3
+ #
4
+ # Exceptions only ever inform another entity (downstream code or users) of an
5
+ # issue. Exceptions may or may not be recoverable.
6
+ #
7
+ # Error classes should be seen as: the error code (class name), human readable
8
+ # message (to_s method), and necessary attributes to act on the error.
9
+ #
10
+ # We define as many error classes as needed to be precise about the issue, thus
11
+ # making it easy for downstream code (bin/genevalidatorapp or config.ru) to act
12
+ # on them.
13
+
14
+ module GeneValidatorApp
15
+ # Error in config file.
16
+ class CONFIG_FILE_ERROR < StandardError
17
+ def initialize(ent, err)
18
+ @ent = ent
19
+ @err = err
20
+ end
21
+
22
+ attr_reader :ent, :err
23
+
24
+ def to_s
25
+ <<MSG
26
+ Error reading config file: #{ent}.
27
+ #{err}
28
+ MSG
29
+ end
30
+ end
31
+
32
+ ## ENOENT ##
33
+
34
+ # Name borrowed from standard Errno::ENOENT, this class serves as a template
35
+ # for defining errors that mean "expected to find <entity> at <path>, but
36
+ # didn't".
37
+ #
38
+ # ENOENT is raised if and only if an entity was set, either using CLI or
39
+ # config file. For instance, it's compulsory to set database_dir. But ENOENT
40
+ # is not raised if database_dir is not set. ENOENT is raised if database_dir
41
+ # was set, but does not exist.
42
+ class ENOENT < StandardError
43
+ def initialize(des, ent)
44
+ @des = des
45
+ @ent = ent
46
+ end
47
+
48
+ attr_reader :des, :ent
49
+
50
+ def to_s
51
+ "Could not find #{des}: #{ent}"
52
+ end
53
+ end
54
+
55
+ # Raised if bin dir set, but does not exist.
56
+ class BIN_DIR_NOT_FOUND < ENOENT
57
+ def initialize(ent)
58
+ super 'bin dir', ent
59
+ end
60
+ end
61
+
62
+ # Raised if database dir set, but does not exist.
63
+ class DATABASE_DIR_NOT_FOUND < ENOENT
64
+ def initialize(ent)
65
+ super 'database dir', ent
66
+ end
67
+ end
68
+
69
+ # Raised if extension file set, but does not exist.
70
+ class EXTENSION_FILE_NOT_FOUND < ENOENT
71
+ def initialize(ent)
72
+ super 'extension file', ent
73
+ end
74
+ end
75
+
76
+ ## NUM THREADS ##
77
+
78
+ # Raised if num_threads set by the user is incorrect.
79
+ class NUM_THREADS_INCORRECT < StandardError
80
+ def to_s
81
+ 'Number of threads should be a number greater than or equal to 1.'
82
+ end
83
+ end
84
+
85
+ ## BLAST NOT INSTALLED OR NOT COMPATIBLE ##
86
+
87
+ # Raised if SequenceServer could not locate NCBI BLAST+ installation on
88
+ # user's system.
89
+ class BLAST_NOT_INSTALLED < StandardError
90
+ def to_s
91
+ 'Could not locate BLAST+ binaries.'
92
+ end
93
+ end
94
+
95
+ # Raised if SequenceServer could not successfully execute 'blastp -version'
96
+ # on user's system (see #141).
97
+ class BLAST_NOT_EXECUTABLE < StandardError
98
+ def to_s
99
+ 'Error executing BLAST+ binaries.'
100
+ end
101
+ end
102
+
103
+ # Raised if SequenceServer determined NCBI BLAST+ present on the user's
104
+ # system but not meeting SequenceServer's minimum version requirement.
105
+ class BLAST_NOT_COMPATIBLE < StandardError
106
+ def initialize(version)
107
+ @version = version
108
+ end
109
+
110
+ attr_reader :version
111
+
112
+ def to_s
113
+ <<MSG
114
+ Your BLAST+ version #{version} is outdated.
115
+ SequenceServer needs NCBI BLAST+ version #{MINIMUM_BLAST_VERSION} or higher.
116
+ MSG
117
+ end
118
+ end
119
+
120
+ ## BLAST+ DATABASE RELATED ##
121
+
122
+ # Raised if 'database_dir' not set.
123
+ class DATABASE_DIR_NOT_SET < StandardError
124
+ def to_s
125
+ 'Database dir not set.'
126
+ end
127
+ end
128
+
129
+ # Raised if not even one BLAST+ database was found in database_dir.
130
+ class NO_BLAST_DATABASE_FOUND < StandardError
131
+ def initialize(database_dir)
132
+ @database_dir = database_dir
133
+ end
134
+
135
+ attr_reader :database_dir
136
+
137
+ def to_s
138
+ "Could not find BLAST+ databases in: #{database_dir}."
139
+ end
140
+ end
141
+
142
+ # Raised if there was an error determining BLAST+ databases in database_dir.
143
+ class BLAST_DATABASE_ERROR < StandardError
144
+ def initialize(cmd, out)
145
+ @cmd = cmd
146
+ @out = out
147
+ end
148
+
149
+ attr_reader :cmd, :out
150
+
151
+ def to_s
152
+ <<MSG
153
+ Error obtaining BLAST databases.
154
+ Tried: #{cmd}
155
+ Error:
156
+ #{out.strip}
157
+
158
+ Please could you report this to 'https://groups.google.com/forum/#!forum/sequenceserver'?
159
+ MSG
160
+ end
161
+ end
162
+ end
@@ -2,6 +2,7 @@ require 'forwardable'
2
2
  require 'bio'
3
3
  require 'fileutils'
4
4
  require 'genevalidator'
5
+ require 'json'
5
6
 
6
7
  module GeneValidatorApp
7
8
  # Module that runs GeneValidator
@@ -24,7 +25,7 @@ module GeneValidatorApp
24
25
  class << self
25
26
  extend Forwardable
26
27
 
27
- def_delegators GeneValidatorApp, :config, :logger
28
+ def_delegators GeneValidatorApp, :config, :logger, :public_dir
28
29
 
29
30
  attr_reader :gv_dir, :tmp_gv_dir, :input_file, :xml_file, :raw_seq,
30
31
  :unique_id, :params
@@ -33,7 +34,7 @@ module GeneValidatorApp
33
34
  def init(url, params)
34
35
  create_unique_id
35
36
  create_subdir_in_main_tmpdir
36
- create_soft_link_from_tmpdir_to_GV_dir
37
+ create_soft_link_from_tmpdir_to_gv_dir
37
38
  @params = params
38
39
  validate_params
39
40
  obtain_db_path
@@ -45,7 +46,9 @@ module GeneValidatorApp
45
46
  def run
46
47
  write_seq_to_file
47
48
  run_genevalidator
48
- (@params[:result_link]) ? @url : produce_table_html
49
+ copy_json_folder
50
+ (@params[:result_link]) ? @url : output_json_file_path
51
+ parse_output_json
49
52
  end
50
53
 
51
54
  private
@@ -53,7 +56,7 @@ module GeneValidatorApp
53
56
  # Creates a unique run ID (based on time),
54
57
  def create_unique_id
55
58
  @unique_id = Time.new.strftime('%Y-%m-%d_%H-%M-%S_%L-%N')
56
- @gv_tmpdir = GeneValidatorApp.tempdir + unique_id
59
+ @gv_tmpdir = File.join(GeneValidatorApp.temp_dir, unique_id)
57
60
  ensure_unique_id
58
61
  end
59
62
 
@@ -62,7 +65,7 @@ module GeneValidatorApp
62
65
  def ensure_unique_id
63
66
  while File.exist?(@gv_tmpdir)
64
67
  @unique_id = create_unique_id
65
- @gv_tmpdir = GeneValidatorApp.tempdir + @unique_id
68
+ @gv_tmpdir = File.join(GeneValidatorApp.temp_dir, unique_id)
66
69
  end
67
70
  logger.debug("Unique ID = #{@unique_id}")
68
71
  end
@@ -74,8 +77,9 @@ module GeneValidatorApp
74
77
  end
75
78
 
76
79
  # Create the Tmp Dir and the create a soft link to it.
77
- def create_soft_link_from_tmpdir_to_GV_dir
78
- @gv_dir = GeneValidatorApp.public_dir + 'GeneValidator' + @unique_id
80
+ def create_soft_link_from_tmpdir_to_gv_dir
81
+ @gv_dir = File.join(GeneValidatorApp.public_dir, 'GeneValidator',
82
+ @unique_id)
79
83
  logger.debug("Local GV dir = #{@gv_dir}")
80
84
  FileUtils.ln_s "#{@gv_tmpdir}", "#{@gv_dir}"
81
85
  end
@@ -84,6 +88,7 @@ module GeneValidatorApp
84
88
  # Only important if POST request is sent via API - Web APP also validates
85
89
  # all params via Javascript.
86
90
  def validate_params
91
+ logger.debug("Input Paramaters: #{@params}")
87
92
  check_seq_param_present
88
93
  check_seq_length
89
94
  check_validations_param_present
@@ -92,23 +97,20 @@ module GeneValidatorApp
92
97
 
93
98
  # Simply asserts whether that the seq param is present
94
99
  def check_seq_param_present
95
- unless @params[:seq]
96
- fail ArgumentError, 'No input sequence provided.'
97
- end
100
+ return if @params[:seq]
101
+ fail ArgumentError, 'No input sequence provided.'
98
102
  end
99
103
 
100
104
  def check_seq_length
101
- return unless config[:max_characters]
102
- unless @params[:seq].length < config[:max_characters]
103
- fail ArgumentError, 'The input sequence is too long.'
104
- end
105
+ return unless config[:max_characters] != 'undefined'
106
+ return if @params[:seq].length < config[:max_characters]
107
+ fail ArgumentError, 'The input sequence is too long.'
105
108
  end
106
109
 
107
110
  # Asserts whether the validations param are specified
108
111
  def check_validations_param_present
109
- unless @params[:validations]
110
- fail ArgumentError, 'No validations specified'
111
- end
112
+ return if @params[:validations]
113
+ fail ArgumentError, 'No validations specified'
112
114
  end
113
115
 
114
116
  # Asserts whether the database parameter is present
@@ -124,7 +126,7 @@ module GeneValidatorApp
124
126
 
125
127
  # Writes the input sequences to a file with the sub_dir in the temp_dir
126
128
  def write_seq_to_file
127
- @input_fasta_file = @gv_tmpdir + 'input_file.fa'
129
+ @input_fasta_file = File.join(@gv_tmpdir, 'input_file.fa')
128
130
  logger.debug("Writing input seqs to: '#{@input_fasta_file}'")
129
131
  ensure_unix_line_ending
130
132
  ensure_fasta_valid
@@ -145,7 +147,8 @@ module GeneValidatorApp
145
147
  unique_queries = {}
146
148
  sequence = @params[:seq].lstrip
147
149
  if sequence[0] != '>'
148
- sequence.insert(0, ">Submitted:#{Time.now.strftime('%H:%M-%B_%d_%Y')}\n")
150
+ sequence.insert(0, '>Submitted:'\
151
+ "#{Time.now.strftime('%H:%M-%B_%d_%Y')}\n")
149
152
  end
150
153
  sequence.gsub!(/^\>(\S+)/) do |s|
151
154
  if unique_queries.key?(s)
@@ -163,8 +166,7 @@ module GeneValidatorApp
163
166
  # empty
164
167
  def assert_input_file_present
165
168
  unless File.exist?(@input_fasta_file) || File.zero?(@input_fasta_file)
166
- fail RuntimeError, 'GeneValidatorApp was unable to create the input' \
167
- ' file.'
169
+ fail 'GeneValidatorApp was unable to create the input file.'
168
170
  end
169
171
  end
170
172
 
@@ -180,62 +182,54 @@ module GeneValidatorApp
180
182
 
181
183
  # Runs GeneValidator
182
184
  def run_genevalidator
183
- opts = set_up_gv_opts
184
- logger.debug("Running GeneValidator with options: #{opts}")
185
185
  create_gv_log_file
186
186
  run_gv
187
- assert_table_output_file_produced
187
+ assert_json_output_file_produced
188
188
  rescue SystemExit
189
- raise RuntimeError, 'GeneValidator failed to run properly'
189
+ raise 'GeneValidator failed to run properly'
190
190
  end
191
191
 
192
192
  def run_gv
193
- original_stdout = $stdout.clone unless logger.debug?
194
- $stdout.reopen(@gv_log_file, 'w') unless logger.debug?
195
- (GeneValidator::Validation.new(opts, 1, true, true)).run
196
- $stdout = original_stdout unless logger.debug?
197
- end
198
-
199
- def set_up_gv_opts
200
- {
201
- validations: @params[:validations],
202
- db: @db,
203
- num_threads: config[:num_threads],
204
- fast: true,
205
- input_fasta_file: @input_fasta_file.to_s
206
- }
193
+ cmd = "genevalidator -v '#{@params[:validations].join(', ')}'" \
194
+ " -d #{@db} -n #{config[:num_threads]} #{@input_fasta_file}"
195
+ logger.debug("GV command: $ #{cmd}")
196
+ log_file = (logger.debug?) ? '' : "> #{@gv_log_file} 2>&1"
197
+ `#{cmd} #{log_file}`
207
198
  end
208
199
 
209
200
  def create_gv_log_file
210
- @gv_log_file = (@gv_tmpdir + 'log_file.txt').to_s
201
+ @gv_log_file = File.join(@gv_tmpdir, 'log_file.txt')
211
202
  logger.debug("Log file: #{@gv_log_file}")
212
203
  end
213
204
 
214
205
  # Assets whether the results file is produced by GeneValidator.
215
- def assert_table_output_file_produced
216
- @table_file = @gv_dir + 'input_file.fa.html/files/table.html'
217
- unless File.exist?(@table_file)
218
- fail RuntimeError, 'GeneValidator did not produce the required' \
219
- ' output file.'
220
- end
221
- end
222
-
223
- # Reads the GV output table file.
224
- # Updates links to the plots with relative links to plot jsons.
225
- def produce_table_html
226
- orig_plots_dir = 'files/json/input_file.fa_'
227
- local_plots_dir = Pathname.new('GeneValidator') + @unique_id +
228
- 'input_file.fa.html/files/json/input_file.fa_'
229
- full_html = IO.binread(@table_file)
230
- full_html.gsub(/#{orig_plots_dir}/, local_plots_dir.to_s).gsub(
231
- '#Place_external_results_link_here', @url)
206
+ def assert_json_output_file_produced
207
+ @json_file = File.join(@gv_dir, 'input_file.fa.json')
208
+ return if File.exist?(@json_file)
209
+ fail 'GeneValidator did not produce the required output file.'
232
210
  end
233
211
 
234
212
  # Reuturns the URL of the results page.
235
213
  def produce_result_url_link(url)
236
- url.gsub(/input/, '').gsub(/\/*$/, '') +
214
+ url.gsub(/input/, '').gsub(%r{/*$}, '') +
237
215
  "/GeneValidator/#{@unique_id}/input_file.fa.html/results.html"
238
216
  end
217
+
218
+ def parse_output_json
219
+ json_contents = File.read(output_json_file_path)
220
+ JSON.parse(json_contents)
221
+ end
222
+
223
+ def output_json_file_path
224
+ "#{@input_fasta_file}.json"
225
+ end
226
+
227
+ def copy_json_folder
228
+ json_dir = File.join("#{@input_fasta_file}.html", 'files/json')
229
+ web_dir_json = File.join(public_dir, 'web_files/json')
230
+ logger.debug("Moving JSON files from #{json_dir} to #{web_dir_json}")
231
+ FileUtils.cp_r(json_dir, web_dir_json)
232
+ end
239
233
  end
240
234
  end
241
235
  end