Here is a piece of code that I have adapted to illustrate my initial question.
def calculate_cells(formatted_content, maxwidth, wrap_preserve)
cells = [] of String
line_index = 0
case wrap_preserve
when :char
formatted_content.split(/\n/).flat_map do |substr|
cell_line = String::Builder.new
cell_line_width = 0
substr.scan(/\X/).each do |r|
char = r[0]
char_width = char.size
if cell_line_width + char_width > maxwidth
cells << cell_line.to_s
line_index += 1
# <<< 3 lines to be duplicated
cell_line.close
cell_line_width = 0
cell_line = String::Builder.new
# >>>
end
cell_line << char
cell_line_width += char_width
end
cells << cell_line.to_s
cell_line.close
line_index += 1
end
when :word
formatted_content.split(/\n/).flat_map do |substr|
cell_line = String::Builder.new
cell_line_width = 0
substr.split(/(?<= |\-|\ā|\āā )\b/).each do |word|
word_width = word.size
combined_width = cell_line_width + word_width
if combined_width - 1 == maxwidth && word[-1] == " "
# do nothing
elsif combined_width > maxwidth
content = cell_line.to_s
if content.strip.size != 0
cells << content
line_index += 1
end
# <<< 3 duplicated lines
cell_line.close
cell_line_width = 0
cell_line = String::Builder.new
# >>
end
if word_width >= maxwidth
word.scan(/\X/).each do |r|
char = r[0]
char_width = char.size
if cell_line_width + char_width > maxwidth
if char != " "
cells << cell_line.to_s
line_index += 1
# <<< 3 duplicated lines
cell_line.close
cell_line_width = 0
cell_line = String::Builder.new
# >>>
end
end
cell_line << char
cell_line_width += char_width
end
else
cell_line << word
cell_line_width += word_width
end
end
cells << cell_line.to_s
line_index += 1
cell_line.close
end
end
cells
end
formatted_line = "Crystal language is the best!\nThere is nothing to discuss!"
puts formatted_line
puts calculate_cells(formatted_line, 12, :char)
puts calculate_cells(formatted_line, 12, :word)
The purpose of the calculate_cells
method is to transform a string into an array of strings of a given maximum length, by cutting the original string either at a character or at a word boundary if possible.
As you see, there are 3 identical lines of code in 3 different places.
With the use of a nested method sharing the data of the enclosing method, we could write:
def calculate_cells(formatted_content, maxwidth, wrap_preserve)
def new_cell_line(check_size)
content = cell_line.to_s
if (check_size && content.strip.size != 0) || !check_size
cells << content
line_index += 1
end
cell_line.close
cell_line_width = 0
cell_line = String::Builder.new
end
cells = [] of String
line_index = 0
case wrap_preserve
when :char
formatted_content.split(/\n/).flat_map do |substr|
cell_line = String::Builder.new
cell_line_width = 0
substr.scan(/\X/).each do |r|
char = r[0]
char_width = char.size
if cell_line_width + char_width > maxwidth
new_cell_line(false)
end
cell_line << char
cell_line_width += char_width
end
cells << cell_line.to_s
cell_line.close
line_index += 1
end
when :word
formatted_content.split(/\n/).flat_map do |substr|
cell_line = String::Builder.new
cell_line_width = 0
substr.split(/(?<= |\-|\ā|\āā )\b/).each do |word|
word_width = word.size
combined_width = cell_line_width + word_width
if combined_width - 1 == maxwidth && word[-1] == " "
# do nothing
elsif combined_width > maxwidth
new_cell_line(true)
end
if word_width >= maxwidth
word.scan(/\X/).each do |r|
char = r[0]
char_width = char.size
if cell_line_width + char_width > maxwidth
if char != " "
new_cell_line(false)
end
end
cell_line << char
cell_line_width += char_width
end
else
cell_line << word
cell_line_width += word_width
end
end
cells << cell_line.to_s
line_index += 1
cell_line.close
end
end
cells
end
5 lines of code can now be managed at the nested method level, there are no more duplicated lines and the total number of lines in the method has been reduced. In addition, IMHO, it makes the code easier to read.
As you see, in this example, I only need one āsubā method.
Using a closure seems a bit more complicated, due in particular to the existence of the +=
syntax line.
For the moment, I use the following macro in my real code, but I find it less elegant than a nested method as it is declared outside the main method;-))
macro new_cell_line(check_size)
content = cell_line.to_s
if ({{check_size}} && content.strip.size != 0) || !{{check_size}}
cells << content
line_index += 1
end
cell_line.close
cell_line_width = 0
cell_line = String::Builder.new
end