awesome_print 0.4.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/.gitignore +22 -0
  2. data/CHANGELOG +8 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +26 -0
  5. data/README.md +52 -14
  6. data/Rakefile +2 -46
  7. data/lib/ap.rb +3 -17
  8. data/lib/awesome_print.rb +16 -12
  9. data/lib/{ap → awesome_print}/core_ext/array.rb +0 -0
  10. data/lib/{ap → awesome_print}/core_ext/class.rb +0 -0
  11. data/lib/{ap → awesome_print}/core_ext/kernel.rb +2 -2
  12. data/lib/awesome_print/core_ext/logger.rb +20 -0
  13. data/lib/{ap → awesome_print}/core_ext/method.rb +0 -0
  14. data/lib/{ap → awesome_print}/core_ext/object.rb +0 -0
  15. data/lib/{ap → awesome_print}/core_ext/string.rb +8 -5
  16. data/lib/awesome_print/ext/action_view.rb +18 -0
  17. data/lib/awesome_print/ext/active_record.rb +40 -0
  18. data/lib/awesome_print/ext/active_support.rb +40 -0
  19. data/lib/awesome_print/ext/mongo_mapper.rb +38 -0
  20. data/lib/awesome_print/ext/mongoid.rb +39 -0
  21. data/lib/awesome_print/ext/nokogiri.rb +45 -0
  22. data/lib/awesome_print/formatter.rb +350 -0
  23. data/lib/awesome_print/inspector.rb +140 -0
  24. data/{rails/init.rb → lib/awesome_print/version.rb} +5 -4
  25. data/spec/colors_spec.rb +106 -0
  26. data/spec/{awesome_print_spec.rb → formats_spec.rb} +187 -37
  27. data/spec/methods_spec.rb +20 -0
  28. data/spec/objects_spec.rb +79 -0
  29. data/spec/spec_helper.rb +1 -1
  30. metadata +49 -53
  31. data/VERSION +0 -1
  32. data/init.rb +0 -1
  33. data/lib/ap/awesome_print.rb +0 -352
  34. data/lib/ap/core_ext/logger.rb +0 -18
  35. data/lib/ap/mixin/action_view.rb +0 -17
  36. data/lib/ap/mixin/active_record.rb +0 -54
  37. data/lib/ap/mixin/active_support.rb +0 -46
  38. data/lib/ap/mixin/mongo_mapper.rb +0 -54
  39. data/spec/action_view_spec.rb +0 -25
  40. data/spec/active_record_spec.rb +0 -136
  41. data/spec/colorization_spec.rb +0 -84
  42. data/spec/logger_spec.rb +0 -43
  43. data/spec/mongo_mapper_spec.rb +0 -63
  44. data/spec/string_spec.rb +0 -20
