You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Tools/CryCommonTools/PathHelpers.cpp

622 lines
14 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include <platform.h>
#include "PathHelpers.h"
#include "StringHelpers.h"
#include "Util.h"
#include <AzCore/std/string/conversions.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzCore/IO/SystemFile.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzCore/PlatformIncl.h>
// Returns position of last extension in last name (string::npos if not found)
// note: returns string::npos for names starting from '.' and having no
// '.' later (for example 'aaa/.ccc', 'a:.abc', '.rc')
template <class TS>
static inline size_t findExtensionPosition_Tpl(const TS& path)
{
const size_t dotPos = path.rfind('.');
if (dotPos == TS::npos)
{
return TS::npos;
}
static const typename TS::value_type separators[] = { '\\', '/', ':', 0 };
const size_t separatorPos = path.find_last_of(separators);
if (separatorPos != TS::npos)
{
if (separatorPos + 1 >= dotPos)
{
return TS::npos;
}
}
else if (dotPos == 0)
{
return TS::npos;
}
return dotPos + 1;
}
static size_t findExtensionPosition(const string& path)
{
return findExtensionPosition_Tpl(path);
}
static size_t findExtensionPosition(const wstring& path)
{
return findExtensionPosition_Tpl(path);
}
string PathHelpers::FindExtension(const string& path)
{
const size_t extPos = findExtensionPosition(path);
return (extPos == string::npos) ? string() : path.substr(extPos, string::npos);
}
wstring PathHelpers::FindExtension(const wstring& path)
{
const size_t extPos = findExtensionPosition(path);
return (extPos == wstring::npos) ? wstring() : path.substr(extPos, wstring::npos);
}
template <class TS>
static inline TS ReplaceExtension_Tpl(const TS& path, const TS& newExtension)
{
if (path.empty())
{
return TS();
}
if (newExtension.empty())
{
return PathHelpers::RemoveExtension(path);
}
const typename TS::value_type last = path[path.length() - 1];
if ((last == '\\') || (last == '/') || (last == ':') || (last == '.'))
{
return path;
}
const size_t extPos = findExtensionPosition(path);
static const typename TS::value_type dot[] = { '.', 0 };
return ((extPos == TS::npos) ? path + dot : path.substr(0, extPos)) + newExtension;
}
string PathHelpers::ReplaceExtension(const string& path, const string& newExtension)
{
return ReplaceExtension_Tpl(path, newExtension);
}
wstring PathHelpers::ReplaceExtension(const wstring& path, const wstring& newExtension)
{
return ReplaceExtension_Tpl(path, newExtension);
}
string PathHelpers::RemoveExtension(const string& path)
{
const size_t extPos = findExtensionPosition(path);
return (extPos == string::npos) ? path : path.substr(0, extPos - 1);
}
wstring PathHelpers::RemoveExtension(const wstring& path)
{
const size_t extPos = findExtensionPosition(path);
return (extPos == wstring::npos) ? path : path.substr(0, extPos - 1);
}
template <class TS>
static inline TS GetDirectory_Tpl(const TS& path)
{
static const typename TS::value_type separators[] = { '/', '\\', ':', 0 };
const size_t pos = path.find_last_of(separators);
if (pos == TS::npos)
{
return TS();
}
if (path[pos] == ':' || pos == 0 || path[pos - 1] == ':')
{
return path.substr(0, pos + 1);
}
// Handle paths like "\\machine"
if (pos == 1 && (path[0] == '/' || path[0] == '\\'))
{
return path;
}
return path.substr(0, pos);
}
string PathHelpers::GetDirectory(const string& path)
{
return GetDirectory_Tpl(path);
}
wstring PathHelpers::GetDirectory(const wstring& path)
{
return GetDirectory_Tpl(path);
}
template <class TS>
static inline TS GetFilename_Tpl(const TS& path)
{
static const typename TS::value_type separators[] = { '/', '\\', ':', 0 };
const size_t pos = path.find_last_of(separators);
if (pos == TS::npos)
{
return path;
}
// Handle paths like "\\machine"
if (pos == 1 && (path[0] == '/' || path[0] == '\\'))
{
return TS();
}
return path.substr(pos + 1, TS::npos);
}
string PathHelpers::GetFilename(const string& path)
{
return GetFilename_Tpl(path);
}
wstring PathHelpers::GetFilename(const wstring& path)
{
return GetFilename_Tpl(path);
}
template <class TS>
static inline TS AddSeparator_Tpl(const TS& path)
{
if (path.empty())
{
return TS();
}
const typename TS::value_type last = path[path.length() - 1];
if (last == '/' || last == '\\' || last == ':')
{
return path;
}
#if defined(AZ_PLATFORM_WINDOWS)
static const typename TS::value_type separator[] = { '\\', 0 };
#else
static const typename TS::value_type separator[] = { '/', 0 };
#endif
return path + separator;
}
string PathHelpers::AddSeparator(const string& path)
{
return AddSeparator_Tpl(path);
}
wstring PathHelpers::AddSeparator(const wstring& path)
{
return AddSeparator_Tpl(path);
}
template <class TS>
static inline TS RemoveSeparator_Tpl(const TS& path)
{
if (path.empty())
{
return TS();
}
const typename TS::value_type last = path[path.length() - 1];
if ((last == '/' || last == '\\') && path.length() > 1 && path[path.length() - 2] != ':')
{
return path.substr(0, path.length() - 1);
}
return path;
}
string PathHelpers::RemoveSeparator(const string& path)
{
return RemoveSeparator_Tpl(path);
}
wstring PathHelpers::RemoveSeparator(const wstring& path)
{
return RemoveSeparator_Tpl(path);
}
template <class TS>
static inline TS RemoveDuplicateSeparators_Tpl(const TS& path)
{
if (path.length() <= 1)
{
return path;
}
TS ret;
ret.reserve(path.length());
const typename TS::value_type* p = path.c_str();
// We start from the second char just to avoid damaging UNC paths with double backslash at the beginning (e.g. "\\Server04\file.txt")
ret += *p++;
while (*p)
{
ret += *p++;
if (p[-1] == '\\' || p[-1] == '/')
{
while (*p == '\\' || *p == '/')
{
++p;
}
}
}
return ret;
}
string PathHelpers::RemoveDuplicateSeparators(const string& path)
{
return RemoveDuplicateSeparators_Tpl(path);
}
wstring PathHelpers::RemoveDuplicateSeparators(const wstring& path)
{
return RemoveDuplicateSeparators_Tpl(path);
}
template <class TS>
static inline TS Join_Tpl(const TS& path1, const TS& path2)
{
if (path1.empty())
{
return path2;
}
if (path2.empty())
{
return path1;
}
if (!PathHelpers::IsRelative(path2))
{
assert(0 && "Join(): path2 is not relative");
return TS();
}
const typename TS::value_type last = path1[path1.length() - 1];
if (last == '/' || last == '\\' || last == ':')
{
return path1 + path2;
}
#if defined(AZ_PLATFORM_WINDOWS)
static const typename TS::value_type separator[] = { '\\', 0 };
#else
static const typename TS::value_type separator[] = { '/', 0 };
#endif
return path1 + separator + path2;
}
string PathHelpers::Join(const string& path1, const string& path2)
{
return Join_Tpl(path1, path2);
}
wstring PathHelpers::Join(const wstring& path1, const wstring& path2)
{
return Join_Tpl(path1, path2);
}
template <class TS>
static inline bool IsRelative_Tpl(const TS& path)
{
if (path.empty())
{
return true;
}
return path[0] != '/' && path[0] != '\\' && path.find(':') == TS::npos;
}
bool PathHelpers::IsRelative(const string& path)
{
return IsRelative_Tpl(path);
}
bool PathHelpers::IsRelative(const wstring& path)
{
return IsRelative_Tpl(path);
}
string PathHelpers::ToUnixPath(const string& path)
{
return StringHelpers::Replace(path, '\\', '/');
}
wstring PathHelpers::ToUnixPath(const wstring& path)
{
wstring s(path);
std::replace(s.begin(), s.end(), L'\\', L'/');
return s;
}
string PathHelpers::ToDosPath(const string& path)
{
return StringHelpers::Replace(path, '/', '\\');
}
wstring PathHelpers::ToDosPath(const wstring& path)
{
wstring s(path);
std::replace(s.begin(), s.end(), L'/', L'\\');
return s;
}
string PathHelpers::ToPlatformPath(const string& path)
{
#if defined(AZ_PLATFORM_WINDOWS)
return ToDosPath(path);
#else
return ToUnixPath(path);
#endif
}
wstring PathHelpers::ToPlatformPath(const wstring& path)
{
#if defined(AZ_PLATFORM_WINDOWS)
return ToDosPath(path);
#else
return ToUnixPath(path);
#endif
}
string PathHelpers::GetAsciiPath(const char* pPath)
{
AZStd::wstring wstr;
AZStd::to_wstring(wstr, pPath);
return GetAsciiPath(wstr.c_str());
}
string PathHelpers::GetAsciiPath(const wchar_t* pPath)
{
if (!pPath[0])
{
return string();
}
wstring w = ToPlatformPath(RemoveSeparator(wstring(pPath)));
if (StringHelpers::Utf16ContainsAsciiOnly(w.c_str()))
{
return StringHelpers::ConvertAsciiUtf16ToAscii(w.c_str());
}
// The path is non-ASCII, so let's resort to using short
// filenames where needed (short names are always ASCII-only)
// Long names components
std::vector<wstring> p0;
StringHelpers::Split(w, wstring(L"\\"), true, p0);
// find last component that is not in ASCII char set
int lastNonAscii;
for (lastNonAscii = (int)p0.size() - 1; lastNonAscii >= 0; --lastNonAscii)
{
if (!StringHelpers::Utf16ContainsAsciiOnly(p0[lastNonAscii].c_str()))
{
break;
}
}
assert(lastNonAscii >= 0);
string res;
res.reserve(w.length());
w.clear();
for (int i = 0; i <= lastNonAscii; ++i)
{
w.append(p0[i]);
if (i < lastNonAscii)
{
w.push_back('\\');
}
}
enum
{
kBufferLen = AZ_MAX_PATH_LEN
};
wchar_t bufferWchars[kBufferLen];
#if defined(AZ_PLATFORM_WINDOWS)
const int charCount = GetShortPathNameW(w.c_str(), bufferWchars, kBufferLen);
#else
const int charCount = w.length();
wcsncpy(bufferWchars, w.c_str(), kBufferLen);
#endif
if (charCount <= 0 || charCount >= kBufferLen)
{
return string();
}
#if defined(AZ_PLATFORM_WINDOWS)
// Paranoid
if (!StringHelpers::Utf16ContainsAsciiOnly(bufferWchars))
{
assert(0);
return string();
}
#endif
// Short names components
std::vector<wstring> p1;
StringHelpers::Split(wstring(bufferWchars), wstring(L"\\"), true, p1);
for (size_t i = 0; i < (int)p0.size(); ++i)
{
if (!p0[i].empty())
{
const wstring& p =
(i > lastNonAscii || StringHelpers::Utf16ContainsAsciiOnly(p0[i].c_str()))
? p0[i]
: p1[i];
res.append(StringHelpers::ConvertAsciiUtf16ToAscii(p.c_str()));
}
if (i + 1 < (int)p0.size())
{
res.push_back('\\');
}
}
return res;
}
string PathHelpers::GetAbsoluteAsciiPath(const char* pPath)
{
char fullPath[AZ_MAX_PATH_LEN];
AZ::IO::LocalFileIO localFileIO;
AZStd::string normalizedPath(pPath);
AzFramework::StringFunc::Path::Normalize(normalizedPath);
localFileIO.ConvertToAbsolutePath(normalizedPath.c_str(), fullPath, AZ_MAX_PATH_LEN);
fullPath[sizeof(fullPath) - 1] = '\0';
AZStd::wstring wstr;
AZStd::to_wstring(wstr, fullPath);
return GetAsciiPath(wstr.c_str());
}
string PathHelpers::GetAbsoluteAsciiPath(const wchar_t* pPath)
{
AZStd::string str;
AZStd::to_string(str, pPath);
AzFramework::StringFunc::Path::Normalize(str);
char fullPath[AZ_MAX_PATH_LEN];
AZ::IO::LocalFileIO localFileIO;
localFileIO.ConvertToAbsolutePath(str.c_str(), fullPath, AZ_MAX_PATH_LEN);
fullPath[sizeof(fullPath) - 1] = '\0';
AZStd::wstring wstr;
AZStd::to_wstring(wstr, fullPath);
return GetAsciiPath(wstr.c_str());
}
string PathHelpers::GetShortestRelativeAsciiPath(const string& baseFolder, const string& dependentPath)
{
const string d = GetAbsoluteAsciiPath(dependentPath.c_str());
if (d.empty())
{
return PathHelpers::CanonicalizePath(dependentPath);
}
const string b = GetAbsoluteAsciiPath(baseFolder.c_str());
if (b.empty())
{
return PathHelpers::CanonicalizePath(dependentPath);
}
const string b2 = AddSeparator(b);
if (StringHelpers::StartsWithIgnoreCase(d, b2))
{
const size_t len = d.length() - b2.length();
// note: len == 0 is possible in case of "C:\" and "C:\".
return (len == 0) ? string(".") : d.substr(b2.length(), len);
}
std::vector<string> p0;
StringHelpers::Split(b2, string("\\"), true, p0);
std::vector<string> p1;
StringHelpers::Split(d, string("\\"), true, p1);
if (!StringHelpers::EqualsIgnoreCase(p0[0], p1[0]))
{
// got different drive letters
return PathHelpers::CanonicalizePath(dependentPath);
}
if (StringHelpers::EqualsIgnoreCase(d, b))
{
// exactly same path
return string(".");
}
// Search for first non-matching component
for (int i = 1; i < (int)p0.size(); ++i)
{
if (StringHelpers::EqualsIgnoreCase(p0[i], p1[i]))
{
continue;
}
string s;
s.reserve(Util::getMax(d.length(), b.length()));
for (int j = i; j < (int)p0.size(); ++j)
{
if (!p0[j].empty())
{
s.append("..\\");
}
}
for (int j = i; j < (int)p1.size(); ++j)
{
s.append(p1[j]);
if (j + 1 < (int)p1.size())
{
s.push_back('\\');
}
}
return s;
}
assert(0);
return string();
}
string PathHelpers::CanonicalizePath(const string& path)
{
string result = RemoveSeparator(path);
// remove .\ or ./ at the path beginning.
if (result.length() > 2)
{
if (result[0] == '.' && (result[1] == '\\' || result[1] == '/'))
{
result = result.substr(2);
}
}
return result;
}