DEV Community

Cover image for Here be docs
Franciscello
Franciscello

Posted on

Here be docs

The other day, while making some changes in the Crystal formatter tool, I learned about heredoc and how to use multiple heredocs as methods' arguments.

Let's start with ...

What is a Heredoc?

The documentation says:

A here document or heredoc can be useful for writing strings spanning over multiple lines.

And continues explaining how they are defined:

A heredoc is denoted by <<- followed by a heredoc identifier [...]. The heredoc starts in the following line and ends with the next line that contains only the heredoc identifier.

How to use heredocs?

The following is an example on how to define a heredoc:

<<-HERE_BE_DOC
Hello World
Beware, here be dragons!
HERE_BE_DOC
Enter fullscreen mode Exit fullscreen mode

A heredoc (as any other String) can be assigned to a variable:

dragons = <<-HERE_BE_DOC
Hello World
Beware, here be dragons!
HERE_BE_DOC

puts dragons
Enter fullscreen mode Exit fullscreen mode

This example outputs:

Hello World
Beware, here be dragons!
Enter fullscreen mode Exit fullscreen mode

And of course, we can use them as a method's argument:

def string_length(str : String)
  str.size
end

puts string_length <<-HERE_BE_DOC
   Hello World
   Beware, here be dragons!
HERE_BE_DOC
Enter fullscreen mode Exit fullscreen mode

This example outputs:

42
Enter fullscreen mode Exit fullscreen mode

Note: It keeps original newlines and spaces! 🤩

Heredoc and methods

So, if a heredoc is a way of defining a String, then a heredoc responds to all String methods (because it is a String). The last example shows exactly this when using the method #size.

So, let's try the following example:

"Hello World".size # => 11
Enter fullscreen mode Exit fullscreen mode

but using a heredoc:

puts <<-WORLD
Hello World
WORLD.size
Enter fullscreen mode Exit fullscreen mode
Error: Unterminated heredoc: can't find "WORLD" anywhere before the end of file
Enter fullscreen mode Exit fullscreen mode

Oh! It cannot find the end of the heredoc (meaning the compiler "is reading" WORLD.size as an entire word) 🤔 ... wait 🤓💡 let's fix it like this:

<<-WORLD
Hello World
WORLD
.size # => 11
Enter fullscreen mode Exit fullscreen mode

It worked 🤓🎉 but this solution feels more like a hack. Let's go to the documentation to find the correct (although not so intuitive) way of writing our example:

After the heredoc identifier, and in that same line, anything that follows continues the original expression that came before the heredoc. It's as if the end of the starting heredoc identifier is the end of the string.

So the following should work:

<<-WORLD.size # => 11
Hello World
WORLD
Enter fullscreen mode Exit fullscreen mode

Great! It's working!
(We should pin this as it might be helpful later. 📌)

Using multiple heredocs

As I was saying at the beginning of this post, I was writing some tests using the method assert_format. Let's focus on the first two arguments of this method: the first argument is the input for the formatter and the second is the expected result after formatting the input.

Since we are formatting source code, using heredoc would be really handy (because we can use indentation and newlines making the code to be formatted easy to read), except for one single detail: the incorrect way I was trying to use it 🙈

I wrote:

assert_format <<-BEFORE
  alias Foo=
  Bar
  BEFORE, 
  <<-AFTER
  alias Foo = Bar
  AFTER
Enter fullscreen mode Exit fullscreen mode

and the compiler was returning:

Error: Unterminated heredoc: can't find "BEFORE" anywhere before the end of file
Enter fullscreen mode Exit fullscreen mode

This sounds familiar, right? Remember the WORLD.size example? Well, we have the same problem here with BEFORE,.

And the solution is the same described in the docs. We should pass both heredocs to the method like this:

assert_format <<-BEFORE, <<-AFTER
  alias Foo=
  Bar
  BEFORE
  alias Foo = Bar
  AFTER
Enter fullscreen mode Exit fullscreen mode

It's working! 🎉

The crux of the matter was in the first line assert_format <<-BEFORE, <<-AFTER. Remember the docs:

It's as if the end of the starting heredoc identifier is the end of the string.

Heredoc vs String

Finally, here is the same example but using strings instead of heredocs:

assert_format "  alias Foo=\n  Bar", "  alias Foo = Bar"
Enter fullscreen mode Exit fullscreen mode

It looks like we didn't gain much but here is a more complex example, with multiple newlines and indentations.

Farewell and see you later

We have learned about Heredoc, how we can use Heredoc's methods, and how to use multiple heredocs as arguments to a method.

All the above and no dragons were harmed while writing this post! 😁

Hope you enjoyed it! 😃


Photo by Clint Bustrillos on Unsplash

Top comments (0)