dycw-utilities 0.175.18__py3-none-any.whl → 0.175.20__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dycw-utilities
3
- Version: 0.175.18
3
+ Version: 0.175.20
4
4
  Summary: Miscellaneous Python utilities
5
5
  Author: Derek Wan
6
6
  Author-email: Derek Wan <d.wan@icloud.com>
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=psCN55v4h2RM9fNitaqX4CE9cRd60bDV0qAa6_nnbEw,61
1
+ utilities/__init__.py,sha256=0iNHFjfunbBFef4oYY7Vp7CHutwqqxx7liLuA-yDLJQ,61
2
2
  utilities/altair.py,sha256=TLfRFbG9HwG7SLXoJ-v0r-t49ZaGgTQZD82cpjVi4vs,9085
3
3
  utilities/asyncio.py,sha256=aJySVxBY0gqsIYnoNmH7-1r8djKuf4vSsU69VCD08t8,16772
4
4
  utilities/atomicwrites.py,sha256=tPo6r-Rypd9u99u66B9z86YBPpnLrlHtwox_8Z7T34Y,5790
@@ -80,7 +80,7 @@ utilities/sqlalchemy.py,sha256=HQYpd7LFxdTF5WYVWYtCJeEBI71EJm7ytvCGyAH9B-U,37163
80
80
  utilities/sqlalchemy_polars.py,sha256=JCGhB37raSR7fqeWV5dTsciRTMVzIdVT9YSqKT0piT0,13370
81
81
  utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
82
82
  utilities/string.py,sha256=shmBK87zZwzGyixuNuXCiUbqzfeZ9xlrFwz6JTaRvDk,582
83
- utilities/subprocess.py,sha256=ySVxt8V5WcEsrsy6cdqiK4kb15wGfkQxpxePJM_NCe0,43843
83
+ utilities/subprocess.py,sha256=1nM6Fb-aD4xO673bqWt79rReS2gAhUGmxiA00ZPHYcU,45578
84
84
  utilities/tempfile.py,sha256=a3_M1QyxGZql_VcGkBOQBeWbbkItjgkfIpVyzU1UAic,3843
85
85
  utilities/testbook.py,sha256=j1KmaVbrX9VrbeMgtPh5gk55myAsn3dyRUn7jGbPbRk,1294
86
86
  utilities/text.py,sha256=7SvwcSR2l_5cOrm1samGnR4C-ZI6qyFLHLzSpO1zeHQ,13958
@@ -97,7 +97,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
97
97
  utilities/whenever.py,sha256=F4ek0-OBWxHYrZdmoZt76N2RnNyKY5KrEHt7rqO4AQE,60183
98
98
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
99
99
  utilities/zoneinfo.py,sha256=tdIScrTB2-B-LH0ukb1HUXKooLknOfJNwHk10MuMYvA,3619
100
- dycw_utilities-0.175.18.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
101
- dycw_utilities-0.175.18.dist-info/entry_points.txt,sha256=cOGtKeJI0KXLSV7MJ8Dhc2G8jPgDcBDm53MVNJU4ycI,136
102
- dycw_utilities-0.175.18.dist-info/METADATA,sha256=NfSdn67Xz5devAMMKWOQknDOuGRphL-0POujF8Vv8FM,1443
103
- dycw_utilities-0.175.18.dist-info/RECORD,,
100
+ dycw_utilities-0.175.20.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
101
+ dycw_utilities-0.175.20.dist-info/entry_points.txt,sha256=cOGtKeJI0KXLSV7MJ8Dhc2G8jPgDcBDm53MVNJU4ycI,136
102
+ dycw_utilities-0.175.20.dist-info/METADATA,sha256=vhmsFDQysT0e5L5jasWdhZPF6zNePjTpq0iLx5LQ_uw,1443
103
+ dycw_utilities-0.175.20.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.175.18"
3
+ __version__ = "0.175.20"
utilities/subprocess.py CHANGED
@@ -5,6 +5,7 @@ import sys
5
5
  from contextlib import contextmanager
6
6
  from dataclasses import dataclass
7
7
  from io import StringIO
8
+ from itertools import repeat
8
9
  from pathlib import Path
9
10
  from re import search
10
11
  from shlex import join
@@ -54,11 +55,36 @@ UPDATE_CA_CERTIFICATES: str = "update-ca-certificates"
54
55
  ##
55
56
 
56
57
 
58
+ def append_text(
59
+ path: PathLike,
60
+ text: str,
61
+ /,
62
+ *,
63
+ sudo: bool = False,
64
+ skip_if_present: bool = False,
65
+ flags: int = 0,
66
+ blank_lines: int = 1,
67
+ ) -> None:
68
+ """Append text to a file."""
69
+ try:
70
+ existing = cat(path, sudo=sudo)
71
+ except (CalledProcessError, FileNotFoundError):
72
+ tee(path, text, sudo=sudo, append=True)
73
+ return
74
+ if skip_if_present and (search(text, existing, flags=flags) is not None):
75
+ return
76
+ full = "".join([*repeat("\n", times=blank_lines), text])
77
+ tee(path, full, sudo=sudo, append=True)
78
+
79
+
80
+ ##
81
+
82
+
57
83
  def apt_install(package: str, /, *, update: bool = False, sudo: bool = False) -> None:
