File indexing completed on 2024-05-19 05:05:50
0001 /*************************************************************************** 0002 * SPDX-License-Identifier: GPL-2.0-or-later 0003 * * 0004 * SPDX-FileCopyrightText: 2022-2023 Thomas Fischer <fischer@unix-ag.uni-kl.de> 0005 * * 0006 * This program is free software; you can redistribute it and/or modify * 0007 * it under the terms of the GNU General Public License as published by * 0008 * the Free Software Foundation; either version 2 of the License, or * 0009 * (at your option) any later version. * 0010 * * 0011 * This program is distributed in the hope that it will be useful, * 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0014 * GNU General Public License for more details. * 0015 * * 0016 * You should have received a copy of the GNU General Public License * 0017 * along with this program; if not, see <https://www.gnu.org/licenses/>. * 0018 ***************************************************************************/ 0019 0020 #include <iostream> 0021 0022 #include <QCoreApplication> 0023 #include <QCommandLineParser> 0024 #include <QSet> 0025 #include <QFile> 0026 #include <QFileInfo> 0027 #include <QTimer> 0028 0029 #include "kbibtex-version.h" 0030 #include <File> 0031 #include <FileImporter> 0032 #include <FileExporter> 0033 #include <FileExporterBibTeX> 0034 #include <IdSuggestions> 0035 0036 int main(int argc, char *argv[]) 0037 { 0038 int exitCode = 0; 0039 QCoreApplication coreApp(argc, argv); 0040 QCoreApplication::setApplicationName(QStringLiteral("kbibtex-cli")); 0041 QCoreApplication::setApplicationVersion(QStringLiteral(KBIBTEX_VERSION_STRING)); 0042 0043 QCommandLineParser cmdLineParser; 0044 cmdLineParser.addHelpOption(); 0045 cmdLineParser.addVersionOption(); 0046 QCommandLineOption outputFileCLO{{QStringLiteral("o"), QStringLiteral("output")}, QStringLiteral("Write output to file, stdout if not specified"), QStringLiteral("outputfilename")}; 0047 cmdLineParser.addOption(outputFileCLO); 0048 QCommandLineOption outputformatCLI{{QStringLiteral("O"), QStringLiteral("output-format")}, QStringLiteral("Provide suggestion for output format"), QStringLiteral("format")}; 0049 cmdLineParser.addOption(outputformatCLI); 0050 QCommandLineOption idSuggestionFormatStringCLO{{QStringLiteral("format-id")}, QStringLiteral("Reformat all entry ids using this format string"), QStringLiteral("formatstring")}; 0051 cmdLineParser.addOption(idSuggestionFormatStringCLO); 0052 cmdLineParser.addPositionalArgument(QStringLiteral("file"), QStringLiteral("Read from this file")); 0053 0054 cmdLineParser.process(coreApp); 0055 0056 QVector<QString> arguments; 0057 arguments.reserve(cmdLineParser.positionalArguments().length()); 0058 for (const QString &pa : cmdLineParser.positionalArguments()) 0059 if (pa.length() > 0) 0060 arguments.append(pa); 0061 0062 if (arguments.length() < 1) { 0063 std::cerr << "No file to read from specified. Use --help for instructions." << std::endl; 0064 coreApp.exit(exitCode = 1); 0065 } else if (arguments.length() > 1) { 0066 std::cerr << "More than one input file specified. Use --help for instructions." << std::endl; 0067 coreApp.exit(exitCode = 1); 0068 } else { 0069 const QFileInfo inputFileInfo{arguments.constFirst()}; 0070 if (!inputFileInfo.exists() || inputFileInfo.size() <= 0 || !inputFileInfo.isFile() || !inputFileInfo.isReadable()) { 0071 std::cerr << "Argument is not an existing, readable, non-empty file: " << inputFileInfo.filePath().toLocal8Bit().constData() << std::endl; 0072 coreApp.exit(exitCode = 1); 0073 } else { 0074 QFile inputFile(inputFileInfo.filePath()); 0075 if (inputFile.open(QFile::ReadOnly)) { 0076 FileImporter *importer = FileImporter::factory(inputFileInfo, &coreApp); 0077 File *file = importer->load(&inputFile); 0078 inputFile.close(); 0079 0080 if (file == nullptr) { 0081 std::cerr << "Failed to load file: " << inputFileInfo.filePath().toLocal8Bit().constData() << std::endl; 0082 coreApp.exit(exitCode = 1); 0083 } else { 0084 if (cmdLineParser.isSet(idSuggestionFormatStringCLO)) { 0085 /// If the user requested applying a certain format string to all entry ids, perform this change here. 0086 QSet<QString> previousNewId; //< Track newly generated entry ids to detect duplicates 0087 const QString formatString{cmdLineParser.value(idSuggestionFormatStringCLO)}; //< extract format string from command line argument 0088 if (formatString.isEmpty()) { 0089 std::cerr << "Got empty format string" << std::endl; 0090 coreApp.exit(exitCode = 1); 0091 } else { 0092 std::cerr << "Using the following format string:" << std::endl; 0093 for (const QString &fse : IdSuggestions::formatStrToHuman(formatString)) 0094 std::cerr << " * " << fse.toLocal8Bit().constData() << std::endl; 0095 for (QSharedPointer<Element> &element : *file) { 0096 /// For every element in the loaded bibliography file ... 0097 /// Check if element is an entry (something that has an 'id') 0098 QSharedPointer<Entry> entry = element.dynamicCast<Entry>(); 0099 if (!entry.isNull()) { 0100 /// Generate new id based on entry and format string 0101 const QString newId{IdSuggestions::formatId(*(entry.data()), formatString)}; 0102 if (newId.isEmpty()) { 0103 std::cerr << "New id generated from entry '" << entry->id().toLocal8Bit().constData() << "' and format string '" << formatString.toLocal8Bit().constData() << "' is empty." << std::endl; 0104 coreApp.exit(exitCode = 1); 0105 break; 0106 } 0107 if (previousNewId.contains(newId)) 0108 std::cerr << "New entry id '" << newId.toLocal8Bit().constData() << "' generated from entry '" << entry->id().toLocal8Bit().constData() << "' and format string '" << formatString.toLocal8Bit().constData() << "' matches a previously generated id, but applying it anyway." << std::endl; 0109 else 0110 previousNewId.insert(newId); 0111 /// Apply newly-generated entry id 0112 entry->setId(newId); 0113 } 0114 } 0115 } 0116 } 0117 0118 if (exitCode == 0) { 0119 if (cmdLineParser.isSet(outputFileCLO)) { 0120 const QFileInfo outputFileInfo{cmdLineParser.value(outputFileCLO)}; 0121 QFile outputfile(outputFileInfo.filePath()); 0122 if (outputfile.open(QFile::WriteOnly)) { 0123 QString exporterClassHint; 0124 if (cmdLineParser.isSet(outputformatCLI)) { 0125 const QString cmpTo = cmdLineParser.value(outputformatCLI).toLower(); 0126 for (const QString &exporterClass : FileExporter::exporterClasses(outputFileInfo)) 0127 if (exporterClass.toLower().contains(cmpTo)) { 0128 exporterClassHint = exporterClass; 0129 std::cerr << "Choosing exporter " << exporterClassHint.toLocal8Bit().constData() << " based on --output-format=" << cmpTo.toLocal8Bit().constData() << std::endl; 0130 break; 0131 } 0132 } 0133 FileExporter *exporter = FileExporter::factory(outputFileInfo, exporterClassHint, &coreApp); 0134 const bool ok = exporter->save(&outputfile, file); 0135 outputfile.close(); 0136 if (!ok) { 0137 std::cerr << "Failed to write to this file: " << outputFileInfo.filePath().toLocal8Bit().constData() << std::endl; 0138 coreApp.exit(exitCode = 1); 0139 } 0140 } else { 0141 std::cerr << "Cannot write to this file: " << outputFileInfo.filePath().toLocal8Bit().constData() << std::endl; 0142 coreApp.exit(exitCode = 1); 0143 } 0144 } else { 0145 /// No output filename specified, so dump BibTeX code to stdout 0146 FileExporter *exporter = new FileExporterBibTeX(&coreApp); 0147 const QString output{exporter->toString(file)}; 0148 std::cout << output.toLocal8Bit().constData() << std::endl; 0149 } 0150 } 0151 0152 delete file; 0153 } 0154 } else { 0155 std::cerr << "Cannot read from file '" << inputFileInfo.filePath().toLocal8Bit().constData() << "'" << std::endl; 0156 coreApp.exit(exitCode = 1); 0157 } 0158 } 0159 } 0160 0161 QTimer::singleShot(100, QCoreApplication::instance(), []() { 0162 QCoreApplication::instance()->quit(); 0163 }); 0164 0165 coreApp.exit(exitCode); 0166 0167 const int r = coreApp.exec(); 0168 return qMax(r, exitCode); 0169 }