Skip to content

Instantly share code, notes, and snippets.

@shock
Created July 21, 2016 15:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shock/1d269a91f938bf1a1c3cba3856bedf19 to your computer and use it in GitHub Desktop.
Save shock/1d269a91f938bf1a1c3cba3856bedf19 to your computer and use it in GitHub Desktop.
String object extensions for handling proper indentation duration HEREDOC interpolation of multi-line strings.
# Created in response to Stack Overflow question:
# http://stackoverflow.com/questions/38504004/interpolate-multiline-string-with-correct-indent
module CoreExtensions
module String
module Heredoc
# Special character to flag lines that are part of a multiline HEREDOC interpolation
# Any character that is not part of the output string will work. Using "\r" because
# it's rarely used in hard-coded strings.
ML_CHAR = "\r"
def hd_multiline
# mark al lines in this string as a multiline heredoc insert
self.split("\n").join("#{ML_CHAR}\n")
end
# Same as Rails' #strip_heredoc without requiring ActiveSupport
def hd_strip
indent = scan(/^[ \t]*(?=\S)/).min.size rescue 0
gsub(/^[ \t]{#{indent}}/, '')
end
# Method to properly indent a HEREDOC with multiline interpolations
def hd_render
lines = self.split("\n") # split the string into separate lines
# current indentation
indent = ""
new_lines = [] # buffer array for result string's lines
new_lines << lines.first # always take the first line as-is
lines.each_with_index do |line, i|
# get the indentation for this line
this_indent = line.scan(/^ +/).first || ""
# if it's greater than the previous line's, update the current indentation
indent = this_indent unless this_indent.length < indent.length
# Load the next line
next_line = lines[i+1]
if next_line
if line.scan(/#{ML_CHAR}+/).first # we have a multiline indent
new_lines << "#{indent}#{next_line}" # add the current indentation to the next line
else # not a multiline indent, keep the line as-is
new_lines << next_line
end
end
end
# clean up the added ML_CHAR characters
new_lines = new_lines.map{|line| "#{line.gsub(/#{ML_CHAR}+/, '')}"}
result = result = new_lines.join("\n") # re-join the lines as a contiguous string
result
end
end
end
end
class String
self.send(:include, CoreExtensions::String::Heredoc)
end
## Example use case
def parse_string
sub_substring = <<-STRING.hd_strip
indented line2
double indented line1
STRING
# Must call #hd_render on any HEREDOCS with interpolated multiline strings
# Note that #hd_render must be called before #hd_strip
substring = <<-STRING.hd_render.hd_strip
first line
second line
indented line1
#{ sub_substring.hd_multiline }
double indented line2
third_line
STRING
string = <<-STRING.hd_render.hd_strip
Quote
#{ substring.hd_multiline }
from substring
STRING
string
end
puts parse_string
=begin
Output:
Quote
first line
second line
indented line1
indented line2
double indented line1
double indented line2
third_line
from substring
=end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment