Coverage for src/toolbox_python/strings.py: 100%

22 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-24 10:34 +0000

1# ============================================================================ # 

2# # 

3# Title : Strings # 

4# Purpose : Manipulate and check strings. # 

5# # 

6# ============================================================================ # 

7 

8 

9# ---------------------------------------------------------------------------- # 

10# # 

11# Overview #### 

12# # 

13# ---------------------------------------------------------------------------- # 

14 

15 

16# ---------------------------------------------------------------------------- # 

17# Description #### 

18# ---------------------------------------------------------------------------- # 

19 

20 

21""" 

22!!! note "Summary" 

23 The `strings` module is for manipulating and checking certain string objects. 

24""" 

25 

26 

27# ---------------------------------------------------------------------------- # 

28# # 

29# Setup #### 

30# # 

31# ---------------------------------------------------------------------------- # 

32 

33 

34# ---------------------------------------------------------------------------- # 

35# Imports #### 

36# ---------------------------------------------------------------------------- # 

37 

38 

39# ## Python StdLib Imports ---- 

40import re 

41import string 

42 

43# ## Python Third Party Imports ---- 

44from typeguard import typechecked 

45 

46# ## Local First Party Imports ---- 

47from toolbox_python.collection_types import str_list, str_list_tuple 

48 

49 

50# ---------------------------------------------------------------------------- # 

51# Exports #### 

52# ---------------------------------------------------------------------------- # 

53 

54__all__: str_list = [ 

55 "str_replace", 

56 "str_contains", 

57 "str_contains_any", 

58 "str_contains_all", 

59 "str_separate_number_chars", 

60] 

61 

62 

63# ---------------------------------------------------------------------------- # 

64# # 

65# Functions #### 

66# # 

67# ---------------------------------------------------------------------------- # 

68 

69 

70@typechecked 

71def str_replace( 

72 old_string: str, 

73 replace_chars: str = string.punctuation + string.whitespace, 

74 replace_with: str = "", 

75) -> str: 

76 """ 

77 !!! note "Summary" 

78 Replace the characters with a given string. 

79 

80 ???+ abstract "Details" 

81 Similar to the Python `#!py str.replace()` method, but provides more customisation through the use of the [`re`](https://docs.python.org/3/library/re.html) package. 

82 

83 Params: 

84 old_string (str): 

85 The old string to be replaced. 

86 replace_chars (str, optional): 

87 The characters that need replacing.<br> 

88 Defaults to `#!py string.punctuation + string.whitespace`. 

89 replace_with (str, optional): 

90 The value to replace the characters with.<br> 

91 Defaults to `""`. 

92 

93 Raises: 

94 TypeError: If any of the inputs parsed to the parameters of this function are not the correct type. Uses the [`@typeguard.typechecked`](https://typeguard.readthedocs.io/en/stable/api.html#typeguard.typechecked) decorator. 

95 

96 Returns: 

97 (str): 

98 The new formatted string. 

99 

100 ???+ example "Examples" 

101 

102 ```{.py .python linenums="1" title="Set up"} 

103 >>> from toolbox_python.strings import str_replace 

104 >>> long_string = "This long string" 

105 >>> complex_sentence = "Because my pizza was cold, I put it in the microwave." 

106 ``` 

107 

108 ```{.py .python linenums="1" title="Example 1: Replace all spaces (` `) with underscore (`_`)"} 

109 >>> print(str_replace(long_string, " ", "_")) 

110 ``` 

111 <div class="result" markdown> 

112 ```{.sh .shell title="Terminal"} 

113 "This_long_string" 

114 ``` 

115 !!! success "Conclusion: Successful conversion." 

116 </div> 

117 

118 ```{.py .python linenums="1" title="Example 2: Remove all punctuation and white space"} 

119 >>> print(str_replace(complex_sentence)) 

120 ``` 

121 <div class="result" markdown> 

122 ```{.sh .shell title="Terminal"} 

123 "BecausemylunchwascoldIputitinthemicrowave" 

124 ``` 

125 !!! success "Conclusion: Successful conversion." 

126 </div> 

127 

128 ```{.py .python linenums="1" title="Example 3: Invalid `old_string` input"} 

129 >>> print(str_replace(123)) 

130 ``` 

131 <div class="result" markdown> 

132 ```{.sh .shell title="Terminal"} 

133 TypeError: ... 

134 ``` 

135 !!! failure "Conclusion: Invalid input." 

136 !!! observation "Note: The same error will occur if `replace_chars` or `replace_with` are not of type `str`." 

137 </div> 

138 

139 ??? success "Credit" 

140 Full credit goes to:<br> 

141 https://stackoverflow.com/questions/23996118/replace-special-characters-in-a-string-python#answer-23996414 

142 

143 ??? tip "See Also" 

144 - [`re`](https://docs.python.org/3/library/re.html) 

145 """ 

