Skip to content

Formatting

provide.foundation.formatting

TODO: Add module docstring.

Functions

format_duration

format_duration(seconds: float, short: bool = False) -> str

Format seconds as human-readable duration.

Parameters:

Name Type Description Default
seconds float

Duration in seconds

required
short bool

Use short format (1h30m vs 1 hour 30 minutes)

False

Returns:

Type Description
str

Human-readable duration string

Examples:

>>> format_duration(90)
'1 minute 30 seconds'
>>> format_duration(90, short=True)
'1m30s'
>>> format_duration(3661)
'1 hour 1 minute 1 second'
>>> format_duration(3661, short=True)
'1h1m1s'
Source code in provide/foundation/formatting/numbers.py
def format_duration(seconds: float, short: bool = False) -> str:
    """Format seconds as human-readable duration.

    Args:
        seconds: Duration in seconds
        short: Use short format (1h30m vs 1 hour 30 minutes)

    Returns:
        Human-readable duration string

    Examples:
        >>> format_duration(90)
        '1 minute 30 seconds'
        >>> format_duration(90, short=True)
        '1m30s'
        >>> format_duration(3661)
        '1 hour 1 minute 1 second'
        >>> format_duration(3661, short=True)
        '1h1m1s'

    """
    if seconds < 0:
        return f"-{format_duration(abs(seconds), short)}"

    if seconds == 0:
        return "0s" if short else "0 seconds"

    # Calculate components
    days = int(seconds // 86400)
    hours = int((seconds % 86400) // 3600)
    minutes = int((seconds % 3600) // 60)
    secs = int(seconds % 60)

    if short:
        return _format_duration_short(days, hours, minutes, secs)
    return _format_duration_long(days, hours, minutes, secs)

format_grouped

format_grouped(
    text: str,
    group_size: int = 8,
    groups: int = 0,
    separator: str = " ",
) -> str

Format a string with grouping separators for display.

Parameters:

Name Type Description Default
text str

Text to format

required
group_size int

Number of characters per group

8
groups int

Number of groups to show (0 for all)

0
separator str

Separator between groups

' '

Returns:

Type Description
str

Formatted string with groups

Examples:

>>> format_grouped("abc123def456", group_size=4, separator="-")
'abc1-23de-f456'
>>> format_grouped("abc123def456", group_size=4, groups=2)
'abc1 23de'
>>> format_grouped("1234567890abcdef", group_size=4)
'1234 5678 90ab cdef'
Source code in provide/foundation/formatting/grouping.py
def format_grouped(
    text: str,
    group_size: int = 8,
    groups: int = 0,
    separator: str = " ",
) -> str:
    """Format a string with grouping separators for display.

    Args:
        text: Text to format
        group_size: Number of characters per group
        groups: Number of groups to show (0 for all)
        separator: Separator between groups

    Returns:
        Formatted string with groups

    Examples:
        >>> format_grouped("abc123def456", group_size=4, separator="-")
        'abc1-23de-f456'
        >>> format_grouped("abc123def456", group_size=4, groups=2)
        'abc1 23de'
        >>> format_grouped("1234567890abcdef", group_size=4)
        '1234 5678 90ab cdef'

    """
    if group_size <= 0:
        return text

    formatted_parts = []
    for i in range(0, len(text), group_size):
        formatted_parts.append(text[i : i + group_size])
        if groups > 0 and len(formatted_parts) >= groups:
            break

    return separator.join(formatted_parts)

format_number

format_number(
    num: float, precision: int | None = None
) -> str

Format number with thousands separators.

Parameters:

Name Type Description Default
num float

Number to format

required
precision int | None

Decimal places (None for automatic)

None

Returns:

Type Description
str

Formatted number string

Examples:

>>> format_number(1234567)
'1,234,567'
>>> format_number(1234.5678, precision=2)
'1,234.57'
Source code in provide/foundation/formatting/numbers.py
def format_number(num: float, precision: int | None = None) -> str:
    """Format number with thousands separators.

    Args:
        num: Number to format
        precision: Decimal places (None for automatic)

    Returns:
        Formatted number string

    Examples:
        >>> format_number(1234567)
        '1,234,567'
        >>> format_number(1234.5678, precision=2)
        '1,234.57'

    """
    if precision is None:
        if isinstance(num, int):
            return f"{num:,}"
        # Auto precision for floats
        return f"{num:,.6f}".rstrip("0").rstrip(".")
    return f"{num:,.{precision}f}"

format_percentage

format_percentage(
    value: float,
    precision: int = 1,
    include_sign: bool = False,
) -> str

Format value as percentage.

Parameters:

Name Type Description Default
value float

Value to format (0.5 = 50%)

required
precision int

Decimal places

1
include_sign bool

Include + sign for positive values

False

Returns:

Type Description
str

Formatted percentage string

Examples:

>>> format_percentage(0.5)
'50.0%'
>>> format_percentage(0.1234, precision=2)
'12.34%'
>>> format_percentage(0.05, include_sign=True)
'+5.0%'
Source code in provide/foundation/formatting/numbers.py
def format_percentage(value: float, precision: int = 1, include_sign: bool = False) -> str:
    """Format value as percentage.

    Args:
        value: Value to format (0.5 = 50%)
        precision: Decimal places
        include_sign: Include + sign for positive values

    Returns:
        Formatted percentage string

    Examples:
        >>> format_percentage(0.5)
        '50.0%'
        >>> format_percentage(0.1234, precision=2)
        '12.34%'
        >>> format_percentage(0.05, include_sign=True)
        '+5.0%'

    """
    percentage = value * 100
    formatted = f"{percentage:.{precision}f}%"

    if include_sign and value > 0:
        formatted = f"+{formatted}"

    return formatted

format_size

format_size(size_bytes: float, precision: int = 1) -> str

Format bytes as human-readable size.

Parameters:

Name Type Description Default
size_bytes float

Size in bytes

required
precision int

Decimal places for display

1

Returns:

Type Description
str

Human-readable size string

Examples:

>>> format_size(1024)
'1.0 KB'
>>> format_size(1536)
'1.5 KB'
>>> format_size(1073741824)
'1.0 GB'
>>> format_size(0)
'0 B'
Source code in provide/foundation/formatting/numbers.py
def format_size(size_bytes: float, precision: int = 1) -> str:
    """Format bytes as human-readable size.

    Args:
        size_bytes: Size in bytes
        precision: Decimal places for display

    Returns:
        Human-readable size string

    Examples:
        >>> format_size(1024)
        '1.0 KB'
        >>> format_size(1536)
        '1.5 KB'
        >>> format_size(1073741824)
        '1.0 GB'
        >>> format_size(0)
        '0 B'

    """
    if size_bytes == 0:
        return "0 B"

    # Handle negative sizes
    negative = size_bytes < 0
    size_bytes = abs(size_bytes)

    units = ["B", "KB", "MB", "GB", "TB", "PB", "EB"]
    unit_index = 0

    while size_bytes >= 1024.0 and unit_index < len(units) - 1:
        size_bytes /= 1024.0
        unit_index += 1

    # Format with specified precision
    if unit_index == 0:
        # Bytes - no decimal places
        formatted = f"{int(size_bytes)} {units[unit_index]}"
    else:
        formatted = f"{size_bytes:.{precision}f} {units[unit_index]}"

    return f"-{formatted}" if negative else formatted

format_table

format_table(
    headers: list[str],
    rows: list[list[Any]],
    alignment: list[str] | None = None,
) -> str

Format data as ASCII table.

Parameters:

Name Type Description Default
headers list[str]

Column headers

required
rows list[list[Any]]

Data rows

required
alignment list[str] | None

Column alignments ('l', 'r', 'c')

None

Returns:

Type Description
str

Formatted table string

Examples:

>>> headers = ['Name', 'Age']
>>> rows = [['Alice', 30], ['Bob', 25]]
>>> print(format_table(headers, rows))
Name  | Age
------|----
Alice | 30
Bob   | 25
Source code in provide/foundation/formatting/tables.py
def format_table(headers: list[str], rows: list[list[Any]], alignment: list[str] | None = None) -> str:
    """Format data as ASCII table.

    Args:
        headers: Column headers
        rows: Data rows
        alignment: Column alignments ('l', 'r', 'c')

    Returns:
        Formatted table string

    Examples:
        >>> headers = ['Name', 'Age']
        >>> rows = [['Alice', 30], ['Bob', 25]]
        >>> print(format_table(headers, rows))
        Name  | Age
        ------|----
        Alice | 30
        Bob   | 25

    """
    if not headers and not rows:
        return ""

    # Convert all cells to strings
    str_headers = [str(h) for h in headers]
    str_rows = [[str(cell) for cell in row] for row in rows]

    # Calculate column widths
    widths = _calculate_column_widths(str_headers, str_rows)

    # Default alignment
    if alignment is None:
        alignment = ["l"] * len(headers)

    # Format header and separator
    header_line, separator_line = _format_table_header(str_headers, widths, alignment)
    lines = [header_line, separator_line]

    # Format data rows
    for row in str_rows:
        lines.append(_format_table_row(row, widths, alignment))

    return "\n".join(lines)

indent

indent(
    text: str, spaces: int = 2, first_line: bool = True
) -> str

Indent text lines.

Parameters:

Name Type Description Default
text str

Text to indent

required
spaces int

Number of spaces to indent

2
first_line bool

Whether to indent the first line

True

Returns:

Type Description
str

Indented text

Examples:

>>> indent("line1\nline2", 4)
'    line1\n    line2'
Source code in provide/foundation/formatting/text.py
def indent(text: str, spaces: int = 2, first_line: bool = True) -> str:
    """Indent text lines.

    Args:
        text: Text to indent
        spaces: Number of spaces to indent
        first_line: Whether to indent the first line

    Returns:
        Indented text

    Examples:
        >>> indent("line1\\nline2", 4)
        '    line1\\n    line2'

    """
    indent_str = " " * spaces
    lines = text.splitlines()

    if not lines:
        return text

    result = []
    for i, line in enumerate(lines):
        if i == 0 and not first_line:
            result.append(line)
        else:
            result.append(indent_str + line if line else "")

    return "\n".join(result)

pluralize

pluralize(
    count: int, singular: str, plural: str | None = None
) -> str

Get singular or plural form based on count.

Parameters:

Name Type Description Default
count int

Item count

required
singular str

Singular form

required
plural str | None

Plural form (default: singular + 's')

None

Returns:

Type Description
str

Appropriate singular/plural form with count

Examples:

>>> pluralize(1, "file")
'1 file'
>>> pluralize(5, "file")
'5 files'
>>> pluralize(2, "child", "children")
'2 children'
Source code in provide/foundation/formatting/text.py
def pluralize(count: int, singular: str, plural: str | None = None) -> str:
    """Get singular or plural form based on count.

    Args:
        count: Item count
        singular: Singular form
        plural: Plural form (default: singular + 's')

    Returns:
        Appropriate singular/plural form with count

    Examples:
        >>> pluralize(1, "file")
        '1 file'
        >>> pluralize(5, "file")
        '5 files'
        >>> pluralize(2, "child", "children")
        '2 children'

    """
    if plural is None:
        plural = f"{singular}s"

    word = singular if count == 1 else plural
    return f"{count} {word}"

strip_ansi

strip_ansi(text: str) -> str

Strip ANSI color codes from text.

Parameters:

Name Type Description Default
text str

Text with potential ANSI codes

required

Returns:

Type Description
str

Text without ANSI codes

Source code in provide/foundation/formatting/text.py
def strip_ansi(text: str) -> str:
    """Strip ANSI color codes from text.

    Args:
        text: Text with potential ANSI codes

    Returns:
        Text without ANSI codes

    """
    return ANSI_PATTERN.sub("", text)

to_camel_case

to_camel_case(text: str, upper_first: bool = False) -> str

Convert text to camelCase or PascalCase.

Parameters:

Name Type Description Default
text str

Text to convert

required
upper_first bool

Use PascalCase instead of camelCase

False

Returns:

Type Description
str

camelCase or PascalCase text

Examples:

>>> to_camel_case("hello_world")
'helloWorld'
>>> to_camel_case("hello-world", upper_first=True)
'HelloWorld'
Source code in provide/foundation/formatting/case.py
def to_camel_case(text: str, upper_first: bool = False) -> str:
    """Convert text to camelCase or PascalCase.

    Args:
        text: Text to convert
        upper_first: Use PascalCase instead of camelCase

    Returns:
        camelCase or PascalCase text

    Examples:
        >>> to_camel_case("hello_world")
        'helloWorld'
        >>> to_camel_case("hello-world", upper_first=True)
        'HelloWorld'

    """
    import re

    # Split on underscores, hyphens, and spaces
    parts = re.split(r"[-_\s]+", text)

    if not parts:
        return text

    # Capitalize each part except possibly the first
    result = []
    for i, part in enumerate(parts):
        if i == 0 and not upper_first:
            result.append(part.lower())
        else:
            result.append(part.capitalize())

    return "".join(result)

to_kebab_case

to_kebab_case(text: str) -> str

Convert text to kebab-case.

Parameters:

Name Type Description Default
text str

Text to convert

required

Returns:

Type Description
str

kebab-case text

Examples:

>>> to_kebab_case("HelloWorld")
'hello-world'
>>> to_kebab_case("some_snake_case")
'some-snake-case'
Source code in provide/foundation/formatting/case.py
def to_kebab_case(text: str) -> str:
    """Convert text to kebab-case.

    Args:
        text: Text to convert

    Returns:
        kebab-case text

    Examples:
        >>> to_kebab_case("HelloWorld")
        'hello-world'
        >>> to_kebab_case("some_snake_case")
        'some-snake-case'

    """
    import re

    # Replace underscores with hyphens
    text = text.replace("_", "-")

    # Insert hyphen before uppercase letters
    text = re.sub(r"([a-z0-9])([A-Z])", r"\1-\2", text)

    # Convert to lowercase
    return text.lower()

to_snake_case

to_snake_case(text: str) -> str

Convert text to snake_case.

Parameters:

Name Type Description Default
text str

Text to convert

required

Returns:

Type Description
str

snake_case text

Examples:

>>> to_snake_case("HelloWorld")
'hello_world'
>>> to_snake_case("some-kebab-case")
'some_kebab_case'
Source code in provide/foundation/formatting/case.py
def to_snake_case(text: str) -> str:
    """Convert text to snake_case.

    Args:
        text: Text to convert

    Returns:
        snake_case text

    Examples:
        >>> to_snake_case("HelloWorld")
        'hello_world'
        >>> to_snake_case("some-kebab-case")
        'some_kebab_case'

    """
    import re

    # Replace hyphens with underscores
    text = text.replace("-", "_")

    # Insert underscore before uppercase letters
    text = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", text)

    # Convert to lowercase
    return text.lower()

truncate

truncate(
    text: str,
    max_length: int,
    suffix: str = "...",
    whole_words: bool = True,
) -> str

Truncate text to maximum length.

Parameters:

Name Type Description Default
text str

Text to truncate

required
max_length int

Maximum length including suffix

required
suffix str

Suffix to append when truncated

'...'
whole_words bool

Truncate at word boundaries

True

Returns:

Type Description
str

Truncated text

Examples:

>>> truncate("Hello world", 8)
'Hello...'
>>> truncate("Hello world", 8, whole_words=False)
'Hello...'
Source code in provide/foundation/formatting/text.py
def truncate(text: str, max_length: int, suffix: str = "...", whole_words: bool = True) -> str:
    """Truncate text to maximum length.

    Args:
        text: Text to truncate
        max_length: Maximum length including suffix
        suffix: Suffix to append when truncated
        whole_words: Truncate at word boundaries

    Returns:
        Truncated text

    Examples:
        >>> truncate("Hello world", 8)
        'Hello...'
        >>> truncate("Hello world", 8, whole_words=False)
        'Hello...'

    """
    if len(text) <= max_length:
        return text

    if max_length <= len(suffix):
        return suffix[:max_length]

    truncate_at = max_length - len(suffix)

    if whole_words:
        # Find last space before truncate point
        space_pos = text.rfind(" ", 0, truncate_at)
        if space_pos > 0:
            truncate_at = space_pos

    return text[:truncate_at] + suffix

wrap_text

wrap_text(
    text: str,
    width: int = 80,
    indent_first: int = 0,
    indent_rest: int = 0,
) -> str

Wrap text to specified width.

Parameters:

Name Type Description Default
text str

Text to wrap

required
width int

Maximum line width

80
indent_first int

Spaces to indent first line

0
indent_rest int

Spaces to indent remaining lines

0

Returns:

Type Description
str

Wrapped text

Source code in provide/foundation/formatting/text.py
def wrap_text(text: str, width: int = 80, indent_first: int = 0, indent_rest: int = 0) -> str:
    """Wrap text to specified width.

    Args:
        text: Text to wrap
        width: Maximum line width
        indent_first: Spaces to indent first line
        indent_rest: Spaces to indent remaining lines

    Returns:
        Wrapped text

    """
    import textwrap

    wrapper = textwrap.TextWrapper(
        width=width,
        initial_indent=" " * indent_first,
        subsequent_indent=" " * indent_rest,
        break_long_words=False,
        break_on_hyphens=False,
    )

    return wrapper.fill(text)