yannickwurm/sequenceserver

View on GitHub
lib/sequenceserver/config.rb

Summary

Maintainability
A
2 hrs
Test Coverage
require 'yaml'
require 'forwardable'

# Define Config class.
module SequenceServer
  # Capture our configuration system.
  class Config
    extend Forwardable

    def_delegators SequenceServer, :logger

    def initialize(data = {})
      @data = normalize data

      @config_file = @data.delete(:config_file)
      if @config_file
        @config_file = File.expand_path(@config_file)
        @data = parse_config_file.deep_merge @data
      end

      @data = defaults.deep_merge @data

      return unless @upgraded

      logger.info 'You are using old configuration syntax. ' \
                  'Run `sequenceserver -s` to update your config file syntax.'
    end

    attr_reader :data, :config_file

    # Get.
    def [](key)
      data[key]
    end

    # Set.
    def []=(key, value)
      data[key] = value
    end

    # Exists?
    def include?(key)
      data.include? key
    end

    # Write config data to config file.
    def write_config_file
      return unless config_file

      File.open(config_file, 'w') do |f|
        f.puts(data.delete_if { |_, v| v.nil? }.to_yaml)
      end
    end

    private

    # Symbolizes keys. Rename/reformat key-values.
    def normalize(data)
      return {} unless data

      # Symbolize keys.
      data = data.transform_keys(&:to_sym)

      # Very old config files may have a key called `database`.
      # Rename it to `database_dir`
      if data[:database]
        database_dir = data.delete(:database)
        data[:database_dir] ||= database_dir
        @upgrade = true
      end

      # Old config files may have an options hash with array values. We turn the
      # array values into a hash. The logic is simple: If the array value is the
      # same as default, we give it the key 'default', otherwise we give it the
      # key 'custom'
      data[:options]&.each do |key, val|
        next if val.is_a? Hash

        data[:options][key] = if val == defaults[:options][key][:default]
                                { default: val }
                              else
                                { custom: val }
                              end
        @upgraded = true
      end

      data
    end

    # Parses and returns data from config_file if it exists. Returns {}
    # otherwise.
    def parse_config_file
      unless file? config_file
        logger.debug "Configuration file not found: #{config_file}"
        return {}
      end
      logger.info "Reading configuration file: #{config_file}."
      normalize YAML.load_file(config_file)
    rescue StandardError => e
      raise CONFIG_FILE_ERROR.new(config_file, e)
    end

    def file?(file)
      file && File.exist?(file) && File.file?(file)
    end

    # Default configuration data.
    def defaults
      @defaults ||= {
        host: '0.0.0.0',
        port: 4567,
        databases_widget: 'classic',
        options: {
          blastn: {
            default: ['-task blastn', '-evalue 1e-5']
          },
          blastp: {
            default: ['-evalue 1e-5']
          },
          blastx: {
            default: ['-evalue 1e-5']
          },
          tblastx: {
            default: ['-evalue 1e-5']
          },
          tblastn: {
            default: ['-evalue 1e-5']
          }
        },
        num_threads: 1,
        num_jobs: 1,
        job_lifetime: 43_200,
        # Set cloud_share_url to 'disabled' to disable the cloud sharing feature
        cloud_share_url: 'https://share.sequenceserver.com/api/v1/shared-job',
        # Warn in the UI before rendering results larger than this value
        large_result_warning_threshold: 250 * 1024 * 1024,
        optimistic: false # Faster, but does not perform DB compatibility checks
      }
    end
  end
end