Exemple #1
0
def delete(path, fatal=True, logger=UNSET, dryrun=UNSET):
    """
    Args:
        path (str | Path | None): Path to file or folder to delete
        fatal (type | bool | None): True: abort execution on failure, False: don't abort but log, None: don't abort, don't log
        logger (callable | bool | None): Logger to use, True to print(), False to trace(), None to disable log chatter
        dryrun (bool | UNSET | None): Optionally override current dryrun setting

    Returns:
        (int): In non-fatal mode, 1: successfully done, 0: was no-op, -1: failed
    """
    path = resolved_path(path)
    islink = path and os.path.islink(path)
    if not islink and (not path or not os.path.exists(path)):
        return 0

    if _R.hdry(dryrun, logger, "delete %s" % short(path)):
        return 1

    try:
        _do_delete(path, islink, fatal)
        _R.hlog(logger, "Deleted %s" % short(path))
        return 1

    except Exception as e:
        return abort("Can't delete %s" % short(path), exc_info=e, return_value=-1, fatal=fatal, logger=logger)
Exemple #2
0
def which(program, ignore_own_venv=False):
    """
    Args:
        program (str | pathlib.Path | None): Program name to find via env var PATH
        ignore_own_venv (bool): If True, do not resolve to executables in current venv

    Returns:
        (str | None): Full path to program, if one exists and is executable
    """
    if not program:
        return None

    program = str(program)
    if os.path.basename(program) != program:
        program = resolved_path(program)
        if SYS_INFO.platform_id.is_windows:  # pragma: no cover
            return _windows_exe(program)

        return program if is_executable(program) else None

    for p in os.environ.get("PATH", "").split(os.pathsep):
        fp = os.path.join(p, program)
        if SYS_INFO.platform_id.is_windows:  # pragma: no cover
            fp = _windows_exe(fp)

        if fp and (not ignore_own_venv or not SYS_INFO.venv_bin_folder
                   or not fp.startswith(SYS_INFO.venv_bin_folder)):
            if is_executable(fp):
                return fp

    program = os.path.join(os.getcwd(), program)
    if is_executable(program):
        return program

    return None
Exemple #3
0
def readlines(path, first=None, errors="ignore", fatal=False, logger=False):
    """
    Args:
        path (str | Path | None): Path to file to read lines from
        first (int | None): Return only the 'first' lines when specified
        errors (str | None): Optional string specifying how encoding errors are to be handled
        fatal (type | bool | None): True: abort execution on failure, False: don't abort but log, None: don't abort, don't log
        logger (callable | bool | None): Logger to use, True to print(), False to trace(), None to disable log chatter

    Yields:
        (str): Lines read, newlines and trailing spaces stripped
    """
    try:
        with io.open(resolved_path(path), errors=errors) as fh:
            if not first:
                first = -1

            for line in fh:
                if first == 0:
                    return

                yield decode(line).rstrip()
                first -= 1

    except Exception as e:
        message = "Can't read %s" % short(path)
        if fatal:
            abort(_R.actual_message(message), exc_info=e, fatal=fatal, logger=logger)

        _R.hlog(logger, message, exc_info=e)
Exemple #4
0
    def python_exes_in_folder(path, version=None):
        """
        Args:
            path (pathlib.Path | str): Path to python exe or folder with a python installation
            version (Version | None): Optional, major/minor version to search for

        Returns:
            Yields all python executable names
        """
        if path:
            path = resolved_path(path)
            if os.path.isdir(path):
                bin_folder = os.path.join(path, "bin")
                if os.path.isdir(bin_folder):
                    path = bin_folder

                candidates = []
                if version and version.given_components:
                    if len(version.given_components) > 1:
                        candidates.append("python%s.%s" %
                                          (version.major, version.minor))

                    candidates.append("python%s" % version.major)

                else:
                    candidates.append("python3")

                candidates.append("python")
                for name in candidates:
                    candidate = os.path.join(path, name)
                    if is_executable(candidate):
                        yield candidate

            elif is_executable(path):
                yield path
Exemple #5
0
def parent_folder(path, base=None):
    """Parent folder of `path`, relative to `base`

    Args:
        path (str | Path | None): Path to file or folder
        base (str | None): Base folder to use for relative paths (default: current working dir)

    Returns:
        (str): Absolute path of parent folder
    """
    return path and os.path.dirname(resolved_path(path, base=base))
