-my ( $options, $in, $out, $record, @args, $arg, $type, $tmp_dir, $seq_file, $pat_file, $out_file,
- $fh_in, $fh_out, $patterns, $pattern, $entry, $result, %head_hash, $i );
-
-$options = Maasha::Biopieces::parse_options(
- [
- { long => 'patterns', short => 'p', type => 'string', mandatory => 'no', default => undef, allowed => undef, disallowed => undef },
- { long => 'patterns_in', short => 'P', type => 'file!', mandatory => 'no', default => undef, allowed => undef, disallowed => undef },
- { long => 'comp', short => 'c', type => 'flag', mandatory => 'no', default => undef, allowed => undef, disallowed => undef },
- { long => 'max_hits', short => 'h', type => 'uint', mandatory => 'no', default => undef, allowed => undef, disallowed => 0 },
- { long => 'max_misses', short => 'm', type => 'uint', mandatory => 'no', default => undef, allowed => undef, disallowed => 0 },
- { long => 'genome', short => 'g', type => 'genome', mandatory => 'no', default => undef, allowed => undef, disallowed => 0 },
- ]
-);
-
-$in = Maasha::Biopieces::read_stream( $options->{ "stream_in" } );
-$out = Maasha::Biopieces::write_stream( $options->{ "stream_out" } );
-
-$tmp_dir = Maasha::Biopieces::get_tmpdir();
-
-$pat_file = "$tmp_dir/patscan.pat";
-$out_file = "$tmp_dir/patscan.out";
-
-$patterns = parse_patterns( $options );
-$arg = parse_args( $options );
-
-if ( defined $options->{ 'genome' } )
-{
- $seq_file = "$ENV{ 'BP_DATA' }/genomes/$options->{ 'genome' }/fasta/$options->{ 'genome' }.fna";
-}
+require 'maasha/biopieces'
+require 'maasha/fasta'
+require 'maasha/seq'
+
+# Class with methods to execute Patscan (a.k.a. scan_for_matches).
+class Patscan
+ # Method to initialize a Patscan object.
+ def initialize(tmp_dir, in_file, patterns)
+ @tmp_dir = tmp_dir
+ @in_file = in_file
+ @patterns = patterns
+
+ patterns_save
+ end
+
+ # Method to run Patscan
+ def run(options)
+ @patterns.each_with_index do |pattern, i|
+ args = []
+ args << "scan_for_matches"
+ args << "-c" if options[:comp]
+ args << "-p" if options[:seq_type] == :protein
+ args << "-o 1" if options[:overlap]
+ args << "-n #{options[:max_misses]}" if options[:max_misses]
+ args << "-m #{options[:max_hits]}" if options[:max_hits]
+ args << File.join(@tmp_dir, "#{i}.pat")
+ args << "< #{@in_file}"
+ args << "> " + File.join(@tmp_dir, "#{i}.out")
+ command = args.join(" ")
+ $stderr.puts command if options[:verbose]
+ system(command)
+ raise "Command failed: #{command}" unless $?.success?
+ end
+ end
+
+ # Method to parse Patscan results and return
+ # these in a hash with ID as key and a list
+ # of hits as value.
+ def results_parse
+ results = Hash.new { |h, k| h[k] = [] }
+
+ @patterns.each_with_index do |pattern, i|
+ Fasta.open(File.join(@tmp_dir, "#{i}.out"), 'r') do |ios|
+ ios.each do |entry|
+ if entry.seq_name =~ /([^:]+):\[(\d+),(\d+)\]/
+ id = $1.to_i
+ start = $2.to_i - 1
+ stop = $3.to_i - 1
+
+ if stop > start
+ strand = '+'
+ else
+ start, stop = stop, start
+ strand = '-'
+ end
+
+ results[id.to_i] << Match.new(start, stop, strand, pattern, entry.seq)
+ else
+ raise "Failed to parse seq_name: #{entry.seq_name} in patscan result"
+ end
+ end
+ end
+ end
+
+ results
+ end
+
+ private
+
+ # Method to save a patscan pattern to a file.
+ def patterns_save
+ @patterns.each_with_index do |pattern, i|
+ File.open(File.join(@tmp_dir, "#{i}.pat"), 'w') do |ios|
+ ios.puts pattern
+ end
+ end
+ end
+
+ # Subclass to define Patscan hits.
+ class Match
+ attr_reader :start, :stop, :strand, :pattern, :match
+
+ def initialize(start, stop, strand, pattern, match)
+ @start = start
+ @stop = stop
+ @strand = strand
+ @pattern = pattern
+ @match = match
+ end
+
+ def length
+ @stop - @start + 1
+ end
+ end
+end
+
+casts = []
+casts << {:long=>'pattern', :short=>'p', :type=>'string', :mandatory=>false, :default=>nil, :allowed=>nil, :disallowed=>nil}
+casts << {:long=>'pattern_in', :short=>'P', :type=>'file!', :mandatory=>false, :default=>nil, :allowed=>nil, :disallowed=>nil}
+casts << {:long=>'inline', :short=>'i', :type=>'flag', :mandatory=>false, :default=>nil, :allowed=>nil, :disallowed=>nil}
+casts << {:long=>'overlap', :short=>'o', :type=>'flag', :mandatory=>false, :default=>nil, :allowed=>nil, :disallowed=>nil}
+casts << {:long=>'comp', :short=>'c', :type=>'flag', :mandatory=>false, :default=>nil, :allowed=>nil, :disallowed=>nil}
+casts << {:long=>'max_hits', :short=>'h', :type=>'uint', :mandatory=>false, :default=>nil, :allowed=>nil, :disallowed=>'0'}
+casts << {:long=>'max_misses', :short=>'m', :type=>'uint', :mandatory=>false, :default=>nil, :allowed=>nil, :disallowed=>'0'}
+
+options = Biopieces.options_parse(ARGV, casts)
+
+unless options[:pattern] or options[:pattern_in]
+ raise ArgumentError, "--pattern or --pattern_in must be specified"
+end
+
+patterns = []
+
+if options[:pattern_in]
+ File.open(options[:pattern_in], 'r') do |ios|
+ ios.each_line { |l| patterns << l.chomp }
+ end