FileHiker 0.3.9.2__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.
- filehiker/__init__.py +4 -0
- filehiker/__main__.py +249 -0
- filehiker/cases/20260806o1211.sorted.txt +49 -0
- filehiker/cases/20260806o1213.invert.py +30 -0
- filehiker/cases/20260806o1215.inverted.txt +49 -0
- filehiker/cases/README.md +24 -0
- filehiker/cases/^^file-to-ignore^^.txt +3 -0
- filehiker/cases/^^folder-to-ignore^^/more123.txt +3 -0
- filehiker/cases/dir_2/file_01_abc.txt +1 -0
- filehiker/cases/dir_2b.txt +1 -0
- filehiker/cases/dir_3/dir_31/dir_311/^^dir_3111^^/file_03_more.txt +3 -0
- filehiker/cases/dir_3/dir_31/dir_311/dir_3113/file_04_bcd.txt +1 -0
- filehiker/cases/dir_3/dir_31/file_02_abc.txt +1 -0
- filehiker/cases/dir_4.txt +1 -0
- filehiker/cases/dir_5/dir_51/dir_511/dir_5111/file_08_k.txt +1 -0
- filehiker/cases/dir_5/dir_51/dir_511/dir_5111/file_10_m.txt +1 -0
- filehiker/cases/dir_5/dir_51/dir_511/dir_5111/file_11_n.txt +1 -0
- filehiker/cases/dir_5/dir_52/dir_521/dir_5212/file_08_g.txt +1 -0
- filehiker/cases/dir_5/dir_52/file_07_c.txt +1 -0
- filehiker/cases/dir_5/dir_53/dir_531/dir_5311/dir_53111/dir_531111/file_12_x.txt +1 -0
- filehiker/cases/dir_5/dir_53/dir_531/dir_5311/dir_53111/dir_531111/file_13_y.txt +1 -0
- filehiker/cases/dir_5/dir_53/dir_531/dir_5311/dir_53111/dir_531111/file_14_z.txt +1 -0
- filehiker/cases/dir_5/file_05_a.txt +1 -0
- filehiker/cases/dir_5/file_06_h.txt +1 -0
- filehiker/cases/dir_5/file_07_p.txt +1 -0
- filehiker/filehiker.py +833 -0
- filehiker-0.3.9.2.dist-info/METADATA +226 -0
- filehiker-0.3.9.2.dist-info/RECORD +30 -0
- filehiker-0.3.9.2.dist-info/WHEEL +5 -0
- filehiker-0.3.9.2.dist-info/top_level.txt +1 -0
filehiker/filehiker.py
ADDED
|
@@ -0,0 +1,833 @@
|
|
|
1
|
+
#! /usr/bin/env python
|
|
2
|
+
# file : 20250406°0911 filehiker.py
|
|
3
|
+
# summary : This finds a filesystem item's neighbour relative to a given path. Find
|
|
4
|
+
# either the next or the previous neighbour, depending on the direction flag.
|
|
5
|
+
# license : BSD 3-Clause | © 2025 – 2026 Norbert C. Maier
|
|
6
|
+
# status : Applicable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import bisect
|
|
10
|
+
import fnmatch
|
|
11
|
+
import itertools
|
|
12
|
+
import os, sys
|
|
13
|
+
import pathlib # Path
|
|
14
|
+
import typing # Optional, Union
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## \register class 20260810°0911 FileHiker <del>20090725°0144 IrunFind</del>
|
|
18
|
+
# @brief Class to find the neighbour of a given target inside a given base path
|
|
19
|
+
# @details If the given path does not exist, then find the nearest existing target according the sort order
|
|
20
|
+
# \remark Code implented after file 20260808°0811 probe_327_walk.py
|
|
21
|
+
# \remark Delete <del>Rename</del> class IrunFind to FileHiker [chg 20260810°0811]
|
|
22
|
+
class FileHiker :
|
|
23
|
+
|
|
24
|
+
## \register Method 20260810°0915 <del>20090725°0147 constructor</del>
|
|
25
|
+
# \brief …
|
|
26
|
+
# \param verbose — Flag telling verbose mode on or off
|
|
27
|
+
def __init__ ( self
|
|
28
|
+
, basepath = ''
|
|
29
|
+
, direction = True
|
|
30
|
+
, ignorewcs = None
|
|
31
|
+
, verbose = False
|
|
32
|
+
, selftest = False # Only use from __main__.py! Ugly flag, but easiest solution, see issue 20260811°1021 'Command `python.exe -m filehiker`'
|
|
33
|
+
) :
|
|
34
|
+
|
|
35
|
+
# (1) Complete properties [seq 20260811°0911]
|
|
36
|
+
# This are instance level properties, they will override the class level attributed possibly defined on top of the class
|
|
37
|
+
|
|
38
|
+
## \register prop 20260811°0913
|
|
39
|
+
## \brief Direction flag
|
|
40
|
+
self.__bDirection = direction
|
|
41
|
+
|
|
42
|
+
## \register prop 20260811°0915
|
|
43
|
+
## \brief Direction flag
|
|
44
|
+
self.__bVerbose = verbose
|
|
45
|
+
|
|
46
|
+
## \register prop 20250729°0911
|
|
47
|
+
# \brief The default ignore list
|
|
48
|
+
self.__lstIgnorePats = ignorewcs \
|
|
49
|
+
if ignorewcs != None \
|
|
50
|
+
else [ '.git'
|
|
51
|
+
, '.hg'
|
|
52
|
+
, '.svn'
|
|
53
|
+
, '.vs' # Visual Studio
|
|
54
|
+
, '.vscode'
|
|
55
|
+
, '__*__' # E.g. '__pycache__'
|
|
56
|
+
, '__*__.*' # This will unfortunately also hit '__init__.py' and '__main__.py' (see todo 20260811°0831 'Finetune ignore feature')
|
|
57
|
+
, '^^*^^' # Specific for self test
|
|
58
|
+
, '^^*^^.*' # Specific for self test
|
|
59
|
+
, 'Debug*' # Formerly 'Debug' plus 'Debug_TMP' and possibly 'ifonor'
|
|
60
|
+
, 'obj'
|
|
61
|
+
, 'tags'
|
|
62
|
+
, 'temp'
|
|
63
|
+
, 'tmp'
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
## \register prop 20260811°0917
|
|
67
|
+
# \brief The base path
|
|
68
|
+
self.__sBasePath = basepath
|
|
69
|
+
|
|
70
|
+
# (2) Notification [seq 20260811°0919]
|
|
71
|
+
if self.__bVerbose:
|
|
72
|
+
sMsg = '[Dbg_4111] *** Here comes a new FileHiker instance …'
|
|
73
|
+
print(sMsg)
|
|
74
|
+
|
|
75
|
+
# (3) Retrieve the decision aids [seq 20260811°0921]
|
|
76
|
+
# (3.1) Is given basepath a relative one? [seq 20260811°0923] When and why shall this happen?!
|
|
77
|
+
bRelativeBasePath = False if os.path.isabs(self.__sBasePath) else True
|
|
78
|
+
|
|
79
|
+
# (3.2) Is this a selftest run? [seq 20260811°0925]
|
|
80
|
+
bIsSelftest = selftest # Testing for `os.path.basename(__file__) == '__main.py__'` won´t work here!
|
|
81
|
+
|
|
82
|
+
# (3.3) Provide the involved folders [seq 20260811°0927]
|
|
83
|
+
sCwd = FileHiker.normaleis(os.getcwd())
|
|
84
|
+
sModLoc = FileHiker.normaleis(os.path.abspath(os.path.dirname(__file__))) # Full path of current module — Interposing abspath() is defensive
|
|
85
|
+
|
|
86
|
+
# (4.1) Notification [seq 20260811°0933]
|
|
87
|
+
sMsg = f'[Dbg_4113] Basepath is empty or relative' \
|
|
88
|
+
+ f'\n • Selftest flag = {bIsSelftest}' \
|
|
89
|
+
+ f'\n • Relative flag = {bRelativeBasePath}' \
|
|
90
|
+
+ f'\n • FileHiker home = {sModLoc}' \
|
|
91
|
+
+ f'\n • CWD = {sCwd}' \
|
|
92
|
+
+ f'\n • BasePath = {self.__sBasePath}' \
|
|
93
|
+
+ f'\n • Direction = {'Forward' if self.__bDirection else 'Backward'}' \
|
|
94
|
+
+ f'\n • Ignore list = {self.__lstIgnorePats}'
|
|
95
|
+
print(sMsg)
|
|
96
|
+
|
|
97
|
+
# note 20260811°1011 'About BasePath determination' — Draft, possibly not really followed!
|
|
98
|
+
# Selftest with parameter 'selftest' is just a proposal, not (yet?) implemented
|
|
99
|
+
# +——————————————————————+————————————————————+———————————————————————+————————————————————+
|
|
100
|
+
# | | Empty basepath | Relative basepath | Absolute basepath |
|
|
101
|
+
# +——————————————————————+————————————————————+———————————————————————+————————————————————+
|
|
102
|
+
# | Regular call by some | CWD | CWD plus | Absolute path |
|
|
103
|
+
# | other script | | relative path | as is |
|
|
104
|
+
# +——————————————————————+————————————————————+———————————————————————+————————————————————+
|
|
105
|
+
# | Selftest | FileHiker folder | FileHiker folder | Absolute path |
|
|
106
|
+
# | | join 'cases' | join relative path | as is |
|
|
107
|
+
# +——————————————————————+————————————————————+———————————————————————+————————————————————+
|
|
108
|
+
# | # Selftest with | FileHiker folder | FileHiker folder | Absolute path |
|
|
109
|
+
# | # param 'selftest' | join CWD | join relative path | as is |
|
|
110
|
+
# +——————————————————————+————————————————————+———————————————————————+————————————————————+
|
|
111
|
+
|
|
112
|
+
# (4) Determine base path [seq 20260811°0931]
|
|
113
|
+
# The determination shall go by the following rules:
|
|
114
|
+
# 1. If given basepath is relative, treat it like an empty basepath with the relative part attached
|
|
115
|
+
# 2. If given basepath is empty, make it the folder where from Python was called, means the CWD
|
|
116
|
+
# 3. If it is a selftest, make it the folder where the package resides (which is easy
|
|
117
|
+
# to know in a development installation, harder to know in a PyPi installation)
|
|
118
|
+
if self.__sBasePath == '' or bRelativeBasePath:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
# (5) Implement just some simple first logic [seq 20260811°0941]
|
|
122
|
+
if self.__sBasePath == '':
|
|
123
|
+
|
|
124
|
+
if bIsSelftest:
|
|
125
|
+
sPth = os.path.join(sModLoc, 'cases')
|
|
126
|
+
sPth = FileHiker.normaleis(sPth)
|
|
127
|
+
self.__sBasePath = sPth
|
|
128
|
+
else:
|
|
129
|
+
self.__sBasePath = sCwd
|
|
130
|
+
|
|
131
|
+
FileHiker.normaleis(self.__sBasePath)
|
|
132
|
+
sMsg = f'[Dbg_4115] Set BasePath = {self.__sBasePath}'
|
|
133
|
+
print(sMsg)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
## \entry Private method 20260806°1511 find_left_neighbour
|
|
140
|
+
# \brief Backward walking work horse …
|
|
141
|
+
# \param self — Mandatory object
|
|
142
|
+
# \param sTgt_FolderName — The target folder
|
|
143
|
+
# \param sTgt_ElementName — The target filename, if target is a folder, this is blank
|
|
144
|
+
# \return The wanted left neighbour
|
|
145
|
+
def __find_left_neighbour ( self
|
|
146
|
+
, sTgt_FolderName #
|
|
147
|
+
, sTgt_ElementName #
|
|
148
|
+
) :
|
|
149
|
+
|
|
150
|
+
"""
|
|
151
|
+
Returns the left neighbour (previous item in global alphabetical order)
|
|
152
|
+
of the target file/directory, searching upward through the file system,
|
|
153
|
+
but not above base_path. If no left neighbour is found, wraps around to
|
|
154
|
+
the deepest last item of base_path.
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
# (L.4) Search the left neighbour [seq 20260806°1521]
|
|
158
|
+
while True :
|
|
159
|
+
|
|
160
|
+
# (L.4.1) Pessimistic predetermination [line 20260806°1522]
|
|
161
|
+
sNeighbour = None
|
|
162
|
+
|
|
163
|
+
# (L.4.2) Retrieve children of currently inspected folder [condi 20260806°1523]
|
|
164
|
+
try :
|
|
165
|
+
children = self.__get_listing(sTgt_FolderName) # [line 20260808°1111]
|
|
166
|
+
except FileNotFoundError :
|
|
167
|
+
sMsg = f'[Err_4117] Fatal — Directory not found: "{sTgt_FolderName}"'
|
|
168
|
+
raise FileNotFoundError(sMsg)
|
|
169
|
+
|
|
170
|
+
# (L.4.3) … [condi 20260806°1525]
|
|
171
|
+
if sTgt_ElementName in children :
|
|
172
|
+
|
|
173
|
+
# (L.4.3.1) Target is in current folder [seq 20260806°1527]
|
|
174
|
+
# (L.4.3.1.1) … [seq 20260806°1531]
|
|
175
|
+
target_index = children.index(sTgt_ElementName)
|
|
176
|
+
|
|
177
|
+
# (L.4.3.1.2) Is the wanted neighbour in this directory? [condi 20260806°1533]
|
|
178
|
+
if target_index > 0 :
|
|
179
|
+
|
|
180
|
+
# (L.4.3.1.2.1) Yes, left sibling is present [seq 20260806°1534]
|
|
181
|
+
# (L.4.3.1.2.1.1) Retrieve the neighbour [seq 20260806°1535]
|
|
182
|
+
left_sibling = children[target_index - 1]
|
|
183
|
+
left_sibling_path = os.path.join(sTgt_FolderName, left_sibling)
|
|
184
|
+
|
|
185
|
+
# (L.4.3.1.2.1.2) Is it a file or a folder? [condi 20260806°1537]
|
|
186
|
+
if os.path.isdir(left_sibling_path) :
|
|
187
|
+
sNeighbour = self.__get_deepest_last_item(left_sibling_path) # 🚀 Found
|
|
188
|
+
###sNeighbour = left_sibling_path # 🚀 Found — Deliver directory itself, not deepest child [chg 20260818°0821´01]
|
|
189
|
+
else :
|
|
190
|
+
sNeighbour = left_sibling_path # 🚀 Found
|
|
191
|
+
|
|
192
|
+
else :
|
|
193
|
+
|
|
194
|
+
# (L.4.3.1.2.2) Neighbour is not in this directory [seq 20260806°1540]
|
|
195
|
+
# Note : We don´t even need ask `if sTgt_FolderName == base_path`, the both cases are satisfied here
|
|
196
|
+
# Asking `if sTgt_FolderName == base_path` were only useful if we want skip outputting the base path
|
|
197
|
+
# and immediately wrap with get_deepest_last_item(base_path), as was the case in the initial versions
|
|
198
|
+
# Remember : todo 20260808°0921 'Clean up the slash mess'
|
|
199
|
+
sNeighbour = sTgt_FolderName # 🚀 Found — [line 20260806°1621] [chg 20260808°0913] Heureka! Just deliver the base folder itself.
|
|
200
|
+
|
|
201
|
+
else :
|
|
202
|
+
|
|
203
|
+
# (L.4.3.2) Target is not found in current folder, so move up [seq 20260806°1551]
|
|
204
|
+
# (L.4.3.2.1) Are we in the base folder? [condi 20260806°1552]
|
|
205
|
+
if sTgt_FolderName == self.__sBasePath :
|
|
206
|
+
# (L.4.3.2.1.1) Yes, we are in base folder, thus wrap around — Find deepest last item of base_path [seq 20260806°1553]
|
|
207
|
+
sNeighbour = self.__get_deepest_last_item(self.__sBasePath) # 🚀 Found
|
|
208
|
+
else :
|
|
209
|
+
# (L.4.3.2.1.2) No, we are not in base folder — Continue searching [seq 20260806°1555]
|
|
210
|
+
sTgt_ElementName = os.path.basename(sTgt_FolderName)
|
|
211
|
+
sTgt_FolderName = os.path.dirname(sTgt_FolderName)
|
|
212
|
+
|
|
213
|
+
# (L.4.4) Finished? Deliver result or continue searching [condi 20260806°1557]
|
|
214
|
+
if sNeighbour :
|
|
215
|
+
return sNeighbour
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
## \entry Private method 20260806°1811 find_right_neighbour
|
|
219
|
+
# \brief Forward walking work horse — Shall be symmetric to find_left_neighbour but is not
|
|
220
|
+
# — Discontinued in favour of method 20260818°0911 __find_right_neighbouur_4
|
|
221
|
+
# \param self — The mandatory object
|
|
222
|
+
# \param sTgt_FolderName — The root of the search area
|
|
223
|
+
# \param sTgt_ElementName — The item inside root for which the right neighbour is wanted
|
|
224
|
+
# \return The wanted right neighbour
|
|
225
|
+
# \note See issue 20260818°0811 'Process order forward not as wanted'
|
|
226
|
+
def __find_right_neighbour(self, sTgt_FolderName, sTgt_ElementName) : # [chg 20260818°0831´05] Formerly `__find_right_neighbour(self, s_target_path)`
|
|
227
|
+
|
|
228
|
+
"""
|
|
229
|
+
Returns the right neighbour (next item in global alphabetical order)
|
|
230
|
+
of the target file/directory, searching upward through the file system,
|
|
231
|
+
but not above base_path. If no right neighbour is found, wraps around to
|
|
232
|
+
the deepest first item of base_path.
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
# (R.4) Search the right neighbour [seq 20260806°1821]
|
|
236
|
+
while True :
|
|
237
|
+
|
|
238
|
+
# (R.4.1) Pessimistic predetermination [line 20260806°1822]
|
|
239
|
+
sNeighbour = None
|
|
240
|
+
|
|
241
|
+
# (R.4.2) Retrieve children of the currently inspected folder [condi 20260806°1823]
|
|
242
|
+
try :
|
|
243
|
+
children = self.__get_listing(sTgt_FolderName) # [line 20260808°1113]
|
|
244
|
+
except FileNotFoundError :
|
|
245
|
+
sMsg = f'[Err_4119] Fatal — Directory not found: "{sTgt_FolderName}"'
|
|
246
|
+
raise FileNotFoundError(sMsg)
|
|
247
|
+
|
|
248
|
+
# (R.4.3) … [condi 20260806°1825]
|
|
249
|
+
if sTgt_ElementName in children :
|
|
250
|
+
|
|
251
|
+
# (R.4.3.1) Target is in current folder [seq 20260806°1827]
|
|
252
|
+
# (R.4.3.1.1) … [seq 20260806°1831]
|
|
253
|
+
target_index = children.index(sTgt_ElementName)
|
|
254
|
+
|
|
255
|
+
# (R.4.3.1.2) Is the wanted neighbour in this directory? [condi 20260806°1833]
|
|
256
|
+
if target_index < len(children) - 1 :
|
|
257
|
+
|
|
258
|
+
# (R.4.3.1.2.1) Yes, right sibling present [seq 20260806°1834]
|
|
259
|
+
# (R.4.3.1.2.1.1) Retrieve the neighbour [seq 20260806°1835]
|
|
260
|
+
right_sibling = children[target_index + 1]
|
|
261
|
+
right_sibling_path = os.path.join(sTgt_FolderName, right_sibling)
|
|
262
|
+
|
|
263
|
+
# (R.4.3.1.2.1.2) Is it a file or a folder? [condi 20260806°1837]
|
|
264
|
+
if os.path.isdir(right_sibling_path) :
|
|
265
|
+
sNeighbour = self.__get_first_folder_item(right_sibling_path) # 🚀 Found
|
|
266
|
+
###sNeighbour = right_sibling_path # 🚀 Found — Deliver directory itself, not deepest child [chg 20260818°0821´02]
|
|
267
|
+
else :
|
|
268
|
+
sNeighbour = right_sibling_path # 🚀 Found
|
|
269
|
+
|
|
270
|
+
else :
|
|
271
|
+
|
|
272
|
+
# (R.4.3.1.2.2) Neighbour is not in this directory [seq 20260806°1840]
|
|
273
|
+
# Note : We don´t even need ask `if sTgt_FolderName == base_path`, the both cases are satisfied here
|
|
274
|
+
# Asking `if sTgt_FolderName == base_path` were only useful if we want skip outputting the base path
|
|
275
|
+
# and immediately wrap with get_deepest_last_item(base_path), as was the case in the initial versions
|
|
276
|
+
# Remember : todo 20260808°0921 'Clean up the slash mess'
|
|
277
|
+
sNeighbour = sTgt_FolderName # 🚀 Found — Empiric experiment [chg 20260808°0911] Heureka! Also deliver the base folder itself
|
|
278
|
+
|
|
279
|
+
else:
|
|
280
|
+
|
|
281
|
+
# (R.4.3.2) Target is not found in current folder, so move up [seq 20260806°1851]
|
|
282
|
+
# (R.4.3.2.1) Are we in the base folder? [condi 20260806°1552]
|
|
283
|
+
if sTgt_FolderName == self.__sBasePath :
|
|
284
|
+
# (R.4.3.2.1.1) Yes, we are in base folder, thus wrap around — Find deepest first item of base_path [seq 20260806°1853]
|
|
285
|
+
sNeighbour = self.__get_first_folder_item(self.__sBasePath) # 🚀 Found
|
|
286
|
+
else :
|
|
287
|
+
# (R.4.3.2.1.2) No, we are not in base folder — Continue searching [seq 20260806°1855]
|
|
288
|
+
sTgt_ElementName = os.path.basename(sTgt_FolderName)
|
|
289
|
+
sTgt_FolderName = os.path.dirname(sTgt_FolderName)
|
|
290
|
+
|
|
291
|
+
# (R.4.4) Finished? Deliver result or continue searching [condi 20260806°1857]
|
|
292
|
+
if sNeighbour :
|
|
293
|
+
return sNeighbour
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
## \entry Private method 20260818°0911 __find_right_neighbouur_4 (replacing 20260806°1811)
|
|
297
|
+
# \brief Forward walking work horse — Outputs symmetric to find_left_neighbour
|
|
298
|
+
# \details This is horrible code, just it seems to work as wanted. Has to be heavily refactored
|
|
299
|
+
# \param self — The mandarory objedt
|
|
300
|
+
# \param given_path — The target for which the right neighbour is wanted
|
|
301
|
+
# \param recursive — Flag telling whether it is the original call or a recursive one
|
|
302
|
+
# \return The wanted right neighbour
|
|
303
|
+
# \note See issue 20260818°0811 'Process order forward not as wanted'
|
|
304
|
+
# \note See issue 20260818°1131 'Refactor __find_right_neighbouur_4'
|
|
305
|
+
def __find_right_neighbouur_4 ( self
|
|
306
|
+
, given_path # Must be normalized
|
|
307
|
+
, recursive = False
|
|
308
|
+
) :
|
|
309
|
+
|
|
310
|
+
# () Set pessibmistic default [line 20260818°0913]
|
|
311
|
+
neighbour = None
|
|
312
|
+
|
|
313
|
+
# () Breakpoint [line 20260818°0914]
|
|
314
|
+
if given_path.endswith('README.md') :
|
|
315
|
+
pass
|
|
316
|
+
|
|
317
|
+
# () Extract the directory and the base name of the given path [line 20260818°0915]
|
|
318
|
+
dir_name, base_name = os.path.split(given_path)
|
|
319
|
+
|
|
320
|
+
# (.) Is given_path a directory? [condi 20260818°0917]
|
|
321
|
+
if os.path.isdir(given_path) :
|
|
322
|
+
|
|
323
|
+
# (.1) Yes — Given path is a directory [seq 20260818°0919]
|
|
324
|
+
# (.1.1) Retrieve children of the given path [seq 20260818°0921]
|
|
325
|
+
items = self.__get_listing(given_path) # Alphabetically sorted list, cleaned from ignored items
|
|
326
|
+
|
|
327
|
+
# (.1.2) Has the given folder children? [condi 20260818°0923]
|
|
328
|
+
if len(items) > 0 :
|
|
329
|
+
# () Yes — Given folder has at least one child [seq 20260818°0925]
|
|
330
|
+
neighbour = os.path.join(given_path, items[0])
|
|
331
|
+
return neighbour # 🚀
|
|
332
|
+
else :
|
|
333
|
+
|
|
334
|
+
# () No — The given folder has no children [seq 20260818°0927]
|
|
335
|
+
# (.1) … [seq 20260818°0929]
|
|
336
|
+
if recursive : # Just a try — Happened to work
|
|
337
|
+
return given_path # 🚀
|
|
338
|
+
|
|
339
|
+
# (.2) Peek into it to see whether it has children, if no children then get next peer [seq 20260818°0931]
|
|
340
|
+
dirnam, basenam = os.path.split(dir_name)
|
|
341
|
+
childs = self.__get_listing(dirnam)
|
|
342
|
+
ndx = childs.index(basenam)
|
|
343
|
+
|
|
344
|
+
# (.3) … [condi 20260818°0933]
|
|
345
|
+
if len(childs) < ndx + 2 : # No more right neighbour? Then go back the parents until …
|
|
346
|
+
dnam, bnam = os.path.split(dirnam)
|
|
347
|
+
parents = self.__get_listing(dnam)
|
|
348
|
+
ndx = parents.index(bnam)
|
|
349
|
+
x1 = parents[ndx + 1]
|
|
350
|
+
x2 = FileHiker.normaleis(os.path.join(dnam, x1))
|
|
351
|
+
return x2 # 🚀
|
|
352
|
+
|
|
353
|
+
# (.4) … [seq 20260818°0935]
|
|
354
|
+
x1 = childs[ndx + 1]
|
|
355
|
+
x2 = os.path.join(dirnam, x1)
|
|
356
|
+
return x2 # 🚀
|
|
357
|
+
|
|
358
|
+
else :
|
|
359
|
+
|
|
360
|
+
# (.1) No — Get it´s peers [seq 20260818°0937]
|
|
361
|
+
# (.1.1) … [seq 20260818°0939]
|
|
362
|
+
items = self.__get_listing(dir_name) # The peers of current file
|
|
363
|
+
ndx = items.index(base_name)
|
|
364
|
+
|
|
365
|
+
# (.1.2) … [seq 20260818°0941]
|
|
366
|
+
if len(items) > ndx + 1 :
|
|
367
|
+
|
|
368
|
+
# (.1.2.1) Yes — … [seq 20260818°0943]
|
|
369
|
+
# (.1.2.1.1) [seq 20260818°0945]
|
|
370
|
+
nbr1 = os.path.join(dir_name, items[ndx + 1])
|
|
371
|
+
nbr2 = FileHiker.normaleis(nbr1)
|
|
372
|
+
|
|
373
|
+
# (.1.2.1.2) … [condi 20260818°0947]
|
|
374
|
+
if os.path.isdir(nbr2) : # Is folder
|
|
375
|
+
|
|
376
|
+
# (.1) Yes — … [seq 20260818°0949]
|
|
377
|
+
# (.1.1) … [condi 20260818°0951]
|
|
378
|
+
if not recursive : # Empirical try
|
|
379
|
+
return nbr2 # 🚀
|
|
380
|
+
|
|
381
|
+
# (.1.2) … [condi 20260818°0953]
|
|
382
|
+
nbr3 = self.__find_right_neighbouur_4(nbr2, True)
|
|
383
|
+
if nbr3 :
|
|
384
|
+
neighbour = nbr3
|
|
385
|
+
else :
|
|
386
|
+
neighbour = nbr2
|
|
387
|
+
|
|
388
|
+
else :
|
|
389
|
+
|
|
390
|
+
# (.2) No — … [seq 20260818°0955]
|
|
391
|
+
neighbour = nbr2
|
|
392
|
+
|
|
393
|
+
# () … [seq 20260818°0957]
|
|
394
|
+
return neighbour # 🚀
|
|
395
|
+
|
|
396
|
+
else :
|
|
397
|
+
|
|
398
|
+
# (.1.2.2) No — … [seq 20260818°1011]
|
|
399
|
+
# () … [seq 20260818°1013]
|
|
400
|
+
sNext = None
|
|
401
|
+
currdir = dir_name
|
|
402
|
+
|
|
403
|
+
# () … [loop 20260818°1015]
|
|
404
|
+
while not sNext :
|
|
405
|
+
|
|
406
|
+
# () Turn around? [condi 20260818°1017]
|
|
407
|
+
if currdir + '/' == self.get_BasePath() :
|
|
408
|
+
return self.get_BasePath() # 🚀
|
|
409
|
+
|
|
410
|
+
# () Find next from the peers of parents (aunts) [seq 20260818°1021]
|
|
411
|
+
dirnm, basenm = os.path.split(currdir)
|
|
412
|
+
peers = self.__get_listing(dirnm)
|
|
413
|
+
ndx = peers.index(basenm)
|
|
414
|
+
|
|
415
|
+
# () … [seq 20260818°1023]
|
|
416
|
+
if len(peers) > ndx + 1 : # No more right neighbour? Then go back the parents until …
|
|
417
|
+
# () Found … [seq 20260818°1025]
|
|
418
|
+
sNext = FileHiker.normaleis(os.path.join(dirnm, peers[ndx + 1]))
|
|
419
|
+
return sNext # 🚀
|
|
420
|
+
else :
|
|
421
|
+
# () Continue diving … [seq 20260818°1027]
|
|
422
|
+
currdir = dirnm
|
|
423
|
+
|
|
424
|
+
# () Find the index of the given base name in the sorted list [seq 20260818°1031]
|
|
425
|
+
try:
|
|
426
|
+
current_index = sorted_items.index(base_name)
|
|
427
|
+
except ValueError:
|
|
428
|
+
raise ValueError(f"[Err_4129] The item {base_name} does not exist in the directory {dir_name}.")
|
|
429
|
+
|
|
430
|
+
# () Determine the next item [condi 20260818°1033]
|
|
431
|
+
if current_index + 1 < len(sorted_items):
|
|
432
|
+
# () Yes — Determine the next item [seq 20260818°1035]
|
|
433
|
+
# (.1) … [seq 20260818°1036]
|
|
434
|
+
next_item = sorted_items[current_index + 1]
|
|
435
|
+
next_path = os.path.join(dir_name, next_item)
|
|
436
|
+
|
|
437
|
+
# (.2) If the next item is a directory, dive into it [condi 20260818°1037]
|
|
438
|
+
if os.path.isdir(next_path):
|
|
439
|
+
return self.__find_right_neighbouur_3(next_path) # 🚀
|
|
440
|
+
else:
|
|
441
|
+
return next_path # 🚀
|
|
442
|
+
else:
|
|
443
|
+
# () No — … [condi 20260818°1041]
|
|
444
|
+
return None # 🚀 No next item available — Does this ever happen?!
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
## \entry Private method 20260806°1731 get_deepest_first_item
|
|
448
|
+
# \brief Helper method — Symmetric to get_deepest_last_item …
|
|
449
|
+
# \param directory — …
|
|
450
|
+
# \return The wanted first item
|
|
451
|
+
def __get_first_folder_item(self, directory) : # Formerly __get_deepest_first_item(self, directory)
|
|
452
|
+
|
|
453
|
+
"""
|
|
454
|
+
Recursively finds the <del>deepest</del> first item (file or directory)
|
|
455
|
+
in a directory, traversed in a depth-first, alphabetically sorted manner.
|
|
456
|
+
"""
|
|
457
|
+
|
|
458
|
+
# (R.1) Pessimistic predetermination [line 20260806°1733]
|
|
459
|
+
return_value = None
|
|
460
|
+
|
|
461
|
+
# (R.2) … [seq 20260806°1735]
|
|
462
|
+
items = self.__get_listing(directory) # Alphabetically sorted and cleaned from to-ignore items
|
|
463
|
+
|
|
464
|
+
# (R.3) Are items in the folder? [condi 20260806°1741]
|
|
465
|
+
if items :
|
|
466
|
+
# (R.3.1) Yes, the folder has items [seq 20260806°1745]
|
|
467
|
+
# (R.3.1.1) … [seq 20260806°1747]
|
|
468
|
+
first_item = items[0]
|
|
469
|
+
first_item_path = os.path.join(directory, first_item)
|
|
470
|
+
|
|
471
|
+
# (R.3.1.2) … [condi 20260806°1751]
|
|
472
|
+
if os.path.isdir(first_item_path):
|
|
473
|
+
x = self.__get_first_folder_item(first_item_path)
|
|
474
|
+
return_value = x # 🚀 Found
|
|
475
|
+
else :
|
|
476
|
+
return_value = first_item_path # 🚀 Found
|
|
477
|
+
else :
|
|
478
|
+
# (R.3.2) No, the folder is empty [seq 20260806°1743]
|
|
479
|
+
return_value = directory # 🚀 Found. Empty directory, return itself
|
|
480
|
+
|
|
481
|
+
# (R.4) Ready [line 20260806°1753]
|
|
482
|
+
return return_value
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
## \entry Private method 20260806°1431 get_deepest_last_item
|
|
486
|
+
# \brief Helper method …
|
|
487
|
+
# \param directory — …
|
|
488
|
+
# \return The wanted last item
|
|
489
|
+
def __get_deepest_last_item(self, directory) :
|
|
490
|
+
|
|
491
|
+
"""
|
|
492
|
+
Recursively finds the deepest last item (file or directory) in a directory,
|
|
493
|
+
traversed in a depth-first, alphabetically sorted manner.
|
|
494
|
+
"""
|
|
495
|
+
|
|
496
|
+
# (L.1) Pessimistic predetermination [line 20260806°1433]
|
|
497
|
+
return_value = None
|
|
498
|
+
|
|
499
|
+
# (L.2) … [seq 20260806°1435]
|
|
500
|
+
items = self.__get_listing(directory) # Alphabetically sorted and cleaned from to-ignore items
|
|
501
|
+
|
|
502
|
+
# (L.3) Are items in the folder? [condi 20260806°1441]
|
|
503
|
+
if items :
|
|
504
|
+
# (L.3.1) Yes, the folder has items [seq 20260806°1445]
|
|
505
|
+
# (L.3.1.1) … [seq 20260806°1447]
|
|
506
|
+
last_item = items[-1]
|
|
507
|
+
last_item_path = os.path.join(directory, last_item)
|
|
508
|
+
|
|
509
|
+
# (L.3.1.2) … [condi 20260806°1451]
|
|
510
|
+
if os.path.isdir(last_item_path) :
|
|
511
|
+
x = self.__get_deepest_last_item(last_item_path)
|
|
512
|
+
return_value = x # 🚀 Found
|
|
513
|
+
else :
|
|
514
|
+
return_value = last_item_path # 🚀 Found
|
|
515
|
+
else :
|
|
516
|
+
# (L.3.2) No, the folder is empty [seq 20260806°1443]
|
|
517
|
+
return_value = directory # 🚀 Found. Empty directory, return itself
|
|
518
|
+
|
|
519
|
+
# (L.4) Ready [line 20260806°1453]
|
|
520
|
+
return return_value
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
## \entry Private method 20260806°1341 get_listing
|
|
524
|
+
# \brief Sort method …
|
|
525
|
+
# \param sDirectory — String with directory of which the listing is wanted …
|
|
526
|
+
# \return Listing sorted and cleaned from ignored items or None, where None is fatal
|
|
527
|
+
# \detail Sorting is made a dedicated function, so we can consistently implement the ignore feature
|
|
528
|
+
# \detail Finding 20260806°1621 — `x = list.sort(key = str.casefold)` yields `x = None´. Explanation: list.sort() does
|
|
529
|
+
# not return a value! Reference https://docs.python.org/3/library/stdtypes.html#list.sort [ref 20260807°1616]
|
|
530
|
+
# says 'This method modifies the sequence in place for economy of space when sorting a large sequence …'
|
|
531
|
+
def __get_listing(self, sDirectory) :
|
|
532
|
+
|
|
533
|
+
# (1) Create sorted list [seq 20260806°1343]
|
|
534
|
+
try :
|
|
535
|
+
items1 = os.listdir(sDirectory)
|
|
536
|
+
except :
|
|
537
|
+
return None
|
|
538
|
+
|
|
539
|
+
items1.sort(key = str.casefold) # Sorting is done 'in place', not returning a new list
|
|
540
|
+
|
|
541
|
+
# (2) Filter out ignored entries [var 20260808°0713]
|
|
542
|
+
items2 = [
|
|
543
|
+
sEntry for sEntry in items1
|
|
544
|
+
if not any(fnmatch.fnmatch(sEntry, pattern) for pattern in self.__lstIgnorePats)
|
|
545
|
+
]
|
|
546
|
+
|
|
547
|
+
# (3) Finished [line 20260806°1345]
|
|
548
|
+
return items2
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
## \entry Private method 20260808°1011
|
|
552
|
+
# \brief Finds the next valid path for an invalid path one
|
|
553
|
+
# \param sPath — The invalid one …
|
|
554
|
+
# \return The next valid path …
|
|
555
|
+
# \detail Expects a string path so far, but might be extended to process a pathlib.Path as well
|
|
556
|
+
# \remark See todo 20260808°1131 'Consolidate invalid path algorithm'
|
|
557
|
+
def __guarantee_valid_pathes(self, s_target_path) :
|
|
558
|
+
|
|
559
|
+
# (1) Tweak input [seq 20260806°1513]
|
|
560
|
+
# (1.1) Make path absolute [seq 20260808°0931]
|
|
561
|
+
sBase_path = os.path.abspath(self.__sBasePath)
|
|
562
|
+
sTarget_path = os.path.abspath(s_target_path)
|
|
563
|
+
|
|
564
|
+
# (1.2) Normalize [seq 20260808°0933] Check — Not yet sure what shall come first, abspath or normalize
|
|
565
|
+
sBase_path = FileHiker.normaleis(sBase_path)
|
|
566
|
+
sTarget_path = FileHiker.normaleis(sTarget_path)
|
|
567
|
+
|
|
568
|
+
# (2) Valid base path is absolute requirement [seq 20260808°1013]
|
|
569
|
+
if not os.path.exists(sBase_path) :
|
|
570
|
+
sMsg = f'[Err_4121] Fatal — Invalid base path: "{sBase_path}"'
|
|
571
|
+
#raise ValueError(sMsg)
|
|
572
|
+
print(sMsg)
|
|
573
|
+
os.sys.exit(66)
|
|
574
|
+
|
|
575
|
+
# (R.2) Paranoia — Guarantee target_path is inside or equal to base_path [condi 20260806°1815]
|
|
576
|
+
if not (sTarget_path.startswith(self.__sBasePath)) :
|
|
577
|
+
|
|
578
|
+
# () Notification
|
|
579
|
+
###raise ValueError(f"[Err_4133] Target path {sTarget_path} is not inside or equal to base path {self.__sBasePath}")
|
|
580
|
+
sMsg = f'[Wrn_4133] Warning — Target is not inside or equal to base path, it will be fixed to basepath:' \
|
|
581
|
+
+ f' • Target given = "{sTarget_path}"' \
|
|
582
|
+
+ f' • Target fixed = "{self.__sBasePath}"'
|
|
583
|
+
print(sMsg)
|
|
584
|
+
|
|
585
|
+
# () Brute force correction [line 20260818°0751]
|
|
586
|
+
sTarget_path = self.__sBasePath
|
|
587
|
+
|
|
588
|
+
return self.__sBasePath, sTarget_path # 🚀
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
# (3) Is it a symlink= [seq 20260808°1021] Space located for later implementation
|
|
592
|
+
# See e.g. https://bobbyhadz.com/blog/python-check-if-file-or-directory-path-is-symbolic-link [ref 20260807°1806]
|
|
593
|
+
if os.path.islink(sTarget_path) :
|
|
594
|
+
|
|
595
|
+
# (3.1) Process symlink= [seq 20260808°1023] Dummy code so far. Don´t know yet what exactly to do then
|
|
596
|
+
sMsg = f'[Msg_4123] Symlink = "{sTarget_path}"'
|
|
597
|
+
print(sMsg)
|
|
598
|
+
|
|
599
|
+
# (4) Fix any wrong target path [seq 20260808°1031] See todo 20260808°1131 'Consolidate invalid path algorithm'
|
|
600
|
+
# See e.g. https://chat.mistral.ai/chat/d5e31f0a-bf1f-404f-ad22-cce3c5757faa [ref 20260802°0920 👻]
|
|
601
|
+
# See e.g. https://www.perplexity.ai/search/0126d5ee-85f6-4758-bcd6-bef53601fcaf [ref 20260802°1312]
|
|
602
|
+
if not os.path.exists(sTarget_path) :
|
|
603
|
+
|
|
604
|
+
sValidPath = None
|
|
605
|
+
|
|
606
|
+
# (4.1) Process invalid parent folder(s) — Quick´n´dirty [seq 20260808°1033]
|
|
607
|
+
# This sequence comes into effect, if one of the parent folders of the target item is invalid.
|
|
608
|
+
# Then it will simple-mindedly search the parents for the first valid folder, doing no
|
|
609
|
+
# interpolation This could be much refined by doing interpolations, looking for existing
|
|
610
|
+
# neighbours of the invalid path element, as does the algorithm below [todo 20260808°1131]
|
|
611
|
+
sSearchDir = os.path.dirname(sTarget_path)
|
|
612
|
+
if not os.path.exists(sSearchDir) :
|
|
613
|
+
while not os.path.exists(sSearchDir) :
|
|
614
|
+
sSearchDir = os.path.dirname(sSearchDir)
|
|
615
|
+
return sBase_path, FileHiker.normaleis(sSearchDir) # 🚀
|
|
616
|
+
|
|
617
|
+
# (4.2) Process invalid path last-element [seq 20260808°1035]
|
|
618
|
+
# Here we really look for the nearest replacement for the invalid item by bisecting the listing.
|
|
619
|
+
# Just so far this is applied only for the 'path last-element', not for any deeper mishap,
|
|
620
|
+
# which were already processed simple-minded above.
|
|
621
|
+
# Note : The 'path last-element' is what os.path.basename() delivers. Just the word 'basename' is
|
|
622
|
+
# not so suited here, since we already talk about the 'base path', which is kind of opposite.
|
|
623
|
+
while True :
|
|
624
|
+
|
|
625
|
+
# (4.2.1) … [seq 20260808°1041]
|
|
626
|
+
sValidEntry = None
|
|
627
|
+
aunts = self.__get_listing(sSearchDir) # Aunts is short for 'aunts and uncles' or 'parent siblings'
|
|
628
|
+
if aunts == None :
|
|
629
|
+
# Continue search in parent folder
|
|
630
|
+
sSearchDir = os.path.dirname(sSearchDir)
|
|
631
|
+
continue
|
|
632
|
+
|
|
633
|
+
# (4.2.2) Find the insertion point for the target_path [seq 20260808°1043]
|
|
634
|
+
sLastElement = os.path.basename(sTarget_path) # basename is last element of path
|
|
635
|
+
#if b_Direction :
|
|
636
|
+
# index = bisect.bisect_left(aunts, sLastElement)
|
|
637
|
+
# index_compare = bisect.bisect_right(aunts, sLastElement)
|
|
638
|
+
#else :
|
|
639
|
+
# index = bisect.bisect_right(aunts, sLastElement) # basename is last element of path
|
|
640
|
+
index = bisect.bisect(aunts, sLastElement) # basename is last element of path
|
|
641
|
+
|
|
642
|
+
# (4.2.3) Determine previous and next paths [seq 20260808°1045]
|
|
643
|
+
if index > 0 :
|
|
644
|
+
sValidEntry = aunts[index - 1]
|
|
645
|
+
elif index < len(aunts) :
|
|
646
|
+
sValidEntry = aunts[index]
|
|
647
|
+
else :
|
|
648
|
+
sMsg = f'[Err_4125] Fatal — Invalid base path: "{sBase_path}"'
|
|
649
|
+
#raise ValueError(sMsg)
|
|
650
|
+
print(sMsg)
|
|
651
|
+
os.sys.exit(66)
|
|
652
|
+
|
|
653
|
+
# (4.2.4) Searching done? [seq 20260808°1047]
|
|
654
|
+
if sValidEntry :
|
|
655
|
+
sTarget_path = os.path.join(os.path.dirname(sTarget_path), sValidEntry)
|
|
656
|
+
break
|
|
657
|
+
|
|
658
|
+
return sBase_path, sTarget_path # 🚀
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
## \register Public method 20260810°1131 find_neighbour
|
|
662
|
+
# \brief The two-way walking work horse API method …
|
|
663
|
+
# \detail Just a wrapper method to allow for the API one single method with a direction flag
|
|
664
|
+
# \remark The spelling 'neighbor' is American English, 'neighbour' is British English
|
|
665
|
+
# \param base_path — The root of the search area
|
|
666
|
+
# \param target_path — The item inside root for which the left neighbour is wanted
|
|
667
|
+
# \param b_forward — Flag telling whether to walk forward or backward
|
|
668
|
+
# \return The wanted right or left neighbour
|
|
669
|
+
def find_neighbour(self, s_target_path) :
|
|
670
|
+
|
|
671
|
+
# () Parameter sanitation [line 20260816°0811]
|
|
672
|
+
s_target_path = FileHiker.normaleis(s_target_path)
|
|
673
|
+
|
|
674
|
+
# () Paranoia — Guarantee target_path is inside or equal to base_path [condi 20260816°0751]
|
|
675
|
+
# This shall replace seq 20260806°1515 and seq 20260806°1815
|
|
676
|
+
if not (s_target_path.startswith(self.__sBasePath)) :
|
|
677
|
+
|
|
678
|
+
# () Notify problem [line 20260816°0753] Todo: Implement more graceful notification [todo 20260816°0755]
|
|
679
|
+
sMsg = f'\n\n[Err_4135] Given target path is not inside or equal to base path' \
|
|
680
|
+
+ f'\n • BasePath = "{self.__sBasePath}"' \
|
|
681
|
+
+ f'\n • Given target = "{s_target_path}"' \
|
|
682
|
+
+ f'\n • Fixed target = "{self.__sBasePath}"\n'
|
|
683
|
+
print(sMsg)
|
|
684
|
+
|
|
685
|
+
s_target_path = self.__sBasePath
|
|
686
|
+
|
|
687
|
+
# ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
|
|
688
|
+
# () Preparations … [seq 20260818°0833] Shifted redundant sequence here from __find_left_neighbour() and __find_left_neighbour() [chg 20260818°0831´01]
|
|
689
|
+
# (L.1) Guarantee valid pathes [seq 20260808°0941]
|
|
690
|
+
sBase_path, sTarget_path = self.__guarantee_valid_pathes(s_target_path)
|
|
691
|
+
|
|
692
|
+
# (L.3) Is target the base path? [seq 20260806°1517]
|
|
693
|
+
if sTarget_path == self.__sBasePath :
|
|
694
|
+
sTgt_FolderName = self.__sBasePath
|
|
695
|
+
sTgt_ElementName = os.path.basename(self.__sBasePath)
|
|
696
|
+
else :
|
|
697
|
+
sTgt_FolderName = os.path.dirname(sTarget_path)
|
|
698
|
+
sTgt_ElementName = os.path.basename(sTarget_path)
|
|
699
|
+
# ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
|
|
700
|
+
|
|
701
|
+
# () Dispatch the call [seq 20260810°1133]
|
|
702
|
+
if self.__bDirection:
|
|
703
|
+
###neighbour = self.__find_right_neighbour(sTgt_FolderName, sTgt_ElementName) # [chg 20260818°0831´06]
|
|
704
|
+
x = FileHiker.normaleis(sTgt_FolderName + '/' + sTgt_ElementName)
|
|
705
|
+
neighbour = self.__find_right_neighbouur_4(x)
|
|
706
|
+
else:
|
|
707
|
+
neighbour = self.__find_left_neighbour(sTgt_FolderName, sTgt_ElementName) # [chg 20260818°0831´07]
|
|
708
|
+
|
|
709
|
+
# () Streamline result — Should be superfluous if the callees return values are normalized [line 20260810°1135]
|
|
710
|
+
sNeighbour = FileHiker.normaleis(neighbour)
|
|
711
|
+
|
|
712
|
+
# () Ready
|
|
713
|
+
return sNeighbour
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
## \entry Method 20260811°1041 get_BasePath
|
|
717
|
+
# \brief Accessor method …
|
|
718
|
+
# \return The wanted value
|
|
719
|
+
def get_BasePath(self) :
|
|
720
|
+
|
|
721
|
+
return self.__sBasePath
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
## \entry Method 20260811°1051 get_Direction
|
|
725
|
+
# \brief Accessor
|
|
726
|
+
# \return The wanted value
|
|
727
|
+
def get_Direction(self) :
|
|
728
|
+
|
|
729
|
+
return self.__bDirection
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
## \entry Method 20260811°1121 get_Verbose
|
|
733
|
+
# \brief Accessor
|
|
734
|
+
# \return The wanted value
|
|
735
|
+
def get_Verbose(self) :
|
|
736
|
+
|
|
737
|
+
return self.__bVerbose
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
## \entry Method 20260811°1111 get_IgnoreList
|
|
741
|
+
# \brief Accessor
|
|
742
|
+
# \return The wanted value
|
|
743
|
+
def get_IgnoreList(self) :
|
|
744
|
+
|
|
745
|
+
return self.__lstIgnorePats
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
## \entry Static method 20260804°0701
|
|
749
|
+
# \brief Helper method …
|
|
750
|
+
# \param path — The path to be normalized, be it a string or pathlib.Path
|
|
751
|
+
# \return String with the normalized path
|
|
752
|
+
@staticmethod
|
|
753
|
+
def normaleis(path) :
|
|
754
|
+
|
|
755
|
+
sPath = str(path) # Convert possible pathlib.Path object
|
|
756
|
+
sPath21 = os.path.normpath(sPath) # Get rid of intermittend dot folders
|
|
757
|
+
sPath22 = sPath21.replace('\\', '/') # Use slashes, not backslashes
|
|
758
|
+
bExists = os.path.exists(sPath)
|
|
759
|
+
sPath23 = sPath22 # Set default
|
|
760
|
+
if bExists and os.path.isdir(sPath22) :
|
|
761
|
+
sPath23 = sPath22 if sPath22.endswith('/') else sPath22 + '/' # Guarantee folder´s trailing slash
|
|
762
|
+
|
|
763
|
+
sPath23 = sPath23[:-1] if sPath23.endswith('//') else sPath23 # Provisory patch against double slashes [line 20260818°1111]
|
|
764
|
+
|
|
765
|
+
return sPath23
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
## \entry Method 20260811°1043 set_BasePath
|
|
769
|
+
# \brief Accessor method to set the base path …
|
|
770
|
+
# \param The value to be set
|
|
771
|
+
def set_BasePath(self, sPath) :
|
|
772
|
+
|
|
773
|
+
sPth = FileHiker.normaleis(sPath)
|
|
774
|
+
|
|
775
|
+
self.__sBasePath = sPth
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
## \entry Method 20260811°1053 set_Direction
|
|
779
|
+
# \brief Set the direction flag
|
|
780
|
+
# \param bDir — The value wanted to be set
|
|
781
|
+
def set_Direction(self, bDir) :
|
|
782
|
+
|
|
783
|
+
self.__bDirection = bDir
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
## \entry Method 20260811°1113 set_IgnoreList
|
|
787
|
+
# \brief Set the ignore list
|
|
788
|
+
# \param The value to be set
|
|
789
|
+
def set_IgnoreList(self, lst) :
|
|
790
|
+
|
|
791
|
+
self.__lstIgnorePats = lst
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
## \entry Method 20260811°1123 set_Verbose
|
|
795
|
+
# \brief Set the verbose flag
|
|
796
|
+
# \param The value to be set
|
|
797
|
+
def get_Verbose(self, bVerbose) :
|
|
798
|
+
|
|
799
|
+
self.__bVerbose = bVerbose
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
## \entry Public?! method 20260808°0731 — So far only called from __main__.py, thus possibly not private. This method may be superfluous!
|
|
803
|
+
# \brief Strip base path prefix from full path string …
|
|
804
|
+
# \param s_BasePath — …
|
|
805
|
+
# \param s_FullPath — …
|
|
806
|
+
# \return Path string with the base path stripped
|
|
807
|
+
def strip_prefix(self, s_BasePath, s_FullPath) :
|
|
808
|
+
|
|
809
|
+
sBasePath = s_BasePath.replace('\\', '/') # Paranoia
|
|
810
|
+
sFullPath = s_FullPath.replace('\\', '/')
|
|
811
|
+
sReturn = sFullPath # Pessimistic default
|
|
812
|
+
|
|
813
|
+
if sFullPath.startswith(sBasePath) :
|
|
814
|
+
sReturn = sFullPath[len(s_BasePath):]
|
|
815
|
+
|
|
816
|
+
return sReturn
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
|
|
820
|
+
## Former functionality code shutdown in favour of class 20260810°0911 FileHiker [chg 20260810°1051 '💫 Breaking change']
|
|
821
|
+
### = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
|
|
822
|
+
|
|
823
|
+
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
|
|
824
|
+
## Seftest functions shifted from here to module __main__.py [chg 20260811°0811]
|
|
825
|
+
### = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
## \register seq 20250406°0731
|
|
829
|
+
# \brief Main idiom — The plain `if __name__ == '__main__'` does not work as usual because the caller here is __main__.py
|
|
830
|
+
if __name__ == "__main__" and __package__ not in (None, ''):
|
|
831
|
+
|
|
832
|
+
print('[Msg_4127] This package module shall not be started directly.')
|
|
833
|
+
sys.exit(123) # Runs without any ado in a commandline call, throws exception `SystemExit` in the debugger
|