-////////////////////////////////////////////////////////////////////////////////\r
-\r
-// Author: Andy Rushton\r
-// Copyright: (c) Southampton University 1999-2004\r
-// (c) Andy Rushton 2004-2009\r
-// License: BSD License, see ../docs/license.html\r
-\r
-// This is a portable interface to the file system.\r
-\r
-// The idea is that you write all file system access code using these functions,\r
-// which are ported to all platforms that we are interested in. Therefore your\r
-// code is inherently portable.\r
-\r
-// Native Windows version: switched on by macro _WIN32 which is defined by VC++/Borland/Mingw compilers\r
-// Unix/Gnu version: default variant, no compiler directives are required but _WIN32 must be absent\r
-// Cygwin/Gnu version: as Unix version but with additional support for Windows drive letters\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-#include "file_system.hpp"\r
-#include "wildcard.hpp"\r
-#include <stdio.h>\r
-#include <stdlib.h>\r
-#include <time.h>\r
-#include <algorithm>\r
-#include <ctype.h>\r
-\r
-#ifdef MSWINDOWS\r
-#include <windows.h>\r
-#include <dos.h>\r
-#include <direct.h>\r
-#include <fcntl.h>\r
-#include <io.h>\r
-#include <sys/stat.h>\r
-#include <sys/types.h>\r
-#else\r
-#include <dirent.h>\r
-#include <fcntl.h>\r
-#include <sys/param.h>\r
-#include <unistd.h>\r
-#include <sys/stat.h>\r
-#include <sys/types.h>\r
-#endif\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-\r
-namespace stlplus\r
-{\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-// definitions of separators\r
-\r
-#ifdef MSWINDOWS\r
- static const char* separator_set = "\\/";\r
- static const char preferred_separator = '\\';\r
-#else\r
- static const char* separator_set = "/";\r
- static const char preferred_separator = '/';\r
-#endif\r
-\r
- static bool is_separator (char ch)\r
- {\r
- for (int i = 0; separator_set[i]; i++)\r
- {\r
- if (separator_set[i] == ch)\r
- return true;\r
- }\r
- return false;\r
- }\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-// implement string comparison of paths - Unix is case-sensitive, Windoze is case-insensitive\r
-\r
-#ifdef MSWINDOWS\r
-\r
- static std::string lowercase(const std::string& val)\r
- {\r
- std::string text = val;\r
- for (unsigned i = 0; i < text.size(); i++)\r
- text[i] = tolower(text[i]);\r
- return text;\r
- }\r
-\r
-#endif\r
-\r
- bool path_compare(const std::string& l, const std::string& r)\r
- {\r
-#ifdef MSWINDOWS\r
- return lowercase(l) == lowercase(r);\r
-#else\r
- return l == r;\r
-#endif\r
- }\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-// Internal data structure used to hold the different parts of a filespec\r
-\r
- class file_specification\r
- {\r
- private:\r
- bool m_relative; // true = relative, false = absolute\r
- std::string m_drive; // drive - drive letter (e.g. "c:") or the path for an UNC (e.g. "\\somewhere")\r
- // empty if not known or on Unix\r
- std::vector<std::string> m_path; // the subdirectory path to follow from the drive\r
- std::string m_filename; // the filename\r
- public:\r
- file_specification(void) : m_relative(false) {}\r
- ~file_specification(void) {}\r
-\r
- bool initialise_folder(const std::string& spec);\r
- bool initialise_file(const std::string& spec);\r
- bool simplify(void);\r
- bool make_absolute(const std::string& root = folder_current_full());\r
- bool make_absolute(const file_specification& root);\r
- bool make_relative(const std::string& root = folder_current_full());\r
- bool make_relative(const file_specification& root);\r
- bool relative(void) const {return m_relative;}\r
- bool absolute(void) const {return !relative();}\r
- void set_relative(void) {m_relative = true;}\r
- void set_absolute(void) {m_relative = false;}\r
-\r
- const std::string& drive(void) const {return m_drive;}\r
- std::string& drive(void) {return m_drive;}\r
- void set_drive(const std::string& drive) {m_drive = drive;}\r
-\r
- const std::vector<std::string>& path(void) const {return m_path;}\r
- std::vector<std::string>& path(void) {return m_path;}\r
- void set_path(const std::vector<std::string>& path) {m_path = path;}\r
-\r
- void add_subpath(const std::string& subpath) {m_path.push_back(subpath);}\r
- unsigned subpath_size(void) const {return m_path.size();}\r
- const std::string& subpath_element(unsigned i) const {return m_path[i];}\r
- void subpath_erase(unsigned i) {m_path.erase(m_path.begin()+i);}\r
-\r
- const std::string& file(void) const {return m_filename;}\r
- std::string& file(void) {return m_filename;}\r
- void set_file(const std::string& file) {m_filename = file;}\r
-\r
- std::string image(void) const;\r
- };\r
-\r
- bool file_specification::initialise_folder(const std::string& folder_spec)\r
- {\r
- std::string spec = folder_spec;\r
- m_relative = true;\r
- m_drive.erase();\r
- m_path.clear();\r
- m_filename.erase();\r
- unsigned i = 0;\r
-#ifdef MSWINDOWS\r
- // first split off the drive letter or UNC prefix on Windows\r
- if (spec.size() >= 2 && isalpha(spec[0]) && spec[1] == ':')\r
- {\r
- // found a drive letter\r
- i = 2;\r
- m_drive = spec.substr(0, 2);\r
- m_relative = false;\r
- // if there is a drive but no path or a relative path, get the current\r
- // path for this drive and prepend it to the path\r
- if (i == spec.size() || !is_separator(spec[i]))\r
- {\r
- // getdcwd requires the drive number (1..26) not the letter (A..Z)\r
- char path [MAX_PATH+1];\r
- int drivenum = toupper(m_drive[0]) - 'A' + 1;\r
- if (_getdcwd(drivenum, path, MAX_PATH+1))\r
- {\r
- // the path includes the drive so we have the drive info twice\r
- // need to prepend this absolute path to the spec such that any remaining relative path is still retained\r
- if (!is_separator(path[strlen(path)-1])) spec.insert(2, 1, preferred_separator);\r
- spec.insert(2, path+2);\r
- }\r
- else\r
- {\r
- // non-existent drive - fill in just the root directory\r
- spec.insert(2, 1, preferred_separator);\r
- }\r
- }\r
- }\r
- else if (spec.size() >= 2 && is_separator(spec[0]) && is_separator(spec[1]))\r
- {\r
- // found an UNC prefix\r
- i = 2;\r
- // find the end of the prefix by scanning for the next seperator or the end of the spec\r
- while (i < spec.size() && !is_separator(spec[i])) i++;\r
- m_drive = spec.substr(0, i);\r
- m_relative = false;\r
- }\r
-#endif\r
-#ifdef CYGWIN\r
- // first split off the drive letter or UNC prefix on Windows - the Cygwin environment supports these too\r
- if (spec.size() >= 2 && isalpha(spec[0]) && spec[1] == ':')\r
- {\r
- // found a drive letter\r
- i = 2;\r
- m_drive = spec.substr(0, 2);\r
- m_relative = false;\r
- // if there is a drive but no path or a relative path, get the current\r
- // path for this drive and prepend it to the path\r
- if (i == spec.size() || !is_separator(spec[i]))\r
- {\r
- // non-existent drive - fill in just the root directory\r
- spec.insert(2, 1, preferred_separator);\r
- }\r
- }\r
- else if (spec.size() >= 2 && is_separator(spec[0]) && is_separator(spec[1]))\r
- {\r
- // found an UNC prefix\r
- i = 2;\r
- // find the end of the prefix by scanning for the next seperator or the end of the spec\r
- while (i < spec.size() && !is_separator(spec[i])) i++;\r
- m_drive = spec.substr(0, i);\r
- m_relative = false;\r
- }\r
-#endif\r
- // check whether the path is absolute or relative and discard the leading / if absolute\r
- if (i < spec.size() && is_separator(spec[i]))\r
- {\r
- m_relative = false;\r
- i++;\r
-#ifdef MSWINDOWS\r
- // if there's no drive, fill it in on Windows since absolute paths must have a drive\r
- if (m_drive.empty())\r
- {\r
- m_drive += (char)(_getdrive() - 1 + 'A');\r
- m_drive += ':';\r
- }\r
-#endif\r
- }\r
- // now extract the path elements - note that a trailing / is not significant since /a/b/c/ === /a/b/c\r
- // also note that the leading / has been discarded - all paths are relative\r
- // if absolute() is set, then paths are relative to the drive, else they are relative to the current path\r
- unsigned start = i;\r
- while(i <= spec.size())\r
- {\r
- if (i == spec.size())\r
- {\r
- // path element terminated by the end of the string\r
- // discard this element if it is zero length because that represents the trailing /\r
- if (i != start)\r
- m_path.push_back(spec.substr(start, i-start));\r
- }\r
- else if (is_separator(spec[i]))\r
- {\r
- // path element terminated by a separator\r
- m_path.push_back(spec.substr(start, i-start));\r
- start = i+1;\r
- }\r
- i++;\r
- }\r
- // TODO - some error handling?\r
- return true;\r
- }\r
-\r
- bool file_specification::initialise_file(const std::string& spec)\r
- {\r
- m_filename.erase();\r
- // remove last element as the file and then treat the rest as a folder\r
- unsigned i = spec.size();\r
- while (--i)\r
- {\r
- if (is_separator(spec[i]))\r
- break;\r
-#ifdef MSWINDOWS\r
- // on windoze you can say a:fred.txt so the colon separates the path from the filename\r
- else if (i == 1 && spec[i] == ':')\r
- break;\r
-#endif\r
- }\r
- bool result = initialise_folder(spec.substr(0,i+1));\r
- m_filename = spec.substr(i+1,spec.size()-i-1);\r
- // TODO - some error handling?\r
- return result;\r
- }\r
-\r
- bool file_specification::simplify(void)\r
- {\r
- // simplify the path by removing unnecessary . and .. entries - Note that zero-length entries are treated like .\r
- for (unsigned i = 0; i < m_path.size(); )\r
- {\r
- if (m_path[i].empty() || m_path[i].compare(".") == 0)\r
- {\r
- // found . or null\r
- // these both mean do nothing - so simply delete this element\r
- m_path.erase(m_path.begin()+i);\r
- }\r
- else if (m_path[i].compare("..") == 0)\r
- {\r
- // found ..\r
- if (i == 0 && !m_relative)\r
- {\r
- // up from the root does nothing so can be deleted\r
- m_path.erase(m_path.begin()+i);\r
- i++;\r
- }\r
- else if (i == 0 || m_path[i-1].compare("..") == 0)\r
- {\r
- // the first element of a relative path or the previous element is .. then keep it\r
- i++;\r
- }\r
- else\r
- {\r
- // otherwise delete this element and the previous one\r
- m_path.erase(m_path.begin()+i);\r
- m_path.erase(m_path.begin()+i-1);\r
- i--;\r
- }\r
- }\r
- // keep all other elements\r
- else\r
- i++;\r
- }\r
- // TODO - error checking?\r
- return true;\r
- }\r
-\r
- bool file_specification::make_absolute(const std::string& root)\r
- {\r
- // test whether already an absolute path in which case there's nothing to do\r
- if (absolute()) return true;\r
- // now simply call the other version of make_absolute\r
- file_specification rootspec;\r
- rootspec.initialise_folder(root);\r
- return make_absolute(rootspec);\r
- }\r
-\r
- bool file_specification::make_absolute(const file_specification& rootspec)\r
- {\r
- // test whether already an absolute path in which case there's nothing to do\r
- if (absolute()) return true;\r
- // initialise the result with the root and make the root absolute\r
- file_specification result = rootspec;\r
- result.make_absolute();\r
- // now append this's relative path and filename to the root's absolute path\r
- for (unsigned i = 0; i < subpath_size(); i++)\r
- result.add_subpath(subpath_element(i));\r
- result.set_file(file());\r
- // now the result is the absolute path, so transfer it to this\r
- *this = result;\r
- // and simplify to get rid of any unwanted .. or . elements\r
- simplify();\r
- return true;\r
- }\r
-\r
- bool file_specification::make_relative(const std::string& root)\r
- {\r
- // test whether already an relative path in which case there's nothing to do\r
- if (relative()) return true;\r
- // now simply call the other version of make_relative\r
- file_specification rootspec;\r
- rootspec.initialise_folder(root);\r
- return make_relative(rootspec);\r
- }\r
-\r
- bool file_specification::make_relative(const file_specification& rootspec)\r
- {\r
- // test whether already an relative path in which case there's nothing to do\r
- if (relative()) return true;\r
- // initialise the result with the root and make the root absolute\r
- file_specification absolute_root = rootspec;\r
- absolute_root.make_absolute();\r
- // now compare elements of the absolute root with elements of this to find the common path\r
- // if the drives are different, no conversion can take place and the result must be absolute, else clear the drive\r
- if (!path_compare(drive(), absolute_root.drive())) return true;\r
- set_drive("");\r
- // first remove leading elements that are identical to the corresponding element in root\r
- unsigned i = 0;\r
- while(subpath_size() > 0 && \r
- i < absolute_root.subpath_size() && \r
- path_compare(subpath_element(0), absolute_root.subpath_element(i)))\r
- {\r
- subpath_erase(0);\r
- i++;\r
- }\r
- // now add a .. prefix for every element in root that is different from this\r
- while (i < absolute_root.subpath_size())\r
- {\r
- m_path.insert(m_path.begin(), "..");\r
- i++;\r
- }\r
- set_relative();\r
- return true;\r
- }\r
-\r
- std::string file_specification::image(void) const\r
- {\r
- std::string result = m_drive;\r
- if (absolute())\r
- result += preferred_separator;\r
- if (!m_path.empty())\r
- {\r
- for (unsigned i = 0; i < m_path.size(); i++)\r
- {\r
- if (i != 0) result += std::string(1,preferred_separator);\r
- result += m_path[i];\r
- }\r
- }\r
- else if (relative())\r
- result += '.';\r
- // add a trailing / to the last directory element\r
- if (result.empty() || !is_separator(result[result.size()-1]))\r
- result += preferred_separator;\r
- if (!m_filename.empty())\r
- result += m_filename;\r
- return result;\r
- }\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-// classifying functions\r
-\r
-#ifdef MSWINDOWS\r
-// file type tests are not defined for some reason on Windows despite them providing the stat() function!\r
-#define R_OK 4\r
-#define W_OK 2\r
-#endif\r
-\r
- bool is_present (const std::string& thing)\r
- {\r
- // strip off any trailing separator because that will cause the stat function to fail\r
- std::string path = thing;\r
- if (!path.empty() && is_separator(path[path.size()-1]))\r
- path.erase(path.size()-1,1);\r
- // now test if this thing exists using the built-in stat function\r
- struct stat buf;\r
- return stat(path.c_str(), &buf) == 0;\r
- }\r
-\r
- bool is_folder (const std::string& thing)\r
- {\r
- // strip off any trailing separator because that will cause the stat function to fail\r
- std::string path = thing;\r
- if (!path.empty() && is_separator(path[path.size()-1]))\r
- path.erase(path.size()-1,1);\r
- // now test if this thing exists using the built-in stat function and if so, is it a folder\r
- struct stat buf;\r
- if (!(stat(path.c_str(), &buf) == 0)) {return false;}\r
- return (buf.st_mode & S_IFDIR) != 0;\r
- }\r
-\r
- bool is_file (const std::string& thing)\r
- {\r
- // strip off any trailing separator because that will cause the stat function to fail\r
- std::string path = thing;\r
- if (!path.empty() && is_separator(path[path.size()-1]))\r
- path.erase(path.size()-1,1);\r
- // now test if this thing exists using the built-in stat function and if so, is it a file\r
- struct stat buf;\r
- if (!(stat(path.c_str(), &buf) == 0)) {return false;}\r
- return (buf.st_mode & S_IFREG) != 0;\r
- }\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-// file functions\r
-\r
- bool file_exists (const std::string& filespec)\r
- {\r
- return is_file(filespec);\r
- }\r
-\r
- bool file_readable (const std::string& filespec)\r
- {\r
- // a file is readable if it exists and can be read\r
- if (!file_exists(filespec)) return false;\r
- return access(filespec.c_str(),R_OK)==0;\r
- }\r
-\r
- bool file_writable (const std::string& filespec)\r
- {\r
- // a file is writable if it exists as a file and is writable or if it doesn't exist but could be created and would be writable\r
- if (is_present(filespec))\r
- {\r
- if (!is_file(filespec)) return false;\r
- return access(filespec.c_str(),W_OK)==0;\r
- }\r
- std::string dir = folder_part(filespec);\r
- if (dir.empty()) dir = ".";\r
- return folder_writable(dir);\r
- }\r
-\r
- size_t file_size (const std::string& filespec)\r
- {\r
- struct stat buf;\r
- if (!(stat(filespec.c_str(), &buf) == 0)) return 0;\r
- return buf.st_size;\r
- }\r
-\r
- bool file_delete (const std::string& filespec)\r
- {\r
- if (!is_file(filespec)) return false;\r
- return remove(filespec.c_str())==0;\r
- }\r
-\r
- bool file_rename (const std::string& old_filespec, const std::string& new_filespec)\r
- {\r
- if (!is_file(old_filespec)) return false;\r
- return rename(old_filespec.c_str(), new_filespec.c_str())==0;\r
- }\r
-\r
- bool file_copy (const std::string& old_filespec, const std::string& new_filespec)\r
- {\r
- if (!is_file(old_filespec)) return false;\r
- // do an exact copy - to do this, use binary mode\r
- bool result = true;\r
- FILE* old_file = fopen(old_filespec.c_str(),"rb");\r
- FILE* new_file = fopen(new_filespec.c_str(),"wb");\r
- if (!old_file)\r
- result = false;\r
- else if (!new_file)\r
- result = false;\r
- else\r
- {\r
- for (int byte = getc(old_file); byte != EOF; byte = getc(old_file))\r
- putc(byte,new_file);\r
- }\r
- if (old_file) fclose(old_file);\r
- if (new_file) fclose(new_file);\r
- return result;\r
- }\r
-\r
- bool file_move (const std::string& old_filespec, const std::string& new_filespec)\r
- {\r
- // try to move the file by renaming - if that fails then do a copy and delete the original\r
- if (file_rename(old_filespec, new_filespec))\r
- return true;\r
- if (!file_copy(old_filespec, new_filespec))\r
- return false;\r
- // I'm not sure what to do if the delete fails - is that an error?\r
- // I've made it an error and then delete the copy so that the original state is recovered\r
- if (file_delete(old_filespec))\r
- return true;\r
- file_delete(new_filespec);\r
- return false;\r
- }\r
-\r
- time_t file_created (const std::string& filespec)\r
- {\r
- struct stat buf;\r
- if (!(stat(filespec.c_str(), &buf) == 0)) return 0;\r
- return buf.st_ctime;\r
- }\r
-\r
- time_t file_modified (const std::string& filespec)\r
- {\r
- struct stat buf;\r
- if (!(stat(filespec.c_str(), &buf) == 0)) return 0;\r
- return buf.st_mtime;\r
- }\r
-\r
- time_t file_accessed (const std::string& filespec)\r
- {\r
- struct stat buf;\r
- if (!(stat(filespec.c_str(), &buf) == 0)) return 0;\r
- return buf.st_atime;\r
- }\r
-\r
- std::string create_filespec (const std::string& directory, const std::string& filename)\r
- {\r
- std::string result = directory;\r
- // if directory is empty then no directory part will be added\r
- // add trailing slash if the directory was specified and does not have a trailing slash\r
- if (!result.empty() && !is_separator(result[result.size()-1]))\r
- result += preferred_separator;\r
- // if filename is null or empty, nothing will be added so the path is then a directory path\r
- result += filename;\r
- return result;\r
- }\r
-\r
- std::string create_filespec (const std::string& directory, const std::string& basename, const std::string& extension)\r
- {\r
- return create_filespec(directory, create_filename(basename, extension));\r
- }\r
-\r
- std::string create_filename(const std::string& basename, const std::string& extension)\r
- {\r
- std::string name = basename;\r
- // extension is optional - so the dot is also optional\r
- if (!extension.empty())\r
- {\r
- if (extension[0] != '.') name += '.';\r
- name += extension;\r
- }\r
- return name;\r
- }\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-// folder functions\r
-\r
- bool folder_create (const std::string& directory)\r
- {\r
-#ifdef MSWINDOWS\r
- return mkdir(directory.c_str()) == 0;\r
-#else\r
- return mkdir(directory.c_str(), 0777) == 0;\r
-#endif\r
- }\r
-\r
- bool folder_exists (const std::string& directory)\r
- {\r
- return is_folder(directory);\r
- }\r
-\r
- bool folder_readable (const std::string& directory)\r
- {\r
- // a folder is readable if it exists and has read access\r
- std::string dir = directory;\r
- if (dir.empty()) dir = ".";\r
- if (!folder_exists(dir)) return false;\r
- return access(dir.c_str(),R_OK)==0;\r
- }\r
-\r
- bool folder_writable (const std::string& directory)\r
- {\r
- // a folder is writable if it exists and has write access\r
- std::string dir = directory;\r
- if (dir.empty()) dir = ".";\r
- if (!folder_exists(dir)) return false;\r
- return access(dir.c_str(),W_OK)==0;\r
- }\r
-\r
- bool folder_delete (const std::string& directory, bool recurse)\r
- {\r
- std::string dir = directory;\r
- if (dir.empty()) dir = ".";\r
- if (!folder_exists(dir)) return false;\r
- bool result = true;\r
- // depth-first traversal ensures that directory contents are deleted before trying to delete the directory itself\r
- if (recurse)\r
- {\r
- std::vector<std::string> subdirectories = folder_subdirectories(dir);\r
- for (std::vector<std::string>::size_type d = 0; d < subdirectories.size(); ++d)\r
- if (!folder_delete(folder_down(dir,subdirectories[d]),true)) \r
- result = false;\r
- std::vector<std::string> files = folder_files(dir);\r
- for (std::vector<std::string>::size_type f = 0; f < files.size(); ++f)\r
- if (!file_delete(create_filespec(dir, files[f]))) \r
- result = false;\r
- }\r
- if (rmdir(dir.c_str())!=0) result = false;\r
- return result;\r
- }\r
-\r
- bool folder_rename (const std::string& old_directory, const std::string& new_directory)\r
- {\r
- if (!folder_exists(old_directory)) return false;\r
- return rename(old_directory.c_str(), new_directory.c_str())==0;\r
- }\r
-\r
- bool folder_empty(const std::string& directory)\r
- {\r
- std::string dir = directory.empty() ? std::string(".") : directory;\r
- bool result = true;\r
-#ifdef MSWINDOWS\r
- std::string wildcard = create_filespec(dir, "*.*");\r
- long handle = -1;\r
- _finddata_t fileinfo;\r
- for (bool OK = (handle = _findfirst((char*)wildcard.c_str(), &fileinfo)) != -1; OK; OK = (_findnext(handle, &fileinfo)==0))\r
- {\r
- std::string strentry = fileinfo.name;\r
- if (strentry.compare(".")!=0 && strentry.compare("..")!=0)\r
- {\r
- result = false;\r
- break;\r
- }\r
- }\r
- _findclose(handle);\r
-#else\r
- DIR* d = opendir(dir.c_str());\r
- if (d)\r
- {\r
- for (dirent* entry = readdir(d); entry; entry = readdir(d))\r
- {\r
- std::string strentry = entry->d_name;\r
- if (strentry.compare(".")!=0 && strentry.compare("..")!=0)\r
- {\r
- result = false;\r
- break;\r
- }\r
- }\r
- closedir(d);\r
- }\r
-#endif\r
- return result;\r
- }\r
-\r
- bool folder_set_current(const std::string& folder)\r
- {\r
- if (!folder_exists(folder))\r
- return false;\r
-#ifdef MSWINDOWS\r
- // Windose implementation - this returns non-zero for success\r
- return (SetCurrentDirectoryA(folder.c_str()) != 0);\r
-#else\r
- // Unix implementation - this returns zero for success\r
- return (chdir(folder.c_str()) == 0);\r
-#endif\r
- }\r
-\r
- std::string folder_current (void)\r
- {\r
- return ".";\r
- }\r
-\r
- std::string folder_current_full(void)\r
- {\r
- // It's not clear from the documentation whether the buffer for a path should be one byte longer\r
- // than the maximum path length to allow for the null termination, so I have made it so anyway\r
-#ifdef MSWINDOWS\r
- char abspath [MAX_PATH+1];\r
- return std::string(_fullpath(abspath, ".", MAX_PATH+1));\r
-#else\r
- char pathname [MAXPATHLEN+1];\r
- getcwd(pathname,MAXPATHLEN+1);\r
- return std::string(pathname);\r
-#endif\r
- }\r
-\r
- std::string folder_down (const std::string& directory, const std::string& subdirectory)\r
- {\r
- file_specification spec;\r
- spec.initialise_folder(directory);\r
- spec.add_subpath(subdirectory);\r
- return spec.image();\r
- }\r
-\r
- std::string folder_up (const std::string& directory, unsigned levels)\r
- {\r
- file_specification spec;\r
- spec.initialise_folder(directory);\r
- for (unsigned i = 0; i < levels; i++)\r
- spec.add_subpath("..");\r
- spec.simplify();\r
- return spec.image();\r
- }\r
-\r
- std::vector<std::string> folder_subdirectories (const std::string& directory)\r
- {\r
- return folder_wildcard(directory, "*", true, false);\r
- }\r
-\r
- std::vector<std::string> folder_files (const std::string& directory)\r
- {\r
- return folder_wildcard(directory, "*", false, true);\r
- }\r
-\r
- std::vector<std::string> folder_all(const std::string& directory)\r
- {\r
- return folder_wildcard(directory, "*", true, true);\r
- }\r
-\r
- std::vector<std::string> folder_wildcard (const std::string& directory, const std::string& wild, bool subdirs, bool files)\r
- {\r
- std::string dir = directory.empty() ? std::string(".") : directory;\r
- std::vector<std::string> results;\r
-#ifdef MSWINDOWS\r
- std::string wildcard = create_filespec(dir, wild);\r
- long handle = -1;\r
- _finddata_t fileinfo;\r
- for (bool OK = (handle = _findfirst((char*)wildcard.c_str(), &fileinfo)) != -1; OK; OK = (_findnext(handle, &fileinfo)==0))\r
- {\r
- std::string strentry = fileinfo.name;\r
- if (strentry.compare(".")!=0 && strentry.compare("..")!=0)\r
- if ((subdirs && (fileinfo.attrib & _A_SUBDIR)) || (files && !(fileinfo.attrib & _A_SUBDIR)))\r
- results.push_back(strentry);\r
- }\r
- _findclose(handle);\r
-#else\r
- DIR* d = opendir(dir.c_str());\r
- if (d)\r
- {\r
- for (dirent* entry = readdir(d); entry; entry = readdir(d))\r
- {\r
- std::string strentry = entry->d_name;\r
- if (strentry.compare(".")!=0 && strentry.compare("..")!=0)\r
- {\r
- std::string subpath = create_filespec(dir, strentry);\r
- if (((subdirs && is_folder(subpath)) || (files && is_file(subpath))) && (wildcard(wild, strentry)))\r
- results.push_back(strentry);\r
- }\r
- }\r
- closedir(d);\r
- }\r
-#endif\r
- return results;\r
- }\r
-\r
- std::string folder_home (void)\r
- {\r
- if (getenv("HOME"))\r
- return std::string(getenv("HOME"));\r
-#ifdef MSWINDOWS\r
- if (getenv("HOMEDRIVE") || getenv("HOMEPATH"))\r
- return std::string(getenv("HOMEDRIVE")) + std::string(getenv("HOMEPATH"));\r
- return "C:\\";\r
-#else\r
- if (getenv("USER"))\r
- return folder_down("/home", std::string(getenv("USER")));\r
- if (getenv("USERNAME"))\r
- return folder_down("/home", std::string(getenv("USERNAME")));\r
- return "";\r
-#endif\r
- }\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-// path functions convert between full and relative paths\r
-\r
- bool is_full_path(const std::string& path)\r
- {\r
- file_specification spec;\r
- spec.initialise_folder(path.empty() ? std::string(".") : path);\r
- return spec.absolute();\r
- }\r
-\r
- bool is_relative_path(const std::string& path)\r
- {\r
- file_specification spec;\r
- spec.initialise_folder(path.empty() ? std::string(".") : path);\r
- return spec.relative();\r
- }\r
-\r
- static std::string full_path(const std::string& root, const std::string& path)\r
- {\r
- // convert path to a full path using root as the start point for relative paths\r
- // decompose the path and test whether it is already an absolute path, in which case just return it\r
- file_specification spec;\r
- spec.initialise_folder(path.empty() ? std::string(".") : path);\r
- if (spec.absolute()) return spec.image();\r
- // okay, so the path is relative after all, so we need to combine it with the root path\r
- // decompose the root path and check whether it is relative\r
- file_specification rootspec;\r
- rootspec.initialise_folder(root.empty() ? std::string(".") : root);\r
- if (rootspec.relative())\r
- rootspec.make_absolute();\r
- // Now do the conversion of the path relative to the root\r
- spec.make_absolute(rootspec);\r
- return spec.image();\r
- }\r
-\r
- static std::string relative_path(const std::string& root, const std::string& path)\r
- {\r
- // convert path to a relative path, using the root path as its starting point\r
- // first convert both paths to full paths relative to CWD\r
- file_specification rootspec;\r
- rootspec.initialise_folder(root.empty() ? std::string(".") : root);\r
- if (rootspec.relative())\r
- rootspec.make_absolute();\r
- file_specification spec;\r
- spec.initialise_folder(path.empty() ? std::string(".") : path);\r
- if (spec.relative())\r
- spec.make_absolute();\r
- // now make path spec relative to the root spec\r
- spec.make_relative(rootspec);\r
- return spec.image();\r
- }\r
-\r
- std::string folder_to_path (const std::string& path, const std::string& directory)\r
- {\r
- return full_path(path, directory);\r
- }\r
-\r
- std::string filespec_to_path (const std::string& path, const std::string& spec)\r
- {\r
- return create_filespec(folder_to_path(path, folder_part(spec)),filename_part(spec));\r
- }\r
-\r
- std::string folder_to_path(const std::string& folder)\r
- {\r
- return folder_to_path(folder_current(), folder);\r
- }\r
-\r
- std::string filespec_to_path(const std::string& filespec)\r
- {\r
- return filespec_to_path(folder_current(), filespec);\r
- }\r
-\r
- std::string folder_to_relative_path(const std::string& root, const std::string& folder)\r
- {\r
- return relative_path(root, folder);\r
- }\r
-\r
- std::string filespec_to_relative_path(const std::string& root, const std::string& spec)\r
- {\r
- return create_filespec(folder_to_relative_path(root, folder_part(spec)),filename_part(spec));\r
- }\r
-\r
- std::string folder_to_relative_path(const std::string& folder)\r
- {\r
- return folder_to_relative_path(folder_current(), folder);\r
- }\r
-\r
- std::string filespec_to_relative_path(const std::string& filespec)\r
- {\r
- return filespec_to_relative_path(folder_current(), filespec);\r
- }\r
-\r
- std::string folder_append_separator(const std::string& folder)\r
- {\r
- std::string result = folder;\r
- if (result.empty() || !is_separator(result[result.size()-1]))\r
- result += preferred_separator;\r
- return result;\r
- }\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-\r
- std::string basename_part (const std::string& spec)\r
- {\r
- std::string fname = filename_part(spec);\r
- // scan back through filename until a '.' is found and remove suffix\r
- // the whole filename is the basename if there is no '.'\r
- std::string::size_type i = fname.find_last_of('.');\r
- // observe Unix convention that a dot at the start of a filename is part of the basename, not the extension\r
- if (i != 0 && i != std::string::npos)\r
- fname.erase(i, fname.size()-i);\r
- return fname;\r
- }\r
-\r
- std::string filename_part (const std::string& spec)\r
- {\r
- // scan back through filename until a preferred_separator is found and remove prefix;\r
- // if there is no preferred_separator then remove nothing, i.e. the whole filespec is filename\r
- unsigned i = spec.size();\r
- while (i--)\r
- {\r
- if (is_separator(spec[i]))\r
- return spec.substr(i+1,spec.size()-i-1);\r
- }\r
- return spec;\r
- }\r
-\r
- std::string extension_part (const std::string& spec)\r
- {\r
- std::string fname = filename_part(spec);\r
- // scan back through filename until a '.' is found and remove prefix;\r
- std::string::size_type i = fname.find_last_of('.');\r
- // observe Unix convention that a dot at the start of a filename is part of the name, not the extension;\r
- if (i != 0 && i != std::string::npos)\r
- fname.erase(0, i+1);\r
- else\r
- fname.erase();\r
- return fname;\r
- }\r
-\r
- std::string folder_part (const std::string& spec)\r
- {\r
- // scan back through filename until a separator is found and remove prefix\r
- // if there is no separator, remove the whole\r
- unsigned i = spec.size();\r
- while (i--)\r
- {\r
- if (is_separator(spec[i]))\r
- return spec.substr(0,i);\r
- }\r
- return std::string();\r
- }\r
-\r
- std::vector<std::string> filespec_elements(const std::string& filespec)\r
- {\r
- file_specification spec;\r
- spec.initialise_file(filespec);\r
- std::vector<std::string> result = spec.path();\r
- if (!spec.drive().empty()) result.insert(result.begin(),spec.drive());\r
- if (!spec.file().empty()) result.push_back(spec.file());\r
- return result;\r
- }\r
-\r
- std::vector<std::string> folder_elements(const std::string& folder)\r
- {\r
- file_specification spec;\r
- spec.initialise_folder(folder);\r
- std::vector<std::string> result = spec.path();\r
- if (!spec.drive().empty()) result.insert(result.begin(),spec.drive());\r
- return result;\r
- }\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-// mimic the command lookup used by the shell\r
-\r
-// Windows looks at the following locations:\r
-// 1) application root\r
-// 2) current directory\r
-// 3) 32-bit system directory\r
-// 4) 16-bit system directory\r
-// 5) windows system directory\r
-// 6) %path%\r
-// currently only (2) and (6) has been implemented although many system folders are on the path anyway\r
-// also implement the implied .exe extension on commands with no path (see CreateProcess documentation)\r
-// TODO - PATHEXT handling to find non-exe executables\r
-\r
- std::string path_lookup (const std::string& command)\r
- {\r
- std::string path = std::string(".") + PATH_SPLITTER + getenv("PATH");\r
- return lookup(command, path);\r
- }\r
-\r
- std::string lookup (const std::string& command, const std::string& path, const std::string& splitter)\r
- {\r
- // first check whether the command is already a path and check whether it exists\r
- if (!folder_part(command).empty())\r
- {\r
- if (file_exists(command))\r
- return command;\r
- }\r
- else\r
- {\r
- // command is just a name - so do path lookup\r
- // split the path into its elements\r
- std::vector<std::string> paths;\r
- if (!path.empty())\r
- {\r
- for(std::string::size_type offset = 0;;)\r
- {\r
- std::string::size_type found = path.find(splitter, offset);\r
- if (found != std::string::npos)\r
- {\r
- paths.push_back(path.substr(offset, found-offset));\r
- offset = found + splitter.size();\r
- }\r
- else\r
- {\r
- paths.push_back(path.substr(offset, path.size()-offset));\r
- break;\r
- }\r
- }\r
- }\r
- // now lookup each path to see if it its the matching one\r
- for (unsigned i = 0; i < paths.size(); i++)\r
- {\r
- std::string spec = create_filespec(paths[i], command);\r
- if (file_exists(spec))\r
- {\r
- return spec;\r
- }\r
- }\r
- }\r
-#ifdef MSWINDOWS\r
- // if there is no extension, try recursing on each possible extension\r
- // TODO iterate through PATHEXT\r
- if (extension_part(command).empty())\r
- return lookup(create_filespec(folder_part(command), basename_part(command), "exe"), path, splitter);\r
-#endif\r
- // if path lookup failed, return empty string to indicate error\r
- return std::string();\r
- }\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-\r
- std::string install_path(const std::string& argv0)\r
- {\r
- std::string bin_directory = folder_part(argv0);\r
- if (bin_directory.empty())\r
- {\r
- // do path lookup to find the executable path\r
- bin_directory = folder_part(path_lookup(argv0));\r
- }\r
- return bin_directory;\r
- }\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-\r
-} // end namespace stlplus\r
+////////////////////////////////////////////////////////////////////////////////
+
+// Author: Andy Rushton
+// Copyright: (c) Southampton University 1999-2004
+// (c) Andy Rushton 2004-2009
+// License: BSD License, see ../docs/license.html
+
+// This is a portable interface to the file system.
+
+// The idea is that you write all file system access code using these functions,
+// which are ported to all platforms that we are interested in. Therefore your
+// code is inherently portable.
+
+// Native Windows version: switched on by macro _WIN32 which is defined by VC++/Borland/Mingw compilers
+// Unix/Gnu version: default variant, no compiler directives are required but _WIN32 must be absent
+// Cygwin/Gnu version: as Unix version but with additional support for Windows drive letters
+
+////////////////////////////////////////////////////////////////////////////////
+#include "file_system.hpp"
+#include "wildcard.hpp"
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <algorithm>
+#include <ctype.h>
+
+#ifdef MSWINDOWS
+#include <windows.h>
+#include <dos.h>
+#include <direct.h>
+#include <fcntl.h>
+#include <io.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#else
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/param.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace stlplus
+{
+
+////////////////////////////////////////////////////////////////////////////////
+// definitions of separators
+
+#ifdef MSWINDOWS
+ static const char* separator_set = "\\/";
+ static const char preferred_separator = '\\';
+#else
+ static const char* separator_set = "/";
+ static const char preferred_separator = '/';
+#endif
+
+ static bool is_separator (char ch)
+ {
+ for (int i = 0; separator_set[i]; i++)
+ {
+ if (separator_set[i] == ch)
+ return true;
+ }
+ return false;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+// implement string comparison of paths - Unix is case-sensitive, Windoze is case-insensitive
+
+#ifdef MSWINDOWS
+
+ static std::string lowercase(const std::string& val)
+ {
+ std::string text = val;
+ for (unsigned i = 0; i < text.size(); i++)
+ text[i] = tolower(text[i]);
+ return text;
+ }
+
+#endif
+
+ bool path_compare(const std::string& l, const std::string& r)
+ {
+#ifdef MSWINDOWS
+ return lowercase(l) == lowercase(r);
+#else
+ return l == r;
+#endif
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+// Internal data structure used to hold the different parts of a filespec
+
+ class file_specification
+ {
+ private:
+ bool m_relative; // true = relative, false = absolute
+ std::string m_drive; // drive - drive letter (e.g. "c:") or the path for an UNC (e.g. "\\somewhere")
+ // empty if not known or on Unix
+ std::vector<std::string> m_path; // the subdirectory path to follow from the drive
+ std::string m_filename; // the filename
+ public:
+ file_specification(void) : m_relative(false) {}
+ ~file_specification(void) {}
+
+ bool initialise_folder(const std::string& spec);
+ bool initialise_file(const std::string& spec);
+ bool simplify(void);
+ bool make_absolute(const std::string& root = folder_current_full());
+ bool make_absolute(const file_specification& root);
+ bool make_relative(const std::string& root = folder_current_full());
+ bool make_relative(const file_specification& root);
+ bool relative(void) const {return m_relative;}
+ bool absolute(void) const {return !relative();}
+ void set_relative(void) {m_relative = true;}
+ void set_absolute(void) {m_relative = false;}
+
+ const std::string& drive(void) const {return m_drive;}
+ std::string& drive(void) {return m_drive;}
+ void set_drive(const std::string& drive) {m_drive = drive;}
+
+ const std::vector<std::string>& path(void) const {return m_path;}
+ std::vector<std::string>& path(void) {return m_path;}
+ void set_path(const std::vector<std::string>& path) {m_path = path;}
+
+ void add_subpath(const std::string& subpath) {m_path.push_back(subpath);}
+ unsigned subpath_size(void) const {return m_path.size();}
+ const std::string& subpath_element(unsigned i) const {return m_path[i];}
+ void subpath_erase(unsigned i) {m_path.erase(m_path.begin()+i);}
+
+ const std::string& file(void) const {return m_filename;}
+ std::string& file(void) {return m_filename;}
+ void set_file(const std::string& file) {m_filename = file;}
+
+ std::string image(void) const;
+ };
+
+ bool file_specification::initialise_folder(const std::string& folder_spec)
+ {
+ std::string spec = folder_spec;
+ m_relative = true;
+ m_drive.erase();
+ m_path.clear();
+ m_filename.erase();
+ unsigned i = 0;
+#ifdef MSWINDOWS
+ // first split off the drive letter or UNC prefix on Windows
+ if (spec.size() >= 2 && isalpha(spec[0]) && spec[1] == ':')
+ {
+ // found a drive letter
+ i = 2;
+ m_drive = spec.substr(0, 2);
+ m_relative = false;
+ // if there is a drive but no path or a relative path, get the current
+ // path for this drive and prepend it to the path
+ if (i == spec.size() || !is_separator(spec[i]))
+ {
+ // getdcwd requires the drive number (1..26) not the letter (A..Z)
+ char path [MAX_PATH+1];
+ int drivenum = toupper(m_drive[0]) - 'A' + 1;
+ if (_getdcwd(drivenum, path, MAX_PATH+1))
+ {
+ // the path includes the drive so we have the drive info twice
+ // need to prepend this absolute path to the spec such that any remaining relative path is still retained
+ if (!is_separator(path[strlen(path)-1])) spec.insert(2, 1, preferred_separator);
+ spec.insert(2, path+2);
+ }
+ else
+ {
+ // non-existent drive - fill in just the root directory
+ spec.insert(2, 1, preferred_separator);
+ }
+ }
+ }
+ else if (spec.size() >= 2 && is_separator(spec[0]) && is_separator(spec[1]))
+ {
+ // found an UNC prefix
+ i = 2;
+ // find the end of the prefix by scanning for the next seperator or the end of the spec
+ while (i < spec.size() && !is_separator(spec[i])) i++;
+ m_drive = spec.substr(0, i);
+ m_relative = false;
+ }
+#endif
+#ifdef CYGWIN
+ // first split off the drive letter or UNC prefix on Windows - the Cygwin environment supports these too
+ if (spec.size() >= 2 && isalpha(spec[0]) && spec[1] == ':')
+ {
+ // found a drive letter
+ i = 2;
+ m_drive = spec.substr(0, 2);
+ m_relative = false;
+ // if there is a drive but no path or a relative path, get the current
+ // path for this drive and prepend it to the path
+ if (i == spec.size() || !is_separator(spec[i]))
+ {
+ // non-existent drive - fill in just the root directory
+ spec.insert(2, 1, preferred_separator);
+ }
+ }
+ else if (spec.size() >= 2 && is_separator(spec[0]) && is_separator(spec[1]))
+ {
+ // found an UNC prefix
+ i = 2;
+ // find the end of the prefix by scanning for the next seperator or the end of the spec
+ while (i < spec.size() && !is_separator(spec[i])) i++;
+ m_drive = spec.substr(0, i);
+ m_relative = false;
+ }
+#endif
+ // check whether the path is absolute or relative and discard the leading / if absolute
+ if (i < spec.size() && is_separator(spec[i]))
+ {
+ m_relative = false;
+ i++;
+#ifdef MSWINDOWS
+ // if there's no drive, fill it in on Windows since absolute paths must have a drive
+ if (m_drive.empty())
+ {
+ m_drive += (char)(_getdrive() - 1 + 'A');
+ m_drive += ':';
+ }
+#endif
+ }
+ // now extract the path elements - note that a trailing / is not significant since /a/b/c/ === /a/b/c
+ // also note that the leading / has been discarded - all paths are relative
+ // if absolute() is set, then paths are relative to the drive, else they are relative to the current path
+ unsigned start = i;
+ while(i <= spec.size())
+ {
+ if (i == spec.size())
+ {
+ // path element terminated by the end of the string
+ // discard this element if it is zero length because that represents the trailing /
+ if (i != start)
+ m_path.push_back(spec.substr(start, i-start));
+ }
+ else if (is_separator(spec[i]))
+ {
+ // path element terminated by a separator
+ m_path.push_back(spec.substr(start, i-start));
+ start = i+1;
+ }
+ i++;
+ }
+ // TODO - some error handling?
+ return true;
+ }
+
+ bool file_specification::initialise_file(const std::string& spec)
+ {
+ m_filename.erase();
+ // remove last element as the file and then treat the rest as a folder
+ unsigned i = spec.size();
+ while (--i)
+ {
+ if (is_separator(spec[i]))
+ break;
+#ifdef MSWINDOWS
+ // on windoze you can say a:fred.txt so the colon separates the path from the filename
+ else if (i == 1 && spec[i] == ':')
+ break;
+#endif
+ }
+ bool result = initialise_folder(spec.substr(0,i+1));
+ m_filename = spec.substr(i+1,spec.size()-i-1);
+ // TODO - some error handling?
+ return result;
+ }
+
+ bool file_specification::simplify(void)
+ {
+ // simplify the path by removing unnecessary . and .. entries - Note that zero-length entries are treated like .
+ for (unsigned i = 0; i < m_path.size(); )
+ {
+ if (m_path[i].empty() || m_path[i].compare(".") == 0)
+ {
+ // found . or null
+ // these both mean do nothing - so simply delete this element
+ m_path.erase(m_path.begin()+i);
+ }
+ else if (m_path[i].compare("..") == 0)
+ {
+ // found ..
+ if (i == 0 && !m_relative)
+ {
+ // up from the root does nothing so can be deleted
+ m_path.erase(m_path.begin()+i);
+ i++;
+ }
+ else if (i == 0 || m_path[i-1].compare("..") == 0)
+ {
+ // the first element of a relative path or the previous element is .. then keep it
+ i++;
+ }
+ else
+ {
+ // otherwise delete this element and the previous one
+ m_path.erase(m_path.begin()+i);
+ m_path.erase(m_path.begin()+i-1);
+ i--;
+ }
+ }
+ // keep all other elements
+ else
+ i++;
+ }
+ // TODO - error checking?
+ return true;
+ }
+
+ bool file_specification::make_absolute(const std::string& root)
+ {
+ // test whether already an absolute path in which case there's nothing to do
+ if (absolute()) return true;
+ // now simply call the other version of make_absolute
+ file_specification rootspec;
+ rootspec.initialise_folder(root);
+ return make_absolute(rootspec);
+ }
+
+ bool file_specification::make_absolute(const file_specification& rootspec)
+ {
+ // test whether already an absolute path in which case there's nothing to do
+ if (absolute()) return true;
+ // initialise the result with the root and make the root absolute
+ file_specification result = rootspec;
+ result.make_absolute();
+ // now append this's relative path and filename to the root's absolute path
+ for (unsigned i = 0; i < subpath_size(); i++)
+ result.add_subpath(subpath_element(i));
+ result.set_file(file());
+ // now the result is the absolute path, so transfer it to this
+ *this = result;
+ // and simplify to get rid of any unwanted .. or . elements
+ simplify();
+ return true;
+ }
+
+ bool file_specification::make_relative(const std::string& root)
+ {
+ // test whether already an relative path in which case there's nothing to do
+ if (relative()) return true;
+ // now simply call the other version of make_relative
+ file_specification rootspec;
+ rootspec.initialise_folder(root);
+ return make_relative(rootspec);
+ }
+
+ bool file_specification::make_relative(const file_specification& rootspec)
+ {
+ // test whether already an relative path in which case there's nothing to do
+ if (relative()) return true;
+ // initialise the result with the root and make the root absolute
+ file_specification absolute_root = rootspec;
+ absolute_root.make_absolute();
+ // now compare elements of the absolute root with elements of this to find the common path
+ // if the drives are different, no conversion can take place and the result must be absolute, else clear the drive
+ if (!path_compare(drive(), absolute_root.drive())) return true;
+ set_drive("");
+ // first remove leading elements that are identical to the corresponding element in root
+ unsigned i = 0;
+ while(subpath_size() > 0 &&
+ i < absolute_root.subpath_size() &&
+ path_compare(subpath_element(0), absolute_root.subpath_element(i)))
+ {
+ subpath_erase(0);
+ i++;
+ }
+ // now add a .. prefix for every element in root that is different from this
+ while (i < absolute_root.subpath_size())
+ {
+ m_path.insert(m_path.begin(), "..");
+ i++;
+ }
+ set_relative();
+ return true;
+ }
+
+ std::string file_specification::image(void) const
+ {
+ std::string result = m_drive;
+ if (absolute())
+ result += preferred_separator;
+ if (!m_path.empty())
+ {
+ for (unsigned i = 0; i < m_path.size(); i++)
+ {
+ if (i != 0) result += std::string(1,preferred_separator);
+ result += m_path[i];
+ }
+ }
+ else if (relative())
+ result += '.';
+ // add a trailing / to the last directory element
+ if (result.empty() || !is_separator(result[result.size()-1]))
+ result += preferred_separator;
+ if (!m_filename.empty())
+ result += m_filename;
+ return result;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+// classifying functions
+
+#ifdef MSWINDOWS
+// file type tests are not defined for some reason on Windows despite them providing the stat() function!
+#define R_OK 4
+#define W_OK 2
+#endif
+
+ bool is_present (const std::string& thing)
+ {
+ // strip off any trailing separator because that will cause the stat function to fail
+ std::string path = thing;
+ if (!path.empty() && is_separator(path[path.size()-1]))
+ path.erase(path.size()-1,1);
+ // now test if this thing exists using the built-in stat function
+ struct stat buf;
+ return stat(path.c_str(), &buf) == 0;
+ }
+
+ bool is_folder (const std::string& thing)
+ {
+ // strip off any trailing separator because that will cause the stat function to fail
+ std::string path = thing;
+ if (!path.empty() && is_separator(path[path.size()-1]))
+ path.erase(path.size()-1,1);
+ // now test if this thing exists using the built-in stat function and if so, is it a folder
+ struct stat buf;
+ if (!(stat(path.c_str(), &buf) == 0)) {return false;}
+ return (buf.st_mode & S_IFDIR) != 0;
+ }
+
+ bool is_file (const std::string& thing)
+ {
+ // strip off any trailing separator because that will cause the stat function to fail
+ std::string path = thing;
+ if (!path.empty() && is_separator(path[path.size()-1]))
+ path.erase(path.size()-1,1);
+ // now test if this thing exists using the built-in stat function and if so, is it a file
+ struct stat buf;
+ if (!(stat(path.c_str(), &buf) == 0)) {return false;}
+ return (buf.st_mode & S_IFREG) != 0;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+// file functions
+
+ bool file_exists (const std::string& filespec)
+ {
+ return is_file(filespec);
+ }
+
+ bool file_readable (const std::string& filespec)
+ {
+ // a file is readable if it exists and can be read
+ if (!file_exists(filespec)) return false;
+ return access(filespec.c_str(),R_OK)==0;
+ }
+
+ bool file_writable (const std::string& filespec)
+ {
+ // a file is writable if it exists as a file and is writable or if it doesn't exist but could be created and would be writable
+ if (is_present(filespec))
+ {
+ if (!is_file(filespec)) return false;
+ return access(filespec.c_str(),W_OK)==0;
+ }
+ std::string dir = folder_part(filespec);
+ if (dir.empty()) dir = ".";
+ return folder_writable(dir);
+ }
+
+ size_t file_size (const std::string& filespec)
+ {
+ struct stat buf;
+ if (!(stat(filespec.c_str(), &buf) == 0)) return 0;
+ return buf.st_size;
+ }
+
+ bool file_delete (const std::string& filespec)
+ {
+ if (!is_file(filespec)) return false;
+ return remove(filespec.c_str())==0;
+ }
+
+ bool file_rename (const std::string& old_filespec, const std::string& new_filespec)
+ {
+ if (!is_file(old_filespec)) return false;
+ return rename(old_filespec.c_str(), new_filespec.c_str())==0;
+ }
+
+ bool file_copy (const std::string& old_filespec, const std::string& new_filespec)
+ {
+ if (!is_file(old_filespec)) return false;
+ // do an exact copy - to do this, use binary mode
+ bool result = true;
+ FILE* old_file = fopen(old_filespec.c_str(),"rb");
+ FILE* new_file = fopen(new_filespec.c_str(),"wb");
+ if (!old_file)
+ result = false;
+ else if (!new_file)
+ result = false;
+ else
+ {
+ for (int byte = getc(old_file); byte != EOF; byte = getc(old_file))
+ putc(byte,new_file);
+ }
+ if (old_file) fclose(old_file);
+ if (new_file) fclose(new_file);
+ return result;
+ }
+
+ bool file_move (const std::string& old_filespec, const std::string& new_filespec)
+ {
+ // try to move the file by renaming - if that fails then do a copy and delete the original
+ if (file_rename(old_filespec, new_filespec))
+ return true;
+ if (!file_copy(old_filespec, new_filespec))
+ return false;
+ // I'm not sure what to do if the delete fails - is that an error?
+ // I've made it an error and then delete the copy so that the original state is recovered
+ if (file_delete(old_filespec))
+ return true;
+ file_delete(new_filespec);
+ return false;
+ }
+
+ time_t file_created (const std::string& filespec)
+ {
+ struct stat buf;
+ if (!(stat(filespec.c_str(), &buf) == 0)) return 0;
+ return buf.st_ctime;
+ }
+
+ time_t file_modified (const std::string& filespec)
+ {
+ struct stat buf;
+ if (!(stat(filespec.c_str(), &buf) == 0)) return 0;
+ return buf.st_mtime;
+ }
+
+ time_t file_accessed (const std::string& filespec)
+ {
+ struct stat buf;
+ if (!(stat(filespec.c_str(), &buf) == 0)) return 0;
+ return buf.st_atime;
+ }
+
+ std::string create_filespec (const std::string& directory, const std::string& filename)
+ {
+ std::string result = directory;
+ // if directory is empty then no directory part will be added
+ // add trailing slash if the directory was specified and does not have a trailing slash
+ if (!result.empty() && !is_separator(result[result.size()-1]))
+ result += preferred_separator;
+ // if filename is null or empty, nothing will be added so the path is then a directory path
+ result += filename;
+ return result;
+ }
+
+ std::string create_filespec (const std::string& directory, const std::string& basename, const std::string& extension)
+ {
+ return create_filespec(directory, create_filename(basename, extension));
+ }
+
+ std::string create_filename(const std::string& basename, const std::string& extension)
+ {
+ std::string name = basename;
+ // extension is optional - so the dot is also optional
+ if (!extension.empty())
+ {
+ if (extension[0] != '.') name += '.';
+ name += extension;
+ }
+ return name;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+// folder functions
+
+ bool folder_create (const std::string& directory)
+ {
+#ifdef MSWINDOWS
+ return mkdir(directory.c_str()) == 0;
+#else
+ return mkdir(directory.c_str(), 0777) == 0;
+#endif
+ }
+
+ bool folder_exists (const std::string& directory)
+ {
+ return is_folder(directory);
+ }
+
+ bool folder_readable (const std::string& directory)
+ {
+ // a folder is readable if it exists and has read access
+ std::string dir = directory;
+ if (dir.empty()) dir = ".";
+ if (!folder_exists(dir)) return false;
+ return access(dir.c_str(),R_OK)==0;
+ }
+
+ bool folder_writable (const std::string& directory)
+ {
+ // a folder is writable if it exists and has write access
+ std::string dir = directory;
+ if (dir.empty()) dir = ".";
+ if (!folder_exists(dir)) return false;
+ return access(dir.c_str(),W_OK)==0;
+ }
+
+ bool folder_delete (const std::string& directory, bool recurse)
+ {
+ std::string dir = directory;
+ if (dir.empty()) dir = ".";
+ if (!folder_exists(dir)) return false;
+ bool result = true;
+ // depth-first traversal ensures that directory contents are deleted before trying to delete the directory itself
+ if (recurse)
+ {
+ std::vector<std::string> subdirectories = folder_subdirectories(dir);
+ for (std::vector<std::string>::size_type d = 0; d < subdirectories.size(); ++d)
+ if (!folder_delete(folder_down(dir,subdirectories[d]),true))
+ result = false;
+ std::vector<std::string> files = folder_files(dir);
+ for (std::vector<std::string>::size_type f = 0; f < files.size(); ++f)
+ if (!file_delete(create_filespec(dir, files[f])))
+ result = false;
+ }
+ if (rmdir(dir.c_str())!=0) result = false;
+ return result;
+ }
+
+ bool folder_rename (const std::string& old_directory, const std::string& new_directory)
+ {
+ if (!folder_exists(old_directory)) return false;
+ return rename(old_directory.c_str(), new_directory.c_str())==0;
+ }
+
+ bool folder_empty(const std::string& directory)
+ {
+ std::string dir = directory.empty() ? std::string(".") : directory;
+ bool result = true;
+#ifdef MSWINDOWS
+ std::string wildcard = create_filespec(dir, "*.*");
+ long handle = -1;
+ _finddata_t fileinfo;
+ for (bool OK = (handle = _findfirst((char*)wildcard.c_str(), &fileinfo)) != -1; OK; OK = (_findnext(handle, &fileinfo)==0))
+ {
+ std::string strentry = fileinfo.name;
+ if (strentry.compare(".")!=0 && strentry.compare("..")!=0)
+ {
+ result = false;
+ break;
+ }
+ }
+ _findclose(handle);
+#else
+ DIR* d = opendir(dir.c_str());
+ if (d)
+ {
+ for (dirent* entry = readdir(d); entry; entry = readdir(d))
+ {
+ std::string strentry = entry->d_name;
+ if (strentry.compare(".")!=0 && strentry.compare("..")!=0)
+ {
+ result = false;
+ break;
+ }
+ }
+ closedir(d);
+ }
+#endif
+ return result;
+ }
+
+ bool folder_set_current(const std::string& folder)
+ {
+ if (!folder_exists(folder))
+ return false;
+#ifdef MSWINDOWS
+ // Windose implementation - this returns non-zero for success
+ return (SetCurrentDirectoryA(folder.c_str()) != 0);
+#else
+ // Unix implementation - this returns zero for success
+ return (chdir(folder.c_str()) == 0);
+#endif
+ }
+
+ std::string folder_current (void)
+ {
+ return ".";
+ }
+
+ std::string folder_current_full(void)
+ {
+ // It's not clear from the documentation whether the buffer for a path should be one byte longer
+ // than the maximum path length to allow for the null termination, so I have made it so anyway
+#ifdef MSWINDOWS
+ char abspath [MAX_PATH+1];
+ return std::string(_fullpath(abspath, ".", MAX_PATH+1));
+#else
+ char pathname [MAXPATHLEN+1];
+ getcwd(pathname,MAXPATHLEN+1);
+ return std::string(pathname);
+#endif
+ }
+
+ std::string folder_down (const std::string& directory, const std::string& subdirectory)
+ {
+ file_specification spec;
+ spec.initialise_folder(directory);
+ spec.add_subpath(subdirectory);
+ return spec.image();
+ }
+
+ std::string folder_up (const std::string& directory, unsigned levels)
+ {
+ file_specification spec;
+ spec.initialise_folder(directory);
+ for (unsigned i = 0; i < levels; i++)
+ spec.add_subpath("..");
+ spec.simplify();
+ return spec.image();
+ }
+
+ std::vector<std::string> folder_subdirectories (const std::string& directory)
+ {
+ return folder_wildcard(directory, "*", true, false);
+ }
+
+ std::vector<std::string> folder_files (const std::string& directory)
+ {
+ return folder_wildcard(directory, "*", false, true);
+ }
+
+ std::vector<std::string> folder_all(const std::string& directory)
+ {
+ return folder_wildcard(directory, "*", true, true);
+ }
+
+ std::vector<std::string> folder_wildcard (const std::string& directory, const std::string& wild, bool subdirs, bool files)
+ {
+ std::string dir = directory.empty() ? std::string(".") : directory;
+ std::vector<std::string> results;
+#ifdef MSWINDOWS
+ std::string wildcard = create_filespec(dir, wild);
+ long handle = -1;
+ _finddata_t fileinfo;
+ for (bool OK = (handle = _findfirst((char*)wildcard.c_str(), &fileinfo)) != -1; OK; OK = (_findnext(handle, &fileinfo)==0))
+ {
+ std::string strentry = fileinfo.name;
+ if (strentry.compare(".")!=0 && strentry.compare("..")!=0)
+ if ((subdirs && (fileinfo.attrib & _A_SUBDIR)) || (files && !(fileinfo.attrib & _A_SUBDIR)))
+ results.push_back(strentry);
+ }
+ _findclose(handle);
+#else
+ DIR* d = opendir(dir.c_str());
+ if (d)
+ {
+ for (dirent* entry = readdir(d); entry; entry = readdir(d))
+ {
+ std::string strentry = entry->d_name;
+ if (strentry.compare(".")!=0 && strentry.compare("..")!=0)
+ {
+ std::string subpath = create_filespec(dir, strentry);
+ if (((subdirs && is_folder(subpath)) || (files && is_file(subpath))) && (wildcard(wild, strentry)))
+ results.push_back(strentry);
+ }
+ }
+ closedir(d);
+ }
+#endif
+ return results;
+ }
+
+ std::string folder_home (void)
+ {
+ if (getenv("HOME"))
+ return std::string(getenv("HOME"));
+#ifdef MSWINDOWS
+ if (getenv("HOMEDRIVE") || getenv("HOMEPATH"))
+ return std::string(getenv("HOMEDRIVE")) + std::string(getenv("HOMEPATH"));
+ return "C:\\";
+#else
+ if (getenv("USER"))
+ return folder_down("/home", std::string(getenv("USER")));
+ if (getenv("USERNAME"))
+ return folder_down("/home", std::string(getenv("USERNAME")));
+ return "";
+#endif
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+// path functions convert between full and relative paths
+
+ bool is_full_path(const std::string& path)
+ {
+ file_specification spec;
+ spec.initialise_folder(path.empty() ? std::string(".") : path);
+ return spec.absolute();
+ }
+
+ bool is_relative_path(const std::string& path)
+ {
+ file_specification spec;
+ spec.initialise_folder(path.empty() ? std::string(".") : path);
+ return spec.relative();
+ }
+
+ static std::string full_path(const std::string& root, const std::string& path)
+ {
+ // convert path to a full path using root as the start point for relative paths
+ // decompose the path and test whether it is already an absolute path, in which case just return it
+ file_specification spec;
+ spec.initialise_folder(path.empty() ? std::string(".") : path);
+ if (spec.absolute()) return spec.image();
+ // okay, so the path is relative after all, so we need to combine it with the root path
+ // decompose the root path and check whether it is relative
+ file_specification rootspec;
+ rootspec.initialise_folder(root.empty() ? std::string(".") : root);
+ if (rootspec.relative())
+ rootspec.make_absolute();
+ // Now do the conversion of the path relative to the root
+ spec.make_absolute(rootspec);
+ return spec.image();
+ }
+
+ static std::string relative_path(const std::string& root, const std::string& path)
+ {
+ // convert path to a relative path, using the root path as its starting point
+ // first convert both paths to full paths relative to CWD
+ file_specification rootspec;
+ rootspec.initialise_folder(root.empty() ? std::string(".") : root);
+ if (rootspec.relative())
+ rootspec.make_absolute();
+ file_specification spec;
+ spec.initialise_folder(path.empty() ? std::string(".") : path);
+ if (spec.relative())
+ spec.make_absolute();
+ // now make path spec relative to the root spec
+ spec.make_relative(rootspec);
+ return spec.image();
+ }
+
+ std::string folder_to_path (const std::string& path, const std::string& directory)
+ {
+ return full_path(path, directory);
+ }
+
+ std::string filespec_to_path (const std::string& path, const std::string& spec)
+ {
+ return create_filespec(folder_to_path(path, folder_part(spec)),filename_part(spec));
+ }
+
+ std::string folder_to_path(const std::string& folder)
+ {
+ return folder_to_path(folder_current(), folder);
+ }
+
+ std::string filespec_to_path(const std::string& filespec)
+ {
+ return filespec_to_path(folder_current(), filespec);
+ }
+
+ std::string folder_to_relative_path(const std::string& root, const std::string& folder)
+ {
+ return relative_path(root, folder);
+ }
+
+ std::string filespec_to_relative_path(const std::string& root, const std::string& spec)
+ {
+ return create_filespec(folder_to_relative_path(root, folder_part(spec)),filename_part(spec));
+ }
+
+ std::string folder_to_relative_path(const std::string& folder)
+ {
+ return folder_to_relative_path(folder_current(), folder);
+ }
+
+ std::string filespec_to_relative_path(const std::string& filespec)
+ {
+ return filespec_to_relative_path(folder_current(), filespec);
+ }
+
+ std::string folder_append_separator(const std::string& folder)
+ {
+ std::string result = folder;
+ if (result.empty() || !is_separator(result[result.size()-1]))
+ result += preferred_separator;
+ return result;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+ std::string basename_part (const std::string& spec)
+ {
+ std::string fname = filename_part(spec);
+ // scan back through filename until a '.' is found and remove suffix
+ // the whole filename is the basename if there is no '.'
+ std::string::size_type i = fname.find_last_of('.');
+ // observe Unix convention that a dot at the start of a filename is part of the basename, not the extension
+ if (i != 0 && i != std::string::npos)
+ fname.erase(i, fname.size()-i);
+ return fname;
+ }
+
+ std::string filename_part (const std::string& spec)
+ {
+ // scan back through filename until a preferred_separator is found and remove prefix;
+ // if there is no preferred_separator then remove nothing, i.e. the whole filespec is filename
+ unsigned i = spec.size();
+ while (i--)
+ {
+ if (is_separator(spec[i]))
+ return spec.substr(i+1,spec.size()-i-1);
+ }
+ return spec;
+ }
+
+ std::string extension_part (const std::string& spec)
+ {
+ std::string fname = filename_part(spec);
+ // scan back through filename until a '.' is found and remove prefix;
+ std::string::size_type i = fname.find_last_of('.');
+ // observe Unix convention that a dot at the start of a filename is part of the name, not the extension;
+ if (i != 0 && i != std::string::npos)
+ fname.erase(0, i+1);
+ else
+ fname.erase();
+ return fname;
+ }
+
+ std::string folder_part (const std::string& spec)
+ {
+ // scan back through filename until a separator is found and remove prefix
+ // if there is no separator, remove the whole
+ unsigned i = spec.size();
+ while (i--)
+ {
+ if (is_separator(spec[i]))
+ return spec.substr(0,i);
+ }
+ return std::string();
+ }
+
+ std::vector<std::string> filespec_elements(const std::string& filespec)
+ {
+ file_specification spec;
+ spec.initialise_file(filespec);
+ std::vector<std::string> result = spec.path();
+ if (!spec.drive().empty()) result.insert(result.begin(),spec.drive());
+ if (!spec.file().empty()) result.push_back(spec.file());
+ return result;
+ }
+
+ std::vector<std::string> folder_elements(const std::string& folder)
+ {
+ file_specification spec;
+ spec.initialise_folder(folder);
+ std::vector<std::string> result = spec.path();
+ if (!spec.drive().empty()) result.insert(result.begin(),spec.drive());
+ return result;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+// mimic the command lookup used by the shell
+
+// Windows looks at the following locations:
+// 1) application root
+// 2) current directory
+// 3) 32-bit system directory
+// 4) 16-bit system directory
+// 5) windows system directory
+// 6) %path%
+// currently only (2) and (6) has been implemented although many system folders are on the path anyway
+// also implement the implied .exe extension on commands with no path (see CreateProcess documentation)
+// TODO - PATHEXT handling to find non-exe executables
+
+ std::string path_lookup (const std::string& command)
+ {
+ std::string path = std::string(".") + PATH_SPLITTER + getenv("PATH");
+ return lookup(command, path);
+ }
+
+ std::string lookup (const std::string& command, const std::string& path, const std::string& splitter)
+ {
+ // first check whether the command is already a path and check whether it exists
+ if (!folder_part(command).empty())
+ {
+ if (file_exists(command))
+ return command;
+ }
+ else
+ {
+ // command is just a name - so do path lookup
+ // split the path into its elements
+ std::vector<std::string> paths;
+ if (!path.empty())
+ {
+ for(std::string::size_type offset = 0;;)
+ {
+ std::string::size_type found = path.find(splitter, offset);
+ if (found != std::string::npos)
+ {
+ paths.push_back(path.substr(offset, found-offset));
+ offset = found + splitter.size();
+ }
+ else
+ {
+ paths.push_back(path.substr(offset, path.size()-offset));
+ break;
+ }
+ }
+ }
+ // now lookup each path to see if it its the matching one
+ for (unsigned i = 0; i < paths.size(); i++)
+ {
+ std::string spec = create_filespec(paths[i], command);
+ if (file_exists(spec))
+ {
+ return spec;
+ }
+ }
+ }
+#ifdef MSWINDOWS
+ // if there is no extension, try recursing on each possible extension
+ // TODO iterate through PATHEXT
+ if (extension_part(command).empty())
+ return lookup(create_filespec(folder_part(command), basename_part(command), "exe"), path, splitter);
+#endif
+ // if path lookup failed, return empty string to indicate error
+ return std::string();
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+ std::string install_path(const std::string& argv0)
+ {
+ std::string bin_directory = folder_part(argv0);
+ if (bin_directory.empty())
+ {
+ // do path lookup to find the executable path
+ bin_directory = folder_part(path_lookup(argv0));
+ }
+ return bin_directory;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // end namespace stlplus