simplibs-path 0.1.0__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.
Files changed (133) hide show
  1. simplibs_path-0.1.0/LICENSE +21 -0
  2. simplibs_path-0.1.0/PKG-INFO +420 -0
  3. simplibs_path-0.1.0/README.md +389 -0
  4. simplibs_path-0.1.0/pyproject.toml +64 -0
  5. simplibs_path-0.1.0/setup.cfg +4 -0
  6. simplibs_path-0.1.0/src/simplibs/path/__init__.py +118 -0
  7. simplibs_path-0.1.0/src/simplibs/path/actions/ChangeSuffix.py +23 -0
  8. simplibs_path-0.1.0/src/simplibs/path/actions/CollectDirs.py +24 -0
  9. simplibs_path-0.1.0/src/simplibs/path/actions/CollectFiles.py +25 -0
  10. simplibs_path-0.1.0/src/simplibs/path/actions/CollectPaths.py +24 -0
  11. simplibs_path-0.1.0/src/simplibs/path/actions/EnsureDir.py +22 -0
  12. simplibs_path-0.1.0/src/simplibs/path/actions/EnsureFile.py +19 -0
  13. simplibs_path-0.1.0/src/simplibs/path/actions/EnsureParentDir.py +20 -0
  14. simplibs_path-0.1.0/src/simplibs/path/actions/FilterPaths.py +27 -0
  15. simplibs_path-0.1.0/src/simplibs/path/actions/MakeUniquePath.py +22 -0
  16. simplibs_path-0.1.0/src/simplibs/path/actions/NormalizeEntryPath.py +20 -0
  17. simplibs_path-0.1.0/src/simplibs/path/actions/NormalizePath.py +23 -0
  18. simplibs_path-0.1.0/src/simplibs/path/actions/RebasePath.py +24 -0
  19. simplibs_path-0.1.0/src/simplibs/path/actions/RelativizePath.py +22 -0
  20. simplibs_path-0.1.0/src/simplibs/path/actions/__init__.py +68 -0
  21. simplibs_path-0.1.0/src/simplibs/path/annotations/PathLike.py +43 -0
  22. simplibs_path-0.1.0/src/simplibs/path/annotations/PathPredicate.py +39 -0
  23. simplibs_path-0.1.0/src/simplibs/path/annotations/RecursionDepth.py +33 -0
  24. simplibs_path-0.1.0/src/simplibs/path/annotations/__init__.py +21 -0
  25. simplibs_path-0.1.0/src/simplibs/path/logic/__init__.py +41 -0
  26. simplibs_path-0.1.0/src/simplibs/path/logic/checks/__init__.py +29 -0
  27. simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/__init__.py +21 -0
  28. simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/_find_length_problem.py +48 -0
  29. simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/_find_windows_name_problem.py +55 -0
  30. simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/can_create_inside.py +39 -0
  31. simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/constants/MAX_NAME_LENGTH.py +18 -0
  32. simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/constants/WINDOWS_FORBIDDEN_CHARS.py +25 -0
  33. simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/constants/WINDOWS_RESERVED_NAMES.py +27 -0
  34. simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/constants/__init__.py +20 -0
  35. simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/find_filename_problem.py +75 -0
  36. simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/to_path.py +77 -0
  37. simplibs_path-0.1.0/src/simplibs/path/logic/checks/is_empty_dir.py +59 -0
  38. simplibs_path-0.1.0/src/simplibs/path/logic/checks/is_hidden_path.py +78 -0
  39. simplibs_path-0.1.0/src/simplibs/path/logic/checks/is_path_readable.py +73 -0
  40. simplibs_path-0.1.0/src/simplibs/path/logic/checks/is_path_writable.py +90 -0
  41. simplibs_path-0.1.0/src/simplibs/path/logic/checks/is_valid_filename.py +47 -0
  42. simplibs_path-0.1.0/src/simplibs/path/logic/checks/is_within.py +88 -0
  43. simplibs_path-0.1.0/src/simplibs/path/logic/ensure/__init__.py +22 -0
  44. simplibs_path-0.1.0/src/simplibs/path/logic/ensure/_validations/__init__.py +25 -0
  45. simplibs_path-0.1.0/src/simplibs/path/logic/ensure/_validations/raise_ensure_dir_error.py +40 -0
  46. simplibs_path-0.1.0/src/simplibs/path/logic/ensure/_validations/raise_ensure_dir_not_a_dir_error.py +36 -0
  47. simplibs_path-0.1.0/src/simplibs/path/logic/ensure/_validations/raise_ensure_file_create_error.py +39 -0
  48. simplibs_path-0.1.0/src/simplibs/path/logic/ensure/_validations/raise_ensure_file_not_a_file_error.py +36 -0
  49. simplibs_path-0.1.0/src/simplibs/path/logic/ensure/_validations/raise_ensure_file_not_found_error.py +36 -0
  50. simplibs_path-0.1.0/src/simplibs/path/logic/ensure/ensure_dir.py +83 -0
  51. simplibs_path-0.1.0/src/simplibs/path/logic/ensure/ensure_file.py +103 -0
  52. simplibs_path-0.1.0/src/simplibs/path/logic/ensure/ensure_parent_dir.py +58 -0
  53. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/__init__.py +35 -0
  54. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_helpers/ScanEntry.py +36 -0
  55. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_helpers/__init__.py +30 -0
  56. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_helpers/as_path_predicate.py +38 -0
  57. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_helpers/build_ignore_matcher.py +88 -0
  58. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_helpers/generate_paths.py +74 -0
  59. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_helpers/is_link_like.py +45 -0
  60. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_helpers/resolve_scan_root.py +66 -0
  61. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_helpers/scan_paths.py +92 -0
  62. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_helpers/walk_tree.py +118 -0
  63. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_validations/__init__.py +24 -0
  64. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_validations/raise_collect_paths_not_a_dir_error.py +34 -0
  65. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_validations/raise_collect_paths_not_found_error.py +38 -0
  66. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_validations/raise_collect_paths_root_unreadable_error.py +38 -0
  67. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_validations/raise_ignore_names_not_names_error.py +36 -0
  68. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_validations/raise_nothing_to_collect_error.py +34 -0
  69. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/collect_dirs.py +62 -0
  70. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/collect_files.py +64 -0
  71. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/collect_paths.py +102 -0
  72. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/filter_paths.py +51 -0
  73. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/ignore_names/DEFAULT_IGNORE_NAMES.py +36 -0
  74. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/ignore_names/IGNORE_BUILD.py +48 -0
  75. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/ignore_names/IGNORE_ENV.py +31 -0
  76. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/ignore_names/IGNORE_GENERIC.py +30 -0
  77. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/ignore_names/IGNORE_IDE.py +20 -0
  78. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/ignore_names/IGNORE_SYSTEM.py +30 -0
  79. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/ignore_names/IGNORE_VCS.py +24 -0
  80. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/ignore_names/__init__.py +30 -0
  81. simplibs_path-0.1.0/src/simplibs/path/logic/scanners/iter_paths.py +91 -0
  82. simplibs_path-0.1.0/src/simplibs/path/logic/transformers/__init__.py +27 -0
  83. simplibs_path-0.1.0/src/simplibs/path/logic/transformers/_helpers/__init__.py +17 -0
  84. simplibs_path-0.1.0/src/simplibs/path/logic/transformers/_helpers/is_valid_suffix.py +37 -0
  85. simplibs_path-0.1.0/src/simplibs/path/logic/transformers/_helpers/split_name.py +46 -0
  86. simplibs_path-0.1.0/src/simplibs/path/logic/transformers/_validations/__init__.py +24 -0
  87. simplibs_path-0.1.0/src/simplibs/path/logic/transformers/_validations/raise_invalid_suffix_error.py +36 -0
  88. simplibs_path-0.1.0/src/simplibs/path/logic/transformers/_validations/raise_normalize_path_error.py +44 -0
  89. simplibs_path-0.1.0/src/simplibs/path/logic/transformers/_validations/raise_path_has_no_name_error.py +36 -0
  90. simplibs_path-0.1.0/src/simplibs/path/logic/transformers/_validations/raise_path_not_within_root_error.py +43 -0
  91. simplibs_path-0.1.0/src/simplibs/path/logic/transformers/_validations/raise_unique_path_exhausted_error.py +39 -0
  92. simplibs_path-0.1.0/src/simplibs/path/logic/transformers/change_suffix.py +77 -0
  93. simplibs_path-0.1.0/src/simplibs/path/logic/transformers/make_unique_path.py +88 -0
  94. simplibs_path-0.1.0/src/simplibs/path/logic/transformers/normalize_entry_path.py +67 -0
  95. simplibs_path-0.1.0/src/simplibs/path/logic/transformers/normalize_path.py +85 -0
  96. simplibs_path-0.1.0/src/simplibs/path/logic/transformers/rebase_path.py +78 -0
  97. simplibs_path-0.1.0/src/simplibs/path/logic/transformers/relativize_path.py +56 -0
  98. simplibs_path-0.1.0/src/simplibs/path/rules/__init__.py +66 -0
  99. simplibs_path-0.1.0/src/simplibs/path/rules/_helpers/PathPartIs.py +152 -0
  100. simplibs_path-0.1.0/src/simplibs/path/rules/_helpers/__init__.py +21 -0
  101. simplibs_path-0.1.0/src/simplibs/path/rules/_helpers/build_not_a_path_exception.py +66 -0
  102. simplibs_path-0.1.0/src/simplibs/path/rules/_helpers/build_path_rule_exception.py +71 -0
  103. simplibs_path-0.1.0/src/simplibs/path/rules/_helpers/check_path.py +48 -0
  104. simplibs_path-0.1.0/src/simplibs/path/rules/_validations/__init__.py +24 -0
  105. simplibs_path-0.1.0/src/simplibs/path/rules/_validations/raise_has_suffix_invalid_suffix_error.py +35 -0
  106. simplibs_path-0.1.0/src/simplibs/path/rules/_validations/raise_has_suffix_no_suffixes_error.py +31 -0
  107. simplibs_path-0.1.0/src/simplibs/path/rules/_validations/raise_rule_param_not_bool_error.py +37 -0
  108. simplibs_path-0.1.0/src/simplibs/path/rules/_validations/raise_rule_param_not_callable_error.py +36 -0
  109. simplibs_path-0.1.0/src/simplibs/path/rules/_validations/raise_rule_param_not_path_error.py +37 -0
  110. simplibs_path-0.1.0/src/simplibs/path/rules/filesystem/IsDir.py +70 -0
  111. simplibs_path-0.1.0/src/simplibs/path/rules/filesystem/IsEmptyDir.py +76 -0
  112. simplibs_path-0.1.0/src/simplibs/path/rules/filesystem/IsEmptyFile.py +76 -0
  113. simplibs_path-0.1.0/src/simplibs/path/rules/filesystem/IsFile.py +76 -0
  114. simplibs_path-0.1.0/src/simplibs/path/rules/filesystem/IsPathReadable.py +77 -0
  115. simplibs_path-0.1.0/src/simplibs/path/rules/filesystem/IsPathWritable.py +82 -0
  116. simplibs_path-0.1.0/src/simplibs/path/rules/filesystem/IsSymlink.py +71 -0
  117. simplibs_path-0.1.0/src/simplibs/path/rules/filesystem/PathExists.py +75 -0
  118. simplibs_path-0.1.0/src/simplibs/path/rules/filesystem/__init__.py +31 -0
  119. simplibs_path-0.1.0/src/simplibs/path/rules/lexical/HasSuffix.py +146 -0
  120. simplibs_path-0.1.0/src/simplibs/path/rules/lexical/IsAbsolutePath.py +74 -0
  121. simplibs_path-0.1.0/src/simplibs/path/rules/lexical/IsHidden.py +92 -0
  122. simplibs_path-0.1.0/src/simplibs/path/rules/lexical/IsRelativePath.py +74 -0
  123. simplibs_path-0.1.0/src/simplibs/path/rules/lexical/IsValidFilename.py +136 -0
  124. simplibs_path-0.1.0/src/simplibs/path/rules/lexical/IsWithin.py +116 -0
  125. simplibs_path-0.1.0/src/simplibs/path/rules/lexical/NameIs.py +37 -0
  126. simplibs_path-0.1.0/src/simplibs/path/rules/lexical/ParentNameIs.py +37 -0
  127. simplibs_path-0.1.0/src/simplibs/path/rules/lexical/StemIs.py +36 -0
  128. simplibs_path-0.1.0/src/simplibs/path/rules/lexical/__init__.py +33 -0
  129. simplibs_path-0.1.0/src/simplibs_path.egg-info/PKG-INFO +420 -0
  130. simplibs_path-0.1.0/src/simplibs_path.egg-info/SOURCES.txt +131 -0
  131. simplibs_path-0.1.0/src/simplibs_path.egg-info/dependency_links.txt +1 -0
  132. simplibs_path-0.1.0/src/simplibs_path.egg-info/requires.txt +8 -0
  133. simplibs_path-0.1.0/src/simplibs_path.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dalibor Sova (Sudip2708)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,420 @@
