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
|
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2025-2026 Benjamin Abendroth <braph93@gmx.de>
'''String utility functions.'''
import re
import subprocess
from .errors import CrazyError
_VALID_OPTION_STRING_RE = re.compile('-[^\\s,]+')
_VALID_VARIABLE_RE = re.compile('[a-zA-Z_][a-zA-Z0-9_]*')
def contains_space(string):
'''Check if string contains space type characters.'''
return (' ' in string or '\t' in string or '\n' in string)
def is_empty_or_whitespace(string):
'''Check if string is empty or whitespace.'''
return not string.strip()
def is_valid_extended_regex(string):
'''Check if string is a valid extended regular expression.'''
try:
r = subprocess.run(
['grep', '-q', '-E', '--', string],
input=b"",
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
timeout=1,
check=False)
return r.returncode != 2
except (FileNotFoundError, PermissionError, subprocess.TimeoutExpired):
return True
def validate_prog(string):
'''Validate a program string.'''
if is_empty_or_whitespace(string):
raise CrazyError('value is empty')
if string.startswith(' '):
raise CrazyError('begins with space')
if string.endswith(' '):
raise CrazyError('ends with space')
if '\t' in string:
raise CrazyError('contains a tabulator')
if '\n' in string:
raise CrazyError('contains a newline')
if ' ' in string:
raise CrazyError('contains multiple spaces')
def indent(string, num_spaces):
'''Indents each line in a string by a specified number of spaces,
preserving empty lines.
Args:
string (str): The input string to be indented.
num_spaces (int): The number of spaces to indent each line.
Returns:
str: The indented string.
'''
assert isinstance(string, str), \
f"indent: string: expected str, got {string!r}"
assert isinstance(num_spaces, int), \
f"indent: num_spaces: expected int, got {num_spaces!r}"
spaces = ' ' * num_spaces
lines = string.split('\n')
lines = [(spaces + line) if line.strip() else line for line in lines]
return '\n'.join(lines)
def join_with_wrap(primary_sep, secondary_sep,
max_line_length, items, line_prefix=''):
'''
Join a list of strings with a primary separator, automatically wrapping
to a new line (using the secondary separator) when the current line
would exceed max_line_length.
Args:
primary_sep:
String used to separate items on the same line.
secondary_sep:
String used to separate wrapped lines.
max_line_length:
Maximum number of characters per line.
items:
List of strings to join.
line_prefix:
Prefix each line with a prefix.
Returns:
str: Joined string with line wrapping
'''
lines = []
current_items = []
current_length = len(line_prefix)
for item in items:
sep_len = len(primary_sep) if current_items else 0
new_length = current_length + sep_len + len(item)
if current_items and new_length > max_line_length:
# flush current line
lines.append(line_prefix + primary_sep.join(current_items))
current_items = [item]
current_length = len(line_prefix) + len(item)
else:
current_items.append(item)
current_length = new_length
if current_items:
lines.append(line_prefix + primary_sep.join(current_items))
return secondary_sep.join(lines)
def is_valid_option_string(option_string):
'''Check if `option_string` is a valid option string.'''
if not _VALID_OPTION_STRING_RE.fullmatch(option_string):
return False
if option_string == '--':
return False
return True
def is_valid_variable_name(string):
'''Check if string is a valid shell variable name.'''
return _VALID_VARIABLE_RE.fullmatch(string)
def strip_comments(string):
'''Strip shell comments from string.'''
lines = []
for line in string.split('\n'):
if line.lstrip().startswith('#'):
continue
lines.append(line)
return '\n'.join(lines)
def strip_double_empty_lines(string):
'''Collapse triple newlines into double newlines.'''
return string.replace('\n\n\n', '\n\n')
def replace_many(string, replacements):
'''
Replace multiple substrings in `string` according to a list
of (search, replace) tuples.
'''
s = string
for search, replace in replacements:
s = s.replace(search, replace)
return s
|