dycw-utilities 0.166.34__tar.gz → 0.166.36__tar.gz

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.

Potentially problematic release.


This version of dycw-utilities might be problematic. Click here for more details.

Files changed (220) hide show
  1. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/PKG-INFO +1 -1
  2. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/pyproject.toml +2 -2
  3. dycw_utilities-0.166.36/src/tests/test_jinja2.py +87 -0
  4. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_text.py +70 -43
  5. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/__init__.py +1 -1
  6. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/jinja2.py +39 -5
  7. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/text.py +48 -32
  8. dycw_utilities-0.166.34/src/tests/test_jinja2.py +0 -26
  9. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/.gitignore +0 -0
  10. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/LICENSE +0 -0
  11. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/README.md +0 -0
  12. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/__init__.py +0 -0
  13. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/conftest.py +0 -0
  14. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/modules/__init__.py +0 -0
  15. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/modules/package_missing/__init__.py +0 -0
  16. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/modules/package_missing/module.py +0 -0
  17. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/modules/package_with/__init__.py +0 -0
  18. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/modules/package_with/outer_1.py +0 -0
  19. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/modules/package_with/outer_2.py +0 -0
  20. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
  21. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
  22. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
  23. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
  24. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/modules/package_without/__init__.py +0 -0
  25. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/modules/package_without/module_1.py +0 -0
  26. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/modules/package_without/module_2.py +0 -0
  27. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/modules/standalone.py +0 -0
  28. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/modules/with_imports.py +0 -0
  29. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
  30. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
  31. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
  32. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
  33. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
  34. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
  35. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
  36. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
  37. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_aeventkit.py +0 -0
  38. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_altair.py +0 -0
  39. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_asyncio.py +0 -0
  40. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_atomicwrites.py +0 -0
  41. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_atools.py +0 -0
  42. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_cachetools.py +0 -0
  43. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_click.py +0 -0
  44. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_concurrent.py +0 -0
  45. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_contextlib.py +0 -0
  46. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_contextvars.py +0 -0
  47. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_cryptography.py +0 -0
  48. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_cvxpy.py +0 -0
  49. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_dataclasses.py +0 -0
  50. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_enum.py +0 -0
  51. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_errors.py +0 -0
  52. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_fastapi.py +0 -0
  53. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_fpdf2.py +0 -0
  54. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_functions.py +0 -0
  55. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_functools.py +0 -0
  56. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_getpass.py +0 -0
  57. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_git.py +0 -0
  58. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_gzip.py +0 -0
  59. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_hashlib.py +0 -0
  60. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_http.py +0 -0
  61. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_hypothesis.py +0 -0
  62. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_importlib.py +0 -0
  63. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_inflect.py +0 -0
  64. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_ipython.py +0 -0
  65. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_iterables.py +0 -0
  66. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_json.py +0 -0
  67. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_jupyter.py +0 -0
  68. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_libcst.py +0 -0
  69. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_lightweight_charts.py +0 -0
  70. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_logging.py +0 -0
  71. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_math.py +0 -0
  72. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_memory_profiler.py +0 -0
  73. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_modules.py +0 -0
  74. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_more_itertools.py +0 -0
  75. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_numpy.py +0 -0
  76. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_objects/__init__.py +0 -0
  77. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_objects/objects.py +0 -0
  78. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_operator.py +0 -0
  79. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_optuna.py +0 -0
  80. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_orjson.py +0 -0
  81. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_os.py +0 -0
  82. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_parse.py +0 -0
  83. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_pathlib.py +0 -0
  84. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_pickle.py +0 -0
  85. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_platform.py +0 -0
  86. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_polars.py +0 -0
  87. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_polars_ols.py +0 -0
  88. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_postgres.py +0 -0
  89. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_pottery.py +0 -0
  90. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_pqdm.py +0 -0
  91. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_psutil.py +0 -0
  92. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_pydantic_settings.py +0 -0
  93. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_pydantic_settings_sops.py +0 -0
  94. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_pyinstrument.py +0 -0
  95. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_pytest.py +0 -0
  96. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_pytest_randomly.py +0 -0
  97. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_pytest_regressions.py +0 -0
  98. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_random.py +0 -0
  99. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_re.py +0 -0
  100. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_redis.py +0 -0
  101. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_reprlib.py +0 -0
  102. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_scipy.py +0 -0
  103. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_sentinel.py +0 -0
  104. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_shelve.py +0 -0
  105. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_slack_sdk.py +0 -0
  106. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_socket.py +0 -0
  107. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_sqlalchemy.py +0 -0
  108. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_sqlalchemy_polars.py +0 -0
  109. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_statsmodels.py +0 -0
  110. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_string.py +0 -0
  111. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_tempfile.py +0 -0
  112. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_testbook.py +0 -0
  113. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_threading.py +0 -0
  114. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_timer.py +0 -0
  115. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_traceback.py +0 -0
  116. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_typed_settings.py +0 -0
  117. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_types.py +0 -0
  118. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_typing.py +0 -0
  119. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_typing_funcs/__init__.py +0 -0
  120. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_typing_funcs/no_future.py +0 -0
  121. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_typing_funcs/with_future.py +0 -0
  122. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_tzdata.py +0 -0
  123. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_tzlocal.py +0 -0
  124. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_uuid.py +0 -0
  125. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_version.py +0 -0
  126. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_warnings.py +0 -0
  127. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_whenever.py +0 -0
  128. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_zipfile.py +0 -0
  129. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/tests/test_zoneinfo.py +0 -0
  130. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/aeventkit.py +0 -0
  131. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/altair.py +0 -0
  132. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/asyncio.py +0 -0
  133. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/atomicwrites.py +0 -0
  134. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/atools.py +0 -0
  135. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/cachetools.py +0 -0
  136. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/click.py +0 -0
  137. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/concurrent.py +0 -0
  138. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/contextlib.py +0 -0
  139. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/contextvars.py +0 -0
  140. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/cryptography.py +0 -0
  141. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/cvxpy.py +0 -0
  142. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/dataclasses.py +0 -0
  143. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/enum.py +0 -0
  144. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/errors.py +0 -0
  145. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/fastapi.py +0 -0
  146. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/fpdf2.py +0 -0
  147. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/functions.py +0 -0
  148. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/functools.py +0 -0
  149. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/getpass.py +0 -0
  150. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/git.py +0 -0
  151. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/gzip.py +0 -0
  152. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/hashlib.py +0 -0
  153. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/http.py +0 -0
  154. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/hypothesis.py +0 -0
  155. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/importlib.py +0 -0
  156. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/inflect.py +0 -0
  157. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/ipython.py +0 -0
  158. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/iterables.py +0 -0
  159. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/json.py +0 -0
  160. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/jupyter.py +0 -0
  161. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/libcst.py +0 -0
  162. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/lightweight_charts.py +0 -0
  163. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/logging.py +0 -0
  164. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/math.py +0 -0
  165. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/memory_profiler.py +0 -0
  166. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/modules.py +0 -0
  167. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/more_itertools.py +0 -0
  168. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/numpy.py +0 -0
  169. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/operator.py +0 -0
  170. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/optuna.py +0 -0
  171. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/orjson.py +0 -0
  172. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/os.py +0 -0
  173. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/parse.py +0 -0
  174. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/pathlib.py +0 -0
  175. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/pickle.py +0 -0
  176. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/platform.py +0 -0
  177. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/polars.py +0 -0
  178. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/polars_ols.py +0 -0
  179. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/postgres.py +0 -0
  180. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/pottery.py +0 -0
  181. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/pqdm.py +0 -0
  182. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/psutil.py +0 -0
  183. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/py.typed +0 -0
  184. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/pydantic_settings.py +0 -0
  185. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/pydantic_settings_sops.py +0 -0
  186. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/pyinstrument.py +0 -0
  187. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/pytest.py +0 -0
  188. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/pytest_plugins/__init__.py +0 -0
  189. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/pytest_plugins/pytest_randomly.py +0 -0
  190. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/pytest_plugins/pytest_regressions.py +0 -0
  191. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/pytest_regressions.py +0 -0
  192. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/random.py +0 -0
  193. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/re.py +0 -0
  194. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/redis.py +0 -0
  195. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/reprlib.py +0 -0
  196. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/scipy.py +0 -0
  197. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/sentinel.py +0 -0
  198. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/shelve.py +0 -0
  199. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/slack_sdk.py +0 -0
  200. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/socket.py +0 -0
  201. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/sqlalchemy.py +0 -0
  202. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/sqlalchemy_polars.py +0 -0
  203. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/statsmodels.py +0 -0
  204. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/string.py +0 -0
  205. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/tempfile.py +0 -0
  206. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/testbook.py +0 -0
  207. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/threading.py +0 -0
  208. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/timer.py +0 -0
  209. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/traceback.py +0 -0
  210. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/typed_settings.py +0 -0
  211. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/types.py +0 -0
  212. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/typing.py +0 -0
  213. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/tzdata.py +0 -0
  214. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/tzlocal.py +0 -0
  215. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/uuid.py +0 -0
  216. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/version.py +0 -0
  217. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/warnings.py +0 -0
  218. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/whenever.py +0 -0
  219. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/zipfile.py +0 -0
  220. {dycw_utilities-0.166.34 → dycw_utilities-0.166.36}/src/utilities/zoneinfo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.166.34
