Source code for autils.file.path

# 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 2 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 LICENSE for more details.
#
# Copyright: Red Hat Inc. 2013-2014
# Author: Yiqiao Pu <ypu@redhat.com>

__doc__ = """
Module to handle file and directory paths.
It provides functions to manipulate file and directory paths,
check permissions, and inspect files.
"""

import os
import stat
import tempfile
import urllib

#: The indicator for a script file, usually the first line of the file.
SHEBANG = "#!"


[docs] class CmdNotFoundError(Exception): """Indicates that the command was not found in the system after a search. :param cmd: String with the command. :param paths: List of paths where we looked after. """ def __init__(self, cmd, paths): # pylint: disable=W0231 super() self.cmd = cmd self.paths = paths def __str__(self): """String representation of the exception. :return: A string describing the missing command and the paths searched. :rtype: str """ return ( f"Command '{self.cmd}' could not be found in any " f"of the PATH dirs: {self.paths}" )
[docs] def get_path(base_path, user_path): """Translate a user specified path to a real path. If user_path is relative, append it to base_path. If user_path is absolute, return it as is. :param base_path: The base path of relative user specified paths. :param type base_path: str :param user_path: The user specified path. :type user_path: str :return: The resolved path. :rtype: str """ if os.path.isabs(user_path) or urllib.parse.urlparse(user_path)[0] in [ "http", "https", "ftp", "file", ]: return user_path return os.path.join(base_path, user_path)
[docs] def init_dir(*args): """Wrapper around os.path.join that creates dirs based on the final path. :param args: List of dir arguments that will be os.path.joined. :return: directory. :rtype: str """ directory = os.path.join(*args) if not os.path.isdir(directory): os.makedirs(directory, exist_ok=True) return directory
[docs] def find_command(cmd, default=None, check_exec=True): """Try to find a command in the PATH, paranoid version. :param cmd: Command to be found. :type cmd: str :param default: Command path to use as a fallback if not found in the standard directories. :type default: str or None :param check_exec: if a check for permissions that render the command executable by the current user should be performed. :type check_exec: bool :raise avocado.utils.path.CmdNotFoundError: in case the command was not found and no default was given. :return: Returns an absolute path to the command or the default value if the command is not found :rtype: str """ common_bin_paths = [ "/usr/libexec", "/usr/local/sbin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin", ] try: path_paths = os.environ["PATH"].split(":") except KeyError: path_paths = [] path_paths = list(set(common_bin_paths + path_paths)) for dir_path in path_paths: cmd_path = os.path.join(dir_path, cmd) if os.path.isfile(cmd_path): if check_exec: if not os.access(cmd_path, os.R_OK | os.X_OK): continue return os.path.abspath(cmd_path) if default is not None: return default path_paths.sort() raise CmdNotFoundError(cmd, path_paths)
[docs] class PathInspector: """Inspects paths to provide information about them. :param path: The path to inspect. :type path: str """ def __init__(self, path): self.path = path
[docs] def get_first_line(self): """Reads and returns the first line of the file from path. :return: The first line of the file or an empty string if the file does not exist or is empty. :rtype: str """ first_line = "" if os.path.isfile(self.path): with open(self.path, "r", encoding="utf-8") as open_file: first_line = open_file.readline() return first_line
[docs] def has_exec_permission(self): """Checks if the file from path has execute permissions for the user. :return: True if the file has execute permissions, False otherwise. :rtype: bool """ if os.path.exists(self.path): mode = os.stat(self.path)[stat.ST_MODE] return mode & stat.S_IXUSR return False
[docs] def is_empty(self): """Checks if the file in path is empty. :return: True if the file is empty, False otherwise. :rtype: bool """ if os.path.exists(self.path): size = os.stat(self.path)[stat.ST_SIZE] return not size return False
[docs] def is_script(self, language=None): """Checks if the file in the path is a script, optionally checking for a specific language. :param language: The scripting language to check for (e.g., "python"). If None, checks for any shebang. :type language: str, optional :return: True if the file is a script (and matches the language, if provided), False otherwise. :rtype: bool """ first_line = self.get_first_line() if first_line: if first_line.startswith(SHEBANG): if language is None: return True if language in first_line: return True return False
[docs] def is_python(self): """Checks if the file in path is a Python script. :return: True if the file is a Python script, False otherwise. :rtype: bool """ if self.path.endswith(".py"): return True return self.is_script(language="python")
[docs] def usable_rw_dir(directory, create=True): """Verify whether we can use this dir (read/write). Checks for appropriate permissions, and creates missing dirs as needed. :param directory: Directory to check. :type directory: str :param create: whether to create the directory if it does not exist. :type create: bool :return: True if the directory is usable for rw, False otherwise. :rtype: bool """ if os.path.isdir(directory): try: fd, path = tempfile.mkstemp(dir=directory) os.close(fd) os.unlink(path) return True except OSError: pass elif create: try: init_dir(directory) return True except OSError: pass return False
[docs] def usable_ro_dir(directory): """Verify whether dir exists and we can access its contents. Check if a usable RO directory is there. :param directory: Directory to check. :type directory: str :return: True if the directory is accessible, False otherwise. :rtype: bool """ try: cwd = os.getcwd() except FileNotFoundError: return False if os.path.isdir(directory): try: os.chdir(directory) os.chdir(cwd) return True except OSError: pass return False
[docs] def check_readable(path): """Verify that the given path exists and is readable. This should be used where an assertion makes sense, and is useful because it can provide a better message in the exception it raises. :param path: the path to test :type path: str :raise OSError: path does not exist or path could not be read """ if not os.path.exists(path): raise OSError(f'File "{path}" does not exist') if not os.access(path, os.R_OK): raise OSError(f'File "{path}" can not be read')
[docs] def get_path_mount_point(path): """Returns the mount point for a given file path. :param path: the complete filename path. if a non-absolute path is given, it's transformed into an absolute path first. :type path: str :returns: the mount point for a given file path :rtype: str """ path = os.path.abspath(path) while not os.path.ismount(path): path = os.path.dirname(path) return path
[docs] def get_max_file_name_length(path): """Returns the maximum length of a file name in the underlying file system. :param path: the complete filename path. if a non-absolute path is given, it's transformed into an absolute path first. :type path: str :returns: the maximum length of a file name :rtype: int """ if hasattr(os, "pathconf"): mount_point = get_path_mount_point(path) return os.pathconf(mount_point, "PC_NAME_MAX") # Given the unavailability of os.pathconf(), always available # under Unix, it should be safe to assume this is Windows. # About Windows, versions and configurations can yield different # file name length limits. The value hardcoded here (248) is # calculated from the 260 MAX_PATH limit, plus the provision # for directories names allowing a 8.3 filename inside it. return 248