awesome_print 0.4.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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