#!/usr/bin/env python3
# --------------------( LICENSE                           )--------------------
# Copyright (c) 2014-2020 Cecil Curry.
# See "LICENSE" for further details.

'''
**Beartype string munging utilities** (i.e., callables transforming passed
strings into new strings with generic string operations).

This private submodule implements supplementary string-handling utility
functions required by various :mod:`beartype` facilities, including callables
generated by the :func:`beartype.beartype` decorator.

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                           }....................
import re

# ....................{ NUMBERERS                         }....................
def number_lines(text: str) -> str:
    '''
    Passed string munged to prefix each line of this string with the 1-based
    number of that line padded by zeroes out to four digits for alignment.

    Parameters
    ----------
    text: str
        String whose lines are to be numbered.

    Returns
    ----------
    str
        This string with all lines numbered.
    '''

    # Let's do this!
    return '\n'.join(
        '(line {:0>4d}) {}'.format(text_line_number, text_line)
        for text_line_number, text_line in enumerate(
            text.splitlines(), start=1)
    )

# ....................{ TRIMMERS                          }....................
def trim_object_repr(obj: object, max_len: int = 76) -> str:
    '''
    Terse human-readable string truncated from the string representation of the
    passed object to the passed maximum length in a general-purpose manner
    suitable for safe interpolation into end-user exception messages.

    Parameters
    ----------
    obj: object
        Object whose representation is to be trimmed, converted into a string
        via the canonical :func:`repr` builtin.
    max_len: Optional[int]
        Maximum length of the string to be returned. Defaults to the customary
        line length of 80 characters minus default output indentation of four
        characters.

    Returns
    ----------
    str
        Human-readable string truncated from the string representation of this
        object to this maximum length.
    '''
    assert isinstance(max_len, int), '"{!r}" not an integer.'.format(max_len)

    # Uncompiled regular expression grouping zero or more non-newline leading
    # characters preceding this maximum length *AND* zero or more trailing
    # delimiters.
    #
    # Note that efficiency is *NOT* a consideration for this utility function,
    # which is only called in the edge-case event of a runtime type exception.
    PRE_MAX_CHARS_LAST_DELIMITERS_REGEX = (
        r'^([^\n]{0,' + str(max_len) + r'}).*?([\])}>\'"]*)$')

    # String describing the passed object. For debuggability, the verbose
    # (albeit less human-readable) output of repr() is preferred to the terse
    # (albeit more human-readable) output of str().
    obj_synopsis = repr(obj)

    # If this synopsis either exceeds this maximum length *OR* contains a
    # newline, replace the substring of this synopsis from whichever of the
    # first character following this maximum length or the first newline occurs
    # first to the string end (excluding any # optional trailing delimiters)
    # with a single ellipses.
    if len(obj_synopsis) > max_len or '\n' in obj_synopsis:
        obj_synopsis = re.sub(
            PRE_MAX_CHARS_LAST_DELIMITERS_REGEX,
            r'\1...\2',
            obj_synopsis,
            flags=re.DOTALL
        )

    # Return this synopsis.
    return obj_synopsis
