Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python 

2# cardinal_pythonlib/sqlalchemy/subproc.py 

3 

4""" 

5=============================================================================== 

6 

7 Original code copyright (C) 2009-2021 Rudolf Cardinal (rudolf@pobox.com). 

8 

9 This file is part of cardinal_pythonlib. 

10 

11 Licensed under the Apache License, Version 2.0 (the "License"); 

12 you may not use this file except in compliance with the License. 

13 You may obtain a copy of the License at 

14 

15 https://www.apache.org/licenses/LICENSE-2.0 

16 

17 Unless required by applicable law or agreed to in writing, software 

18 distributed under the License is distributed on an "AS IS" BASIS, 

19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

20 See the License for the specific language governing permissions and 

21 limitations under the License. 

22 

23=============================================================================== 

24""" 

25 

26import re 

27from typing import Any, Dict, Iterable, List 

28import unicodedata 

29 

30 

31# ============================================================================= 

32# Finding 

33# ============================================================================= 

34 

35def find_nth(s: str, x: str, n: int = 0, overlap: bool = False) -> int: 

36 """ 

37 Finds the position of *n*\ th occurrence of ``x`` in ``s``, or ``-1`` if 

38 there isn't one. 

39 

40 - The ``n`` parameter is zero-based (i.e. 0 for the first, 1 for the 

41 second...). 

42 - If ``overlap`` is true, allows fragments to overlap. If not, they must be 

43 distinct. 

44  

45 As per 

46 https://stackoverflow.com/questions/1883980/find-the-nth-occurrence-of-substring-in-a-string  

47 """ # noqa 

48 length_of_fragment = 1 if overlap else len(x) 

49 i = -length_of_fragment 

50 for _ in range(n + 1): 

51 i = s.find(x, i + length_of_fragment) 

52 if i < 0: 

53 break 

54 return i 

55 

56 

57# ============================================================================= 

58# Splitting 

59# ============================================================================= 

60 

61def split_string(x: str, n: int) -> List[str]: 

62 """ 

63 Split string into chunks of length n 

64 """ 

65 # https://stackoverflow.com/questions/9475241/split-string-every-nth-character # noqa 

66 return [x[i:i+n] for i in range(0, len(x), n)] 

67 

68 

69# ============================================================================= 

70# Replacement 

71# ============================================================================= 

72 

73def multiple_replace(text: str, rep: Dict[str, str]) -> str: 

74 """ 

75 Returns a version of ``text`` in which the keys of ``rep`` (a dict) have 

76 been replaced by their values. 

77 

78 As per 

79 https://stackoverflow.com/questions/6116978/python-replace-multiple-strings. 

80 """ 

81 rep = dict((re.escape(k), v) for k, v in rep.items()) 

82 pattern = re.compile("|".join(rep.keys())) 

83 return pattern.sub(lambda m: rep[re.escape(m.group(0))], text) 

84 

85 

86def replace_in_list(stringlist: Iterable[str], 

87 replacedict: Dict[str, str]) -> List[str]: 

88 """ 

89 Returns a list produced by applying :func:`multiple_replace` to every 

90 string in ``stringlist``. 

91 

92 Args: 

93 stringlist: list of source strings 

94 replacedict: dictionary mapping "original" to "replacement" strings 

95 

96 Returns: 

97 list of final strings 

98 

99 """ 

100 newlist = [] 

101 for fromstring in stringlist: 

102 newlist.append(multiple_replace(fromstring, replacedict)) 

103 return newlist 

104 

105 

106# ============================================================================= 

107# Mangling to ASCII 

108# ============================================================================= 

109 

110def mangle_unicode_to_ascii(s: Any) -> str: 

111 """ 

112 Mangle unicode to ASCII, losing accents etc. in the process. 

113 """ 

114 # https://stackoverflow.com/questions/1207457 

115 if s is None: 

116 return "" 

117 if not isinstance(s, str): 

118 s = str(s) 

119 return ( 

120 unicodedata.normalize('NFKD', s) 

121 .encode('ascii', 'ignore') # gets rid of accents 

122 .decode('ascii') # back to a string 

123 ) 

124 

125 

126# ============================================================================= 

127# Making strings and string lists 

128# ============================================================================= 

129 

130def strnum(prefix: str, num: int, suffix: str = "") -> str: 

131 """ 

132 Makes a string of the format ``<prefix><number><suffix>``. 

133 """ 

134 return f"{prefix}{num}{suffix}" 

135 

136 

137def strnumlist(prefix: str, numbers: List[int], suffix: str = "") -> List[str]: 

138 """ 

139 Makes a string of the format ``<prefix><number><suffix>`` for every number 

140 in ``numbers``, and returns them as a list. 

141 """ 

142 return [f"{prefix}{num}{suffix}" for num in numbers] 

143 

144 

145def strseq(prefix: str, first: int, last: int, suffix: str = "") -> List[str]: 

146 """ 

147 Makes a string of the format ``<prefix><number><suffix>`` for every number 

148 from ``first`` to ``last`` inclusive, and returns them as a list. 

149 """ 

150 return [strnum(prefix, n, suffix) for n in range(first, last + 1)]