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.
Files changed (128) hide show
  1. simplibs/path/__init__.py +118 -0
  2. simplibs/path/actions/ChangeSuffix.py +23 -0
  3. simplibs/path/actions/CollectDirs.py +24 -0
  4. simplibs/path/actions/CollectFiles.py +25 -0
  5. simplibs/path/actions/CollectPaths.py +24 -0
  6. simplibs/path/actions/EnsureDir.py +22 -0
  7. simplibs/path/actions/EnsureFile.py +19 -0
  8. simplibs/path/actions/EnsureParentDir.py +20 -0
  9. simplibs/path/actions/FilterPaths.py +27 -0
  10. simplibs/path/actions/MakeUniquePath.py +22 -0
  11. simplibs/path/actions/NormalizeEntryPath.py +20 -0
  12. simplibs/path/actions/NormalizePath.py +23 -0
  13. simplibs/path/actions/RebasePath.py +24 -0
  14. simplibs/path/actions/RelativizePath.py +22 -0
  15. simplibs/path/actions/__init__.py +68 -0
  16. simplibs/path/annotations/PathLike.py +43 -0
  17. simplibs/path/annotations/PathPredicate.py +39 -0
  18. simplibs/path/annotations/RecursionDepth.py +33 -0
  19. simplibs/path/annotations/__init__.py +21 -0
  20. simplibs/path/logic/__init__.py +41 -0
  21. simplibs/path/logic/checks/__init__.py +29 -0
  22. simplibs/path/logic/checks/_helpers/__init__.py +21 -0
  23. simplibs/path/logic/checks/_helpers/_find_length_problem.py +48 -0
  24. simplibs/path/logic/checks/_helpers/_find_windows_name_problem.py +55 -0
  25. simplibs/path/logic/checks/_helpers/can_create_inside.py +39 -0
  26. simplibs/path/logic/checks/_helpers/constants/MAX_NAME_LENGTH.py +18 -0
  27. simplibs/path/logic/checks/_helpers/constants/WINDOWS_FORBIDDEN_CHARS.py +25 -0
  28. simplibs/path/logic/checks/_helpers/constants/WINDOWS_RESERVED_NAMES.py +27 -0
  29. simplibs/path/logic/checks/_helpers/constants/__init__.py +20 -0
  30. simplibs/path/logic/checks/_helpers/find_filename_problem.py +75 -0
  31. simplibs/path/logic/checks/_helpers/to_path.py +77 -0
  32. simplibs/path/logic/checks/is_empty_dir.py +59 -0
  33. simplibs/path/logic/checks/is_hidden_path.py +78 -0
  34. simplibs/path/logic/checks/is_path_readable.py +73 -0
  35. simplibs/path/logic/checks/is_path_writable.py +90 -0
  36. simplibs/path/logic/checks/is_valid_filename.py +47 -0
  37. simplibs/path/logic/checks/is_within.py +88 -0
  38. simplibs/path/logic/ensure/__init__.py +22 -0
  39. simplibs/path/logic/ensure/_validations/__init__.py +25 -0
  40. simplibs/path/logic/ensure/_validations/raise_ensure_dir_error.py +40 -0
  41. simplibs/path/logic/ensure/_validations/raise_ensure_dir_not_a_dir_error.py +36 -0
  42. simplibs/path/logic/ensure/_validations/raise_ensure_file_create_error.py +39 -0
  43. simplibs/path/logic/ensure/_validations/raise_ensure_file_not_a_file_error.py +36 -0
  44. simplibs/path/logic/ensure/_validations/raise_ensure_file_not_found_error.py +36 -0
  45. simplibs/path/logic/ensure/ensure_dir.py +83 -0
  46. simplibs/path/logic/ensure/ensure_file.py +103 -0
  47. simplibs/path/logic/ensure/ensure_parent_dir.py +58 -0
  48. simplibs/path/logic/scanners/__init__.py +35 -0
  49. simplibs/path/logic/scanners/_helpers/ScanEntry.py +36 -0
  50. simplibs/path/logic/scanners/_helpers/__init__.py +30 -0
  51. simplibs/path/logic/scanners/_helpers/as_path_predicate.py +38 -0
  52. simplibs/path/logic/scanners/_helpers/build_ignore_matcher.py +88 -0
  53. simplibs/path/logic/scanners/_helpers/generate_paths.py +74 -0
  54. simplibs/path/logic/scanners/_helpers/is_link_like.py +45 -0
  55. simplibs/path/logic/scanners/_helpers/resolve_scan_root.py +66 -0
  56. simplibs/path/logic/scanners/_helpers/scan_paths.py +92 -0
  57. simplibs/path/logic/scanners/_helpers/walk_tree.py +118 -0
  58. simplibs/path/logic/scanners/_validations/__init__.py +24 -0
  59. simplibs/path/logic/scanners/_validations/raise_collect_paths_not_a_dir_error.py +34 -0
  60. simplibs/path/logic/scanners/_validations/raise_collect_paths_not_found_error.py +38 -0
  61. simplibs/path/logic/scanners/_validations/raise_collect_paths_root_unreadable_error.py +38 -0
  62. simplibs/path/logic/scanners/_validations/raise_ignore_names_not_names_error.py +36 -0
  63. simplibs/path/logic/scanners/_validations/raise_nothing_to_collect_error.py +34 -0
  64. simplibs/path/logic/scanners/collect_dirs.py +62 -0
  65. simplibs/path/logic/scanners/collect_files.py +64 -0
  66. simplibs/path/logic/scanners/collect_paths.py +102 -0
  67. simplibs/path/logic/scanners/filter_paths.py +51 -0
  68. simplibs/path/logic/scanners/ignore_names/DEFAULT_IGNORE_NAMES.py +36 -0
  69. simplibs/path/logic/scanners/ignore_names/IGNORE_BUILD.py +48 -0
  70. simplibs/path/logic/scanners/ignore_names/IGNORE_ENV.py +31 -0
  71. simplibs/path/logic/scanners/ignore_names/IGNORE_GENERIC.py +30 -0
  72. simplibs/path/logic/scanners/ignore_names/IGNORE_IDE.py +20 -0
  73. simplibs/path/logic/scanners/ignore_names/IGNORE_SYSTEM.py +30 -0
  74. simplibs/path/logic/scanners/ignore_names/IGNORE_VCS.py +24 -0
  75. simplibs/path/logic/scanners/ignore_names/__init__.py +30 -0
  76. simplibs/path/logic/scanners/iter_paths.py +91 -0
  77. simplibs/path/logic/transformers/__init__.py +27 -0
  78. simplibs/path/logic/transformers/_helpers/__init__.py +17 -0
  79. simplibs/path/logic/transformers/_helpers/is_valid_suffix.py +37 -0
  80. simplibs/path/logic/transformers/_helpers/split_name.py +46 -0
  81. simplibs/path/logic/transformers/_validations/__init__.py +24 -0
  82. simplibs/path/logic/transformers/_validations/raise_invalid_suffix_error.py +36 -0
  83. simplibs/path/logic/transformers/_validations/raise_normalize_path_error.py +44 -0
  84. simplibs/path/logic/transformers/_validations/raise_path_has_no_name_error.py +36 -0
  85. simplibs/path/logic/transformers/_validations/raise_path_not_within_root_error.py +43 -0
  86. simplibs/path/logic/transformers/_validations/raise_unique_path_exhausted_error.py +39 -0
  87. simplibs/path/logic/transformers/change_suffix.py +77 -0
  88. simplibs/path/logic/transformers/make_unique_path.py +88 -0
  89. simplibs/path/logic/transformers/normalize_entry_path.py +67 -0
  90. simplibs/path/logic/transformers/normalize_path.py +85 -0
  91. simplibs/path/logic/transformers/rebase_path.py +78 -0
  92. simplibs/path/logic/transformers/relativize_path.py +56 -0
  93. simplibs/path/rules/__init__.py +66 -0
  94. simplibs/path/rules/_helpers/PathPartIs.py +152 -0
  95. simplibs/path/rules/_helpers/__init__.py +21 -0
  96. simplibs/path/rules/_helpers/build_not_a_path_exception.py +66 -0
  97. simplibs/path/rules/_helpers/build_path_rule_exception.py +71 -0
  98. simplibs/path/rules/_helpers/check_path.py +48 -0
  99. simplibs/path/rules/_validations/__init__.py +24 -0
  100. simplibs/path/rules/_validations/raise_has_suffix_invalid_suffix_error.py +35 -0
  101. simplibs/path/rules/_validations/raise_has_suffix_no_suffixes_error.py +31 -0
  102. simplibs/path/rules/_validations/raise_rule_param_not_bool_error.py +37 -0
  103. simplibs/path/rules/_validations/raise_rule_param_not_callable_error.py +36 -0
  104. simplibs/path/rules/_validations/raise_rule_param_not_path_error.py +37 -0
  105. simplibs/path/rules/filesystem/IsDir.py +70 -0
  106. simplibs/path/rules/filesystem/IsEmptyDir.py +76 -0
  107. simplibs/path/rules/filesystem/IsEmptyFile.py +76 -0
  108. simplibs/path/rules/filesystem/IsFile.py +76 -0
  109. simplibs/path/rules/filesystem/IsPathReadable.py +77 -0
  110. simplibs/path/rules/filesystem/IsPathWritable.py +82 -0
  111. simplibs/path/rules/filesystem/IsSymlink.py +71 -0
  112. simplibs/path/rules/filesystem/PathExists.py +75 -0
  113. simplibs/path/rules/filesystem/__init__.py +31 -0
  114. simplibs/path/rules/lexical/HasSuffix.py +146 -0
  115. simplibs/path/rules/lexical/IsAbsolutePath.py +74 -0
  116. simplibs/path/rules/lexical/IsHidden.py +92 -0
  117. simplibs/path/rules/lexical/IsRelativePath.py +74 -0
  118. simplibs/path/rules/lexical/IsValidFilename.py +136 -0
  119. simplibs/path/rules/lexical/IsWithin.py +116 -0
  120. simplibs/path/rules/lexical/NameIs.py +37 -0
  121. simplibs/path/rules/lexical/ParentNameIs.py +37 -0
  122. simplibs/path/rules/lexical/StemIs.py +36 -0
  123. simplibs/path/rules/lexical/__init__.py +33 -0
  124. simplibs_path-0.1.0.dist-info/METADATA +420 -0
  125. simplibs_path-0.1.0.dist-info/RECORD +128 -0
  126. simplibs_path-0.1.0.dist-info/WHEEL +5 -0
  127. simplibs_path-0.1.0.dist-info/licenses/LICENSE +21 -0
  128. 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
+ """