X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fyoink;a=blobdiff_plain;f=src%2Fstlplus%2Fportability%2Ffile_system.cpp;h=cac32927e30adf39e7513891bb022235e54ab879;hp=cfca256232a3e7e6cb4e14fd76a0ece2b71a403b;hb=4f6e4488a55f7e3ba3f7485d78177f793c0eab9a;hpb=574af38ed616d1adfa5e6ce35f67cda1f707f89d diff --git a/src/stlplus/portability/file_system.cpp b/src/stlplus/portability/file_system.cpp index cfca256..cac3292 100644 --- a/src/stlplus/portability/file_system.cpp +++ b/src/stlplus/portability/file_system.cpp @@ -1,1058 +1,1121 @@ -//////////////////////////////////////////////////////////////////////////////// - -// 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 -#include -#include -#include -#include - -#ifdef MSWINDOWS -#include -#include -#include -#include -#include -#include -#include -#else -#include -#include -#include -#include -#include -#include -#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 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& path(void) const {return m_path;} - std::vector& path(void) {return m_path;} - void set_path(const std::vector& 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 subdirectories = folder_subdirectories(dir); - for (std::vector::size_type d = 0; d < subdirectories.size(); ++d) - if (!folder_delete(folder_down(dir,subdirectories[d]),true)) - result = false; - std::vector files = folder_files(dir); - for (std::vector::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 folder_subdirectories (const std::string& directory) - { - return folder_wildcard(directory, "*", true, false); - } - - std::vector folder_files (const std::string& directory) - { - return folder_wildcard(directory, "*", false, true); - } - - std::vector folder_all(const std::string& directory) - { - return folder_wildcard(directory, "*", true, true); - } - - std::vector folder_wildcard (const std::string& directory, const std::string& wild, bool subdirs, bool files) - { - std::string dir = directory.empty() ? std::string(".") : directory; - std::vector 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 filespec_elements(const std::string& filespec) - { - file_specification spec; - spec.initialise_file(filespec); - std::vector 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 folder_elements(const std::string& folder) - { - file_specification spec; - spec.initialise_folder(folder); - std::vector 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 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 +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// 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 +#include +#include +#include +#include + +#ifdef MSWINDOWS +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#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 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& path(void) const {return m_path;} + std::vector& path(void) {return m_path;} + void set_path(const std::vector& 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 + +// Under both Windows and Unix, the stat function is used for classification + +// Under Linux, the following classifications are defined +// source: Linux man page for stat(2) http://linux.die.net/man/2/stat +// S_IFMT 0170000 bitmask for the file type bitfields +// S_IFSOCK 0140000 socket (Note this overlaps with S_IFDIR) +// S_IFLNK 0120000 symbolic link +// S_IFREG 0100000 regular file +// S_IFBLK 0060000 block device +// S_IFDIR 0040000 directory +// S_IFCHR 0020000 character device +// S_IFIFO 0010000 FIFO +// There are also some Posix-standard macros: +// S_ISREG(m) is it a regular file? +// S_ISDIR(m) directory? +// S_ISCHR(m) character device? +// S_ISBLK(m) block device? +// S_ISFIFO(m) FIFO (named pipe)? +// S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.) +// S_ISSOCK(m) socket? (Not in POSIX.1-1996.) +// Under Windows, the following are defined: +// source: Header file sys/stat.h distributed with Visual Studio 10 +// _S_IFMT (S_IFMT) 0xF000 file type mask +// _S_IFREG (S_IFREG) 0x8000 regular +// _S_IFDIR (S_IFDIR) 0x4000 directory +// _S_IFCHR (S_IFCHR) 0x2000 character special +// _S_IFIFO 0x1000 pipe + +#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 +// Posix-style macros for Windows +#ifndef S_ISREG +#define S_ISREG(mode) ((mode & _S_IFMT) == _S_IFREG) +#endif +#ifndef S_ISDIR +#define S_ISDIR(mode) ((mode & _S_IFMT) == _S_IFDIR) +#endif +#ifndef S_ISCHR +#define S_ISCHR(mode) ((mode & _S_IFMT) == _S_IFCHR) +#endif +#ifndef S_ISBLK +#define S_ISBLK(mode) (false) +#endif +#ifndef S_ISFIFO +#define S_ISFIFO(mode) ((mode & _S_IFMT) == _S_IFIFO) +#endif +#ifndef S_ISLNK +#define S_ISLNK(mode) (false) +#endif +#ifndef S_ISSOCK +#define S_ISSOCK(mode) (false) +#endif +#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; + // If the object is present, see if it is a directory + // this is the Posix-approved way of testing + return S_ISDIR(buf.st_mode); + } + + 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; + // If the object is present, see if it is a file or file-like object + // Note that devices are neither folders nor files + // this is the Posix-approved way of testing + return S_ISREG(buf.st_mode) || S_ISLNK(buf.st_mode) || S_ISSOCK(buf.st_mode) || S_ISFIFO(buf.st_mode); + } + +//////////////////////////////////////////////////////////////////////////////// +// 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 subdirectories = folder_subdirectories(dir); + for (std::vector::size_type d = 0; d < subdirectories.size(); ++d) + if (!folder_delete(folder_down(dir,subdirectories[d]),true)) + result = false; + std::vector files = folder_files(dir); + for (std::vector::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]; + char* result = getcwd(pathname,MAXPATHLEN+1); + if (!result) + { + // should really report the error from errno + return std::string(); + } + return std::string(result); +#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 folder_subdirectories (const std::string& directory) + { + return folder_wildcard(directory, "*", true, false); + } + + std::vector folder_files (const std::string& directory) + { + return folder_wildcard(directory, "*", false, true); + } + + std::vector folder_all(const std::string& directory) + { + return folder_wildcard(directory, "*", true, true); + } + + std::vector folder_wildcard (const std::string& directory, const std::string& wild, bool subdirs, bool files) + { + std::string dir = directory.empty() ? std::string(".") : directory; + std::vector 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 filespec_elements(const std::string& filespec) + { + file_specification spec; + spec.initialise_file(filespec); + std::vector 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 folder_elements(const std::string& folder) + { + file_specification spec; + spec.initialise_folder(folder); + std::vector 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 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