@@ -0,0 +1,38 @@
1
+ # Copyright (c) 2010-2011 Michael Dvorkin
2
+ #
3
+ # Awesome Print is freely distributable under the terms of MIT license.
4
+ # See LICENSE file or http://www.opensource.org/licenses/mit-license.php
5
+ #------------------------------------------------------------------------------
6
+ module AwesomePrint
7
+ module MongoMapper
8
+
9
+ def self.included(base)
10
+ base.send :alias_method, :cast_without_mongo_mapper, :cast
11
+ base.send :alias_method, :cast, :cast_with_mongo_mapper
12
+ end
13
+
14
+ # Add MongoMapper class names to the dispatcher pipeline.
15
+ #------------------------------------------------------------------------------
16
+ def cast_with_mongo_mapper(object, type)
17
+ cast = cast_without_mongo_mapper(object, type)
18
+ if defined?(::MongoMapper::Document) && object.is_a?(Class) && (object.ancestors & [ ::MongoMapper::Document, ::MongoMapper::EmbeddedDocument ]).size > 0
19
+ cast = :mongo_mapper_class
20
+ end
21
+ cast
22
+ end
23
+
24
+ # Format MongoMapper class object.
25
+ #------------------------------------------------------------------------------
26
+ def awesome_mongo_mapper_class(object)
27
+ return object.inspect if !defined?(::ActiveSupport::OrderedHash) || !object.respond_to?(:keys)
28
+
29
+ data = object.keys.sort.inject(::ActiveSupport::OrderedHash.new) do |hash, c|
30
+ hash[c.first] = (c.last.type || "undefined").to_s.underscore.intern
31
+ hash
32
+ end
33
+ "class #{object} < #{object.superclass} " << awesome_hash(data)
34
+ end
35
+ end
36
+ end
37
+
38
+ AwesomePrint::Formatter.send(:include, AwesomePrint::MongoMapper)
@@ -0,0 +1,39 @@
1
+ # Copyright (c) 2010-2011 Michael Dvorkin
2
+ #
3
+ # Awesome Print is freely distributable under the terms of MIT license.
4
+ # See LICENSE file or http://www.opensource.org/licenses/mit-license.php
5
+ #------------------------------------------------------------------------------
6
+ module AwesomePrint
7
+ module Mongoid
8
+
9
+ def self.included(base)
10
+ base.send :alias_method, :cast_without_mongoid, :cast
11
+ base.send :alias_method, :cast, :cast_with_mongoid
12
+ end
13
+
14
+ # Add Mongoid class names to the dispatcher pipeline.
15
+ #------------------------------------------------------------------------------
16
+ def cast_with_mongoid(object, type)
17
+ cast = cast_without_mongoid(object, type)
18
+ if defined?(::Mongoid::Document) && object.is_a?(Class) && object.ancestors.include?(::Mongoid::Document)
19
+ cast = :mongoid_class
20
+ end
21
+ cast
22
+ end
23
+
24
+ # Format Mongoid class object.
25
+ #------------------------------------------------------------------------------
26
+ def awesome_mongoid_class(object)
27
+ return object.inspect if !defined?(::ActiveSupport::OrderedHash) || !object.respond_to?(:fields)
28
+
29
+ data = object.fields.inject(::ActiveSupport::OrderedHash.new) do |hash, c|
30
+ hash[c[1].name] = (c[1].type || "undefined").to_s.underscore.intern
31
+ # hash[c[1].name] = (c[1].type || "undefined").to_s.underscore.intern rescue c[1].type
32
+ hash
33
+ end
34
+ "class #{object} < #{object.superclass} " << awesome_hash(data)
35
+ end
36
+ end
37
+ end
38
+
39
+ AwesomePrint::Formatter.send(:include, AwesomePrint::Mongoid)
@@ -0,0 +1,45 @@
1
+ # Copyright (c) 2010-2011 Michael Dvorkin
2
+ #
3
+ # Awesome Print is freely distributable under the terms of MIT license.
4
+ # See LICENSE file or http://www.opensource.org/licenses/mit-license.php
5
+ #------------------------------------------------------------------------------
6
+ module AwesomePrint
7
+ module Nokogiri
8
+
9
+ def self.included(base)
10
+ base.send :alias_method, :cast_without_nokogiri, :cast
11
+ base.send :alias_method, :cast, :cast_with_nokogiri
12
+ end
13
+
14
+ # Add Nokogiri XML Node and NodeSet names to the dispatcher pipeline.
15
+ #------------------------------------------------------------------------------
16
+ def cast_with_nokogiri(object, type)
17
+ cast = cast_without_nokogiri(object, type)
18
+ if (defined?(::Nokogiri::XML::Node) && object.is_a?(::Nokogiri::XML::Node)) ||
19
+ (defined?(::Nokogiri::XML::NodeSet) && object.is_a?(::Nokogiri::XML::NodeSet))
20
+ cast = :nokogiri_xml_node
21
+ end
22
+ cast
23
+ end
24
+
25
+ #------------------------------------------------------------------------------
26
+ def awesome_nokogiri_xml_node(object)
27
+ if object.is_a?(::Nokogiri::XML::NodeSet) && object.empty?
28
+ return "[]"
29
+ end
30
+ xml = object.to_xml(:indent => 2)
31
+ #
32
+ # Colorize tag, id/class name, and contents.
33
+ #
34
+ xml.gsub!(/(<)(\/?[A-Za-z1-9]+)/) { |tag| "#{$1}#{colorize($2, :keyword)}" }
35
+ xml.gsub!(/(id|class)="[^"]+"/i) { |id| colorize(id, :class) }
36
+ xml.gsub!(/>([^<]+)</) do |contents|
37
+ contents = colorize($1, :trueclass) if contents && !contents.empty?
38
+ ">#{contents}<"
39
+ end
40
+ xml
41
+ end
42
+ end
43
+ end
44
+
45
+ AwesomePrint::Formatter.send(:include, AwesomePrint::Nokogiri)
@@ -0,0 +1,350 @@
1
+ # Copyright (c) 2010-2011 Michael Dvorkin
2
+ #
3
+ # Awesome Print is freely distributable under the terms of MIT license.
4
+ # See LICENSE file or http://www.opensource.org/licenses/mit-license.php
5
+ #------------------------------------------------------------------------------
6
+ require "cgi"
7
+ require "shellwords"
8
+
9
+ module AwesomePrint
10
+ class Formatter
11
+
12
+ CORE = [ :array, :hash, :class, :file, :dir, :bigdecimal, :rational, :struct, :method, :unboundmethod ]
13
+ DEFAULT_LIMIT_SIZE = 7
14
+
15
+ def initialize(inspector)
16
+ @inspector = inspector
17
+ @options = inspector.options
18
+ @indentation = @options[:indent].abs
19
+ end
20
+
21
+ # Main entry point to format an object.
22
+ #------------------------------------------------------------------------------
23
+ def format(object, type = nil)
24
+ core_class = cast(object, type)
25
+ awesome = if core_class != :self
26
+ send(:"awesome_#{core_class}", object) # Core formatters.
27
+ else
28
+ awesome_self(object, type) # Catch all that falls back on object.inspect.
29
+ end
30
+ @options[:html] ? "<pre>#{awesome}</pre>" : awesome
31
+ end
32
+
33
+ # Hook this when adding custom formatters.
34
+ #------------------------------------------------------------------------------
35
+ def cast(object, type)
36
+ CORE.grep(type)[0] || :self
37
+ end
38
+
39
+ # Pick the color and apply it to the given string as necessary.
40
+ #------------------------------------------------------------------------------
41
+ def colorize(s, type)
42
+ s = CGI.escapeHTML(s) if @options[:html]
43
+ if @options[:plain] || !@options[:color][type] || !@inspector.colorize?
44
+ s
45
+ else
46
+ s.send(@options[:color][type], @options[:html])
47
+ end
48
+ end
49
+
50
+
51
+ private
52
+
53
+ # Catch all method to format an arbitrary object.
54
+ #------------------------------------------------------------------------------
55
+ def awesome_self(object, type)
56
+ return awesome_object(object) if object.instance_variables.any?
57
+ colorize(object.inspect.to_s, type)
58
+ end
59
+
60
+ # Format an array.
61
+ #------------------------------------------------------------------------------
62
+ def awesome_array(a)
63
+ return "[]" if a == []
64
+
65
+ if a.instance_variable_defined?('@__awesome_methods__')
66
+ methods_array(a)
67
+ elsif @options[:multiline]
68
+ width = (a.size - 1).to_s.size
69
+
70
+ data = a.inject([]) do |arr, item|
71
+ index = indent
72
+ index << colorize("[#{arr.size.to_s.rjust(width)}] ", :array) if @options[:index]
73
+ indented do
74
+ arr << (index << @inspector.awesome(item))
75
+ end
76
+ end
77
+
78
+ data = limited(data, width) if should_be_limited?
79
+ "[\n" << data.join(",\n") << "\n#{outdent}]"
80
+ else
81
+ "[ " << a.map{ |item| @inspector.awesome(item) }.join(", ") << " ]"
82
+ end
83
+ end
84
+
85
+ # Format a hash. If @options[:indent] if negative left align hash keys.
86
+ #------------------------------------------------------------------------------
87
+ def awesome_hash(h)
88
+ return "{}" if h == {}
89
+
90
+ keys = @options[:sort_keys] ? h.keys.sort { |a, b| a.to_s <=> b.to_s } : h.keys
91
+ data = keys.map do |key|
92
+ plain_single_line do
93
+ [ @inspector.awesome(key), h[key] ]
94
+ end
95
+ end
96
+
97
+ width = data.map { |key, | key.size }.max || 0
98
+ width += @indentation if @options[:indent] > 0
99
+
100
+ data = data.map do |key, value|
101
+ indented do
102
+ align(key, width) << colorize(" => ", :hash) << @inspector.awesome(value)
103
+ end
104
+ end
105
+
106
+ data = limited(data, width, :hash => true) if should_be_limited?
107
+ if @options[:multiline]
108
+ "{\n" << data.join(",\n") << "\n#{outdent}}"
109
+ else
110
+ "{ #{data.join(', ')} }"
111
+ end
112
+ end
113
+
114
+ # Format an object.
115
+ #------------------------------------------------------------------------------
116
+ def awesome_object(o)
117
+ vars = o.instance_variables.map do |var|
118
+ property = var[1..-1].to_sym
119
+ accessor = if o.respond_to?(:"#{property}=")
120
+ o.respond_to?(property) ? :accessor : :writer
121
+ else
122
+ o.respond_to?(property) ? :reader : nil
123
+ end
124
+ if accessor
125
+ [ "attr_#{accessor} :#{property}", var ]
126
+ else
127
+ [ var.to_s, var ]
128
+ end
129
+ end
130
+
131
+ data = vars.sort.map do |declaration, var|
132
+ key = left_aligned do
133
+ align(declaration, declaration.size)
134
+ end
135
+
136
+ unless @options[:plain]
137
+ if key =~ /(@\w+)/
138
+ key.sub!($1, colorize($1, :variable))
139
+ else
140
+ key.sub!(/(attr_\w+)\s(\:\w+)/, "#{colorize('\\1', :keyword)} #{colorize('\\2', :method)}")
141
+ end
142
+ end
143
+ indented do
144
+ key << colorize(" = ", :hash) + @inspector.awesome(o.instance_variable_get(var))
145
+ end
146
+ end
147
+ if @options[:multiline]
148
+ "#<#{awesome_instance(o)}\n#{data.join(%Q/,\n/)}\n#{outdent}>"
149
+ else
150
+ "#<#{awesome_instance(o)} #{data.join(', ')}>"
151
+ end
152
+ end
153
+
154
+ # Format a Struct.
155
+ #------------------------------------------------------------------------------
156
+ def awesome_struct(s)
157
+ #
158
+ # The code is slightly uglier because of Ruby 1.8.6 quirks:
159
+ # awesome_hash(Hash[s.members.zip(s.values)]) <-- ArgumentError: odd number of arguments for Hash)
160
+ # awesome_hash(Hash[*s.members.zip(s.values).flatten]) <-- s.members returns strings, not symbols.
161
+ #
162
+ hash = {}
163
+ s.each_pair { |key, value| hash[key] = value }
164
+ awesome_hash(hash)
165
+ end
166
+
167
+ # Format Class object.
168
+ #------------------------------------------------------------------------------
169
+ def awesome_class(c)
170
+ if superclass = c.superclass # <-- Assign and test if nil.
171
+ colorize("#{c.inspect} < #{superclass}", :class)
172
+ else
173
+ colorize(c.inspect, :class)
174
+ end
175
+ end
176
+
177
+ # Format File object.
178
+ #------------------------------------------------------------------------------
179
+ def awesome_file(f)
180
+ ls = File.directory?(f) ? `ls -adlF #{f.path.shellescape}` : `ls -alF #{f.path.shellescape}`
181
+ colorize(ls.empty? ? f.inspect : "#{f.inspect}\n#{ls.chop}", :file)
182
+ end
183
+
184
+ # Format Dir object.
185
+ #------------------------------------------------------------------------------
186
+ def awesome_dir(d)
187
+ ls = `ls -alF #{d.path.shellescape}`
188
+ colorize(ls.empty? ? d.inspect : "#{d.inspect}\n#{ls.chop}", :dir)
189
+ end
190
+
191
+ # Format BigDecimal and Rational objects by convering them to Float.
192
+ #------------------------------------------------------------------------------
193
+ def awesome_bigdecimal(n)
194
+ colorize(n.to_f.inspect, :bigdecimal)
195
+ end
196
+ alias :awesome_rational :awesome_bigdecimal
197
+
198
+ # Format a method.
199
+ #------------------------------------------------------------------------------
200
+ def awesome_method(m)
201
+ name, args, owner = method_tuple(m)
202
+ "#{colorize(owner, :class)}##{colorize(name, :method)}#{colorize(args, :args)}"
203
+ end
204
+ alias :awesome_unboundmethod :awesome_method
205
+
206
+ # Format object instance.
207
+ #------------------------------------------------------------------------------
208
+ def awesome_instance(o)
209
+ "#{o.class}:0x%08x" % (o.__id__ * 2)
210
+ end
211
+
212
+ # Format object.methods array.
213
+ #------------------------------------------------------------------------------
214
+ def methods_array(a)
215
+ object = a.instance_variable_get('@__awesome_methods__')
216
+ tuples = a.map do |name|
217
+ tuple = if object.respond_to?(name, true) # Is this a regular method?
218
+ the_method = object.method(name) rescue nil # Avoid potential ArgumentError if object#method is overridden.
219
+ if the_method && the_method.respond_to?(:arity) # Is this original object#method?
220
+ method_tuple(the_method) # Yes, we are good.
221
+ end
222
+ elsif object.respond_to?(:instance_method) # Is this an unbound method?
223
+ method_tuple(object.instance_method(name))
224
+ end
225
+ tuple || [ name.to_s, '(?)', '' ] # Return WTF default if all the above fails.
226
+ end
227
+
228
+ width = (tuples.size - 1).to_s.size
229
+ name_width = tuples.map { |item| item[0].size }.max || 0
230
+ args_width = tuples.map { |item| item[1].size }.max || 0
231
+
232
+ data = tuples.inject([]) do |arr, item|
233
+ index = indent
234
+ index << "[#{arr.size.to_s.rjust(width)}]" if @options[:index]
235
+ indented do
236
+ arr << "#{index} #{colorize(item[0].rjust(name_width), :method)}#{colorize(item[1].ljust(args_width), :args)} #{colorize(item[2], :class)}"
237
+ end
238
+ end
239
+
240
+ "[\n" << data.join("\n") << "\n#{outdent}]"
241
+ end
242
+
243
+ # Return [ name, arguments, owner ] tuple for a given method.
244
+ #------------------------------------------------------------------------------
245
+ def method_tuple(method)
246
+ if method.respond_to?(:parameters) # Ruby 1.9.2+
247
+ # See http://ruby.runpaint.org/methods#method-objects-parameters
248
+ args = method.parameters.inject([]) do |arr, (type, name)|
249
+ name ||= (type == :block ? 'block' : "arg#{arr.size + 1}")
250
+ arr << case type
251
+ when :req then name.to_s
252
+ when :opt, :rest then "*#{name}"
253
+ when :block then "&#{name}"
254
+ else '?'
255
+ end
256
+ end
257
+ else # See http://ruby-doc.org/core/classes/Method.html#M001902
258
+ args = (1..method.arity.abs).map { |i| "arg#{i}" }
259
+ args[-1] = "*#{args[-1]}" if method.arity < 0
260
+ end
261
+
262
+ if method.to_s =~ /(Unbound)*Method: (.*?)[#\.]/
263
+ owner = "#{$2}#{$1 ? '(unbound)' : ''}".gsub('(', ' (')
264
+ end
265
+
266
+ [ method.name.to_s, "(#{args.join(', ')})", owner.to_s ]
267
+ end
268
+
269
+ # Format hash keys as plain string regardless of underlying data type.
270
+ #------------------------------------------------------------------------------
271
+ def plain_single_line
272
+ plain, multiline = @options[:plain], @options[:multiline]
273
+ @options[:plain], @options[:multiline] = true, false
274
+ yield
275
+ ensure
276
+ @options[:plain], @options[:multiline] = plain, multiline
277
+ end
278
+
279
+ # Utility methods.
280
+ #------------------------------------------------------------------------------
281
+ def align(value, width)
282
+ if @options[:multiline]
283
+ if @options[:indent] > 0
284
+ value.rjust(width)
285
+ elsif @options[:indent] == 0
286
+ indent + value.ljust(width)
287
+ else
288
+ indent[0, @indentation + @options[:indent]] + value.ljust(width)
289
+ end
290
+ else
291
+ value
292
+ end
293
+ end
294
+
295
+ def indented
296
+ @indentation += @options[:indent].abs
297
+ yield
298
+ ensure
299
+ @indentation -= @options[:indent].abs
300
+ end
301
+
302
+ def left_aligned
303
+ current, @options[:indent] = @options[:indent], 0
304
+ yield
305
+ ensure
306
+ @options[:indent] = current
307
+ end
308
+
309
+ def indent
310
+ ' ' * @indentation
311
+ end
312
+
313
+ def outdent
314
+ ' ' * (@indentation - @options[:indent].abs)
315
+ end
316
+
317
+ # To support limited output.
318
+ #------------------------------------------------------------------------------
319
+ def should_be_limited?
320
+ @options[:limit] == true or (@options[:limit].is_a?(Fixnum) and @options[:limit] > 0)
321
+ end
322
+
323
+ def get_limit_size
324
+ @options[:limit] == true ? DEFAULT_LIMIT_SIZE : @options[:limit]
325
+ end
326
+
327
+ def limited(data, width, is_hash = false)
328
+ limit = get_limit_size
329
+ if data.length <= limit
330
+ data
331
+ else
332
+ # Calculate how many elements to be displayed above and below the separator.
333
+ head = limit / 2
334
+ tail = head - (limit - 1) % 2
335
+
336
+ # Add the proper elements to the temp array and format the separator.
337
+ temp = data[0, head] + [ nil ] + data[-tail, tail]
338
+
339
+ if is_hash
340
+ temp[head] = "#{indent}#{data[head].strip} .. #{data[data.length - tail - 1].strip}"
341
+ else
342
+ temp[head] = "#{indent}[#{head.to_s.rjust(width)}] .. [#{data.length - tail - 1}]"
343
+ end
344
+
345
+ temp
346
+ end
347
+ end
348
+
349
+ end
350
+ end