add_executable(test_strings strutils_test.cxx )
add_test(test_strings ${EXECUTABLE_OUTPUT_PATH}/test_strings)
target_link_libraries(test_strings sgmisc)
+
+add_executable(test_path path_test.cxx )
+add_test(test_path ${EXECUTABLE_OUTPUT_PATH}/test_path)
+target_link_libraries(test_path sgmisc sgdebug)
+
--- /dev/null
+
+#include <simgear/compiler.h>
+
+#include <iostream>
+
+using std::cout;
+using std::cerr;
+using std::endl;
+
+#define COMPARE(a, b) \
+ if ((a) != (b)) { \
+ cerr << "failed:" << #a << " != " << #b << endl; \
+ exit(1); \
+ }
+
+#define VERIFY(a) \
+ if (!(a)) { \
+ cerr << "failed:" << #a << endl; \
+ exit(1); \
+ }
+
+#include <simgear/misc/sg_path.hxx>
+#include <simgear/misc/sg_dir.hxx>
+
+void test_dir()
+{
+ simgear::Dir temp = simgear::Dir::tempDir("foo");
+ cout << "created:" << temp.path().str() << endl;
+
+ VERIFY(temp.exists());
+ VERIFY(temp.path().isDir());
+ VERIFY(!temp.path().isFile());
+
+ SGPath fileInDir = temp.file("foobaz");
+ VERIFY(!fileInDir.exists());
+
+ if (!temp.remove(true)) {
+ cout << "remove failed!" << endl;
+ }
+
+ cout << temp.path().modTime() << endl;
+}
+
+int main(int argc, char* argv[])
+{
+ SGPath pa;
+ VERIFY(pa.isNull());
+ COMPARE(pa.exists(), false);
+
+// test basic parsing
+ SGPath pb("/Foo/bar/something.png");
+ COMPARE(pb.str(), std::string("/Foo/bar/something.png"));
+ COMPARE(strcmp(pb.c_str(), "/Foo/bar/something.png"), 0);
+ COMPARE(pb.dir(), std::string("/Foo/bar"));
+ COMPARE(pb.file(), std::string("something.png"));
+ COMPARE(pb.base(), std::string("/Foo/bar/something"));
+ COMPARE(pb.file_base(), std::string("something"));
+ COMPARE(pb.extension(), std::string("png"));
+ VERIFY(pb.isAbsolute());
+ VERIFY(!pb.isRelative());
+
+// relative paths
+ SGPath ra("where/to/begin.txt");
+ COMPARE(ra.str(), std::string("where/to/begin.txt"));
+ COMPARE(ra.dir(), std::string("where/to"));
+ COMPARE(ra.file(), std::string("begin.txt"));
+ COMPARE(ra.file_base(), std::string("begin"));
+ VERIFY(!ra.isAbsolute());
+ VERIFY(ra.isRelative());
+
+// dots in paths / missing extensions
+ SGPath pk("/Foo/bar.dot/thing");
+ COMPARE(pk.dir(), std::string("/Foo/bar.dot"));
+ COMPARE(pk.file(), std::string("thing"));
+ COMPARE(pk.base(), std::string("/Foo/bar.dot/thing"));
+ COMPARE(pk.file_base(), std::string("thing"));
+ COMPARE(pk.extension(), std::string());
+
+// multiple file extensions
+ SGPath pj("/Foo/zot.dot/thing.tar.gz");
+ COMPARE(pj.dir(), std::string("/Foo/zot.dot"));
+ COMPARE(pj.file(), std::string("thing.tar.gz"));
+ COMPARE(pj.base(), std::string("/Foo/zot.dot/thing"));
+ COMPARE(pj.file_base(), std::string("thing"));
+ COMPARE(pj.extension(), std::string("gz"));
+ COMPARE(pj.complete_lower_extension(), std::string("tar.gz"));
+
+// path fixing
+ SGPath rd("where\\to\\begin.txt");
+ COMPARE(rd.str(), std::string("where/to/begin.txt"));
+
+// test modification
+// append
+ SGPath d1("/usr/local");
+ SGPath pc = d1;
+ COMPARE(pc.str(), std::string("/usr/local"));
+ pc.append("include");
+
+ COMPARE(pc.str(), std::string("/usr/local/include"));
+ COMPARE(pc.file(), std::string("include"));
+
+// add
+ pc.add("/opt/local");
+ COMPARE(pc.str(), std::string("/usr/local/include/:/opt/local"));
+
+// concat
+ SGPath pd = pb;
+ pd.concat("-1");
+ COMPARE(pd.str(), std::string("/Foo/bar/something.png-1"));
+
+// create with relative path
+ SGPath rb(d1, "include/foo");
+ COMPARE(rb.str(), std::string("/usr/local/include/foo"));
+ VERIFY(rb.isAbsolute());
+
+// lower-casing of file extensions
+ SGPath extA("FOO.ZIP");
+ COMPARE(extA.base(), "FOO");
+ COMPARE(extA.extension(), "ZIP");
+ COMPARE(extA.lower_extension(), "zip");
+ COMPARE(extA.complete_lower_extension(), "zip");
+
+ SGPath extB("BAH/FOO.HTML.GZ");
+ COMPARE(extB.extension(), "GZ");
+ COMPARE(extB.base(), "BAH/FOO");
+ COMPARE(extB.lower_extension(), "gz");
+ COMPARE(extB.complete_lower_extension(), "html.gz");
+#ifdef _WIN32
+ COMPARE(d1.str_native(), std::string("\\usr\\local"));
+
+ SGPath winAbs("C:\\Windows\\System32");
+ COMPARE(winAbs.str(), std::string("C:/Windows/System32"));
+#else
+ COMPARE(d1.str_native(), std::string("/usr/local"));
+#endif
+
+ test_dir();
+
+ cout << "all tests passed OK" << endl;
+ return 0; // passed
+}
+
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
+# include <direct.h>
#else
# include <sys/types.h>
# include <dirent.h>
+# include <sys/stat.h>
+# include <unistd.h>
+# include <errno.h>
#endif
#include <simgear/debug/logstream.hxx>
+#include <boost/foreach.hpp>
#include <cstring>
#include <iostream>
+using std::string;
+
namespace simgear
{
Dir::Dir(const SGPath& path) :
_path(path)
{
+ _path.set_cached(false); // disable caching, so create/remove work
}
Dir::Dir(const Dir& rel, const SGPath& relPath) :
_path(rel.file(relPath.str()))
{
+ _path.set_cached(false); // disable caching, so create/remove work
+}
+
+Dir Dir::current()
+{
+#ifdef _WIN32
+ char* buf = _getcwd(NULL, 0);
+#else
+ char* buf = ::getcwd(NULL, 0);
+#endif
+ SGPath p(buf);
+ free(buf);
+ return Dir(p);
+}
+
+Dir Dir::tempDir(const std::string& templ)
+{
+ SGPath p(tempnam(0, templ.c_str()));
+ Dir t(p);
+ if (!t.create(0700)) {
+ SG_LOG(SG_IO, SG_WARN, "failed to create temporary directory at " << p.str());
+ }
+
+ return t;
}
PathList Dir::children(int types, const std::string& nameFilter) const
SGPath Dir::file(const std::string& name) const
{
SGPath childPath = _path;
+ childPath.set_cached(true);
childPath.append(name);
return childPath;
}
+#ifdef _WIN32
+# define sgMkDir(d,m) _mkdir(d)
+#else
+# define sgMkDir(d,m) mkdir(d,m)
+#endif
+
+bool Dir::create(mode_t mode)
+{
+ if (exists()) {
+ return false; // already exists
+ }
+
+// recursively create parent directories
+ Dir pr(parent());
+ if (!pr.exists()) {
+ bool ok = pr.create(mode);
+ if (!ok) {
+ return false;
+ }
+ }
+
+// finally, create ourselves
+ int err = sgMkDir(_path.c_str(), mode);
+ if (err) {
+ SG_LOG(SG_IO, SG_WARN, "directory creation failed: (" << _path.str() << ") " << strerror(errno) );
+ }
+
+ return (err == 0);
+}
+
+bool Dir::remove(bool recursive)
+{
+ if (!exists()) {
+ SG_LOG(SG_IO, SG_WARN, "attempt to remove non-existant dir:" << _path.str());
+ return false;
+ }
+
+ if (recursive) {
+ bool ok;
+ PathList cs = children(NO_DOT_OR_DOTDOT | INCLUDE_HIDDEN | TYPE_FILE | TYPE_DIR);
+ BOOST_FOREACH(SGPath path, cs) {
+ if (path.isDir()) {
+ Dir childDir(path);
+ ok = childDir.remove(true);
+ } else {
+ ok = path.remove();
+ }
+
+ if (!ok) {
+ return false;
+ }
+ } // of child iteration
+ } // of recursive deletion
+
+#ifdef _WIN32
+ int err = _rmdir(_path.c_str());
+#else
+ int err = rmdir(_path.c_str());
+#endif
+ if (err) {
+ SG_LOG(SG_IO, SG_WARN, "rmdir failed:" << _path.str() << ":" << strerror(errno));
+ }
+ return (err == 0);
+}
+
+Dir Dir::parent() const
+{
+ return Dir(_path.dir());
+}
+
} // of namespace simgear
#include <simgear/math/sg_types.hxx>
#include <simgear/misc/sg_path.hxx>
+#ifdef _MSC_VER
+ typedef int mode_t;
+#endif
+
namespace simgear
{
typedef std::vector<SGPath> PathList;
class Dir
{
public:
+ static Dir current();
+
+ /**
+ * create a temporary directory, using the supplied name
+ */
+ static Dir tempDir(const std::string& templ);
+
Dir(const SGPath& path);
Dir(const Dir& rel, const SGPath& relPath);
SGPath file(const std::string& name) const;
+ SGPath path() const
+ { return _path; }
+
+ /**
+ * create the directory (and any parents as required) with the
+ * request mode, or return failure
+ */
+ bool create(mode_t mode);
+
+ /**
+ * remove the directory.
+ * If recursive is true, contained files and directories are
+ * recursively removed
+ */
+ bool remove(bool recursive = false);
+
/**
* Check that the directory at the path exists (and is a directory!)
*/
bool exists() const;
+
+ /**
+ * parent directory, if one exists
+ */
+ Dir parent() const;
private:
mutable SGPath _path;
};
#include <simgear/debug/logstream.hxx>
#include <stdio.h>
#include <sys/stat.h>
+#include <errno.h>
+
#ifdef _WIN32
# include <direct.h>
#endif
#include "sg_path.hxx"
-using std::string;
+#include <boost/algorithm/string/case_conv.hpp>
+using std::string;
/**
* define directory path separators
{
for ( string::size_type i = 0; i < path.size(); ++i ) {
#if defined( WIN32 )
- // for windoze, don't replace the ":" for the second character
- if ( i == 1 ) {
- continue;
- }
+ // for windoze, don't replace the ":" for the second character
+ if ( i == 1 ) {
+ continue;
+ }
#endif
- if ( path[i] == sgDirPathSepBad ) {
- path[i] = sgDirPathSep;
- }
+ if ( path[i] == sgDirPathSepBad ) {
+ path[i] = sgDirPathSep;
+ }
}
}
// default constructor
SGPath::SGPath()
: path(""),
- _cached(false)
+ _cached(false),
+ _cacheEnabled(true)
{
}
// create a path based on "path"
SGPath::SGPath( const std::string& p )
: path(p),
- _cached(false)
+ _cached(false),
+ _cacheEnabled(true)
{
fix();
}
// create a path based on "path" and a "subpath"
SGPath::SGPath( const SGPath& p, const std::string& r )
: path(p.path),
- _cached(false)
+ _cached(false),
+ _cacheEnabled(p._cacheEnabled)
{
append(r);
fix();
SGPath::SGPath(const SGPath& p) :
path(p.path),
_cached(p._cached),
+ _cacheEnabled(p._cacheEnabled),
_exists(p._exists),
_isDir(p._isDir),
- _isFile(p._isFile)
+ _isFile(p._isFile),
+ _modTime(p._modTime)
{
}
{
path = p.path;
_cached = p._cached;
+ _cacheEnabled = p._cacheEnabled;
_exists = p._exists;
_isDir = p._isDir;
_isFile = p._isFile;
+ _modTime = p._modTime;
return *this;
}
_cached = false;
}
+void SGPath::set_cached(bool cached)
+{
+ _cacheEnabled = cached;
+}
// append another piece to the existing path
void SGPath::append( const string& p ) {
if ( path.size() == 0 ) {
- path = p;
+ path = p;
} else {
- if ( p[0] != sgDirPathSep ) {
- path += sgDirPathSep;
- }
- path += p;
+ if ( p[0] != sgDirPathSep ) {
+ path += sgDirPathSep;
+ }
+ path += p;
}
fix();
_cached = false;
// path separator
void SGPath::concat( const string& p ) {
if ( path.size() == 0 ) {
- path = p;
+ path = p;
} else {
- path += p;
+ path += p;
}
fix();
_cached = false;
string SGPath::file() const {
int index = path.rfind(sgDirPathSep);
if (index >= 0) {
- return path.substr(index + 1);
+ return path.substr(index + 1);
} else {
- return "";
+ return "";
}
}
string SGPath::dir() const {
int index = path.rfind(sgDirPathSep);
if (index >= 0) {
- return path.substr(0, index);
+ return path.substr(0, index);
} else {
- return "";
+ return "";
}
}
// get the base part of the path (everything but the extension.)
string SGPath::base() const {
- int index = path.rfind(".");
- if ((index >= 0) && (path.find("/", index) == string::npos)) {
- return path.substr(0, index);
+ unsigned int index = path.rfind(sgDirPathSep);
+ if (index == string::npos) {
+ index = 0; // no separator in the name
} else {
- return "";
+ ++index; // skip past the separator
}
+
+// find the first dot after the final separator
+ unsigned int firstDot = path.find(".", index);
+ if (firstDot == string::npos) {
+ return path; // no extension, return whole path
+ }
+
+ return path.substr(0, firstDot);
+}
+
+string SGPath::file_base() const
+{
+ unsigned int index = path.rfind(sgDirPathSep);
+ if (index == string::npos) {
+ index = 0; // no separator in the name
+ } else {
+ ++index; // skip past the separator
+ }
+
+ unsigned int firstDot = path.find(".", index);
+ if (firstDot == string::npos) {
+ return path.substr(index); // no extensions
+ }
+
+ return path.substr(index, firstDot - index);
}
// get the extension (everything after the final ".")
string SGPath::extension() const {
int index = path.rfind(".");
if ((index >= 0) && (path.find("/", index) == string::npos)) {
- return path.substr(index + 1);
+ return path.substr(index + 1);
+ } else {
+ return "";
+ }
+}
+
+string SGPath::lower_extension() const {
+ return boost::to_lower_copy(extension());
+}
+
+string SGPath::complete_lower_extension() const
+{
+ unsigned int index = path.rfind(sgDirPathSep);
+ if (index == string::npos) {
+ index = 0; // no separator in the name
} else {
- return "";
+ ++index; // skip past the separator
+ }
+
+ int firstDot = path.find(".", index);
+ if ((firstDot >= 0) && (path.find(sgDirPathSep, firstDot) == string::npos)) {
+ return boost::to_lower_copy(path.substr(firstDot + 1));
+ } else {
+ return "";
}
}
void SGPath::validate() const
{
- if (_cached) {
+ if (_cached && _cacheEnabled) {
return;
}
_exists = true;
_isFile = ((S_IFREG & buf.st_mode ) !=0);
_isDir = ((S_IFDIR & buf.st_mode ) !=0);
+ _modTime = buf.st_mtime;
}
#else
_exists = true;
_isFile = ((S_ISREG(buf.st_mode )) != 0);
_isDir = ((S_ISDIR(buf.st_mode )) != 0);
+ _modTime = buf.st_mtime;
}
#endif
return str();
#endif
}
+
+bool SGPath::remove()
+{
+ int err = ::unlink(c_str());
+ if (err) {
+ SG_LOG(SG_IO, SG_WARN, "file remove failed: (" << str() << ") " << strerror(errno));
+ }
+ return (err == 0);
+}
+
+time_t SGPath::modTime() const
+{
+ validate();
+ return _modTime;
+}
+
+bool SGPath::operator==(const SGPath& other) const
+{
+ return (path == other.path);
+}
+
+bool SGPath::operator!=(const SGPath& other) const
+{
+ return (path != other.path);
+}
+
#include <simgear/compiler.h>
#include <string>
+#include <ctime>
#include <simgear/math/sg_types.hxx>
class SGPath {
-private:
-
- std::string path;
-
public:
/** Default constructor */
void set( const std::string& p );
SGPath& operator= ( const char* p ) { this->set(p); return *this; }
+ bool operator==(const SGPath& other) const;
+ bool operator!=(const SGPath& other) const;
+
+ /**
+ * Set if file information (exists, type, mod-time) is cached or
+ * retrieved each time it is queried. Caching is enabled by default
+ */
+ void set_cached(bool cached);
+
/**
* Append another piece to the existing path. Inserts a path
* separator between the existing component and the new component.
*/
std::string base() const;
+ /**
+ * Get the base part of the filename (everything before the first '.')
+ * @return the base filename
+ */
+ std::string file_base() const;
+
/**
* Get the extension part of the path (everything after the final ".")
* @return the extension string
*/
std::string extension() const;
-
+
+ /**
+ * Get the extension part of the path (everything after the final ".")
+ * converted to lowercase
+ * @return the extension string
+ */
+ std::string lower_extension() const;
+
+ /**
+ * Get the complete extension part of the path (everything after the first ".")
+ * this might look like 'tar.gz' or 'txt.Z', or might be identical to 'extension' above
+ * the extension is converted to lowercase.
+ * @return the extension string
+ */
+ std::string complete_lower_extension() const;
+
/**
* Get the path string
* @return path string
* check for default constructed path
*/
bool isNull() const;
+
+ /**
+ * delete the file, if possible
+ */
+ bool remove();
+
+ /**
+ * modification time of the file
+ */
+ time_t modTime() const;
private:
void fix();
void validate() const;
- mutable bool _cached;
- mutable bool _exists;
- mutable bool _isDir;
- mutable bool _isFile;
+ std::string path;
+
+ mutable bool _cached : 1;
+ bool _cacheEnabled : 1; ///< cacheing can be disbled if required
+ mutable bool _exists : 1;
+ mutable bool _isDir : 1;
+ mutable bool _isFile : 1;
+ mutable time_t _modTime;
};