File indexing completed on 2024-04-28 04:38:28

0001 /*
0002     SPDX-FileCopyrightText: 2014 Sergey Kalinichev <kalinichev.so.0@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "includepathsconverter.h"
0008 
0009 #include <KSharedConfig>
0010 
0011 #include <QCommandLineParser>
0012 #include <QCoreApplication>
0013 #include <QDirIterator>
0014 #include <QFileInfo>
0015 #include <QTextStream>
0016 
0017 #include <algorithm>
0018 
0019 #include "settingsmanager.h"
0020 
0021 using namespace KDevelop;
0022 
0023 namespace {
0024 KSharedConfigPtr openConfigFile(const QString& configFile)
0025 {
0026     return KSharedConfig::openConfig(configFile, KConfig::SimpleConfig);
0027 }
0028 
0029 QString findconfigFile(const QString& projectDir)
0030 {
0031     QDirIterator dirIterator(projectDir + QLatin1String("/.kdev4"));
0032     while (dirIterator.hasNext()) {
0033         dirIterator.next();
0034         if (dirIterator.fileName().endsWith(QLatin1String(".kdev4"))) {
0035             return dirIterator.fileInfo().canonicalFilePath();
0036         }
0037     }
0038     return {};
0039 }
0040 
0041 QString findProject(const QString& subdirectory)
0042 {
0043     QDir project(subdirectory);
0044     do {
0045         if (project.exists(QStringLiteral(".kdev4"))) {
0046             return project.path();
0047         }
0048     } while(project.cdUp());
0049 
0050     return {};
0051 }
0052 }
0053 
0054 IncludePathsConverter::IncludePathsConverter()
0055 {
0056 }
0057 
0058 bool IncludePathsConverter::addIncludePaths(const QStringList& includeDirectories, const QString& projectConfigFile, const QString& subdirectory)
0059 {
0060     auto configFile = openConfigFile(projectConfigFile);
0061     if (!configFile) {
0062         return false;
0063     }
0064 
0065     auto configEntries = SettingsManager::globalInstance()->readPaths(configFile.data());
0066     QString path = subdirectory.isEmpty() ? QStringLiteral(".") : subdirectory;
0067 
0068     ConfigEntry config;
0069     for (auto& entry: configEntries) {
0070         if (path == entry.path) {
0071             config = entry;
0072             config.includes += includeDirectories;
0073             config.includes.removeDuplicates();
0074             entry = config;
0075             break;
0076         }
0077     }
0078 
0079     if (config.path.isEmpty()) {
0080         config.path = path;
0081         config.includes = includeDirectories;
0082         configEntries.append(config);
0083     }
0084 
0085     SettingsManager::globalInstance()->writePaths(configFile.data(), configEntries);
0086 
0087     return true;
0088 }
0089 
0090 bool IncludePathsConverter::removeIncludePaths(const QStringList& includeDirectories, const QString& projectConfigFile, const QString& subdirectory)
0091 {
0092     auto configFile = openConfigFile(projectConfigFile);
0093     if (!configFile) {
0094         return false;
0095     }
0096 
0097     auto configEntries = SettingsManager::globalInstance()->readPaths(configFile.data());
0098     QString path = subdirectory.isEmpty() ? QStringLiteral(".") : subdirectory;
0099 
0100     for (auto& entry: configEntries) {
0101         if (path == entry.path) {
0102             for(const auto& include: includeDirectories) {
0103                 entry.includes.removeAll(include);
0104             }
0105             SettingsManager::globalInstance()->writePaths(configFile.data(), configEntries);
0106             return true;
0107         }
0108     }
0109 
0110     return true;
0111 }
0112 
0113 QStringList IncludePathsConverter::readIncludePaths(const QString& projectConfigFile, const QString& subdirectory) const
0114 {
0115     auto configFile = openConfigFile(projectConfigFile);
0116     if (!configFile) {
0117         return  {};
0118     }
0119 
0120     QString path = subdirectory.isEmpty() ? QStringLiteral(".") : subdirectory;
0121     const auto configEntries = SettingsManager::globalInstance()->readPaths(configFile.data());
0122     for (const auto& entry: configEntries) {
0123         if (path == entry.path) {
0124             return entry.includes;
0125         }
0126     }
0127 
0128     return {};
0129 }
0130 
0131 int main(int argc, char** argv)
0132 {
0133     QCoreApplication app(argc, argv);
0134     QCoreApplication::setApplicationName(QStringLiteral("kdev_includepathsconverter"));
0135 
0136     QCommandLineParser parser;
0137     parser.setApplicationDescription(QStringLiteral("\nAdds, removes or shows include directories of a project. Also it can be used as a tool to convert include directories from .kdev_include_paths file to the new format.\n\n"
0138     "Examples:\ncat /project/path/.kdev_include_paths | xargs -d '\\n' kdev_includepathsconverter -a /project/path/\n\n"
0139     "kdev_includepathsconverter -r /project/path/subdirectory/ \"/some/include/dir\" \"/another/include/dir\" \n\n"
0140     "kdev_includepathsconverter -l /project/path/another/subdirectory/"));
0141     parser.addHelpOption();
0142 
0143     QCommandLineOption listOption(QStringLiteral("l"), QCoreApplication::translate("main", "Shows include directories used by the project"),
0144                                      QCoreApplication::translate("main", "project"));
0145     parser.addOption(listOption);
0146     QCommandLineOption addOption(QStringLiteral("a"), QCoreApplication::translate("main", "Adds include directories to the project"),
0147                                      QCoreApplication::translate("main", "project"));
0148     parser.addOption(addOption);
0149     QCommandLineOption removeOption(QStringLiteral("r"), QCoreApplication::translate("main", "Removes include directories from the project"),
0150                                      QCoreApplication::translate("main", "project"));
0151     parser.addOption(removeOption);
0152 
0153     parser.process(app);
0154 
0155     QString projectDir;
0156     QStringList includeDirectories(parser.positionalArguments());
0157 
0158     std::transform(includeDirectories.begin(), includeDirectories.end(), includeDirectories.begin(), [](const QString& path) {
0159         return path.trimmed();
0160     } );
0161     includeDirectories.erase(std::remove_if(includeDirectories.begin(), includeDirectories.end(), [](const QString& path) {
0162         return path.isEmpty();
0163     } ), includeDirectories.end());
0164 
0165     bool show = parser.isSet(listOption);
0166     bool add = parser.isSet(addOption);
0167     bool remove = parser.isSet(removeOption);
0168     if (show) {
0169         projectDir = parser.value(listOption);
0170     } else if(add) {
0171         projectDir = parser.value(addOption);
0172     } else if(remove) {
0173         projectDir = parser.value(removeOption);
0174     }
0175 
0176     if (projectDir.isEmpty()) {
0177         parser.showHelp(-1);
0178     }
0179 
0180     QString subdirectory = projectDir;
0181     projectDir = findProject(projectDir);
0182 
0183     QString configFile = findconfigFile(projectDir);
0184 
0185     QTextStream out(stdout);
0186 
0187     if (configFile.isEmpty()) {
0188         out << QCoreApplication::translate("main", "No project found for: ") << subdirectory;
0189         return -1;
0190     }
0191 
0192     if (add || remove) {
0193         if (includeDirectories.isEmpty()) {
0194             parser.showHelp(-1);
0195         }
0196     }
0197 
0198     {
0199         auto subdirCanonical = QFileInfo(subdirectory).canonicalFilePath();
0200         auto projectCanonical = QFileInfo(projectDir).canonicalFilePath();
0201         if (subdirCanonical != projectCanonical) {
0202             subdirectory = subdirCanonical.mid(projectCanonical.size());
0203             if (subdirectory.startsWith(QLatin1Char('/'))) {
0204                 subdirectory.remove(0,1);
0205             }
0206         } else {
0207             subdirectory.clear();
0208         }
0209     }
0210 
0211     IncludePathsConverter converter;
0212 
0213     if (remove) {
0214         if (!converter.removeIncludePaths(includeDirectories, configFile, subdirectory)) {
0215             out << QCoreApplication::translate("main", "Can't remove include paths");
0216         }
0217     }
0218 
0219     if (add) {
0220         if (!converter.addIncludePaths(includeDirectories, configFile, subdirectory)) {
0221             out << QCoreApplication::translate("main", "Can't add include paths");
0222         }
0223     }
0224 
0225     if (show) {
0226         const auto& includes = converter.readIncludePaths(configFile, subdirectory);
0227         for (const auto& include : includes) {
0228             out << include << "\n";
0229         }
0230     }
0231 }