<commit_before>require 'forwardable'
require 'yaml'

module Feed2Email
  class Config
    class ConfigError < StandardError; end
    class MissingConfigError < ConfigError; end
    class InvalidConfigPermissionsError < ConfigError; end
    class InvalidConfigSyntaxError < ConfigError; end
    class InvalidConfigDataTypeError < ConfigError; end
    class MissingConfigOptionError < ConfigError; end
    class InvalidConfigOptionError < ConfigError; end

    SEND_METHODS = %w{file sendmail smtp}

    extend Forwardable

    delegate [:[], :keys, :slice] => :config

    def initialize(path)
      @path = path
    end

    private

    def check_data_type
      if !data.is_a?(Hash)
        raise InvalidConfigDataTypeError,
          "Invalid data type (not a Hash) for config file #{path}"
      end
    end

    def check_existence
      if !File.exist?(path)
        raise MissingConfigError, "Missing config file #{path}"
      end
    end

    def check_file
      check_existence
      check_permissions
      check_syntax
      check_data_type
    end

    def check_option(option)
      if config[option].nil?
        raise MissingConfigOptionError,
          "Option #{option} missing from config file #{path}"
      end
    end

    def check_options
      check_recipient
      check_sender
      check_send_method
      check_smtp_options if config['send_method'] == 'smtp'
    end

    def check_permissions
      if '%o' % (File.stat(path).mode & 0777) != '600'
        raise InvalidConfigPermissionsError,
          'Invalid permissions for config file' +
          "\nTo fix it, issue: chmod 600 #{path}"
      end
    end

    def check_recipient
      check_option('recipient')
    end

    def check_send_method
      unless SEND_METHODS.include?(config['send_method'])
        raise InvalidConfigOptionError,
          "Option send_method not one of: #{SEND_METHODS.join(' ')}"
      end
    end

    def check_sender
      check_option('sender')
    end

    def check_smtp_options
      check_option('smtp_host')
      check_option('smtp_port')
      check_option('smtp_user')
      check_option('smtp_pass')
    end

    def check_syntax
      begin
        data
      rescue Psych::SyntaxError
        raise InvalidConfigSyntaxError,
          "Invalid YAML syntax for config file #{path}"
      end
    end

    def config
      return @config if @config

      begin
        check_file
        @config = defaults.merge(data)
        check_options
      rescue ConfigError => e
        abort e.message
      end

      @config
    end

    def data
      @data ||= YAML.load(File.read(path))
    end

    def defaults
      {
        'log_level'      => 'info',
        'log_path'       => true,
        'log_shift_age'  => 0,
        'log_shift_size' => 1, # megabyte
        'mail_path'      => File.join(ENV['HOME'], 'Mail'),
        'max_entries'    => 20,
        'send_delay'     => 10,
        'send_method'    => 'file',
        'sendmail_path'  => '/usr/sbin/sendmail',
        'smtp_auth'      => 'login',
        'smtp_starttls'  => true,
      }
    end

    attr_reader :path
  end
end<commit_msg>Create default config if missing<commit_after>require 'fileutils'
require 'forwardable'
require 'yaml'

module Feed2Email
  class Config
    class ConfigError < StandardError; end
    class InvalidConfigPermissionsError < ConfigError; end
    class InvalidConfigSyntaxError < ConfigError; end
    class InvalidConfigDataTypeError < ConfigError; end
    class MissingConfigOptionError < ConfigError; end
    class InvalidConfigOptionError < ConfigError; end

    SEND_METHODS = %w{file sendmail smtp}

    extend Forwardable

    delegate [:[], :keys, :slice] => :config

    def initialize(path)
      @path = path

      create_defaults if config_missing?
    end

    private

    def check_data_type
      if !data.is_a?(Hash)
        raise InvalidConfigDataTypeError,
          "Invalid data type (not a Hash) for config file #{path}"
      end
    end

    def check_file
      check_permissions
      check_syntax
      check_data_type
    end

    def check_option(option)
      if config[option].nil?
        raise MissingConfigOptionError,
          "Option #{option} missing from config file #{path}"
      end
    end

    def check_options
      check_recipient
      check_sender
      check_send_method
      check_smtp_options if config['send_method'] == 'smtp'
    end

    def check_permissions
      if '%o' % (File.stat(path).mode & 0777) != '600'
        raise InvalidConfigPermissionsError,
          'Invalid permissions for config file' +
          "\nTo fix it, issue: chmod 600 #{path}"
      end
    end

    def check_recipient
      check_option('recipient')
    end

    def check_send_method
      unless SEND_METHODS.include?(config['send_method'])
        raise InvalidConfigOptionError,
          "Option send_method not one of: #{SEND_METHODS.join(' ')}"
      end
    end

    def check_sender
      check_option('sender')
    end

    def check_smtp_options
      check_option('smtp_host')
      check_option('smtp_port')
      check_option('smtp_user')
      check_option('smtp_pass')
    end

    def check_syntax
      begin
        data
      rescue Psych::SyntaxError
        raise InvalidConfigSyntaxError,
          "Invalid YAML syntax for config file #{path}"
      end
    end

    def config
      return @config if @config

      begin
        check_file
        @config = defaults.merge(data)
        check_options
      rescue ConfigError => e
        abort e.message
      end

      @config
    end

    def config_missing?
      !File.exist?(path)
    end

    def create_defaults
      FileUtils.mkdir_p(File.dirname(path))
      FileUtils.touch(path)
      File.chmod(0600, path)
      File.open(path, "w") {|f| f << defaults.to_yaml }
    end

    def data
      @data ||= YAML.load(File.read(path))
    end

    def defaults
      {
        'log_level'      => 'info',
        'log_path'       => true,
        'log_shift_age'  => 0,
        'log_shift_size' => 1, # megabyte
        'mail_path'      => File.join(ENV['HOME'], 'Mail'),
        'max_entries'    => 20,
        'send_delay'     => 10,
        'send_method'    => 'file',
        'sendmail_path'  => '/usr/sbin/sendmail',
        'smtp_auth'      => '<PASSWORD>',
        'smtp_starttls'  => true,
      }
    end

    attr_reader :path
  end
end
{'fraction_non_alphanumeric': 0.08743169398907104, 'fraction_numerical': 0.005932864949258392, 'mean_word_length': 2.255081300813008, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 30, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, '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': '3794299', 'n_tokens_mistral': 1990, 'n_tokens_neox': 1889, 'n_words': 483}