146 chars: str = re.escape(replace_chars) 

147 return re.sub(rf"[{chars}]", replace_with, old_string) 

148 

149 

150@typechecked 

151def str_contains(check_string: str, sub_string: str) -> bool: 

152 """ 

153 !!! note "Summary" 

154 Check whether one string contains another string. 

155 

156 ???+ abstract "Details" 

157 This is a super simple one-line function. 

158 

159 ```py linenums="1" title="Example" 

160 return True if sub_string in check_string else False 

161 ``` 

162 

163 Params: 

164 check_string (str): 

165 The main string to check. 

166 sub_string (str): 

167 The substring to check. 

168 

169 Raises: 

170 TypeError: If any of the inputs parsed to the parameters of this function are not the correct type. Uses the [`@typeguard.typechecked`](https://typeguard.readthedocs.io/en/stable/api.html#typeguard.typechecked) decorator. 

171 

172 Returns: 

173 (bool): 

174 `#!py True` if `#!py sub_string` in `#!py check_string` 

175 

176 ???+ example "Examples" 

177 

178 ```{.py .python linenums="1" title="Set up"} 

179 >>> from toolbox_python.strings import str_contains 

180 >>> long_string = "This long string" 

181 ``` 

182 

183 ```{.py .python linenums="1" title="Example 1: String is contained"} 

184 >>> print(str_contains(long_string, "long")) 

185 ``` 

186 <div class="result" markdown> 

187 ```{.sh .shell title="Terminal"} 

188 True 

189 ``` 

190 !!! success "Conclusion: `#!py long_string` contains `#!py "long"`." 

191 </div> 

192 

193 ```{.py .python linenums="1" title="Example 2: String is not contained"} 

194 >>> print(str_contains(long_string, "short")) 

195 ``` 

196 <div class="result" markdown> 

197 ```{.sh .shell title="Terminal"} 

198 False 

199 ``` 

200 !!! success "Conclusion: `#!py long_string` does not contain `#!py "short"`." 

201 </div> 

202 

203 ```{.py .python linenums="1" title="Example 3: Invalid `check_string` input"} 

204 >>> print(str_contains(123, "short")) 

205 ``` 

206 <div class="result" markdown> 

207 ```{.sh .shell title="Terminal"} 

208 TypeError: ... 

209 ``` 

210 !!! failure "Conclusion: Invalid input." 

211 !!! observation "Note: The same error will occur if `sub_string` is not of type `str`." 

212 </div> 

213 

214 ??? tip "See Also" 

215 - [`str_contains_any()`][toolbox_python.strings.str_contains_any] 

216 - [`str_contains_all()`][toolbox_python.strings.str_contains_all] 

217 """ 

218 return sub_string in check_string 

219 

220 

221@typechecked 

222def str_contains_any( 

223 check_string: str, 

224 sub_strings: str_list_tuple, 

225) -> bool: 

