<commit_before>require 'ox'

require 'sequenceserver/report'
require 'sequenceserver/links'

require_relative 'formatter'
require_relative 'query'
require_relative 'hit'
require_relative 'hsp'

module SequenceServer
  module BLAST
    # Captures results of a BLAST search.
    #
    # A report is constructed from a search id. Search id is simply the
    # basename of the temporary file that holds BLAST results in binary
    # BLAST archive format.
    #
    # For a given search id, result is obtained in XML format using the
    # Formatter class, parsed into a simple intermediate representation
    # (Array of values and Arrays) and information extracted from the
    # intermediate representation (ir).
    class Report < Report
      def initialize(job)
        super do
          @querydb = job.databases
          @queries = []
        end
      end

      # :nodoc:
      # Attributes parsed out from XML output.
      attr_reader :program, :program_version
      attr_reader :queries, :querydb
      attr_reader :params, :stats

      def to_json
        [:querydb, :program, :program_version, :params, :stats,
         :queries].inject({}) { |h, k| h[k] = send(k); h }.update(search_id: job.id).to_json
      end

      private

      # Generate report.
      def generate
        xml = Formatter.run(job.rfile, 'xml').file
        tsv = parse_tsv Formatter.run(job.rfile, '__ssparse').file
        ir = node_to_array(Ox.parse(xml.open.read).root)
        extract_program_info ir
        extract_params ir
        extract_stats ir
        extract_query ir, tsv
      end

      def parse_tsv(tsv)
        # HASH => qseqids => sseqids => [qcovs, [qcovhsp]]
        info = {}

        tsv.each_line do |line|
          unless line.start_with? '#'
            row = line.chomp.split("\t")
            if !info.has_key? row[0]
              info[row[0]] = {row[1] => [row[2], row[3], [row[4]]] }
            elsif !info[row[0]].has_key? row[1]
              info[row[0]][row[1]] = [row[2], row[3], [row[4]]]
            else
              info[row[0]][row[1]][2].push(row[4])
            end
          end
        end
        info
      end

      # Make program name and program name + version available via `program`
      # and `program_version` attributes.
      def extract_program_info(ir)
        @program         = ir[0]
        @program_version = ir[1]
      end

      # Make search params available via `params` attribute.
      #
      # Search params tweak the results. Like evalue cutoff or penalty to open
      # a gap. BLAST+ doesn't list all input params in the XML output. Only
      # matrix, evalue, gapopen, gapextend, and filters are available from XML
      # output.
      def extract_params(ir)
        params = Hash[
          *ir[7].first.map { |k, v| [k.gsub('Parameters_', ''), v] }.flatten
        ].merge(job.advanced_params)
        @params = Hash['e value', params.delete('expect')].merge!(params)
      end

      # Make search stats available via `stats` attribute.
      #
      # Search stats are computed metrics. Like total number of sequences or
      # effective search space.
      def extract_stats(ir)
        stats  = ir[8].first[5][0]
        @stats = {
          :nsequences   => stats[0],
          :ncharacters  => stats[1],
          :hsp_length   => stats[2],<commit_msg>dont attempt to merge nil<commit_after>require 'ox'

require 'sequenceserver/report'
require 'sequenceserver/links'

require_relative 'formatter'
require_relative 'query'
require_relative 'hit'
require_relative 'hsp'

module SequenceServer
  module BLAST
    # Captures results of a BLAST search.
    #
    # A report is constructed from a search id. Search id is simply the
    # basename of the temporary file that holds BLAST results in binary
    # BLAST archive format.
    #
    # For a given search id, result is obtained in XML format using the
    # Formatter class, parsed into a simple intermediate representation
    # (Array of values and Arrays) and information extracted from the
    # intermediate representation (ir).
    class Report < Report
      def initialize(job)
        super do
          @querydb = job.databases
          @queries = []
        end
      end

      # :nodoc:
      # Attributes parsed out from XML output.
      attr_reader :program, :program_version
      attr_reader :queries, :querydb
      attr_reader :params, :stats

      def to_json
        [:querydb, :program, :program_version, :params, :stats,
         :queries].inject({}) { |h, k| h[k] = send(k); h }.update(search_id: job.id).to_json
      end

      private

      # Generate report.
      def generate
        xml = Formatter.run(job.rfile, 'xml').file
        tsv = parse_tsv Formatter.run(job.rfile, '__ssparse').file
        ir = node_to_array(Ox.parse(xml.open.read).root)
        extract_program_info ir
        extract_params ir
        extract_stats ir
        extract_query ir, tsv
      end

      def parse_tsv(tsv)
        # HASH => qseqids => sseqids => [qcovs, [qcovhsp]]
        info = {}

        tsv.each_line do |line|
          unless line.start_with? '#'
            row = line.chomp.split("\t")
            if !info.has_key? row[0]
              info[row[0]] = {row[1] => [row[2], row[3], [row[4]]] }
            elsif !info[row[0]].has_key? row[1]
              info[row[0]][row[1]] = [row[2], row[3], [row[4]]]
            else
              info[row[0]][row[1]][2].push(row[4])
            end
          end
        end
        info
      end

      # Make program name and program name + version available via `program`
      # and `program_version` attributes.
      def extract_program_info(ir)
        @program         = ir[0]
        @program_version = ir[1]
      end

      # Make search params available via `params` attribute.
      #
      # Search params tweak the results. Like evalue cutoff or penalty to open
      # a gap. BLAST+ doesn't list all input params in the XML output. Only
      # matrix, evalue, gapopen, gapextend, and filters are available from XML
      # output.
      def extract_params(ir)
        params = Hash[
          *ir[7].first.map { |k, v| [k.gsub('Parameters_', ''), v] }.flatten
        ]
        params = params.merge(job.advanced_params) unless job.advanced_params.nil?
        @params = Hash['e value', params.delete('expect')].merge!(params)
      end

      # Make search stats available via `stats` attribute.
      #
      # Search stats are computed metrics. Like total number of sequences or
      # effective search space.
      def extract_stats(ir)
        stats  = ir[8].first[5][0]
        @stats = {
          :nsequences   => stats[0],
          :ncharacters  => stats[1],
          :hsp_length   => stats[2],
{'fraction_non_alphanumeric': 0.10692010692010692, 'fraction_numerical': 0.007722007722007722, 'mean_word_length': 2.2102001906577695, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 6}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3848964', 'n_tokens_mistral': 2050, 'n_tokens_neox': 1998, 'n_words': 608}