This is the first patch of a series which merges a pure python implementation of the kernel-doc parser. It adds the prerequisites which are needed by the pure python implementation (which comes later in the series). The fspath module in this patch is a reduced implementation of pypi's fspath [1]. As an alternative to this patch we can add an external dependence which can be installed from PyPi with 'pip install fspath' see [2] (but I guess we won't more external dependencies). [1] https://pypi.python.org/pypi/fspath/ {2] https://return42.github.io/fspath/ Signed-off-by: Markus Heiser <markus.heiser@xxxxxxxxxxx> --- Documentation/sphinx/fspath.py | 435 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 435 insertions(+) create mode 100644 Documentation/sphinx/fspath.py diff --git a/Documentation/sphinx/fspath.py b/Documentation/sphinx/fspath.py new file mode 100644 index 0000000..554b8a2 --- /dev/null +++ b/Documentation/sphinx/fspath.py @@ -0,0 +1,435 @@ +# -*- coding: utf-8; mode: python -*- +u""" +Handling path names and executables more comfortable. +""" + +# ============================================================================== +# imports +# ============================================================================== + +import io +import os +from os import path +import re +import shutil +import subprocess +import sys +from glob import iglob + +import six + +# ============================================================================== +class FSPath(six.text_type): +# ============================================================================== + + u""" + A path name to a file or folder. + + Handling path names more comfortable, e.g.: + + * concatenate path names with the division operator ``/`` + * call functions like *mkdir* on path names + * get properties like *EXISTS* + + .. code-block:: python + + >>> folder = fspath.FSPath("/tmp") + >>> folder.EXISTS + True + >>> (folder / "subfolder").makedirs() + >>> list(folder.reMatchFind(".*sub")) + ['/tmp/subfolder'] + >>> (folder / "test.txt").FILENAME + 'test' + >>> (folder / "test.txt").DIRNAME + '/tmp' + >>> six.print_("topfolder" / folder) + topfolder/temp + >>> six.print_(folder + "addedstr") + tmpaddedstr + >>> six.print_((folder/"foo"/"bar.txt").splitpath()) + (u'/tmp/foo', u'bar.txt') + >>> six.print_(folder / "foo" / "../bar.txt") + tmp/bar.txt + """ + + def __new__(cls, pathname): + u"""Constructor of a path name object. + + Regardless of how the encoding of the file system is, the ``pathname`` + is converted to unicode. The conversion of byte strings is based the + default encodings. + + To issue "File-system Encoding" See also: + + * https://docs.python.org/3.5/howto/unicode.html#unicode-filenames + """ + pathname = path.normpath(path.expanduser(six.text_type(pathname))) + return super(FSPath, cls).__new__(cls, pathname) + + @property + def VALUE(self): + u"""string of the path name""" + return six.text_type(self) + + @property + def EXISTS(self): + u"""True if file/pathname exist""" + return path.exists(self) + + @property + def SIZE(self): + u"""Size in bytes""" + return path.getsize(self) + + @property + def isReadable(self): + u"""True if file/path is readable""" + return os.access(self, os.R_OK) + + @property + def isWriteable(self): + u"""True if file/path is writeable""" + return os.access(self, os.W_OK) + + @property + def isExecutable(self): + u"""True if file is executable""" + return os.access(self, os.X_OK) + + @property + def ISDIR(self): + u"""True if path is a folder""" + return path.isdir(self) + + @property + def ISFILE(self): + u"""True if path is a file""" + return path.isfile(self) + + @property + def ISLINK(self): + u"""True if path is a symbolic link""" + return path.islink(self) + + @property + def DIRNAME(self): + u"""The path name of the folder, where the file is located + + E.g.: ``/path/to/folder/filename.ext`` --> ``/path/to/folder`` + """ + return self.__class__(path.dirname(self)) + + @property + def BASENAME(self): + u"""The path name with suffix, but without the folder name. + + E.g.: ``/path/to/folder/filename.ext`` --> ``filename.ext`` + """ + return self.__class__(path.basename(self)) + + @property + def FILENAME(self): + u"""The path name without folder and suffix. + + E.g.: ``/path/to/folder/filename.ext`` --> ``filename`` + + """ + return self.__class__(path.splitext(path.basename(self))[0]) + + @property + def SUFFIX(self): + u"""The filename suffix + + E.g.: ``/path/to/folder/filename.ext`` --> ``.ext`` + + """ + return self.__class__(path.splitext(self)[1]) + + @property + def SKIPSUFFIX(self): + u"""The complete file name without suffix. + + E.g.: ``/path/to/folder/filename.ext`` --> ``/path/to/folder/filename`` + """ + return self.__class__(path.splitext(self)[0]) + + @property + def ABSPATH(self): + u"""The absolute pathname + + E.g: ``../to/../to/folder/filename.ext`` --> ``/path/to/folder/filename.ext`` + + """ + return self.__class__(path.abspath(self)) + + @property + def REALPATH(self): + u"""The real pathname without symbolic links.""" + return self.__class__(path.realpath(self)) + + @property + def POSIXPATH(self): + u"""The path name in *POSIX* notation. + + Helpfull if you are on MS-Windows and need the POSIX name. + """ + if os.sep == "/": + return six.text_type(self) + else: + p = six.text_type(self) + if p[1] == ":": + p = "/" + p.replace(":", "", 1) + return p.replace(os.sep, "/") + + @property + def NTPATH(self): + u"""The path name in the Windows (NT) notation. + """ + if os.sep == "\\": + return six.text_type(self) + else: + return six.text_type(self).replace(os.sep, "\\") + + @property + def EXPANDVARS(self): + u"""Path with environment variables expanded.""" + return self.__class__(path.expandvars(self)) + + @property + def EXPANDUSER(self): + u"""Path with an initial component of ~ or ~user replaced by that user's home.""" + return self.__class__(path.expanduser(self)) + + @classmethod + def getHOME(cls): + u"""User's home folder.""" + return cls(path.expanduser("~")) + + def makedirs(self, mode=0o775): + u"""Recursive directory creation, default mode is 0o775 (octal).""" + if not self.ISDIR: + return os.makedirs(self, mode) + + def __div__(self, pathname): + return self.__class__(self.VALUE + os.sep + six.text_type(pathname)) + __truediv__ = __div__ + + def __rdiv__(self, pathname): + return self.__class__(six.text_type(pathname) + os.sep + self.VALUE) + + def __add__(self, other): + return self.__class__(self.VALUE + six.text_type(other)) + + def __radd__(self, other): + return self.__class__(six.text_type(other) + self.VALUE) + + def relpath(self, start): + return self.__class__(path.relpath(self, start)) + + def splitpath(self): + head, tail = path.split(self) + return (self.__class__(head), self.__class__(tail)) + + def listdir(self): + for name in os.listdir(self): + yield self.__class__(name) + + def glob(self, pattern): + for name in iglob(self / pattern ): + yield self.__class__(name) + + def walk(self, topdown=True, onerror=None, followlinks=False): + for dirpath, dirnames, filenames in os.walk(self, topdown, onerror, followlinks): + yield (self.__class__(dirpath), + [self.__class__(x) for x in dirnames], + [self.__class__(x) for x in filenames] ) + + def reMatchFind(self, name, isFile=True, isDir=True, followlinks=False): + + # find first e.g: next(myFolder.reMatchFind(r".*name.*"), None) + + name_re = re.compile(name) + for folder, dirnames, filenames in self.walk(followlinks=followlinks): + if isDir: + for d_name in [x for x in dirnames if name_re.match(x)]: + yield folder / d_name + if isFile: + for f_name in [x for x in filenames if name_re.match(x)]: + yield folder / f_name + + def suffix(self, newSuffix): + return self.__class__(self.SKIPSUFFIX + newSuffix) + + def copyfile(self, dest, preserve=False): + u"""Copy the file src to the file or directory dest. + + Argument preserve copies permission bits. + """ + if preserve: + shutil.copy2(self, dest) + else: + shutil.copy(self, dest) + + def copytree(self, dest, symlinks=False, ignore=None): + u"""Recursively copy the entire directory tree""" + shutil.copytree(self, dest, symlinks, ignore) + + def move(self, dest): + u"""Move path to another location (dest)""" + shutil.move(self, dest) + + def delete(self): + u"""remove file/folder""" + if self.ISDIR: + self.rmtree() + else: + os.remove(self) + + def rmtree(self, ignore_errors=False, onerror=None): + u"""remove tree""" + shutil.rmtree(self, ignore_errors, onerror) + + def openTextFile( + self, mode = 'r', encoding = 'utf-8' + , errors = 'strict', buffering = 1 + , newline = None): + u"""Open file as text file""" + return io.open( + self, mode=mode, encoding=encoding + , errors=errors, buffering=buffering + , newline=newline) + + def readFile(self, encoding='utf-8', errors='strict'): + u"""read entire file""" + with self.openTextFile(encoding=encoding, errors=errors) as f: + return f.read() + + def Popen(self, *args, **kwargs): + u"""Get a ``subprocess.Popen`` object (``proc``). + + The path name of the self-object is the programm to call. The program + arguments are given py ``*args`` and the ``*kwargs`` are passed to the + ``subprocess.Popen`` constructor. The ``universal_newlines=True`` is + true by default. + + see https://docs.python.org/3/library/subprocess.html#popen-constructor + + .. code-block:: python + + import six + from fspath import FSPath + proc = FSPath("arp").Popen("-a",) + stdout, stderr = proc.communicate() + retVal = proc.returncode + six.print_("stdout: %s" % stdout) + six.print_("stderr: %s" % stderr) + six.print_("exit code = %d" % retVal) + + """ + + defaults = { + 'stdout' : subprocess.PIPE, + 'stderr' : subprocess.PIPE, + 'stdin' : subprocess.PIPE, + 'universal_newlines' : True + } + defaults.update(kwargs) + return subprocess.Popen([self,] + list(args), **defaults) + + +# ============================================================================== +def which(fname, findall=True): +# ============================================================================== + u""" + Searches the fname in the enviroment ``PATH``. + + This *which* is not POSIX conform, it searches the fname (without extension) + and the fname with one of the ".exe", ".cmd", ".bat" extension. If nothing + is found, ``None` is returned if something matches, a list (``set``) is + returned. With option ``findall=False`` the first match is returned or + ``None``, if nothing is found. + """ + exe = ["", ".exe", ".cmd", ".bat"] + if sys.platform != 'win32': + exe = [""] + envpath = os.environ.get('PATH', None) or os.defpath + + locations = set() + for folder in envpath.split(os.pathsep): + for ext in exe: + fullname = FSPath(folder + os.sep + fname + ext) + if fullname.ISFILE: + if not findall: + return fullname + locations.add(fullname) + return locations or None + +# ============================================================================== +def callEXE(cmd, *args, **kwargs): +# ============================================================================== + + u""" + Synchronous command call ``cmd`` with arguments ``*args`` . + + The ``*kwargs`` are passed to the ``subprocess.Popen`` constructor. The + return value is a three-digit tuple ``(stdout, stderr, rc)``. + + .. code-block:: python + + import six + from fspath import callEXE + out, err, rc = callEXE("arp", "-a") + + six.print_("stdout: %s" % out) + six.print_("stderr: %s" % err) + six.print_("exit code = %d" % rc) + """ + + exe = which(cmd, findall=False) + if exe is None: + raise IOError('command "%s" not availble!' % cmd) + proc = exe.Popen(*args, **kwargs) + stdout, stderr = proc.communicate() + rc = proc.returncode + return (stdout, stderr, rc) + + +# ============================================================================== +class DevNull(object): # pylint: disable=R0903 +# ============================================================================== + + """A dev/null file descriptor.""" + def write(self, *args, **kwargs): + pass + +DevNull = DevNull() + +# ============================================================================== +class OS_ENV(dict): +# ============================================================================== + + u""" + Environment object (singleton). + + .. code-block:: python + + >>> if OS_ENV.get("SHELL") is None: + OS_ENV.SHELL = "/bin/bash" + >>> OS_ENV.MY_NAME + '/bin/bash' + """ + @property + def __dict__(self): + return os.environ + + def __getattr__(self, attr): + return os.environ[attr] + + def __setattr__(self, attr, val): + os.environ[attr] = val + + def get(self, attr, default=None): + return os.environ.get(attr, default) + +OS_ENV = OS_ENV() -- 2.7.4 -- To unsubscribe from this list: send the line "unsubscribe linux-doc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html