226 """ 

227 !!! note "Summary" 

228 Check whether any one of a number of strings are contained within a main string. 

229 

230 Params: 

231 check_string (str): 

232 The main string to check. 

233 sub_strings (str_list_tuple): 

234 The collection of substrings to check. 

235 

236 Raises: 

237 TypeError: If any of the inputs parsed to the parameters of this function are not the correct type. Uses the [`@typeguard.typechecked`](https://typeguard.readthedocs.io/en/stable/api.html#typeguard.typechecked) decorator. 

238 

239 Returns: 

240 (bool): 

241 `#!py True` if `#!py any` of the strings in `#!py sub_strings` are contained within `#!py check_string`. 

242 

243 ???+ example "Examples" 

244 

245 ```{.py .python linenums="1" title="Set up"} 

246 >>> from toolbox_python.strings import str_contains_any 

247 >>> long_string = "This long string" 

248 ``` 

249 

250 ```{.py .python linenums="1" title="Example 1: Contains any"} 

251 >>> print(str_contains_any(long_string, ["long", "short"])) 

252 ``` 

253 <div class="result" markdown> 

254 ```{.sh .shell title="Terminal"} 

255 True 

256 ``` 

257 !!! success "Conclusion: `#!py long_string` contains either `#!py "long"` or `#!py "short"`." 

258 </div> 

259 

260 ```{.py .python linenums="1" title="Example 2: Contains none"} 

261 >>> print(str_contains_any(long_string, ["this", "that"])) 

262 ``` 

263 <div class="result" markdown> 

264 ```{.sh .shell title="Terminal"} 

265 False 

266 ``` 

267 !!! success "Conclusion: `#!py long_string` contains neither `#!py "this"` nor `#!py "that"`." 

268 </div> 

269 

270 ```{.py .python linenums="1" title="Example 3: Invalid `check_string` input"} 

271 >>> print(str_contains_any(123, ["short", "long"])) 

272 ``` 

273 <div class="result" markdown> 

274 ```{.sh .shell title="Terminal"} 

275 TypeError: ... 

276 ``` 

277 !!! failure "Conclusion: Invalid input." 

278 !!! observation "Note: The same error will occur if any of the elements in `sub_strings` are not of type `str`." 

279 </div> 

280 

281 ??? tip "See Also" 

282 - [`str_contains()`][toolbox_python.strings.str_contains] 

283 - [`str_contains_all()`][toolbox_python.strings.str_contains_all] 

284 """ 

285 return any( 

286 str_contains( 

287 check_string=check_string, 

288 sub_string=sub_string, 

289 ) 

290 for sub_string in sub_strings 

291 ) 

292 

293 

294@typechecked 

295def str_contains_all( 

296 check_string: str, 

297 sub_strings: str_list_tuple, 

298) -> bool: 

299 """ 

300 !!! note "Summary" 

301 Check to ensure that all sub-strings are contained within a main string. 

302 

303 Params: 

304 check_string (str): 

305 The main string to check. 

306 sub_strings (str_list_tuple): 

307 The collection of substrings to check. 

308 

309 Raises: 

310 TypeError: If any of the inputs parsed to the parameters of this function are not the correct type. Uses the [`@typeguard.typechecked`](https://typeguard.readthedocs.io/en/stable/api.html#typeguard.typechecked) decorator. 

311 

312 Returns: 

313 (bool): 

314 `#!py True` if `#!py all` of the strings in `#!py sub_strings` are contained within `#!py check_string`. 

315 

316 ???+ example "Examples" 

317 

318 ```{.py .python linenums="1" title="Set up"} 

319 >>> from toolbox_python.strings import str_contains_all 

320 >>> long_string = "This long string" 

321 ``` 

322 

323 ```{.py .python linenums="1" title="Example 1: Contains all"} 

324 >>> print(str_contains_all(long_string, ["long", "string"])) 

325 ``` 

326 <div class="result" markdown> 

327 ```{.sh .shell title="Terminal"} 

328 True 

329 ``` 

330 !!! success "Conclusion: `#!py long_string` contains both `#!py "long"` and `#!py "string"`." 

331 </div> 

332 

333 ```{.py .python linenums="1" title="Example 2: Contains some"} 

334 >>> print(str_contains_all(long_string, ["long", "something"])) 

335 ``` 

336 <div class="result" markdown> 

337 ```{.sh .shell title="Terminal"} 

338 False 

339 ``` 

340 !!! failure "Conclusion: `#!py long_string` contains `#!py "long"` but not `#!py "something"`." 

341 </div> 

342 

343 ```{.py .python linenums="1" title="Example 3: Contains none"} 

344 >>> print(str_contains_all(long_string, ["this", "that"])) 

345 ``` 

346 <div class="result" markdown> 

347 ```{.sh .shell title="Terminal"} 

348 False 

349 ``` 

350 !!! failure "Conclusion: `#!py long_string` contains neither `#!py "this"` nor `#!py "that"`." 

351 </div> 

352 

353 ```{.py .python linenums="1" title="Example 4: Invalid `check_string` input"} 

354 >>> print(str_contains_all(123, ["short", "long"])) 

355 ``` 

356 <div class="result" markdown> 

357 ```{.sh .shell title="Terminal"} 

358 TypeError: ... 

359 ``` 

360 !!! failure "Conclusion: Invalid input." 

361 !!! observation "Note: The same error will occur if any of the elements in `sub_strings` are not of type `str`." 

362 </div> 

363 

364 ??? tip "See Also" 

365 - [`str_contains()`][toolbox_python.strings.str_contains] 

366 - [`str_contains_any()`][toolbox_python.strings.str_contains_any] 

367 """ 

