--- a +++ b/src/glib/gpath.h @@ -0,0 +1,223 @@ +// +// Copyright (C) 2017 Graeme Walker +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// === +/// +/// \file gpath.h +/// + +#ifndef G_PATH_H +#define G_PATH_H + +#include "gdef.h" +#include "gstrings.h" +#include <string> +#include <iostream> + +namespace G +{ + class Path ; + class PathImp ; +} + +/// \class G::Path +/// A Path object represents a file system path. The class is concerned with +/// path syntax, not file system i/o. +/// +/// A full path is made up of a root, a set of directories, and a filename. The +/// posix root is just a forward slash, but on Windows the root can be complex, +/// possibly including non-splitting separator characters. The filename may have +/// an extension part, which is to the right of the right-most dot. +/// +/// The path separator is used between directories and filename, but only +/// between the root and the first directory if the root does not itself end in +/// a separator character. +/// +/// A windows drive-letter root may end with a separator character or not; if +/// there is no separator character at the end of the drive-letter root then +/// the path is relative to the drive's current working directory. +/// +/// Path components of "." are ignored by simple(), basename(), and dirname(). +/// Path components of ".." are retained but can be eliminated if they are +/// collapsed(). Path components of "." are eliminated by split(), except +/// in the degenerate case. +/// +/// This class is agnostic on the choice of utf8 or eight-bit characters since +/// the delimiters are all seven-bit ascii. On Windows it might make sense to +/// obtain paths from the win32 api as multi-byte and convert immediately to +/// utf8 before wrapping in G::Path. +/// +/// Both posix and windows behaviours are available at run-time; the default +/// behaviour is the native behaviour, but this can be overridden, typically +/// for testing purposes. +/// +/// The posix path separator character is the forward-slash; on Windows it is a +/// back-slash, but with all forward-slashes converted to back-slashes +/// immediately on input. +/// +/// \see G::File, G::Directory +/// +class G::Path +{ +public: + Path() ; + ///< Default constructor for a zero-length path. + ///< Postcondition: str().empty() + + Path( const std::string & path ) ; + ///< Implicit constructor from a string. + + Path( const char * path ) ; + ///< Implicit constructor from a c-style string. + + Path( const Path & path , const std::string & tail ) ; + ///< Constructor with an implicit pathAppend(). + + Path( const Path & path , const std::string & tail_1 , const std::string & tail_2 ) ; + ///< Constructor with two implicit pathAppend()s. + + Path( const Path & other ) ; + ///< Copy constructor. + + ~Path() ; + ///< Destructor. + + std::string str() const ; + ///< Returns the path string. + + bool simple() const ; + ///< Returns true if the path has a single component (ignoring "." parts), + ///< ie. the dirname() is empty. + + std::string basename() const ; + ///< Returns the rightmost part of the path, ignoring "." parts. + ///< For a directory path this may be "..", but see also collapsed(). + + Path dirname() const ; + ///< Returns the path without the rightmost part, ignoring "." parts. + ///< For simple() paths the empty path is returned. + + std::string extension() const ; + ///< Returns the path's basename extension, ie. anything + ///< after the rightmost dot. Returns the zero-length + ///< string if there is none. + + Path withExtension( const std::string & ext ) const ; + ///< Returns the path with the new basename extension. + ///< Any previous extension is replaced. The extension + ///< should not normally have a leading dot and it + ///< should not be the empty string. + + Path withoutExtension() const ; + ///< Returns a path without the basename extension, if any. + ///< As a special case, a basename() that starts with a dot + ///< is replaced by a single dot. Prefer withExtension() + ///< where appropriate to avoid this. + + bool isAbsolute() const ; + ///< Returns !isRelative(). + + bool isRelative() const ; + ///< Returns true if the path is a relative path. + + void pathAppend( const std::string & tail ) ; + ///< Appends a filename or a relative path to this path. + + StringArray split() const ; + ///< Spits the path into a list of component parts (ignoring "." parts + ///< unless the whole path is "."). + + static Path join( const StringArray & parts ) ; + ///< Builds a path from a set of parts. Note that part boundaries + ///< are not necessarily preserved once they have been join()ed + ///< into a path. + + static Path join( const Path & p1 , const Path & p2 ) ; + ///< Joins two paths together. The second should be a relative path. + + static Path difference( const Path & p1 , const Path & p2 ) ; + ///< Returns the relative path from p1 to p2. Returns the empty + ///< path if p2 is not under p1. Returns "." if p1 and p2 are the + ///< same. Input paths are collapsed(). Empty input paths are + ///< treated as ".". + + Path collapsed() const ; + ///< Returns the path with "foo/.." and "." parts removed, so far + ///< as is possible without changing the meaning of the path. + ///< Parts like "../foo" at the beginning of the path, or immediately + ///< following the root, are not removed. + + static Path nullDevice() ; + ///< Returns the path of the "/dev/null" special file, or equivalent. + + void swap( Path & other ) ; + ///< Swaps this with other. + + Path & operator=( const Path & other ) ; + ///< Assignment operator. + + bool operator==( const Path & path ) const ; + ///< Comparison operator. + + bool operator!=( const Path & path ) const ; + ///< Comparison operator. + + static void setPosixStyle() ; + ///< Sets posix mode for testing purposes. + + static void setWindowsStyle() ; + ///< Sets windows mode for testing purposes. + + static bool less( const Path & a , const Path & b ) ; + ///< Compares two paths, with simple eight-bit lexicographical + ///< comparisons of each path component. This is slightly different + ///< from a lexicographical comparison of the compete strings + ///< (eg. "a/b" compared to "a./b"), and it is not suitable for + ///< utf8 paths. + +private: + friend class G::PathImp ; + std::string m_str ; +} ; + +namespace G +{ + inline + std::ostream & operator<<( std::ostream & stream , const Path & path ) + { + return stream << path.str() ; + } + + inline + Path & operator+=( Path & p , const std::string & str ) + { + p.pathAppend( str ) ; + return p ; + } + + inline + Path operator+( const Path & p , const std::string & str ) + { + return Path( p , str ) ; + } + + inline + void swap( Path & p1 , Path & p2 ) + { + p1.swap( p2 ) ; + } +} + +#endif