krzyzak/nazar

View on GitHub
lib/nazar.rb

Summary

Maintainability
A
0 mins
Test Coverage
B
80%
# frozen_string_literal: true

require 'dry-configurable'
require 'tty-table'
require 'pastel'
require 'tty-pager'

require 'nazar/version'
require 'nazar/base_table'
require 'nazar/horizontal_table'
require 'nazar/vertical_table'
require 'nazar/cell_formatter'
require 'nazar/headers_formatter'
require 'nazar/renderer'
require 'nazar/formatter'
require 'nazar/formatter/generic'
require 'nazar/view'

module Nazar # rubocop:disable Metrics/ModuleLength
  extend Dry::Configurable

  setting :formatter do
    setting :nil, default: '∅'
    setting :boolean, default: ['✓', '✗']
    setting :layout, default: :auto
    setting :paginate, default: true
  end

  setting :table do
    setting :resize, default: true
    setting :min_width, default: nil
    setting :max_width, default: nil
  end

  setting :colors do
    setting :enabled, default: ENV.fetch('ENABLE_TTY_COLORS') { TTY::Color.color? ? 'true' : 'false' } == 'true'
  end

  setting :enable_shorthand_method, default: true

  class << self # rubocop:disable Metrics/ClassLength
    def formatters
      @formatters ||= Set.new
    end

    def enable!(extensions: [:active_record, :csv, :struct])
      return if @enabled

      load_extensions!(extensions)
      enable_repl!

      enable_shorthand_method! if Nazar.config.enable_shorthand_method

      @enabled = true
    end

    def load!(extensions: [:active_record, :csv])
      load_extensions!(extensions)
      enable_shorthand_method! if Nazar.config.enable_shorthand_method

      true
    end

    def load_csv!
      require 'csv'

      register_formatter!('CSVTable', 'nazar/formatter/csv_table')
    end

    def load_struct!
      require 'ostruct'

      register_formatter!('Struct', 'nazar/formatter/struct')
    end

    def load_active_record!
      require 'active_record'

      register_formatter!('ActiveRecordCollection', 'nazar/formatter/active_record_collection')
      register_formatter!('ActiveRecordItem', 'nazar/formatter/active_record_item')
    end

    def load_sequel!
      require 'sequel'

      register_formatter!('SequelCollection', 'nazar/formatter/sequel_collection')
      register_formatter!('SequelItem', 'nazar/formatter/sequel_item')
    end

    def register_formatter!(klass_name, path)
      require path

      formatters << Nazar::Formatter.const_get(klass_name)
    end

    def pry_proc
      return unless defined?(Pry)

      proc do |output, value, instance|
        renderer = Nazar::Renderer.new(value, layout: Nazar.config.formatter.layout,
                                              paginate: Nazar.config.formatter.paginate)
        renderer.valid? ? renderer.render : @__original_pry_print.call(output, value, instance)
      end
    end

    def disable!
      disable_shorthand_method! if @defined_shorthand_method

      return unless @enabled

      disable_for_irb! if defined?(IRB)
      disable_for_pry! if defined?(Pry)

      @enabled = false
    end

    private

    def load_extensions!(extensions)
      load_active_record! if extensions.include?(:active_record)
      load_csv! if extensions.include?(:csv)
      load_struct! if extensions.include?(:struct)
      load_sequel! if extensions.include?(:sequel)
    end

    def enable_repl!
      enable_for_irb! if defined?(IRB)
      enable_for_pry! if defined?(Pry)
    end

    def enable_for_irb!
      ::IRB::Irb.class_eval do
        alias_method :__original_output_value__, :output_value
        def output_value(omit = false) # rubocop:disable Style/OptionalBooleanParameter
          renderer = Nazar::Renderer.new(@context.last_value, layout: Nazar.config.formatter.layout,
                                                              paginate: Nazar.config.formatter.paginate)
          renderer.valid? ? renderer.render : __original_output_value__(omit)
        end
      end
    end

    def enable_for_pry!
      @__original_pry_print ||= Pry.config.print

      if Pry.toplevel_binding.local_variable_defined?(:pry_instance)
        Pry.toplevel_binding.eval('pry_instance.config.print = Nazar.pry_proc', __FILE__, __LINE__)
      else
        Pry.config.print = Nazar.pry_proc
      end
    end

    def enable_shorthand_method!
      if Object.respond_to?(:__)
        return warn Pastel.new.red("Already defined Object#__() method, Nazar won't redefine it.")
      end

      @defined_shorthand_method = true

      Object.class_eval do
        def __(item, layout: Nazar.config.formatter.layout, paginate: Nazar.config.formatter.paginate)
          Nazar::Renderer.new(item, use_generic_formatter: true, layout: layout, paginate: paginate).render
        end
      end
    end

    def disable_shorthand_method!
      @defined_shorthand_method = nil

      Object.class_eval do
        undef_method :__
      end
    end

    def disable_for_irb!
      ::IRB::Irb.send(:alias_method, :output_value, :__original_output_value__)
    end

    def disable_for_pry!
      Pry.toplevel_binding.eval(
        'pry_instance.config.print = Nazar.instance_variable_get(:@__original_pry_print)',
        __FILE__,
        __LINE__
      )
    end
  end
end