Text
Zero-dependency string and name utilities: expand, pysafe, snakecase,
camelcase, kebabcase, parse_bool (with the BOOL_TRUE/BOOL_FALSE token
tables), and gettext.
duho.text
Zero-dependency string and name utilities.
Brace-range expansion (:func:expand), Python-safe name coercion
(:func:pysafe), case conversion (:func:snakecase, :func:camelcase),
and a :mod:gettext shim.
BOOL_FALSE = frozenset({'0', 'false', 'no', 'off', 'n', 'f', ''})
module-attribute
BOOL_TRUE = frozenset({'1', 'true', 'yes', 'on', 'y', 't'})
module-attribute
PYREPLACE = {'+': 'plus', '!': 'not', '*': 'all'}
module-attribute
__all__ = ['gettext', 'ngettext', 'snakecase', 'camelcase', 'kebabcase', 'pysafe', 'expand', 'PYREPLACE', 'parse_bool', 'BOOL_TRUE', 'BOOL_FALSE']
module-attribute
camelcase(text, separators=None)
Join text into CamelCase, splitting on separators.
separators defaults to (".", "_", "-"); a single string is treated
as one separator.
Source code in src/duho/text.py
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | |
expand(text)
Expand [a-b] brace ranges in text.
expand("host[01-03]") yields host1, host2, host3 (output is
NOT zero-padded); expand("x[A-C]") yields xA, xB, xC.
A :spec suffix inside the brackets is a str.format spec applied to
each member, so expand("host[1-3:02d]") yields host01, host02,
host03; a spec with braces, or one the member does not accept, raises
:class:ValueError.
Multiple ranges expand as their Cartesian product, computed iteratively
(:func:itertools.product) rather than by recursion, so the number of
ranges in text is not bounded by Python's recursion limit -- but in the
same order the original recursive implementation produced: the LEFTMOST
range varies fastest and the RIGHTMOST varies slowest ("x[1-2][a-b]"
yields x1a, x2a, x1b, x2b -- the opposite of
:func:itertools.product's own left-to-right nesting, which is why the
ranges are iterated in reverse and each combination un-reversed before
use). Text with no range is yielded unchanged. A malformed range
(reversed, mismatched-kind, a multi-character/mixed-case letter
endpoint, or a bad format spec) raises :class:ValueError rather than silently yielding nothing
or a surprising result -- see :func:range.
Source code in src/duho/text.py
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 | |
gettext(message)
Source code in src/duho/text.py
32 33 | |
kebabcase(name)
Coerce name to acronym-aware kebab-case.
Unlike :func:snakecase (which lowers an acronym run letter-by-letter,
HTTPServer -> h_t_t_p_server), an acronym run is kept together up
to its last letter: ShowHTTPStatus -> show-http-status,
PyTrueNAS -> py-true-nas. Splits also occur at a digit->Upper
boundary (Ipv4Tool -> ipv4-tool) and at one or more _
(_Private -> private; a leading/trailing/doubled _ never
yields a leading/trailing/doubled -, since an empty split part is
dropped). Text with no such boundary -- including one already
hyphenated, e.g. "already-kebab" -- passes through unchanged (lower-
cased, though it already is in that case). Every part is lower-cased
independently, so mixed case elsewhere in a part (e.g. inside an existing
hyphenated word) is preserved verbatim other than casing. An empty string
returns "".
This is the one rule behind duho's own kebab-case derived names (a
command's default subcommand name, the default long flag of a field) --
see duho.args._command_name / the field-flag default in duho.args.
Source code in src/duho/text.py
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | |
ngettext(singular, plural, n)
Source code in src/duho/text.py
35 36 | |
parse_bool(text)
Parse text as a strict boolean.
Case-insensitive after stripping whitespace: a member of :data:BOOL_TRUE
gives True, a member of :data:BOOL_FALSE (including the empty string)
gives False. Anything else, and any non-string, raises
:class:ValueError naming the accepted tokens.
Source code in src/duho/text.py
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | |
pysafe(text, separator='.')
Coerce text into a Python-safe (dotted) identifier.
Runs duho's original (0.5.x) transform first -- a per-part keyword suffix,
then a whole-string -/space-to-_ substitution, then the
:data:PYREPLACE symbol spell-out, applied across the whole joined string
rather than to each part in isolation -- so every input that transform
already turned into a valid identifier still comes out byte-for-byte the
same here, quirks included (pysafe("a+") is "aplus_plus", not the
more obvious "a_plus"; a symbol at a dotted-part boundary, like
pysafe("a.+b"), can affect the neighboring part). Only where THAT
result is not already a valid, non-keyword identifier for one of its
separator-delimited parts does this go further (:func:_pysafe_fixup):
underscoring any character still not identifier-safe, prefixing a leading
digit, and suffixing a newly-produced keyword. Never returns an empty
string, and never returns a string containing a reserved keyword as one of
its dotted parts.
Source code in src/duho/text.py
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 | |
range(start, end, step=1, format=None)
Yield formatted range members between start and end inclusive.
Digit endpoints produce an integer range; single-letter endpoints of the
SAME case produce a character range. format is an optional
str.format spec (including its leading :, e.g. ":03d") applied
to each member. Shadows the builtin range inside this module by design
(not exported on :data:__all__, so from duho.text import * cannot
shadow it for a star-importer).
Raises :class:ValueError -- never silently yields an empty or surprising
range -- for: mismatched-kind endpoints (one digit, one letter), a
multi-character letter endpoint, mixed-case letter endpoints (which would
otherwise walk the ASCII punctuation between the two cases), or a reversed
range (start after end), or a format containing braces or one the
member does not accept (":03d" on a letter range).
Source code in src/duho/text.py
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 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 | |
snakecase(name)
Coerce name to snake_case.
Separators (-, whitespace, _) collapse to a single _; a leading
digit and any other non-word character are underscored; an upper-case letter
is lower-cased, prefixed with _ UNLESS it already immediately follows a
separator (so a title-cased or kebab-case word never produces a doubled
__: CamelCaseName -> camel_case_name, My-App -> my_app,
not my__app). An acronym run is lowered as individual letters
(HTTPServer -> h_t_t_p_server). An empty string returns "".
Source code in src/duho/text.py
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | |
unicode_range(start, end, step=1)
Yield characters from start to end inclusive.
Source code in src/duho/text.py
225 226 227 228 | |