#
##
## This file is part of pyFormex 2.0 (Mon Sep 14 12:29:05 CEST 2020)
## pyFormex is a tool for generating, manipulating and transforming 3D
## geometrical models by sequences of mathematical operations.
## Home page: http://pyformex.org
## Project page: http://savannah.nongnu.org/projects/pyformex/
## Copyright 2004-2020 (C) Benedict Verhegghe (benedict.verhegghe@ugent.be)
## Distributed under the GNU General Public License version 3 or later.
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program. If not, see http://www.gnu.org/licenses/.
##
"""Object oriented filesystem paths.
This module defines the Path class which is used throughout pyFormex
for handling filesystem paths.
The Path class provides object oriented handling of filesystem paths.
It has many similarities to the classes in the :mod:`pathlib` module of
Python3. But there are some important differences and these are
mostly inpired by the specialized requirements of pyFormex as opposed
to the more general requirements of Python.
Our Path class derives from Python's :class:`str`. This was almost a necessity,
because pyFormex uses a lot of external programs with their own file
naming rules. Very often we need string methods to build the proper
file names. The constant switching between Path and str is a real
hindrance in using the Python pathlib classes. Because our Path is a
str, it can be used immediately in all places and all functions as
before, which allowed for a gradual transition from using os.path and
other modules to our Path.
Unlike pathlib, we do not discern between pure paths and concrete paths.
The basic usage of a file path in pyFormex is to use the file. There is
hardly a need for file paths besides concrete paths.
While pyFormex is currently almost exclusively used on Linux, the Path
class makes it rather straightforward to port it to other OSes, as all
the path related code is concentrated in a single module.
Despite the differences, there are also quite some similarities with
the pathlib classes. Most of the methods are named likewise, so that
even changing to the use of pathlib could be done inside this single
module.
Our Path class has however a lot more methods available that those of
pathlib. Since we are not bound to pathlib, we can as well move all
path and file related functionally into this module and extend the Path
class with any interesting functionality that has common use.
In order for this module to be of general use however, we have kept
things that are strictly pyFormex oriented out of this module.
This version is for Python3 only. See pyFormex 1.0.7 for a version that
supports both Python2.7 and Python3.x.
"""
import os
import shutil
import tempfile
import re
[docs]class Path(str):
"""
A filesystem path which also behaves like a str.
A Path instance represents a valid path to a file in the filesystem,
existing or not. Path is thus a subclass of str that can only represent
strings that are valid as file paths. The constructor will always
normalize the path.
Parameters
----------
args: :term:`path_like`, ...
One or more path components that will be concatenated to form the
new Path. Each component can be a str or a Path.
It can be relative or absolute. If multiple absolute components are
specified, the last one is used.
The following all create the same Path:
>>> Path('/pyformex/gui/menus')
Path('/pyformex/gui/menus')
>>> Path('/pyformex', 'gui', 'menus')
Path('/pyformex/gui/menus')
>>> Path('/pyformex', Path('gui'), 'menus')
Path('/pyformex/gui/menus')
But this is different:
>>> Path('/pyformex', '/gui', 'menus')
Path('/gui/menus')
Spurious slashes and single and double dots are collapsed:
>>> Path('/pyformex//gui///menus')
Path('/pyformex/gui/menus')
>>> Path('/pyformex/./gui/menus')
Path('/pyformex/gui/menus')
>>> Path('/pyformex/../gui/menus')
Path('/gui/menus')
Note
----
The collapsing of double dots is different from the :mod:`pathlib` behavior.
Our Path class follows the :func:`os.path.normpath` behavior here.
**Operators**:
The slash operator helps create child paths, similarly to
:func:`os.path.join`.
The plus operator can be used to add a trailing part without a slash
separator. The equal operator allows comparing paths.
>>> p = Path('/etc') / 'init.d' / 'apache2'
>>> p
Path('/etc/init.d/apache2')
>>> p + '.d'
Path('/etc/init.d/apache2.d')
>>> p1 = Path('/etc') + '/init.d/apache2'
>>> p1 == p
True
Note
----
Unlike the :mod:`pathlib`, our Path class does not provide the possibility
to join a str and a Path with a slash operator: the first component must
be a Path.
**Properties**:
The following properties give access to different components of the Path:
- :attr:`parts`: a tuple with the various parts of the Path,
- :attr:`parent`: the parent directory of the Path
- :attr:`parents`: a tuple with the subsequent parent Paths
- :attr:`name`: a string with the final component of the Path
- :attr:`suffix`: the file extension of the final component, if any
- :attr:`stem`: the final component without its suffix
- :attr:`lsuffix`: the suffix in lower case
- :attr:`ftype`: the suffix in lower case and without the leading dot
Note
----
We currently do not have the following properties available with
pathlib: drive, root, anchor, suffixes
Examples
--------
>>> Path('/a/b')
Path('/a/b')
>>> Path('a/b//c')
Path('a/b/c')
>>> Path('a/b/./c')
Path('a/b/c')
>>> Path('a/b/../c')
Path('a/c')
>>> Path('a/b/.../c')
Path('a/b/.../c')
>>> Path('//a/b')
Path('//a/b')
>>> Path('///a/b')
Path('/a/b')
>>> p = Path('/etc/init.d/')
>>> p.parts
('/', 'etc', 'init.d')
>>> p.parent
Path('/etc')
>>> p.parents
(Path('/etc'), Path('/'))
>>> p0 = Path('pyformex/gui/menus')
>>> p0.parts
('pyformex', 'gui', 'menus')
>>> p0.parents
(Path('pyformex/gui'), Path('pyformex'), Path('.'))
>>> Path('../pyformex').parents
(Path('..'), Path('.'))
>>> p.name
'init.d'
>>> p.stem
'init'
>>> p.suffix
'.d'
>>> p1 = Path('Aa.Bb')
>>> p1.suffix, p1.lsuffix, p1.ftype
('.Bb', '.bb', 'bb')
>>> p.exists()
True
>>> p.is_dir()
True
>>> p.is_file()
False
>>> p.is_symlink()
False
>>> p.is_absolute()
True
>>> Path('/var/run').is_symlink()
True
"""
def __new__(self, *args):
"""
Create a new Path instance
"""
if len(args) == 1 and isinstance(args[0], Path):
return args[0]
return str.__new__(self, os.path.normpath(os.path.join(*args)))
def __truediv__(self, component):
"""
Defines / operator for joining path components
"""
return Path(os.path.join(self, component))
# We do not define __rtruediv__: use Path('...') / instead
def __add__(self, suffix):
"""
Defines + operator for joining path suffixes
"""
return Path(str(self)+suffix)
def __repr__(self):
"""
String representation of a path
"""
return ''.join(["Path('", self, "')"])
@property
def parts(self):
"""
Split the Path in its components.
Returns
-------
tuple of str
The various components of the Path
Examples
--------
>>> Path('/a/b/c/d').parts
('/', 'a', 'b', 'c', 'd')
>>> Path('a/b//d').parts
('a', 'b', 'd')
>>> Path('a/b/./d').parts
('a', 'b', 'd')
>>> Path('a/b/../d').parts
('a', 'd')
>>> Path('a/b/.../d').parts
('a', 'b', '...', 'd')
"""
parts = []
p = os.path.normpath(self)
while True:
head, tail = os.path.split(p)
if tail:
parts.insert(0, tail)
if head == '':
break
if head[-1] == '/':
parts.insert(0, head)
break
p = head
return tuple(parts)
@property
def parents(self):
"""
Return the parents of the Path.
Returns
-------
tuple of Path
The subsequent parent directories of the Path
"""
parents = [self.parent]
while parents[-1] != '/' and parents[-1] != '.':
parents.append(parents[-1].parent)
return tuple(parents)
@property
def parent(self):
"""
Return the parent directory.
Returns
-------
Path
The parent directory of the Path.
"""
return Path(os.path.dirname(self))
@property
def name(self):
"""Return the final path component.
Returns
-------
str
The final component of the Path.
"""
return os.path.basename(self)
@property
def stem(self):
"""
Return the final path component without its suffix.
Returns
-------
str
The final component of the Path without its :attr:`suffix`.
Examples
--------
>>> Path('aA.bB').stem
'aA'
"""
stem, ext = os.path.splitext(self.name)
if ext == '.':
stem += ext
return stem
@property
def suffix(self):
"""
Return the file extension of the Path component.
The file extension is the last substring of the final component
starting at a dot that is neither the start nor the end
of the component.
Returns
-------
str
The file extension of the Path, including the leading dot.
Examples
--------
>>> Path('aA.bB').suffix
'.bB'
"""
ext = os.path.splitext(self)[1]
if ext == '.':
ext = ''
return ext
@property
def lsuffix(self):
"""
Return the file extension in lower case.
Returns
-------
str
The suffix of the Path, converted to lower case.
Examples
--------
>>> Path('aA.bB').lsuffix
'.bb'
"""
return self.suffix.lower()
@property
def ftype(self):
"""
Return the file extension in lower case and with the leading dot.
Returns
-------
str
The lsuffix of the Path without the leading dot.
Examples
--------
>>> Path('aA.bB').ftype
'bb'
"""
return self.suffix.lower().lstrip('.')
@property
def without_suffix(self):
"""
Return the Path without the suffix.
The file suffix is the last substring of the final component
starting at a dot that is neither the start nor the end
of the component.
Returns
-------
Path
The Path without the suffix.
Notes
-----
This is equivalent to::
self.parent / self.stem
If the path has no suffix, the output is identical to the input.
Examples
--------
>>> f = Path('/dD/aA.bB')
>>> f.without_suffix
Path('/dD/aA')
>>> f.parent / f.stem
Path('/dD/aA')
"""
return Path(os.path.splitext(self)[0])
[docs] def exists(self):
"""
Return True if the Path exists
"""
return os.path.exists(self)
[docs] def is_dir(self):
"""
Return True if the Path exists and is a directory
"""
return os.path.isdir(self)
[docs] def is_file(self):
"""
Return True if the Path exists and is a file
"""
return os.path.isfile(self)
[docs] def is_symlink(self):
"""
Return True if the Path exists and is a symlink
"""
return os.path.islink(self)
[docs] def is_badlink(self):
"""
Return True if the Path exists and is a bad symlink
A bad symlink is a symlink that points to a non-existing file
"""
return os.path.islink(self) and not os.path.exists(self.real())
[docs] def is_absolute(self):
"""
Return True if the Path is absolute.
The Path is absolute if it start with a '/'.
>>> Path('/a/b').is_absolute()
True
>>> Path('a/b').is_absolute()
False
"""
return os.path.isabs(self)
[docs] def with_name(self, name):
"""
Return a new Path with the filename changed.
Parameters
----------
name: str
Name to replace the last component of the Path
Returns
-------
Path
A Path where the last component has been changed to ``name``.
Examples
--------
>>> Path('data/testrun.inp').with_name('testimg.png')
Path('data/testimg.png')
"""
return self.parent / name # noqa
[docs] def with_suffix(self, suffix):
"""
Return a new Path with the suffix changed.
Parameters
----------
suffix: str
Suffix to replace the last component's :meth:`suffix`.
The replacement string will normally start with a dot.
If it doesn't, not dot is added. See Examples.
Returns
-------
Path
A Path where the suffix of the last component has been changed
to ``suffix``.
Examples
--------
>>> Path('data/testrun.inp').with_suffix('.cfg')
Path('data/testrun.cfg')
>>> Path('data/testrun.inp').with_suffix('_1.inp')
Path('data/testrun_1.inp')
"""
return Path(os.path.splitext(self)[0] + suffix)
[docs] def absolute(self):
"""
Return an absolute version of the path.
Returns
-------
Path
The absolute filesystem path of the Path. This also works if the
Path does not exist. It does not resolve symlinks.
See Also
--------
resolve: return an absolute path resolving any symlinks.
Examples
--------
>>> Path('.').absolute() # doctest: +ELLIPSIS
Path('/home/.../pyformex')
>>> Path('something').absolute() # doctest: +ELLIPSIS
Path('/home/.../pyformex/something')
"""
return Path(os.path.abspath(self))
[docs] def resolve(self):
"""Return absolute path resolving all symlinks.
Returns
-------
Path
The absolute filesystem path of the Path, resolving all symlinks.
This also works if any of the Path components does not exist.
Examples
--------
>>> Path('/var/run').resolve()
Path('/run')
>>> Path('something/inside').resolve() # doctest: +ELLIPSIS
Path('/home/.../pyformex/something/inside')
"""
return Path(os.path.realpath(self))
[docs] def expanduser(self):
"""Expand the ~ and ~user in Path.
A leading '~' in the Path is expanded tot the home directory
of the user executing the code. A leading '~user' is expanded
to the home directory of the user named 'user'.
Returns
-------
Path
The Path with ~ and ~user expanded, the latter only if user
exists.
Examples
--------
>>> Path('~').expanduser() # doctest: +ELLIPSIS
Path('/home/...')
>>> Path('~root').expanduser()
Path('/root')
"""
return Path(os.path.expanduser(self))
[docs] def as_uri(self):
"""
Return the Path as an URI.
Returns
-------
str
A string starting with 'file://' followed by the resolved
absolute path of the Path. Also ~ and ~user are expanded
(if user exists).
Examples
--------
>>> Path('~/some/file.html').as_uri() # doctest: +ELLIPSIS
'file:///home/.../some/file.html'
"""
return 'file://' + self.expanduser().resolve()
[docs] def samefile(self, other_file):
"""
Test whether two pathnames reference the same actual file
Parameters
----------
other: :term:`path_like`
Another file path to compare with.
Returns
-------
bool
True if the other file is actually the same file as self.
Examples
--------
>>> Path.home().samefile(Path('~').expanduser())
True
"""
return os.path.samefile(self, other_file)
[docs] def commonprefix(self, *other):
"""
Return the longest common leading part in a list of paths.
Parameters
----------
*other: one or more :term:`path_like`
Other file path(s) to compare with.
Returns
-------
Path
The longest common leading part in all the file paths.
Examples
--------
>>> p = Path('/etc/password')
>>> q = Path('/etc/profile')
>>> p.commonprefix(p,q,'/etc/pam.d')
Path('/etc/p')
>>> p.commonprefix(p,q,'/etc/group')
Path('/etc')
>>> p.commonpath(p,q,'/etc/pam.d')
Path('/etc')
"""
return Path(os.path.commonprefix([self]+list(other)))
[docs] def commonpath(self, *other):
"""
Return the longest common sub-path in a list of paths.
Parameters
----------
*other: one or more :term:`path_like`
Other file path(s) to compare with.
Returns
-------
Path
The longest common sub-path in all the file paths.
Examples
--------
>>> p = Path('/etc/password')
>>> p.commonpath(p,'/etc/pam.d')
Path('/etc')
"""
return Path(os.path.commonpath([self]+list(other)))
[docs] def joinpath(self, *other):
"""
Join two or more path components.
Parameters
----------
*other: one or more :term:`path_like`
Other path components to join to self.
Notes
-----
This alternative to using the slash operator is especially
useful if the components are a computed and/or long sequence.
Examples
--------
>>> home = Path.home()
>>> p1 = home.joinpath('.config', 'pyformex', 'pyformex.conf')
>>> p2 = home / '.config' / 'pyformex' / 'pyformex.conf'
>>> p1 == p2
True
"""
return Path(os.path.join(self, *other))
[docs] def relative_to(self, other):
"""
Return a relative path version of a path.
Parameters
----------
other: :term:`path_like`
Another file path to compare with.
Returns
-------
Path:
Path relative to other pointing to same file as self.
See Also
--------
absolute: make a Path absolute
Examples
--------
>>> p1 = Path('/usr/local/bin')
>>> p2 = p1.relative_to('/usr/bin')
>>> p2
Path('../local/bin')
>>> p2.absolute() # doctest: +ELLIPSIS
Path('/home/.../local/bin')
"""
return Path(os.path.relpath(self, start=other))
[docs] def mkdir(self, mode=0o775, parents=False, exist_ok=False):
"""
Create a directory.
Parameters
----------
mode: int
The mode to be set on the created directory.
parents: bool
If True, nonexisting intermediate directories will also be
created. The default requires all parent directories to exist.
exist_ok: bool
If True and the target already exist and is a directory, it will
be silently accepted. The default (False) will raise an exeption
if the target exists.
"""
if self.exists():
if exist_ok and self.resolve().is_dir():
self.chmod(mode)
else:
raise ValueError("%r exists already" % self)
else:
if parents:
os.makedirs(self, mode)
else:
os.mkdir(self, mode)
[docs] def rmdir(self):
"""
Remove an empty directory.
"""
os.rmdir(self)
[docs] def unlink(self):
"""
Remove an existing file.
"""
os.unlink(self)
[docs] def remove(self):
"""
Remove a file, but silently ignores non-existing"""
if self.exists():
self.unlink()
[docs] def removeTree(self, top=True):
"""
Recursively delete a directory tree.
Parameters
----------
top: bool
If True (default), the top level directory will be removed as well.
If False, the top level directory will be kept, and only its
contents will be removed.
"""
shutil.rmtree(self)
if not top:
self.mkdir(exist_ok=True)
[docs] def move(self, dst):
"""
Rename or move a file or directory
Parameters
----------
dst: :term:`path_like`
Destination path.
Returns
-------
Path
The new Path.
Notes
-----
Changing a directory component will move the file. Moving a file
accross file system boundaries may not work. If the destination
is an existing file, it will be overwritten.
"""
os.replace(self, dst)
return Path(dst)
[docs] def copy(self, dst):
"""
Copy the file under another name.
Parameters
----------
dst: :term:`path_like`
Destination path.
"""
return Path(shutil.copy(self, dst))
[docs] def symlink(self, dst):
"""
Create a symlink for this Path.
Parameters
----------
dst: :term:`path_like`
Path of the symlink, which will point to self if successfully
created.
"""
os.symlink(self, dst)
return Path(dst)
[docs] def touch(self):
"""
Create an empty file or update an existing file's timestamp.
If the file does not exist, it is create as an empty file.
If the file exists, it remains unchanged but its time of last
modification is set to the current time.
"""
self.open('a').close()
[docs] def truncate(self):
"""
Create an empty file or truncate an existing file.
If the file does not exist, it is create as an empty file.
If the file exists, its contents is erased.
"""
self.open('w').close()
[docs] def chmod(self, mode):
"""
Change the access permissions of a file.
Parameters
----------
mode: int
Permission mode to set on the file. This is usually given as
an octal number reflecting the access mode bitfield. Typical
values in a trusted environment are 0o664 for files and 0o775
for directories. If you want to deny all access for others,
the values are 0o660 and 0o770 respectively.
"""
return os.chmod(self, mode)
[docs] def stat(self):
"""
Return the full stat info for the file.
Returns
-------
stat_result
The full stat results for the file Path.
Notes
-----
The return value can be interrogated using Python's stat module.
Often used values can also be got from Path methods :meth:`mtime`,
:meth:`size`, :meth:`owner`, :meth:`group`.
"""
return os.stat(self)
[docs] def mtime(self):
"""
Return the (UNIX) time of last change
"""
return self.stat().st_mtime
[docs] def size(self):
"""
Return the file size in bytes
"""
return self.stat().st_size
[docs] def owner(self):
"""
Return the login name of the file owner.
"""
import pwd
return pwd.getpwuid(self.stat().st_uid).pw_name
[docs] def group(self):
"""
Return the group name of the file gid.
"""
import grp
return grp.getgrgid(self.stat().st_gid).gr_name
[docs] def open(self, mode='r', buffering=-1, encoding=None, errors=None):
"""
Open the file pointed to by the Path.
Parameters are like in Python's built-in :func:`.open` function.
"""
return open(self, mode=mode, buffering=buffering,
encoding=encoding, errors=errors)
[docs] def read_bytes(self):
"""
Open the file in bytes mode, read it, and close the file.
"""
with self.open(mode='rb') as f:
return f.read()
[docs] def write_bytes(self, data):
"""
Open the file in bytes mode, write to it, and close the file.
"""
with self.open('wb') as f:
return f.write(data)
[docs] def read_text(self, encoding=None, errors=None):
"""
Open the file in text mode, read it, and close the file.
"""
with self.open(mode='r', encoding=encoding, errors=errors) as f:
return f.read()
[docs] def write_text(self, text, encoding=None, errors=None):
"""
Open the file in text mode, write to it, and close the file.
Examples
--------
>>> p = Path('my_text_file')
>>> p.write_text('Text file contents')
18
>>> p.read_text()
'Text file contents'
"""
with self.open('w', encoding=encoding, errors=errors) as f:
return f.write(text)
[docs] def walk(self):
"""
Recursively walk through a directory.
This walks top-down through the directory, yielding tuples
``root, dirs, files``, like :func:`os.walk`.
"""
return os.walk(self)
[docs] def scandir(self):
"""
Returns all entries in a directory
"""
return os.scandir(self)
[docs] def dirs(self):
"""
List the subdirectories in a directory path.
Returns
-------
list of str
A list of the names of all directory type entries in the
Path. If the Path is not a directory or not accessible,
an exception is raised.
"""
return sorted([i.name for i in os.scandir(self) if i.is_dir()])
[docs] def files(self):
"""
List the files in a directory path.
Returns
-------
list of str
A list of the names of all file type entries in the
Path. If the Path is not a directory or not accessible,
an exception is raised.
"""
return sorted([i.name for i in os.scandir(self) if i.is_file()])
[docs] def symlinks(self):
"""
Returns all symlinks in a drirectory.
"""
return sorted([i.name for i in os.scandir(self) if i.is_symlink()])
[docs] def glob(self, recursive=False):
"""
Return a list of paths matching a pathname pattern.
For a Path including wildcards (*, ?, [], **), finds all existing
files (of any type, including directories) matching the Path.
The '**' wildcard matches all files and any number of
subdirectories.
Returns
-------
list of Path
A sorted list of existing files matching the pattern.
Note
----
This method works differently from :meth:`pathlib.Path.glob`.
The wildcards are part of the calling input Path and operation is
like that of :func:`glob.glob`.
Parameters
----------
recursive: bool
If True, operation is recursive and a '**' wildcard matches
all files and zero or any subdirectories.
See Also
--------
listTree: find files matching regular expressions
Examples
--------
>>> Path('/etc/init.d').glob()
[Path('/etc/init.d')]
>>> Path('pyformex/pr*.py').glob()
[Path('pyformex/process.py'), Path('pyformex/project.py')]
>>> Path('pyformex/**/pa*.py').glob()
[Path('pyformex/plugins/partition.py')]
>>> Path('pyformex/**/pa*.py').glob(recursive=True)
[Path('pyformex/path.py'), Path('pyformex/plugins/partition.py')]
>>> Path('**/q*.py').glob()
[]
>>> Path('**/q*.py').glob(recursive=True)
[Path('pyformex/gui/qtutils.py')]
"""
import glob
return sorted([Path(i) for i in glob.glob(self, recursive=recursive)])
[docs] def listTree(self, listdirs=False, topdown=True, sorted=False,
excludedirs=[], excludefiles=[],
includedirs=[], includefiles=[], symlinks=True):
"""
List recursively all files matching some regular expressions.
This scans a directory tree for all files matching specified
regular expressions.
Parameters
----------
listdirs: bool
If True, matching directories are listed as well. The default
is to only list files.
topdown: bool
If True (default) the directories are scanned top down, and files
are listed in that order.
sorted: bool
If True, directories on the same level and files within a
directory, will be sorted. The default is to treat items on the
same level in an undefined order.
excludedirs: :term:`re` or list of re's
Regular expression(s) for dirnames to exclude from the tree scan.
excludefiles: :term:`re`
Regular expression(s) for filenames to exclude from the file list.
includedirs: :term:`re` or list of re's, optional
Regular expression(s) for dirnames to include in the tree scan.
includefiles: :term:`re`
Regular expression(s) for filenames to include in the file list.
symlinks: bool
If True, symlinks are included in the listed results. If False,
symlinks are removed.
Returns
-------
list of str
The list of all existing file names (and possibly directory names)
under the Path that satisfy the provided patterns. An exception
is raised if the Path is not an existing directory.
Notes
-----
If neither exclude nor include patterns are provided,
all subdirectories are scanned and all files are reported.
If only exclude patterns are provided, all directories and files
except those matching any of the exclude patterns.
If only include patterns are provided, only those matching at least
on of the patterns are included.
If both exclude and include patterns are provided, items are only
listed if they match at least one of the include patterns but none
of the exclude patterns
The use of ``excludedirs`` and/or ``includedirs`` forces top down
handling.
"""
def force_list(s):
"""Force a parameter to be a sequence (if it is a string"""
return (s,) if isinstance(s, (str, bytes)) else s
excludedirs = force_list(excludedirs)
includedirs = force_list(includedirs)
excludefiles = force_list(excludefiles)
includefiles = force_list(includefiles)
filelist = []
if excludedirs or includedirs:
topdown = True
for root, dirs, files in os.walk(self, topdown=topdown):
if sorted:
dirs.sort()
files.sort()
if excludedirs:
remove = [d for d in dirs if matchAny(d, *excludedirs)]
for d in remove:
dirs.remove(d)
if includedirs:
remove = [d for d in dirs if not matchAny(d, *includedirs)]
for d in remove:
dirs.remove(d)
if listdirs and topdown:
filelist.append(root)
if excludefiles:
files = [f for f in files if not matchAny(f, *excludefiles)]
if includefiles:
files = [f for f in files if matchAny(f, *includefiles)]
filelist.extend([root/f for f in files])
if listdirs and not topdown:
filelist.append(root)
if not symlinks:
filelist = [f for f in filelist if not os.path.islink(f)]
return filelist
[docs] def filetype(self, compressed=['.gz', '.bz2']):
"""
Return a normalized file type based on the filename suffix.
The returned suffix is in most cases the part of the filename starting
at the last dot. However, if the thus obtained suffix is one of the
specified compressed types (default: .gz or .bz2) and the file contains
another dot that is not at the start of the filename, the returned suffix
starts at the penultimate dot.
This allows for transparent handling of compressed files.
Parameters
----------
compressed: list of str
List of suffixes that are considered compressed file types.
Returns
-------
str
The filetype. This is the file suffix converted to lower case
and without the leading dot. If the suffix is included in
``compressed``, the returned suffix also includes the preceding
suffix part, if any.
See Also
--------
ftype: the file type without accounting for compressed types
Examples
--------
>>> Path('pyformex').filetype()
''
>>> Path('pyformex.pgf').filetype()
'pgf'
>>> Path('pyformex.pgf.gz').filetype()
'pgf.gz'
>>> Path('pyformex.gz').filetype()
'gz'
>>> Path('abcd/pyformex.GZ').filetype()
'gz'
"""
ext = self.lsuffix
if ext in compressed:
ext1 = Path(self.stem).lsuffix
if ext1:
ext = ext1+ext
return ext.lstrip('.')
[docs] def ftype_compr(self, compressed=['.gz', '.bz2']):
"""
Return the file type and compression based on suffix.
Parameters
----------
compressed: list of str
List of suffixes that are considered compressed file types.
Returns
-------
ftype: str
File type based on the last suffix if it is not a compression
type, or on the penultimate suffix if the file is compressed.
compr: str
Compression type. This is the last suffix if it is one of the
compressed types, or an empty string otherwise.
Examples
--------
>>> Path('pyformex').ftype_compr()
('', '')
>>> Path('pyformex.pgf').ftype_compr()
('pgf', '')
>>> Path('pyformex.pgf.gz').ftype_compr()
('pgf', 'gz')
>>> Path('pyformex.gz').ftype_compr()
('gz', '')
"""
ftype = self.filetype(compressed)
i = ftype.find('.')
if i >= 0:
return ftype[:i], ftype[i+1:]
else:
return ftype, ''
[docs] @staticmethod
def cwd():
"""
Return the current working directory
Returns
-------
Path
The current working directory.
"""
return Path(os.getcwd())
[docs] @staticmethod
def home():
"""
Return the user's home directory.
Returns
-------
Path
The user's home directory as defined by the environment
variable HOME.
"""
return Path(os.environ['HOME'])
[docs]def matchAny(target, *regexps):
"""Check whether target matches any of the regular expressions.
Parameters
----------
target: str
String to match with the regular expressions.
*regexp: sequence of regular expressions.
The regular expressions to match the target string.
Returns
-------
bool
True, if target matches at least one of the provided regular
expressions. False if no matches.
Examples
--------
>>> matchAny('test.jpg', '.*[.]png', '.*[.]jpg')
True
>>> matchAny('test.jpeg', '.*[.]png', '.*[.]jpg')
False
>>> matchAny('test.jpg')
False
"""
for r in regexps:
if re.match(r, target):
return True
return False
if __name__ == "__main__":
import doctest
doctest.testmod()