Module: SequenceServer::BLAST

Extended by:
Forwardable
Defined in:
lib/sequenceserver/blast.rb,
lib/sequenceserver/blast/hit.rb,
lib/sequenceserver/blast/hsp.rb,
lib/sequenceserver/blast/query.rb,
lib/sequenceserver/blast/report.rb,
lib/sequenceserver/blast/constants.rb,
lib/sequenceserver/blast/formatter.rb,
lib/sequenceserver/blast/exceptions.rb

Overview

Define constanst used by BLAST module.

Defined Under Namespace

Classes: ArgumentError, Formatter, HSP, Hit, Query, Report, RuntimeError

Constant Summary collapse

ERROR_LINE =
/\(CArgException.*\)\s(.*)/
ALGORITHMS =
%w(blastn blastp blastx tblastn tblastx)
OUTFMT_SPECIFIERS =
%w(qseqid qgi qacc sseqid sallseqid sgi sallgi sacc
sallacc qstart qend sstart send qseq sseq evalue
bitscore score length length pident nident
mismatch positive gapopen gaps ppos frames
qframe hframe btop staxids sscinames scomnames
sblastnames sskingdoms stitle salltitles sstrand
qcovs qcovhsp).join(' ')
OUTFMT =
{
  'pairwise'        => [0, :txt],
  'qa'              => [1, :txt],
  'qa_no_identity'  => [2, :txt],
  'fqa'             => [3, :txt],
  'fqa_no_identity' => [4, :txt],
  'xml'             => [5, :xml],
  'std_tsv'         => [7, :tsv],
  'full_tsv'        => [7, :tsv, OUTFMT_SPECIFIERS],
  'asn_text'        => [8, :asn],
  'asn_binary'      => [9, :asn],
  'csv'             => [10, :csv],
  'archive'         => [11, :txt]
}

Class Method Summary collapse

Class Method Details

.allowed_charsObject


155
156
157
# File 'lib/sequenceserver/blast.rb', line 155

def allowed_chars
  /\A[a-z0-9\-_\. ']*\Z/i
end

.defaultsObject


112
113
114
# File 'lib/sequenceserver/blast.rb', line 112

def defaults
  " -outfmt 11 -num_threads #{config[:num_threads]}"
end

.disallowed_optionsObject


159
160
161
# File 'lib/sequenceserver/blast.rb', line 159

def disallowed_options
  /-out|-html|-outfmt|-db|-query/i
end

.pre_process(params) ⇒ Object

rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity rubocop:enable Metrics/MethodLength


101
102
103
# File 'lib/sequenceserver/blast.rb', line 101

def pre_process(params)
  params[:sequence].strip! unless params[:sequence].nil?
end

.run(params) ⇒ Object

rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity rubocop:disable Metrics/MethodLength


28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/sequenceserver/blast.rb', line 28

def run(params)
  pre_process params
  validate_blast_params params

  # Compile parameters for BLAST search into a shell executable command.
  #
  # BLAST method to use.
  method  = params[:method]
  #
  # BLAST+ expects query sequence as a file.
  qfile = Tempfile.new('sequenceserver_query')
  qfile.puts(params[:sequence])
  qfile.close
  #
  # Retrieve database objects from database id.
  databases = Database[params[:databases]]
  #
  # Concatenate other blast options.
  options = params[:advanced].to_s.strip + defaults
  #
  # blastn implies blastn, not megablast; but let's not interfere if a
  # user specifies `task` herself.
  options << ' -task blastn' if method == 'blastn' && !(options =~ /task/)

  # Run BLAST search.
  #
  # Command to execute.
  command = "#{method} -db '#{databases.map(&:name).join(' ')}'" \
            " -query '#{qfile.path}' #{options}"
  #
  # Debugging log.
  logger.debug("Executing: #{command}")
  #
  # Temporary files to capture stdout and stderr.
  rfile = Tempfile.new('sequenceserver_blast_result')
  efile = Tempfile.new('sequenceserver_blast_error')
  [rfile, efile].each(&:close)
  #
  # Execute.
  system("#{command} > #{rfile.path} 2> #{efile.path}")

  # Capture error.
  status = $CHILD_STATUS.exitstatus
  case status
  when 1 # error in query sequence or options; see [1]
    efile.open

    # Most of the time BLAST+ generates a verbose error message with
    # details we don't require.  So we parse out the relevant lines.
    error = efile.each_line do |l|
      break Regexp.last_match[1] if l.match(ERROR_LINE)
    end

    # But sometimes BLAST+ returns the exact/relevant error message.
    # Trying to parse such messages returns nil, and we use the error
    # message from BLAST+ as it is.
    error = efile.rewind && efile.read unless error.is_a? String

    efile.close
    fail ArgumentError, error
  when 2, 3, 4, 255 # see [1]
    efile.open
    error = efile.read
    efile.close
    fail RuntimeError.new(status, error)
  end

  Search << rfile
  Report.new(File.basename(rfile.path), databases)
end

.validate_blast_databases(database_ids) ⇒ Object


127
128
129
130
131
132
133
# File 'lib/sequenceserver/blast.rb', line 127

def validate_blast_databases(database_ids)
  ids = Database.ids
  return true if database_ids.is_a?(Array) && !database_ids.empty? &&
                 (ids & database_ids).length == database_ids.length
  fail ArgumentError, 'Database id should be one of:' \
                      " #{ids.join("\n")}."
end

.validate_blast_method(method) ⇒ Object


116
117
118
119
120
# File 'lib/sequenceserver/blast.rb', line 116

def validate_blast_method(method)
  return true if ALGORITHMS.include? method
  fail ArgumentError, 'BLAST algorithm should be one of:' \
                      " #{ALGORITHMS.join(', ')}."
end

.validate_blast_options(options) ⇒ Object

Advanced options are specified by the user. Here they are checked for interference with SequenceServer operations.

Raise ArgumentError if an error has occurred.


139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/sequenceserver/blast.rb', line 139

def validate_blast_options(options)
  return true if !options || (options.is_a?(String) &&
                              options.strip.empty?)

  unless allowed_chars.match(options)
    fail ArgumentError, 'Invalid characters detected in options.'
  end

  if disallowed_options.match(options)
    failedopt = Regexp.last_match[0]
    fail ArgumentError, "Option \"#{failedopt}\" is prohibited."
  end

  true
end

.validate_blast_params(params) ⇒ Object


105
106
107
108
109
110
# File 'lib/sequenceserver/blast.rb', line 105

def validate_blast_params(params)
  validate_blast_method params[:method]
  validate_blast_sequences params[:sequence]
  validate_blast_databases params[:databases]
  validate_blast_options params[:advanced]
end

.validate_blast_sequences(sequences) ⇒ Object


122
123
124
125
# File 'lib/sequenceserver/blast.rb', line 122

def validate_blast_sequences(sequences)
  return true if sequences.is_a?(String) && !sequences.empty?
  fail ArgumentError, 'Sequences should be a non-empty string.'
end