lib/naughty_or_nice.rb



# frozen_string_literal: true

require 'public_suffix'
require 'addressable/uri'
require_relative './naughty_or_nice/version'

# Primary module to be mixed into the child class
module NaughtyOrNice
  # Source: http://bit.ly/1n2X9iv
  EMAIL_REGEX = %r{
        ^
        (
          [\w!\#$%&'*+\-/=?\^`\{|\}~]+
          \.
        )
        *
        [\w!\#$%&'*+\-/=?\^`\{|\}~]+
        @
        (
          (
            (
              (
                (
                  [a-z0-9]{1}
                  [a-z0-9\-]{0,62}
                  [a-z0-9]{1}
                )
                |
                [a-z]
              )
              \.
            )+
            [a-z]{2,6}
          )
          |
          (
            \d{1,3}
            \.
          ){3}
          \d{1,3}
          (
            :\d{1,5}
          )?
        )
        $
      }xi.freeze

  # Ruby idiom that allows `include` to create class methods
  module ClassMethods
    def valid?(text)
      new(text).valid?
    end
  end

  def self.included(base)
    base.extend(ClassMethods)
  end

  def initialize(domain)
    if domain.is_a?(PublicSuffix::Domain)
      @domain = domain
      @text   = domain.to_s
    else
      @text = domain.to_s.downcase.strip
    end
  end

  # Return the public suffix domain object
  #
  # Supports all domain strings (URLs, emails)
  #
  # Returns the domain object or nil, but no errors, never an error
  def domain
    return @domain if defined? @domain

    @domain = begin
      PublicSuffix.parse(normalized_domain, default_rule: nil)
    rescue PublicSuffix::DomainInvalid, PublicSuffix::DomainNotAllowed
      nil
    end
  end

  # Checks if the input string represents a valid domain
  #
  # Returns boolean true if a valid domain, otherwise false
  def valid?
    !domain.nil?
  end

  # Is the input text in the form of a valid email address?
  #
  # Returns true if email, otherwise false
  def email?
    !(@text =~ EMAIL_REGEX).nil?
  end

  # Return the parsed domain as a string
  def to_s
    @to_s ||= domain.to_s if domain
  end

  def inspect
    "#<#{self.class} domain=\"#{domain}\" valid=#{valid?}>"
  end

  private

  # Parse the domain from the input string
  #
  # Can handle urls, domains, or emails
  #
  # Returns the domain string
  def normalized_domain
    if @text.empty?
      nil
    elsif parsed_domain
      parsed_domain.host
    end
  end

  def parsed_domain
    @parsed_domain ||= begin
      text = @text.dup
      text.prepend('http://') unless %r{\Ahttps?://}.match?(text)
      uri = Addressable::URI.parse(text)
      # properly parse http://foo edge cases
      # see https://github.com/sporkmonger/addressable/issues/145
      uri if uri.host.include?('.')
    end
  rescue Addressable::URI::InvalidURIError
    nil
  end
end