py3toolset 1.2.18__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.
py3toolset/fs.py ADDED
@@ -0,0 +1,543 @@
1
+ """
2
+ A module for file system operations.
3
+
4
+ File removal, copying, checking, path operations, etc.
5
+ """
6
+
7
+ from glob import glob
8
+ from os.path import (isabs, exists, os, dirname, basename, isfile, isdir, join,
9
+ sep)
10
+ from os import environ, pathsep, mkdir
11
+ import re
12
+ import sys
13
+ from posix import remove, getcwd, rmdir
14
+ from shutil import which, copyfile
15
+
16
+
17
+ def remove_files(*files):
18
+ """
19
+ Removes all ``files`` from the file system.
20
+
21
+ .. note::
22
+ Item which is not a file is ignored.
23
+
24
+ .. seealso::
25
+ :func:`.remove_dirs`
26
+
27
+ Example:
28
+ >>> from random import randint
29
+ >>> rand_suffix = str(randint(1, 2**10))
30
+ >>> # create a folder and dummy files
31
+ >>> parent_dir = "test_remove_files" + rand_suffix
32
+ >>> os.mkdir(parent_dir)
33
+ >>> f1 = os.path.join(parent_dir, "f1")
34
+ >>> f_out = open(f1, 'w')
35
+ >>> f_out.writelines(["Test test test"])
36
+ >>> f_out.close()
37
+ >>> f2 = os.path.join(parent_dir, "f2")
38
+ >>> f2_out = open(f2, 'w')
39
+ >>> f2_out.writelines(["Test2 test2 test2"])
40
+ >>> f2_out.close()
41
+ >>> os.path.exists(f1)
42
+ True
43
+ >>> os.path.exists(f2)
44
+ True
45
+ >>> # remove files f1 and f2
46
+ >>> remove_files(f1, f2)
47
+ >>> os.path.exists(f1)
48
+ False
49
+ >>> os.path.exists(f2)
50
+ False
51
+ >>> remove_dirs(parent_dir)
52
+
53
+ """
54
+ for f in files:
55
+ if isfile(f):
56
+ remove(f)
57
+
58
+
59
+ def remove_dirs(*dirs):
60
+ """
61
+ Removes recursively directories/folders.
62
+
63
+ Args:
64
+ dirs: ``list``
65
+ List of directories to remove.
66
+
67
+ .. note::
68
+ If an element in dirs isn't a folder, it is ignored.
69
+
70
+ .. seealso::
71
+ :func:`.remove_files`
72
+
73
+
74
+ Example:
75
+ >>> from random import randint
76
+ >>> rand_suffix = str(randint(1, 2**10))
77
+ >>> # create a folder and dummy files
78
+ >>> parent_dir = "test_remove_dirs" + rand_suffix
79
+ >>> parent_dir2 = "test_remove_dirs" + rand_suffix + '2'
80
+ >>> os.mkdir(parent_dir)
81
+ >>> os.mkdir(parent_dir2)
82
+ >>> f1 = os.path.join(parent_dir, "f1")
83
+ >>> f_out = open(f1, 'w')
84
+ >>> f_out.close()
85
+ >>> f2 = os.path.join(parent_dir, "f2")
86
+ >>> f2_out = open(f2, 'w')
87
+ >>> f2_out.close()
88
+ >>> os.path.exists(f1)
89
+ True
90
+ >>> os.path.exists(f2)
91
+ True
92
+ >>> os.path.exists(parent_dir2)
93
+ True
94
+ >>> # remove directories
95
+ >>> remove_dirs(parent_dir, parent_dir2)
96
+ >>> os.path.exists(parent_dir)
97
+ False
98
+ >>> os.path.exists(parent_dir2)
99
+ False
100
+
101
+ """
102
+ for d in dirs:
103
+ if isdir(d):
104
+ for e in glob(d+os.sep+"*"):
105
+ if isdir(e):
106
+ remove_dirs(e)
107
+ else:
108
+ remove_files(e)
109
+ # d empty
110
+ rmdir(d)
111
+
112
+
113
+ def get_prog_parent_dir():
114
+ """
115
+ Returns absolute path of parent folder of the currently running script.
116
+
117
+ Example:
118
+
119
+ Script ``./nop.py``:
120
+
121
+ .. code-block:: python
122
+
123
+ #!/usr/bin/env python3
124
+ from py3toolset.fs import get_prog_parent_dir
125
+
126
+ if __name__ == '__main__':
127
+ print(get_prog_parent_dir())
128
+
129
+ Use of script and function:
130
+
131
+ .. code-block:: bash
132
+
133
+ $ echo $PWD
134
+ /my/current/working/dir/.
135
+ $ ./nop.py
136
+ /my/current/working/dir
137
+ $ export PATH=$PATH:.
138
+ $ nop.py
139
+ /my/current/working/dir/.
140
+ $ cd ..
141
+ $ ./dir/nop.py
142
+ /my/current/working/dir
143
+
144
+
145
+ """
146
+ pdir = dirname(sys.argv[0])
147
+ if pdir.startswith(os.sep):
148
+ # absolute path of prog was given
149
+ return pdir
150
+ elif not pdir:
151
+ # no parent dir given in script cmd
152
+ # prog probably located through PATH
153
+ return which(sys.argv[0]) or "."
154
+ # TODO: or "." should be removed
155
+ # it was just a tmp test trick
156
+ # anyway it shouldn't be reached
157
+ else:
158
+ # relative dir given to script
159
+ # return absolute dir
160
+ return getcwd() + os.sep + pdir
161
+
162
+
163
+ def set_abs_or_relative_path(parent_dir, _file):
164
+ """
165
+ Sets a full path for ``_file`` (can be relative or absolute).
166
+
167
+ If ``_file`` is an absolute path returns ``_file`` as is.
168
+ Otherwise if ``parent_dir`` is set, returns the path ``parent_dir/_file``.
169
+ Raises an exception if ``parent_dir`` is ``None`` while ``_file`` is not
170
+ absolute.
171
+ The function never verifies the file exists, it only builds a full path.
172
+
173
+ Args:
174
+ parent_dir: ``str``
175
+ a candidate parent directory (relative or absolute path)
176
+ for _file. It is only used if _file is not an absolute path.
177
+ _file: ``str``
178
+ any filename or filepath (relative to ``parent_dir`` or
179
+ ``absolute``).
180
+
181
+ Return:
182
+ the full (absolute or relative path) of ``_file``; that is
183
+ ``parent_dir + os.sep + _file`` or ``_file``.
184
+
185
+ Example:
186
+ >>> set_abs_or_relative_path(None, '/my/absolute/file/path')
187
+ '/my/absolute/file/path'
188
+ >>> set_abs_or_relative_path('/my/absolute/file', 'path')
189
+ '/my/absolute/file/path'
190
+ >>> set_abs_or_relative_path(None, 'path')
191
+ Traceback (most recent call last):
192
+ ...
193
+ ValueError: parent_dir must be defined
194
+
195
+ """
196
+ if isabs(_file):
197
+ # _file is absolute path
198
+ # or not absolute path, but exists
199
+ return _file
200
+ elif parent_dir is None:
201
+ raise ValueError('parent_dir must be defined')
202
+ else: # parent_dir is not None: # not abs path, prefix with parent dir
203
+ return parent_dir + os.sep + _file
204
+
205
+
206
+ def all_files_in_same_dir(*files):
207
+ """
208
+ Returns ``True`` all ``files`` are in the same folder, ``False`` otherwise.
209
+
210
+ Example:
211
+ >>> all_files_in_same_dir('/etc/hosts', '/etc/passwd')
212
+ True
213
+ >>> all_files_in_same_dir('/etc/hosts', '/etc/passwd', '/usr/share')
214
+ False
215
+
216
+ """
217
+ pdir = None
218
+ for f in files:
219
+ if pdir is None:
220
+ pdir = dirname(f) or "."
221
+ elif not exists(pdir + os.sep + basename(f)):
222
+ return False
223
+ return True
224
+
225
+
226
+ def glob_files(*files, only_first=True):
227
+ """
228
+ Helper to do a :func:`glob` on each file pattern in ``files``.
229
+
230
+ Args:
231
+ only_first: ``bool``
232
+ If ``True`` (default) returns only the first file matching pattern
233
+ ``files[i]`` for all ``i`` otherwise (``False``) returns all of
234
+ them.
235
+ Return:
236
+ the list of "globbed" files.
237
+
238
+ Example:
239
+ >>> from random import randint
240
+ >>> rand_suffix = str(randint(1, 2**10))
241
+ >>> # create a folder and dummy files
242
+ >>> dir = "test_remove_dirs" + rand_suffix
243
+ >>> dir2 = "test_remove_dirs" + rand_suffix + '2'
244
+ >>> os.mkdir(dir)
245
+ >>> os.mkdir(dir2)
246
+ >>> f1 = os.path.join(dir, "f1")
247
+ >>> f_out = open(f1, 'w')
248
+ >>> f_out.close()
249
+ >>> f2 = os.path.join(dir, "f2")
250
+ >>> f2_out = open(f2, 'w')
251
+ >>> f2_out.close()
252
+ >>> f3 = os.path.join(dir2, "f3")
253
+ >>> f3_out = open(f3, 'w')
254
+ >>> f3_out.close()
255
+ >>> # globbing onfly first file of each globbing
256
+ >>> files = glob_files(dir + '/f*', dir2 + '/f*')
257
+ >>> len(files) == 2
258
+ True
259
+ >>> files[0] == f1
260
+ True
261
+ >>> files[1] == f3
262
+ True
263
+ >>> # globbing all matching files
264
+ >>> files = glob_files(dir + '/f*', dir2 + '/f*', only_first=False)
265
+ >>> len(files) == 3
266
+ True
267
+ >>> files[0] == f1
268
+ True
269
+ >>> files[1] == f2
270
+ True
271
+ >>> files[2] == f3
272
+ True
273
+
274
+ """
275
+ out_files = []
276
+ for f in files:
277
+ gl = glob(f)
278
+ if len(gl) > 0:
279
+ if only_first:
280
+ out_files.append(gl[0])
281
+ else:
282
+ for f in gl:
283
+ out_files.append(f)
284
+ else:
285
+ out_files.append(f)
286
+ return out_files
287
+
288
+
289
+ def force_suffix_folder(dir_path, suffix):
290
+ """
291
+ Enforces path ``dir_path`` to be terminated with folder/file ``suffix``.
292
+
293
+ Return:
294
+ the resulting path (with ``suffix`` appended if not already present).
295
+
296
+ Example:
297
+ >>> dir_path = './my/path'
298
+ >>> force_suffix_folder(dir_path, 'is_here')
299
+ './my/path/is_here'
300
+ >>> dir_path = './my/path/is_here'
301
+ >>> force_suffix_folder(dir_path, 'is_here')
302
+ './my/path/is_here'
303
+ """
304
+ if not re.match(r".*" + suffix + r"/?", dir_path):
305
+ dir_path += os.sep + suffix
306
+ return dir_path
307
+
308
+
309
+ def check_file(file_path, additional_msg=""):
310
+ """
311
+ Checks file ``file_path`` exists.
312
+
313
+ Raises an exception if file doesn't exist.
314
+ Otherwise returns the ``file_path`` passed as argument.
315
+
316
+ Args:
317
+ additional_msg: ``str``
318
+ Message additionally printed if exception raised.
319
+
320
+ .. seealso::
321
+ :func:`.check_files`
322
+
323
+ Example:
324
+ >>> check_file('/etc/passwd')
325
+ '/etc/passwd'
326
+ >>> check_file('nonexistingfileforsureornot')
327
+ Traceback (most recent call last):
328
+ ...
329
+ Exception: Error: the file nonexistingfileforsureornot doesn't exist.
330
+ """
331
+ if not exists(file_path):
332
+ raise Exception("Error: the file " + file_path +
333
+ " doesn't exist." + additional_msg)
334
+ return file_path
335
+
336
+
337
+ def check_files(*files):
338
+ """
339
+ Proceeds to a :func:`.check_file` for each file in ``files``.
340
+
341
+ Return:
342
+ ``None``
343
+
344
+ .. seealso::
345
+ :func:`.check_file`
346
+
347
+
348
+ Example:
349
+ >>> check_files('/etc/passwd', '/etc/hosts')
350
+ >>> check_files('/etc/passwd', '/etc/hosts', 'likelynonexistingfile')
351
+ Traceback (most recent call last):
352
+ ...
353
+ Exception: Error: the file likelynonexistingfile doesn't exist.
354
+
355
+ """
356
+ for f in files:
357
+ check_file(f)
358
+
359
+
360
+ def count_file_lines(file):
361
+ r"""
362
+ Counts the number of lines in ``file``.
363
+
364
+ Return:
365
+ number of lines found.
366
+
367
+ Example:
368
+ >>> from random import randint
369
+ >>> rand_suffix = str(randint(1, 2**10))
370
+ >>> f1 = "f1-" + rand_suffix
371
+ >>> f_out = open(f1, 'w')
372
+ >>> f_out.writelines(["line 1\n", "line 2\n", "line 3\n"])
373
+ >>> f_out.close()
374
+ >>> count_file_lines(f1)
375
+ 3
376
+ >>> remove_files(f1)
377
+
378
+ """
379
+ check_file(file)
380
+ fd = open(file)
381
+ n = len(fd.readlines())
382
+ fd.close()
383
+ return n
384
+
385
+
386
+ def truncate_file(file, nlines):
387
+ r"""
388
+ Truncates ``file`` to ``nlines`` number of lines.
389
+
390
+ Example:
391
+ >>> from random import randint
392
+ >>> rand_suffix = str(randint(1, 2**10))
393
+ >>> f1 = "f1-" + rand_suffix
394
+ >>> f_out = open(f1, 'w')
395
+ >>> f_out.writelines(["line 1\n", "line 2\n", "line 3\n"])
396
+ >>> f_out.close()
397
+ >>> count_file_lines(f1)
398
+ 3
399
+ >>> truncate_file(f1, 1)
400
+ >>> count_file_lines(f1)
401
+ 1
402
+ >>> remove_files(f1)
403
+
404
+ """
405
+ check_file(file)
406
+ fd = open(file)
407
+ lines = fd.readlines()
408
+ fd.close()
409
+ fd = open(file, 'w')
410
+ fd.writelines(lines[:nlines])
411
+ fd.close()
412
+
413
+
414
+ def infer_path_rel_and_abs_cmds(argv):
415
+ r"""
416
+ This function is an helper to define which string (filename or filepath) to
417
+ use to launch a program (``argv[0]``).
418
+
419
+ This function can help to write a USAGE message for a script. Precisely, to
420
+ determine how to print the program (as a filepath or just a name).
421
+
422
+ The string is not the same if the current working directory is the parent
423
+ directory of ``argv[0]`` (a relative path to the program might be used)
424
+ or another directory (an absolute path to the program might be used).
425
+ Besides, the program can also be available from a directory set in the PATH
426
+ environment variable (in this case only the program filename is enough to
427
+ launch it).
428
+
429
+ Args:
430
+ argv: ``list[str]``
431
+ the arguments of the program of interest (see ``sys.argv``).
432
+
433
+ Return: ``tuple(str, str)``
434
+ - ``tuple[0]``: the command to use in current working directory to run
435
+ ``argv[0]``,
436
+ - ``tuple[1]``: the command to use in another directory to run
437
+ ``argv[0]``.
438
+ ``tuple[0]`` can possibly be the same as ``tuple[1]``.
439
+
440
+ Example:
441
+ >>> # for a program in PATH
442
+ >>> cwd_cmd, other_dir_cmd = infer_path_rel_and_abs_cmds(['ls'])
443
+ >>> cwd_cmd == 'ls'
444
+ True
445
+ >>> other_dir_cmd == os.getcwd() + '/ls'
446
+ True
447
+ >>> # for a program in PATH but with absolute path given
448
+ >>> cwd_cmd, odir_cmd = infer_path_rel_and_abs_cmds(['/usr/bin/ls'])
449
+ >>> cwd_cmd == 'ls'
450
+ True
451
+ >>> odir_cmd == 'ls'
452
+ True
453
+ >>> # for a program not in PATH
454
+ >>> script = 'nop232323.py'
455
+ >>> exists(script)
456
+ False
457
+ >>> s = open(script, 'w')
458
+ >>> s.writelines(['#!/usr/bin/env python3\n'])
459
+ >>> s.write('from sys import argv\n')
460
+ 21
461
+ >>> s.write('from py3toolset.fs import infer_path_rel_and_abs_cmds\n')
462
+ 51
463
+ >>> s.writelines(["if __name__ == '__main__':\n"])
464
+ >>> s.writelines([' print(infer_path_rel_and_abs_cmds(argv))\n'])
465
+ >>> s.close()
466
+ >>> os.system("chmod +x "+script)
467
+ 0
468
+ >>> import subprocess
469
+ >>> ret = subprocess.run("./"+script, capture_output=True)
470
+ >>> ret.stdout # doctest:+ELLIPSIS
471
+ b"('./nop232323.py', '/.../nop232323.py')\n"
472
+ >>> # 1st path is ./ because script is in cwd
473
+ >>> # 2nd path is absolute path to use the script from any directory
474
+ >>> remove_files(script)
475
+ """
476
+ prog_name = basename(argv[0])
477
+ if dirname(argv[0]) in environ['PATH'].split(pathsep):
478
+ # the script is in path we don't need to print location in usage
479
+ prog_abs_path = ""
480
+ prog_rel_path = ""
481
+ elif argv[0].startswith(os.path.sep):
482
+ # absolute path
483
+ prog_abs_path = dirname(argv[0])
484
+ prog_rel_path = dirname(argv[0])
485
+ else:
486
+ # relative path
487
+ prog_abs_path = join(getcwd(), dirname(argv[0]))
488
+ prog_abs_path = prog_abs_path.replace(os.path.sep+'.', '')
489
+ prog_rel_path = dirname(argv[0])
490
+ cwd_cmd = join(prog_rel_path, prog_name)
491
+ other_dir_cmd = join(prog_abs_path, prog_name)
492
+ return cwd_cmd, other_dir_cmd
493
+
494
+
495
+ def copy_file_with_rpath(path, targetdir):
496
+ """
497
+ Copies file ``path`` into ``targetdir`` keeping its relative path.
498
+
499
+ For example, ``path = 'dir1/dir2/dir3/file'`` is copied as
500
+ ``targetdir + '/dir1/dir2/dir3/file'
501
+
502
+ ``targetdir`` and intermediate folders are created recursively if needed.
503
+
504
+ Args:
505
+ path: ``str``
506
+ relative path of the file.
507
+ targetdir: ``str``
508
+ target directory to copy the file into (keeping its relative path).
509
+
510
+ Example:
511
+ >>> from random import randint
512
+ >>> rand_suffix = str(randint(1, 2**10))
513
+ >>> # create a folder and dummy files
514
+ >>> parent_dir = "my_dir" + rand_suffix
515
+ >>> os.mkdir(parent_dir)
516
+ >>> f1 = os.path.join(parent_dir, "f1")
517
+ >>> f_out = open(f1, 'w')
518
+ >>> f_out.writelines(["Test test test"])
519
+ >>> f_out.close()
520
+ >>> f2 = os.path.join(parent_dir, "f2")
521
+ >>> f2_out = open(f2, 'w')
522
+ >>> f2_out.writelines(["Test2 test2 test2"])
523
+ >>> f2_out.close()
524
+ >>> target_dir = 'new_dir' + rand_suffix
525
+ >>> copy_file_with_rpath(f2, target_dir)
526
+ >>> exists(os.path.join(target_dir, f2))
527
+ True
528
+ >>> print(os.path.join(target_dir, f2)) # doctest:+ELLIPSIS
529
+ new_dir.../my_dir.../f2
530
+ >>> remove_dirs(target_dir, parent_dir)
531
+
532
+ """
533
+ if path.startswith(sep):
534
+ raise ValueError('path must be relative')
535
+ dpath = join(targetdir, dirname(path))
536
+ folders = dpath.split(sep)
537
+ bpath = ""
538
+ for f in folders:
539
+ bpath += f
540
+ bpath += sep
541
+ if not exists(bpath):
542
+ mkdir(bpath)
543
+ copyfile(path, join(targetdir, path))