...

Text file src/go.starlark.net/starlark/testdata/paths.star

Documentation: go.starlark.net/starlark/testdata

     1# Copyright 2017 The Bazel Authors. All rights reserved.
     2#
     3# Licensed under the Apache License, Version 2.0 (the "License");
     4# you may not use this file except in compliance with the License.
     5# You may obtain a copy of the License at
     6#
     7#    http://www.apache.org/licenses/LICENSE-2.0
     8#
     9# Unless required by applicable law or agreed to in writing, software
    10# distributed under the License is distributed on an "AS IS" BASIS,
    11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12# See the License for the specific language governing permissions and
    13# limitations under the License.
    14
    15"""Skylib module containing file path manipulation functions.
    16
    17NOTE: The functions in this module currently only support paths with Unix-style
    18path separators (forward slash, "/"); they do not handle Windows-style paths
    19with backslash separators or drive letters.
    20"""
    21
    22# This file is in the Bazel build language dialect of Starlark,
    23# so declarations of 'fail' and 'struct' are required to make
    24# it compile in the core language.
    25def fail(msg):
    26    print(msg)
    27
    28struct = dict
    29
    30def _basename(p):
    31    """Returns the basename (i.e., the file portion) of a path.
    32
    33    Note that if `p` ends with a slash, this function returns an empty string.
    34    This matches the behavior of Python's `os.path.basename`, but differs from
    35    the Unix `basename` command (which would return the path segment preceding
    36    the final slash).
    37
    38    Args:
    39      p: The path whose basename should be returned.
    40
    41    Returns:
    42      The basename of the path, which includes the extension.
    43    """
    44    return p.rpartition("/")[-1]
    45
    46def _dirname(p):
    47    """Returns the dirname of a path.
    48
    49    The dirname is the portion of `p` up to but not including the file portion
    50    (i.e., the basename). Any slashes immediately preceding the basename are not
    51    included, unless omitting them would make the dirname empty.
    52
    53    Args:
    54      p: The path whose dirname should be returned.
    55
    56    Returns:
    57      The dirname of the path.
    58    """
    59    prefix, sep, _ = p.rpartition("/")
    60    if not prefix:
    61        return sep
    62    else:
    63        # If there are multiple consecutive slashes, strip them all out as Python's
    64        # os.path.dirname does.
    65        return prefix.rstrip("/")
    66
    67def _is_absolute(path):
    68    """Returns `True` if `path` is an absolute path.
    69
    70    Args:
    71      path: A path (which is a string).
    72
    73    Returns:
    74      `True` if `path` is an absolute path.
    75    """
    76    return path.startswith("/") or (len(path) > 2 and path[1] == ":")
    77
    78def _join(path, *others):
    79    """Joins one or more path components intelligently.
    80
    81    This function mimics the behavior of Python's `os.path.join` function on POSIX
    82    platform. It returns the concatenation of `path` and any members of `others`,
    83    inserting directory separators before each component except the first. The
    84    separator is not inserted if the path up until that point is either empty or
    85    already ends in a separator.
    86
    87    If any component is an absolute path, all previous components are discarded.
    88
    89    Args:
    90      path: A path segment.
    91      *others: Additional path segments.
    92
    93    Returns:
    94      A string containing the joined paths.
    95    """
    96    result = path
    97
    98    for p in others:
    99        if _is_absolute(p):
   100            result = p
   101        elif not result or result.endswith("/"):
   102            result += p
   103        else:
   104            result += "/" + p
   105
   106    return result
   107
   108def _normalize(path):
   109    """Normalizes a path, eliminating double slashes and other redundant segments.
   110
   111    This function mimics the behavior of Python's `os.path.normpath` function on
   112    POSIX platforms; specifically:
   113
   114    - If the entire path is empty, "." is returned.
   115    - All "." segments are removed, unless the path consists solely of a single
   116      "." segment.
   117    - Trailing slashes are removed, unless the path consists solely of slashes.
   118    - ".." segments are removed as long as there are corresponding segments
   119      earlier in the path to remove; otherwise, they are retained as leading ".."
   120      segments.
   121    - Single and double leading slashes are preserved, but three or more leading
   122      slashes are collapsed into a single leading slash.
   123    - Multiple adjacent internal slashes are collapsed into a single slash.
   124
   125    Args:
   126      path: A path.
   127
   128    Returns:
   129      The normalized path.
   130    """
   131    if not path:
   132        return "."
   133
   134    if path.startswith("//") and not path.startswith("///"):
   135        initial_slashes = 2
   136    elif path.startswith("/"):
   137        initial_slashes = 1
   138    else:
   139        initial_slashes = 0
   140    is_relative = (initial_slashes == 0)
   141
   142    components = path.split("/")
   143    new_components = []
   144
   145    for component in components:
   146        if component in ("", "."):
   147            continue
   148        if component == "..":
   149            if new_components and new_components[-1] != "..":
   150                # Only pop the last segment if it isn't another "..".
   151                new_components.pop()
   152            elif is_relative:
   153                # Preserve leading ".." segments for relative paths.
   154                new_components.append(component)
   155        else:
   156            new_components.append(component)
   157
   158    path = "/".join(new_components)
   159    if not is_relative:
   160        path = ("/" * initial_slashes) + path
   161
   162    return path or "."
   163
   164def _relativize(path, start):
   165    """Returns the portion of `path` that is relative to `start`.
   166
   167    Because we do not have access to the underlying file system, this
   168    implementation differs slightly from Python's `os.path.relpath` in that it
   169    will fail if `path` is not beneath `start` (rather than use parent segments to
   170    walk up to the common file system root).
   171
   172    Relativizing paths that start with parent directory references only works if
   173    the path both start with the same initial parent references.
   174
   175    Args:
   176      path: The path to relativize.
   177      start: The ancestor path against which to relativize.
   178
   179    Returns:
   180      The portion of `path` that is relative to `start`.
   181    """
   182    segments = _normalize(path).split("/")
   183    start_segments = _normalize(start).split("/")
   184    if start_segments == ["."]:
   185        start_segments = []
   186    start_length = len(start_segments)
   187
   188    if (path.startswith("/") != start.startswith("/") or
   189        len(segments) < start_length):
   190        fail("Path '%s' is not beneath '%s'" % (path, start))
   191
   192    for ancestor_segment, segment in zip(start_segments, segments):
   193        if ancestor_segment != segment:
   194            fail("Path '%s' is not beneath '%s'" % (path, start))
   195
   196    length = len(segments) - start_length
   197    result_segments = segments[-length:]
   198    return "/".join(result_segments)
   199
   200def _replace_extension(p, new_extension):
   201    """Replaces the extension of the file at the end of a path.
   202
   203    If the path has no extension, the new extension is added to it.
   204
   205    Args:
   206      p: The path whose extension should be replaced.
   207      new_extension: The new extension for the file. The new extension should
   208          begin with a dot if you want the new filename to have one.
   209
   210    Returns:
   211      The path with the extension replaced (or added, if it did not have one).
   212    """
   213    return _split_extension(p)[0] + new_extension
   214
   215def _split_extension(p):
   216    """Splits the path `p` into a tuple containing the root and extension.
   217
   218    Leading periods on the basename are ignored, so
   219    `path.split_extension(".bashrc")` returns `(".bashrc", "")`.
   220
   221    Args:
   222      p: The path whose root and extension should be split.
   223
   224    Returns:
   225      A tuple `(root, ext)` such that the root is the path without the file
   226      extension, and `ext` is the file extension (which, if non-empty, contains
   227      the leading dot). The returned tuple always satisfies the relationship
   228      `root + ext == p`.
   229    """
   230    b = _basename(p)
   231    last_dot_in_basename = b.rfind(".")
   232
   233    # If there is no dot or the only dot in the basename is at the front, then
   234    # there is no extension.
   235    if last_dot_in_basename <= 0:
   236        return (p, "")
   237
   238    dot_distance_from_end = len(b) - last_dot_in_basename
   239    return (p[:-dot_distance_from_end], p[-dot_distance_from_end:])
   240
   241paths = struct(
   242    basename = _basename,
   243    dirname = _dirname,
   244    is_absolute = _is_absolute,
   245    join = _join,
   246    normalize = _normalize,
   247    relativize = _relativize,
   248    replace_extension = _replace_extension,
   249    split_extension = _split_extension,
   250)

View as plain text