RubyGuides
Share this post!

Code Reading Adventures: Awesome Print Gem

Awesome print is a nice gem that formats your output in irb & pry to make it more readable.

For example…

This is what displaying a hash with awesome_print looks like:

ruby awesome print

But how does this work?

“Truth can only be found in one place: the code.” ― Robert C. Martin

Let’s take a look at the source code to find out!

Printing Awesomely

I like to start a code reading session with a quick overview of the project structure (files & folders), then I like to ask a question to focus my exploration going forward.

So the first question I came up with is:

How does awesome_print change the output of pry?

Now I put my detective hat on & make a hypothesis:

“This gem could be replacing $stdout so that it can capture the output of pry & then make it pretty.”

But this implementation wouldn’t allow you to customize your output, like pretty_print (from Ruby’s Standard Library) does.

Also, we would have to parse, or even eval code, not a great idea!

Next:

I looked into how this gem is loaded.

This will give us an entry point into the code.

Loading Awesome Print

To load awesome_print on pry you have to do this:

require 'awesome_print'
AwesomePrint.pry!

Now we want to find where pry! is defined.

I used the “search in directory” feature in Atom to find it.

Here’s the code:

def pry!
  Pry.print = proc { |output, value| output.puts value.ai } if defined?(Pry)
end

It seems like Pry allows you to modify its output by setting the value of print.

So this answers our “where to get started” question 🙂

Want to learn more about this “proc” thing? Read “The Ultimate Guide to Blocks, Procs & Lambdas”. This is a sample chapter from my Ruby Deep Dive book.

This proc takes two arguments:

  • output
  • value

And it calls two methods on those objects.

Next question:

What is this ai method?

It’s a method defined on the Kernel module:

def ai(options = {})
  ap      = AwesomePrint::Inspector.new(options)
  awesome = ap.awesome(self)

  if options[:html]
    awesome = "</pre>#{awesome}</pre>"
    awesome = awesome.html_safe if defined? ActiveSupport
  end

  awesome
end

alias :awesome_inspect :ai

Because all objects include Kernel by default, they will have this ai method available on them.

Now let’s dig a little deeper & see how this Inspector class works.

The Inspector Class

Right away after opening inspector.rb we find a huge options hash inside the initialize method.

Here’s part of it:

@options = {
  indent:        4,      # Number of spaces for indenting.
  index:         true,   # Display array indices.
  html:          false,  # Use ANSI color codes rather than HTML.
  multiline:     true,   # Display in multiple lines.
  # ...
}

After that we can find this code:

@formatter  = AwesomePrint::Formatter.new(self)
@indentator = AwesomePrint::Indentator.new(@options[:indent].abs)

Thread.current[AP] ||= []

So this sets up two more objects which seem to handle the formatting itself & indentation of the code.

But what’s with this Thread.current thing?

Well, this gives you access to the current thread. Even if you are not using threads in your application you will have one, the “main” thread.

This AP constant is just a constant which is defined at the top of inspector.rb:

AP = :__awesome_print__

So what’s happening here?

Awesome print is using Thread.current & the __awesome_print__ key to save some data that is only available on the current thread.

This is used to avoid problems with multi-threading.

Awesome Formatting

Let’s take a look at the output formatting code, which happens inside the AwesomePrint::Formatter class.

The way this works is that the inspector (the object created by the ai method) will call the format method.

def unnested(object)
  @formatter.format(object, printable(object))
end

Then this format method on the Formatter class will find the best way to deal with this type of object & call another method using a bit of metaprogramming.

Here’s the method:

def format(object, type = nil)
  core_class = cast(object, type)

  awesome = if core_class != :self
    send(:"awesome_#{core_class}", object) # Core formatters.
  else
    awesome_self(object, type) # Catch all that falls back to object.inspect.
  end

  awesome
end

To understand this Formatter class we also need to take a look at the cast method:

def cast(object, type)
  CORE.grep(type)[0] || :self
end

The CORE constant is an array of symbols representing core Ruby classes & :self is a symbol used to mean “not found” (just in this example, not in Ruby in general).

CORE = [:array, :bigdecimal, :class, :dir, :file, :hash, :method, :rational, :set, :struct, :unboundmethod]

What happens is this:

If the object being formatted is in the “core class” list then it will get a specialized format.

Otherwise, it will get a generic one.

Specialized formatters are defined under the lib/awesome_print/formatters/ directory & include things like Array, Hash & Class.

For example, here’s the formatter method for classes:

def format
  superclass = klass.superclass
  if superclass
    colorize("#{klass.inspect} < #{superclass}", :class)
  else
    colorize(klass.inspect, :class)
  end
end

You can write your own formatters if you want.

Summary

You have learned about the Awesome Print gem, which lets you display objects like arrays & hashes in a nice way.

Hope you enjoyed this & learned something new!

Please share this post on your favorite social network now so more people can learn 🙂