SGPath/Dir extensions to ease file handling in TerraGear. Also a unit-test, shocking.

This commit is contained in:
James Turner
2011-10-14 11:37:36 +01:00
parent e4e31be7d4
commit d37bf8a4ae
6 changed files with 450 additions and 39 deletions
+5
View File
@@ -41,3 +41,8 @@ target_link_libraries(test_tabbed_values sgmisc)
add_executable(test_strings strutils_test.cxx ) add_executable(test_strings strutils_test.cxx )
add_test(test_strings ${EXECUTABLE_OUTPUT_PATH}/test_strings) add_test(test_strings ${EXECUTABLE_OUTPUT_PATH}/test_strings)
target_link_libraries(test_strings sgmisc) 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)
+142
View File
@@ -0,0 +1,142 @@
#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
}
+103
View File
@@ -24,27 +24,59 @@
#ifdef _WIN32 #ifdef _WIN32
# define WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN
# include <windows.h> # include <windows.h>
# include <direct.h>
#else #else
# include <sys/types.h> # include <sys/types.h>
# include <dirent.h> # include <dirent.h>
# include <sys/stat.h>
# include <unistd.h>
# include <errno.h>
#endif #endif
#include <simgear/debug/logstream.hxx> #include <simgear/debug/logstream.hxx>
#include <boost/foreach.hpp>
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
using std::string;
namespace simgear namespace simgear
{ {
Dir::Dir(const SGPath& path) : Dir::Dir(const SGPath& path) :
_path(path) _path(path)
{ {
_path.set_cached(false); // disable caching, so create/remove work
} }
Dir::Dir(const Dir& rel, const SGPath& relPath) : Dir::Dir(const Dir& rel, const SGPath& relPath) :
_path(rel.file(relPath.str())) _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 PathList Dir::children(int types, const std::string& nameFilter) const
@@ -175,8 +207,79 @@ bool Dir::exists() const
SGPath Dir::file(const std::string& name) const SGPath Dir::file(const std::string& name) const
{ {
SGPath childPath = _path; SGPath childPath = _path;
childPath.set_cached(true);
childPath.append(name); childPath.append(name);
return childPath; 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 } // of namespace simgear
+32
View File
@@ -31,6 +31,10 @@
#include <simgear/math/sg_types.hxx> #include <simgear/math/sg_types.hxx>
#include <simgear/misc/sg_path.hxx> #include <simgear/misc/sg_path.hxx>
#ifdef _MSC_VER
typedef int mode_t;
#endif
namespace simgear namespace simgear
{ {
typedef std::vector<SGPath> PathList; typedef std::vector<SGPath> PathList;
@@ -38,6 +42,13 @@ namespace simgear
class Dir class Dir
{ {
public: 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 SGPath& path);
Dir(const Dir& rel, const SGPath& relPath); Dir(const Dir& rel, const SGPath& relPath);
@@ -53,10 +64,31 @@ namespace simgear
SGPath file(const std::string& name) const; 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!) * Check that the directory at the path exists (and is a directory!)
*/ */
bool exists() const; bool exists() const;
/**
* parent directory, if one exists
*/
Dir parent() const;
private: private:
mutable SGPath _path; mutable SGPath _path;
}; };
+118 -30
View File
@@ -28,13 +28,16 @@
#include <simgear/debug/logstream.hxx> #include <simgear/debug/logstream.hxx>
#include <stdio.h> #include <stdio.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <errno.h>
#ifdef _WIN32 #ifdef _WIN32
# include <direct.h> # include <direct.h>
#endif #endif
#include "sg_path.hxx" #include "sg_path.hxx"
using std::string; #include <boost/algorithm/string/case_conv.hpp>
using std::string;
/** /**
* define directory path separators * define directory path separators
@@ -58,14 +61,14 @@ SGPath::fix()
{ {
for ( string::size_type i = 0; i < path.size(); ++i ) { for ( string::size_type i = 0; i < path.size(); ++i ) {
#if defined( WIN32 ) #if defined( WIN32 )
// for windoze, don't replace the ":" for the second character // for windoze, don't replace the ":" for the second character
if ( i == 1 ) { if ( i == 1 ) {
continue; continue;
} }
#endif #endif
if ( path[i] == sgDirPathSepBad ) { if ( path[i] == sgDirPathSepBad ) {
path[i] = sgDirPathSep; path[i] = sgDirPathSep;
} }
} }
} }
@@ -73,7 +76,8 @@ SGPath::fix()
// default constructor // default constructor
SGPath::SGPath() SGPath::SGPath()
: path(""), : path(""),
_cached(false) _cached(false),
_cacheEnabled(true)
{ {
} }
@@ -81,7 +85,8 @@ SGPath::SGPath()
// create a path based on "path" // create a path based on "path"
SGPath::SGPath( const std::string& p ) SGPath::SGPath( const std::string& p )
: path(p), : path(p),
_cached(false) _cached(false),
_cacheEnabled(true)
{ {
fix(); fix();
} }
@@ -89,7 +94,8 @@ SGPath::SGPath( const std::string& p )
// create a path based on "path" and a "subpath" // create a path based on "path" and a "subpath"
SGPath::SGPath( const SGPath& p, const std::string& r ) SGPath::SGPath( const SGPath& p, const std::string& r )
: path(p.path), : path(p.path),
_cached(false) _cached(false),
_cacheEnabled(p._cacheEnabled)
{ {
append(r); append(r);
fix(); fix();
@@ -98,9 +104,11 @@ SGPath::SGPath( const SGPath& p, const std::string& r )
SGPath::SGPath(const SGPath& p) : SGPath::SGPath(const SGPath& p) :
path(p.path), path(p.path),
_cached(p._cached), _cached(p._cached),
_cacheEnabled(p._cacheEnabled),
_exists(p._exists), _exists(p._exists),
_isDir(p._isDir), _isDir(p._isDir),
_isFile(p._isFile) _isFile(p._isFile),
_modTime(p._modTime)
{ {
} }
@@ -108,9 +116,11 @@ SGPath& SGPath::operator=(const SGPath& p)
{ {
path = p.path; path = p.path;
_cached = p._cached; _cached = p._cached;
_cacheEnabled = p._cacheEnabled;
_exists = p._exists; _exists = p._exists;
_isDir = p._isDir; _isDir = p._isDir;
_isFile = p._isFile; _isFile = p._isFile;
_modTime = p._modTime;
return *this; return *this;
} }
@@ -126,16 +136,20 @@ void SGPath::set( const string& p ) {
_cached = false; _cached = false;
} }
void SGPath::set_cached(bool cached)
{
_cacheEnabled = cached;
}
// append another piece to the existing path // append another piece to the existing path
void SGPath::append( const string& p ) { void SGPath::append( const string& p ) {
if ( path.size() == 0 ) { if ( path.size() == 0 ) {
path = p; path = p;
} else { } else {
if ( p[0] != sgDirPathSep ) { if ( p[0] != sgDirPathSep ) {
path += sgDirPathSep; path += sgDirPathSep;
} }
path += p; path += p;
} }
fix(); fix();
_cached = false; _cached = false;
@@ -151,9 +165,9 @@ void SGPath::add( const string& p ) {
// path separator // path separator
void SGPath::concat( const string& p ) { void SGPath::concat( const string& p ) {
if ( path.size() == 0 ) { if ( path.size() == 0 ) {
path = p; path = p;
} else { } else {
path += p; path += p;
} }
fix(); fix();
_cached = false; _cached = false;
@@ -164,9 +178,9 @@ void SGPath::concat( const string& p ) {
string SGPath::file() const { string SGPath::file() const {
int index = path.rfind(sgDirPathSep); int index = path.rfind(sgDirPathSep);
if (index >= 0) { if (index >= 0) {
return path.substr(index + 1); return path.substr(index + 1);
} else { } else {
return ""; return "";
} }
} }
@@ -175,20 +189,45 @@ string SGPath::file() const {
string SGPath::dir() const { string SGPath::dir() const {
int index = path.rfind(sgDirPathSep); int index = path.rfind(sgDirPathSep);
if (index >= 0) { if (index >= 0) {
return path.substr(0, index); return path.substr(0, index);
} else { } else {
return ""; return "";
} }
} }
// get the base part of the path (everything but the extension.) // get the base part of the path (everything but the extension.)
string SGPath::base() const { string SGPath::base() const {
int index = path.rfind("."); unsigned int index = path.rfind(sgDirPathSep);
if ((index >= 0) && (path.find("/", index) == string::npos)) { if (index == string::npos) {
return path.substr(0, index); index = 0; // no separator in the name
} else { } 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 ".") // get the extension (everything after the final ".")
@@ -197,15 +236,36 @@ string SGPath::base() const {
string SGPath::extension() const { string SGPath::extension() const {
int index = path.rfind("."); int index = path.rfind(".");
if ((index >= 0) && (path.find("/", index) == string::npos)) { if ((index >= 0) && (path.find("/", index) == string::npos)) {
return path.substr(index + 1); return path.substr(index + 1);
} else { } else {
return ""; 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 {
++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 void SGPath::validate() const
{ {
if (_cached) { if (_cached && _cacheEnabled) {
return; return;
} }
@@ -221,6 +281,7 @@ void SGPath::validate() const
_exists = true; _exists = true;
_isFile = ((S_IFREG & buf.st_mode ) !=0); _isFile = ((S_IFREG & buf.st_mode ) !=0);
_isDir = ((S_IFDIR & buf.st_mode ) !=0); _isDir = ((S_IFDIR & buf.st_mode ) !=0);
_modTime = buf.st_mtime;
} }
#else #else
@@ -232,6 +293,7 @@ void SGPath::validate() const
_exists = true; _exists = true;
_isFile = ((S_ISREG(buf.st_mode )) != 0); _isFile = ((S_ISREG(buf.st_mode )) != 0);
_isDir = ((S_ISDIR(buf.st_mode )) != 0); _isDir = ((S_ISDIR(buf.st_mode )) != 0);
_modTime = buf.st_mtime;
} }
#endif #endif
@@ -383,3 +445,29 @@ std::string SGPath::str_native() const
return str(); return str();
#endif #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);
}
+50 -9
View File
@@ -32,6 +32,7 @@
#include <simgear/compiler.h> #include <simgear/compiler.h>
#include <string> #include <string>
#include <ctime>
#include <simgear/math/sg_types.hxx> #include <simgear/math/sg_types.hxx>
@@ -49,10 +50,6 @@
class SGPath { class SGPath {
private:
std::string path;
public: public:
/** Default constructor */ /** Default constructor */
@@ -86,6 +83,15 @@ public:
void set( const std::string& p ); void set( const std::string& p );
SGPath& operator= ( const char* p ) { this->set(p); return *this; } 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 * Append another piece to the existing path. Inserts a path
* separator between the existing component and the new component. * separator between the existing component and the new component.
@@ -123,12 +129,33 @@ public:
*/ */
std::string base() const; 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 ".") * Get the extension part of the path (everything after the final ".")
* @return the extension string * @return the extension string
*/ */
std::string extension() const; 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 * Get the path string
* @return path string * @return path string
@@ -176,16 +203,30 @@ public:
* check for default constructed path * check for default constructed path
*/ */
bool isNull() const; bool isNull() const;
/**
* delete the file, if possible
*/
bool remove();
/**
* modification time of the file
*/
time_t modTime() const;
private: private:
void fix(); void fix();
void validate() const; void validate() const;
mutable bool _cached; std::string path;
mutable bool _exists;
mutable bool _isDir; mutable bool _cached : 1;
mutable bool _isFile; 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;
}; };