simplibs-path 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- simplibs/path/__init__.py +118 -0
- simplibs/path/actions/ChangeSuffix.py +23 -0
- simplibs/path/actions/CollectDirs.py +24 -0
- simplibs/path/actions/CollectFiles.py +25 -0
- simplibs/path/actions/CollectPaths.py +24 -0
- simplibs/path/actions/EnsureDir.py +22 -0
- simplibs/path/actions/EnsureFile.py +19 -0
- simplibs/path/actions/EnsureParentDir.py +20 -0
- simplibs/path/actions/FilterPaths.py +27 -0
- simplibs/path/actions/MakeUniquePath.py +22 -0
- simplibs/path/actions/NormalizeEntryPath.py +20 -0
- simplibs/path/actions/NormalizePath.py +23 -0
- simplibs/path/actions/RebasePath.py +24 -0
- simplibs/path/actions/RelativizePath.py +22 -0
- simplibs/path/actions/__init__.py +68 -0
- simplibs/path/annotations/PathLike.py +43 -0
- simplibs/path/annotations/PathPredicate.py +39 -0
- simplibs/path/annotations/RecursionDepth.py +33 -0
- simplibs/path/annotations/__init__.py +21 -0
- simplibs/path/logic/__init__.py +41 -0
- simplibs/path/logic/checks/__init__.py +29 -0
- simplibs/path/logic/checks/_helpers/__init__.py +21 -0
- simplibs/path/logic/checks/_helpers/_find_length_problem.py +48 -0
- simplibs/path/logic/checks/_helpers/_find_windows_name_problem.py +55 -0
- simplibs/path/logic/checks/_helpers/can_create_inside.py +39 -0
- simplibs/path/logic/checks/_helpers/constants/MAX_NAME_LENGTH.py +18 -0
- simplibs/path/logic/checks/_helpers/constants/WINDOWS_FORBIDDEN_CHARS.py +25 -0
- simplibs/path/logic/checks/_helpers/constants/WINDOWS_RESERVED_NAMES.py +27 -0
- simplibs/path/logic/checks/_helpers/constants/__init__.py +20 -0
- simplibs/path/logic/checks/_helpers/find_filename_problem.py +75 -0
- simplibs/path/logic/checks/_helpers/to_path.py +77 -0
- simplibs/path/logic/checks/is_empty_dir.py +59 -0
- simplibs/path/logic/checks/is_hidden_path.py +78 -0
- simplibs/path/logic/checks/is_path_readable.py +73 -0
- simplibs/path/logic/checks/is_path_writable.py +90 -0
- simplibs/path/logic/checks/is_valid_filename.py +47 -0
- simplibs/path/logic/checks/is_within.py +88 -0
- simplibs/path/logic/ensure/__init__.py +22 -0
- simplibs/path/logic/ensure/_validations/__init__.py +25 -0
- simplibs/path/logic/ensure/_validations/raise_ensure_dir_error.py +40 -0
- simplibs/path/logic/ensure/_validations/raise_ensure_dir_not_a_dir_error.py +36 -0
- simplibs/path/logic/ensure/_validations/raise_ensure_file_create_error.py +39 -0
- simplibs/path/logic/ensure/_validations/raise_ensure_file_not_a_file_error.py +36 -0
- simplibs/path/logic/ensure/_validations/raise_ensure_file_not_found_error.py +36 -0
- simplibs/path/logic/ensure/ensure_dir.py +83 -0
- simplibs/path/logic/ensure/ensure_file.py +103 -0
- simplibs/path/logic/ensure/ensure_parent_dir.py +58 -0
- simplibs/path/logic/scanners/__init__.py +35 -0
- simplibs/path/logic/scanners/_helpers/ScanEntry.py +36 -0
- simplibs/path/logic/scanners/_helpers/__init__.py +30 -0
- simplibs/path/logic/scanners/_helpers/as_path_predicate.py +38 -0
- simplibs/path/logic/scanners/_helpers/build_ignore_matcher.py +88 -0
- simplibs/path/logic/scanners/_helpers/generate_paths.py +74 -0
- simplibs/path/logic/scanners/_helpers/is_link_like.py +45 -0
- simplibs/path/logic/scanners/_helpers/resolve_scan_root.py +66 -0
- simplibs/path/logic/scanners/_helpers/scan_paths.py +92 -0
- simplibs/path/logic/scanners/_helpers/walk_tree.py +118 -0
- simplibs/path/logic/scanners/_validations/__init__.py +24 -0
- simplibs/path/logic/scanners/_validations/raise_collect_paths_not_a_dir_error.py +34 -0
- simplibs/path/logic/scanners/_validations/raise_collect_paths_not_found_error.py +38 -0
- simplibs/path/logic/scanners/_validations/raise_collect_paths_root_unreadable_error.py +38 -0
- simplibs/path/logic/scanners/_validations/raise_ignore_names_not_names_error.py +36 -0
- simplibs/path/logic/scanners/_validations/raise_nothing_to_collect_error.py +34 -0
- simplibs/path/logic/scanners/collect_dirs.py +62 -0
- simplibs/path/logic/scanners/collect_files.py +64 -0
- simplibs/path/logic/scanners/collect_paths.py +102 -0
- simplibs/path/logic/scanners/filter_paths.py +51 -0
- simplibs/path/logic/scanners/ignore_names/DEFAULT_IGNORE_NAMES.py +36 -0
- simplibs/path/logic/scanners/ignore_names/IGNORE_BUILD.py +48 -0
- simplibs/path/logic/scanners/ignore_names/IGNORE_ENV.py +31 -0
- simplibs/path/logic/scanners/ignore_names/IGNORE_GENERIC.py +30 -0
- simplibs/path/logic/scanners/ignore_names/IGNORE_IDE.py +20 -0
- simplibs/path/logic/scanners/ignore_names/IGNORE_SYSTEM.py +30 -0
- simplibs/path/logic/scanners/ignore_names/IGNORE_VCS.py +24 -0
- simplibs/path/logic/scanners/ignore_names/__init__.py +30 -0
- simplibs/path/logic/scanners/iter_paths.py +91 -0
- simplibs/path/logic/transformers/__init__.py +27 -0
- simplibs/path/logic/transformers/_helpers/__init__.py +17 -0
- simplibs/path/logic/transformers/_helpers/is_valid_suffix.py +37 -0
- simplibs/path/logic/transformers/_helpers/split_name.py +46 -0
- simplibs/path/logic/transformers/_validations/__init__.py +24 -0
- simplibs/path/logic/transformers/_validations/raise_invalid_suffix_error.py +36 -0
- simplibs/path/logic/transformers/_validations/raise_normalize_path_error.py +44 -0
- simplibs/path/logic/transformers/_validations/raise_path_has_no_name_error.py +36 -0
- simplibs/path/logic/transformers/_validations/raise_path_not_within_root_error.py +43 -0
- simplibs/path/logic/transformers/_validations/raise_unique_path_exhausted_error.py +39 -0
- simplibs/path/logic/transformers/change_suffix.py +77 -0
- simplibs/path/logic/transformers/make_unique_path.py +88 -0
- simplibs/path/logic/transformers/normalize_entry_path.py +67 -0
- simplibs/path/logic/transformers/normalize_path.py +85 -0
- simplibs/path/logic/transformers/rebase_path.py +78 -0
- simplibs/path/logic/transformers/relativize_path.py +56 -0
- simplibs/path/rules/__init__.py +66 -0
- simplibs/path/rules/_helpers/PathPartIs.py +152 -0
- simplibs/path/rules/_helpers/__init__.py +21 -0
- simplibs/path/rules/_helpers/build_not_a_path_exception.py +66 -0
- simplibs/path/rules/_helpers/build_path_rule_exception.py +71 -0
- simplibs/path/rules/_helpers/check_path.py +48 -0
- simplibs/path/rules/_validations/__init__.py +24 -0
- simplibs/path/rules/_validations/raise_has_suffix_invalid_suffix_error.py +35 -0
- simplibs/path/rules/_validations/raise_has_suffix_no_suffixes_error.py +31 -0
- simplibs/path/rules/_validations/raise_rule_param_not_bool_error.py +37 -0
- simplibs/path/rules/_validations/raise_rule_param_not_callable_error.py +36 -0
- simplibs/path/rules/_validations/raise_rule_param_not_path_error.py +37 -0
- simplibs/path/rules/filesystem/IsDir.py +70 -0
- simplibs/path/rules/filesystem/IsEmptyDir.py +76 -0
- simplibs/path/rules/filesystem/IsEmptyFile.py +76 -0
- simplibs/path/rules/filesystem/IsFile.py +76 -0
- simplibs/path/rules/filesystem/IsPathReadable.py +77 -0
- simplibs/path/rules/filesystem/IsPathWritable.py +82 -0
- simplibs/path/rules/filesystem/IsSymlink.py +71 -0
- simplibs/path/rules/filesystem/PathExists.py +75 -0
- simplibs/path/rules/filesystem/__init__.py +31 -0
- simplibs/path/rules/lexical/HasSuffix.py +146 -0
- simplibs/path/rules/lexical/IsAbsolutePath.py +74 -0
- simplibs/path/rules/lexical/IsHidden.py +92 -0
- simplibs/path/rules/lexical/IsRelativePath.py +74 -0
- simplibs/path/rules/lexical/IsValidFilename.py +136 -0
- simplibs/path/rules/lexical/IsWithin.py +116 -0
- simplibs/path/rules/lexical/NameIs.py +37 -0
- simplibs/path/rules/lexical/ParentNameIs.py +37 -0
- simplibs/path/rules/lexical/StemIs.py +36 -0
- simplibs/path/rules/lexical/__init__.py +33 -0
- simplibs_path-0.1.0.dist-info/METADATA +420 -0
- simplibs_path-0.1.0.dist-info/RECORD +128 -0
- simplibs_path-0.1.0.dist-info/WHEEL +5 -0
- simplibs_path-0.1.0.dist-info/licenses/LICENSE +21 -0
- simplibs_path-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from .logic.checks import (
|
|
2
|
+
is_empty_dir,
|
|
3
|
+
is_hidden_path,
|
|
4
|
+
is_path_readable,
|
|
5
|
+
is_path_writable,
|
|
6
|
+
is_valid_filename,
|
|
7
|
+
is_within,
|
|
8
|
+
)
|
|
9
|
+
from .logic.ensure import (
|
|
10
|
+
ensure_dir,
|
|
11
|
+
ensure_file,
|
|
12
|
+
ensure_parent_dir,
|
|
13
|
+
)
|
|
14
|
+
from .logic.scanners import (
|
|
15
|
+
DEFAULT_IGNORE_NAMES,
|
|
16
|
+
IGNORE_BUILD,
|
|
17
|
+
IGNORE_ENV,
|
|
18
|
+
IGNORE_GENERIC,
|
|
19
|
+
IGNORE_IDE,
|
|
20
|
+
IGNORE_SYSTEM,
|
|
21
|
+
IGNORE_VCS,
|
|
22
|
+
collect_dirs,
|
|
23
|
+
collect_files,
|
|
24
|
+
collect_paths,
|
|
25
|
+
filter_paths,
|
|
26
|
+
iter_paths,
|
|
27
|
+
)
|
|
28
|
+
from .logic.transformers import (
|
|
29
|
+
change_suffix,
|
|
30
|
+
make_unique_path,
|
|
31
|
+
normalize_path,
|
|
32
|
+
normalize_entry_path,
|
|
33
|
+
rebase_path,
|
|
34
|
+
relativize_path,
|
|
35
|
+
)
|
|
36
|
+
from .annotations import (
|
|
37
|
+
PathLike,
|
|
38
|
+
PathPredicate,
|
|
39
|
+
RecursionDepth,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
# transformers — path in, path out
|
|
44
|
+
"normalize_entry_path",
|
|
45
|
+
"normalize_path",
|
|
46
|
+
"rebase_path",
|
|
47
|
+
"relativize_path",
|
|
48
|
+
"change_suffix",
|
|
49
|
+
"make_unique_path",
|
|
50
|
+
# checks — a bool, never an exception for disk problems
|
|
51
|
+
"is_empty_dir",
|
|
52
|
+
"is_hidden_path",
|
|
53
|
+
"is_path_readable",
|
|
54
|
+
"is_path_writable",
|
|
55
|
+
"is_valid_filename",
|
|
56
|
+
"is_within",
|
|
57
|
+
# ensure — make a filesystem state true
|
|
58
|
+
"ensure_dir",
|
|
59
|
+
"ensure_file",
|
|
60
|
+
"ensure_parent_dir",
|
|
61
|
+
# scanners — look at a directory tree
|
|
62
|
+
"collect_paths",
|
|
63
|
+
"collect_files",
|
|
64
|
+
"collect_dirs",
|
|
65
|
+
"iter_paths",
|
|
66
|
+
"filter_paths",
|
|
67
|
+
"DEFAULT_IGNORE_NAMES",
|
|
68
|
+
"IGNORE_BUILD",
|
|
69
|
+
"IGNORE_ENV",
|
|
70
|
+
"IGNORE_GENERIC",
|
|
71
|
+
"IGNORE_IDE",
|
|
72
|
+
"IGNORE_SYSTEM",
|
|
73
|
+
"IGNORE_VCS",
|
|
74
|
+
# annotations — types for your own signatures
|
|
75
|
+
"PathLike",
|
|
76
|
+
"PathPredicate",
|
|
77
|
+
"RecursionDepth",
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
_DESIGN_NOTES = """
|
|
82
|
+
# simplibs-path — Package Root
|
|
83
|
+
|
|
84
|
+
## Purpose
|
|
85
|
+
Safe, composable work with paths and directory trees: normalize, check,
|
|
86
|
+
ensure and scan — as plain functions, as `Rule`s, and as pipeline `Action`s.
|
|
87
|
+
|
|
88
|
+
## What the Root Exports
|
|
89
|
+
Everything from the four logic sub-packages of `logic/`
|
|
90
|
+
(`transformers`, `checks`, `ensure`, `scanners`) and the three public
|
|
91
|
+
types of `annotations/`:
|
|
92
|
+
|
|
93
|
+
from simplibs.path import collect_files, normalize_path, PathLike
|
|
94
|
+
|
|
95
|
+
## What the Root Deliberately Does NOT Export
|
|
96
|
+
* **`rules/` and `actions/`** — domain folders with their own `__all__`,
|
|
97
|
+
imported through their domain:
|
|
98
|
+
|
|
99
|
+
from simplibs.path.rules import HasSuffix, IsFile
|
|
100
|
+
from simplibs.path.actions import CollectFiles
|
|
101
|
+
|
|
102
|
+
Twenty-nine class names would drown the thirty-odd functions, and a
|
|
103
|
+
`Rule` and an `Action` are different kinds of thing that should stay
|
|
104
|
+
visibly different at the import line.
|
|
105
|
+
* **`ScanEntry`** — the walker's internal result type; it never appears in
|
|
106
|
+
a public signature.
|
|
107
|
+
|
|
108
|
+
## Why the Three Types ARE Exported
|
|
109
|
+
`PathLike`, `PathPredicate` and `RecursionDepth` are annotations meant for
|
|
110
|
+
the caller's own signatures (`def load(path: PathLike) -> ...`), always used
|
|
111
|
+
together with the functions. They are few, tiny and unambiguous — unlike
|
|
112
|
+
the rules and actions — so the root is the natural place for them. They stay
|
|
113
|
+
importable from `simplibs.path.types` as well.
|
|
114
|
+
|
|
115
|
+
## `__all__`
|
|
116
|
+
Defined here and in the domain folders (`rules`, `actions`) only; the
|
|
117
|
+
sub-packages below export by plain imports.
|
|
118
|
+
"""
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from simplibs.actions import create_action
|
|
2
|
+
# Outers
|
|
3
|
+
from ..logic.transformers import change_suffix
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
ChangeSuffix = create_action(
|
|
7
|
+
change_suffix,
|
|
8
|
+
main_param="path",
|
|
9
|
+
class_name="ChangeSuffix",
|
|
10
|
+
)
|
|
11
|
+
"""Action wrapping `change_suffix` — replaces, adds or removes the last suffix of a path."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_DESIGN_NOTES = """
|
|
15
|
+
# ChangeSuffix — Action Over `change_suffix`
|
|
16
|
+
|
|
17
|
+
## Purpose
|
|
18
|
+
`ChangeSuffix(new_suffix=".md")` as one named step of a pipeline.
|
|
19
|
+
|
|
20
|
+
## Call-Time Shape Check
|
|
21
|
+
`new_suffix` is a plain `str` at construction; its shape (leading dot, no separators) is checked by the
|
|
22
|
+
function when the action runs.
|
|
23
|
+
"""
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from simplibs.actions import create_action
|
|
2
|
+
# Outers
|
|
3
|
+
from ..logic.scanners import collect_dirs
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
CollectDirs = create_action(
|
|
7
|
+
collect_dirs,
|
|
8
|
+
main_param="root_dir",
|
|
9
|
+
class_name="CollectDirs",
|
|
10
|
+
use_logging=False,
|
|
11
|
+
)
|
|
12
|
+
"""Action wrapping `collect_dirs` — collects only the directories of a tree into a sorted list."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_DESIGN_NOTES = """
|
|
16
|
+
# CollectDirs — Action Over `collect_dirs`
|
|
17
|
+
|
|
18
|
+
## Purpose
|
|
19
|
+
"Which folders exist under here" as a pipeline step. The root itself is not included.
|
|
20
|
+
|
|
21
|
+
## Logging Is Off
|
|
22
|
+
The result is a list that can hold tens of thousands of paths, and the `log_this` layer would record all of it.
|
|
23
|
+
Remove `use_logging=False` once `log_this` is known to truncate large results.
|
|
24
|
+
"""
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from simplibs.actions import create_action
|
|
2
|
+
# Outers
|
|
3
|
+
from ..logic.scanners import collect_files
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
CollectFiles = create_action(
|
|
7
|
+
collect_files,
|
|
8
|
+
main_param="root_dir",
|
|
9
|
+
class_name="CollectFiles",
|
|
10
|
+
use_logging=False,
|
|
11
|
+
)
|
|
12
|
+
"""Action wrapping `collect_files` — collects only the files of a tree into a sorted list."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_DESIGN_NOTES = """
|
|
16
|
+
# CollectFiles — Action Over `collect_files`
|
|
17
|
+
|
|
18
|
+
## Purpose
|
|
19
|
+
Real locking: the type flags are not part of `collect_files`' signature, so `CollectFiles` cannot be made to
|
|
20
|
+
collect directories. (The former `functools.partial` preset only changed defaults.)
|
|
21
|
+
|
|
22
|
+
## Logging Is Off
|
|
23
|
+
The result is a list that can hold tens of thousands of paths, and the `log_this` layer would record all of it.
|
|
24
|
+
Remove `use_logging=False` once `log_this` is known to truncate large results.
|
|
25
|
+
"""
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from simplibs.actions import create_action
|
|
2
|
+
# Outers
|
|
3
|
+
from ..logic.scanners import collect_paths
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
CollectPaths = create_action(
|
|
7
|
+
collect_paths,
|
|
8
|
+
main_param="root_dir",
|
|
9
|
+
class_name="CollectPaths",
|
|
10
|
+
use_logging=False,
|
|
11
|
+
)
|
|
12
|
+
"""Action wrapping `collect_paths` — collects files and/or directories of a tree into a sorted list."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_DESIGN_NOTES = """
|
|
16
|
+
# CollectPaths — Action Over `collect_paths`
|
|
17
|
+
|
|
18
|
+
## Purpose
|
|
19
|
+
The head of an extraction pipeline: the root goes in, a sorted list of paths comes out.
|
|
20
|
+
|
|
21
|
+
## Logging Is Off
|
|
22
|
+
The result is a list that can hold tens of thousands of paths, and the `log_this` layer would record all of it.
|
|
23
|
+
Remove `use_logging=False` once `log_this` is known to truncate large results.
|
|
24
|
+
"""
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from simplibs.actions import create_action
|
|
2
|
+
# Outers
|
|
3
|
+
from ..logic.ensure import ensure_dir
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
EnsureDir = create_action(
|
|
7
|
+
ensure_dir,
|
|
8
|
+
main_param="path",
|
|
9
|
+
class_name="EnsureDir",
|
|
10
|
+
)
|
|
11
|
+
"""Action wrapping `ensure_dir` — guarantees a directory (and its parents) exists."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_DESIGN_NOTES = """
|
|
15
|
+
# EnsureDir — Action Over `ensure_dir`
|
|
16
|
+
|
|
17
|
+
## Purpose
|
|
18
|
+
Turns "I need this folder" into a pipeline step that passes the normalized directory on.
|
|
19
|
+
|
|
20
|
+
## Changes the Disk
|
|
21
|
+
Together with `EnsureFile` and `EnsureParentDir`, the only actions that do.
|
|
22
|
+
"""
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from simplibs.actions import create_action
|
|
2
|
+
# Outers
|
|
3
|
+
from ..logic.ensure import ensure_file
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
EnsureFile = create_action(
|
|
7
|
+
ensure_file,
|
|
8
|
+
main_param="path",
|
|
9
|
+
class_name="EnsureFile",
|
|
10
|
+
)
|
|
11
|
+
"""Action wrapping `ensure_file` — guarantees a file (and its parent directory) exists."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_DESIGN_NOTES = """
|
|
15
|
+
# EnsureFile — Action Over `ensure_file`
|
|
16
|
+
|
|
17
|
+
## Purpose
|
|
18
|
+
`EnsureFile(create=False)` is also a pure precondition: it passes an existing file on, or raises `NotFoundError`.
|
|
19
|
+
"""
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from simplibs.actions import create_action
|
|
2
|
+
# Outers
|
|
3
|
+
from ..logic.ensure import ensure_parent_dir
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
EnsureParentDir = create_action(
|
|
7
|
+
ensure_parent_dir,
|
|
8
|
+
main_param="path",
|
|
9
|
+
class_name="EnsureParentDir",
|
|
10
|
+
)
|
|
11
|
+
"""Action wrapping `ensure_parent_dir` — guarantees the directory a path lives in exists, and passes the path on."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_DESIGN_NOTES = """
|
|
15
|
+
# EnsureParentDir — Action Over `ensure_parent_dir`
|
|
16
|
+
|
|
17
|
+
## Purpose
|
|
18
|
+
The step right before a write: `RebasePath(...) >> EnsureParentDir() >> write_step`. The value flowing on is the
|
|
19
|
+
file path itself, not its folder.
|
|
20
|
+
"""
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from simplibs.actions import create_action
|
|
2
|
+
# Outers
|
|
3
|
+
from ..logic.scanners import filter_paths
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
FilterPaths = create_action(
|
|
7
|
+
filter_paths,
|
|
8
|
+
main_param="paths",
|
|
9
|
+
class_name="FilterPaths",
|
|
10
|
+
use_logging=False,
|
|
11
|
+
)
|
|
12
|
+
"""Action wrapping `filter_paths` — keeps the paths of a list that satisfy a Rule or callable."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_DESIGN_NOTES = """
|
|
16
|
+
# FilterPaths — Action Over `filter_paths`
|
|
17
|
+
|
|
18
|
+
## Purpose
|
|
19
|
+
`CollectFiles() >> FilterPaths(rule=HasSuffix(".py") & ~IsHidden())`.
|
|
20
|
+
|
|
21
|
+
## `rule` Is Required
|
|
22
|
+
A filter without a rule would only copy the list. The main input is a collection of paths (not a generator).
|
|
23
|
+
|
|
24
|
+
## Logging Is Off
|
|
25
|
+
The result is a list that can hold tens of thousands of paths, and the `log_this` layer would record all of it.
|
|
26
|
+
Remove `use_logging=False` once `log_this` is known to truncate large results.
|
|
27
|
+
"""
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from simplibs.actions import create_action
|
|
2
|
+
# Outers
|
|
3
|
+
from ..logic.transformers import make_unique_path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
MakeUniquePath = create_action(
|
|
7
|
+
make_unique_path,
|
|
8
|
+
main_param="path",
|
|
9
|
+
class_name="MakeUniquePath",
|
|
10
|
+
)
|
|
11
|
+
"""Action wrapping `make_unique_path` — returns the path, or a numbered variant of it that does not exist yet."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_DESIGN_NOTES = """
|
|
15
|
+
# MakeUniquePath — Action Over `make_unique_path`
|
|
16
|
+
|
|
17
|
+
## Purpose
|
|
18
|
+
Choosing an output name that overwrites nothing: `RebasePath(...) >> MakeUniquePath()`.
|
|
19
|
+
|
|
20
|
+
## Not a Reservation
|
|
21
|
+
The name is free at the moment of the call; claim it by opening the result with mode `"x"`.
|
|
22
|
+
"""
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from simplibs.actions import create_action
|
|
2
|
+
# Outers
|
|
3
|
+
from ..logic.transformers import normalize_entry_path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
NormalizeEntryPath = create_action(
|
|
7
|
+
normalize_entry_path,
|
|
8
|
+
main_param="path",
|
|
9
|
+
class_name="NormalizeEntryPath",
|
|
10
|
+
)
|
|
11
|
+
"""Action wrapping `normalize_entry_path` — resolves an entry's folder and keeps its own name as written."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_DESIGN_NOTES = """
|
|
15
|
+
# NormalizeEntryPath — Action Over `normalize_entry_path`
|
|
16
|
+
|
|
17
|
+
## Purpose
|
|
18
|
+
The first step of pipelines that act on the entry itself (delete, move, rename)
|
|
19
|
+
rather than on its content — `NormalizeEntryPath() >> DeleteFile()`.
|
|
20
|
+
"""
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from simplibs.actions import create_action
|
|
2
|
+
# Outers
|
|
3
|
+
from ..logic.transformers import normalize_path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
NormalizePath = create_action(
|
|
7
|
+
normalize_path,
|
|
8
|
+
main_param="path",
|
|
9
|
+
class_name="NormalizePath",
|
|
10
|
+
)
|
|
11
|
+
"""Action wrapping `normalize_path` — makes a path absolute, `~`-expanded and (by default) symlink-resolved."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_DESIGN_NOTES = """
|
|
15
|
+
# NormalizePath — Action Over `normalize_path`
|
|
16
|
+
|
|
17
|
+
## Purpose
|
|
18
|
+
The first step of most path pipelines (`NormalizePath() >> EnsureDir() >> CollectFiles()`), so every later
|
|
19
|
+
step sees one canonical form of the path.
|
|
20
|
+
|
|
21
|
+
## Configuration
|
|
22
|
+
`NormalizePath(resolve_symlinks=False)` keeps symlinks as written. The main input is the path; the result is a `Path`.
|
|
23
|
+
"""
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from simplibs.actions import create_action
|
|
2
|
+
# Outers
|
|
3
|
+
from ..logic.transformers import rebase_path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
RebasePath = create_action(
|
|
7
|
+
rebase_path,
|
|
8
|
+
main_param="path",
|
|
9
|
+
class_name="RebasePath",
|
|
10
|
+
)
|
|
11
|
+
"""Action wrapping `rebase_path` — maps a path from `source_root` to the same place under `target_root`."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_DESIGN_NOTES = """
|
|
15
|
+
# RebasePath — Action Over `rebase_path`
|
|
16
|
+
|
|
17
|
+
## Purpose
|
|
18
|
+
The "where does the output of this input go" step:
|
|
19
|
+
`RebasePath(source_root="src", target_root="out", new_suffix=".md")`.
|
|
20
|
+
|
|
21
|
+
## Configuration
|
|
22
|
+
`source_root` and `target_root` are required; `new_suffix` is optional (`None` keeps the name, `""` removes the
|
|
23
|
+
suffix). Pure path arithmetic — pair it with `EnsureParentDir` before writing.
|
|
24
|
+
"""
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from simplibs.actions import create_action
|
|
2
|
+
# Outers
|
|
3
|
+
from ..logic.transformers import relativize_path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
RelativizePath = create_action(
|
|
7
|
+
relativize_path,
|
|
8
|
+
main_param="path",
|
|
9
|
+
class_name="RelativizePath",
|
|
10
|
+
)
|
|
11
|
+
"""Action wrapping `relativize_path` — expresses a path relative to `root`."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_DESIGN_NOTES = """
|
|
15
|
+
# RelativizePath — Action Over `relativize_path`
|
|
16
|
+
|
|
17
|
+
## Purpose
|
|
18
|
+
Shows a collected path as it appears inside the scanned tree (`src/a/b.py` instead of the absolute path).
|
|
19
|
+
|
|
20
|
+
## Configuration
|
|
21
|
+
`RelativizePath(root=...)` — `root` is required. A path outside it raises a `ParamError` when the action runs.
|
|
22
|
+
"""
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from .ChangeSuffix import ChangeSuffix
|
|
2
|
+
from .CollectDirs import CollectDirs
|
|
3
|
+
from .CollectFiles import CollectFiles
|
|
4
|
+
from .CollectPaths import CollectPaths
|
|
5
|
+
from .EnsureDir import EnsureDir
|
|
6
|
+
from .EnsureFile import EnsureFile
|
|
7
|
+
from .EnsureParentDir import EnsureParentDir
|
|
8
|
+
from .FilterPaths import FilterPaths
|
|
9
|
+
from .MakeUniquePath import MakeUniquePath
|
|
10
|
+
from .NormalizeEntryPath import NormalizeEntryPath
|
|
11
|
+
from .NormalizePath import NormalizePath
|
|
12
|
+
from .RebasePath import RebasePath
|
|
13
|
+
from .RelativizePath import RelativizePath
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
# transformers — path in, path out
|
|
18
|
+
"NormalizeEntryPath",
|
|
19
|
+
"NormalizePath",
|
|
20
|
+
"RebasePath",
|
|
21
|
+
"RelativizePath",
|
|
22
|
+
"ChangeSuffix",
|
|
23
|
+
"MakeUniquePath",
|
|
24
|
+
# ensure — make a filesystem state true
|
|
25
|
+
"EnsureDir",
|
|
26
|
+
"EnsureFile",
|
|
27
|
+
"EnsureParentDir",
|
|
28
|
+
# scanners — look at a tree
|
|
29
|
+
"CollectPaths",
|
|
30
|
+
"CollectFiles",
|
|
31
|
+
"CollectDirs",
|
|
32
|
+
"FilterPaths",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
_DESIGN_NOTES = """
|
|
37
|
+
# Path Actions Sub-Package
|
|
38
|
+
|
|
39
|
+
## Purpose
|
|
40
|
+
Every function of the library that takes one main value and returns one
|
|
41
|
+
result, as an `Action`: constructed once with its options, then called with
|
|
42
|
+
the main input and chainable with `>>`, `&` and `|`.
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
pipeline = (
|
|
46
|
+
IsDir() # a Rule — becomes a guard step
|
|
47
|
+
>> CollectFiles(include_filter=HasSuffix(".py"))
|
|
48
|
+
>> FilterPaths(rule=~IsHidden())
|
|
49
|
+
)
|
|
50
|
+
paths = pipeline("~/project")
|
|
51
|
+
````
|
|
52
|
+
|
|
53
|
+
## What Is Not Here
|
|
54
|
+
|
|
55
|
+
- **Predicates (`checks/`, rules):** a bool is not a pipeline value. A Rule\
|
|
56
|
+
already works as a step — `Action` turns it into a pass-through `guard`.
|
|
57
|
+
- **`iter_paths`:** a generator is consumed once and cannot be logged or\
|
|
58
|
+
re-run by the action machinery; pipelines use the list-returning collectors.
|
|
59
|
+
|
|
60
|
+
## Async
|
|
61
|
+
|
|
62
|
+
All thirteen are synchronous actions, so `await action.acall(data)` runs them\
|
|
63
|
+
in a worker thread — the right model for blocking file-system calls.
|
|
64
|
+
|
|
65
|
+
## Domain Folder: `__all__`
|
|
66
|
+
|
|
67
|
+
Like `rules/`, this package defines `__all__` (the public actions).\
|
|
68
|
+
"""
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from simplibs.types import str_not_blank
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
PathLike = str_not_blank | Path
|
|
6
|
+
"""A filesystem path given as a non-blank string or as a `Path` instance.
|
|
7
|
+
|
|
8
|
+
The annotation of every path parameter in this library. A blank string is
|
|
9
|
+
rejected on purpose: `Path("")` silently means "the current directory",
|
|
10
|
+
which is almost never what the caller intended.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_DESIGN_NOTES = """
|
|
15
|
+
# PathLike — the accepted shape of a path argument
|
|
16
|
+
|
|
17
|
+
## Purpose
|
|
18
|
+
One shared annotation for every path parameter in `simplibs-path`, so the
|
|
19
|
+
accepted input is defined (and described in error cards) in exactly one place.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 1. Why a Blank String Is Rejected
|
|
24
|
+
|
|
25
|
+
`Path("")` and `Path(" ")` do not fail — they quietly become `.` (the
|
|
26
|
+
current working directory). `ensure_dir("")` would "succeed" and
|
|
27
|
+
`collect_paths("")` would scan whatever directory the process happens to run
|
|
28
|
+
in. Rejecting blank strings at the boundary turns that silent surprise into
|
|
29
|
+
an immediate, readable error.
|
|
30
|
+
|
|
31
|
+
## 2. Why `str | Path`, Not `os.PathLike`
|
|
32
|
+
|
|
33
|
+
Every function normalizes its input through `Path` first. Keeping the
|
|
34
|
+
accepted input to the two types people actually pass keeps validation
|
|
35
|
+
messages short ("non-blank string | Path") and avoids bytes-based
|
|
36
|
+
`os.PathLike` objects, which `Path` cannot represent.
|
|
37
|
+
|
|
38
|
+
## 3. Not a Rule Input Type
|
|
39
|
+
|
|
40
|
+
Rules never raise on a wrong input type — they use `to_path` (see
|
|
41
|
+
`_helpers/to_path.py`) and answer `False`. This annotation is for the
|
|
42
|
+
`@validate_call` boundary of functions only.
|
|
43
|
+
"""
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Callable
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
PathPredicate = Callable[[Path], bool]
|
|
6
|
+
"""A `(Path) -> bool` predicate: a plain function, a lambda, or any `Rule`.
|
|
7
|
+
|
|
8
|
+
A `Rule` instance is callable (`Rule.__call__` delegates to `is_valid`), so
|
|
9
|
+
rules such as `HasSuffix(".py") & ~IsHidden()` fit this type directly —
|
|
10
|
+
no special protocol is needed.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_DESIGN_NOTES = """
|
|
15
|
+
# PathPredicate — what a path filter may be
|
|
16
|
+
|
|
17
|
+
## Purpose
|
|
18
|
+
The annotation of `include_filter`, `exclude_filter` and `filter_paths`'s
|
|
19
|
+
`rule`: anything callable with one `Path` that returns a truthy/falsy answer.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 1. Why the Former `HasIsValid` Protocol Is Gone
|
|
24
|
+
|
|
25
|
+
An earlier version also accepted any object with an `is_valid` method via a
|
|
26
|
+
structural `Protocol`. But every `Rule` is already callable, and
|
|
27
|
+
`build_typing_rule` turns `Callable[[...], R]` into a plain `IsCallable()`
|
|
28
|
+
check (argument and return types are never inspected). The protocol added a
|
|
29
|
+
second code path (`getattr(rule, "is_valid", rule)`) for objects that are
|
|
30
|
+
not callable but carry `is_valid` — a shape nobody produces. Removing it
|
|
31
|
+
leaves one rule: "it is callable".
|
|
32
|
+
|
|
33
|
+
## 2. Resolved Once, Outside the Loop
|
|
34
|
+
|
|
35
|
+
Consumers (`collect_paths`, `filter_paths`) resolve the predicate a single
|
|
36
|
+
time via `as_path_predicate` (see `scanners/_helpers/`), which unwraps a
|
|
37
|
+
`Rule` to its bound `is_valid` — skipping the one extra Python call of
|
|
38
|
+
`Rule.__call__` per entry.
|
|
39
|
+
"""
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
from simplibs.rules import is_bool, is_int, ge
|
|
3
|
+
|
|
4
|
+
RecursionDepth = Annotated[bool | int, is_bool | (is_int & ge(0))]
|
|
5
|
+
"""How deep a recursive scan may go.
|
|
6
|
+
|
|
7
|
+
- `True`: infinite recursion.
|
|
8
|
+
- `False`: no recursion (only the starting entries).
|
|
9
|
+
- A non-negative `int` (`0`, `1`, `2`, ...): maximum depth of traversal.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_DESIGN_NOTES = """
|
|
14
|
+
# RecursionDepth — controlling traversal depth
|
|
15
|
+
|
|
16
|
+
## Purpose
|
|
17
|
+
A single shared annotation for recursive functions (`collect_paths`, `walk_tree`)
|
|
18
|
+
to validate depth constraints uniformly across the library.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1. Why `bool` Combined With `int`
|
|
23
|
+
|
|
24
|
+
In Python APIs, passing `True` or `False` is a clean and readable way to enable
|
|
25
|
+
or disable recursion entirely without remembering numeric constants like `-1`
|
|
26
|
+
or `sys.maxsize`. Combining `bool` with non-negative integers (`ge(0)`) gives
|
|
27
|
+
the exact semantics needed: infinite, disabled, or an exact maximum depth limit.
|
|
28
|
+
|
|
29
|
+
## 2. Boundary Enforcement
|
|
30
|
+
|
|
31
|
+
Negative integers (e.g., `-1`) are strictly rejected by `ge(0)`, preventing invalid
|
|
32
|
+
configurations at the `@validate_call` boundary.
|
|
33
|
+
"""
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .PathLike import PathLike
|
|
2
|
+
from .PathPredicate import PathPredicate
|
|
3
|
+
from .RecursionDepth import RecursionDepth
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
_DESIGN_NOTES = """
|
|
7
|
+
# Path Shared Types Sub-Package
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
Annotations shared by several sub-packages (`transformers`, `ensure`,
|
|
11
|
+
`scanners`, `rules`, `actions`). Kept at package level instead of inside
|
|
12
|
+
`scanners/` because rules and functions outside the scanners need them too.
|
|
13
|
+
|
|
14
|
+
## Internal Components Registry
|
|
15
|
+
|
|
16
|
+
| Component | Type | Description |
|
|
17
|
+
| :--------------- | :--------- | :------------------------------------------------------------------- |
|
|
18
|
+
| `PathLike` | Annotation | A non-blank string or a `Path` — the accepted shape of a path input. |
|
|
19
|
+
| `PathPredicate` | Annotation | A `(Path) -> bool` callable; every `Rule` fits. |
|
|
20
|
+
| `RecursionDepth` | Annotation | `True`/`False` or a non-negative `int` — how deep a scan may go. |
|
|
21
|
+
"""
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
_DESIGN_NOTES = """
|
|
2
|
+
# Path Core Logic Package
|
|
3
|
+
|
|
4
|
+
## Purpose
|
|
5
|
+
The functional core of `simplibs-path`: plain, reusable functions that work on
|
|
6
|
+
their own, with no knowledge of the `Rule` or `Action` layers. `rules/` and
|
|
7
|
+
`actions/` are thin layers built *on top of* these functions — a rule wraps a
|
|
8
|
+
check, an action wraps a function — never the other way round.
|
|
9
|
+
|
|
10
|
+
This package groups; it does not export. It has no imports of its own, so
|
|
11
|
+
importing it costs nothing and creates no import cycles. The main package
|
|
12
|
+
`__init__` (`simplibs.path`) reaches into the four sub-packages below and
|
|
13
|
+
propagates their public names.
|
|
14
|
+
|
|
15
|
+
## Internal Components Registry
|
|
16
|
+
|
|
17
|
+
| Sub-package | Kind | What it holds |
|
|
18
|
+
| :------------- | :----------------------- | :---------------------------------------------------------------------------------------------- |
|
|
19
|
+
| `transformers` | Pure path arithmetic | `normalize_path`, `rebase_path`, `relativize_path`, `change_suffix`, `make_unique_path` — path in, path out. |
|
|
20
|
+
| `checks` | Predicates with real logic | `is_empty_dir`, `is_hidden_path`, `is_path_readable`, `is_path_writable`, `is_valid_filename`, `is_within` — a `bool`, never an exception for disk problems. |
|
|
21
|
+
| `ensure` | Changes the disk | `ensure_dir`, `ensure_file`, `ensure_parent_dir` — make a filesystem state true, return the normalized path. |
|
|
22
|
+
| `scanners` | Looks at a directory tree | `collect_paths`, `collect_files`, `collect_dirs`, `iter_paths`, `filter_paths`, plus the `ignore_names` constants. |
|
|
23
|
+
|
|
24
|
+
## Layering
|
|
25
|
+
The packages depend on each other in one direction only:
|
|
26
|
+
|
|
27
|
+
* `transformers` and `checks` are leaves — neither imports the other.
|
|
28
|
+
* `ensure` and `scanners` build on `transformers` (every path is normalized first).
|
|
29
|
+
|
|
30
|
+
Two small helpers are deliberately shared with the `rules/` package, which
|
|
31
|
+
imports them directly: `to_path` (in `checks/_helpers`) and `is_valid_suffix`
|
|
32
|
+
(in `transformers/_helpers`). That keeps one definition of "a usable path"
|
|
33
|
+
and "a suffix shape" for functions and rules alike.
|
|
34
|
+
|
|
35
|
+
## Why `logic` Is Its Own Folder
|
|
36
|
+
Without it, the package root would mix four logic packages with `rules/`,
|
|
37
|
+
`actions/` and `annotations/` — seven siblings of very different kinds. Grouping
|
|
38
|
+
the logic leaves the root readable as a map: *logic* (`logic`),
|
|
39
|
+
*vocabulary of predicates* (`rules`), *vocabulary of pipeline steps*
|
|
40
|
+
(`actions`) and *shared types* (`annotations`).
|
|
41
|
+
"""
|