File indexing completed on 2024-06-16 04:54:23

0001 /*
0002     SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include <config-kitinerary.h>
0008 #include <kitinerary_version.h>
0009 
0010 #include <KItinerary/CalendarHandler>
0011 #include <KItinerary/ExtractorCapabilities>
0012 #include <KItinerary/ExtractorEngine>
0013 #include <KItinerary/ExtractorPostprocessor>
0014 #include <KItinerary/ExtractorRepository>
0015 #include <KItinerary/ExtractorValidator>
0016 #include <KItinerary/JsonLdDocument>
0017 #include <KItinerary/MergeUtil>
0018 #include <KItinerary/Reservation>
0019 #include <KItinerary/ScriptExtractor>
0020 
0021 #include <KCalendarCore/Event>
0022 #include <KCalendarCore/ICalFormat>
0023 #include <KCalendarCore/MemoryCalendar>
0024 
0025 #include <QCommandLineParser>
0026 #include <QCoreApplication>
0027 #include <QDateTime>
0028 #include <QDebug>
0029 #include <QDir>
0030 #include <QFile>
0031 #include <QJsonArray>
0032 #include <QJsonDocument>
0033 #include <QJsonObject>
0034 #include <QObject>
0035 
0036 #include <iostream>
0037 
0038 using namespace KItinerary;
0039 
0040 static QList<QList<QVariant>>
0041 batchReservations(const QList<QVariant> &reservations) {
0042   using namespace KItinerary;
0043 
0044   QList<QList<QVariant>> batches;
0045   QList<QVariant> batch;
0046 
0047   for (const auto &res : reservations) {
0048     if (batch.isEmpty()) {
0049       batch.push_back(res);
0050       continue;
0051     }
0052 
0053     if (JsonLd::canConvert<Reservation>(res) &&
0054         JsonLd::canConvert<Reservation>(batch.at(0))) {
0055       const auto trip1 = JsonLd::convert<Reservation>(res).reservationFor();
0056       const auto trip2 =
0057           JsonLd::convert<Reservation>(batch.at(0)).reservationFor();
0058       if (KItinerary::MergeUtil::isSame(trip1, trip2)) {
0059         batch.push_back(res);
0060         continue;
0061       }
0062     }
0063 
0064     batches.push_back(batch);
0065     batch.clear();
0066     batch.push_back(res);
0067   }
0068 
0069   if (!batch.isEmpty()) {
0070     batches.push_back(batch);
0071   }
0072   return batches;
0073 }
0074 
0075 static void printCapabilities()
0076 {
0077     std::cout << qPrintable(ExtractorCapabilities::capabilitiesString());
0078 }
0079 
0080 static void printExtractors()
0081 {
0082     ExtractorRepository repo;
0083     for (const auto &ext : repo.extractors()) {
0084         std::cout << qPrintable(ext->name());
0085         if (auto scriptExt = dynamic_cast<const ScriptExtractor*>(ext.get())) {
0086             std::cout << " (" << qPrintable(scriptExt->mimeType()) << ", "
0087                       << qPrintable(scriptExt->scriptFileName()) << ":"
0088                       << qPrintable(scriptExt->scriptFunction()) << ")";
0089         }
0090         std::cout << std::endl;
0091     }
0092 }
0093 
0094 int main(int argc, char** argv)
0095 {
0096     QCoreApplication::setApplicationName(QStringLiteral("kitinerary-extractor"));
0097     QCoreApplication::setApplicationVersion(QStringLiteral(KITINERARY_VERSION_STRING));
0098     QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org"));
0099     QCoreApplication::setOrganizationName(QStringLiteral("KDE"));
0100     QCoreApplication app(argc, argv);
0101 
0102 #ifdef KITINERARY_STANDALONE_CLI_EXTRACTOR
0103     // set additional data file search path relative to the current binary location
0104     // NOTE: QCoreApplication::applicationDirPath is only valid once QCoreApplication has been created
0105     auto xdgDataDirs = qgetenv("XDG_DATA_DIRS");
0106     if (!xdgDataDirs.isEmpty()) {
0107         xdgDataDirs += QDir::listSeparator().toLatin1();
0108     }
0109     xdgDataDirs += QString(QCoreApplication::applicationDirPath() +
0110                            QDir::separator() + QLatin1StringView("..") +
0111                            QDir::separator() + QLatin1String("share"))
0112                        .toUtf8();
0113     qputenv("XDG_DATA_DIRS", xdgDataDirs);
0114 #endif
0115 
0116     QCommandLineParser parser;
0117     parser.setApplicationDescription(QStringLiteral("Command line itinerary extractor."));
0118     parser.addHelpOption();
0119     parser.addVersionOption();
0120     QCommandLineOption capOpt({QStringLiteral("capabilities")}, QStringLiteral("Show available extraction capabilities."));
0121     parser.addOption(capOpt);
0122     QCommandLineOption listExtOpt({QStringLiteral("list-extractors")}, QStringLiteral("List all available extractors."));
0123     parser.addOption(listExtOpt);
0124 
0125     QCommandLineOption ctxOpt({QStringLiteral("c"), QStringLiteral("context-date")}, QStringLiteral("ISO date/time for when this data has been received."), QStringLiteral("date"));
0126     parser.addOption(ctxOpt);
0127     QCommandLineOption typeOpt({QStringLiteral("t"), QStringLiteral("type")}, QStringLiteral("Deprecated, no longer needed and ignored."), QStringLiteral("type"));
0128     parser.addOption(typeOpt);
0129     QCommandLineOption extOpt({QStringLiteral("e"), QStringLiteral("extractors")}, QStringLiteral("Additional extractors to apply."), QStringLiteral("extractors"));
0130     parser.addOption(extOpt);
0131     QCommandLineOption pathsOpt({QStringLiteral("additional-search-path")}, QStringLiteral("Additional search path for extractors."), QStringLiteral("search-path"));
0132     parser.addOption(pathsOpt);
0133     QCommandLineOption formatOpt({QStringLiteral("o"), QStringLiteral("output")}, QStringLiteral("Output format [JsonLd, iCal]. Default: JsonLd"), QStringLiteral("format"));
0134     parser.addOption(formatOpt);
0135     QCommandLineOption noValidationOpt({QStringLiteral("no-validation")}, QStringLiteral("Disable result validation."));
0136     parser.addOption(noValidationOpt);
0137 
0138     parser.addPositionalArgument(QStringLiteral("input"), QStringLiteral("File to extract data from, omit for using stdin."));
0139     parser.process(app);
0140 
0141     ExtractorRepository repo;
0142     if (parser.isSet(pathsOpt)) {
0143         repo.setAdditionalSearchPaths(parser.values(pathsOpt));
0144         repo.reload();
0145     }
0146 
0147     if (parser.isSet(capOpt)) {
0148         printCapabilities();
0149         return 0;
0150     }
0151     if (parser.isSet(listExtOpt)) {
0152         printExtractors();
0153         return 0;
0154     }
0155 
0156     ExtractorEngine engine;
0157     engine.setUseSeparateProcess(false); // we are the external extractor
0158     ExtractorPostprocessor postproc;
0159 
0160     auto contextDt = QDateTime::fromString(parser.value(ctxOpt), Qt::ISODate);
0161     if (!contextDt.isValid()) {
0162         contextDt = QDateTime::currentDateTime();
0163     }
0164     postproc.setContextDate(contextDt);
0165 
0166     const auto files = parser.positionalArguments().isEmpty() ? QStringList(QString()) : parser.positionalArguments();
0167     for (const auto &arg : files) {
0168         QFile f;
0169         if (!arg.isEmpty()) {
0170             f.setFileName(arg);
0171             if (!f.open(QFile::ReadOnly)) {
0172                 std::cerr << qPrintable(f.errorString()) << std::endl;
0173                 return 1;
0174             }
0175         } else {
0176             f.open(stdin, QFile::ReadOnly);
0177         }
0178 
0179         auto fileName = f.fileName();
0180 
0181         engine.clear();
0182         engine.setContextDate(contextDt);
0183 
0184         if (!parser.value(extOpt).isEmpty()) {
0185             const auto extNames = parser.value(extOpt).split(QLatin1Char(';'),
0186                                                              Qt::SkipEmptyParts);
0187             std::vector<const AbstractExtractor*> exts;
0188             exts.reserve(extNames.size());
0189             for (const auto &name : extNames) {
0190                 const auto ext = repo.extractorByName(name);
0191                 exts.push_back(ext);
0192             }
0193             engine.setAdditionalExtractors(std::move(exts));
0194         }
0195 
0196         engine.setData(f.readAll(), fileName);
0197         const auto result = JsonLdDocument::fromJson(engine.extract());
0198         postproc.process(result);
0199     }
0200 
0201     auto result = postproc.result();
0202     if (!parser.isSet(noValidationOpt)) {
0203         ExtractorValidator validator;
0204         result.erase(std::remove_if(result.begin(), result.end(), [&validator](const auto &elem) {
0205             return !validator.isValidElement(elem);
0206         }), result.end());
0207     }
0208 
0209     if (parser.value(formatOpt).compare(QLatin1StringView("ical"),
0210                                         Qt::CaseInsensitive) == 0) {
0211       const auto batches = batchReservations(result);
0212       KCalendarCore::Calendar::Ptr cal(
0213           new KCalendarCore::MemoryCalendar(QTimeZone::systemTimeZone()));
0214       for (const auto &batch : batches) {
0215         KCalendarCore::Event::Ptr event(new KCalendarCore::Event);
0216         CalendarHandler::fillEvent(batch, event);
0217         cal->addEvent(event);
0218       }
0219       KCalendarCore::ICalFormat format;
0220       std::cout << qPrintable(format.toString(cal));
0221     } else {
0222       const auto postProcResult = JsonLdDocument::toJson(result);
0223       std::cout << QJsonDocument(postProcResult).toJson().constData()
0224                 << std::endl;
0225     }
0226 }