Files
bx/src/filepath.cpp
Бранимир Караџић 13c40f9a6e Happy New Year!
2025-01-13 15:45:25 -08:00

434 lines
8.0 KiB
C++

/*
* Copyright 2010-2025 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bx/blob/master/LICENSE
*/
#include <bx/file.h>
#include <bx/os.h>
#include <bx/readerwriter.h>
#if BX_CRT_MSVC
# include <direct.h> // _getcwd
#else
# include <unistd.h> // getcwd
#endif // BX_CRT_MSVC
#if BX_PLATFORM_WINDOWS
#if !defined(GetModuleFileName)
extern "C" __declspec(dllimport) unsigned long __stdcall GetModuleFileNameA(void* _module, char* _outFilePath, unsigned long _size);
#endif
extern "C" __declspec(dllimport) unsigned long __stdcall GetTempPathA(unsigned long _max, char* _outFilePath);
#elif BX_PLATFORM_OSX
extern "C" int _NSGetExecutablePath(char* _buf, uint32_t* _bufSize);
#endif // BX_PLATFORM_WINDOWS
namespace bx
{
static bool isPathSeparator(char _ch)
{
return false
|| '/' == _ch
|| '\\' == _ch
;
}
static int32_t normalizeFilePath(char* _dst, int32_t _dstSize, const char* _src, int32_t _num)
{
// Reference(s):
// - Lexical File Names in Plan 9 or Getting Dot-Dot Right
// https://web.archive.org/web/20180629044444/https://9p.io/sys/doc/lexnames.html
const int32_t num = strLen(_src, _num);
if (0 == num)
{
return strCopy(_dst, _dstSize, ".");
}
int32_t size = 0;
StaticMemoryBlockWriter writer(_dst, _dstSize);
Error err;
int32_t idx = 0;
int32_t dotdot = 0;
if (2 <= num
&& ':' == _src[1])
{
size += write(&writer, toUpper(_src[idx]), &err);
size += write(&writer, ':', &err);
idx += 2;
dotdot = size;
}
const int32_t slashIdx = idx;
bool rooted = isPathSeparator(_src[idx]);
if (rooted)
{
size += write(&writer, '/', &err);
++idx;
dotdot = size;
}
bool trailingSlash = false;
while (idx < num && err.isOk() )
{
switch (_src[idx])
{
case '/':
case '\\':
++idx;
trailingSlash = idx == num;
break;
case '.':
if (idx+1 == num
|| isPathSeparator(_src[idx+1]) )
{
++idx;
break;
}
if ('.' == _src[idx+1]
&& (idx+2 == num || isPathSeparator(_src[idx+2]) ) )
{
idx += 2;
if (dotdot < size)
{
for (--size
; dotdot < size && !isPathSeparator(_dst[size])
; --size)
{
}
seek(&writer, size, Whence::Begin);
}
else if (!rooted)
{
if (0 < size)
{
size += write(&writer, '/', &err);
}
size += write(&writer, "..", &err);
dotdot = size;
}
break;
}
[[fallthrough]];
default:
if ( ( rooted && slashIdx+1 != size)
|| (!rooted && 0 != size) )
{
size += write(&writer, '/', &err);
}
for (; idx < num && !isPathSeparator(_src[idx]); ++idx)
{
size += write(&writer, _src[idx], &err);
}
break;
}
}
if (0 == size)
{
size += write(&writer, '.', &err);
}
if (trailingSlash)
{
size += write(&writer, '/', &err);
}
write(&writer, '\0', &err);
return size;
}
static bool getEnv(char* _out, uint32_t* _inOutSize, const StringView& _name, FileType::Enum _type)
{
uint32_t len = *_inOutSize;
*_out = '\0';
if (getEnv(_out, &len, _name) )
{
FileInfo fi;
if (stat(fi, _out)
&& _type == fi.type)
{
*_inOutSize = len;
return true;
}
}
return false;
}
static char* pwd(char* _buffer, uint32_t _size)
{
#if BX_PLATFORM_PS4 \
|| BX_PLATFORM_XBOXONE \
|| BX_PLATFORM_WINRT \
|| BX_CRT_NONE
BX_UNUSED(_buffer, _size);
return NULL;
#elif BX_CRT_MSVC
return ::_getcwd(_buffer, (int32_t)_size);
#else
return ::getcwd(_buffer, _size);
#endif // BX_PLATFORM_*
}
static bool getCurrentPath(char* _out, uint32_t* _inOutSize)
{
uint32_t len = *_inOutSize;
if (NULL != pwd(_out, len))
{
*_inOutSize = strLen(_out);
return true;
}
return false;
}
static bool getExecutablePath(char* _out, uint32_t* _inOutSize)
{
#if BX_PLATFORM_WINDOWS
uint32_t len = ::GetModuleFileNameA(NULL, _out, *_inOutSize);
bool result = len != 0 && len < *_inOutSize;
*_inOutSize = len;
return result;
#elif BX_PLATFORM_LINUX
char tmp[64];
snprintf(tmp, sizeof(tmp), "/proc/%d/exe", getpid() );
ssize_t result = readlink(tmp, _out, *_inOutSize);
if (-1 < result)
{
*_inOutSize = uint32_t(result);
return true;
}
return false;
#elif BX_PLATFORM_OSX
uint32_t len = *_inOutSize;
bool result = _NSGetExecutablePath(_out, &len);
return 0 == result;
#else
BX_UNUSED(_out, _inOutSize);
return false;
#endif // BX_PLATFORM_*
}
static bool getHomePath(char* _out, uint32_t* _inOutSize)
{
return false
#if BX_PLATFORM_WINDOWS
|| getEnv(_out, _inOutSize, "USERPROFILE", FileType::Dir)
#endif // BX_PLATFORM_WINDOWS
|| getEnv(_out, _inOutSize, "HOME", FileType::Dir)
;
}
static bool getTempPath(char* _out, uint32_t* _inOutSize)
{
#if BX_PLATFORM_WINDOWS
uint32_t len = ::GetTempPathA(*_inOutSize, _out);
bool result = len != 0 && len < *_inOutSize;
*_inOutSize = len;
return result;
#else
static const StringView s_tmp[] =
{
"TMPDIR",
"TMP",
"TEMP",
"TEMPDIR",
""
};
for (const StringView* tmp = s_tmp; !tmp->isEmpty(); ++tmp)
{
uint32_t len = *_inOutSize;
*_out = '\0';
bool ok = getEnv(_out, &len, *tmp, FileType::Dir);
if (ok
&& len != 0
&& len < *_inOutSize)
{
*_inOutSize = len;
return ok;
}
}
FileInfo fi;
if (stat(fi, "/tmp")
&& FileType::Dir == fi.type)
{
strCopy(_out, *_inOutSize, "/tmp");
*_inOutSize = 4;
return true;
}
return false;
#endif // BX_PLATFORM_*
}
FilePath::FilePath()
{
set("");
}
FilePath::FilePath(Dir::Enum _dir)
{
set(_dir);
}
FilePath::FilePath(const char* _rhs)
{
set(_rhs);
}
FilePath::FilePath(const StringView& _filePath)
{
set(_filePath);
}
FilePath& FilePath::operator=(const StringView& _rhs)
{
set(_rhs);
return *this;
}
void FilePath::clear()
{
if (!isEmpty() )
{
set("");
}
}
void FilePath::set(Dir::Enum _dir)
{
bool ok = false;
char tmp[kMaxFilePath];
uint32_t len = BX_COUNTOF(tmp);
switch (_dir)
{
case Dir::Current: ok = getCurrentPath(tmp, &len); break;
case Dir::Executable: ok = getExecutablePath(tmp, &len); break;
case Dir::Home: ok = getHomePath(tmp, &len); break;
case Dir::Temp: ok = getTempPath(tmp, &len); break;
default: break;
}
len = ok ? len : 0;
set(StringView(tmp, len) );
}
void FilePath::set(const StringView& _filePath)
{
normalizeFilePath(
m_filePath
, BX_COUNTOF(m_filePath)
, _filePath.getPtr()
, _filePath.getLength()
);
}
void FilePath::join(const StringView& _str)
{
char tmp[kMaxFilePath];
strCopy(tmp, BX_COUNTOF(tmp), m_filePath);
strCat(tmp, BX_COUNTOF(tmp), "/");
strCat(tmp, BX_COUNTOF(tmp), _str);
set(tmp);
}
FilePath::operator StringView() const
{
return StringView(m_filePath, strLen(m_filePath) );
}
const char* FilePath::getCPtr() const
{
return m_filePath;
}
StringView FilePath::getPath() const
{
StringView end = strRFind(m_filePath, '/');
if (!end.isEmpty() )
{
return StringView(m_filePath, end.getPtr()+1);
}
return StringView();
}
StringView FilePath::getFileName() const
{
StringView fileName = strRFind(m_filePath, '/');
if (!fileName.isEmpty() )
{
return StringView(fileName.getPtr()+1);
}
return getCPtr();
}
StringView FilePath::getBaseName() const
{
const StringView fileName = getFileName();
if (!fileName.isEmpty() )
{
StringView ext = strFind(fileName, '.');
if (!ext.isEmpty() )
{
return StringView(fileName.getPtr(), ext.getPtr() );
}
return fileName;
}
return StringView();
}
StringView FilePath::getExt() const
{
const StringView fileName = getFileName();
if (!fileName.isEmpty() )
{
const StringView dot = strFind(fileName, '.');
return StringView(dot.getPtr(), fileName.getTerm() );
}
return StringView();
}
bool FilePath::isAbsolute() const
{
return '/' == m_filePath[0] // no drive letter
|| (':' == m_filePath[1] && '/' == m_filePath[2]) // with drive letter
;
}
bool FilePath::isEmpty() const
{
return 0 == strCmp(m_filePath, ".");
}
} // namespace bx