Exemple #6
0
 def python_from_path(self, path):
     if path:
         short_name = str(path)
         spec = self.spec_from_path(path, family=short_name)
         if spec:
             exes = list(PythonDepot.python_exes_in_folder(path))
             problem = None if exes else "invalid python installation"
             exes.append(resolved_path(path))
             return PythonInstallation(exes[0],
                                       spec,
                                       equivalents=exes,
                                       problem=problem,
                                       short_name=short_name)
Exemple #7
0
    def __init__(self, text, family=None):
        """
        Args:
            text: Text describing desired python (note: an empty or None `text` will yield a generic "cpython:" spec)
            family (str | None): Additional text to examine to determine python family
        """
        text = stringified(text, none="").strip()
        self.text = text
        if not text or text == "invoker":
            self.family = guess_family(family or sys.version)
            self.canonical = "invoker"
            self.version = get_current_version()
            return

        if _is_path(text):
            self.family = guess_family(family or text)
            self.canonical = resolved_path(text)
            return

        m = _R.lc.rx_spec.match(text)
        if not m:
            m = _R.lc.rx_family.match(text)
            if m:
                self.family = guess_family(family or m.group(1))
                self.canonical = "%s:" % self.family

            else:
                self.canonical = "?%s" % text

            return

        version_text = m.group(3)
        if version_text and version_text.endswith("+"):
            self.is_min_spec = "+"
            version_text = version_text[:-1]

        if version_text:
            if len(version_text) > 1 and "." not in version_text:
                version_text = "%s.%s" % (version_text[0], version_text[1:])

            self.version = Version.from_text(version_text, strict=True)

        if self.version:
            self.family = guess_family(family or m.group(1))
            self.canonical = "%s:%s%s" % (self.family, self.version
                                          or "", self.is_min_spec)

        else:
            self.canonical = "?%s" % text
Exemple #8
0
def ask_once(name,
             instructions,
             default=None,
             base="~/.config",
             serializer=stringified,
             fatal=False,
             logger=False):
    """
    Args:
        name (str): Name under which to store provided answer (will be stored in ~/.config/<name>.json)
        instructions (str): Instructions to show to user when prompt is necessary
        default: Default value to return if answer not available
        base (str): Base folder where to stored provided answer
        serializer (callable): Function that will turn provided value into object to be stored
        logger (callable | bool | None): Logger to use, True to print(), False to trace(), None to disable log chatter
        fatal (type | bool | None): True: abort execution on failure, False: don't abort but log, None: don't abort, don't log

    Returns:
        Value given by user (or 'default' if given), optionally wrapped via `serializer`
    """
    path = resolved_path(name, base=base)
    if not path.endswith(".json"):
        path += ".json"

    existing = read_json(path, logger=logger)
    if existing is not None:
        return existing

    if not SYS_INFO.terminal.is_stdout_tty:
        return _R.habort(default, fatal, logger,
                         "Can't prompt for %s, not on a tty" % name)

    try:
        provided = input(instructions)
        if provided:
            value = serializer(provided)
            if value is not None:
                save_json(value, path, fatal=fatal, logger=logger)
                return value

            return _R.habort(default, fatal, logger,
                             "Invalid value provided for %s" % name)

        return _R.habort(default, fatal, logger,
                         "No value provided for %s" % name)

    except KeyboardInterrupt:
        return _R.habort(default, fatal, logger, "Cancelled by user")
Exemple #9
0
def ensure_folder(path, clean=False, fatal=True, logger=UNSET, dryrun=UNSET):
    """Ensure folder with 'path' exists

    Args:
        path (str | Path | None): Path to file or folder
        clean (bool): True: If True, ensure folder is clean (delete any file/folder it may have)
        fatal (type | bool | None): True: abort execution on failure, False: don't abort but log, None: don't abort, don't log
        logger (callable | bool | None): Logger to use, True to print(), False to trace(), None to disable log chatter
        dryrun (bool | UNSET | None): Optionally override current dryrun setting

    Returns:
        (int): In non-fatal mode, >=1: successfully done, 0: was no-op, -1: failed
    """
    path = resolved_path(path)
    if not path:
        return 0

    if os.path.isdir(path):
        if not clean:
            return 0

        cleaned = 0
        for fname in os.listdir(path):
            cleaned += delete(os.path.join(path, fname), fatal=fatal, logger=None, dryrun=dryrun)

        if cleaned:
            msg = "%s from %s" % (_R.lc.rm.plural(cleaned, "file"), short(path))
            if not _R.hdry(dryrun, logger, "clean %s" % msg):
                _R.hlog(logger, "Cleaned %s" % msg)

        return cleaned

    if _R.hdry(dryrun, logger, "create %s" % short(path)):
        return 1

    try:
        os.makedirs(path)
        _R.hlog(logger, "Created folder %s" % short(path))

        return 1

    except Exception as e:
        return abort("Can't create folder %s" % short(path), exc_info=e, return_value=-1, fatal=fatal, logger=logger)
