[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/aboutcode-org/commoncode/main/src/commoncode/paths.py [Back]  [Original]

#
# Copyright (c) nexB Inc. and others. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
# See https://github.com/aboutcode-org/commoncode for support or download.
# See https://aboutcode.org for more information about nexB OSS projects.
#

import ntpath
import posixpath
import re
from os.path import commonprefix

from commoncode.fileutils import as_posixpath
from commoncode.fileutils import as_winpath
from commoncode.fileutils import is_posixpath
from commoncode.text import as_unicode
from commoncode.text import toascii

"""
Various path utilities such as common prefix and suffix functions, conversion
to OS-safe paths and to POSIX paths.
"""
#
# Build OS-portable and safer paths


def safe_path(path, posix=False, preserve_spaces=False, posix_only=False):
    """
    Convert `path` to a safe and portable POSIX path usable on multiple OSes.
    The returned path is an ASCII-only byte string, resolved for relative
    segments and itself relative.

    The `path` is treated as a POSIX path if `posix` is True or as a Windows
    path with blackslash separators otherwise.

    If `preserve_spaces` is True, then the spaces in `path` will not be replaced.
    """
    # if the path is UTF, try to use unicode instead
    if not isinstance(path, str):
        path = as_unicode(path)

    path = path.strip()

    if not is_posixpath(path):
        path = as_winpath(path)
        posix = False

    path = resolve(path, posix)

    _pathmod, path_sep = path_handlers(path, posix)

    segments = [s.strip() for s in path.split(path_sep) if s.strip()]
    segments = [
        portable_filename(s, preserve_spaces=preserve_spaces, posix_only=posix_only)
        for s in segments
    ]

    if not segments:
        return "_"

    # always return posix
    path = "/".join(segments)
    return as_posixpath(path)


def path_handlers(path, posix=True):
    """
    Return a path module and path separator to use for handling (e.g. split and
    join) `path` using either POSIX or Windows conventions depending on the
    `path` content. Force usage of POSIX conventions if `posix` is True.
    """
    # determine if we use posix or windows path handling
    is_posix = is_posixpath(path)
    use_posix = posix or is_posix
    pathmod = use_posix and posixpath or ntpath
    path_sep = "/" if use_posix else "\\"
    return pathmod, path_sep


def resolve(path, posix=True):
    """
    Return a resolved relative POSIX path from `path` where extra slashes
    including leading and trailing slashes are removed, dot '.' and dotdot '..'
    path segments have been removed or resolved as possible. When a dotdot path
    segment cannot be further resolved and would be "escaping" from the provided
    path "tree", it is replaced by the string 'dotdot'.

    The `path` is treated as a POSIX path if `posix` is True (default) or as a
    Windows path with blackslash separators otherwise.
    """
    if not path:
        return "."

    path = path.strip()
    if not path:
        return "."

    if not is_posixpath(path):
        path = as_winpath(path)
        posix = False

    pathmod, path_sep = path_handlers(path, posix)

    path = path.strip(path_sep)
    segments = [s.strip() for s in path.split(path_sep) if s.strip()]

    # remove empty (// or ///) or blank (space only) or single dot segments
    segments = [s for s in segments if s and s != "."]

    path = path_sep.join(segments)

    # resolves . dot, .. dotdot
    path = pathmod.normpath(path)

    segments = path.split(path_sep)

    # remove empty or blank segments
    segments = [s.strip() for s in segments if s and s.strip()]

    # is this a windows absolute path? if yes strip the colon to make this relative
    if segments and len(segments[0]) == 2 and segments[0].endswith(":"):
        segments[0] = segments[0][:-1]

    # replace any remaining (usually leading) .. segment with a literal "dotdot"
    dotdot = "dotdot"
    dd = ".."
    segments = [dotdot if s == dd else s for s in segments if s]
    if segments:
        path = path_sep.join(segments)
    else:
        path = "."

    path = as_posixpath(path)

    return path


legal_punctuation = r"!\#$%&\(\)\+,\-\.;\=@\[\]_\{\}\~"
legal_spaces = r" "
legal_alphanumeric = r"A-Za-z0-9"
legal_chars = legal_alphanumeric + legal_punctuation
legal_chars_inc_spaces = legal_chars + legal_spaces
illegal_chars_re = r"[^" + legal_chars + r"]"
illegal_chars_exc_spaces_re = r"[^" + legal_chars_inc_spaces + r"]"
replace_illegal_chars = re.compile(illegal_chars_re).sub
replace_illegal_chars_exc_spaces = re.compile(illegal_chars_exc_spaces_re).sub


posix_legal_punctuation = r'

Web Proxy Viewer  |  New URL  |  Original Page