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:
parent
0f14846fcd
commit
dfbebd4887
15 changed files with 568 additions and 523 deletions
|
@ -7,7 +7,8 @@
|
|||
require File.dirname(__FILE__) + "/ap/core_ext/#{file}"
|
||||
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/mixin/action_view" if defined?(ActionView)
|
||||
|
||||
|
|
|
@ -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
|
|
@ -6,7 +6,7 @@
|
|||
module Kernel
|
||||
|
||||
def ai(options = {})
|
||||
ap = AwesomePrint.new(options)
|
||||
ap = AwesomePrint::Inspector.new(options)
|
||||
ap.send(:awesome, self)
|
||||
end
|
||||
alias :awesome_inspect :ai
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
# Awesome Print is freely distributable under the terms of MIT license.
|
||||
# See LICENSE file or http://www.opensource.org/licenses/mit-license.php
|
||||
#------------------------------------------------------------------------------
|
||||
module AwesomePrintLogger
|
||||
module AwesomePrint
|
||||
module Logger
|
||||
|
||||
# Add ap method to logger
|
||||
#------------------------------------------------------------------------------
|
||||
|
@ -11,8 +12,8 @@ module AwesomePrintLogger
|
|||
level ||= AwesomePrint.defaults[:log_level] || :debug
|
||||
send level, object.ai
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
Logger.send(:include, AwesomePrintLogger)
|
||||
ActiveSupport::BufferedLogger.send(:include, AwesomePrintLogger) if defined?(::ActiveSupport::BufferedLogger)
|
||||
Logger.send(:include, AwesomePrint::Logger)
|
||||
ActiveSupport::BufferedLogger.send(:include, AwesomePrint::Logger) if defined?(::ActiveSupport::BufferedLogger)
|
||||
|
|
238
lib/ap/formatter.rb
Executable file
238
lib/ap/formatter.rb
Executable 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
122
lib/ap/inspector.rb
Executable 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
|
|
@ -3,7 +3,8 @@
|
|||
# Awesome Print is freely distributable under the terms of MIT license.
|
||||
# See LICENSE file or http://www.opensource.org/licenses/mit-license.php
|
||||
#------------------------------------------------------------------------------
|
||||
module AwesomePrintActionView
|
||||
module AwesomePrint
|
||||
module ActionView
|
||||
|
||||
def self.included(base)
|
||||
unless base.const_defined?(:AP_ANSI_TO_HTML)
|
||||
|
@ -32,7 +33,7 @@ module AwesomePrintActionView
|
|||
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)
|
||||
|
|
|
@ -3,35 +3,34 @@
|
|||
# Awesome Print is freely distributable under the terms of MIT license.
|
||||
# See LICENSE file or http://www.opensource.org/licenses/mit-license.php
|
||||
#------------------------------------------------------------------------------
|
||||
module AwesomePrintActiveRecord
|
||||
module AwesomePrint
|
||||
module ActiveRecord
|
||||
|
||||
def self.included(base)
|
||||
base.send :alias_method, :printable_without_active_record, :printable
|
||||
base.send :alias_method, :printable, :printable_with_active_record
|
||||
base.send :alias_method, :cast_without_active_record, :cast
|
||||
base.send :alias_method, :cast, :cast_with_active_record
|
||||
end
|
||||
|
||||
# Add ActiveRecord class names to the dispatcher pipeline.
|
||||
#------------------------------------------------------------------------------
|
||||
def printable_with_active_record(object)
|
||||
printable = printable_without_active_record(object)
|
||||
return printable if !defined?(ActiveRecord::Base)
|
||||
def cast_with_active_record(object, type)
|
||||
cast = cast_without_active_record(object, type)
|
||||
if defined?(::ActiveRecord)
|
||||
if object.is_a?(::ActiveRecord::Base)
|
||||
cast = :active_record_instance
|
||||
elsif object.is_a?(Class) and object.ancestors.include?(::ActiveRecord::Base)
|
||||
cast = :active_record_class
|
||||
end
|
||||
end
|
||||
cast
|
||||
end
|
||||
|
||||
if printable == :self
|
||||
if object.is_a?(ActiveRecord::Base)
|
||||
printable = :active_record_instance
|
||||
end
|
||||
elsif printable == :class and object.ancestors.include?(ActiveRecord::Base)
|
||||
printable = :active_record_class
|
||||
end
|
||||
printable
|
||||
end
|
||||
private
|
||||
|
||||
# Format ActiveRecord instance object.
|
||||
#------------------------------------------------------------------------------
|
||||
def awesome_active_record_instance(object)
|
||||
return object.inspect if !defined?(ActiveSupport::OrderedHash)
|
||||
return object.inspect if !defined?(::ActiveSupport::OrderedHash)
|
||||
|
||||
data = object.class.column_names.inject(ActiveSupport::OrderedHash.new) do |hash, name|
|
||||
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
|
||||
|
@ -41,14 +40,15 @@ module AwesomePrintActiveRecord
|
|||
# Format ActiveRecord 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
|
||||
end
|
||||
"class #{object} < #{object.superclass} " << awesome_hash(data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
AwesomePrint.send(:include, AwesomePrintActiveRecord)
|
||||
AwesomePrint::Formatter.send(:include, AwesomePrint::ActiveRecord)
|
||||
|
|
|
@ -3,27 +3,24 @@
|
|||
# Awesome Print is freely distributable under the terms of MIT license.
|
||||
# See LICENSE file or http://www.opensource.org/licenses/mit-license.php
|
||||
#------------------------------------------------------------------------------
|
||||
module AwesomePrintActiveSupport
|
||||
module AwesomePrint
|
||||
module ActiveSupport
|
||||
|
||||
def self.included(base)
|
||||
base.send :alias_method, :printable_without_active_support, :printable
|
||||
base.send :alias_method, :printable, :printable_with_active_support
|
||||
base.send :alias_method, :cast_without_active_support, :cast
|
||||
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
|
||||
def cast_with_active_support(object, type)
|
||||
cast = cast_without_active_support(object, type)
|
||||
if defined?(::ActiveSupport) && defined?(::HashWithIndifferentAccess)
|
||||
if object.is_a?(::ActiveSupport::TimeWithZone)
|
||||
cast = :active_support_time
|
||||
elsif object.is_a?(::HashWithIndifferentAccess)
|
||||
cast = :hash_with_indifferent_access
|
||||
end
|
||||
end
|
||||
printable
|
||||
cast
|
||||
end
|
||||
|
||||
# Format ActiveSupport::TimeWithZone as standard Time.
|
||||
|
@ -33,14 +30,11 @@ module AwesomePrintActiveSupport
|
|||
end
|
||||
|
||||
# Format HashWithIndifferentAccess as standard Hash.
|
||||
#
|
||||
# NOTE: can't use awesome_self(object, :as => :hash) since awesome_self uses
|
||||
# object.inspect internally, i.e. it would convert hash to string.
|
||||
#------------------------------------------------------------------------------
|
||||
def awesome_hash_with_indifferent_access(object)
|
||||
awesome_hash(object)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
AwesomePrint.send(:include, AwesomePrintActiveSupport)
|
||||
AwesomePrint::Formatter.send(:include, AwesomePrint::ActiveSupport)
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
require File.dirname(__FILE__) + "/ap/core_ext/#{file}"
|
||||
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/mixin/action_view" if defined?(ActionView)
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ require 'action_view'
|
|||
require 'ap/mixin/action_view'
|
||||
|
||||
describe "AwesomePrint ActionView extensions" do
|
||||
before(:each) do
|
||||
before do
|
||||
@view = ActionView::Base.new
|
||||
end
|
||||
|
||||
|
|
|
@ -4,11 +4,9 @@ require 'active_record'
|
|||
require 'ap/mixin/active_record'
|
||||
|
||||
|
||||
if defined?(::ActiveRecord)
|
||||
|
||||
# Create tableless ActiveRecord model.
|
||||
#------------------------------------------------------------------------------
|
||||
class User < ActiveRecord::Base
|
||||
# Create tableless ActiveRecord model.
|
||||
#------------------------------------------------------------------------------
|
||||
class User < ActiveRecord::Base
|
||||
def self.columns()
|
||||
@columns ||= []
|
||||
end
|
||||
|
@ -26,26 +24,26 @@ if defined?(::ActiveRecord)
|
|||
def self.table_exists?
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class SubUser < User
|
||||
class SubUser < User
|
||||
def self.columns
|
||||
User.columns
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "AwesomePrint/ActiveRecord" do
|
||||
before(:each) do
|
||||
describe "AwesomePrint::ActiveRecord" do
|
||||
before do
|
||||
stub_dotfile!
|
||||
end
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
describe "ActiveRecord instance" do
|
||||
before(:each) do
|
||||
before 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)
|
||||
@ap = AwesomePrint::Inspector.new(:plain => true)
|
||||
end
|
||||
|
||||
it "display single record" do
|
||||
|
@ -103,7 +101,7 @@ EOS
|
|||
#------------------------------------------------------------------------------
|
||||
describe "ActiveRecord class" do
|
||||
it "should print the class" do
|
||||
@ap = AwesomePrint.new(:plain => true)
|
||||
@ap = AwesomePrint::Inspector.new(:plain => true)
|
||||
@ap.send(:awesome, User).should == <<-EOS.strip
|
||||
class User < ActiveRecord::Base {
|
||||
:id => :integer,
|
||||
|
@ -117,7 +115,7 @@ class User < ActiveRecord::Base {
|
|||
end
|
||||
|
||||
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
|
||||
class SubUser < User {
|
||||
:id => :integer,
|
||||
|
@ -130,5 +128,4 @@ class SubUser < User {
|
|||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
22
spec/active_support_spec.rb
Executable file
22
spec/active_support_spec.rb
Executable 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
|
|
@ -3,12 +3,12 @@ require "bigdecimal"
|
|||
require "rational"
|
||||
|
||||
describe "AwesomePrint" do
|
||||
before(:each) do
|
||||
before do
|
||||
stub_dotfile!
|
||||
end
|
||||
|
||||
describe "Array" do
|
||||
before(:each) do
|
||||
before do
|
||||
@arr = [ 1, :two, "three", [ nil, [ true, false] ] ]
|
||||
end
|
||||
|
||||
|
@ -129,7 +129,7 @@ EOS
|
|||
|
||||
#------------------------------------------------------------------------------
|
||||
describe "Nested Array" do
|
||||
before(:each) do
|
||||
before do
|
||||
@arr = [ 1, 2 ]
|
||||
@arr << @arr
|
||||
end
|
||||
|
@ -161,7 +161,7 @@ EOS
|
|||
|
||||
#------------------------------------------------------------------------------
|
||||
describe "Hash" do
|
||||
before(:each) do
|
||||
before do
|
||||
@hash = { 1 => { :sym => { "str" => { [1, 2, 3] => { { :k => :v } => Hash } } } } }
|
||||
end
|
||||
|
||||
|
@ -245,7 +245,7 @@ EOS
|
|||
|
||||
#------------------------------------------------------------------------------
|
||||
describe "Nested Hash" do
|
||||
before(:each) do
|
||||
before do
|
||||
@hash = {}
|
||||
@hash[:a] = @hash
|
||||
end
|
||||
|
@ -265,7 +265,7 @@ EOS
|
|||
|
||||
#------------------------------------------------------------------------------
|
||||
describe "Hash with several keys" do
|
||||
before(:each) do
|
||||
before do
|
||||
@hash = {"b" => "b", :a => "a", :z => "z", "alpha" => "alpha"}
|
||||
end
|
||||
|
||||
|
@ -290,7 +290,7 @@ EOS
|
|||
end
|
||||
|
||||
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",
|
||||
"alpha" => "alpha",
|
||||
|
@ -304,7 +304,7 @@ EOS
|
|||
|
||||
#------------------------------------------------------------------------------
|
||||
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)" }
|
||||
end
|
||||
|
||||
|
@ -363,7 +363,7 @@ EOS
|
|||
#------------------------------------------------------------------------------
|
||||
describe "Utility methods" do
|
||||
it "should merge options" do
|
||||
ap = AwesomePrint.new
|
||||
ap = AwesomePrint::Inspector.new
|
||||
ap.send(:merge_options!, { :color => { :array => :black }, :indent => 0 })
|
||||
options = ap.instance_variable_get("@options")
|
||||
options[:color][:array].should == :black
|
||||
|
@ -374,7 +374,7 @@ EOS
|
|||
|
||||
#------------------------------------------------------------------------------
|
||||
describe "Struct" do
|
||||
before(:each) do
|
||||
before do
|
||||
@struct = unless defined?(Struct::SimpleStruct)
|
||||
Struct.new("SimpleStruct", :name, :address).new
|
||||
else
|
||||
|
|
|
@ -17,8 +17,8 @@ describe "AwesomePrint logging extensions" do
|
|||
end
|
||||
|
||||
describe "the log level" do
|
||||
before(:each) do
|
||||
AwesomePrint.defaults = { }
|
||||
before do
|
||||
AwesomePrint.defaults = {}
|
||||
end
|
||||
|
||||
it "should fallback to the default :debug log level" do
|
||||
|
|
Loading…
Reference in a new issue