Browse Source

Added FilePath.

Branimir Karadžić 8 years ago
parent
commit
6e252cf889

+ 64 - 0
include/bx/filepath.h

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

+ 5 - 0
include/bx/inline/readerwriter.inl

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

+ 10 - 0
include/bx/inline/string.inl

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

+ 3 - 0
include/bx/readerwriter.h

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

+ 18 - 3
include/bx/string.h

@@ -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 - 0
src/filepath.cpp

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

+ 21 - 16
src/string.cpp

@@ -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 - 0
tests/filepath_test.cpp

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