File indexing completed on 2024-05-05 04:38:46

0001 /*
0002     SPDX-FileCopyrightText: 2012 Milian Wolff <mail@milianw.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #ifndef KDEVELOP_PATH_H
0008 #define KDEVELOP_PATH_H
0009 
0010 #include "utilexport.h"
0011 
0012 #include <QMetaType>
0013 #include <QString>
0014 #include <QVector>
0015 #include <QUrl>
0016 
0017 #include <algorithm>
0018 
0019 namespace KDevelop {
0020 
0021 /**
0022  * @return Return a string representation of @p url, if possible as local file
0023  *
0024  * Convenience method for working around https://bugreports.qt.io/browse/QTBUG-41729
0025  */
0026 QString KDEVPLATFORMUTIL_EXPORT toUrlOrLocalFile(const QUrl& url,
0027                                                  QUrl::FormattingOptions options = QUrl::FormattingOptions( QUrl::PrettyDecoded ));
0028 
0029 /**
0030  * @brief Path data type optimized for memory consumption.
0031  *
0032  * This class holds data that represents a local or remote path.
0033  * In the project model e.g. we usually store whole trees such as
0034  *
0035  * /foo/
0036  * /foo/bar/
0037  * /foo/bar/asdf.txt
0038  *
0039  * Normal QString/QUrl/QUrl types would not share any memory for these paths
0040  * at all. This class though can share the segments of the paths and thus
0041  * consume far less total memory.
0042  *
0043  * Just like the URL types, the Path can point to a remote location.
0044  *
0045  * Example for how to leverage memory sharing for the above input data:
0046  *
0047  * @code
0048  * Path foo("/foo");
0049  * Path bar(foo, "bar");
0050  * Path asdf(foo, "asdf.txt");
0051  * @endcode
0052  *
0053  * @note Just as with QString e.g. you won't share any data implicitly when
0054  * you do something like this:
0055  *
0056  * @code
0057  * Path foo1("/foo");
0058  * Path foo2("/foo");
0059  * @endcode
0060  */
0061 class KDEVPLATFORMUTIL_EXPORT Path
0062 {
0063 public:
0064     using List = QVector<Path>;
0065 
0066     /**
0067      * Construct an empty, invalid Path.
0068      */
0069     Path() = default;
0070 
0071     /**
0072      * Create a Path out of a string representation of a path or URL.
0073      *
0074      * @note Not every kind of remote URL is supported, rather only path-like
0075      * URLs without fragments, queries, sub-Paths and the like are supported.
0076      *
0077      * Empty paths or URLs containing one of the following are considered invalid:
0078      * - URL fragments (i.e. "...#fragment")
0079      * - URL queries (i.e. "...?query=")
0080      * - sub-URLs (i.e. "file:///tmp/kde.tgz#gzip:/#tar:/kdevelop")
0081      *
0082      * @sa isValid()
0083      */
0084     explicit Path(const QString& pathOrUrl);
0085 
0086     /**
0087      * Convert a QUrl to a Path.
0088      *
0089      * @note Not every kind of remote URL is supported, rather only path-like
0090      * URLs without fragments, queries, sub-Paths and the like are supported.
0091      *
0092      * Empty paths or URLs containing one of the following are considered invalid:
0093      * - URL fragments (i.e. "...#fragment")
0094      * - URL queries (i.e. "...?query=")
0095      * - sub-URLs (i.e. "file:///tmp/kde.tgz#gzip:/#tar:/kdevelop")
0096      *
0097      * @sa isValid()
0098      */
0099     explicit Path(const QUrl& url);
0100 
0101     /**
0102      * Create a copy of @p base and append a path segment @p subPath.
0103      *
0104      * This implicitly shares the data of @p base and thus is very efficient
0105      * memory wise compared to creating two Paths from separate strings.
0106      *
0107      * @p subPath A relative or absolute path. If this is an absolute path then
0108      * the path in @p base will be ignored and only the remote data copied. If
0109      * this is a relative path it will be combined with @p base.
0110      *
0111      * @sa addPath()
0112      */
0113     explicit Path(const Path& base, const QString& subPath);
0114 
0115     friend void swap(Path& a, Path& b) noexcept
0116     {
0117         a.m_data.swap(b.m_data);
0118     }
0119 
0120     /**
0121      * Equality comparison between @p other and this Path.
0122      *
0123      * @return true if @p other is equal to this Path.
0124      */
0125     inline bool operator==(const Path& other) const
0126     {
0127         if (other.m_data.data() == m_data.data())
0128             return true; // fast path when both containers point to the same shared data
0129         // The size check here is a bit faster than calling std::equal with 4 arguments.
0130         if (other.m_data.size() != m_data.size())
0131             return false;
0132         // Optimization: compare in reverse order as often the mismatch is at the end,
0133         // while the first few path segments are usually the same in different paths.
0134         return std::equal(m_data.rbegin(), m_data.rend(), other.m_data.rbegin());
0135     }
0136 
0137     /**
0138      * Inequality comparison between @p other and this Path.
0139      *
0140      * @return true if @p other is different from this Path.
0141      */
0142     inline bool operator!=(const Path& other) const
0143     {
0144         return !operator==(other);
0145     }
0146 
0147     /**
0148      * Compares *this with @p other and returns an integer less than, equal to,
0149      * or greater than zero if *this is less than, equal to, or greater than @p other.
0150      *
0151      * If @p cs is Qt::CaseSensitive, the comparison is case sensitive;
0152      * otherwise the comparison is case insensitive.
0153     */
0154     int compare(const Path& other, Qt::CaseSensitivity cs = Qt::CaseSensitive) const;
0155 
0156     /**
0157      * Less-than path comparison between @p other and this Path.
0158      *
0159      * @return true if this Path is less than @p other.
0160      */
0161     bool operator<(const Path& other) const
0162     {
0163         return compare(other, Qt::CaseSensitive) < 0;
0164     }
0165 
0166     /**
0167      * Greater-than path comparison between @p other and this Path.
0168      *
0169      * @return true if this Path is greater than @p other.
0170      */
0171     inline bool operator>(const Path& other) const
0172     {
0173         return other < *this;
0174     }
0175 
0176     /**
0177      * Less-than-equal path comparison between @p other and this Path.
0178      *
0179      * @return true if this Path is less than @p other or equal.
0180      */
0181     inline bool operator<=(const Path& other) const
0182     {
0183         return !(other < *this);
0184     }
0185 
0186     /**
0187      * Greater-than-equal path comparison between @p other and this Path.
0188      *
0189      * @return true if this Path is greater than @p other or equal.
0190      */
0191     inline bool operator>=(const Path& other) const
0192     {
0193         return !(*this < other);
0194     }
0195 
0196     /**
0197      * Check whether this Path is valid.
0198      *
0199      * @return true if the Path is valid, i.e. contains data, false otherwise.
0200      */
0201     inline bool isValid() const
0202     {
0203         return !m_data.isEmpty();
0204     }
0205 
0206     /**
0207      * Check whether this Path is empty.
0208      *
0209      * @return true if the Path is empty, false otherwise, i.e. if it contains data.
0210      */
0211     inline bool isEmpty() const
0212     {
0213         return m_data.isEmpty();
0214     }
0215 
0216     /**
0217      * Convert the Path to a string, yielding either the plain path for local
0218      * paths or the stringified URL for remote Paths.
0219      *
0220      * @return a string representation of this Path.
0221      * @sa path()
0222      */
0223     QString pathOrUrl() const;
0224 
0225     /**
0226      * Return the path segment of this Path. This is the same for local paths
0227      * as calling pathOrUrl(). The difference is only for remote paths which
0228      * would return the protocol, host etc. data in pathOrUrl but not here.
0229      *
0230      * @return the path segment of this Path.
0231      *
0232      * @sa pathOrUrl()
0233      */
0234     QString path() const;
0235 
0236     /**
0237      * Return the path for local path and an empty string for remote paths.
0238      */
0239     QString toLocalFile() const;
0240 
0241     /**
0242      * @return the relative path from this path to @p path.
0243      *
0244      * Examples:
0245      * @code
0246      * Path p1("/foo/bar");
0247      * Path p2("/foo/bar/asdf/test.txt");
0248      * p1.relativePath(p2); // returns: asdf/test.txt
0249      * Path p3("/foo/asdf/lala");
0250      * p3.relativePath(p1); // returns: ../../bar
0251      * @endcode
0252      *
0253      * @sa QUrl::relativePath
0254      */
0255     QString relativePath(const Path& path) const;
0256 
0257     /**
0258      * @return True if this path is the parent of @p path.
0259      *
0260      * For instance, ftp://host/dir/ is a parent of ftp://host/dir/subdir/blub,
0261      * or /foo is a parent of /foo/bar.
0262      *
0263      * NOTE: Contrary to QUrl::isParentOf this returns false if the path equals this one.
0264      */
0265     bool isParentOf(const Path& path) const;
0266 
0267     /**
0268      * @return True if this path is the direct parent of @p path.
0269      *
0270      * For instance, ftp://host/dir/ is the direct parent of ftp://host/dir/subdir,
0271      * but ftp.//host/ is a parent but not the direct parent.
0272      *
0273      * This is more efficient than @code path.parent() == *this @endcode since
0274      * it does not require any temporary allocations as for the parent() call.
0275      */
0276     bool isDirectParentOf(const Path& path) const;
0277 
0278     /**
0279      * @return the prefix of a remote URL containing protocol, host, port etc. pp.
0280      *         If this path is not remote, this returns an empty string.
0281      */
0282     QString remotePrefix() const;
0283 
0284     /**
0285      * @return a const reference to the internal data.
0286      *
0287      * @note Returning a reference to rather than a copy of QVector can substantially
0288      * improve performance of a tight loop that calls this function.
0289      *
0290      * TODO: return std::span once we can rely on C++20.
0291      */
0292     inline const QVector<QString>& segments() const
0293     {
0294         return m_data;
0295     }
0296 
0297     /**
0298      * @return the Path converted to a QUrl.
0299      */
0300     QUrl toUrl() const;
0301 
0302     /**
0303      * @return true when this Path points to a local file, false otherwise.
0304      */
0305     bool isLocalFile() const;
0306 
0307     /**
0308      * @return true when this Path points to a remote file, false otherwise.
0309      */
0310     bool isRemote() const;
0311 
0312     /**
0313      * @return the last element of the path.
0314      *
0315      * This will never return the remote URL prefix.
0316      */
0317     QString lastPathSegment() const;
0318 
0319     /**
0320      * Set the file name of this Path, i.e. the last element of the path.
0321      *
0322      * This will never overwrite the remote URL prefix.
0323      */
0324     void setLastPathSegment(const QString& name);
0325 
0326     /**
0327      * Append @p path to this Path.
0328      *
0329      * NOTE: If @p path starts with a slash, this function ignores it.
0330      *       I.e. you cannot set the path this way. @sa QUrl::addPath
0331      */
0332     void addPath(const QString& path);
0333 
0334     /**
0335      * @return the path pointing to the parent folder of this path.
0336      *
0337      * @sa KIO::upUrl()
0338      */
0339     Path parent() const;
0340 
0341     /**
0342      * @return true when this path has a parent and false if this is a root or invalid path.
0343      */
0344     bool hasParent() const;
0345 
0346     /**
0347      * Change directory by relative path @p dir.
0348      *
0349      * NOTE: This is expensive.
0350      *
0351      * @sa QUrl::cd
0352      */
0353     Path cd(const QString& dir) const;
0354 
0355 private:
0356     // for remote urls the first element contains the a Path prefix
0357     // containing the protocol, user, port etc. pp.
0358     QVector<QString> m_data;
0359 };
0360 
0361 KDEVPLATFORMUTIL_EXPORT uint qHash(const Path& path);
0362 
0363 /**
0364  * Convert the @p list of QUrls to a list of Paths.
0365  */
0366 KDEVPLATFORMUTIL_EXPORT Path::List toPathList(const QList<QUrl>& list);
0367 
0368 /**
0369  * Convert the @p list of QStrings to a list of Paths.
0370  */
0371 KDEVPLATFORMUTIL_EXPORT Path::List toPathList(const QList<QString>& list);
0372 }
0373 
0374 /**
0375  * qDebug() stream operator.  Writes the string to the debug output.
0376  */
0377 KDEVPLATFORMUTIL_EXPORT QDebug operator<<(QDebug s, const KDevelop::Path& string);
0378 
0379 namespace QTest {
0380 
0381 template<typename T> char* toString(const T&);
0382 /**
0383  * QTestLib integration to have nice output in e.g. QCOMPARE failures.
0384  */
0385 template<>
0386 KDEVPLATFORMUTIL_EXPORT char* toString(const KDevelop::Path& path);
0387 }
0388 
0389 Q_DECLARE_TYPEINFO(KDevelop::Path, Q_MOVABLE_TYPE);
0390 Q_DECLARE_METATYPE(KDevelop::Path)
0391 Q_DECLARE_TYPEINFO(KDevelop::Path::List, Q_MOVABLE_TYPE);
0392 
0393 #endif // KDEVELOP_PATH_H