mirror of
https://github.com/bkaradzic/bx.git
synced 2026-02-17 20:52:37 +01:00
409 lines
7.0 KiB
C++
409 lines
7.0 KiB
C++
/*
|
|
* Copyright 2010-2021 Branimir Karadzic. All rights reserved.
|
|
* License: https://github.com/bkaradzic/bx#license-bsd-2-clause
|
|
*/
|
|
|
|
#include "bx_p.h"
|
|
#include <bx/file.h>
|
|
#include <bx/os.h>
|
|
#include <bx/readerwriter.h>
|
|
|
|
#if !BX_CRT_NONE
|
|
# if BX_CRT_MSVC
|
|
# include <direct.h> // _getcwd
|
|
# else
|
|
# include <unistd.h> // getcwd
|
|
# endif // BX_CRT_MSVC
|
|
#endif // !BX_CRT_NONE
|
|
|
|
#if BX_PLATFORM_WINDOWS
|
|
extern "C" __declspec(dllimport) unsigned long __stdcall GetTempPathA(unsigned long _max, char* _ptr);
|
|
#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;
|
|
}
|
|
BX_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_COMPILER_
|
|
}
|
|
|
|
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 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)
|
|
{
|
|
char tmp[kMaxFilePath];
|
|
uint32_t len = BX_COUNTOF(tmp);
|
|
|
|
switch (_dir)
|
|
{
|
|
case Dir::Current:
|
|
getCurrentPath(tmp, &len);
|
|
break;
|
|
|
|
case Dir::Temp:
|
|
getTempPath(tmp, &len);
|
|
break;
|
|
|
|
case Dir::Home:
|
|
getHomePath(tmp, &len);
|
|
break;
|
|
|
|
default:
|
|
len = 0;
|
|
break;
|
|
}
|
|
|
|
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
|