File indexing completed on 2024-05-05 08:44:54
0001 /* 0002 SPDX-FileCopyrightText: 2013 Sven Brauch <svenbrauch@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "docfilewizard.h" 0008 #include "docfilemanagerwidget.h" 0009 0010 #include <QGroupBox> 0011 #include <QFormLayout> 0012 #include <QLabel> 0013 #include <QLineEdit> 0014 #include <QBoxLayout> 0015 #include <QPushButton> 0016 #include <QTabWidget> 0017 #include <QScrollBar> 0018 #include <QDebug> 0019 #include <QDir> 0020 #include <QStandardPaths> 0021 0022 #include <KLocalizedString> 0023 #include <KMessageBox> 0024 #include <KProcess> 0025 #include <interfaces/icore.h> 0026 #include <interfaces/iproject.h> 0027 #include <interfaces/iprojectcontroller.h> 0028 #include <project/projectmodel.h> 0029 #include <util/path.h> 0030 0031 DocfileWizard::DocfileWizard(const QString& workingDirectory, QWidget* parent) 0032 : QDialog(parent) 0033 , worker(nullptr) 0034 , workingDirectory(workingDirectory) 0035 { 0036 setLayout(new QVBoxLayout); 0037 0038 // The interpreter group box 0039 QGroupBox* interpreter = new QGroupBox; 0040 interpreter->setTitle(i18n("Configure the Python interpreter to use")); 0041 QFormLayout* interpreterLayout = new QFormLayout; 0042 interpreterField = new QLineEdit("python"); 0043 interpreterLayout->addRow(new QLabel(i18n("Python executable")), interpreterField); 0044 interpreter->setLayout(interpreterLayout); 0045 0046 // The module + output file group box 0047 QGroupBox* module = new QGroupBox; 0048 module->setTitle(i18n("Select a python module to generate documentation for")); 0049 QFormLayout* moduleLayout = new QFormLayout; 0050 moduleField = new QLineEdit; 0051 moduleLayout->addRow(new QLabel(i18nc("refers to selecting a python module to perform some operation on", 0052 "Target module (e.g. \"math\")")), moduleField); 0053 outputFilenameField = new QLineEdit; 0054 moduleLayout->addRow(new QLabel(i18n("Output filename")), outputFilenameField); 0055 module->setLayout(moduleLayout); 0056 0057 // Status group box 0058 QGroupBox* status = new QGroupBox; 0059 QTabWidget* tabs = new QTabWidget; 0060 status->setTitle(i18n("Status and output")); 0061 statusField = new QTextEdit(); 0062 statusField->setText(i18n("The process has not been run yet.")); 0063 statusField->setFontFamily("monospace"); 0064 statusField->setLineWrapMode(QTextEdit::NoWrap); 0065 statusField->setReadOnly(true); 0066 statusField->setAcceptRichText(false); 0067 resultField = new QTextEdit(); 0068 resultField->setText(i18n("The process has not been run yet.")); 0069 resultField->setFontFamily("monospace"); 0070 resultField->setLineWrapMode(QTextEdit::NoWrap); 0071 resultField->setReadOnly(true); 0072 resultField->setAcceptRichText(false); 0073 status->setLayout(new QHBoxLayout); 0074 tabs->addTab(statusField, i18n("Script output")); 0075 tabs->addTab(resultField, i18n("Results")); 0076 status->layout()->addWidget(tabs); 0077 0078 // The run / close buttons 0079 QHBoxLayout* buttonsLayout = new QHBoxLayout; 0080 buttonsLayout->setDirection(QBoxLayout::RightToLeft); 0081 QPushButton* closeButton = new QPushButton(i18n("Close")); 0082 closeButton->setIcon(QIcon::fromTheme("dialog-close")); 0083 saveButton = new QPushButton(i18n("Save and close")); 0084 saveButton->setEnabled(false); 0085 saveButton->setIcon(QIcon::fromTheme("dialog-ok-apply")); 0086 runButton = new QPushButton(i18n("Generate")); 0087 runButton->setDefault(true); 0088 runButton->setIcon(QIcon::fromTheme("tools-wizard")); 0089 buttonsLayout->addWidget(closeButton); 0090 buttonsLayout->addWidget(runButton); 0091 buttonsLayout->addWidget(saveButton); 0092 buttonsLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding)); 0093 0094 // connections 0095 QObject::connect(closeButton, &QAbstractButton::clicked, this, &QWidget::close); 0096 QObject::connect(saveButton, &QAbstractButton::clicked, this, &DocfileWizard::saveAndClose); 0097 QObject::connect(moduleField, &QLineEdit::textChanged, this, &DocfileWizard::updateOutputFilename); 0098 QObject::connect(runButton, &QAbstractButton::clicked, this, &DocfileWizard::run); 0099 0100 // putting it all together 0101 layout()->addWidget(interpreter); 0102 layout()->addWidget(module); 0103 layout()->addWidget(status); 0104 layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding)); 0105 qobject_cast<QVBoxLayout*>(layout())->addLayout(buttonsLayout); // TODO ugh 0106 0107 resize(640, 480); 0108 } 0109 0110 const QString DocfileWizard::wasSavedAs() const 0111 { 0112 return savedAs; 0113 } 0114 0115 QString DocfileWizard::fileNameForModule(QString moduleName) const 0116 { 0117 if ( moduleName.isEmpty() ) { 0118 return moduleName; 0119 } 0120 return moduleName.replace('.', '/') + ".py"; 0121 } 0122 0123 void DocfileWizard::setModuleName(const QString& moduleName) 0124 { 0125 moduleField->setText(moduleName); 0126 } 0127 0128 bool DocfileWizard::run() 0129 { 0130 // validate input data, setup and program state 0131 if ( worker ) { 0132 // process already running 0133 return false; 0134 } 0135 QString scriptUrl = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevpythonsupport/scripts/introspect.py"); 0136 if ( scriptUrl.isEmpty() ) { 0137 KMessageBox::error(this, i18n("Couldn't find the introspect.py script; check your installation!")); 0138 return false; 0139 } 0140 if ( workingDirectory.isEmpty() ) { 0141 KMessageBox::error(this, i18n("Couldn't find a valid kdev-python data directory; check your installation!")); 0142 return false; 0143 } 0144 QString outputFilename = outputFilenameField->text(); 0145 if ( outputFilename.contains("..") ) { 0146 // protect the user from writing outside the data directory accidentally 0147 KMessageBox::error(this, i18n("Invalid output filename")); 0148 return false; 0149 } 0150 0151 runButton->setEnabled(false); 0152 0153 // clean output from previous script runs; since the fields are set to readonly, 0154 // no user data will be lost 0155 statusField->clear(); 0156 resultField->clear(); 0157 0158 // set up the process and connect relevant slots 0159 QString interpreter = interpreterField->text(); 0160 QString module = moduleField->text(); 0161 worker = new QProcess(this); 0162 QObject::connect(worker, &QProcess::readyReadStandardError, this, &DocfileWizard::processScriptOutput); 0163 QObject::connect(worker, &QProcess::readyReadStandardOutput, this, &DocfileWizard::processScriptOutput); 0164 QObject::connect(worker, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &DocfileWizard::processFinished); 0165 0166 // can never have too many slashes 0167 outputFile.setFileName(workingDirectory + "/" + outputFilename); 0168 0169 QList<KDevelop::IProject*> projs = KDevelop::ICore::self()->projectController()->projects(); 0170 QStringList args; 0171 args << scriptUrl; 0172 foreach(const KDevelop::IProject* proj, projs) 0173 { 0174 if ( proj ) 0175 args << proj->path().toLocalFile(); 0176 } 0177 args << module; 0178 worker->start(interpreter, args); 0179 return true; 0180 } 0181 0182 void DocfileWizard::saveAndClose() 0183 { 0184 bool mayWrite = true; 0185 if ( outputFile.exists() ) { 0186 mayWrite = KMessageBox::questionYesNo(this, i18n("The output file <br/>%1<br/> already exists, " 0187 "do you want to overwrite it?", 0188 outputFile.fileName())) == KMessageBox::Yes; 0189 } 0190 if ( mayWrite ) { 0191 auto url = QUrl::fromLocalFile(outputFile.fileName()); 0192 Q_ASSERT(url.isLocalFile()); 0193 auto basePath = url.url(QUrl::RemoveFilename | QUrl::PreferLocalFile); 0194 0195 // should have been done previously 0196 Q_ASSERT(QDir(basePath).exists()); 0197 if ( ! QDir(basePath).exists() ) { 0198 QDir(basePath).mkpath(basePath); 0199 } 0200 outputFile.open(QIODevice::WriteOnly); 0201 QString header = "\"\"\"" + i18n("This file contains auto-generated documentation extracted\n" 0202 "from python run-time information. It is analyzed by KDevelop\n" 0203 "to offer features such as code-completion and syntax highlighting.\n" 0204 "If you discover errors in KDevelop's support for this module,\n" 0205 "you can edit this file to correct the errors, e.g. you can add\n" 0206 "additional return statements to functions to control the return\n" 0207 "type to be used for that function by the analyzer.\n" 0208 "Make sure to keep a copy of your changes so you don't accidentally\n" 0209 "overwrite them by re-generating the file.\n") + "\"\"\"\n\n"; 0210 outputFile.write(header.toUtf8() + resultField->toPlainText().toUtf8()); 0211 outputFile.close(); 0212 savedAs = outputFile.fileName(); 0213 close(); 0214 } 0215 } 0216 0217 void DocfileWizard::processScriptOutput() 0218 { 0219 statusField->insertPlainText(worker->readAllStandardError()); 0220 resultField->insertPlainText(worker->readAllStandardOutput()); 0221 QScrollBar* scrollbar = statusField->verticalScrollBar(); 0222 scrollbar->setValue(scrollbar->maximum()); 0223 } 0224 0225 void DocfileWizard::processFinished(int, QProcess::ExitStatus) 0226 { 0227 worker = nullptr; 0228 runButton->setEnabled(true); 0229 saveButton->setEnabled(true); 0230 } 0231 0232 void DocfileWizard::updateOutputFilename(const QString& newModuleName) 0233 { 0234 QString newFileName = fileNameForModule(newModuleName); 0235 if ( fileNameForModule(previousModuleName) == outputFilenameField->text() ) { 0236 // the user didn't edit the field, or edited it to what it is anyways, so update the text 0237 // otherwise, do nothing. 0238 outputFilenameField->setText(newFileName); 0239 } 0240 previousModuleName = newModuleName; 0241 } 0242 0243 #include "moc_docfilewizard.cpp"