Module: Philiprehberger::WordWrap

Defined in:
lib/philiprehberger/word_wrap.rb,
lib/philiprehberger/word_wrap/version.rb

Constant Summary collapse

ANSI_PATTERN =
/\e\[[0-9;]*m/
VERSION =
'0.3.0'

Class Method Summary collapse

Class Method Details

.center(text, width: 80) ⇒ String

Center text within a given width

Parameters:

  • text (String)

    the text to center

  • width (Integer) (defaults to: 80)

    the total width

Returns:

  • (String)

    centered text



42
43
44
45
46
47
48
# File 'lib/philiprehberger/word_wrap.rb', line 42

def center(text, width: 80)
  text.split("\n").map do |line|
    vis_width = visible_width(line.strip)
    padding = [(width - vis_width) / 2, 0].max
    "#{' ' * padding}#{line.strip}"
  end.join("\n")
end

.columns(texts, widths:, separator: ' ') ⇒ String

Format multiple strings into parallel columns

Parameters:

  • texts (Array<String>)

    array of text strings, one per column

  • widths (Array<Integer>)

    width of each column

  • separator (String) (defaults to: ' ')

    separator between columns

Returns:

  • (String)

    multi-column formatted text



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/philiprehberger/word_wrap.rb', line 56

def columns(texts, widths:, separator: '  ')
  wrapped = texts.each_with_index.map do |text, i|
    wrap(text, width: widths[i]).split("\n")
  end

  max_lines = wrapped.map(&:length).max || 0

  (0...max_lines).map do |line_idx|
    wrapped.each_with_index.map do |col_lines, col_idx|
      line = col_lines[line_idx] || ''
      pad_width = widths[col_idx]
      vis = visible_width(line)
      line + (' ' * [(pad_width - vis), 0].max)
    end.join(separator)
  end.join("\n")
end

.fit(text, width:, height:, omission: '...') ⇒ String

Wrap text to width, then truncate to at most height lines

Parameters:

  • text (String)

    the text to wrap and fit

  • width (Integer)

    the line width

  • height (Integer)

    the maximum number of lines

  • omission (String) (defaults to: '...')

    string to append when truncated

Returns:

  • (String)

    wrapped and height-truncated text



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/philiprehberger/word_wrap.rb', line 92

def fit(text, width:, height:, omission: '...')
  wrapped = wrap(text, width: width)
  lines = wrapped.split("\n")

  return wrapped if lines.length <= height

  truncated_lines = lines[0...height]
  last_line = truncated_lines.last
  omission_width = visible_width(omission)
  available = width - omission_width

  truncated_lines[-1] = if available <= 0
                          omission[0...width]
                        elsif visible_width(last_line) > available
                          truncate_at_word_boundary(last_line, available) + omission
                        else
                          "#{last_line}#{omission}"
                        end

  truncated_lines.join("\n")
end

.hanging_indent(text, width, indent:) ⇒ String

Wrap text with a hanging indent where the first line is flush left and subsequent lines are indented

Parameters:

  • text (String)

    the text to wrap

  • width (Integer)

    the total line width

  • indent (Integer)

    number of spaces to indent subsequent lines

Returns:

  • (String)

    wrapped text with hanging indent



80
81
82
83
# File 'lib/philiprehberger/word_wrap.rb', line 80

def hanging_indent(text, width, indent:)
  indent_str = ' ' * indent
  wrap(text, width: width, first_indent: '', indent: indent_str)
end

.paragraphs(text, width, spacing: 1) ⇒ String

Split on double newlines, wrap each paragraph independently, and rejoin with spacing blank lines

Parameters:

  • text (String)

    the text containing paragraphs

  • width (Integer)

    the line width

  • spacing (Integer) (defaults to: 1)

    number of blank lines between paragraphs

Returns:

  • (String)

    wrapped paragraphs joined with spacing



121
122
123
124
125
126
# File 'lib/philiprehberger/word_wrap.rb', line 121

def paragraphs(text, width, spacing: 1)
  parts = text.split(/\n{2,}/)
  wrapped_parts = parts.map { |para| wrap(para.strip, width: width) }
  separator = "\n#{"\n" * spacing}"
  wrapped_parts.join(separator)
end

.truncate(text, width: 80, omission: '...') ⇒ Object



23
24
25
26
27
28
29
30
31
# File 'lib/philiprehberger/word_wrap.rb', line 23

def truncate(text, width: 80, omission: '...')
  return text if visible_width(text) <= width

  omission_width = visible_width(omission)
  available = width - omission_width
  return omission if available <= 0

  truncate_at_word_boundary(text, available) + omission
end

.unwrap(text) ⇒ String

Remove single newlines within paragraphs (rejoin soft-wrapped text) while preserving paragraph boundaries (double newlines)

Parameters:

  • text (String)

    the soft-wrapped text

Returns:

  • (String)

    unwrapped text with paragraph boundaries preserved



133
134
135
136
# File 'lib/philiprehberger/word_wrap.rb', line 133

def unwrap(text)
  paragraphs = text.split(/\n{2,}/)
  paragraphs.map { |para| para.gsub("\n", ' ').squeeze(' ').strip }.join("\n\n")
end

.visible_width(text) ⇒ Object



33
34
35
# File 'lib/philiprehberger/word_wrap.rb', line 33

def visible_width(text)
  text.gsub(ANSI_PATTERN, '').length
end

.wrap(text, width: 80, indent: nil, first_indent: nil, justify: false) ⇒ Object



10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/philiprehberger/word_wrap.rb', line 10

def wrap(text, width: 80, indent: nil, first_indent: nil, justify: false)
  indent ||= ''
  first_indent ||= indent
  paragraphs = text.split("\n")

  wrapped_paragraphs = paragraphs.map do |paragraph|
    wrap_paragraph(paragraph, width: width, indent: indent, first_indent: first_indent)
  end

  result = wrapped_paragraphs.join("\n")
  justify ? justify_text(result, width) : result
end