File indexing completed on 2024-04-21 04:35:54

0001 /* This file is part of KDevelop
0002  *
0003  * Copyright (C) 2012-2015 Miquel Sabaté Solà <mikisabate@gmail.com>
0004  *
0005  * This program is free software: you can redistribute it and/or modify
0006  * it under the terms of the GNU General Public License as published by
0007  * the Free Software Foundation, either version 3 of the License, or
0008  * (at your option) any later version.
0009  *
0010  * This program is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013  * GNU General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU General Public License
0016  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017  */
0018 
0019 #include <duchain/loader.h>
0020 
0021 #include <QtCore/QDirIterator>
0022 #include <QtCore/QProcess>
0023 
0024 #include <duchain/editorintegrator.h>
0025 #include <parser/node.h>
0026 
0027 namespace ruby {
0028 
0029 QList<KDevelop::Path> Loader::s_rubyPath;
0030 QList<KDevelop::Path> Loader::s_gemPath;
0031 
0032 KDevelop::Path Loader::getRequiredFile(Node *node,
0033                                        const EditorIntegrator *editor,
0034                                        bool local)
0035 {
0036     QList<KDevelop::Path> searchPaths;
0037 
0038     // Get the name of the file and update the cache of search paths.
0039     auto name = editor->tokenToString(node);
0040     if (name.startsWith(QStringLiteral("'")) ||
0041         name.startsWith(QStringLiteral("\""))) {
0042 
0043         // remove surrounding '
0044         name.replace(name[0], "");
0045     }
0046     if (!name.endsWith(QStringLiteral(".rb"))) {
0047         name += ".rb";
0048     }
0049     searchPaths << KDevelop::Path(editor->url().toUrl()).parent();
0050     if (!local) {
0051         fillUrlCache();
0052         searchPaths << s_rubyPath;
0053     }
0054 
0055     // Check first in the standard search path.
0056     int i = 0;
0057     for (const KDevelop::Path &path : searchPaths) {
0058         KDevelop::Path url(path, name);
0059         QFile script(url.path());
0060         QFileInfo info(url.path());
0061         if (script.exists() && !info.isDir()) {
0062             // Sort the cache to break this loop sooner next time.
0063             if (i > 1) {
0064                 s_rubyPath.prepend(s_rubyPath.at(i - 1));
0065                 s_rubyPath.removeAt(i);
0066             }
0067             return url;
0068         }
0069         i++;
0070     }
0071     if (local) {
0072         return KDevelop::Path();
0073     }
0074 
0075     // This is not a local search and we haven't found it yet, go for the gems.
0076     name.chop(3); // .rb
0077     return getGem(name);
0078 }
0079 
0080 QVector<KDevelop::IncludeItem>
0081 Loader::getFilesInSearchPath(const QString &url,
0082                              const QString &hint,
0083                              const KDevelop::Path &relative)
0084 {
0085     int number = 0;
0086     QList<KDevelop::Path> paths;
0087     QVector<KDevelop::IncludeItem> res;
0088 
0089     if (relative.isValid()) {
0090         paths << relative;
0091     } else {
0092         fillUrlCache();
0093         paths = s_rubyPath;
0094 
0095         /* Gem paths need some extra work :P */
0096         foreach (const KDevelop::Path &path, s_gemPath) {
0097             QDir dir(path.path() + "/");
0098             QStringList list = dir.entryList(QStringList() << hint + "*");
0099             foreach (const QString &inside, list) {
0100                 paths << KDevelop::Path(path, inside + "lib");
0101             }
0102         }
0103     }
0104 
0105     for (const KDevelop::Path &path : paths) {
0106         KDevelop::Path aux(path, url);
0107         QDirIterator it(aux.path());
0108 
0109         while (it.hasNext()) {
0110             it.next();
0111             KDevelop::IncludeItem item;
0112             item.name = it.fileInfo().fileName();
0113             if (item.name.startsWith(QStringLiteral(".")) ||
0114                 item.name.endsWith(QStringLiteral("~")) ||
0115                 item.name.endsWith(QStringLiteral(".so"))) {
0116 
0117                 continue;
0118             }
0119             item.pathNumber = number;
0120             item.isDirectory = it.fileInfo().isDir();
0121             item.basePath = aux.toUrl();
0122             res << item;
0123         }
0124         number++;
0125     }
0126     return res;
0127 }
0128 
0129 KDevelop::Path Loader::getGem(const QString &name)
0130 {
0131     if (name.isEmpty()) {
0132       return KDevelop::Path();
0133     }
0134 
0135     QStringList filter{ QString(name[0]) + "*" };
0136     const QString &real = name + QStringLiteral(".rb");
0137 
0138     for (const KDevelop::Path &path : s_gemPath) {
0139         QDir dir(path.path());
0140         QStringList list = dir.entryList(filter, QDir::Dirs);
0141         int i = 0;
0142         for (const QString &inside : list) {
0143             KDevelop::Path url(path, inside + "/lib/" + real);
0144             QFile script(url.path());
0145             QFileInfo info(url.path());
0146             if (script.exists() && !info.isDir()) {
0147                 // Sort the cache to break this loop sooner next time.
0148                 if (i > 1) {
0149                     s_gemPath.prepend(s_gemPath.at(i - 1));
0150                     s_gemPath.removeAt(i);
0151                 }
0152                 return url;
0153             }
0154             i++;
0155         }
0156     }
0157     return KDevelop::Path();
0158 }
0159 
0160 
0161 void Loader::fillUrlCache()
0162 {
0163     QProcess ruby;
0164     QList<QByteArray> rpaths, epaths;
0165 
0166     if (urlsCached()) {
0167         return;
0168     }
0169     s_rubyPath = {};
0170     s_gemPath = {};
0171 
0172     QStringList code{ "ruby", "-e", "puts $:; STDERR.puts Gem.path" };
0173     ruby.start(QStringLiteral("/usr/bin/env"), code);
0174     ruby.waitForFinished();
0175     rpaths = ruby.readAllStandardOutput().split('\n');
0176     epaths = ruby.readAllStandardError().split('\n');
0177 
0178     /* For both rpaths and epaths, the last item is empty */
0179     for (int it = 0; it < rpaths.size() - 1; it++) {
0180         s_rubyPath << KDevelop::Path(rpaths.at(it));
0181     }
0182     for (int it = 0; it < epaths.size() - 1; it++) {
0183         KDevelop::Path aux(epaths.at(it));
0184         aux.addPath(QStringLiteral("gems"));
0185         s_rubyPath << aux;
0186     }
0187 }
0188 
0189 }