ronin-rb/ronin-db

View on GitHub
lib/ronin/db/config_file.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true
#
# ronin-db - A common database library for managing and querying security data.
#
# Copyright (c) 2006-2023 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# ronin-db is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ronin-db is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with ronin-db.  If not, see <https://www.gnu.org/licenses/>.
#

require 'ronin/db/exceptions'
require 'ronin/db/home'

require 'fileutils'
require 'yaml'
require 'yaml/store'

module Ronin
  module DB
    #
    # Represents the `~/.config/ronin-db/databases.yml` configuration file.
    #
    # @api private
    #
    module ConfigFile
      # Path to the `~/.config/ronin-db/databases.yml` configuration file.
      PATH = File.join(DB::Home::CONFIG_DIR,'databases.yml')

      # Path to the default sqlite3 database file
      # (`~/.local/share/ronin-db/default.sqlite3`).
      DEFAULT_DB_FILE = File.join(DB::Home::LOCAL_SHARE_DIR,'default.sqlite3')

      # Default database configuration.
      DEFAULT_CONFIG = {
        default: {
          adapter: 'sqlite3',
          database: DEFAULT_DB_FILE
        }
      }

      #
      # Validates the loaded configuration data from the given path.
      #
      # @param [String] path
      #   The path the configuration data was loaded from.
      #
      # @param [Object] data
      #   The loaded configuration data.
      #
      # @raise [InvalidConfig]
      #   The configuration data is not a Hash, does not contain Symbol keys,
      #   or does not contain Hashes.
      #
      # @return [true]
      #   The configuration data is valid.
      #
      def self.validate(path,data)
        unless data.kind_of?(Hash)
          raise(InvalidConfig,"config data must be a Hash: #{data.inspect}")
        end

        data.each do |key,value|
          unless key.kind_of?(Symbol)
            raise(InvalidConfig,"all Hash keys must be a Symbol: #{key.inspect}")
          end

          unless value.kind_of?(Hash)
            raise(InvalidConfig,"all Hash values must also be a Hash: #{value.inspect}")
          end

          value.each_key do |sub_key|
            unless sub_key.kind_of?(Symbol)
              raise(InvalidConfig,"all sub-keys must be a Symbol: #{sub_key.inspect}")
            end
          end
        end

        return true
      end

      #
      # Loads configuration data from the given path.
      #
      # @param [String] path
      #   The path to the configuration file.
      #
      # @return [Hash{Symbol => Hash}]
      #   The loaded configuration data.
      #
      # @raise [InvalidConfig]
      #   The configuration data is not a Hash, does not contain Symbol keys,
      #   or does not contain Hashes.
      #
      def self.load(path=PATH)
        if File.file?(path)
          config = YAML.load_file(path, symbolize_names: true).tap do |data|
            validate(path,data)
          end

          DEFAULT_CONFIG.merge(config)
        else
          DEFAULT_CONFIG
        end
      end

      #
      # Opens the configuration file and edits it's contents.
      #
      # @yield [yaml]
      #   The given block will be given the loaded configuration data.
      #   Once the block has returned, the YAML configuration data will be
      #   written back to the file.
      #
      # @yieldparam [YAML::Store] yaml
      #   The loaded YAML configuration data.
      #
      def self.edit(path=PATH,&block)
        unless File.file?(path)
          # create the parent directory for YAML::Store
          FileUtils.mkdir_p(File.dirname(path))
        end

        store = YAML::Store.new(path)
        store.transaction(&block)
      end
    end
  end
end