示例#1
0
def get_filename(command_basename: str) -> str:
    '''
    Absolute path of the command in the current ``${PATH}`` with the passed
    basename if found *or* raise an exception otherwise.

    Parameters
    ----------
    command_basename : str
        Basename of the command to return the absolute path of.

    Returns
    ----------
    str
        Absolute path of this command.

    Raises
    ----------
    BetseCommandException
        If no such command is found.
    '''

    # Absolute path of this command if found or "None" otherwise.
    command_filename = get_filename_or_none(command_basename)

    # If this command is *NOT* found, raise an exception.
    if command_filename is None:
        raise BetseCommandException(
            'Command "{}" not found.'.format(command_basename))

    # Return this path.
    return command_filename
示例#2
0
def get_first_basename(command_basenames: SequenceTypes,
                       exception_message: str = None) -> str:
    '''
    First pathable string in the passed list (i.e., the first string that is
    the basename of a command in the current ``${PATH}``) if any *or* raise an
    exception otherwise.

    Parameters
    ----------
    command_basenames : SequenceTypes
        List of the basenames of all commands to be iteratively searched for
        (in descending order of preference).
    exception_message : optional[str]
        Optional exception message to be raised if no such string is pathable.
        Defaults to ``None``, in which case an exception message synthesized
        from the passed strings is raised.

    Returns
    ----------
    str
        First pathable string in the passed list.

    Raises
    ----------
    BetseCommandException
        If no passed strings are pathable.
    '''

    # Avoid circular import dependencies.
    from betse.util.type.text.string import strjoin

    # If this list contains the basename of a pathable command, return the
    # first such basename.
    for command_basename in command_basenames:
        if is_pathable(command_basename):
            return command_basename

    # Else, this list contains no such basename. Ergo, raise an exception.
    exception_message_suffix = (
        '{} not found in the current ${{PATH}}.'.format(
            strjoin.join_as_conjunction_double_quoted(*command_basenames)))

    # If a non-empty exception message is passed, suffix this message with this
    # detailed explanation.
    if exception_message:
        exception_message += ' ' + exception_message_suffix
    # Else, default this message to this detailed explanation.
    else:
        exception_message = exception_message_suffix

    # Raise this exception.
    raise BetseCommandException(exception_message)
示例#3
0
def die_unless_command(filename: str, reason: StrOrNoneTypes = None) -> None:
    '''
    Raise an exception unless a command with the passed filename exists.

    Parameters
    ----------
    filename : str
        Either the basename *or* the absolute or relative filename of the
        executable file to be validated.
    reason : optional[str]
        Human-readable sentence fragment to be embedded in this exception's
        message (e.g., ``due to "pyside2-tools" not being installed``).
        Defaults to ``None``, in which case this message has no such reason.

    Raises
    ----------
    BetseCommandException
        If this command does *not* exist.

    See Also
    ----------
    :func:`is_command`
        Further details.
    '''

    # If this command does *NOT* exist...
    if not is_command(filename):
        # Exception message to be raised.
        message = 'Command "{}" not found'.format(filename)

        # If an exception reason was passed, embed this reason in this message.
        if reason is not None:
            message += ' ({})'.format(reason)

        # Finalize this message.
        message += '.'

        # Raise this exception.
        raise BetseCommandException(message)
示例#4
0
def die_unless_pathable(command_basename: str,
                        exception_message: StrOrNoneTypes = None):
    '''
    Raise an exception with the passed message unless an external command with
    the passed basename exists (i.e., unless an executable file with this
    basename resides in the current ``${PATH}``).

    Raises
    ----------
    BetseCommandException
        If no external command with this basename exists.
    '''

    # If this pathable is not found, raise an exception.
    if not is_pathable(command_basename):
        # If no message was passed, default this message.
        if exception_message is None:
            exception_message = (
                'Command "{}" not found in the current ${{PATH}} or '
                'found but not an executable file.'.format(command_basename))

        # Raise this exception.
        raise BetseCommandException(exception_message)
示例#5
0
def _init_popen_kwargs(
    command_words: SequenceTypes, popen_kwargs: MappingOrNoneTypes
) -> MappingType:
    '''
    Sanitized dictionary of all keyword arguments to pass to the
    :class:`subprocess.Popen` callable when running the command specified by
    the passed shell words with the passed user-defined keyword arguments.

    `close_fds`
    ----------
    If the current platform is vanilla Windows *and* none of the ``stdin``,
    ``stdout``, ``stderr``, or ``close_fds`` arguments are passed, the latter
    argument will be explicitly set to ``False`` -- causing the command to be
    run to inherit all file handles (including stdin, stdout, and stderr) from
    the current process. By default, :class:`subprocess.Popen` documentation
    insists that:

    > On Windows, if ``close_fds`` is ``True`` then no handles will be
    > inherited by the child process.

    The child process will then open new file handles for stdin, stdout, and
    stderr. If the current terminal is a Windows Console, the underlying
    terminal devices and hence file handles will remain the same, in which case
    this is *not* an issue. If the current terminal is Cygwin-based (e.g.,,
    MinTTY), however, the underlying terminal devices and hence file handles
    will differ, in which case this behaviour prevents interaction between the
    current shell and the vanilla Windows command to be run below. In
    particular, all output from this command will be squelched.

    If at least one of stdin, stdout, or stderr are redirected to a blocking
    pipe, setting ``close_fds`` to ``False`` can induce deadlocks under certain
    edge-case scenarios. Since all such file handles default to ``None`` and
    hence are *not* redirected in this case, ``close_fds`` may be safely set to
    ``False``.

    On all other platforms, if ``close_fds`` is ``True``, no file handles
    *except* stdin, stdout, and stderr will be inherited by the child process.
    This function fundamentally differs in subtle (and only slightly documented
    ways) between vanilla Windows and all other platforms. These discrepancies
    appear to be harmful but probably unavoidable, given the philosophical gulf
    between vanilla Windows and all other platforms.

    Parameters
    ----------
    command_words : SequenceTypes
        List of one or more shell words comprising this command.
    popen_kwargs : optional[MappingType]
        Dictionary of all keyword arguments to be sanitized if any *or*
        ``None`` otherwise, in which case the empty dictionary is defaulted to.
    '''

    # Avoid circular import dependencies.
    from betse.util.path.command import cmds
    from betse.util.os.brand import windows
    from betse.util.os.shell import shellenv
    from betse.util.type.iterable.mapping import maptest

    # If this list of shell words is empty, raise an exception.
    if not command_words:
        raise BetseCommandException('Non-empty command expected.')

    # If these keyword arguments are empty, default to the empty dictionary.
    if popen_kwargs is None:
        popen_kwargs = {}

    # If the first shell word is this list is unrunnable, raise an exception.
    cmds.die_unless_command(command_words[0])

    # Log the command to be run before doing so.
    logs.log_debug('Running command: %s', ' '.join(command_words))

    # If this is vanilla Windows, sanitize the "close_fds" argument.
    if windows.is_windows_vanilla() and not maptest.has_keys(
        mapping=popen_kwargs,
        keys=('stdin', 'stdout', 'stderr', 'close_fds',)):
        popen_kwargs['close_fds'] = False

    # Isolate the current set of environment variables to this command,
    # preventing concurrent changes in these variables in the current process
    # from affecting this command's subprocess.
    popen_kwargs['env'] = shellenv.get_env()

    # Decode command output with the current locale's preferred encoding.
    popen_kwargs['universal_newlines'] = True

    # Return these keyword arguments.
    return popen_kwargs