1
+ Metadata-Version: 2.4
2
+ Name: simplibs-path
3
+ Version: 0.1.0
4
+ Summary: Safe path handling and directory scanning: normalize, check, ensure and collect paths — as plain functions, composable rules and pipeline actions.
5
+ Author-email: "Dalibor Sova (Sudip2708)" <daliborsova@seznam.cz>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/simplibs/simplibs-path
8
+ Project-URL: Repository, https://github.com/simplibs/simplibs-path
9
+ Project-URL: Issues, https://github.com/simplibs/simplibs-path/issues
10
+ Project-URL: Changelog, https://github.com/simplibs/simplibs-path/blob/main/CHANGELOG.md
11
+ Keywords: path,pathlib,file-system,filesystem,files,io,inspection,caller-info,simplibs,simplibs-path,developer-tools
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: Topic :: System :: Filesystems
20
+ Requires-Python: >=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: simplibs-exception>=1.1.4
24
+ Requires-Dist: simplibs-rules>=0.4.0
25
+ Requires-Dist: simplibs-types>=0.1.0
26
+ Requires-Dist: simplibs-validate>=0.3.1
27
+ Requires-Dist: simplibs-actions>=0.2.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=7.0; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # 🧭 `simplibs-path`
33
+
34
+ [![PyPI](https://img.shields.io/pypi/v/simplibs-path)](https://pypi.org/project/simplibs-path/)
35
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/downloads/)
36
+ [![Licence](https://img.shields.io/badge/licence-MIT-green)](https://github.com/simplibs/simplibs-path/blob/main/LICENSE)
37
+
38
+ **Paths you can trust — normalize, check, ensure and scan, without surprises.**
39
+
40
+ A Python library for everyday work with paths and directory trees, built around one
41
+ idea: every operation exists first as a plain, well-behaved **function**, and is then
42
+ offered as a composable **`Rule`** (for filters and validation) and as a pipeline
43
+ **`Action`** (for chained processing). Predicates never raise for disk problems,
44
+ operations that fail explain exactly why through structured
45
+ [`simplibs-exception`](https://pypi.org/project/simplibs-exception/) cards, and the
46
+ defaults are the safe ones — symlink-aware, Windows-aware, nothing silently hidden.
47
+
48
+ ```python
49
+ from simplibs.path import collect_files, rebase_path, ensure_parent_dir
50
+ from simplibs.path.rules import HasSuffix, IsHidden
51
+
52
+ # Select by rule, map to an output location, prepare its folder
53
+ sources = collect_files("~/project", include_filter=HasSuffix(".py") & ~IsHidden())
54
+ target = rebase_path(sources[0], source_root="~/project", target_root="out", new_suffix=".md")
55
+ ensure_parent_dir(target)
56
+ ```
57
+
58
+ > `simplibs-path` is the first building block for file-processing pipelines in the
59
+ > ecosystem. Its `Rule`s are built on [`simplibs-rules`](https://pypi.org/project/simplibs-rules/),
60
+ > its `Action`s on [`simplibs-actions`](https://pypi.org/project/simplibs-actions/).
61
+ > You can use the plain functions without ever touching either.
62
+
63
+ ---
64
+
65
+ ## 🧭 The Core Philosophy
66
+
67
+ Path handling in Python is easy until it is not: a blank string quietly means "the
68
+ current directory", `os.access` lies on Windows, a symlink loop turns a recursive scan
69
+ into an endless one, `.gitignore`-style folders drown the result, and a "check" that
70
+ opens a file for writing leaves an empty file behind. `simplibs-path` takes those
71
+ traps seriously:
72
+
73
+ * **Functions first.** All logic lives in small, independent, reusable functions.
74
+ Rules and actions are thin layers over them — never a second copy of the logic.
75
+ * **Predicates answer, they do not throw.** `is_path_writable`, `is_within`,
76
+ `IsFile()` and friends return a plain `bool` for anything the disk can refuse.
77
+ * **Operations explain themselves.** `ensure_dir`, `collect_paths` and the others raise
78
+ structured, actionable errors (`NotFoundError`, `StateError`, `AccessError`,
79
+ `ParamError`) instead of leaking a bare `OSError`.
80
+ * **Safe defaults.** Symlinks are resolved, links and junctions are never entered
81
+ during a scan, version-control and cache folders are skipped, and path-traversal is
82
+ one call away (`is_within`).
83
+ * **Honest about time.** A filesystem answer is true *now*; the docs say where a check
84
+ is a fail-fast message rather than a guarantee.
85
+
86
+ ---
87
+
88
+ ## 📦 Installation
89
+
90
+ ```bash
91
+ pip install simplibs-path
92
+ ```
93
+
94
+ ---
95
+
96
+ ## 🚀 Quick Start in 60 Seconds
97
+
98
+ ### Level 1: Plain functions
99
+
100
+ ```python
101
+ from simplibs.path import normalize_path, collect_files, ensure_dir
102
+
103
+ root = normalize_path("~/project") # absolute, ~ expanded, symlinks resolved
104
+ files = collect_files(root) # sorted list of Path objects
105
+ ensure_dir(root / "out" / "reports") # created, with its parents, if missing
106
+ ```
107
+
108
+ ### Level 2: Rules as filters and gates
109
+
110
+ ```python
111
+ from simplibs.path import collect_files
112
+ from simplibs.path.rules import HasSuffix, IsHidden, IsFile, IsWithin
113
+
114
+ wanted = HasSuffix(".py", ".md") & ~IsHidden()
115
+ collect_files("~/project", include_filter=wanted)
116
+
117
+ wanted.describe() # -> "suffix one of '.py', '.md' & not hidden path"
118
+ IsWithin("out").is_valid("out/reports/a.md") # -> True
119
+ IsWithin("out").is_valid("out/../secret.txt") # -> False
120
+ ```
121
+
122
+ ### Level 3: Actions as pipeline steps
123
+
124
+ ```python
125
+ from simplibs.path.actions import CollectFiles, FilterPaths
126
+ from simplibs.path.rules import IsDir, HasSuffix, IsHidden
127
+
128
+ pipeline = (
129
+ IsDir() # a Rule becomes a guard step
130
+ >> CollectFiles(include_filter=HasSuffix(".py"))
131
+ >> FilterPaths(rule=~IsHidden())
132
+ )
133
+
134
+ pipeline("~/project") # -> [Path(...), Path(...), ...]
135
+ await pipeline.acall("~/project") # the same, off the event loop
136
+ ```
137
+
138
+ ### Level 4: Safety helpers
139
+
140
+ ```python
141
+ from simplibs.path import is_within, is_valid_filename, make_unique_path
142
+
143
+ is_within(target, "out") # symlink-safe containment check
144
+ is_valid_filename("what?.md") # -> False ('?' is forbidden on Windows)
145
+ make_unique_path("out/report.md") # -> out/report (1).md, if the name is taken
146
+ ```
147
+
148
+ ---
149
+
150
+ ## 🛠️ Architecture & Package Structure
151
+
152
+ ```
153
+ src/simplibs/path/
154
+ ├── logic/ ◄── The functional core — plain functions, no Rule/Action knowledge
155
+ │ ├── transformers/ ◄── normalize_path, normalize_entry_path, rebase_path, relativize_path, change_suffix, make_unique_path
156
+ │ ├── checks/ ◄── is_empty_dir, is_hidden_path, is_path_readable, is_path_writable, is_valid_filename, is_within
157
+ │ ├── ensure/ ◄── ensure_dir, ensure_file, ensure_parent_dir
158
+ │ └── scanners/ ◄── collect_paths, collect_files, collect_dirs, iter_paths, filter_paths, ignore_names
159
+ ├── rules/ ◄── Rule classes over the functions (domain folder)
160
+ │ ├── lexical/ ◄── Judge a path by its text — HasSuffix, NameIs, StemIs, ParentNameIs, IsAbsolutePath, ...
161
+ │ └── filesystem/ ◄── Ask the disk — PathExists, IsFile, IsDir, IsSymlink, IsEmptyDir, ...
162
+ ├── actions/ ◄── Action classes over the functions (domain folder)
163
+ └── annotations/ ◄── Shared types — PathLike, PathPredicate, RecursionDepth
164
+ ```
165
+
166
+ Everything in `logic/` and the three public types is importable straight from
167
+ `simplibs.path`. `rules` and `actions` are separate domains, imported through their own
168
+ folders:
169
+
170
+ ```python
171
+ from simplibs.path import collect_files, PathLike # functions and types
172
+ from simplibs.path.rules import HasSuffix, IsFile # rules
173
+ from simplibs.path.actions import CollectFiles, RebasePath # actions
174
+ ```
175
+
176
+ ---
177
+
178
+ ## 🔄 `logic/transformers/` — Path in, path out
179
+
180
+ Pure path arithmetic: nothing here changes the disk (`make_unique_path` only *looks*).
181
+
182
+ | Function | Description / Parameters |
183
+ |------------------------|----------------------------------------------------------------------------------------------------------------------|
184
+ | `normalize_path` | Absolute, `~`-expanded, collapsed path (`path`, `resolve_symlinks=True`). The path does not have to exist. |
185
+ | `normalize_entry_path` | Resolves the containing directory while preserving the final path name, even if it is a symlink (`path`). |
186
+ | `rebase_path` | Maps a path from one root to the same place under another (`path`, `source_root`, `target_root`, `new_suffix=None`). |
187
+ | `relativize_path` | Expresses a path relative to a root (`path`, `root`). |
188
+ | `change_suffix` | Replaces, adds or removes the last suffix (`path`, `new_suffix`). |
189
+ | `make_unique_path` | Returns the path, or a numbered variant (`name (1).ext`) that does not exist yet (`path`, `max_attempts=1000`). |
190
+
191
+ ➡️ [README_PATH_TRANSFORMERS](https://github.com/simplibs/simplibs-path/blob/main/docs/logic/README_PATH_TRANSFORMERS.md)
192
+
193
+ ---
194
+
195
+ ## ✅ `logic/checks/` — Predicates that never raise
196
+
197
+ Each returns a `bool` and treats any refusal by the operating system as `False`.
198
+ Checks that are a single `pathlib` call (`exists`, `is_file`, `is_dir`, ...) have no
199
+ function here — only their rule.
200
+
201
+ | Function | Description / Parameters |
202
+ |---------------------|----------------------------------------------------------------------------------------------------------------|
203
+ | `is_path_readable` | A file that can be opened, or a directory that can be listed (`path`). |
204
+ | `is_path_writable` | Could be written to *now*, without creating or changing anything (`path`). |
205
+ | `is_empty_dir` | An existing, listable directory with no entries (`path`). |
206
+ | `is_hidden_path` | The final name starts with a dot; optionally the Windows attribute (`path`, `check_attributes=False`). |
207
+ | `is_within` | The path lies inside a root — symlink-safe path-traversal guard (`path`, `root`, `resolve_symlinks=True`, `allow_equal=True`). |
208
+ | `is_valid_filename` | A string usable as a file name; portable across Windows and POSIX by default (`name`, `portable=True`). |
209
+
210
+ ➡️ [README_PATH_CHECKS](https://github.com/simplibs/simplibs-path/blob/main/docs/logic/README_PATH_CHECKS.md)
211
+
212
+ ---
213
+
214
+ ## 🏗️ `logic/ensure/` — Make a state true
215
+
216
+ The functions that change the disk. Each returns the normalized path, so it chains.
217
+
218
+ | Function | Description / Parameters |
219
+ |---------------------|---------------------------------------------------------------------------------------|
220
+ | `ensure_dir` | Guarantees a directory exists, creating its parents too (`path`). |
221
+ | `ensure_file` | Guarantees a file exists, optionally creating it and its folder (`path`, `create=True`). |
222
+ | `ensure_parent_dir` | Guarantees the folder a path lives in exists; returns the path itself (`path`). |
223
+
224
+ ➡️ [README_PATH_ENSURE](https://github.com/simplibs/simplibs-path/blob/main/docs/logic/README_PATH_ENSURE.md)
225
+
226
+ ---
227
+
228
+ ## 🔍 `logic/scanners/` — Look at a directory tree
229
+
230
+ | Function | Description / Parameters |
231
+ |-----------------|--------------------------------------------------------------------------------------------------------------|
232
+ | `collect_paths` | Sorted list of files and/or directories (`root_dir`, `recursive`, `include_filter`, `exclude_filter`, `ignore_names`, `collect_files`, `collect_dirs`). |
233
+ | `collect_files` | Files only — the same options without the type flags. |
234
+ | `collect_dirs` | Directories only — the same options without the type flags. |
235
+ | `iter_paths` | The lazy, unsorted counterpart of `collect_paths` — results as they are found. |
236
+ | `filter_paths` | Keeps the paths of a list that satisfy a `Rule` or callable (`paths`, `rule`). |
237
+
238
+ Scans skip `.git`, `__pycache__`, `node_modules`, virtual environments, IDE folders and
239
+ OS housekeeping files by default (`DEFAULT_IGNORE_NAMES`). Ambiguous folder names such as
240
+ `build` or `dist` are opt-in (`IGNORE_GENERIC`).
241
+
242
+ | Constant | Description |
243
+ |------------------------|----------------------------------------------------------------------------|
244
+ | `DEFAULT_IGNORE_NAMES` | Union of the groups below, without `IGNORE_GENERIC` — the scanners' default. |
245
+ | `IGNORE_VCS` | Version control metadata (`.git`, `.svn`, ...). |
246
+ | `IGNORE_ENV` | Virtual environments, installed dependencies, `.env`. |
247
+ | `IGNORE_BUILD` | Caches and tool output with unmistakable names (`__pycache__`, `*.egg-info`, ...). |
248
+ | `IGNORE_IDE` | Editor and IDE settings. |
249
+ | `IGNORE_SYSTEM` | OS housekeeping (`.DS_Store`, `Thumbs.db`, ...). |
250
+ | `IGNORE_GENERIC` | `build`, `dist`, `site`, `env`, ... — opt-in, because they are also normal folder names. |
251
+
252
+ ➡️ [README_PATH_SCANNERS](https://github.com/simplibs/simplibs-path/blob/main/docs/logic/README_PATH_SCANNERS.md)
253
+
254
+ ---
255
+
256
+ ## 📐 `rules/` — Rules for paths
257
+
258
+ Every rule is a `Rule` from [`simplibs-rules`](https://pypi.org/project/simplibs-rules/):
259
+ composable with `&`, `|` and `~`, usable as a scanner filter, as a `@validate_call`
260
+ annotation (`validated_type(Path, IsDir())`) and as a pipeline gate (`IsDir() >> ...`).
261
+
262
+ ### `lexical/` — Judge a path by its text (cheap, no disk access)
263
+
264
+ | Class | Description / Parameters |
265
+ |-------------------|-----------------------------------------------------------------------------------------------------|
266
+ | `HasSuffix` | The name ends with one of the suffixes (`*suffixes`, `ignore_case=True`). `HasSuffix("")` = no suffix. |
267
+ | `NameIs` | The final name satisfies another rule (`rule`) — any string rule, pointed at the name. |
268
+ | `StemIs` | The name without its last suffix satisfies another rule (`rule`). |
269
+ | `ParentNameIs` | The containing folder's name satisfies another rule (`rule`). |
270
+ | `IsAbsolutePath` | The path is absolute. |
271
+ | `IsRelativePath` | The path is relative. |
272
+ | `IsHidden` | The final name starts with a dot (`check_attributes=False`). |
273
+ | `IsWithin` | The path lies inside a root (`root`, `resolve_symlinks=True`, `allow_equal=True`). |
274
+ | `IsValidFilename` | A string — or a `Path`'s final name — is usable as a file name (`portable=True`). |
275
+
276
+ ### `filesystem/` — Ask the disk
277
+
278
+ | Class | Description |
279
+ |------------------|---------------------------------------------------------------|
280
+ | `PathExists` | Something exists at the path. |
281
+ | `IsFile` | An existing regular file (or a symlink to one). |
282
+ | `IsDir` | An existing directory (or a symlink to one). |
283
+ | `IsSymlink` | The path itself is a symbolic link, working or broken. |
284
+ | `IsEmptyDir` | An existing directory with no entries. |
285
+ | `IsEmptyFile` | An existing regular file of size zero. |
286
+ | `IsPathReadable` | A file that can be read, or a directory that can be listed. |
287
+ | `IsPathWritable` | Could be written to now, with no side effects. |
288
+
289
+ ➡️ [README_PATH_RULES](https://github.com/simplibs/simplibs-path/blob/main/docs/README_PATH_RULES.md)
290
+
291
+ ---
292
+
293
+ ## ⚙️ `actions/` — Pipeline steps
294
+
295
+ Every function that takes one main value and returns one result is also an `Action` from
296
+ [`simplibs-actions`](https://pypi.org/project/simplibs-actions/): configured once, called
297
+ with the main input, chainable with `>>`, `&` and `|`, and awaitable through `acall`.
298
+
299
+ | Action | Wraps | Main input → result |
300
+ |----------------------|------------------------|------------------------------------|
301
+ | `NormalizePath` | `normalize_path` | path → `Path` |
302
+ | `NormalizeEntryPath` | `normalize_entry_path` | path → `Path` |
303
+ | `RebasePath` | `rebase_path` | path → `Path` |
304
+ | `RelativizePath` | `relativize_path` | path → `Path` |
305
+ | `ChangeSuffix` | `change_suffix` | path → `Path` |
306
+ | `MakeUniquePath` | `make_unique_path` | path → `Path` |
307
+ | `EnsureDir` | `ensure_dir` | path → `Path` |
308
+ | `EnsureFile` | `ensure_file` | path → `Path` |
309
+ | `EnsureParentDir` | `ensure_parent_dir` | path → `Path` |
310
+ | `CollectPaths` | `collect_paths` | root → `list[Path]` |
311
+ | `CollectFiles` | `collect_files` | root → `list[Path]` |
312
+ | `CollectDirs` | `collect_dirs` | root → `list[Path]` |
313
+ | `FilterPaths` | `filter_paths` | `list[Path]` → `list[Path]` |
314
+
315
+ Predicates and `iter_paths` have no action: a `Rule` is already a valid pipeline step
316
+ (it becomes a pass-through gate), and a generator does not fit a one-value-in,
317
+ one-value-out model.
318
+
319
+ ➡️ [README_PATH_ACTIONS](https://github.com/simplibs/simplibs-path/blob/main/docs/README_PATH_ACTIONS.md)
320
+
321
+ ---
322
+
323
+ ## 🏷️ `annotations/` — Shared validatable annotations
324
+
325
+ | Type | Description |
326
+ |------------------|------------------------------------------------------------------------------------------------|
327
+ | `PathLike` | A non-blank string or a `Path`. Rejects `""`, which `Path` would silently turn into `.`. |
328
+ | `PathPredicate` | A `(Path) -> bool` callable — a function, a lambda, or any `Rule`. |
329
+ | `RecursionDepth` | `True`/`False` or a non-negative `int` — how deep a scan may go. |
330
+
331
+ Use them in your own signatures so your functions accept exactly what this library does.
332
+
333
+ ➡️ [README_PATH_ANNOTATIONS](https://github.com/simplibs/simplibs-path/blob/main/docs/README_PATH_ANNOTATIONS.md)
334
+
335
+ ---
336
+
337
+ ## 🪟 Platform Notes
338
+
339
+ The library is written for Windows and POSIX alike, and where they differ it says so:
340
+
341
+ * **Symlinks and junctions** are followed when a path is normalized, but never *entered*
342
+ during a scan — so link cycles cannot trap it. On Windows this includes directory
343
+ junctions (`mklink /J`), which `is_symlink()` alone does not detect.
344
+ * **Case:** names compare case-insensitively on Windows and case-sensitively elsewhere
345
+ (`ignore_names`, `Path` ordering).
346
+ * **File names:** `is_valid_filename` is *portable by default* — it rejects names that
347
+ only Windows refuses (`con`, `a:b`, `name.`) so that generated files can be copied
348
+ anywhere.
349
+ * **Permissions:** a check such as `is_path_writable` actually probes the target, because
350
+ `os.access` ignores ACLs and read-only attributes on Windows.
351
+
352
+ ---
353
+
354
+ ## ⚠️ Exceptions
355
+
356
+ Functions raise structured [`simplibs.exception`](https://pypi.org/project/simplibs-exception/)
357
+ cards rather than bare `OSError`s:
358
+
359
+ | Exception | Raised when |
360
+ |-------------------|-------------------------------------------------------------------------------------|
361
+ | `ValidationError` | An argument has the wrong type or shape (e.g. a blank path) — via `validate_call`. |
362
+ | `ParamError` | An argument combination is unusable (path outside its root, both collect flags off, a malformed suffix). |
363
+ | `NotFoundError` | A required path or scan root does not exist. |
364
+ | `StateError` | The filesystem is in the wrong state (a file where a directory is needed, ...). |
365
+ | `AccessError` | The operating system refused (permissions, unresolvable path). |
366
+ | `OperationError` | An operation ran out of options (no free numbered name left). |
367
+
368
+ Predicates (`checks/`, `rules/`) never raise for disk problems — they return `False`.
369
+
370
+ ---
371
+
372
+ ## 🔗 Related libraries
373
+
374
+ * **[`simplibs-rules`](https://pypi.org/project/simplibs-rules/)** — the `Rule` engine behind `simplibs.path.rules`.
375
+ * **[`simplibs-actions`](https://pypi.org/project/simplibs-actions/)** — the `Action` abstraction and containers behind `simplibs.path.actions`.
376
+ * **[`simplibs-validate`](https://pypi.org/project/simplibs-validate/)** — `@validate_call`, which validates every function argument here.
377
+ * **[`simplibs-exception`](https://pypi.org/project/simplibs-exception/)** — the structured exception cards.
378
+
379
+ ---
380
+
381
+ ## ☯️ About simplibs
382
+
383
+ All libraries in the **simplibs** (Simple Libraries) ecosystem share a common
384
+ engineering philosophy:
385
+
386
+ * **Dyslexia-friendly:**
387
+ We actively minimize cognitive load. Code is atomized into small, self-contained units,
388
+ files are named directly after the logical task they perform, and explanations describe
389
+ *why* something is designed, not just *what* it is.
390
+ * **Programmer's Zen:**
391
+ Nothing should be missing, and nothing should be superfluous. We value clean execution
392
+ paths and robust, understandable code architectures over rushed, messy feature sets.
393
+ * **Defensive Style:**
394
+ We actively anticipate edge cases and failure modes so that only safe operational paths
395
+ remain. Our code is built to degrade gracefully rather than crash unexpectedly.
396
+ * **Minimalism:**
397
+ Find the most direct path to the goal in as few operational steps as possible without
398
+ taking shortcuts on safety, readability, or completeness.
399
+ * **Code as Craft:**
400
+ Code should be pleasant to look at, readable at a glance, and evoke structural harmony.
401
+ We treat software engineering as a precision trade.
402
+
403
+ ---
404
+
405
+ ### 🤝 Contributing & Community
406
+
407
+ This is an **open-source project** built with love and care. We strongly believe in
408
+ community collaboration and welcome any feedback, bug reports, or feature ideas!
409
+
410
+ * **Want to contribute?** Feel free to open an Issue or submit a Pull Request.
411
+ * **Want to get in touch?** If you'd like to discuss the project further, collaborate,
412
+ or just say hello, feel free to open a GitHub Issue or start a Discussion.
413
+
414
+ ---
415
+
416
+ ### 📝 License
417
+
418
+ This library is released under the **MIT License**. Build great things!
419
+
420
+ ---