Forked from
Core Modules / dune-common
6026 commits behind the upstream repository.
-
Jorrit Fahlke authored
[[Imported from SVN: r6039]]
Jorrit Fahlke authored[[Imported from SVN: r6039]]
path.cc 5.51 KiB
// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
// vi: set et ts=4 sw=2 sts=2:
#if HAVE_CONFIG_H
#include "config.h"
#endif
#include <algorithm>
#include <iterator>
#include <string>
#include <dune/common/exceptions.hh>
#include <dune/common/misc.hh>
#include <dune/common/path.hh>
namespace Dune {
/**
* @addtogroup Path Filesystem Paths
* @ingroup Common
* @{
*/
/**
* @file
* @author Jö Fahlke <jorrit@jorrit.de>
* @brief Utilites for handling filesystem paths
*/
//! concatenate two paths
std::string concatPaths(const std::string& base, const std::string& p) {
if(p == "") return base;
if(p[0] == '/') return p;
if(base == "") return p;
if(hasSuffix(base, "/")) return base+p;
else return base+'/'+p;
}
//! sanitize a path for further processing
std::string processPath(const std::string& p) {
std::string result = p;
std::string::size_type src, dst;
// append a '/' to non-empty paths
if(result != "") result += '/';
// each path component now has a trailing '/'
// collapse any occurance of multiple '/' to a single '/'
dst = src = 0;
while(src < result.size()) {
result[dst] = result[src];
++src;
if(result[dst] == '/')
while(src < result.size() && result[src] == '/')
++src;
++dst;
}
result.resize(dst);
// the path is now free of multiple '/' in a row
// collapse any occurance of "/./" to "/"
dst = src = 0;
while(src < result.size()) {
result[dst] = result[src];
++src;
if(result[dst] == '/')
while(src+1 < result.size() && result[src] == '.' &&
result[src+1] == '/')
src+=2;
++dst;
}
result.resize(dst);
// there may be at most one leading "./". If so, remove it
if(hasPrefix(result, "./")) result.erase(0, 2);
// the path is now free of "."-components
// remove any "<component>/../" pairs
src = 0;
while(true) {
src = result.find("/../", src);
if(src == std::string::npos)
break;
for(dst = src; dst > 0 && result[dst-1] != '/'; --dst) ;
if(result.substr(dst, src-dst) == "..") {
// don't remove "../../"
src += 3;
continue;
}
if(dst == src)
// special case: "<component>" is the empty component. This means we
// found a leading "/../" in an absolute path, remove "/.."
result.erase(0, 3);
else {
// remove "<component>/../".
result.erase(dst, src-dst+4);
src = dst;
// try to back up one character so we are at a '/' instead of at the
// beginning of a component
if(src > 0) --src;
}
}
// absolute paths are now free of ".." components, and relative paths
// contain only leading ".." components
return result;
}
//! check whether the given path indicates that it is a directory
bool pathIndicatesDirectory(const std::string& p) {
if(p == "") return true;
if(p == ".") return true;
if(p == "..") return true;
if(hasSuffix(p, "/")) return true;
if(hasSuffix(p, "/.")) return true;
if(hasSuffix(p, "/..")) return true;
else return false;
}
//! pretty print path
std::string prettyPath(const std::string& p, bool isDirectory) {
std::string result = processPath(p);
// current directory
if(result == "") return ".";
// root directory
if(result == "/") return result;
// remove the trailing '/' for now
result.resize(result.size()-1);
// if the result ends in "..", we don't need to append '/' to make clear
// it's a directory
if(result == ".." || hasSuffix(result, "/.."))
return result;
// if it's a directory, tuck the '/' back on
if(isDirectory) result += '/';
return result;
}
//! pretty print path
std::string prettyPath(const std::string& p) {
return prettyPath(p, pathIndicatesDirectory(p));
}
//! compute a relative path between two paths
std::string relativePath(const std::string& newbase, const std::string& p)
{
bool absbase = hasPrefix(newbase, "/");
bool absp = hasPrefix(p, "/");
if(absbase != absp)
DUNE_THROW(NotImplemented, "relativePath: paths must be either both "
"relative or both absolute: newbase=\"" << newbase << "\" "
"p=\"" << p << "\"");
std::string mybase = processPath(newbase);
std::string myp = processPath(p);
// remove as many matching leading components as possible
// determine prefix length
std::string::size_type preflen = 0;
while(preflen < mybase.size() && preflen < myp.size() &&
mybase[preflen] == myp[preflen])
++preflen;
// backup to the beginning of the component
while(preflen > 0 && myp[preflen-1] != '/')
--preflen;
mybase.erase(0, preflen);
myp.erase(0,preflen);
// if mybase contains leading ".." components, we're screwed
if(hasPrefix(mybase, "../"))
DUNE_THROW(NotImplemented, "relativePath: newbase has too many leading "
"\"..\" components: newbase=\"" << newbase << "\" "
"p=\"" << p << "\"");
// count the number of components in mybase
typedef std::iterator_traits<std::string::iterator>::difference_type
count_t;
count_t count = std::count(mybase.begin(), mybase.end(), '/');
std::string result;
// prefix with that many leading components
for(count_t i = 0; i < count; ++i)
result += "../";
// append what is left of p
result += myp;
return result;
}
/** @} group Path */
}