368 return all( 

369 str_contains( 

370 check_string=check_string, 

371 sub_string=sub_string, 

372 ) 

373 for sub_string in sub_strings 

374 ) 

375 

376 

377@typechecked 

378def str_separate_number_chars(text: str) -> str_list: 

379 """ 

380 !!! note "Summary" 

381 Take in a string that contains both numbers and letters, and output a list of strings, separated to have each element containing either entirely number or entirely letters. 

382 

383 ???+ abstract "Details" 

384 Uses regex ([`re.split()`](https://docs.python.org/3/library/re.html#re.split)) to perform the actual splitting.<br> 

385 Note, it _will_ preserve special characters & punctuation, but it _will not_ preserve whitespaces. 

386 

387 Params: 

388 text (str): 

389 The string to split. 

390 

391 Raises: 

392 TypeError: If any of the inputs parsed to the parameters of this function are not the correct type. Uses the [`@typeguard.typechecked`](https://typeguard.readthedocs.io/en/stable/api.html#typeguard.typechecked) decorator. 

393 

394 Returns: 

395 (str_list): 

396 The updated list, with each element of the list containing either entirely characters or entirely numbers. 

397 

398 ???+ example "Examples" 

399 

400 ```{.py .python linenums="1" title="Set up"} 

401 >>> from toolbox_python.strings import str_contains_all 

402 >>> simple_string = "-12.1grams" 

403 >>> complex_string = "abcd2343 abw34324 abc3243-23A 123" 

404 ``` 

405 

406 ```{.py .python linenums="1" title="Example 1: Simple split"} 

407 >>> print(str_separate_number_chars(simple_string)) 

408 ``` 

409 <div class="result" markdown> 

410 ```{.sh .shell title="Terminal"} 

411 ["-12.1", "grams"] 

412 ``` 

413 !!! success "Conclusion: Successful split." 

414 </div> 

415 

416 ```{.py .python linenums="1" title="Example 2: Complex split"} 

417 >>> print(str_separate_number_chars(complex_string)) 

418 ``` 

419 <div class="result" markdown> 

420 ```{.sh .shell title="Terminal"} 

421 [ 

422 "abcd", 

423 "2343", 

424 "abw", 

425 "34324", 

426 "abc", 

427 "3243", 

428 "-23", 

429 "A", 

430 "123", 

431 ] 

432 ``` 

433 !!! success "Conclusion: Successful split." 

434 </div> 

435 

436 ```{.py .python linenums="1" title="Example 3: `text` does not contain any numbers"} 

437 >>> print(str_separate_number_chars("abcd")) 

438 ``` 

439 <div class="result" markdown> 

440 ```{.sh .shell title="Terminal"} 

441 ["abcd"] 

442 ``` 

443 !!! success "Conclusion: No numbers in `#!py text`, so returns a single-element long list." 

444 </div> 

445 

446 ```{.py .python linenums="1" title="Example 4: Invalid `text` input"} 

447 >>> print(str_separate_number_chars(123)) 

448 ``` 

449 <div class="result" markdown> 

450 ```{.sh .shell title="Terminal"} 

451 TypeError: ... 

452 ``` 

453 !!! failure "Conclusion: Invalid input." 

454 </div> 

455 

456 ??? success "Credit" 

457 Full credit goes to:<br> 

458 https://stackoverflow.com/questions/3340081/product-code-looks-like-abcd2343-how-to-split-by-letters-and-numbers#answer-63362709. 

459 

460 ??? tip "See Also" 

461 - [`re`](https://docs.python.org/3/library/re.html) 

462 """ 

463 res = re.split(r"([-+]?\d+\.\d+)|([-+]?\d+)", text.strip()) 

464 return [r.strip() for r in res if r is not None and r.strip() != ""]