mirror of
https://github.com/bkaradzic/bx.git
synced 2026-02-17 20:52:37 +01:00
Added FilePath.
This commit is contained in:
64
include/bx/filepath.h
Normal file
64
include/bx/filepath.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2010-2017 Branimir Karadzic. All rights reserved.
|
||||
* License: https://github.com/bkaradzic/bx#license-bsd-2-clause
|
||||
*/
|
||||
|
||||
#ifndef BX_FILEPATH_H_HEADER_GUARD
|
||||
#define BX_FILEPATH_H_HEADER_GUARD
|
||||
|
||||
#include "string.h"
|
||||
|
||||
namespace bx
|
||||
{
|
||||
const int32_t kMaxFilePath = 1024;
|
||||
|
||||
/// FilePath parser and helper.
|
||||
///
|
||||
/// /abv/gd/555/333/pod.mac
|
||||
/// ppppppppppppppppbbbeeee
|
||||
/// ^ ^ ^
|
||||
/// +-path base-+ +-ext
|
||||
/// ^^^^^^^
|
||||
/// +-filename
|
||||
///
|
||||
class FilePath
|
||||
{
|
||||
public:
|
||||
///
|
||||
FilePath();
|
||||
|
||||
///
|
||||
FilePath(const StringView& _str);
|
||||
|
||||
///
|
||||
void set(const StringView& _str);
|
||||
|
||||
///
|
||||
const StringView get() const;
|
||||
|
||||
/// If path is `/abv/gd/555/333/pod.mac` returns `/abv/gd/555/333/`.
|
||||
///
|
||||
const StringView getPath() const;
|
||||
|
||||
/// If path is `/abv/gd/555/333/pod.mac` returns `pod.mac`.
|
||||
///
|
||||
const StringView getFileName() const;
|
||||
|
||||
/// If path is `/abv/gd/555/333/pod.mac` returns `pod`.
|
||||
///
|
||||
const StringView getBaseName() const;
|
||||
|
||||
/// If path is `/abv/gd/555/333/pod.mac` returns `.mac`.
|
||||
///
|
||||
const StringView getExt() const;
|
||||
|
||||
///
|
||||
bool isAbsolute() const;
|
||||
|
||||
private:
|
||||
char m_filePath[kMaxFilePath];
|
||||
};
|
||||
|
||||
} // namespace bx
|
||||
|
||||
#endif // BX_FILEPATH_H_HEADER_GUARD
|
||||
@@ -293,6 +293,11 @@ namespace bx
|
||||
return _writer->write(_data, _size, _err);
|
||||
}
|
||||
|
||||
inline int32_t write(WriterI* _writer, const char* _str, Error* _err)
|
||||
{
|
||||
return write(_writer, _str, strLen(_str), _err);
|
||||
}
|
||||
|
||||
inline int32_t writeRep(WriterI* _writer, uint8_t _byte, int32_t _size, Error* _err)
|
||||
{
|
||||
BX_ERROR_SCOPE(_err);
|
||||
|
||||
@@ -75,6 +75,11 @@ namespace bx
|
||||
set(_ptr, _len);
|
||||
}
|
||||
|
||||
inline StringView::StringView(const char* _ptr, const char* _term)
|
||||
{
|
||||
set(_ptr, _term);
|
||||
}
|
||||
|
||||
inline void StringView::set(const char* _ptr, int32_t _len)
|
||||
{
|
||||
clear();
|
||||
@@ -90,6 +95,11 @@ namespace bx
|
||||
}
|
||||
}
|
||||
|
||||
inline void StringView::set(const char* _ptr, const char* _term)
|
||||
{
|
||||
set(_ptr, int32_t(_term-_ptr) );
|
||||
}
|
||||
|
||||
inline void StringView::clear()
|
||||
{
|
||||
m_ptr = "";
|
||||
|
||||
@@ -247,6 +247,9 @@ namespace bx
|
||||
/// Write data.
|
||||
int32_t write(WriterI* _writer, const void* _data, int32_t _size, Error* _err = NULL);
|
||||
|
||||
/// Writer string.
|
||||
inline int32_t write(WriterI* _writer, const char* _str, Error* _err = NULL);
|
||||
|
||||
/// Write repeat the same value.
|
||||
int32_t writeRep(WriterI* _writer, uint8_t _byte, int32_t _size, Error* _err = NULL);
|
||||
|
||||
|
||||
@@ -37,9 +37,15 @@ namespace bx
|
||||
///
|
||||
StringView(const char* _ptr, int32_t _len = INT32_MAX);
|
||||
|
||||
///
|
||||
StringView(const char* _ptr, const char* _term);
|
||||
|
||||
///
|
||||
void set(const char* _ptr, int32_t _len = INT32_MAX);
|
||||
|
||||
///
|
||||
void set(const char* _ptr, const char* _term);
|
||||
|
||||
///
|
||||
void clear();
|
||||
|
||||
@@ -138,9 +144,15 @@ namespace bx
|
||||
/// String compare.
|
||||
int32_t strCmp(const char* _lhs, const char* _rhs, int32_t _max = INT32_MAX);
|
||||
|
||||
/// String compare.
|
||||
int32_t strCmp(const char* _lhs, const StringView& _rhs);
|
||||
|
||||
/// Case insensitive string compare.
|
||||
int32_t strCmpI(const char* _lhs, const char* _rhs, int32_t _max = INT32_MAX);
|
||||
|
||||
/// Case insensitive string compare.
|
||||
int32_t strCmpI(const char* _lhs, const StringView& _rhs);
|
||||
|
||||
/// Get string length.
|
||||
int32_t strLen(const char* _str, int32_t _max = INT32_MAX);
|
||||
|
||||
@@ -148,9 +160,15 @@ namespace bx
|
||||
/// including zero terminator. Copy will be terminated with '\0'.
|
||||
int32_t strCopy(char* _dst, int32_t _dstSize, const char* _src, int32_t _num = INT32_MAX);
|
||||
|
||||
///
|
||||
int32_t strCopy(char* _dst, int32_t _dstSize, const StringView& _str);
|
||||
|
||||
/// Concatinate string.
|
||||
int32_t strCat(char* _dst, int32_t _dstSize, const char* _src, int32_t _num = INT32_MAX);
|
||||
|
||||
///
|
||||
int32_t strCat(char* _dst, int32_t _dstSize, const StringView& _str);
|
||||
|
||||
/// Find character in string. Limit search to _max characters.
|
||||
const char* strFind(const char* _str, char _ch, int32_t _max = INT32_MAX);
|
||||
|
||||
@@ -218,9 +236,6 @@ namespace bx
|
||||
template <typename Ty>
|
||||
Ty replaceAll(const Ty& _str, const char* _from, const char* _to);
|
||||
|
||||
/// Extract base file name from file path.
|
||||
const char* baseName(const char* _filePath);
|
||||
|
||||
/// Convert size in bytes to human readable string kibi units.
|
||||
int32_t prettify(char* _out, int32_t _count, uint64_t _size, Units::Enum _units = Units::Kibi);
|
||||
|
||||
|
||||
212
src/filepath.cpp
Normal file
212
src/filepath.cpp
Normal file
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright 2010-2017 Branimir Karadzic. All rights reserved.
|
||||
* License: https://github.com/bkaradzic/bx#license-bsd-2-clause
|
||||
*/
|
||||
|
||||
#include <bx/filepath.h>
|
||||
#include <bx/readerwriter.h>
|
||||
|
||||
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: Lexical File Names in Plan 9 or Getting Dot-Dot Right
|
||||
// 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;
|
||||
}
|
||||
|
||||
while (idx < num && err.isOk() )
|
||||
{
|
||||
switch (_src[idx])
|
||||
{
|
||||
case '/':
|
||||
case '\\':
|
||||
++idx;
|
||||
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);
|
||||
}
|
||||
|
||||
write(&writer, '\0', &err);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
FilePath::FilePath()
|
||||
{
|
||||
set("");
|
||||
}
|
||||
|
||||
FilePath::FilePath(const StringView& _filePath)
|
||||
{
|
||||
set(_filePath);
|
||||
}
|
||||
|
||||
void FilePath::set(const StringView& _filePath)
|
||||
{
|
||||
normalizeFilePath(
|
||||
m_filePath
|
||||
, BX_COUNTOF(m_filePath)
|
||||
, _filePath.getPtr()
|
||||
, _filePath.getLength()
|
||||
);
|
||||
}
|
||||
|
||||
const StringView FilePath::get() const
|
||||
{
|
||||
return StringView(m_filePath);
|
||||
}
|
||||
|
||||
const StringView FilePath::getPath() const
|
||||
{
|
||||
const char* end = strRFind(m_filePath, '/');
|
||||
if (NULL != end)
|
||||
{
|
||||
return StringView(m_filePath, end+1);
|
||||
}
|
||||
|
||||
return StringView();
|
||||
}
|
||||
|
||||
const StringView FilePath::getFileName() const
|
||||
{
|
||||
const char* fileName = strRFind(m_filePath, '/');
|
||||
if (NULL != fileName)
|
||||
{
|
||||
return StringView(fileName+1);
|
||||
}
|
||||
|
||||
return get();
|
||||
}
|
||||
|
||||
const StringView FilePath::getBaseName() const
|
||||
{
|
||||
const StringView fileName = getFileName();
|
||||
if (!fileName.isEmpty() )
|
||||
{
|
||||
const char* ext = strFind(fileName.getPtr(), '.', fileName.getLength() );
|
||||
if (ext != NULL)
|
||||
{
|
||||
return StringView(fileName.getPtr(), ext);
|
||||
}
|
||||
}
|
||||
|
||||
return StringView();
|
||||
}
|
||||
|
||||
const StringView FilePath::getExt() const
|
||||
{
|
||||
const StringView fileName = getFileName();
|
||||
if (!fileName.isEmpty() )
|
||||
{
|
||||
const char* ext = strFind(fileName.getPtr(), '.', fileName.getLength() );
|
||||
return StringView(ext);
|
||||
}
|
||||
|
||||
return StringView();
|
||||
}
|
||||
|
||||
bool FilePath::isAbsolute() const
|
||||
{
|
||||
return '/' == m_filePath[0] // no drive letter
|
||||
|| (':' == m_filePath[1] && '/' == m_filePath[2]) // with drive letter
|
||||
;
|
||||
}
|
||||
|
||||
} // namespace bx
|
||||
@@ -134,11 +134,21 @@ namespace bx
|
||||
return strCmp<toNoop>(_lhs, _rhs, _max);
|
||||
}
|
||||
|
||||
int32_t strCmp(const char* _lhs, const StringView& _rhs)
|
||||
{
|
||||
return strCmp(_lhs, _rhs.getPtr(), _rhs.getLength() );
|
||||
}
|
||||
|
||||
int32_t strCmpI(const char* _lhs, const char* _rhs, int32_t _max)
|
||||
{
|
||||
return strCmp<toLower>(_lhs, _rhs, _max);
|
||||
}
|
||||
|
||||
int32_t strCmpI(const char* _lhs, const StringView& _rhs)
|
||||
{
|
||||
return strCmpI(_lhs, _rhs.getPtr(), _rhs.getLength() );
|
||||
}
|
||||
|
||||
int32_t strLen(const char* _str, int32_t _max)
|
||||
{
|
||||
if (NULL == _str)
|
||||
@@ -166,6 +176,11 @@ namespace bx
|
||||
return num;
|
||||
}
|
||||
|
||||
int32_t strCopy(char* _dst, int32_t _dstSize, const StringView& _str)
|
||||
{
|
||||
return strCopy(_dst, _dstSize, _str.getPtr(), _str.getLength() );
|
||||
}
|
||||
|
||||
int32_t strCat(char* _dst, int32_t _dstSize, const char* _src, int32_t _num)
|
||||
{
|
||||
BX_CHECK(NULL != _dst, "_dst can't be NULL!");
|
||||
@@ -177,6 +192,11 @@ namespace bx
|
||||
return strCopy(&_dst[len], max-len, _src, _num);
|
||||
}
|
||||
|
||||
int32_t strCat(char* _dst, int32_t _dstSize, const StringView& _str)
|
||||
{
|
||||
return strCat(_dst, _dstSize, _str.getPtr(), _str.getLength() );
|
||||
}
|
||||
|
||||
const char* strFind(const char* _str, char _ch, int32_t _max)
|
||||
{
|
||||
for (int32_t ii = 0, len = strLen(_str, _max); ii < len; ++ii)
|
||||
@@ -192,7 +212,7 @@ namespace bx
|
||||
|
||||
const char* strRFind(const char* _str, char _ch, int32_t _max)
|
||||
{
|
||||
for (int32_t ii = strLen(_str, _max); 0 < ii; --ii)
|
||||
for (int32_t ii = strLen(_str, _max); 0 <= ii; --ii)
|
||||
{
|
||||
if (_str[ii] == _ch)
|
||||
{
|
||||
@@ -900,21 +920,6 @@ namespace bx
|
||||
return len;
|
||||
}
|
||||
|
||||
const char* baseName(const char* _filePath)
|
||||
{
|
||||
const char* bs = strRFind(_filePath, '\\');
|
||||
const char* fs = strRFind(_filePath, '/');
|
||||
const char* slash = (bs > fs ? bs : fs);
|
||||
const char* colon = strRFind(_filePath, ':');
|
||||
const char* basename = slash > colon ? slash : colon;
|
||||
if (NULL != basename)
|
||||
{
|
||||
return basename+1;
|
||||
}
|
||||
|
||||
return _filePath;
|
||||
}
|
||||
|
||||
static const char s_units[] = { 'B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' };
|
||||
|
||||
template<uint32_t Kilo, char KiloCh0, char KiloCh1>
|
||||
|
||||
118
tests/filepath_test.cpp
Normal file
118
tests/filepath_test.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2010-2017 Branimir Karadzic. All rights reserved.
|
||||
* License: https://github.com/bkaradzic/bx#license-bsd-2-clause
|
||||
*/
|
||||
|
||||
#include "test.h"
|
||||
#include <bx/filepath.h>
|
||||
|
||||
struct FilePathTest
|
||||
{
|
||||
const char* filePath;
|
||||
const char* expected;
|
||||
};
|
||||
|
||||
FilePathTest s_filePathTest[] =
|
||||
{
|
||||
// Already clean
|
||||
{"", "."},
|
||||
{"abc", "abc"},
|
||||
{"abc/def", "abc/def"},
|
||||
{"a/b/c", "a/b/c"},
|
||||
{".", "."},
|
||||
{"..", ".."},
|
||||
{"../..", "../.."},
|
||||
{"../../abc", "../../abc"},
|
||||
{"/abc", "/abc"},
|
||||
{"/", "/"},
|
||||
|
||||
// Remove trailing slash
|
||||
{"abc/", "abc"},
|
||||
{"abc/def/", "abc/def"},
|
||||
{"a/b/c/", "a/b/c"},
|
||||
{"./", "."},
|
||||
{"../", ".."},
|
||||
{"../../", "../.."},
|
||||
{"/abc/", "/abc"},
|
||||
|
||||
// Remove doubled slash
|
||||
{"abc//def//ghi", "abc/def/ghi"},
|
||||
{"//abc", "/abc"},
|
||||
{"///abc", "/abc"},
|
||||
{"//abc//", "/abc"},
|
||||
{"abc//", "abc"},
|
||||
|
||||
// Remove . elements
|
||||
{"abc/./def", "abc/def"},
|
||||
{"/./abc/def", "/abc/def"},
|
||||
{"abc/.", "abc"},
|
||||
|
||||
// Remove .. elements
|
||||
{"abc/def/ghi/../jkl", "abc/def/jkl"},
|
||||
{"abc/def/../ghi/../jkl", "abc/jkl"},
|
||||
{"abc/def/..", "abc"},
|
||||
{"abc/def/../..", "."},
|
||||
{"/abc/def/../..", "/"},
|
||||
{"abc/def/../../..", ".."},
|
||||
{"/abc/def/../../..", "/"},
|
||||
{"abc/def/../../../ghi/jkl/../../../mno", "../../mno"},
|
||||
|
||||
// Combinations
|
||||
{"abc/./../def", "def"},
|
||||
{"abc//./../def", "def"},
|
||||
{"abc/../../././../def", "../../def"},
|
||||
|
||||
{"abc\\/../..\\/././../def", "../../def"},
|
||||
{"\\abc/def\\../..\\..", "/"},
|
||||
};
|
||||
|
||||
struct FilePathSplit
|
||||
{
|
||||
const char* filePath;
|
||||
bool absolute;
|
||||
const char* path;
|
||||
const char* fileName;
|
||||
const char* baseName;
|
||||
const char* extension;
|
||||
};
|
||||
|
||||
static const FilePathSplit s_filePathSplit[] =
|
||||
{
|
||||
{ "\\abc/def\\../..\\../test.txt", true, "/", "test.txt", "test", ".txt" },
|
||||
{ "/abv/gd/555/333/pod.mac", true, "/abv/gd/555/333/", "pod.mac", "pod", ".mac" },
|
||||
{ "archive.tar.gz", false, "", "archive.tar.gz", "archive", ".tar.gz" },
|
||||
{ "tmp/archive.tar.gz", false, "tmp/", "archive.tar.gz", "archive", ".tar.gz" },
|
||||
{ "/tmp/archive.tar.gz", true, "/tmp/", "archive.tar.gz", "archive", ".tar.gz" },
|
||||
{ "d:/tmp/archive.tar.gz", true, "D:/tmp/", "archive.tar.gz", "archive", ".tar.gz" },
|
||||
};
|
||||
|
||||
TEST_CASE("FilePath", "")
|
||||
{
|
||||
bx::FilePath fp;
|
||||
for (uint32_t ii = 0; ii < BX_COUNTOF(s_filePathTest); ++ii)
|
||||
{
|
||||
const FilePathTest& test = s_filePathTest[ii];
|
||||
|
||||
fp.set(test.filePath);
|
||||
const bx::StringView result = fp.get();
|
||||
|
||||
REQUIRE(0 == bx::strCmp(test.expected, result) );
|
||||
}
|
||||
|
||||
for (uint32_t ii = 0; ii < BX_COUNTOF(s_filePathSplit); ++ii)
|
||||
{
|
||||
const FilePathSplit& test = s_filePathSplit[ii];
|
||||
|
||||
fp.set(test.filePath);
|
||||
const bx::StringView path = fp.getPath();
|
||||
const bx::StringView fileName = fp.getFileName();
|
||||
const bx::StringView baseName = fp.getBaseName();
|
||||
const bx::StringView ext = fp.getExt();
|
||||
|
||||
REQUIRE(0 == bx::strCmp(test.path, path) );
|
||||
REQUIRE(0 == bx::strCmp(test.fileName, fileName) );
|
||||
REQUIRE(0 == bx::strCmp(test.baseName, baseName) );
|
||||
REQUIRE(0 == bx::strCmp(test.extension, ext) );
|
||||
REQUIRE(test.absolute == fp.isAbsolute() );
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user