Source code for buildtest.utils.shell

import shutil

from buildtest.exceptions import BuildTestError
from buildtest.utils.command import BuildTestCommand
from buildtest.utils.file import is_file


[docs]def get_shells(): """Return a list of shell returned from /etc/shells file. If file exist we return a list The command we run is the following which will omit any lines that start with ``#`` which is for comments. If file doesn't exist we return an empty list .. code-block:: console $ grep '^[^#]' /etc/shells /bin/bash /bin/csh /bin/dash /bin/ksh /bin/sh /bin/tcsh /bin/zsh Returns: list: Return a list of shells """ etc_shell = "/etc/shells" if not is_file(etc_shell): return [] cmd = BuildTestCommand(f"grep '^[^#]' {etc_shell}") cmd.execute() out = cmd.get_output() out = [item.strip() for item in out] return out
[docs]def get_python_shells(): """Return a list of all python shells by running ``which -a python3 python`` which will report full path to all python and python3 wrapper in current $PATH. Shown below is an expected output. .. code-block:: console $ which -a python3 python /Users/siddiq90/.local/share/virtualenvs/buildtest-KLOcDrW0/bin/python3 /usr/local/bin/python3 /usr/bin/python3 /Users/siddiq90/.local/share/virtualenvs/buildtest-KLOcDrW0/bin/python /usr/bin/python Returns: list: A list of full path to python shells """ python_shells = [] if not shutil.which("which"): raise BuildTestError("Unable to find program 'which'. Please install 'which' ") cmd = BuildTestCommand("which -a python python3") cmd.execute() out = cmd.get_output() python_shells += [item.strip() for item in out] return python_shells
[docs]def shell_lookup(): """Return a dictionary of shell types and list of all shell interpreter. If shell is not present the entry will be an empty list.""" shells = {"bash": ["bash"], "sh": ["sh"], "csh": ["csh"], "zsh": ["zsh"]} for name in shells.keys(): cmd = BuildTestCommand(f"which -a {name}") cmd.execute() out = cmd.get_output() shells[name] += [item.strip() for item in out] return shells
[docs]def is_bash_shell(name): """Return ``True`` if specified shell is valid bash shell >>> is_bash_shell("bash") True >>> is_bash_shell("/bin/bash") True """ return name in shell_dict["bash"]
[docs]def is_sh_shell(name): """Return ``True`` if specified shell is valid sh shell >>> is_sh_shell("sh") True >>> is_sh_shell("/bin/sh") True """ return name in shell_dict["sh"]
[docs]def is_csh_shell(name): """Return ``True`` if specified shell is valid csh shell""" return name in shell_dict["csh"]
[docs]def is_zsh_shell(name): """Return ``True`` if specified shell is valid zsh shell""" return name in shell_dict["zsh"]
python_shells = get_python_shells() system_shells = get_shells() shell_dict = shell_lookup()
[docs]class Shell: def __init__(self, shell="bash"): """The Shell initializer takes an input shell and shell options and split string by shell name and options. Args: shell (str): Specify shell program and any options passed to shell. Defaults to ``bash`` """ # enforce input argument 'shell' to be a string if not isinstance(shell, str): raise BuildTestError( f"Invalid type for input: {shell} must be of type 'str'" ) self.name = shell.split()[0] self.valid_shells = ( system_shells + python_shells + ["bash", "csh", "tcsh", "sh", "zsh", "python", "python3"] ) # if input shell is not in list of valid shells we raise error. if self.name not in self.valid_shells: raise BuildTestError( f"Invalid shell: {self.name} select from one of the following shells: {self.valid_shells}" ) self._opts = " ".join(shell.split()[1:]) self.path = self.name @property def opts(self): """retrieve the shell opts that are set on init, and updated with setter""" return self._opts @opts.setter def opts(self, shell_opts): """Override the shell options in class attribute, this would be useful when shell options need to change due to change in shell program. """ self._opts = shell_opts return self._opts @property def path(self): """This method returns the full path to shell program using ``shutil.which()`` If shell program is not found we raise an exception. The shebang is is updated assuming path is valid which is just adding character '#!' in front of path. The return is full path to shell program. This method automatically updates the shell path when there is a change in attribute self.name >>> shell = Shell("bash") >>> shell.path '/usr/bin/bash' >>> shell.name="sh" >>> shell.path '/usr/bin/sh' """ return self._path # Identity functions
[docs] def __str__(self): return "[buildtest.shell][%s]" % self.name
[docs] def __repr__(self): return self.__str__()
@path.setter def path(self, name): """If the user provides a new path with a name, do same checks to ensure that it's found. """ path = shutil.which(name) # raise an exception if shell program is not found if not path: raise BuildTestError(f"Can't find program: {name}") # Update the name not that we are sure path is found self.name = name # if input shell is not in list of valid shells we raise error. if self.name not in self.valid_shells: raise BuildTestError( f"Please select one of the following shells: {self.valid_shells}" ) self._path = path # shebang is formed by adding the char '#!' with path to program self.shebang = f"#!{path}"
[docs] def get(self): """Return shell attributes as a dictionary""" return { "name": self.name, "opts": self._opts, "path": self._path, "shebang": self.shebang, }