go.starlark.net@v0.0.0-20231101134539-556fd59b42f6/starlark/testdata/paths.star (about)

     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  
    17  NOTE: The functions in this module currently only support paths with Unix-style
    18  path separators (forward slash, "/"); they do not handle Windows-style paths
    19  with 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.
    25  def fail(msg):
    26      print(msg)
    27  
    28  struct = dict
    29  
    30  def _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  
    46  def _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  
    67  def _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  
    78  def _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  
   108  def _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  
   164  def _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  
   200  def _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  
   215  def _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  
   241  paths = 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  )