Low commit activity in last 3 years
Wrap text to a specific width at word boundaries. Supports indentation, hanging indent, ANSI escape code-safe width calculation, and truncation with configurable omission strings.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

philiprehberger-word_wrap

Tests Gem Version Last updated

Text wrapping with word-boundary awareness, indentation, and ANSI escape code support

Requirements

  • Ruby >= 3.1

Installation

Add to your Gemfile:

gem "philiprehberger-word_wrap"

Or install directly:

gem install philiprehberger-word_wrap

Usage

require "philiprehberger/word_wrap"

# Basic wrapping at a given width
Philiprehberger::WordWrap.wrap('the quick brown fox jumps over the lazy dog', width: 20)
# => "the quick brown fox\njumps over the lazy\ndog"

Indentation

Use indent to add a prefix to every line, or combine first_indent and indent for hanging-indent lists.

# Uniform indent
Philiprehberger::WordWrap.wrap('the quick brown fox jumps over', width: 25, indent: '  ')
# => "  the quick brown fox\n  jumps over"

# Hanging indent (first line different from subsequent lines)
Philiprehberger::WordWrap.wrap('the quick brown fox jumps over', width: 25, first_indent: '- ', indent: '  ')
# => "- the quick brown fox\n  jumps over"

ANSI Support

ANSI escape codes are preserved in the output but excluded from width calculations, so colored or styled text wraps correctly.

text = "\e[31mhello\e[0m \e[32mworld\e[0m"
Philiprehberger::WordWrap.wrap(text, width: 8)
# => "\e[31mhello\e[0m\n\e[32mworld\e[0m"

# Check visible width without ANSI codes
Philiprehberger::WordWrap.visible_width("\e[31mhello\e[0m")
# => 5

# Strip ANSI codes and return the plain string
Philiprehberger::WordWrap.strip_ansi("\e[31mhello\e[0m")
# => "hello"

Justification

Full text justification expands spaces to fill the line width:

Philiprehberger::WordWrap.wrap('the quick brown fox jumps over the lazy dog', width: 30, justify: true)

Center Alignment

Philiprehberger::WordWrap.center('hello', width: 20)
# => "       hello"

Multi-Column Formatting

Philiprehberger::WordWrap.columns(
  ['Column one text here', 'Column two text'],
  widths: [20, 20],
  separator: ' | '
)

Hanging Indent

Wrap text where the first line is flush left and subsequent lines are indented. Useful for bullet lists and definition formatting.

Philiprehberger::WordWrap.hanging_indent('the quick brown fox jumps over the lazy dog', 25, indent: 4)
# => "the quick brown fox\n    jumps over the lazy\n    dog"

Fit to Box

Wrap text to a width, then truncate to at most a given number of lines. If truncated, an omission string is appended to the last line.

Philiprehberger::WordWrap.fit('the quick brown fox jumps over the lazy dog', width: 20, height: 2)
# => "the quick brown fox\njumps over the..."

Paragraph Wrapping

Split text on double newlines, wrap each paragraph independently, and rejoin with configurable spacing.

text = "First paragraph here.\n\nSecond paragraph here."
Philiprehberger::WordWrap.paragraphs(text, 30)

# Custom spacing (2 blank lines between paragraphs)
Philiprehberger::WordWrap.paragraphs(text, 30, spacing: 2)

Unwrap

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

text = "the quick brown\nfox jumps over\n\nthe lazy dog\nsleeps"
Philiprehberger::WordWrap.unwrap(text)
# => "the quick brown fox jumps over\n\nthe lazy dog sleeps"

Re-indenting Pre-wrapped Text

Prepend an indent to every line of an already-wrapped string. Optionally use a different first_indent for the first line.

require 'philiprehberger/word_wrap'

text = "first line\nsecond line\nthird line"
Philiprehberger::WordWrap.indent_lines(text, '  ')
# => "  first line\n  second line\n  third line"

Philiprehberger::WordWrap.indent_lines(text, '  ', first_indent: '* ')
# => "* first line\n  second line\n  third line"

Truncation

Truncate text at a word boundary with a configurable omission string.

Philiprehberger::WordWrap.truncate('the quick brown fox', width: 15)
# => "the quick..."

# Custom omission string
Philiprehberger::WordWrap.truncate('the quick brown fox', width: 18, omission: ' [...]')
# => "the quick [...]"

API

Method Description
WordWrap.wrap(text, width: 80, indent: nil, first_indent: nil, justify: false) Wrap text at word boundaries to fit within the given width. Words exceeding the line width are hard-wrapped.
WordWrap.hanging_indent(text, width, indent:) Wrap text with first line flush left and subsequent lines indented by indent spaces
WordWrap.indent_lines(text, indent, first_indent:) Prepend indent to every line; optional first_indent for the first line
WordWrap.fit(text, width:, height:, omission: '...') Wrap text to width, then truncate to at most height lines with omission string
WordWrap.paragraphs(text, width, spacing: 1) Split on double newlines, wrap each paragraph independently, rejoin with spacing blank lines
WordWrap.unwrap(text) Remove single newlines within paragraphs, preserving paragraph boundaries (double newlines)
WordWrap.truncate(text, width: 80, omission: '...') Truncate text at a word boundary, appending the omission string
WordWrap.visible_width(text) Return the visible character width, excluding ANSI escape codes
WordWrap.strip_ansi(text) Return a copy of text with all ANSI escape codes removed
WordWrap.center(text, width: 80) Center text within the given width
WordWrap.columns(texts, widths:, separator: ' ') Format multiple strings into parallel columns
WordWrap::ANSI_PATTERN Regex matching ANSI escape sequences (\e[...m)
WordWrap.wrap_paragraph (private) Wrap a single paragraph, applying indent and first_indent per line
WordWrap.split_preserving_ansi (private) Split text into words while keeping ANSI codes attached to their word
WordWrap.hard_wrap_word (private) Break a single word into chunks that fit within max width
WordWrap.split_at_visible (private) Split a string at a visible-character offset, skipping ANSI sequences
WordWrap.extract_active_ansi (private) Return the last active ANSI code from a string (or empty if reset)
WordWrap.truncate_at_word_boundary (private) Truncate text to fit within available width at a word boundary

Development

bundle install
bundle exec rspec
bundle exec rubocop

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT