diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 0cc1165f..924f223a 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -4,6 +4,7 @@ include_directories (${CMAKE_SOURCE_DIR} ${TIMEW_INCLUDE_DIRS}) set (common_SRCS Color.cpp Color.h + FS.cpp FS.h RX.cpp RX.h text.cpp text.h utf8.cpp utf8.h diff --git a/src/common/FS.cpp b/src/common/FS.cpp new file mode 100644 index 00000000..a9a607b0 --- /dev/null +++ b/src/common/FS.cpp @@ -0,0 +1,902 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2015, Paul Beckingham, Federico Hernandez. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// http://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined SOLARIS || defined NETBSD || defined FREEBSD +#include +#endif + +// Fixes build with musl libc. +#ifndef GLOB_TILDE +#define GLOB_TILDE 0 +#endif + +#ifndef GLOB_BRACE +#define GLOB_BRACE 0 +#endif + +//////////////////////////////////////////////////////////////////////////////// +Path::Path () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +Path::Path (const Path& other) +{ + if (this != &other) + { + _original = other._original; + _data = other._data; + } +} + +//////////////////////////////////////////////////////////////////////////////// +Path::Path (const std::string& in) +{ + _original = in; + _data = expand (in); +} + +//////////////////////////////////////////////////////////////////////////////// +Path::~Path () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +Path& Path::operator= (const Path& other) +{ + if (this != &other) + { + this->_original = other._original; + this->_data = other._data; + } + + return *this; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Path::operator== (const Path& other) +{ + return _data == other._data; +} + +//////////////////////////////////////////////////////////////////////////////// +Path& Path::operator+= (const std::string& dir) +{ + _data += "/" + dir; + return *this; +} + +//////////////////////////////////////////////////////////////////////////////// +Path::operator std::string () const +{ + return _data; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string Path::name () const +{ + if (_data.length ()) + { + auto slash = _data.rfind ('/'); + if (slash != std::string::npos) + return _data.substr (slash + 1, std::string::npos); + } + + return _data; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string Path::parent () const +{ + if (_data.length ()) + { + auto slash = _data.rfind ('/'); + if (slash != std::string::npos) + return _data.substr (0, slash); + } + + return ""; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string Path::extension () const +{ + if (_data.length ()) + { + auto dot = _data.rfind ('.'); + if (dot != std::string::npos) + return _data.substr (dot + 1, std::string::npos); + } + + return ""; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Path::exists () const +{ + return access (_data.c_str (), F_OK) ? false : true; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Path::is_directory () const +{ + struct stat s {}; + if (! stat (_data.c_str (), &s) && + S_ISDIR (s.st_mode)) + return true; + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Path::is_absolute () const +{ + if (_data.length () && _data[0] == '/') + return true; + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Path::is_link () const +{ + struct stat s {}; + if (! lstat (_data.c_str (), &s) && + S_ISLNK (s.st_mode)) + return true; + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Path::readable () const +{ + return access (_data.c_str (), R_OK) ? false : true; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Path::writable () const +{ + return access (_data.c_str (), W_OK) ? false : true; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Path::executable () const +{ + return access (_data.c_str (), X_OK) ? false : true; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Path::rename (const std::string& new_name) +{ + std::string expanded = expand (new_name); + if (_data != expanded) + { + if (::rename (_data.c_str (), expanded.c_str ()) == 0) + { + _data = expanded; + return true; + } + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// ~ --> /home/user +// ~foo/x --> /home/foo/s +// ~/x --> /home/foo/x +// ./x --> $PWD/x +// x --> $PWD/x +std::string Path::expand (const std::string& in) +{ + std::string copy = in; + + auto tilde = copy.find ("~"); + std::string::size_type slash; + + if (tilde != std::string::npos) + { + const char *home = getenv("HOME"); + if (home == NULL) + { + struct passwd* pw = getpwuid (getuid ()); + home = pw->pw_dir; + } + + // Convert: ~ --> /home/user + if (copy.length () == 1) + copy = home; + + // Convert: ~/x --> /home/user/x + else if (copy.length () > tilde + 1 && + copy[tilde + 1] == '/') + { + copy.replace (tilde, 1, home); + } + + // Convert: ~foo/x --> /home/foo/x + else if ((slash = copy.find ("/", tilde)) != std::string::npos) + { + std::string name = copy.substr (tilde + 1, slash - tilde - 1); + struct passwd* pw = getpwnam (name.c_str ()); + if (pw) + copy.replace (tilde, slash - tilde, pw->pw_dir); + } + } + + // Relative paths + else if (in.length () > 2 && + in.substr (0, 2) == "./") + { + copy = Directory::cwd () + "/" + in.substr (2); + } + else if (in.length () > 1 && + in[0] != '.' && + in[0] != '/') + { + copy = Directory::cwd () + "/" + in; + } + + return copy; +} + +//////////////////////////////////////////////////////////////////////////////// +std::vector Path::glob (const std::string& pattern) +{ + std::vector results; + + glob_t g; +#ifdef SOLARIS + if (!::glob (pattern.c_str (), GLOB_ERR, NULL, &g)) +#else + if (!::glob (pattern.c_str (), GLOB_ERR | GLOB_BRACE | GLOB_TILDE, NULL, &g)) +#endif + for (int i = 0; i < (int) g.gl_pathc; ++i) + results.push_back (g.gl_pathv[i]); + + globfree (&g); + return results; +} + +//////////////////////////////////////////////////////////////////////////////// +File::File () +: Path::Path () +, _fh (NULL) +, _h (-1) +, _locked (false) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +File::File (const Path& other) +: Path::Path (other) +, _fh (NULL) +, _h (-1) +, _locked (false) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +File::File (const File& other) +: Path::Path (other) +, _fh (NULL) +, _h (-1) +, _locked (false) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +File::File (const std::string& in) +: Path::Path (in) +, _fh (NULL) +, _h (-1) +, _locked (false) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +File::~File () +{ + if (_fh) + close (); +} + +//////////////////////////////////////////////////////////////////////////////// +File& File::operator= (const File& other) +{ + if (this != &other) + Path::operator= (other); + + _locked = false; + return *this; +} + +//////////////////////////////////////////////////////////////////////////////// +bool File::create (int mode /* = 0640 */) +{ + if (open ()) + { + fchmod (_h, mode); + close (); + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool File::remove () const +{ + return unlink (_data.c_str ()) == 0 ? true : false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool File::open () +{ + if (_data != "") + { + if (! _fh) + { + bool already_exists = exists (); + if (already_exists) + if (!readable () || !writable ()) + throw std::string (format ("Insufficient permissions for '{1}'.", _data)); + + _fh = fopen (_data.c_str (), (already_exists ? "r+" : "w+")); + if (_fh) + { + _h = fileno (_fh); + _locked = false; + return true; + } + } + else + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +void File::close () +{ + if (_fh) + { + if (_locked) + unlock (); + + fclose (_fh); + _fh = NULL; + _h = -1; + _locked = false; + } +} + +//////////////////////////////////////////////////////////////////////////////// +bool File::lock () +{ + _locked = false; + if (_fh && _h != -1) + { + // l_type l_whence l_start l_len l_pid + struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0 }; + fl.l_pid = getpid (); + if (fcntl (_h, F_SETLKW, &fl) == 0) + _locked = true; + } + + return _locked; +} + +//////////////////////////////////////////////////////////////////////////////// +void File::unlock () +{ + if (_locked) + { + // l_type l_whence l_start l_len l_pid + struct flock fl = {F_UNLCK, SEEK_SET, 0, 0, 0 }; + fl.l_pid = getpid (); + + fcntl (_h, F_SETLK, &fl); + _locked = false; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Opens if necessary. +void File::read (std::string& contents) +{ + contents = ""; + contents.reserve (size ()); + + std::ifstream in (_data.c_str ()); + if (in.good ()) + { + std::string line; + line.reserve (512 * 1024); + while (getline (in, line)) + contents += line + "\n"; + + in.close (); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Opens if necessary. +void File::read (std::vector & contents) +{ + contents.clear (); + + std::ifstream in (_data.c_str ()); + if (in.good ()) + { + std::string line; + line.reserve (512 * 1024); + while (getline (in, line)) + contents.push_back (line); + + in.close (); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Opens if necessary. +void File::append (const std::string& line) +{ + if (!_fh) + open (); + + if (_fh) + { + fseek (_fh, 0, SEEK_END); + fputs (line.c_str (), _fh); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Opens if necessary. +void File::append (const std::vector & lines) +{ + if (!_fh) + open (); + + if (_fh) + { + fseek (_fh, 0, SEEK_END); + for (auto& line : lines) + fputs (line.c_str (), _fh); + } +} + +//////////////////////////////////////////////////////////////////////////////// +void File::write_raw (const std::string& line) +{ + if (!_fh) + open (); + + if (_fh) + { + fputs (line.c_str (), _fh); + } +} + +//////////////////////////////////////////////////////////////////////////////// +void File::truncate () +{ + if (!_fh) + open (); + + if (_fh) + ftruncate (_h, 0); +} + +//////////////////////////////////////////////////////////////////////////////// +// S_IFMT 0170000 type of file +// S_IFIFO 0010000 named pipe (fifo) +// S_IFCHR 0020000 character special +// S_IFDIR 0040000 directory +// S_IFBLK 0060000 block special +// S_IFREG 0100000 regular +// S_IFLNK 0120000 symbolic link +// S_IFSOCK 0140000 socket +// S_IFWHT 0160000 whiteout +// S_ISUID 0004000 set user id on execution +// S_ISGID 0002000 set group id on execution +// S_ISVTX 0001000 save swapped text even after use +// S_IRUSR 0000400 read permission, owner +// S_IWUSR 0000200 write permission, owner +// S_IXUSR 0000100 execute/search permission, owner +mode_t File::mode () +{ + struct stat s; + if (!stat (_data.c_str (), &s)) + return s.st_mode; + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +size_t File::size () const +{ + struct stat s; + if (!stat (_data.c_str (), &s)) + return s.st_size; + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +time_t File::mtime () const +{ + struct stat s; + if (!stat (_data.c_str (), &s)) + return s.st_mtime; + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +time_t File::ctime () const +{ + struct stat s; + if (!stat (_data.c_str (), &s)) + return s.st_ctime; + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +time_t File::btime () const +{ + struct stat s; + if (!stat (_data.c_str (), &s)) +#ifdef HAVE_ST_BIRTHTIME + return s.st_birthtime; +#else + return s.st_ctime; +#endif + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +bool File::create (const std::string& name, int mode /* = 0640 */) +{ + std::string full_name = expand (name); + std::ofstream out (full_name.c_str ()); + if (out.good ()) + { + out.close (); + chmod (full_name.c_str (), mode); + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool File::read (const std::string& name, std::string& contents) +{ + contents = ""; + + std::ifstream in (name.c_str ()); + if (in.good ()) + { + std::string line; + line.reserve (1024); + while (getline (in, line)) + contents += line + "\n"; + + in.close (); + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool File::read (const std::string& name, std::vector & contents) +{ + contents.clear (); + + std::ifstream in (name.c_str ()); + if (in.good ()) + { + std::string line; + line.reserve (1024); + while (getline (in, line)) + contents.push_back (line); + + in.close (); + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool File::write (const std::string& name, const std::string& contents) +{ + std::ofstream out (expand (name).c_str (), + std::ios_base::out | std::ios_base::trunc); + if (out.good ()) + { + out << contents; + out.close (); + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool File::write ( + const std::string& name, + const std::vector & lines, + bool addNewlines /* = true */) +{ + std::ofstream out (expand (name).c_str (), + std::ios_base::out | std::ios_base::trunc); + if (out.good ()) + { + for (auto& line : lines) + { + out << line; + + if (addNewlines) + out << "\n"; + } + + out.close (); + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool File::remove (const std::string& name) +{ + return unlink (expand (name).c_str ()) == 0 ? true : false; +} + +//////////////////////////////////////////////////////////////////////////////// +Directory::Directory () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +Directory::Directory (const Directory& other) +: File::File (other) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +Directory::Directory (const File& other) +: File::File (other) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +Directory::Directory (const Path& other) +: File::File (other) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +Directory::Directory (const std::string& in) +: File::File (in) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +Directory::~Directory () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +Directory& Directory::operator= (const Directory& other) +{ + if (this != &other) + { + File::operator= (other); + } + + return *this; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Directory::create (int mode /* = 0755 */) +{ + return mkdir (_data.c_str (), mode) == 0 ? true : false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Directory::remove () const +{ + return remove_directory (_data); +} + +//////////////////////////////////////////////////////////////////////////////// +bool Directory::remove_directory (const std::string& dir) const +{ + DIR* dp = opendir (dir.c_str ()); + if (dp != NULL) + { + struct dirent* de; + while ((de = readdir (dp)) != NULL) + { + if (!strcmp (de->d_name, ".") || + !strcmp (de->d_name, "..")) + continue; + +#if defined (SOLARIS) || defined (HAIKU) + struct stat s; + lstat ((dir + "/" + de->d_name).c_str (), &s); + if (S_ISDIR (s.st_mode)) + remove_directory (dir + "/" + de->d_name); + else + unlink ((dir + "/" + de->d_name).c_str ()); +#else + if (de->d_type == DT_UNKNOWN) + { + struct stat s; + lstat ((dir + "/" + de->d_name).c_str (), &s); + if (S_ISDIR (s.st_mode)) + de->d_type = DT_DIR; + } + if (de->d_type == DT_DIR) + remove_directory (dir + "/" + de->d_name); + else + unlink ((dir + "/" + de->d_name).c_str ()); +#endif + } + + closedir (dp); + } + + return rmdir (dir.c_str ()) ? false : true; +} + +//////////////////////////////////////////////////////////////////////////////// +std::vector Directory::list () +{ + std::vector files; + list (_data, files, false); + return files; +} + +//////////////////////////////////////////////////////////////////////////////// +std::vector Directory::listRecursive () +{ + std::vector files; + list (_data, files, true); + return files; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string Directory::cwd () +{ +#ifdef HAVE_GET_CURRENT_DIR_NAME + char *buf = get_current_dir_name (); + if (buf == NULL) + throw std::bad_alloc (); + std::string result (buf); + free (buf); + return result; +#else + char buf[PATH_MAX]; + getcwd (buf, PATH_MAX - 1); + return std::string (buf); +#endif +} + +//////////////////////////////////////////////////////////////////////////////// +bool Directory::up () +{ + if (_data == "/") + return false; + + auto slash = _data.rfind ('/'); + if (slash == 0) + { + _data = "/"; // Root dir should retain the slash. + return true; + } + else if (slash != std::string::npos) + { + _data = _data.substr (0, slash); + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +bool Directory::cd () const +{ + return chdir (_data.c_str ()) == 0 ? true : false; +} + +//////////////////////////////////////////////////////////////////////////////// +void Directory::list ( + const std::string& base, + std::vector & results, + bool recursive) +{ + DIR* dp = opendir (base.c_str ()); + if (dp != NULL) + { + struct dirent* de; + while ((de = readdir (dp)) != NULL) + { + if (!strcmp (de->d_name, ".") || + !strcmp (de->d_name, "..")) + continue; + +#if defined (SOLARIS) || defined (HAIKU) + struct stat s; + stat ((base + "/" + de->d_name).c_str (), &s); + if (recursive && S_ISDIR (s.st_mode)) + list (base + "/" + de->d_name, results, recursive); + else + results.push_back (base + "/" + de->d_name); +#else + if (recursive && de->d_type == DT_UNKNOWN) + { + struct stat s; + lstat ((base + "/" + de->d_name).c_str (), &s); + if (S_ISDIR (s.st_mode)) + de->d_type = DT_DIR; + } + if (recursive && de->d_type == DT_DIR) + list (base + "/" + de->d_name, results, recursive); + else + results.push_back (base + "/" + de->d_name); +#endif + } + + closedir (dp); + } +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/common/FS.h b/src/common/FS.h new file mode 100644 index 00000000..b803d907 --- /dev/null +++ b/src/common/FS.h @@ -0,0 +1,146 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2015, Paul Beckingham, Federico Hernandez. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// http://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDED_FS +#define INCLUDED_FS + +#include +#include +#include +#include + +class Path +{ +public: + Path (); + Path (const Path&); + Path (const std::string&); + virtual ~Path (); + + Path& operator= (const Path&); + bool operator== (const Path&); + Path& operator+= (const std::string&); + operator std::string () const; + + std::string name () const; + std::string parent () const; + std::string extension () const; + bool exists () const; + bool is_directory () const; + bool is_absolute () const; + bool is_link () const; + bool readable () const; + bool writable () const; + bool executable () const; + bool rename (const std::string&); + + // Statics + static std::string expand (const std::string&); + static std::vector glob (const std::string&); + +public: + std::string _original; + std::string _data; +}; + +class File : public Path +{ +public: + File (); + File (const Path&); + File (const File&); + File (const std::string&); + virtual ~File (); + + File& operator= (const File&); + + virtual bool create (int mode = 0640); + virtual bool remove () const; + + bool open (); + void close (); + + bool lock (); + void unlock (); + + void read (std::string&); + void read (std::vector &); + + void append (const std::string&); + void append (const std::vector &); + void write_raw (const std::string&); + + void truncate (); + + virtual mode_t mode (); + virtual size_t size () const; + virtual time_t mtime () const; + virtual time_t ctime () const; + virtual time_t btime () const; + + static bool create (const std::string&, int mode = 0640); + static bool read (const std::string&, std::string&); + static bool read (const std::string&, std::vector &); + static bool write (const std::string&, const std::string&); + static bool write (const std::string&, const std::vector &, bool addNewlines = true); + static bool remove (const std::string&); + +private: + FILE* _fh; + int _h; + bool _locked; +}; + +class Directory : public File +{ +public: + Directory (); + Directory (const Directory&); + Directory (const File&); + Directory (const Path&); + Directory (const std::string&); + virtual ~Directory (); + + Directory& operator= (const Directory&); + + virtual bool create (int mode = 0755); + virtual bool remove () const; + + std::vector list (); + std::vector listRecursive (); + + static std::string cwd (); + bool up (); + bool cd () const; + +private: + void list (const std::string&, std::vector &, bool); + bool remove_directory (const std::string&) const; +}; + +#endif +//////////////////////////////////////////////////////////////////////////////// + diff --git a/test/.gitignore b/test/.gitignore index b017134e..43aaf6a6 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,5 +1,6 @@ all.log color.t +fs.t lexer.t rx.t text.t diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ef0c94c8..b6c71912 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -9,7 +9,7 @@ include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/test ${TIMEW_INCLUDE_DIRS}) -set (test_SRCS color.t lexer.t rx.t text.t utf8.t) +set (test_SRCS color.t fs.t lexer.t rx.t text.t utf8.t) add_custom_target (test ./run_all --verbose DEPENDS ${test_SRCS} timew_executable diff --git a/test/fs.t.cpp b/test/fs.t.cpp new file mode 100644 index 00000000..b0d49e26 --- /dev/null +++ b/test/fs.t.cpp @@ -0,0 +1,290 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2015, Paul Beckingham, Federico Hernandez. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// http://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +int main (int, char**) +{ + UnitTest t (112); + + // Path (); + Path p0; + t.is (p0._data, "", "Path::Path"); + + // Path (const Path&); + Path p1 = Path ("foo"); + t.is (p1._data, Directory::cwd () + "/foo", "Path::operator="); + + // Path (const std::string&); + Path p2 ("~"); + t.ok (p2._data != "~", "~ expanded to " + p2._data); + + Path p3 ("/tmp"); + t.ok (p3._data == "/tmp", "/tmp -> /tmp"); + + // operator== + t.notok (p2 == p3, "p2 != p3"); + + // Path& operator= (const Path&); + Path p3_copy (p3); + t.is (p3._data, p3_copy._data, "Path::Path (Path&)"); + + // operator (std::string) const; + t.is ((std::string) p3, "/tmp", "Path::operator (std::string) const"); + + // std::string name () const; + Path p4 ("/a/b/c/file.ext"); + t.is (p4.name (), "file.ext", "/a/b/c/file.ext name is file.ext"); + + // std::string parent () const; + t.is (p4.parent (), "/a/b/c", "/a/b/c/file.ext parent is /a/b/c"); + + // std::string extension () const; + t.is (p4.extension (), "ext", "/a/b/c/file.ext extension is ext"); + + // bool exists () const; + t.ok (p2.exists (), "~ exists"); + t.ok (p3.exists (), "/tmp exists"); + + // bool is_directory () const; + t.ok (p2.is_directory (), "~ is_directory"); + t.ok (p3.is_directory (), "/tmp is_directory"); + + // bool is_link () const; + t.notok (p2.is_link (), "~ !is_link"); + + // bool readable () const; + t.ok (p2.readable (), "~ readable"); + t.ok (p3.readable (), "/tmp readable"); + + // bool writable () const; + t.ok (p2.writable (), "~ writable"); + t.ok (p3.writable (), "/tmp writable"); + + // bool executable () const; + t.ok (p2.executable (), "~ executable"); + t.ok (p3.executable (), "/tmp executable"); + + // static std::string expand (const std::string&); + t.ok (Path::expand ("~") != "~", "Path::expand ~ != ~"); + t.ok (Path::expand ("~/") != "~/", "Path::expand ~/ != ~/"); + + // static std::vector glob (const std::string&); + std::vector out = Path::glob ("/tmp"); + t.ok (out.size () == 1, "/tmp -> 1 result"); + t.is (out[0], "/tmp", "/tmp -> /tmp"); + + out = Path::glob ("/t?p"); + t.ok (out.size () == 1, "/t?p -> 1 result"); + t.is (out[0], "/tmp", "/t?p -> /tmp"); + + out = Path::glob ("/[s-u]mp"); + t.ok (out.size () == 1, "/[s-u]mp -> 1 result"); + t.is (out[0], "/tmp", "/[s-u]mp -> /tmp"); + + // bool is_absolute () const; + t.notok (p0.is_absolute (), "'' !is_absolute"); + t.ok (p1.is_absolute (), "foo is_absolute"); + t.ok (p2.is_absolute (), "~ is_absolute (after expansion)"); + t.ok (p3.is_absolute (), "/tmp is_absolute"); + t.ok (p4.is_absolute (), "/a/b/c/file.ext is_absolute"); + + Path p5 ("~/file.ext"); + t.notok (p5.name () == "~/file.ext", "~/file.ext --> ! ~/file.ext"); + + Directory tmp ("tmp"); + tmp.create (); + t.ok (tmp.exists (), "tmp dir created."); + + File::write ("tmp/file.t.txt", "This is a test\n"); + File f6 ("tmp/file.t.txt"); + t.ok (f6.size () == 15, "File::size tmp/file.t.txt good"); + t.ok (f6.mode () & S_IRUSR, "File::mode tmp/file.t.txt good"); + t.ok (File::remove ("tmp/file.t.txt"), "File::remove tmp/file.t.txt good"); + + // operator (std::string) const; + t.is ((std::string) f6, Directory::cwd () + "/tmp/file.t.txt", "File::operator (std::string) const"); + + t.ok (File::create ("tmp/file.t.create"), "File::create tmp/file.t.create good"); + t.ok (File::remove ("tmp/file.t.create"), "File::remove tmp/file.t.create good"); + + // basename (std::string) const; + t.is (f6.name (), "file.t.txt", "File::basename tmp/file.t.txt --> file.t.txt"); + + // dirname (std::string) const; + t.is (f6.parent (), Directory::cwd () + "/tmp", "File::dirname tmp/file.t.txt --> tmp"); + + // bool rename (const std::string&); + File f7 ("tmp/file.t.2.txt"); + f7.append ("something\n"); + f7.close (); + + t.ok (f7.rename ("tmp/file.t.3.txt"), "File::rename did not fail"); + t.is (f7._data, Directory::cwd () + "/tmp/file.t.3.txt", "File::rename stored new name"); + t.ok (f7.exists (), "File::rename new file exists"); + t.ok (f7.remove (), "File::remove tmp/file.t.3.txt good"); + t.notok (f7.exists (), "File::remove new file no longer exists"); + + // Test permissions. + File f8 ("tmp/file.t.perm.txt"); + f8.create (0744); + t.ok (f8.exists (), "File::create perm file exists"); + mode_t m = f8.mode (); + t.ok (m & S_IFREG, "File::mode tmp/file.t.perm.txt S_IFREG good"); + t.ok (m & S_IRUSR, "File::mode tmp/file.t.perm.txt r-------- good"); + t.ok (m & S_IWUSR, "File::mode tmp/file.t.perm.txt -w------- good"); + t.ok (m & S_IXUSR, "File::mode tmp/file.t.perm.txt --x------ good"); + t.ok (m & S_IRGRP, "File::mode tmp/file.t.perm.txt ---r----- good"); + t.notok (m & S_IWGRP, "File::mode tmp/file.t.perm.txt ----w---- good"); + t.notok (m & S_IXGRP, "File::mode tmp/file.t.perm.txt -----x--- good"); + t.ok (m & S_IROTH, "File::mode tmp/file.t.perm.txt ------r-- good"); + t.notok (m & S_IWOTH, "File::mode tmp/file.t.perm.txt -------w- good"); + t.notok (m & S_IXOTH, "File::mode tmp/file.t.perm.txt --------x good"); + f8.remove (); + t.notok (f8.exists (), "File::remove perm file no longer exists"); + + tmp.remove (); + t.notok (tmp.exists (), "tmp dir removed."); + tmp.create (); + t.ok (tmp.exists (), "tmp dir created."); + + // Directory (const File&); + // Directory (const Path&); + Directory d0 (Path ("tmp")); + Directory d1 (File ("tmp")); + Directory d2 (File (Path ("tmp"))); + t.is (d0._data, d1._data, "Directory(std::string) == Directory (File&)"); + t.is (d0._data, d2._data, "Directory(std::string) == Directory (File (Path &))"); + t.is (d1._data, d2._data, "Directory(File&)) == Directory (File (Path &))"); + + // Directory (const Directory&); + Directory d3 (d2); + t.is (d3._data, Directory::cwd () + "/tmp", "Directory (Directory&)"); + + // Directory (const std::string&); + Directory d4 ("tmp/test_directory"); + + // Directory& operator= (const Directory&); + Directory d5 = d4; + t.is (d5._data, Directory::cwd () + "/tmp/test_directory", "Directory::operator="); + + // operator (std::string) const; + t.is ((std::string) d3, Directory::cwd () + "/tmp", "Directory::operator (std::string) const"); + + // virtual bool create (); + t.ok (d5.create (), "Directory::create tmp/test_directory"); + t.ok (d5.exists (), "Directory::exists tmp/test_directory"); + + Directory d6 (d5._data + "/dir"); + t.ok (d6.create (), "Directory::create tmp/test_directory/dir"); + + File::create (d5._data + "/f0"); + File::create (d6._data + "/f1"); + + // std::vector list (); + std::vector files = d5.list (); + std::sort (files.begin (), files.end ()); + t.is ((int)files.size (), 2, "Directory::list 1 file"); + t.is (files[0], Directory::cwd () + "/tmp/test_directory/dir", "file[0] is tmp/test_directory/dir"); + t.is (files[1], Directory::cwd () + "/tmp/test_directory/f0", "file[1] is tmp/test_directory/f0"); + + // std::vector listRecursive (); + files = d5.listRecursive (); + std::sort (files.begin (), files.end ()); + t.is ((int)files.size (), 2, "Directory::list 1 file"); + t.is (files[0], Directory::cwd () + "/tmp/test_directory/dir/f1", "file is tmp/test_directory/dir/f1"); + t.is (files[1], Directory::cwd () + "/tmp/test_directory/f0", "file is tmp/test_directory/f0"); + + // virtual bool remove (); + t.ok (File::remove (d5._data + "/f0"), "File::remove tmp/test_directory/f0"); + t.ok (File::remove (d6._data + "/f1"), "File::remove tmp/test_directory/dir/f1"); + + t.ok (d6.remove (), "Directory::remove tmp/test_directory/dir"); + t.notok (d6.exists (), "Directory::exists tmp/test_directory/dir - no"); + + t.ok (d5.remove (), "Directory::remove tmp/test_directory"); + t.notok (d5.exists (), "Directory::exists tmp/test_directory - no"); + + // bool remove (const std::string&); + Directory d7 ("tmp/to_be_removed"); + t.ok (d7.create (), "Directory::create tmp/to_be_removed"); + File::create (d7._data + "/f0"); + Directory d8 (d7._data + "/another"); + t.ok (d8.create (), "Directory::create tmp/to_be_removed/another"); + File::create (d8._data + "/f1"); + t.ok (d7.remove (), "Directory::remove tmp/to_be_removed"); + t.notok (d7.exists (), "Directory tmp/to_be_removed gone"); + + // static std::string cwd (); + std::string cwd = Directory::cwd (); + t.ok (cwd.length () > 0, "Directory::cwd returned a value"); + + // bool parent (std::string&) const; + Directory d9 ("/one/two/three/four.txt"); + t.ok (d9.up (), "parent /one/two/three/four.txt --> true"); + t.is (d9._data, "/one/two/three", "parent /one/two/three/four.txt --> /one/two/three"); + t.ok (d9.up (), "parent /one/two/three --> true"); + t.is (d9._data, "/one/two", "parent /one/two/three --> /one/two"); + t.ok (d9.up (), "parent /one/two --> true"); + t.is (d9._data, "/one", "parent /one/two --> /one"); + t.ok (d9.up (), "parent /one --> true"); + t.is (d9._data, "/", "parent /one --> /"); + t.notok (d9.up (), "parent / --> false"); + + // Test permissions. + umask (0022); + Directory d10 ("tmp/dir.perm"); + d10.create (0750); + t.ok (d10.exists (), "Directory::create perm file exists"); + m = d10.mode (); + t.ok (m & S_IFDIR, "Directory::mode tmp/dir.perm S_IFDIR good"); + t.ok (m & S_IRUSR, "Directory::mode tmp/dir.perm r-------- good"); + t.ok (m & S_IWUSR, "Directory::mode tmp/dir.perm -w------- good"); + t.ok (m & S_IXUSR, "Directory::mode tmp/dir.perm --x------ good"); + t.ok (m & S_IRGRP, "Directory::mode tmp/dir.perm ---r----- good"); + t.notok (m & S_IWGRP, "Directory::mode tmp/dir.perm ----w---- good"); + t.ok (m & S_IXGRP, "Directory::mode tmp/dir.perm -----x--- good"); + t.notok (m & S_IROTH, "Directory::mode tmp/dir.perm ------r-- good"); + t.notok (m & S_IWOTH, "Directory::mode tmp/dir.perm -------w- good"); + t.notok (m & S_IXOTH, "Directory::mode tmp/dir.perm --------x good"); + d10.remove (); + t.notok (d10.exists (), "Directory::remove temp/dir.perm file no longer exists"); + + // Directory::cd + Directory d11 ("/tmp"); + t.ok (d11.cd (), "Directory::cd /tmp good"); + + tmp.remove (); + t.notok (tmp.exists (), "tmp dir removed."); + + return 0; +} + +////////////////////////////////////////////////////////////////////////////////