1
0
Fork 0
mirror of https://github.com/awesome-print/awesome_print synced 2023-03-27 23:22:34 -04:00

Major refactoring: split AwesomePrint class into Inspector and Formatter

This commit is contained in:
Mike Dvorkin 2011-02-05 11:31:51 -08:00
parent 0f14846fcd
commit dfbebd4887
15 changed files with 568 additions and 523 deletions

View file

@ -7,7 +7,8 @@
require File.dirname(__FILE__) + "/ap/core_ext/#{file}" require File.dirname(__FILE__) + "/ap/core_ext/#{file}"
end end
require File.dirname(__FILE__) + "/ap/awesome_print" require File.dirname(__FILE__) + "/ap/inspector"
require File.dirname(__FILE__) + "/ap/formatter"
require File.dirname(__FILE__) + "/ap/core_ext/logger" if defined?(Logger) require File.dirname(__FILE__) + "/ap/core_ext/logger" if defined?(Logger)
require File.dirname(__FILE__) + "/ap/mixin/action_view" if defined?(ActionView) require File.dirname(__FILE__) + "/ap/mixin/action_view" if defined?(ActionView)

View file

@ -1,332 +0,0 @@
# Copyright (c) 2010-2011 Michael Dvorkin
#
# Awesome Print is freely distributable under the terms of MIT license.
# See LICENSE file or http://www.opensource.org/licenses/mit-license.php
#------------------------------------------------------------------------------
require "shellwords"
class AwesomePrint
AP = :__awesome_print__ unless defined?(AwesomePrint::AP)
CORE = [ :array, :hash, :class, :file, :dir, :bigdecimal, :rational, :struct, :method, :unboundmethod ] unless defined?(AwesomePrint::CORE)
def initialize(options = {})
@options = {
:multiline => true,
:plain => false,
:indent => 4,
:index => true,
:sorted_hash_keys => false,
:color => {
:array => :white,
:bigdecimal => :blue,
:class => :yellow,
:date => :greenish,
:falseclass => :red,
:fixnum => :blue,
:float => :blue,
:hash => :pale,
:struct => :pale,
:nilclass => :red,
:string => :yellowish,
:symbol => :cyanish,
:time => :greenish,
:trueclass => :green,
:method => :purpleish,
:args => :pale
}
}
# Merge custom defaults and let explicit options parameter override them.
merge_custom_defaults!
merge_options!(options)
@indentation = @options[:indent].abs
Thread.current[AP] ||= []
end
private
# Format an array.
#------------------------------------------------------------------------------
def awesome_array(a)
return "[]" if a == []
if a.instance_variable_defined?('@__awesome_methods__')
methods_array(a)
elsif @options[:multiline]
width = (a.size - 1).to_s.size
data = a.inject([]) do |arr, item|
index = if @options[:index]
colorize("#{indent}[#{arr.size.to_s.rjust(width)}] ", :array)
else
colorize(indent, :array)
end
indented do
arr << (index << awesome(item))
end
end
"[\n" << data.join(",\n") << "\n#{outdent}]"
else
"[ " << a.map{ |item| awesome(item) }.join(", ") << " ]"
end
end
# Format a hash. If @options[:indent] if negative left align hash keys.
#------------------------------------------------------------------------------
def awesome_hash(h)
return "{}" if h == {}
keys = @options[:sorted_hash_keys] ? h.keys.sort { |a, b| a.to_s <=> b.to_s } : h.keys
data = keys.map do |key|
plain_single_line do
[ awesome(key), h[key] ]
end
end
width = data.map { |key, | key.size }.max || 0
width += @indentation if @options[:indent] > 0
data = data.map do |key, value|
if @options[:multiline]
formatted_key = (@options[:indent] >= 0 ? key.rjust(width) : indent + key.ljust(width))
else
formatted_key = key
end
indented do
formatted_key << colorize(" => ", :hash) << awesome(value)
end
end
if @options[:multiline]
"{\n" << data.join(",\n") << "\n#{outdent}}"
else
"{ #{data.join(', ')} }"
end
end
# Format a Struct. If @options[:indent] if negative left align hash keys.
#------------------------------------------------------------------------------
def awesome_struct(s)
h = {}
s.each_pair { |k,v| h[k] = v }
awesome_hash(h)
end
# Format Class object.
#------------------------------------------------------------------------------
def awesome_class(c)
if superclass = c.superclass # <-- Assign and test if nil.
awesome_self(c, :with => " < #{superclass}")
else
awesome_self(c)
end
end
# Format File object.
#------------------------------------------------------------------------------
def awesome_file(f)
ls = File.directory?(f) ? `ls -adlF #{f.path.shellescape}` : `ls -alF #{f.path.shellescape}`
awesome_self(f, :with => ls.empty? ? nil : "\n#{ls.chop}")
end
# Format Dir object.
#------------------------------------------------------------------------------
def awesome_dir(d)
ls = `ls -alF #{d.path.shellescape}`
awesome_self(d, :with => ls.empty? ? nil : "\n#{ls.chop}")
end
# Format BigDecimal and Rational objects by convering them to Float.
#------------------------------------------------------------------------------
def awesome_bigdecimal(n)
awesome_self(n.to_f, :as => :bigdecimal)
end
alias :awesome_rational :awesome_bigdecimal
# Format a method.
#------------------------------------------------------------------------------
def awesome_method(m)
name, args, owner = method_tuple(m)
"#{colorize(owner, :class)}##{colorize(name, :method)}#{colorize(args, :args)}"
end
alias :awesome_unboundmethod :awesome_method
# Catch all method to format an arbitrary object.
#------------------------------------------------------------------------------
def awesome_self(object, appear = {})
colorize(object.inspect.to_s << appear[:with].to_s, appear[:as] || declassify(object))
end
# Dispatcher that detects data nesting and invokes object-aware formatter.
#------------------------------------------------------------------------------
def awesome(object)
if Thread.current[AP].include?(object.object_id)
nested(object)
else
begin
Thread.current[AP] << object.object_id
send(:"awesome_#{printable(object)}", object)
ensure
Thread.current[AP].pop
end
end
end
# Format object.methods array.
#------------------------------------------------------------------------------
def methods_array(a)
object = a.instance_variable_get('@__awesome_methods__')
tuples = a.map do |name|
if object.respond_to?(name, true) # Regular method?
method_tuple(object.method(name))
elsif object.respond_to?(:instance_method) # Unbound method?
method_tuple(object.instance_method(name))
else # WTF method.
[ name.to_s, '(?)', '' ]
end
end
width = (tuples.size - 1).to_s.size
name_width = tuples.map { |item| item[0].size }.max || 0
args_width = tuples.map { |item| item[1].size }.max || 0
data = tuples.inject([]) do |arr, item|
index = if @options[:index]
"#{indent}[#{arr.size.to_s.rjust(width)}]"
else
indent
end
indented do
arr << "#{index} #{colorize(item[0].rjust(name_width), :method)}#{colorize(item[1].ljust(args_width), :args)} #{colorize(item[2], :class)}"
end
end
"[\n" << data.join("\n") << "\n#{outdent}]"
end
# Format nested data, for example:
# arr = [1, 2]; arr << arr
# => [1,2, [...]]
# hsh = { :a => 1 }; hsh[:b] = hsh
# => { :a => 1, :b => {...} }
#------------------------------------------------------------------------------
def nested(object)
case printable(object)
when :array then colorize("[...]", :array)
when :hash then colorize("{...}", :hash)
when :struct then colorize("{...}", :struct)
else colorize("...#{object.class}...", :class)
end
end
# Return one of the "core" types that have a formatter of :self otherwise.
#------------------------------------------------------------------------------
def printable(object)
CORE.grep(declassify(object))[0] || :self
end
# Turn class name into symbol, ex: Hello::World => :hello_world. Classes that
# inherit from Array, Hash, File, Dir, and Struct are treated as the base class.
#------------------------------------------------------------------------------
def declassify(object)
case object
when Array then :array
when Hash then :hash
when File then :file
when Dir then :dir
when Struct then :struct
else object.class.to_s.gsub(/:+/, "_").downcase.to_sym
end
end
# Pick the color and apply it to the given string as necessary.
#------------------------------------------------------------------------------
def colorize(s, type)
if @options[:plain] || @options[:color][type].nil?
s
else
s.send(@options[:color][type])
end
end
# Return [ name, arguments, owner ] tuple for a given method.
#------------------------------------------------------------------------------
def method_tuple(method)
if method.respond_to?(:parameters) # Ruby 1.9.2+
# See http://ruby.runpaint.org/methods#method-objects-parameters
args = method.parameters.inject([]) do |arr, (type, name)|
name ||= (type == :block ? 'block' : "arg#{arr.size + 1}")
arr << case type
when :req then name.to_s
when :opt, :rest then "*#{name}"
when :block then "&#{name}"
else '?'
end
end
else # See http://ruby-doc.org/core/classes/Method.html#M001902
args = (1..method.arity.abs).map { |i| "arg#{i}" }
args[-1] = "*#{args[-1]}" if method.arity < 0
end
if method.to_s =~ /(Unbound)*Method: (.*?)[#\.]/
owner = "#{$2}#{$1 ? '(unbound)' : ''}".gsub('(', ' (')
end
[ method.name.to_s, "(#{args.join(', ')})", owner.to_s ]
end
# Format hash keys as plain string regardless of underlying data type.
#------------------------------------------------------------------------------
def plain_single_line
plain, multiline = @options[:plain], @options[:multiline]
@options[:plain], @options[:multiline] = true, false
yield
ensure
@options[:plain], @options[:multiline] = plain, multiline
end
#------------------------------------------------------------------------------
def indented
@indentation += @options[:indent].abs
yield
ensure
@indentation -= @options[:indent].abs
end
def indent
@indent = ' ' * @indentation
end
def outdent
@outdent = ' ' * (@indentation - @options[:indent].abs)
end
# Update @options by first merging the :color hash and then the remaining keys.
#------------------------------------------------------------------------------
def merge_options!(options = {})
@options[:color].merge!(options.delete(:color) || {})
@options.merge!(options)
end
# Load ~/.aprc file with custom defaults that override default options.
#------------------------------------------------------------------------------
def merge_custom_defaults!
dotfile = File.join(ENV["HOME"], ".aprc")
if File.readable?(dotfile)
load dotfile
merge_options!(self.class.defaults)
end
rescue => e
$stderr.puts "Could not load #{dotfile}: #{e}"
end
# Class accessors for custom defaults.
#------------------------------------------------------------------------------
def self.defaults
@@defaults ||= {}
end
def self.defaults=(args = {})
@@defaults = args
end
end

View file

@ -6,7 +6,7 @@
module Kernel module Kernel
def ai(options = {}) def ai(options = {})
ap = AwesomePrint.new(options) ap = AwesomePrint::Inspector.new(options)
ap.send(:awesome, self) ap.send(:awesome, self)
end end
alias :awesome_inspect :ai alias :awesome_inspect :ai

View file

@ -3,16 +3,17 @@
# Awesome Print is freely distributable under the terms of MIT license. # Awesome Print is freely distributable under the terms of MIT license.
# See LICENSE file or http://www.opensource.org/licenses/mit-license.php # See LICENSE file or http://www.opensource.org/licenses/mit-license.php
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
module AwesomePrintLogger module AwesomePrint
module Logger
# Add ap method to logger # Add ap method to logger
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
def ap(object, level = nil) def ap(object, level = nil)
level ||= AwesomePrint.defaults[:log_level] || :debug level ||= AwesomePrint.defaults[:log_level] || :debug
send level, object.ai send level, object.ai
end
end end
end end
Logger.send(:include, AwesomePrintLogger) Logger.send(:include, AwesomePrint::Logger)
ActiveSupport::BufferedLogger.send(:include, AwesomePrintLogger) if defined?(::ActiveSupport::BufferedLogger) ActiveSupport::BufferedLogger.send(:include, AwesomePrint::Logger) if defined?(::ActiveSupport::BufferedLogger)

238
lib/ap/formatter.rb Executable file
View file

@ -0,0 +1,238 @@
# Copyright (c) 2010-2011 Michael Dvorkin
#
# Awesome Print is freely distributable under the terms of MIT license.
# See LICENSE file or http://www.opensource.org/licenses/mit-license.php
#------------------------------------------------------------------------------
require "shellwords"
module AwesomePrint
class Formatter
CORE = [ :array, :hash, :class, :file, :dir, :bigdecimal, :rational, :struct, :method, :unboundmethod ]
def initialize(inspector)
@inspector = inspector
@options = inspector.options
@indentation = @options[:indent].abs
end
# Main entry point to format an object.
#------------------------------------------------------------------------------
def format(object, type = nil)
klass = cast(object, type)
return send(:"awesome_#{klass}", object) if klass != :self
send(:awesome_self, object, :as => type) # Catch all that falls back on object.inspect.
end
# Hook this when adding custom formatters. Check out how it's done in
# ap/lib/mixin/active_record.rb
#------------------------------------------------------------------------------
def cast(object, type)
CORE.grep(type)[0] || :self
end
# Pick the color and apply it to the given string as necessary.
#------------------------------------------------------------------------------
def colorize(s, type = nil)
return s if @options[:plain] || @options[:color][type].nil?
s.send(@options[:color][type])
end
private
# Catch all method to format an arbitrary object.
#------------------------------------------------------------------------------
def awesome_self(object, appear = {})
colorize(object.inspect.to_s << appear[:with].to_s, appear[:as])
end
# Format an array.
#------------------------------------------------------------------------------
def awesome_array(a)
return "[]" if a == []
if a.instance_variable_defined?('@__awesome_methods__')
methods_array(a)
elsif @options[:multiline]
width = (a.size - 1).to_s.size
data = a.inject([]) do |arr, item|
index = if @options[:index]
colorize("#{indent}[#{arr.size.to_s.rjust(width)}] ", :array)
else
colorize(indent, :array)
end
indented do
arr << (index << @inspector.send(:awesome, item))
end
end
"[\n" << data.join(",\n") << "\n#{outdent}]"
else
"[ " << a.map{ |item| @inspector.send(:awesome, item) }.join(", ") << " ]"
end
end
# Format a hash. If @options[:indent] if negative left align hash keys.
#------------------------------------------------------------------------------
def awesome_hash(h)
return "{}" if h == {}
keys = @options[:sort_keys] ? h.keys.sort { |a, b| a.to_s <=> b.to_s } : h.keys
data = keys.map do |key|
plain_single_line do
[ @inspector.send(:awesome, key), h[key] ]
end
end
width = data.map { |key, | key.size }.max || 0
width += @indentation if @options[:indent] > 0
data = data.map do |key, value|
if @options[:multiline]
formatted_key = (@options[:indent] >= 0 ? key.rjust(width) : indent + key.ljust(width))
else
formatted_key = key
end
indented do
formatted_key << colorize(" => ", :hash) << @inspector.send(:awesome, value)
end
end
if @options[:multiline]
"{\n" << data.join(",\n") << "\n#{outdent}}"
else
"{ #{data.join(', ')} }"
end
end
# Format a Struct. If @options[:indent] if negative left align hash keys.
#------------------------------------------------------------------------------
def awesome_struct(s)
h = {}
s.each_pair { |k,v| h[k] = v }
awesome_hash(h)
end
# Format Class object.
#------------------------------------------------------------------------------
def awesome_class(c)
if superclass = c.superclass # <-- Assign and test if nil.
awesome_self(c, :as => :class, :with => " < #{superclass}")
else
awesome_self(c, :as => :class)
end
end
# Format File object.
#------------------------------------------------------------------------------
def awesome_file(f)
ls = File.directory?(f) ? `ls -adlF #{f.path.shellescape}` : `ls -alF #{f.path.shellescape}`
awesome_self(f, :as => :file, :with => ls.empty? ? nil : "\n#{ls.chop}")
end
# Format Dir object.
#------------------------------------------------------------------------------
def awesome_dir(d)
ls = `ls -alF #{d.path.shellescape}`
awesome_self(d, :as => :dir, :with => ls.empty? ? nil : "\n#{ls.chop}")
end
# Format BigDecimal and Rational objects by convering them to Float.
#------------------------------------------------------------------------------
def awesome_bigdecimal(n)
awesome_self(n.to_f, :as => :bigdecimal)
end
alias :awesome_rational :awesome_bigdecimal
# Format a method.
#------------------------------------------------------------------------------
def awesome_method(m)
name, args, owner = method_tuple(m)
"#{colorize(owner, :class)}##{colorize(name, :method)}#{colorize(args, :args)}"
end
alias :awesome_unboundmethod :awesome_method
# Format object.methods array.
#------------------------------------------------------------------------------
def methods_array(a)
object = a.instance_variable_get('@__awesome_methods__')
tuples = a.map do |name|
if object.respond_to?(name, true) # Regular method?
method_tuple(object.method(name))
elsif object.respond_to?(:instance_method) # Unbound method?
method_tuple(object.instance_method(name))
else # WTF method.
[ name.to_s, '(?)', '' ]
end
end
width = (tuples.size - 1).to_s.size
name_width = tuples.map { |item| item[0].size }.max || 0
args_width = tuples.map { |item| item[1].size }.max || 0
data = tuples.inject([]) do |arr, item|
index = if @options[:index]
"#{indent}[#{arr.size.to_s.rjust(width)}]"
else
indent
end
indented do
arr << "#{index} #{colorize(item[0].rjust(name_width), :method)}#{colorize(item[1].ljust(args_width), :args)} #{colorize(item[2], :class)}"
end
end
"[\n" << data.join("\n") << "\n#{outdent}]"
end
# Return [ name, arguments, owner ] tuple for a given method.
#------------------------------------------------------------------------------
def method_tuple(method)
if method.respond_to?(:parameters) # Ruby 1.9.2+
# See http://ruby.runpaint.org/methods#method-objects-parameters
args = method.parameters.inject([]) do |arr, (type, name)|
name ||= (type == :block ? 'block' : "arg#{arr.size + 1}")
arr << case type
when :req then name.to_s
when :opt, :rest then "*#{name}"
when :block then "&#{name}"
else '?'
end
end
else # See http://ruby-doc.org/core/classes/Method.html#M001902
args = (1..method.arity.abs).map { |i| "arg#{i}" }
args[-1] = "*#{args[-1]}" if method.arity < 0
end
if method.to_s =~ /(Unbound)*Method: (.*?)[#\.]/
owner = "#{$2}#{$1 ? '(unbound)' : ''}".gsub('(', ' (')
end
[ method.name.to_s, "(#{args.join(', ')})", owner.to_s ]
end
# Format hash keys as plain string regardless of underlying data type.
#------------------------------------------------------------------------------
def plain_single_line
plain, multiline = @options[:plain], @options[:multiline]
@options[:plain], @options[:multiline] = true, false
yield
ensure
@options[:plain], @options[:multiline] = plain, multiline
end
#------------------------------------------------------------------------------
def indented
@indentation += @options[:indent].abs
yield
ensure
@indentation -= @options[:indent].abs
end
def indent
' ' * @indentation
end
def outdent
' ' * (@indentation - @options[:indent].abs)
end
end
end

122
lib/ap/inspector.rb Executable file
View file

@ -0,0 +1,122 @@
# Copyright (c) 2010-2011 Michael Dvorkin
#
# Awesome Print is freely distributable under the terms of MIT license.
# See LICENSE file or http://www.opensource.org/licenses/mit-license.php
#------------------------------------------------------------------------------
module AwesomePrint
class << self # Class accessors for custom defaults.
attr_accessor :defaults
end
class Inspector
attr_accessor :options
AP = :__awesome_print__
def initialize(options = {})
@options = {
:color => {
:array => :white,
:bigdecimal => :blue,
:class => :yellow,
:date => :greenish,
:falseclass => :red,
:fixnum => :blue,
:float => :blue,
:hash => :pale,
:struct => :pale,
:nilclass => :red,
:string => :yellowish,
:symbol => :cyanish,
:time => :greenish,
:trueclass => :green,
:method => :purpleish,
:args => :pale
},
:indent => 4,
:index => true,
:multiline => true,
:plain => false,
:sort_keys => false
}
# Merge custom defaults and let explicit options parameter override them.
merge_custom_defaults!
merge_options!(options)
@formatter = AwesomePrint::Formatter.new(self)
Thread.current[AP] ||= []
end
private
# Dispatcher that detects data nesting and invokes object-aware formatter.
#------------------------------------------------------------------------------
def awesome(object)
if Thread.current[AP].include?(object.object_id)
nested(object)
else
begin
Thread.current[AP] << object.object_id
unnested(object)
ensure
Thread.current[AP].pop
end
end
end
# Format nested data, for example:
# arr = [1, 2]; arr << arr
# => [1,2, [...]]
# hash = { :a => 1 }; hash[:b] = hash
# => { :a => 1, :b => {...} }
#------------------------------------------------------------------------------
def nested(object)
case printable(object)
when :array then @formatter.colorize("[...]", :array)
when :hash then @formatter.colorize("{...}", :hash)
when :struct then @formatter.colorize("{...}", :struct)
else @formatter.colorize("...#{object.class}...", :class)
end
end
#------------------------------------------------------------------------------
def unnested(object)
@formatter.format(object, printable(object))
end
# Turn class name into symbol, ex: Hello::World => :hello_world. Classes that
# inherit from Array, Hash, File, Dir, and Struct are treated as the base class.
#------------------------------------------------------------------------------
def printable(object)
case object
when Array then :array
when Hash then :hash
when File then :file
when Dir then :dir
when Struct then :struct
else object.class.to_s.gsub(/:+/, "_").downcase.to_sym
end
end
# Update @options by first merging the :color hash and then the remaining keys.
#------------------------------------------------------------------------------
def merge_options!(options = {})
@options[:color].merge!(options.delete(:color) || {})
@options.merge!(options)
end
# Load ~/.aprc file with custom defaults that override default options.
#------------------------------------------------------------------------------
def merge_custom_defaults!
dotfile = File.join(ENV["HOME"], ".aprc")
if File.readable?(dotfile)
load dotfile
merge_options!(AwesomePrint.defaults)
end
rescue => e
$stderr.puts "Could not load #{dotfile}: #{e}"
end
end
end

View file

@ -3,36 +3,37 @@
# Awesome Print is freely distributable under the terms of MIT license. # Awesome Print is freely distributable under the terms of MIT license.
# See LICENSE file or http://www.opensource.org/licenses/mit-license.php # See LICENSE file or http://www.opensource.org/licenses/mit-license.php
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
module AwesomePrintActionView module AwesomePrint
module ActionView
def self.included(base) def self.included(base)
unless base.const_defined?(:AP_ANSI_TO_HTML) unless base.const_defined?(:AP_ANSI_TO_HTML)
hash = {} # Build ANSI => HTML color map. hash = {} # Build ANSI => HTML color map.
[ :gray, :red, :green, :yellow, :blue, :purple, :cyan, :white ].each_with_index do |color, i| [ :gray, :red, :green, :yellow, :blue, :purple, :cyan, :white ].each_with_index do |color, i|
hash["\033[1;#{30+i}m"] = color hash["\033[1;#{30+i}m"] = color
end
[ :black, :darkred, :darkgreen, :brown, :navy, :darkmagenta, :darkcyan, :slategray ].each_with_index do |color, i|
hash["\033[0;#{30+i}m"] = color
end
base.const_set(:AP_ANSI_TO_HTML, hash.freeze)
end end
[ :black, :darkred, :darkgreen, :brown, :navy, :darkmagenta, :darkcyan, :slategray ].each_with_index do |color, i|
hash["\033[0;#{30+i}m"] = color
end
base.const_set(:AP_ANSI_TO_HTML, hash.freeze)
end
end
def ap_debug(object, options = {})
formatted = h(object.ai(options))
unless options[:plain]
self.class::AP_ANSI_TO_HTML.each do |key, value|
formatted.gsub!(key, %Q|<font color="#{value}">|)
end
formatted.gsub!("\033[0m", "</font>")
end end
content_tag(:pre, formatted, :class => "debug_dump") def ap_debug(object, options = {})
formatted = h(object.ai(options))
unless options[:plain]
self.class::AP_ANSI_TO_HTML.each do |key, value|
formatted.gsub!(key, %Q|<font color="#{value}">|)
end
formatted.gsub!("\033[0m", "</font>")
end
content_tag(:pre, formatted, :class => "debug_dump")
end
alias_method :ap, :ap_debug
end end
alias_method :ap, :ap_debug
end end
ActionView::Base.send(:include, AwesomePrintActionView) if defined?(ActionView) ActionView::Base.send(:include, AwesomePrint::ActionView) if defined?(ActionView)

View file

@ -3,52 +3,52 @@
# Awesome Print is freely distributable under the terms of MIT license. # Awesome Print is freely distributable under the terms of MIT license.
# See LICENSE file or http://www.opensource.org/licenses/mit-license.php # See LICENSE file or http://www.opensource.org/licenses/mit-license.php
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
module AwesomePrintActiveRecord module AwesomePrint
module ActiveRecord
def self.included(base) def self.included(base)
base.send :alias_method, :printable_without_active_record, :printable base.send :alias_method, :cast_without_active_record, :cast
base.send :alias_method, :printable, :printable_with_active_record base.send :alias_method, :cast, :cast_with_active_record
end end
# Add ActiveRecord class names to the dispatcher pipeline. def cast_with_active_record(object, type)
#------------------------------------------------------------------------------ cast = cast_without_active_record(object, type)
def printable_with_active_record(object) if defined?(::ActiveRecord)
printable = printable_without_active_record(object) if object.is_a?(::ActiveRecord::Base)
return printable if !defined?(ActiveRecord::Base) cast = :active_record_instance
elsif object.is_a?(Class) and object.ancestors.include?(::ActiveRecord::Base)
if printable == :self cast = :active_record_class
if object.is_a?(ActiveRecord::Base) end
printable = :active_record_instance
end end
elsif printable == :class and object.ancestors.include?(ActiveRecord::Base) cast
printable = :active_record_class
end end
printable
end
# Format ActiveRecord instance object. private
#------------------------------------------------------------------------------
def awesome_active_record_instance(object)
return object.inspect if !defined?(ActiveSupport::OrderedHash)
data = object.class.column_names.inject(ActiveSupport::OrderedHash.new) do |hash, name| # Format ActiveRecord instance object.
hash[name.to_sym] = object.send(name) if object.has_attribute?(name) || object.new_record? #------------------------------------------------------------------------------
hash def awesome_active_record_instance(object)
return object.inspect if !defined?(::ActiveSupport::OrderedHash)
data = object.class.column_names.inject(::ActiveSupport::OrderedHash.new) do |hash, name|
hash[name.to_sym] = object.send(name) if object.has_attribute?(name) || object.new_record?
hash
end
"#{object} " + awesome_hash(data)
end end
"#{object} " + awesome_hash(data)
end
# Format ActiveRecord class object. # Format ActiveRecord class object.
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
def awesome_active_record_class(object) def awesome_active_record_class(object)
return object.inspect if !defined?(ActiveSupport::OrderedHash) || !object.respond_to?(:columns) return object.inspect if !defined?(::ActiveSupport::OrderedHash) || !object.respond_to?(:columns)
data = object.columns.inject(ActiveSupport::OrderedHash.new) do |hash, c| data = object.columns.inject(::ActiveSupport::OrderedHash.new) do |hash, c|
hash[c.name.to_sym] = c.type hash[c.name.to_sym] = c.type
hash hash
end
"class #{object} < #{object.superclass} " << awesome_hash(data)
end end
"class #{object} < #{object.superclass} " << awesome_hash(data)
end end
end end
AwesomePrint.send(:include, AwesomePrintActiveRecord) AwesomePrint::Formatter.send(:include, AwesomePrint::ActiveRecord)

View file

@ -3,44 +3,38 @@
# Awesome Print is freely distributable under the terms of MIT license. # Awesome Print is freely distributable under the terms of MIT license.
# See LICENSE file or http://www.opensource.org/licenses/mit-license.php # See LICENSE file or http://www.opensource.org/licenses/mit-license.php
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
module AwesomePrintActiveSupport module AwesomePrint
module ActiveSupport
def self.included(base) def self.included(base)
base.send :alias_method, :printable_without_active_support, :printable base.send :alias_method, :cast_without_active_support, :cast
base.send :alias_method, :printable, :printable_with_active_support base.send :alias_method, :cast, :cast_with_active_support
end
# Add ActiveSupport class names to the dispatcher pipeline.
#------------------------------------------------------------------------------
def printable_with_active_support(object)
printable = printable_without_active_support(object)
return printable if !defined?(ActiveSupport::TimeWithZone) || !defined?(HashWithIndifferentAccess)
if printable == :self
if object.is_a?(ActiveSupport::TimeWithZone)
printable = :active_support_time
elsif object.is_a?(HashWithIndifferentAccess)
printable = :hash_with_indifferent_access
end
end end
printable
end
# Format ActiveSupport::TimeWithZone as standard Time. def cast_with_active_support(object, type)
#------------------------------------------------------------------------------ cast = cast_without_active_support(object, type)
def awesome_active_support_time(object) if defined?(::ActiveSupport) && defined?(::HashWithIndifferentAccess)
awesome_self(object, :as => :time) if object.is_a?(::ActiveSupport::TimeWithZone)
end cast = :active_support_time
elsif object.is_a?(::HashWithIndifferentAccess)
cast = :hash_with_indifferent_access
end
end
cast
end
# Format HashWithIndifferentAccess as standard Hash. # Format ActiveSupport::TimeWithZone as standard Time.
# #------------------------------------------------------------------------------
# NOTE: can't use awesome_self(object, :as => :hash) since awesome_self uses def awesome_active_support_time(object)
# object.inspect internally, i.e. it would convert hash to string. awesome_self(object, :as => :time)
#------------------------------------------------------------------------------ end
def awesome_hash_with_indifferent_access(object)
awesome_hash(object)
end
# Format HashWithIndifferentAccess as standard Hash.
#------------------------------------------------------------------------------
def awesome_hash_with_indifferent_access(object)
awesome_hash(object)
end
end
end end
AwesomePrint.send(:include, AwesomePrintActiveSupport) AwesomePrint::Formatter.send(:include, AwesomePrint::ActiveSupport)

View file

@ -14,7 +14,8 @@
require File.dirname(__FILE__) + "/ap/core_ext/#{file}" require File.dirname(__FILE__) + "/ap/core_ext/#{file}"
end end
require File.dirname(__FILE__) + "/ap/awesome_print" require File.dirname(__FILE__) + "/ap/inspector"
require File.dirname(__FILE__) + "/ap/formatter"
require File.dirname(__FILE__) + "/ap/core_ext/logger" if defined?(Logger) require File.dirname(__FILE__) + "/ap/core_ext/logger" if defined?(Logger)
require File.dirname(__FILE__) + "/ap/mixin/action_view" if defined?(ActionView) require File.dirname(__FILE__) + "/ap/mixin/action_view" if defined?(ActionView)

View file

@ -4,7 +4,7 @@ require 'action_view'
require 'ap/mixin/action_view' require 'ap/mixin/action_view'
describe "AwesomePrint ActionView extensions" do describe "AwesomePrint ActionView extensions" do
before(:each) do before do
@view = ActionView::Base.new @view = ActionView::Base.new
end end

View file

@ -4,53 +4,51 @@ require 'active_record'
require 'ap/mixin/active_record' require 'ap/mixin/active_record'
if defined?(::ActiveRecord) # Create tableless ActiveRecord model.
#------------------------------------------------------------------------------
class User < ActiveRecord::Base
def self.columns()
@columns ||= []
end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
end
column :id, :integer
column :name, :string
column :rank, :integer
column :admin, :boolean
column :created_at, :datetime
def self.table_exists?
true
end
end
class SubUser < User
def self.columns
User.columns
end
end
describe "AwesomePrint::ActiveRecord" do
before do
stub_dotfile!
end
# Create tableless ActiveRecord model.
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
class User < ActiveRecord::Base describe "ActiveRecord instance" do
def self.columns() before do
@columns ||= [] ActiveRecord::Base.default_timezone = :utc
@diana = User.new(:name => "Diana", :rank => 1, :admin => false, :created_at => "1992-10-10 12:30:00")
@laura = User.new(:name => "Laura", :rank => 2, :admin => true, :created_at => "2003-05-26 14:15:00")
@ap = AwesomePrint::Inspector.new(:plain => true)
end end
def self.column(name, sql_type = nil, default = nil, null = true) it "display single record" do
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null) out = @ap.send(:awesome, @diana)
end str = <<-EOS.strip
column :id, :integer
column :name, :string
column :rank, :integer
column :admin, :boolean
column :created_at, :datetime
def self.table_exists?
true
end
end
class SubUser < User
def self.columns
User.columns
end
end
describe "AwesomePrint/ActiveRecord" do
before(:each) do
stub_dotfile!
end
#------------------------------------------------------------------------------
describe "ActiveRecord instance" do
before(:each) do
ActiveRecord::Base.default_timezone = :utc
@diana = User.new(:name => "Diana", :rank => 1, :admin => false, :created_at => "1992-10-10 12:30:00")
@laura = User.new(:name => "Laura", :rank => 2, :admin => true, :created_at => "2003-05-26 14:15:00")
@ap = AwesomePrint.new(:plain => true)
end
it "display single record" do
out = @ap.send(:awesome, @diana)
str = <<-EOS.strip
#<User:0x01234567> { #<User:0x01234567> {
:id => nil, :id => nil,
:name => "Diana", :name => "Diana",
@ -59,18 +57,18 @@ if defined?(::ActiveRecord)
:created_at => ? :created_at => ?
} }
EOS EOS
if RUBY_VERSION.to_f < 1.9 if RUBY_VERSION.to_f < 1.9
str.sub!('?', 'Sat Oct 10 12:30:00 UTC 1992') str.sub!('?', 'Sat Oct 10 12:30:00 UTC 1992')
else else
str.sub!('?', '1992-10-10 12:30:00 UTC') str.sub!('?', '1992-10-10 12:30:00 UTC')
end
out.gsub(/0x([a-f\d]+)/, "0x01234567").should == str
end end
it "display multiple records" do out.gsub(/0x([a-f\d]+)/, "0x01234567").should == str
out = @ap.send(:awesome, [ @diana, @laura ]) end
str = <<-EOS.strip
it "display multiple records" do
out = @ap.send(:awesome, [ @diana, @laura ])
str = <<-EOS.strip
[ [
[0] #<User:0x01234567> { [0] #<User:0x01234567> {
:id => nil, :id => nil,
@ -88,23 +86,23 @@ EOS
} }
] ]
EOS EOS
if RUBY_VERSION.to_f < 1.9 if RUBY_VERSION.to_f < 1.9
str.sub!('?', 'Sat Oct 10 12:30:00 UTC 1992') str.sub!('?', 'Sat Oct 10 12:30:00 UTC 1992')
str.sub!('!', 'Mon May 26 14:15:00 UTC 2003') str.sub!('!', 'Mon May 26 14:15:00 UTC 2003')
else else
str.sub!('?', '1992-10-10 12:30:00 UTC') str.sub!('?', '1992-10-10 12:30:00 UTC')
str.sub!('!', '2003-05-26 14:15:00 UTC') str.sub!('!', '2003-05-26 14:15:00 UTC')
end
out.gsub(/0x([a-f\d]+)/, "0x01234567").should == str
end end
end
#------------------------------------------------------------------------------ out.gsub(/0x([a-f\d]+)/, "0x01234567").should == str
describe "ActiveRecord class" do end
it "should print the class" do end
@ap = AwesomePrint.new(:plain => true)
@ap.send(:awesome, User).should == <<-EOS.strip #------------------------------------------------------------------------------
describe "ActiveRecord class" do
it "should print the class" do
@ap = AwesomePrint::Inspector.new(:plain => true)
@ap.send(:awesome, User).should == <<-EOS.strip
class User < ActiveRecord::Base { class User < ActiveRecord::Base {
:id => :integer, :id => :integer,
:name => :string, :name => :string,
@ -117,7 +115,7 @@ class User < ActiveRecord::Base {
end end
it "should print the class for non-direct subclasses of AR::Base" do it "should print the class for non-direct subclasses of AR::Base" do
@ap = AwesomePrint.new(:plain => true) @ap = AwesomePrint::Inspector.new(:plain => true)
@ap.send(:awesome, SubUser).should == <<-EOS.strip @ap.send(:awesome, SubUser).should == <<-EOS.strip
class SubUser < User { class SubUser < User {
:id => :integer, :id => :integer,
@ -128,7 +126,6 @@ class SubUser < User {
} }
EOS EOS
end
end end
end end
end end

22
spec/active_support_spec.rb Executable file
View file

@ -0,0 +1,22 @@
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
require 'active_support'
require 'ap/mixin/active_support'
describe "AwesomePrint::ActiveSupport" do
before do
stub_dotfile!
@ap = AwesomePrint::Inspector.new()
end
it "should format ActiveSupport::TimeWithZone as regular Time" do
Time.zone = 'Eastern Time (US & Canada)'
time = Time.utc(2007, 2, 10, 20, 30, 45).in_time_zone
@ap.send(:awesome, time).should == "\e[0;32mSat, 10 Feb 2007 15:30:45 EST -05:00\e[0m"
end
it "should format HashWithIndifferentAccess as regular Hash" do
hash = HashWithIndifferentAccess.new({ :hello => "world" })
@ap.send(:awesome, hash).should == "{\n \"hello\"\e[0;37m => \e[0m\e[0;33m\"world\"\e[0m\n}"
end
end

View file

@ -3,12 +3,12 @@ require "bigdecimal"
require "rational" require "rational"
describe "AwesomePrint" do describe "AwesomePrint" do
before(:each) do before do
stub_dotfile! stub_dotfile!
end end
describe "Array" do describe "Array" do
before(:each) do before do
@arr = [ 1, :two, "three", [ nil, [ true, false] ] ] @arr = [ 1, :two, "three", [ nil, [ true, false] ] ]
end end
@ -129,7 +129,7 @@ EOS
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
describe "Nested Array" do describe "Nested Array" do
before(:each) do before do
@arr = [ 1, 2 ] @arr = [ 1, 2 ]
@arr << @arr @arr << @arr
end end
@ -161,7 +161,7 @@ EOS
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
describe "Hash" do describe "Hash" do
before(:each) do before do
@hash = { 1 => { :sym => { "str" => { [1, 2, 3] => { { :k => :v } => Hash } } } } } @hash = { 1 => { :sym => { "str" => { [1, 2, 3] => { { :k => :v } => Hash } } } } }
end end
@ -245,7 +245,7 @@ EOS
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
describe "Nested Hash" do describe "Nested Hash" do
before(:each) do before do
@hash = {} @hash = {}
@hash[:a] = @hash @hash[:a] = @hash
end end
@ -265,7 +265,7 @@ EOS
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
describe "Hash with several keys" do describe "Hash with several keys" do
before(:each) do before do
@hash = {"b" => "b", :a => "a", :z => "z", "alpha" => "alpha"} @hash = {"b" => "b", :a => "a", :z => "z", "alpha" => "alpha"}
end end
@ -290,7 +290,7 @@ EOS
end end
it "plain multiline with sorted keys" do it "plain multiline with sorted keys" do
@hash.ai(:plain => true, :sorted_hash_keys => true).should == <<-EOS.strip @hash.ai(:plain => true, :sort_keys => true).should == <<-EOS.strip
{ {
:a => "a", :a => "a",
"alpha" => "alpha", "alpha" => "alpha",
@ -304,7 +304,7 @@ EOS
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
describe "Negative options[:indent]" do describe "Negative options[:indent]" do
before(:each) do before do
@hash = { [0, 0, 255] => :yellow, :red => "rgb(255, 0, 0)", "magenta" => "rgb(255, 0, 255)" } @hash = { [0, 0, 255] => :yellow, :red => "rgb(255, 0, 0)", "magenta" => "rgb(255, 0, 255)" }
end end
@ -363,7 +363,7 @@ EOS
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
describe "Utility methods" do describe "Utility methods" do
it "should merge options" do it "should merge options" do
ap = AwesomePrint.new ap = AwesomePrint::Inspector.new
ap.send(:merge_options!, { :color => { :array => :black }, :indent => 0 }) ap.send(:merge_options!, { :color => { :array => :black }, :indent => 0 })
options = ap.instance_variable_get("@options") options = ap.instance_variable_get("@options")
options[:color][:array].should == :black options[:color][:array].should == :black
@ -374,7 +374,7 @@ EOS
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
describe "Struct" do describe "Struct" do
before(:each) do before do
@struct = unless defined?(Struct::SimpleStruct) @struct = unless defined?(Struct::SimpleStruct)
Struct.new("SimpleStruct", :name, :address).new Struct.new("SimpleStruct", :name, :address).new
else else

View file

@ -17,8 +17,8 @@ describe "AwesomePrint logging extensions" do
end end
describe "the log level" do describe "the log level" do
before(:each) do before do
AwesomePrint.defaults = { } AwesomePrint.defaults = {}
end end
it "should fallback to the default :debug log level" do it "should fallback to the default :debug log level" do