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.
- simplibs_path-0.1.0/LICENSE +21 -0
- simplibs_path-0.1.0/PKG-INFO +420 -0
- simplibs_path-0.1.0/README.md +389 -0
- simplibs_path-0.1.0/pyproject.toml +64 -0
- simplibs_path-0.1.0/setup.cfg +4 -0
- simplibs_path-0.1.0/src/simplibs/path/__init__.py +118 -0
- simplibs_path-0.1.0/src/simplibs/path/actions/ChangeSuffix.py +23 -0
- simplibs_path-0.1.0/src/simplibs/path/actions/CollectDirs.py +24 -0
- simplibs_path-0.1.0/src/simplibs/path/actions/CollectFiles.py +25 -0
- simplibs_path-0.1.0/src/simplibs/path/actions/CollectPaths.py +24 -0
- simplibs_path-0.1.0/src/simplibs/path/actions/EnsureDir.py +22 -0
- simplibs_path-0.1.0/src/simplibs/path/actions/EnsureFile.py +19 -0
- simplibs_path-0.1.0/src/simplibs/path/actions/EnsureParentDir.py +20 -0
- simplibs_path-0.1.0/src/simplibs/path/actions/FilterPaths.py +27 -0
- simplibs_path-0.1.0/src/simplibs/path/actions/MakeUniquePath.py +22 -0
- simplibs_path-0.1.0/src/simplibs/path/actions/NormalizeEntryPath.py +20 -0
- simplibs_path-0.1.0/src/simplibs/path/actions/NormalizePath.py +23 -0
- simplibs_path-0.1.0/src/simplibs/path/actions/RebasePath.py +24 -0
- simplibs_path-0.1.0/src/simplibs/path/actions/RelativizePath.py +22 -0
- simplibs_path-0.1.0/src/simplibs/path/actions/__init__.py +68 -0
- simplibs_path-0.1.0/src/simplibs/path/annotations/PathLike.py +43 -0
- simplibs_path-0.1.0/src/simplibs/path/annotations/PathPredicate.py +39 -0
- simplibs_path-0.1.0/src/simplibs/path/annotations/RecursionDepth.py +33 -0
- simplibs_path-0.1.0/src/simplibs/path/annotations/__init__.py +21 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/__init__.py +41 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/checks/__init__.py +29 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/__init__.py +21 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/_find_length_problem.py +48 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/_find_windows_name_problem.py +55 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/can_create_inside.py +39 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/constants/MAX_NAME_LENGTH.py +18 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/constants/WINDOWS_FORBIDDEN_CHARS.py +25 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/constants/WINDOWS_RESERVED_NAMES.py +27 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/constants/__init__.py +20 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/find_filename_problem.py +75 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/checks/_helpers/to_path.py +77 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/checks/is_empty_dir.py +59 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/checks/is_hidden_path.py +78 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/checks/is_path_readable.py +73 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/checks/is_path_writable.py +90 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/checks/is_valid_filename.py +47 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/checks/is_within.py +88 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/ensure/__init__.py +22 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/ensure/_validations/__init__.py +25 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/ensure/_validations/raise_ensure_dir_error.py +40 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/ensure/_validations/raise_ensure_dir_not_a_dir_error.py +36 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/ensure/_validations/raise_ensure_file_create_error.py +39 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/ensure/_validations/raise_ensure_file_not_a_file_error.py +36 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/ensure/_validations/raise_ensure_file_not_found_error.py +36 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/ensure/ensure_dir.py +83 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/ensure/ensure_file.py +103 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/ensure/ensure_parent_dir.py +58 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/__init__.py +35 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_helpers/ScanEntry.py +36 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_helpers/__init__.py +30 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_helpers/as_path_predicate.py +38 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_helpers/build_ignore_matcher.py +88 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_helpers/generate_paths.py +74 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_helpers/is_link_like.py +45 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_helpers/resolve_scan_root.py +66 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_helpers/scan_paths.py +92 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_helpers/walk_tree.py +118 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_validations/__init__.py +24 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_validations/raise_collect_paths_not_a_dir_error.py +34 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_validations/raise_collect_paths_not_found_error.py +38 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_validations/raise_collect_paths_root_unreadable_error.py +38 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_validations/raise_ignore_names_not_names_error.py +36 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/_validations/raise_nothing_to_collect_error.py +34 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/collect_dirs.py +62 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/collect_files.py +64 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/collect_paths.py +102 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/filter_paths.py +51 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/ignore_names/DEFAULT_IGNORE_NAMES.py +36 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/ignore_names/IGNORE_BUILD.py +48 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/ignore_names/IGNORE_ENV.py +31 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/ignore_names/IGNORE_GENERIC.py +30 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/ignore_names/IGNORE_IDE.py +20 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/ignore_names/IGNORE_SYSTEM.py +30 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/ignore_names/IGNORE_VCS.py +24 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/ignore_names/__init__.py +30 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/scanners/iter_paths.py +91 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/transformers/__init__.py +27 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/transformers/_helpers/__init__.py +17 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/transformers/_helpers/is_valid_suffix.py +37 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/transformers/_helpers/split_name.py +46 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/transformers/_validations/__init__.py +24 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/transformers/_validations/raise_invalid_suffix_error.py +36 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/transformers/_validations/raise_normalize_path_error.py +44 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/transformers/_validations/raise_path_has_no_name_error.py +36 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/transformers/_validations/raise_path_not_within_root_error.py +43 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/transformers/_validations/raise_unique_path_exhausted_error.py +39 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/transformers/change_suffix.py +77 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/transformers/make_unique_path.py +88 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/transformers/normalize_entry_path.py +67 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/transformers/normalize_path.py +85 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/transformers/rebase_path.py +78 -0
- simplibs_path-0.1.0/src/simplibs/path/logic/transformers/relativize_path.py +56 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/__init__.py +66 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/_helpers/PathPartIs.py +152 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/_helpers/__init__.py +21 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/_helpers/build_not_a_path_exception.py +66 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/_helpers/build_path_rule_exception.py +71 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/_helpers/check_path.py +48 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/_validations/__init__.py +24 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/_validations/raise_has_suffix_invalid_suffix_error.py +35 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/_validations/raise_has_suffix_no_suffixes_error.py +31 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/_validations/raise_rule_param_not_bool_error.py +37 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/_validations/raise_rule_param_not_callable_error.py +36 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/_validations/raise_rule_param_not_path_error.py +37 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/filesystem/IsDir.py +70 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/filesystem/IsEmptyDir.py +76 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/filesystem/IsEmptyFile.py +76 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/filesystem/IsFile.py +76 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/filesystem/IsPathReadable.py +77 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/filesystem/IsPathWritable.py +82 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/filesystem/IsSymlink.py +71 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/filesystem/PathExists.py +75 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/filesystem/__init__.py +31 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/lexical/HasSuffix.py +146 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/lexical/IsAbsolutePath.py +74 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/lexical/IsHidden.py +92 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/lexical/IsRelativePath.py +74 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/lexical/IsValidFilename.py +136 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/lexical/IsWithin.py +116 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/lexical/NameIs.py +37 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/lexical/ParentNameIs.py +37 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/lexical/StemIs.py +36 -0
- simplibs_path-0.1.0/src/simplibs/path/rules/lexical/__init__.py +33 -0
- simplibs_path-0.1.0/src/simplibs_path.egg-info/PKG-INFO +420 -0
- simplibs_path-0.1.0/src/simplibs_path.egg-info/SOURCES.txt +131 -0
- simplibs_path-0.1.0/src/simplibs_path.egg-info/dependency_links.txt +1 -0
- simplibs_path-0.1.0/src/simplibs_path.egg-info/requires.txt +8 -0
- 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
|
+
[](https://pypi.org/project/simplibs-path/)
|
|
35
|
+
[](https://www.python.org/downloads/)
|
|
36
|
+
[](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
|
+
---
|