Source code for GLXShell.plugins.builtins.cd

import os
import cmd2

# Inspired from: http://pwet.fr/man/linux/commandes/posix/cd/
# Inspired by: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/cd.html

cd_parser = cmd2.Cmd2ArgumentParser(
    prog="cd",
    description="Change the working directory",
)

cd_parser.add_argument(
    "directory",
    nargs="?",
    const=0,
    help="An absolute or relative pathname of the directory that shall become the new working directory.",
)
cd_parser.add_argument(
    "-P",
    dest="physical",
    action="store_true",
    default=False,
    help="Symbolic link components shall be resolved before dot-dot components are processed",
)
cd_parser.add_argument(
    "-L",
    dest="logical",
    action="store_true",
    default=False,
    help="Symbolic link components shall not be resolved before dot-dot components are processed",
)


[docs]class GLXCd(cmd2.CommandSet): def __init__(self): super().__init__() self.__curpath = None self.__directory = None self.__logical = None self.__physical = None self.__can_continue = None self.curpath = None self.directory = None self.logical = None self.physical = None self.can_continue = None @property def curpath(self): """ ``curpath`` represents an intermediate value used to simplify the description of the algorithm used by cd. :return: value used to simplify the description :rtype: str or None """ return self.__curpath @curpath.setter def curpath(self, value=None): """ Set the ``curpath`` property value :param value: value used to simplify the description :type value: str or None :raise TypeError: when ``directory`` property value is not str type or None """ if value is not None and type(value) != str: raise TypeError("'curpath' property value must be str type or None") if self.curpath != value: self.__curpath = value @property def directory(self): """ An absolute or relative pathname of the directory that shall become the new working directory. :return: directory that shall become the new working directory :rtype: str or None """ return self.__directory @directory.setter def directory(self, value=None): """ Set the ``directory`` property value :param value: directory that shall become the new working directory :type value: str or None :raise TypeError: when ``directory`` property value is not str type or None """ if value is not None and type(value) != str: raise TypeError("'directory' property value must be str type or None") if self.directory != value: self.__directory = value @property def logical(self): """ The ``logical`` property is use as control during the 9 steps of the posix sequence Default: False :return: True if handle the operand dot-dot logically :rtype: bool """ return self.__logical @logical.setter def logical(self, value=None): """ Set the ``logical`` property value Default: False :param value: True if handle the operand dot-dot logically :type value: bool or None """ if value is None: value = False if type(value) != bool: raise TypeError("'logical' property value must be bool type or None") if self.logical != value: self.__logical = value @property def physical(self): """ The ``physical`` property is use as control during the 9 steps of the posix sequence Default: False :return: True if handle the operand dot-dot physically :rtype: bool """ return self.__physical @physical.setter def physical(self, value=None): """ Set the ``physical`` property value Default: True :param value: True if handle the operand dot-dot physically :type value: bool or None """ if value is None: value = False if type(value) != bool: raise TypeError("'physical' property value must be bool type or None") if self.physical != value: self.__physical = value @property def can_continue(self): """ The ``working`` property is use as control during the 9 steps of the posix sequence Default: True :return: True if any step can be operate :rtype: bool """ return self.__can_continue @can_continue.setter def can_continue(self, value=None): """ Set the ``working`` property value Default: True :param value: True if any step can be operate :type value: bool or None """ if value is None: value = True if type(value) != bool: raise TypeError("'working' property value must be bool type or None") if self.can_continue != value: self.__can_continue = value
[docs] def cd(self, directory=None, logical=None, physical=None): # Init self.can_continue = True self.curpath = None self.directory = directory self.logical = logical self.physical = physical # Logical if self.logical and self.physical: self.physical = False if not self.logical and not self.physical: self.logical = True if self.directory == "-": if os.environ.get("OLDPWD"): self.directory = os.environ.get("OLDPWD") else: self.directory = os.environ.get("PWD") elif self.directory is not None: self.directory = os.path.expanduser(self.directory) # Start the posix sequence self._step_1() self._step_2() self._step_3() self._step_4() self._step_5() self._step_6() self._step_7() self._step_8() self._step_9() # Inform cmd2 directly cmd2.Cmd().last_result = os.getcwd() cmd2.Cmd().set_window_title( os.path.normpath(os.getcwd()).replace( os.path.realpath(os.path.expanduser("~")), "~" ) )
def _step_1(self): """ If no directory operand is given and the HOME environment variable is empty or undefined, the default behavior is implementation-defined and no further steps shall be taken. """ if self.can_continue and self.directory is None and not os.environ.get("HOME"): self.can_continue = False def _step_2(self): """ If no directory operand is given and the HOME environment variable is set to a non-empty value, the *cd* utility shall behave as if the directory named in the HOME environment variable was specified as the directory operand. """ if self.can_continue and self.directory is None and os.environ.get("HOME"): self.directory = os.environ.get("HOME") def _step_3(self): """ If the directory operand begins with a slash character, set ``curpath`` to the operand and proceed to step 7. """ if self.can_continue and self.directory.startswith(os.path.sep): self.curpath = self.directory self._step_7() def _step_4(self): """ If the first component of the directory operand is dot or dot-dot, proceed to step 6. """ if ( self.can_continue and self.directory.startswith("..") or self.directory.startswith(".") ): self._step_6() def _step_5(self): """ Starting with the first pathname in the colon-separated pathnames of CDPATH (see the ENVIRONMENT VARIABLES section) if the pathname is non-null, test if the concatenation of that pathname, a slash character, and the directory operand names a directory. If the pathname is null, test if the concatenation of dot, a slash character, and the operand names a directory. In either case, if the resulting string names an existing directory, set curpath to that string and proceed to step 7. Otherwise, repeat this step with the next pathname in CDPATH until all pathnames have been tested. """ # 5. Starting with the first pathname in the colon-separated pathnames of CDPATH # (see the ENVIRONMENT VARIABLES section) if self.can_continue and os.environ.get("CDPATH"): for pathname in os.environ.get("CDPATH").split(":"): if pathname: # if the pathname is non-null, test if the concatenation of that pathname, # a slash character, and the directory operand names a directory. if os.path.isdir(os.path.join(pathname, self.directory)): self.curpath = os.path.join(pathname, self.directory) self._step_7() break # If the pathname is null, test if the concatenation of dot, a slash character, and # the operand names a directory. # set curpath to that string and proceed to step 7. elif os.path.isdir( os.path.join(".{0}".format(os.path.sep), self.directory) ): self.curpath = os.path.join( ".{0}".format(os.path.sep), self.directory ) self._step_7() break # elif os.path.isdir(pathname): # # In either case, if the resulting string names an existing directory, # self.curpath = pathname # self._step_7() # break # Otherwise, repeat this step with the next pathname in CDPATH until all pathnames have been tested. def _step_6(self): """ Set ``curpath`` to the string formed by the concatenation of the value of *PWD* , a slash character, and the operand. """ if self.can_continue: self.curpath = os.path.join(os.environ.get("PWD"), self.directory) def _step_7(self): """ If the **-P** option is in effect, the cd utility shall perform actions equivalent to the chdir() function, called with ``curpath`` as the path argument. If these actions succeed, the *PWD* environment variable shall be set to an absolute pathname for the current working directory and shall not contain filename components that, in the context of pathname resolution, refer to a file of type symbolic link. If there is insufficient permission on the new directory, or on any parent of that directory, to determine the current working directory, the value of the *PWD* environment variable is unspecified. If the actions equivalent to ``chdir()`` fail for any reason, the cd utility shall display an appropriate error message and not alter the PWD environment variable. Whether the actions equivalent to ``chdir()`` succeed or fail, no further steps shall be taken. """ if self.can_continue and self.physical: # Make sure the directory exists, is a directory, and we have read access err = None if not os.path.isdir(self.curpath): err = f"cd: {self.curpath}: No such file or directory" elif not os.access(self.curpath, os.R_OK): # pragma: no cover err = f"cd: {self.curpath}: Permission denied" else: try: os.chdir(self.curpath) except Exception as ex: # pragma: no cover err = f"{ex}" else: if os.environ.get("OLDPWD") != os.environ["PWD"]: os.environ["OLDPWD"] = os.environ["PWD"] if os.environ.get("PWD") != os.path.normpath(os.getcwd()): os.environ["PWD"] = os.path.normpath(os.getcwd()) if err: cmd2.Cmd().perror(err) self.can_continue = False def _step_8(self): """ The ``curpath`` value shall then be converted to canonical form as follows, considering each component from beginning to end, in sequence: a. Dot components and any slashes that separate them from the next component shall be deleted. b. For each dot-dot component, if there is a preceding component and it is neither root nor dot-dot, the preceding component, all slashes separating the preceding component from dot-dot, dot-dot and all slashes separating dot-dot from the following component shall be deleted. c. An implementation may further simplify ``curpath`` by removing any trailing slash characters that are not also leading slashes, replacing multiple non-leading consecutive slashes with a single slash, and replacing three or more leading slashes with a single slash. If, as a result of this canonicalization, the ``curpath`` variable is null, no further steps shall be taken. """ if self.can_continue: self.curpath = os.path.abspath(self.curpath) def _step_9(self): """ The cd utility shall then perform actions equivalent to the ``chdir()`` function called with ``curpath`` as the path argument. If these actions failed for any reason, the cd utility shall display an appropriate error message and no further steps shall be taken. The *PWD* environment variable shall be set to ``curpath``. """ if self.can_continue: err = None if not os.path.isdir(self.curpath): err = f"cd: {self.curpath}: No such file or directory" elif not os.access(self.curpath, os.R_OK): # pragma: no cover err = f"cd: {self.curpath}: Permission denied" else: try: os.chdir(self.curpath) except Exception as ex: # pragma: no cover err = f"{ex}" else: if os.environ.get("OLDPWD") != os.environ["PWD"]: os.environ["OLDPWD"] = os.environ["PWD"] if os.environ["PWD"] != self.curpath: os.environ["PWD"] = self.curpath if err: cmd2.Cmd().perror(err) self.can_continue = False
[docs] @cmd2.with_argparser(cd_parser) @cmd2.with_category("Builtins") def do_cd(self, args): self.cd( directory=args.directory, logical=args.logical, physical=args.physical ) # pragma: no cover
[docs] @staticmethod def complete_cd(text, line, begidx, endidx): return cmd2.Cmd().path_complete( text=text, line=line, begidx=begidx, endidx=endidx, path_filter=os.path.isdir, )