<commit_before>// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef __STOUT_PATH_HPP__
#define __STOUT_PATH_HPP__

#include <string>
#include <utility>
#include <vector>

#include <glog/logging.h>

#include <stout/stringify.hpp>
#include <stout/strings.hpp>

#include <stout/os/constants.hpp>


namespace path {

// Converts a fully formed URI to a filename for the platform.
//
// On all platforms, the optional "file://" prefix is removed if it
// exists.
//
// On Windows, this also converts "/" characters to "\" characters.
// The Windows file system APIs don't work with "/" in the filename
// when using long paths (although they do work fine if the file
// path happens to be short).
//
// NOTE: Currently, Mesos uses URIs and files somewhat interchangably.
// For compatibility, lack of "file://" prefix is not considered an
// error.
inline std::string from_uri(const std::string& uri)
{
  // Remove the optional "file://" if it exists.
  // TODO(coffler): Remove the `hostname` component.
  const std::string path = strings::remove(uri, "file://", strings::PREFIX);

#ifndef __WINDOWS__
  return path;
#else
  return strings::replace(path, "/", "\\");
#endif // __WINDOWS__
}


// Normalizes a given pathname and removes redundant separators and up-level
// references.
//
// Pathnames like `A/B/`, `A///B`, `A/./B`, 'A/foobar/../B` are all normalized
// to `A/B`. An empty pathname is normalized to `.`. Up-level entry also cannot
// escape a root path, in which case an error will be returned.
//
// This function follows the rules described in path_resolution(7) for Linux.
// However, it only performs pure lexical processing without touching the
// actual filesystem.
inline Try<std::string> normalize(
    const std::string& path,
    const char _separator = os::PATH_SEPARATOR)
{
  if (path.empty()) {
    return ".";
  }

  std::vector<std::string> components;
  const bool isAbs = (path[0] == _separator);
  const std::string separator(1, _separator);

  // TODO(jasonlai): Handle pathnames (including absolute paths) in Windows.

  foreach (const std::string& component, strings::tokenize(path, separator)) {
    // Skips empty components and "." (current directory).
    if (component == "." || component.empty()) {
      continue;
    }

    if (component == "..") {
      if (components.empty()) {
        if (isAbs) {
          return Error("Absolute path '" + path + "' tries to escape root");
        }
        components.push_back(component);
      } else if (components.back() == "..") {
        components.push_back(component);
      } else {
        components.pop_back();
      }
    } else {
      components.push_back(component);
    }
  }

  if (components.empty()) {
    return isAbs ? separator : ".";
  } else if (isAbs) {
    // Make sure that a separator is prepended if it is an absolute path.
    components.insert(components.begin(), "");
  }

  return strings::join(separator, components);
}


// Base case.
inline std::string join(
    const std::string& path1,
    const std::string& path2,
    const char _separator = os::PATH_SEPARATOR)
{
  const std::string separator = stringify(_separator);
  return strings::remove(path1, separator, strings::SUFFIX) +
         separator +
         strings::remove(path2, separator, strings::PREFIX);
}


template <typename... Paths>
inline std::string join(
    const std::string& path1,
    const std::string& path2,
    Paths&&... paths)
{
  return join(path1, join(path2, std::forward<Paths>(paths)...));
}


inline std::string join(const std::vector<std::string>& paths)
{
  if (paths.empty()) {
    return "";
  }

  std::string result = paths[0];
  for (size_t i = 1; i < paths.size(); ++i) {
    result = join(result, paths[i]);
  }
  return result;
}


/**
 * Returns whether the given path is an absolute path.
 * If an invalid path is given, the return result is also invalid.
 */
inline bool is_absolute(const std::string& path)
{
#ifndef __WINDOWS__
  return strings::startsWith(path, os::PATH_SEPARATOR);
#else
  // NOTE: We do not use `PathIsRelative` Windows utility function
  // here because it does not support long paths.
  //
  // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx<commit_msg>Allowed specifying path separator in a `path::join` overload.<commit_after>// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef __STOUT_PATH_HPP__
#define __STOUT_PATH_HPP__

#include <string>
#include <utility>
#include <vector>

#include <glog/logging.h>

#include <stout/stringify.hpp>
#include <stout/strings.hpp>

#include <stout/os/constants.hpp>


namespace path {

// Converts a fully formed URI to a filename for the platform.
//
// On all platforms, the optional "file://" prefix is removed if it
// exists.
//
// On Windows, this also converts "/" characters to "\" characters.
// The Windows file system APIs don't work with "/" in the filename
// when using long paths (although they do work fine if the file
// path happens to be short).
//
// NOTE: Currently, Mesos uses URIs and files somewhat interchangably.
// For compatibility, lack of "file://" prefix is not considered an
// error.
inline std::string from_uri(const std::string& uri)
{
  // Remove the optional "file://" if it exists.
  // TODO(coffler): Remove the `hostname` component.
  const std::string path = strings::remove(uri, "file://", strings::PREFIX);

#ifndef __WINDOWS__
  return path;
#else
  return strings::replace(path, "/", "\\");
#endif // __WINDOWS__
}


// Normalizes a given pathname and removes redundant separators and up-level
// references.
//
// Pathnames like `A/B/`, `A///B`, `A/./B`, 'A/foobar/../B` are all normalized
// to `A/B`. An empty pathname is normalized to `.`. Up-level entry also cannot
// escape a root path, in which case an error will be returned.
//
// This function follows the rules described in path_resolution(7) for Linux.
// However, it only performs pure lexical processing without touching the
// actual filesystem.
inline Try<std::string> normalize(
    const std::string& path,
    const char _separator = os::PATH_SEPARATOR)
{
  if (path.empty()) {
    return ".";
  }

  std::vector<std::string> components;
  const bool isAbs = (path[0] == _separator);
  const std::string separator(1, _separator);

  // TODO(jasonlai): Handle pathnames (including absolute paths) in Windows.

  foreach (const std::string& component, strings::tokenize(path, separator)) {
    // Skips empty components and "." (current directory).
    if (component == "." || component.empty()) {
      continue;
    }

    if (component == "..") {
      if (components.empty()) {
        if (isAbs) {
          return Error("Absolute path '" + path + "' tries to escape root");
        }
        components.push_back(component);
      } else if (components.back() == "..") {
        components.push_back(component);
      } else {
        components.pop_back();
      }
    } else {
      components.push_back(component);
    }
  }

  if (components.empty()) {
    return isAbs ? separator : ".";
  } else if (isAbs) {
    // Make sure that a separator is prepended if it is an absolute path.
    components.insert(components.begin(), "");
  }

  return strings::join(separator, components);
}


// Base case.
inline std::string join(
    const std::string& path1,
    const std::string& path2,
    const char _separator = os::PATH_SEPARATOR)
{
  const std::string separator = stringify(_separator);
  return strings::remove(path1, separator, strings::SUFFIX) +
         separator +
         strings::remove(path2, separator, strings::PREFIX);
}


template <typename... Paths>
inline std::string join(
    const std::string& path1,
    const std::string& path2,
    Paths&&... paths)
{
  return join(path1, join(path2, std::forward<Paths>(paths)...));
}


inline std::string join(
    const std::vector<std::string>& paths,
    const char separator = os::PATH_SEPARATOR)
{
  if (paths.empty()) {
    return "";
  }

  std::string result = paths[0];
  for (size_t i = 1; i < paths.size(); ++i) {
    result = join(result, paths[i], separator);
  }
  return result;
}


/**
 * Returns whether the given path is an absolute path.
 * If an invalid path is given, the return result is also invalid.
 */
inline bool is_absolute(const std::string& path)
{
#ifndef __WINDOWS__
  return strings::startsWith(path, os::PATH_SEPARATOR);
#else
  // NOTE: We do not use `PathIsRelative` Windows utility function
  // here because it does not support long paths.
  //
  // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
{'fraction_non_alphanumeric': 0.13575226775101656, 'fraction_numerical': 0.005213220727765614, 'mean_word_length': 3.332429990966576, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 27, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7133547', 'n_tokens_mistral': 2917, 'n_tokens_neox': 2802, 'n_words': 996}