3
+ Version: 0.166.36
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -249,7 +249,7 @@ dependencies = [
249
249
  name = "dycw-utilities"
250
250
  readme = "README.md"
251
251
  requires-python = ">= 3.12"
252
- version = "0.166.34"
252
+ version = "0.166.36"
253
253
 
254
254
  [project.entry-points.pytest11]
255
255
  pytest-randomly = "utilities.pytest_plugins.pytest_randomly"
@@ -282,7 +282,7 @@ test = [
282
282
  # bump-my-version
283
283
  [tool.bumpversion]
284
284
  allow_dirty = true
285
- current_version = "0.166.34"
285
+ current_version = "0.166.36"
286
286
 
287
287
  [[tool.bumpversion.files]]
288
288
  filename = "src/utilities/__init__.py"
@@ -0,0 +1,87 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from jinja2 import DictLoader
6
+
7
+ from utilities.jinja2 import EnhancedEnvironment, TemplateJob
8
+ from utilities.text import strip_and_dedent
9
+
10
+ if TYPE_CHECKING:
11
+ from pathlib import Path
12
+
13
+
14
+ class TestEnhancedTemplate:
15
+ def test_main(self) -> None:
16
+ env = EnhancedEnvironment(
17
+ loader=DictLoader({
18
+ "test.j2": strip_and_dedent("""
19
+ text = '{{ text }}'
20
+ kebab = '{{ text | kebab }}'
21
+ pascal = '{{ text | pascal }}'
22
+ snake = '{{ text | snake }}'
23
+ """)
24
+ })
25
+ )
26
+ result = env.get_template("test.j2").render(text="multi-word string")
27
+ expected = strip_and_dedent("""
28
+ text = 'multi-word string'
29
+ kebab = 'multi-word-string'
30
+ pascal = 'MultiWordString'
31
+ snake = 'multi_word_string'
32
+ """)
33
+ assert result == expected
34
+
35
+
36
+ class TestTemplateJob:
37
+ def test_main(self, *, tmp_path: Path) -> None:
38
+ path_template = tmp_path.joinpath("template.j2")
39
+ _ = path_template.write_text(
40
+ strip_and_dedent("""
41
+ text = '{{ text }}'
42
+ """)
43
+ )
44
+ path_target = tmp_path.joinpath("target.txt")
45
+ job = TemplateJob(
46
+ template=path_template, kwargs={"text": "example text"}, target=path_target
47
+ )
48
+ expected = strip_and_dedent("""
49
+ text = 'example text'
50
+ """)
51
+ assert job.rendered == expected
52
+ assert not path_target.exists()
53
+ job.run()
54
+ assert path_target.exists()
55
+ assert path_target.read_text() == expected
56
+
57
+ def test_append(self, *, tmp_path: Path) -> None:
58
+ path_template = tmp_path.joinpath("template.j2")
59
+ _ = path_template.write_text(
60
+ strip_and_dedent(
61
+ """
62
+ new = '{{ text }}'
63
+ """,
64
+ trailing=True,
65
+ )
66
+ )
67
+ path_target = tmp_path.joinpath("target.txt")
68
+ _ = path_target.write_text(
69
+ strip_and_dedent(
70
+ """
71
+ old = 'old text'
72
+ """,
73
+ trailing=True,
74
+ )
75
+ )
76
+ job = TemplateJob(
77
+ template=path_template,
78
+ kwargs={"text": "new text"},
79
+ target=path_target,
80
+ mode="append",
81
+ )
82
+ job.run()
83
+ assert path_target.exists()
84
+ assert path_target.read_text() == strip_and_dedent("""
85
+ old = 'old text'
86
+ new = 'new text'
87
+ """)
@@ -28,6 +28,7 @@ from utilities.text import (
28
28
  _SplitStrCountError,
29
29
  _SplitStrOpeningBracketUnmatchedError,
30
30
  join_strs,
31
+ kebab_case,
31
32
  parse_bool,
32
33
  parse_none,
33
34
  pascal_case,
@@ -111,60 +112,86 @@ class TestReprEncode:
111
112
  assert result == expected
112
113
 
113
114
 
114
- class TestPascalAndSnakeCase:
115
+ class TestPascalSnakeAndKebabCase:
115
116
  @mark.parametrize(
116
- ("text", "exp_pascal", "exp_snake"),
117
+ ("text", "exp_pascal", "exp_snake", "exp_kebab"),
117
118
  [
118
- param("API", "API", "api"),
119
- param("APIResponse", "APIResponse", "api_response"),
119
+ param("API", "API", "api", "api"),
120
+ param("APIResponse", "APIResponse", "api_response", "api-response"),
120
121
  param(
121
122
  "ApplicationController",
122
123
  "ApplicationController",
123
124
  "application_controller",
125
+ "application-controller",
124
126
  ),
125
- param("Area51Controller", "Area51Controller", "area51_controller"),
126
- param("FreeBSD", "FreeBSD", "free_bsd"),
127
- param("HTML", "HTML", "html"),
128
- param("HTMLTidy", "HTMLTidy", "html_tidy"),
129
- param("HTMLTidyGenerator", "HTMLTidyGenerator", "html_tidy_generator"),
130
- param("HTMLVersion", "HTMLVersion", "html_version"),
131
- param("NoHTML", "NoHTML", "no_html"),
132
- param("One Two", "OneTwo", "one_two"),
133
- param("One Two", "OneTwo", "one_two"),
134
- param("One Two", "OneTwo", "one_two"),
135
- param("OneTwo", "OneTwo", "one_two"),
136
- param("One_Two", "OneTwo", "one_two"),
137
- param("One__Two", "OneTwo", "one_two"),
138
- param("One___Two", "OneTwo", "one_two"),
139
- param("Product", "Product", "product"),
140
- param("SpecialGuest", "SpecialGuest", "special_guest"),
141
- param("Text", "Text", "text"),
142
- param("Text123", "Text123", "text123"),
143
- param("Text123Text456", "Text123Text456", "text123_text456"),
144
- param("_APIResponse_", "APIResponse", "_api_response_"),
145
- param("_API_", "API", "_api_"),
146
- param("__APIResponse__", "APIResponse", "_api_response_"),
147
- param("__API__", "API", "_api_"),
148
- param("__impliedVolatility_", "ImpliedVolatility", "_implied_volatility_"),
149
- param("_itemID", "ItemID", "_item_id"),
150
- param("_lastPrice__", "LastPrice", "_last_price_"),
151
- param("_symbol", "Symbol", "_symbol"),
152
- param("aB", "AB", "a_b"),
153
- param("changePct", "ChangePct", "change_pct"),
154
- param("changePct_", "ChangePct", "change_pct_"),
155
- param("impliedVolatility", "ImpliedVolatility", "implied_volatility"),
156
- param("lastPrice", "LastPrice", "last_price"),
157
- param("memMB", "MemMB", "mem_mb"),
158
- param("sizeX", "SizeX", "size_x"),
159
- param("symbol", "Symbol", "symbol"),
160
- param("testNTest", "TestNTest", "test_n_test"),
161
- param("text", "Text", "text"),
162
- param("text123", "Text123", "text123"),
127
+ param(
128
+ "Area51Controller",
129
+ "Area51Controller",
130
+ "area51_controller",
131
+ "area51-controller",
132
+ ),
133
+ param("FreeBSD", "FreeBSD", "free_bsd", "free-bsd"),
134
+ param("HTML", "HTML", "html", "html"),
135
+ param("HTMLTidy", "HTMLTidy", "html_tidy", "html-tidy"),
136
+ param(
137
+ "HTMLTidyGenerator",
138
+ "HTMLTidyGenerator",
139
+ "html_tidy_generator",
140
+ "html-tidy-generator",
141
+ ),
142
+ param("HTMLVersion", "HTMLVersion", "html_version", "html-version"),
143
+ param("NoHTML", "NoHTML", "no_html", "no-html"),
144
+ param("One Two", "OneTwo", "one_two", "one-two"),
145
+ param("One Two", "OneTwo", "one_two", "one-two"),
146
+ param("One Two", "OneTwo", "one_two", "one-two"),
147
+ param("OneTwo", "OneTwo", "one_two", "one-two"),
148
+ param("One_Two", "OneTwo", "one_two", "one-two"),
149
+ param("One__Two", "OneTwo", "one_two", "one-two"),
150
+ param("One___Two", "OneTwo", "one_two", "one-two"),
151
+ param("Product", "Product", "product", "product"),
152
+ param("SpecialGuest", "SpecialGuest", "special_guest", "special-guest"),
153
+ param("Text", "Text", "text", "text"),
154
+ param("Text123", "Text123", "text123", "text123"),
155
+ param(
156
+ "Text123Text456", "Text123Text456", "text123_text456", "text123-text456"
157
+ ),
158
+ param("_APIResponse_", "APIResponse", "_api_response_", "-api-response-"),
159
+ param("_API_", "API", "_api_", "-api-"),
160
+ param("__APIResponse__", "APIResponse", "_api_response_", "-api-response-"),
161
+ param("__API__", "API", "_api_", "-api-"),
162
+ param(
163
+ "__impliedVolatility_",
164
+ "ImpliedVolatility",
165
+ "_implied_volatility_",
166
+ "-implied-volatility-",
167
+ ),
168
+ param("_itemID", "ItemID", "_item_id", "-item-id"),
169
+ param("_lastPrice__", "LastPrice", "_last_price_", "-last-price-"),
170
+ param("_symbol", "Symbol", "_symbol", "-symbol"),
171
+ param("aB", "AB", "a_b", "a-b"),
172
+ param("changePct", "ChangePct", "change_pct", "change-pct"),
173
+ param("changePct_", "ChangePct", "change_pct_", "change-pct-"),
174
+ param(
175
+ "impliedVolatility",
176
+ "ImpliedVolatility",
177
+ "implied_volatility",
178
+ "implied-volatility",
179
+ ),
180
+ param("lastPrice", "LastPrice", "last_price", "last-price"),
181
+ param("memMB", "MemMB", "mem_mb", "mem-mb"),
182
+ param("sizeX", "SizeX", "size_x", "size-x"),
183
+ param("symbol", "Symbol", "symbol", "symbol"),
184
+ param("testNTest", "TestNTest", "test_n_test", "test-n-test"),
185
+ param("text", "Text", "text", "text"),
186
+ param("text123", "Text123", "text123", "text123"),
163
187
  ],
164
188
  )
165
- def test_main(self, *, text: str, exp_pascal: str, exp_snake: str) -> None:
189
+ def test_main(
190
+ self, *, text: str, exp_pascal: str, exp_snake: str, exp_kebab: str
191
+ ) -> None:
166
192
  assert pascal_case(text) == exp_pascal
167
193
  assert snake_case(text) == exp_snake
194
+ assert kebab_case(text) == exp_kebab
168
195
 
169
196
 
170
197
  class TestPromptBool:
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.166.34"
3
+ __version__ = "0.166.36"
@@ -1,8 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Any, Literal, override
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, Any, Literal, assert_never, override
4
5
 
5
- from jinja2 import BaseLoader, BytecodeCache, Environment, Undefined
6
+ from jinja2 import BaseLoader, BytecodeCache, Environment, FileSystemLoader, Undefined
6
7
  from jinja2.defaults import (
7
8
  BLOCK_END_STRING,
8
9
  BLOCK_START_STRING,
@@ -18,13 +19,17 @@ from jinja2.defaults import (
18
19
  VARIABLE_START_STRING,
19
20
  )
20
21
 
21
- from utilities.text import pascal_case, snake_case
22
+ from utilities.atomicwrites import writer
23
+ from utilities.text import kebab_case, pascal_case, snake_case
22
24
 
23
25
  if TYPE_CHECKING:
24
26
  from collections.abc import Callable, Sequence
27
+ from pathlib import Path
25
28
 
26
29
  from jinja2.ext import Extension
27
30
 
31
+ from utilities.types import StrMapping
32
+
28
33
 
29
34
  class EnhancedEnvironment(Environment):
30
35
  """Environment with enhanced features."""
@@ -79,8 +84,37 @@ class EnhancedEnvironment(Environment):
79
84
  bytecode_cache,
80
85
  enable_async,
81
86
  )
82
- self.filters["snake"] = snake_case
87
+ self.filters["kebab"] = kebab_case
83
88
  self.filters["pascal"] = pascal_case
89
+ self.filters["snake"] = snake_case
90
+
91
+
92
+ @dataclass(order=True, unsafe_hash=True, kw_only=True, slots=True)
93
+ class TemplateJob:
94
+ """A template with an associated rendering job."""
95
+
96
+ template: Path
97
+ kwargs: StrMapping
98
+ target: Path
99
+ mode: Literal["write", "append"] = "write"
100
+
101
+ def run(self) -> None:
102
+ """Run the job."""
103
+ match self.mode:
104
+ case "write":
105
+ with writer(self.target, overwrite=True) as temp:
106
+ _ = temp.write_text(self.rendered)
107
+ case "append":
108
+ with self.target.open(mode="a") as fh:
109
+ _ = fh.write(self.rendered)
110
+ case never:
111
+ assert_never(never)
112
+
113
+ @property
114
+ def rendered(self) -> str:
115
+ """The template, rendered."""
116
+ env = EnhancedEnvironment(loader=FileSystemLoader(self.template.parent))
117
+ return env.get_template(self.template.name).render(self.kwargs)
84
118
 
85
119
 
86
- __all__ = ["EnhancedEnvironment"]
120
+ __all__ = ["EnhancedEnvironment", "TemplateJob"]
@@ -31,7 +31,15 @@ if TYPE_CHECKING:
31
31
  from utilities.types import MaybeCallableBoolLike, MaybeCallableStr, StrStrMapping
32
32
 
33
33
 
34
- DEFAULT_SEPARATOR = ","
34
+ _DEFAULT_SEPARATOR = ","
35
+
36
+
37
+ ##
38
+
39
+
40
+ def kebab_case(text: str, /) -> str:
41
+ """Convert text into kebab case."""
42
+ return _kebab_snake_case(text, "-")
35
43
 
36
44
 
37
45
  ##
@@ -110,27 +118,13 @@ def repr_encode(obj: Any, /) -> bytes:
110
118
 
111
119
  def snake_case(text: str, /) -> str:
112
120
  """Convert text into snake case."""
113
- leading = bool(search(r"^_", text))
114
- trailing = bool(search(r"_$", text))
115
- parts = _SPLIT_TEXT.findall(text)
116
- parts = (p for p in parts if len(p) >= 1)
117
- parts = chain([""] if leading else [], parts, [""] if trailing else [])
118
- return "_".join(parts).lower()
121
+ return _kebab_snake_case(text, "_")
119
122
 
120
123
 
121
- _SPLIT_TEXT = re.compile(
122
- r"""
123
- [A-Z]+(?=[A-Z][a-z0-9]) | # all caps followed by Upper+lower or digit (API in APIResponse2)
124
- [A-Z]?[a-z]+[0-9]* | # normal words with optional trailing digits (Text123)
125
- [A-Z]+[0-9]* | # consecutive caps with optional trailing digits (ID2)
126
- """,
127
- flags=VERBOSE,
128
- )
129
-
130
124
  ##
131
125
 
132
126
 
133
- LIST_SEPARATOR = DEFAULT_SEPARATOR
127
+ LIST_SEPARATOR = _DEFAULT_SEPARATOR
134
128
  PAIR_SEPARATOR = "="
135
129
  BRACKETS = [("(", ")"), ("[", "]"), ("{", "}")]
136
130
 
@@ -140,7 +134,7 @@ def split_key_value_pairs(
140
134
  text: str,
141
135
  /,
142
136
  *,
143
- list_separator: str = DEFAULT_SEPARATOR,
137
+ list_separator: str = _DEFAULT_SEPARATOR,
144
138
  pair_separator: str = PAIR_SEPARATOR,
145
139
  brackets: Iterable[tuple[str, str]] | None = BRACKETS,
146
140
  mapping: Literal[True],
@@ -150,7 +144,7 @@ def split_key_value_pairs(
150
144
  text: str,
151
145
  /,
152
146
  *,
153
- list_separator: str = DEFAULT_SEPARATOR,
147
+ list_separator: str = _DEFAULT_SEPARATOR,
154
148
  pair_separator: str = PAIR_SEPARATOR,
155
149
  brackets: Iterable[tuple[str, str]] | None = BRACKETS,
156
150
  mapping: Literal[False] = False,
@@ -160,7 +154,7 @@ def split_key_value_pairs(
160
154
  text: str,
161
155
  /,
162
156
  *,
163
- list_separator: str = DEFAULT_SEPARATOR,
157
+ list_separator: str = _DEFAULT_SEPARATOR,
164
158
  pair_separator: str = PAIR_SEPARATOR,
165
159
  brackets: Iterable[tuple[str, str]] | None = BRACKETS,
166
160
  mapping: bool = False,
@@ -169,7 +163,7 @@ def split_key_value_pairs(
169
163
  text: str,
170
164
  /,
171
165
  *,
172
- list_separator: str = DEFAULT_SEPARATOR,
166
+ list_separator: str = _DEFAULT_SEPARATOR,
173
167
  pair_separator: str = PAIR_SEPARATOR,
174
168
  brackets: Iterable[tuple[str, str]] | None = BRACKETS,
175
169
  mapping: bool = False,
@@ -228,7 +222,7 @@ def split_str(
228
222
  text: str,
229
223
  /,
230
224
  *,
231
- separator: str = DEFAULT_SEPARATOR,
225
+ separator: str = _DEFAULT_SEPARATOR,
232
226
  brackets: Iterable[tuple[str, str]] | None = None,
233
227
  n: Literal[1],
234
228
  ) -> tuple[str]: ...
@@ -237,7 +231,7 @@ def split_str(
237
231
  text: str,
238
232
  /,
239
233
  *,
240
- separator: str = DEFAULT_SEPARATOR,
234
+ separator: str = _DEFAULT_SEPARATOR,
241
235
  brackets: Iterable[tuple[str, str]] | None = None,
242
236
  n: Literal[2],
243
237
  ) -> tuple[str, str]: ...
@@ -246,7 +240,7 @@ def split_str(
246
240
  text: str,
247
241
  /,
248
242
  *,
249
- separator: str = DEFAULT_SEPARATOR,
243
+ separator: str = _DEFAULT_SEPARATOR,
250
244
  brackets: Iterable[tuple[str, str]] | None = None,
251
245
  n: Literal[3],
252
246
  ) -> tuple[str, str, str]: ...
@@ -255,7 +249,7 @@ def split_str(
255
249
  text: str,
256
250
  /,
257
251
  *,
258
- separator: str = DEFAULT_SEPARATOR,
252
+ separator: str = _DEFAULT_SEPARATOR,
259
253
  brackets: Iterable[tuple[str, str]] | None = None,
260
254
  n: Literal[4],
261
255
  ) -> tuple[str, str, str, str]: ...
@@ -264,7 +258,7 @@ def split_str(
264
258
  text: str,
265
259
  /,
266
260
  *,
267
- separator: str = DEFAULT_SEPARATOR,
261
+ separator: str = _DEFAULT_SEPARATOR,
268
262
  brackets: Iterable[tuple[str, str]] | None = None,
269
263
  n: Literal[5],
270
264
  ) -> tuple[str, str, str, str, str]: ...
@@ -273,7 +267,7 @@ def split_str(
273
267
  text: str,
274
268
  /,
275
269
  *,
276
- separator: str = DEFAULT_SEPARATOR,
270
+ separator: str = _DEFAULT_SEPARATOR,
277
271
  brackets: Iterable[tuple[str, str]] | None = None,
278
272
  n: int | None = None,
279
273
  ) -> tuple[str, ...]: ...
@@ -281,7 +275,7 @@ def split_str(
281
275
  text: str,
282
276
  /,
283
277
  *,
284
- separator: str = DEFAULT_SEPARATOR,
278
+ separator: str = _DEFAULT_SEPARATOR,
285
279
  brackets: Iterable[tuple[str, str]] | None = None,
286
280
  n: int | None = None,
287
281
  ) -> tuple[str, ...]:
@@ -306,7 +300,7 @@ def _split_str_brackets(
306
300
  brackets: Iterable[tuple[str, str]],
307
301
  /,
308
302
  *,
309
- separator: str = DEFAULT_SEPARATOR,
303
+ separator: str = _DEFAULT_SEPARATOR,
310
304
  ) -> list[str]:
311
305
  brackets = list(brackets)
312
306
  opens, closes = transpose(brackets)
@@ -397,7 +391,7 @@ class _SplitStrOpeningBracketUnmatchedError(SplitStrError):
397
391
 
398
392
 
399
393
  def join_strs(
400
- texts: Iterable[str], /, *, sort: bool = False, separator: str = DEFAULT_SEPARATOR
394
+ texts: Iterable[str], /, *, sort: bool = False, separator: str = _DEFAULT_SEPARATOR
401
395
  ) -> str:
402
396
  """Join a collection of strings, with a special provision for the empty list."""
403
397
  texts = list(texts)
@@ -410,7 +404,7 @@ def join_strs(
410
404
  return separator.join(texts)
411
405
 
412
406
 
413
- def _escape_separator(*, separator: str = DEFAULT_SEPARATOR) -> str:
407
+ def _escape_separator(*, separator: str = _DEFAULT_SEPARATOR) -> str:
414
408
  return f"\\{separator}"
415
409
 
416
410
 
@@ -513,9 +507,30 @@ def unique_str() -> str:
513
507
  return f"{now}_{pid}_{ident}_{key}"
514
508
 
515
509
 
510
+ ##
511
+
512
+
513
+ def _kebab_snake_case(text: str, separator: str, /) -> str:
514
+ """Convert text into kebab/snake case."""
515
+ leading = bool(search(r"^_", text))
516
+ trailing = bool(search(r"_$", text))
517
+ parts = _SPLIT_TEXT.findall(text)
518
+ parts = (p for p in parts if len(p) >= 1)
519
+ parts = chain([""] if leading else [], parts, [""] if trailing else [])
520
+ return separator.join(parts).lower()
521
+
522
+
523
+ _SPLIT_TEXT = re.compile(
524
+ r"""
525
+ [A-Z]+(?=[A-Z][a-z0-9]) | # all caps followed by Upper+lower or digit (API in APIResponse2)
526
+ [A-Z]?[a-z]+[0-9]* | # normal words with optional trailing digits (Text123)
527
+ [A-Z]+[0-9]* | # consecutive caps with optional trailing digits (ID2)
528
+ """,
529
+ flags=VERBOSE,
530
+ )
531
+
516
532
  __all__ = [
517
533
  "BRACKETS",
518
- "DEFAULT_SEPARATOR",
519
534
  "LIST_SEPARATOR",
520
535
  "PAIR_SEPARATOR",
521
536
  "ParseBoolError",
@@ -523,6 +538,7 @@ __all__ = [
523
538
  "SplitKeyValuePairsError",
524
539
  "SplitStrError",
525
540
  "join_strs",
541
+ "kebab_case",
526
542
  "parse_bool",
527
543
  "parse_none",
528
544
  "pascal_case",
@@ -1,26 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from jinja2 import DictLoader
4
-
5
- from utilities.jinja2 import EnhancedEnvironment
6
- from utilities.text import strip_and_dedent
7
-
8
-
9
- class TestEnhancedTemplate:
10
- def test_main(self) -> None:
11
- env = EnhancedEnvironment(
12
- loader=DictLoader({
13
- "test.j2": strip_and_dedent("""
14
- text = '{{ text }}'
15
- pascal = '{{ text | pascal }}'
16
- snake = '{{ text | snake }}'
17
- """)
18
- })
19
- )
20
- result = env.get_template("test.j2").render(text="multi-word string")
21
- expected = strip_and_dedent("""
22
- text = 'multi-word string'
23
- pascal = 'MultiWordString'
24
- snake = 'multi_word_string'
25
- """)
26
- assert result == expected