58
84
  """Install a package."""
59
85
  if update: # pragma: no cover
60
86
  run(*maybe_sudo_cmd(*APT_UPDATE, sudo=sudo))
61
- run(*maybe_sudo_cmd(*apt_install_cmd(package), sudo=sudo))
87
+ run(*maybe_sudo_cmd(*apt_install_cmd(package), sudo=sudo)) # pragma: no cover
62
88
 
63
89
 
64
90
  def apt_install_cmd(package: str, /) -> list[str]:
@@ -69,9 +95,17 @@ def apt_install_cmd(package: str, /) -> list[str]:
69
95
  ##
70
96
 
71
97
 
72
- def cat_cmd(path: PathLike, /) -> list[str]:
98
+ def cat(path: PathLike, /, *paths: PathLike, sudo: bool = False) -> str:
99
+ """Concatenate and print files."""
100
+ if sudo: # pragma: no cover
101
+ return run(*sudo_cmd(*cat_cmd(path, *paths)), return_=True)
102
+ all_paths = list(map(Path, [path, *paths]))
103
+ return "\n".join(p.read_text() for p in all_paths)
104
+
105
+
106
+ def cat_cmd(path: PathLike, /, *paths: PathLike) -> list[str]:
73
107
  """Command to use 'cat' to concatenate and print files."""
74
- return ["cat", str(path)]
108
+ return ["cat", str(path), *map(str, paths)]
75
109
 
76
110
 
77
111
  ##
@@ -170,6 +204,24 @@ def chpasswd(user_name: str, password: str, /, *, sudo: bool = False) -> None:
170
204
  ##
171
205
 
172
206
 
207
+ def copy_text(
208
+ src: PathLike,
209
+ dest: PathLike,
210
+ /,
211
+ *,
212
+ sudo: bool = False,
213
+ substitutions: StrMapping | None = None,
214
+ ) -> None:
215
+ """Copy the text contents of a file."""
216
+ text = cat(src, sudo=sudo)
217
+ if substitutions is not None:
218
+ text = Template(text).substitute(**substitutions)
219
+ tee(dest, text, sudo=sudo)
220
+
221
+
222
+ ##
223
+
224
+
173
225
  def cp(
174
226
  src: PathLike,
175
227
  dest: PathLike,
@@ -356,6 +408,20 @@ def mv_cmd(src: PathLike, dest: PathLike, /) -> list[str]:
356
408
  ##
357
409
 
358
410
 
411
+ def replace_text(
412
+ path: PathLike, /, *replacements: tuple[str, str], sudo: bool = False
413
+ ) -> None:
414
+ """Replace the text in a file."""
415
+ path = Path(path)
416
+ text = cat(path, sudo=sudo)
417
+ for old, new in replacements:
418
+ text = text.replace(old, new)
419
+ tee(path, text, sudo=sudo)
420
+
421
+
422
+ ##
423
+
424
+
359
425
  def ripgrep(*args: str, path: PathLike = PWD) -> str | None:
360
426
  """Search for lines."""
361
427
  try: # skipif-ci
@@ -379,7 +445,8 @@ def rm(path: PathLike, /, *paths: PathLike, sudo: bool = False) -> None:
379
445
  if sudo: # pragma: no cover
380
446
  run(*sudo_cmd(*rm_cmd(path, *paths)))
381
447
  else:
382
- for p in map(Path, [path, *paths]):
448
+ all_paths = list(map(Path, [path, *paths]))
449
+ for p in all_paths:
383
450
  if p.is_file():
384
451
  p.unlink(missing_ok=True)
385
452
  elif p.is_dir():
@@ -1260,7 +1327,7 @@ def symlink_cmd(target: PathLike, link: PathLike, /) -> list[str]:
1260
1327
  def tee(
1261
1328
  path: PathLike, text: str, /, *, sudo: bool = False, append: bool = False
1262
1329
  ) -> None:
1263
- """Use 'tee' to duplicate standard input."""
1330
+ """Duplicate standard input."""
1264
1331
  mkdir(path, sudo=sudo, parent=True)
1265
1332
  if sudo: # pragma: no cover
1266
1333
  run(*sudo_cmd(*tee_cmd(path, append=append)), input=text)
@@ -1516,14 +1583,17 @@ __all__ = [
1516
1583
  "RsyncCmdError",
1517
1584
  "RsyncCmdNoSourcesError",
1518
1585
  "RsyncCmdSourcesNotFoundError",
1586
+ "append_text",
1519
1587
  "apt_install",
1520
1588
  "apt_install_cmd",
1589
+ "cat",
1521
1590
  "cd_cmd",
1522
1591
  "chmod",
1523
1592
  "chmod_cmd",
1524
1593
  "chown",
1525
1594
  "chown_cmd",
1526
1595
  "chpasswd",
1596
+ "copy_text",
1527
1597
  "cp",
1528
1598
  "cp_cmd",
1529
1599
  "echo_cmd",
@@ -1540,6 +1610,7 @@ __all__ = [
1540
1610
  "mkdir_cmd",
1541
1611
  "mv",
1542
1612
  "mv_cmd",
1613
+ "replace_text",
1543
1614
  "ripgrep",
1544
1615
  "ripgrep_cmd",
1545
1616
  "rm",