Added FilePath.

This commit is contained in:
Branimir Karadžić
2017-07-14 00:09:52 -07:00
parent a517e191d4
commit 6e252cf889
8 changed files with 451 additions and 19 deletions

64
include/bx/filepath.h Normal file
View 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

View File

@@ -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);

View File

@@ -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 = "";

View File

@@ -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);

View File

@@ -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
View 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

View File

@@ -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
View 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() );
};
}