diff --git a/src/libs/modelinglib/qmt/controller/namecontroller.h b/src/libs/modelinglib/qmt/controller/namecontroller.h index 50ed023d201..45a10b1567f 100644 --- a/src/libs/modelinglib/qmt/controller/namecontroller.h +++ b/src/libs/modelinglib/qmt/controller/namecontroller.h @@ -44,6 +44,7 @@ private: public: static QString convertFileNameToElementName(const QString &fileName); static QString convertElementNameToBaseFileName(const QString &elementName); + // TODO use Utils::FilePath instead static QString calcRelativePath(const QString &absoluteFileName, const QString &anchorPath); static QString calcElementNameSearchId(const QString &elementName); static QStringList buildElementsPath(const QString &filePath, bool ignoreLastFilePathPart); diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index 7a67cfb4d52..46ce6db8db5 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -821,6 +821,15 @@ FilePath FilePath::absolutePath() const return result; } +/// Constructs an absolute FilePath from this path which +/// is interpreted as being relative to \a anchor. +FilePath FilePath::absoluteFromRelativePath(const FilePath &anchor) const +{ + QDir anchorDir = QFileInfo(anchor.m_data).absoluteDir(); + QString absoluteFilePath = QFileInfo(anchorDir, m_data).canonicalFilePath(); + return FilePath::fromString(absoluteFilePath); +} + /// Constructs a FilePath from \a filename /// \a filename is not checked for validity. FilePath FilePath::fromString(const QString &filename) @@ -970,6 +979,94 @@ FilePath FilePath::relativeChildPath(const FilePath &parent) const return FilePath::fromString(m_data.mid(parent.m_data.size() + 1, -1)); } +/// \returns the relativePath of FilePath to given \a anchor. +/// Both, FilePath and anchor may be files or directories. +/// Example usage: +/// +/// \code +/// FilePath filePath("/foo/b/ar/file.txt"); +/// FilePath relativePath = filePath.relativePath("/foo/c"); +/// qDebug() << relativePath +/// \endcode +/// +/// The debug output will be "../b/ar/file.txt". +/// +FilePath FilePath::relativePath(const FilePath &anchor) const +{ + const QFileInfo fileInfo(m_data); + QString absolutePath; + QString filename; + if (fileInfo.isFile()) { + absolutePath = fileInfo.absolutePath(); + filename = fileInfo.fileName(); + } else if (fileInfo.isDir()) { + absolutePath = fileInfo.absoluteFilePath(); + } else { + return {}; + } + const QFileInfo anchorInfo(anchor.m_data); + QString absoluteAnchorPath; + if (anchorInfo.isFile()) + absoluteAnchorPath = anchorInfo.absolutePath(); + else if (anchorInfo.isDir()) + absoluteAnchorPath = anchorInfo.absoluteFilePath(); + else + return {}; + QString relativeFilePath = calcRelativePath(absolutePath, absoluteAnchorPath); + if (!filename.isEmpty()) { + if (!relativeFilePath.isEmpty()) + relativeFilePath += '/'; + relativeFilePath += filename; + } + return FilePath::fromString(relativeFilePath); +} + +/// \returns the relativePath of \a absolutePath to given \a absoluteAnchorPath. +/// Both paths must be an absolute path to a directory. Example usage: +/// +/// \code +/// qDebug() << FilePath::calcRelativePath("/foo/b/ar", "/foo/c"); +/// \endcode +/// +/// The debug output will be "../b/ar". +/// +/// \see FilePath::relativePath +/// +QString FilePath::calcRelativePath(const QString &absolutePath, const QString &absoluteAnchorPath) +{ + if (absolutePath.isEmpty() || absoluteAnchorPath.isEmpty()) + return QString(); + // TODO using split() instead of parsing the strings by char index is slow + // and needs more memory (but the easiest implementation for now) + const QStringList splits1 = absolutePath.split('/'); + const QStringList splits2 = absoluteAnchorPath.split('/'); + int i = 0; + while (i < splits1.count() && i < splits2.count() && splits1.at(i) == splits2.at(i)) + ++i; + QString relativePath; + int j = i; + bool addslash = false; + while (j < splits2.count()) { + if (!splits2.at(j).isEmpty()) { + if (addslash) + relativePath += '/'; + relativePath += ".."; + addslash = true; + } + ++j; + } + while (i < splits1.count()) { + if (!splits1.at(i).isEmpty()) { + if (addslash) + relativePath += '/'; + relativePath += splits1.at(i); + addslash = true; + } + ++i; + } + return relativePath; +} + FilePath FilePath::pathAppended(const QString &str) const { FilePath fn = *this; diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index 73800d2df9c..47194dda420 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -61,6 +61,9 @@ extern Q_CORE_EXPORT int qt_ntfs_permission_lookup; QT_END_NAMESPACE +// tst_fileutils becomes a friend of Utils::FilePath for testing private method +class tst_fileutils; + namespace Utils { class QTCREATOR_UTILS_EXPORT FilePath @@ -89,6 +92,7 @@ public: FilePath parentDir() const; FilePath absolutePath() const; + FilePath absoluteFromRelativePath(const FilePath &anchor) const; bool operator==(const FilePath &other) const; bool operator!=(const FilePath &other) const; @@ -107,6 +111,7 @@ public: bool isNewerThan(const QDateTime &timeStamp) const; FilePath relativeChildPath(const FilePath &parent) const; + FilePath relativePath(const FilePath &anchor) const; FilePath pathAppended(const QString &str) const; FilePath stringAppended(const QString &str) const; FilePath resolvePath(const QString &fileName) const; @@ -126,6 +131,9 @@ public: QUrl toUrl() const; private: + friend class ::tst_fileutils; + static QString calcRelativePath(const QString &absolutePath, const QString &absoluteAnchorPath); + QString m_data; QUrl m_url; }; diff --git a/tests/auto/utils/fileutils/tst_fileutils.cpp b/tests/auto/utils/fileutils/tst_fileutils.cpp index b6eab1655ba..cd94526a7a1 100644 --- a/tests/auto/utils/fileutils/tst_fileutils.cpp +++ b/tests/auto/utils/fileutils/tst_fileutils.cpp @@ -38,14 +38,47 @@ class tst_fileutils : public QObject public: private slots: + void initTestCase(); void parentDir_data(); void parentDir(); void isChildOf_data(); void isChildOf(); void fileName_data(); void fileName(); + void calcRelativePath_data(); + void calcRelativePath(); + void relativePath_specials(); + void relativePath_data(); + void relativePath(); + +private: + QTemporaryDir tempDir; + QString rootPath; }; +static void touch(const QDir &dir, const QString &filename) +{ + QFile file(dir.absoluteFilePath(filename)); + file.open(QIODevice::WriteOnly); + file.close(); +} + +void tst_fileutils::initTestCase() +{ + // initialize test for tst_fileutiles::relativePath*() + QVERIFY(tempDir.isValid()); + rootPath = tempDir.path(); + QDir dir(rootPath); + dir.mkpath("a/b/c/d"); + dir.mkpath("a/x/y/z"); + dir.mkpath("a/b/x/y/z"); + dir.mkpath("x/y/z"); + touch(dir, "a/b/c/d/file1.txt"); + touch(dir, "a/x/y/z/file2.txt"); + touch(dir, "a/file3.txt"); + touch(dir, "x/y/file4.txt"); +} + void tst_fileutils::parentDir_data() { QTest::addColumn("path"); @@ -164,5 +197,65 @@ void tst_fileutils::fileName() QCOMPARE(FilePath::fromString(path).fileNameWithPathComponents(components), result); } +void tst_fileutils::calcRelativePath_data() +{ + QTest::addColumn("absolutePath"); + QTest::addColumn("anchorPath"); + QTest::addColumn("result"); + + QTest::newRow("empty") << "" << "" << ""; + QTest::newRow("leftempty") << "" << "/" << ""; + QTest::newRow("rightempty") << "/" << "" << ""; + QTest::newRow("root") << "/" << "/" << ""; + QTest::newRow("simple1") << "/a" << "/" << "a"; + QTest::newRow("simple2") << "/" << "/a" << ".."; + QTest::newRow("simple3") << "/a" << "/a" << ""; + QTest::newRow("extraslash1") << "/a/b/c" << "/a/b/c" << ""; + QTest::newRow("extraslash2") << "/a/b/c" << "/a/b/c/" << ""; + QTest::newRow("extraslash3") << "/a/b/c/" << "/a/b/c" << ""; + QTest::newRow("normal1") << "/a/b/c" << "/a/x" << "../b/c"; + QTest::newRow("normal2") << "/a/b/c" << "/a/x/y" << "../../b/c"; + QTest::newRow("normal3") << "/a/b/c" << "/x/y" << "../../a/b/c"; +} + +void tst_fileutils::calcRelativePath() +{ + QFETCH(QString, absolutePath); + QFETCH(QString, anchorPath); + QFETCH(QString, result); + QString relativePath = Utils::FilePath::calcRelativePath(absolutePath, anchorPath); + QCOMPARE(relativePath, result); +} + +void tst_fileutils::relativePath_specials() +{ + QString path = FilePath::fromString("").relativePath(FilePath::fromString("")).toString(); + QCOMPARE(path, ""); +} + +void tst_fileutils::relativePath_data() +{ + QTest::addColumn("relative"); + QTest::addColumn("anchor"); + QTest::addColumn("result"); + + QTest::newRow("samedir") << "/" << "/" << ""; + QTest::newRow("dir2dir_1") << "a/b/c/d" << "a/x/y/z" << "../../../b/c/d"; + QTest::newRow("dir2dir_2") << "a/b" <<"a/b/c" << ".."; + QTest::newRow("file2file_1") << "a/b/c/d/file1.txt" << "a/file3.txt" << "b/c/d/file1.txt"; + QTest::newRow("dir2file_1") << "a/b/c" << "a/x/y/z/file2.txt" << "../../../b/c"; + QTest::newRow("file2dir_1") << "a/b/c/d/file1.txt" << "x/y" << "../../a/b/c/d/file1.txt"; +} + +void tst_fileutils::relativePath() +{ + QFETCH(QString, relative); + QFETCH(QString, anchor); + QFETCH(QString, result); + FilePath actualPath = FilePath::fromString(rootPath + "/" + relative) + .relativePath(FilePath::fromString(rootPath + "/" + anchor)); + QCOMPARE(actualPath.toString(), result); +} + QTEST_APPLESS_MAIN(tst_fileutils) #include "tst_fileutils.moc"