# -*- coding: utf-8 -*-
"""*yapyutils.files.finder* provides file locations.
"""
import sys
import os
import re
import fnmatch
from yapyutils.files import YapyUtilsFilesError
__author__ = 'Arno-Can Uestuensoez'
__license__ = "Artistic-License-2.0 + Forced-Fairplay-Constraints"
__copyright__ = "Copyright (C) 2019 Arno-Can Uestuensoez" \
" @Ingenieurbuero Arno-Can Uestuensoez"
__version__ = '0.1.1'
__uuid__ = "60cac28d-efe6-4a8d-802f-fa4fc94fa741"
__docformat__ = "restructuredtext en"
[docs]class YapyUtilsFinderError(YapyUtilsFilesError):
pass
_debug = 0
_verbose = 0
[docs]def splitfile(fpname):
"""Splits filename from it's pathname.
Args:
fpname:
The file path name to be split.
Returns:
The splitted file path name::
ret := (<pathname>, <filename>)
In case of directory input::
ret := (<pathname>, '')
Raises:
YapyUtilsFinderError
pass-through
"""
if not os.path.exists(fpname):
raise YapyUtilsFinderError('requires existing node: ' + str(fpname))
if os.path.isfile(fpname):
return os.path.split(fpname)
return (fpname, '')
[docs]def get_filelocation(fname, spaths=None):
"""A very basic function for the detection of the absolute path and
the relative module search-path-name for a given path of a module.
The values are the same as would be present in *sys.modules*.
Supports source modules as input only.
The *platformids* is a low-level library within the software stack.
The generic functions for the allocation of module sources and binaries
are provided by *sourceinfo* [sourceinfo]_, which itself depends on
the *platformids*. Thus *sourceinfo* could not be used in order to avoid
circular dependencies. So implemented this function to keep *platformids*
on lowest possible software-stack level only.
Args:
fname:
The relative path of the module in dotted *Python* notation, without
file suffix.
.. parsed-literal::
mname := (
<dotted-module-name-str>
| <dotted-module-name-path-name-str>
)
spaths:
List of search paths,
.. parsed-literal::
default := sys.path + os.environ['PATH'].split(os.pathsep)
Returns:
Returns in case of a match the resulting entry within *sys.modules*:
.. parsed-literal::
match -> (<abs-file-path>, <file-name>,)
The default when no match occured is to rely on the more versatile
search mechanism of the import implementation of the concrete
*Python* implementation for another final trial by the caller:
.. parsed-literal::
default -> ('', <fime-name>,)
Raises:
PlatformIDsError
'mbase' does not match 'mpaths'
PlatformIDsPresentError
missing 'mbase'
pass-through
"""
if os.path.isabs(fname):
if os.path.exists(fname):
return os.path.split(fname)
return ('', '')
elif spaths == None:
# switch to (syspath + PATH) by default
for s in ( os.getcwd(),) + sys.path + os.environ['PATH'].split(os.pathsep):
if os.path.exists(s + os.sep + fname):
return os.path.split(s + os.sep + fname)
else:
raise YapyUtilsFinderError("Cannot find file: '" + str(fname) + "'")
else:
# switch to (syspath + PATH) by default
for s in spaths:
if os.path.exists(s + os.sep + fname):
return os.path.split(s + os.sep + fname)
else:
raise YapyUtilsFinderError("Cannot find file: '" + str(fname) + "'")
[docs]def get_filesysposition(rfpname, toppath=None, spaths=None,):
"""Loads the specified module by it's name and file system path.
Provides a common interface for all supported platforms and Python
implementations: *CPyhton*, *IPython*, *IronPython*, *Jython*,
and *PyPy*. For the syntaxversions *Python2.7* and *Python3*.
Args:
rfpname:
File path name of file to be searched, which is either
absolute, or relative. An absolute file path is vallidated
only, while a relative is searched within the set of *spaths*,
which are resolved iterative from longest upward.
The limiting top node is *toppath* as the highest valid hook
for 'rfpname' as subpath.
toppath:
The constraint of the top level for the search operation.
spaths:
The list of search paths as insertion points of the subpath
'rfpname'::
default := [ os.getcwd(),] \\
+ sys.path \\
+ os.environ['PATH'].split(os.pathsep)
Returns:
The module object on success::
res := (<path-name>, <filename>)
else::
res := ('', '')
Raises:
PlatformIDsError
pass-through
"""
rfpname = os.path.normpath(rfpname)
toppath = os.path.normpath(toppath)
if os.path.isabs(rfpname):
if toppath and not rfpname.startswith(toppath):
raise YapyUtilsFinderError(
"toppath is not in path:\n %s\n %s'" % (str(rfpname), str(toppath),)
)
if os.path.exists(rfpname):
return splitfile(rfpname)
return ('', '')
elif spaths == None:
# switch to (syspath + PATH) by default
for s in [ os.getcwd(),] + sys.path + os.environ['PATH'].split(os.pathsep):
if toppath and not (s + os.sep + rfpname).startswith(toppath + os.sep):
continue
if os.path.exists(s + os.sep + rfpname):
return splitfile(s + os.sep + rfpname)
else:
_s = s
_f = _s.split(os.sep)
for sx in range(len(_f)):
if os.path.exists(_s):
return splitfile(_s)
_s = re.sub(r'[\\\\/]*[^\\\\/]$', '', _s)
else:
raise YapyUtilsFinderError("Cannot find file: '" + str(rfpname) + "'")
else:
for s in spaths:
if toppath and not s.startswith(toppath):
continue
if os.path.exists(s + os.sep + rfpname):
return splitfile(s + os.sep + rfpname)
else:
_s = s
_f = re.split(os.sep, s)
for sx in range(len(_f)):
if os.path.exists(_s + os.sep + rfpname):
return splitfile(_s + os.sep + rfpname)
_s = re.sub(r'[\\\\/]*?[^\\\\/]*$', '', _s)
else:
raise YapyUtilsFinderError("Cannot find file: '" + str(rfpname) + "'")
[docs]def find_files(srcdir, *wildcards, **kargs):
"""Assembles a list of package files for package_files.
Args:
srcdir:
Source root.
\*wildcards:
List of globs.
kargs:
single_level:
Flat only.
subpath:
Cut topmost path elemenr from listelements,
special for dictionaries.
nopostfix:
Drop filename postfix.
packages:
List packages only, else files.
yield_folders:
List folders only.
Returns:
Results in an list.
Raises:
pass-through
"""
ret=[]
single_level = kargs.get('single_level', False)
subpath = kargs.get('subpath', False)
nopostfix = kargs.get('nopostfix', False)
packages = kargs.get('packages', False)
yield_folders = kargs.get('yield_folders', True)
blacklist = kargs.get('blacklist', ('.gitignore', '.git', '.svn'))
for path, subdirs, files in os.walk(srcdir):
if yield_folders:
files.extend(subdirs)
if subpath:
path=re.sub(r'^[^'+os.sep+']*'+os.sep, '',path)
for name in sorted(files):
if name in blacklist:
continue
for pattern in wildcards:
if fnmatch.fnmatch(name, pattern):
if packages:
if not name == '__init__.py':
continue
ret.append(path)
continue
if nopostfix:
name=os.path.splitext(name)[0]
ret.append(os.path.join(path, name))
if single_level:
break
return ret