Exemple #10
0
def write(path, contents, fatal=True, logger=UNSET, dryrun=UNSET):
    """Write `contents` to file with `path`

    Args:
        path (str | Path | None): Path to file
        contents (str | bytes | None): Contents to write (only touch file if None)
        fatal (type | bool | None): True: abort execution on failure, False: don't abort but log, None: don't abort, don't log
        logger (callable | bool | None): Logger to use, True to print(), False to trace(), None to disable log chatter
        dryrun (bool | UNSET | None): Optionally override current dryrun setting

    Returns:
        (int): In non-fatal mode, 1: successfully done, 0: was no-op, -1: failed
    """
    if not path:
        return 0

    path = resolved_path(path)
    byte_size = _R.lc.rm.represented_bytesize(len(contents), unit="bytes") if contents else ""

    def dryrun_msg():
        return "%s %s" % ("write %s to" % byte_size if byte_size else "touch", short(path))

    if _R.hdry(dryrun, logger, dryrun_msg):
        return 1

    ensure_folder(parent_folder(path), fatal=fatal, logger=None, dryrun=dryrun)
    try:
        mode = "wb" if isinstance(contents, bytes) else "wt"
        with io.open(path, mode) as fh:
            if contents is None:
                os.utime(path, None)

            else:
                fh.write(contents)

        _R.hlog(logger, "%s %s" % ("Wrote %s to" % byte_size if byte_size else "Touched", short(path)))
        return 1

    except Exception as e:
        return abort("Can't write to %s" % short(path), exc_info=e, return_value=-1, fatal=fatal, logger=logger)
Exemple #11
0
def _file_op(source, destination, func, overwrite, fatal, logger, dryrun, must_exist=True, ignore=None, **extra):
    """Call func(source, destination)

    Args:
        source (str | None): Source file or folder
        destination (str | None): Destination file or folder
        func (callable): Implementation function
        overwrite (bool | None): True: replace existing, False: fail if destination exists, None: no destination check
        fatal (type | bool | None): True: abort execution on failure, False: don't abort but log, None: don't abort, don't log
        logger (callable | bool | None): Logger to use, True to print(), False to trace(), None to disable log chatter
        dryrun (bool | UNSET | None): Optionally override current dryrun setting
        must_exist (bool): If True, verify that source does indeed exist
        ignore (callable | list | str | None): Names to be ignored
        **extra: Passed-through to 'func'

    Returns:
        (int): In non-fatal mode, 1: successfully done, 0: was no-op, -1: failed
    """
    if not source or not destination or source == destination:
        return 0

    action = func.__name__[1:]
    indicator = "<-" if action == "symlink" else "->"
    description = "%s %s %s %s" % (action, short(source), indicator, short(destination))
    psource = parent_folder(source)
    pdest = resolved_path(destination)
    if psource.startswith(pdest):
        message = "Can't %s: source contained in destination" % description
        return abort(message, return_value=-1, fatal=fatal, logger=logger)

    if _R.hdry(dryrun, logger, description):
        return 1

    if must_exist and not (os.path.exists(source) or os.path.islink(source)):
        message = "%s does not exist, can't %s to %s" % (short(source), action.lower(), short(destination))
        return abort(message, return_value=-1, fatal=fatal, logger=logger)

    if overwrite is not None:
        islink = os.path.islink(pdest)
        if islink or os.path.exists(pdest):
            if not overwrite:
                message = "%s exists, can't %s" % (short(destination), action.lower())
                return abort(message, return_value=-1, fatal=fatal, logger=logger)

            _do_delete(pdest, islink, fatal)

    try:
        # Ensure parent folder exists
        ensure_folder(parent_folder(destination), fatal=fatal, logger=None, dryrun=dryrun)
        _R.hlog(logger, "%s%s" % (description[0].upper(), description[1:]))
        if ignore is not None:
            if not callable(ignore):
                given = ignore

                def ignore(*_):
                    return given

            extra["ignore"] = ignore

        func(source, destination, **extra)
        return 1

    except Exception as e:
        return abort("Can't %s" % description, exc_info=e, return_value=-1, fatal=fatal, logger=logger)