X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fstlplus%2Fportability%2Ffile_system.cpp;fp=src%2Fstlplus%2Fportability%2Ffile_system.cpp;h=983d98483ac92d8285d8e44df41a008fe69524e2;hb=6b0a0d0efafe34d48ab344fca3b479553bd4e62c;hp=0000000000000000000000000000000000000000;hpb=85783316365181491a3e3c0c63659972477cebba;p=chaz%2Fyoink diff --git a/src/stlplus/portability/file_system.cpp b/src/stlplus/portability/file_system.cpp new file mode 100644 index 0000000..983d984 --- /dev/null +++ b/src/stlplus/portability/file_system.cpp @@ -0,0 +1,1058 @@ +//////////////////////////////////////////////////////////////////////////////// + +// 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