File: utiltextjoin.py

package info (click to toggle)
python-beartype 0.22.9-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 9,504 kB
  • sloc: python: 85,502; sh: 328; makefile: 30; javascript: 18
file content (261 lines) | stat: -rw-r--r-- 9,873 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
#!/usr/bin/env python3
# --------------------( LICENSE                            )--------------------
# Copyright (c) 2014-2025 Beartype authors.
# See "LICENSE" for further details.

'''
Project-wide **string joining utilities** (i.e., callables joining passed
strings into new strings delimited by passed substring delimiters).

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

# ....................{ IMPORTS                            }....................
from beartype._data.typing.datatyping import (
    BoolTristate,
    IterableStrs,
    IterableTypes,
)
from collections.abc import (
    Iterable as IterableABC,
    Sequence as SequenceABC,
)

# ....................{ JOINERS                            }....................
#FIXME: Unit test the "is_double_quoted" parameter, please.
def join_delimited(
    # Mandatory parameters.
    strs: IterableStrs,

    # Mandatory keyword-only parameters.
    *,
    delimiter_if_two: str,
    delimiter_if_three_or_more_nonlast: str,
    delimiter_if_three_or_more_last: str,

    # Optional keyword-only parameters.
    is_double_quoted: bool = False,
) -> str:
    '''
    Concatenate the passed iterable of zero or more strings delimited by the
    passed delimiter (conditionally depending on both the length of this
    sequence and index of each string in this sequence), yielding a
    human-readable string listing arbitrarily many substrings.

    Specifically, this function returns either:

    * If this iterable contains no strings, the empty string.
    * If this iterable contains one string, this string as is is unmodified.
    * If this iterable contains two strings, these strings delimited by the
      passed ``delimiter_if_two`` delimiter.
    * If this iterable contains three or more strings, a string listing these
      contained strings such that:

      * All contained strings except the last two are suffixed by the passed
        ``delimiter_if_three_or_more_nonlast`` delimiter.
      * The last two contained strings are delimited by the passed
        ``delimiter_if_three_or_more_last`` separator.

    Parameters
    ----------
    strs : Iterable[str]
        Iterable of all strings to be joined.
    delimiter_if_two : str
        Substring separating each string contained in this iterable if this
        iterable contains exactly two strings.
    delimiter_if_three_or_more_nonlast : str
        Substring separating each string *except* the last two contained in
        this iterable if this iterable contains three or more strings.
    delimiter_if_three_or_more_last : str
        Substring separating each string the last two contained in this
        iterable if this iterable contains three or more strings.
    is_double_quoted : bool, optional
        :data:`True` only if **double-quoting** (i.e., both prefixing and
        suffixing by the ``"`` character) each item of this iterable. Defaults
        to :data:`False`.

    Returns
    -------
    str
        Concatenation of these strings.

    Examples
    --------
        >>> join_delimited(
        ...     strs=('Fulgrim', 'Perturabo', 'Angron', 'Mortarion'),
        ...     delimiter_if_two=' and ',
        ...     delimiter_if_three_or_more_nonlast=', ',
        ...     delimiter_if_three_or_more_last=', and ',
        ... )
        'Fulgrim, Perturabo, Angron, and Mortarion'
    '''
    assert isinstance(strs, IterableABC) and not isinstance(strs, str), (
        f'{repr(strs)} not non-string iterable.')
    assert isinstance(delimiter_if_two, str), (
        f'{repr(delimiter_if_two)} not string.')
    assert isinstance(delimiter_if_three_or_more_nonlast, str), (
        f'{repr(delimiter_if_three_or_more_nonlast)} not string.')
    assert isinstance(delimiter_if_three_or_more_last, str), (
        f'{repr(delimiter_if_three_or_more_last)} not string.')

    # If this iterable is *NOT* a sequence, internally coerce this iterable
    # into a sequence for subsequent indexing purposes.
    if not isinstance(strs, SequenceABC):
        strs = tuple(strs)
    # Else, this iterable is already a sequence.
    #
    # In either case, this iterable is now a sequence.

    # If double-quoting these strings, do so.
    if is_double_quoted:
        strs = tuple(f'"{text}"' for text in strs)
    # Else, preserve these strings as is.

    # Number of strings in this sequence.
    num_strs = len(strs)

    # If no strings are passed, return the empty string.
    if num_strs == 0:
        return ''
    # If one string is passed, return this string as is.
    elif num_strs == 1:
        # This is clearly a string, yet mypy thinks it's Any
        return strs[0]  # type: ignore[no-any-return]
    # If two strings are passed, return these strings delimited appropriately.
    elif num_strs == 2:
        return f'{strs[0]}{delimiter_if_two}{strs[1]}'
    # Else, three or more strings are passed.

    # All such strings except the last two, delimited appropriately.
    strs_nonlast = delimiter_if_three_or_more_nonlast.join(strs[0:-2])

    # The last two such strings, delimited appropriately.
    strs_last = f'{strs[-2]}{delimiter_if_three_or_more_last}{strs[-1]}'

    # Return these two substrings, delimited appropriately.
    return f'{strs_nonlast}{delimiter_if_three_or_more_nonlast}{strs_last}'

# ....................{ JOINERS ~ conjunction              }....................
#FIXME: Unit test us up, please.
def join_delimited_conjunction(strs: IterableStrs, **kwargs) -> str:
    '''
    Concatenate the passed iterable of zero or more strings delimited by commas
    and/or the conjunction "and" (conditionally depending on both the length of
    this iterable and index of each string in this iterable), yielding a
    human-readable string listing arbitrarily many substrings conjunctively.

    Specifically, this function returns either:

    * If this iterable contains no strings, the empty string.
    * If this iterable contains one string, this string as is is unmodified.
    * If this iterable contains two strings, these strings delimited by the
      conjunction "and".
    * If this iterable contains three or more strings, a string listing these
      contained strings such that:

      * All contained strings except the last two are suffixed by commas.
      * The last two contained strings are delimited by the conjunction "and".

    Parameters
    ----------
    strs : Iterable[str]
        Iterable of all strings to be concatenated conjunctively.

    All remaining keyword parameters are passed as is to the lower-level
    :func:`.join_delimeted` function underlying this higher-level function.

    Returns
    -------
    str
        Conjunctive concatenation of these strings.
    '''

    # One of us. We accept one-liner. One of us.
    return join_delimited(
        strs=strs,
        delimiter_if_two=' and ',
        delimiter_if_three_or_more_nonlast=', ',
        delimiter_if_three_or_more_last=', and ',
        **kwargs
    )

# ....................{ JOINERS ~ disjunction              }....................
def join_delimited_disjunction(strs: IterableStrs, **kwargs) -> str:
    '''
    Concatenate the passed iterable of zero or more strings delimited by commas
    and/or the disjunction "or" (conditionally depending on both the length of
    this iterable and index of each string in this iterable), yielding a
    human-readable string listing arbitrarily many substrings disjunctively.

    Specifically, this function returns either:

    * If this iterable contains no strings, the empty string.
    * If this iterable contains one string, this string as is is unmodified.
    * If this iterable contains two strings, these strings delimited by the
      disjunction "or".
    * If this iterable contains three or more strings, a string listing these
      contained strings such that:

      * All contained strings except the last two are suffixed by commas.
      * The last two contained strings are delimited by the disjunction "or".

    Parameters
    ----------
    strs : Iterable[str]
        Iterable of all strings to be concatenated disjunctively.

    All remaining keyword parameters are passed as is to the lower-level
    :func:`.join_delimeted` function underlying this higher-level function.

    Returns
    -------
    str
        Disjunctive concatenation of these strings.
    '''

    # He will join us... OR DIE! *cackling heard*
    return join_delimited(
        strs=strs,
        delimiter_if_two=' or ',
        delimiter_if_three_or_more_nonlast=', ',
        delimiter_if_three_or_more_last=', or ',
        **kwargs
    )


def join_delimited_disjunction_types(
    # Mandatory parameters.
    types: IterableTypes,

    # Optional parameters.
    is_color: BoolTristate = False,
) -> str:
    '''
    Concatenate the human-readable classname of each class in the passed
    iterable delimited by commas and/or the disjunction "or" (conditionally
    depending on both the length of this iterable and index of each string in
    this iterable), yielding a human-readable string listing arbitrarily many
    classnames disjunctively.

    Parameters
    ----------
    types : Iterable[type]
        Iterable of all classes whose human-readable classnames are to be
        concatenated disjunctively.
    is_color : BoolTristate, optional
        Tri-state colouring boolean governing ANSI usage. See the
        :attr:`beartype.BeartypeConf.is_color` attribute for further details.
        Defaults to :data:`False`.

    Returns
    -------
    str
        Disjunctive concatenation of these classnames.
    '''

    # Avoid circular import dependencies.
    from beartype._util.text.utiltextlabel import label_type

    # Make it so, ensign.
    return join_delimited_disjunction(
        label_type(cls=cls, is_color=is_color) for cls in types)