Created
July 21, 2016 15:16
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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