1 /*
2     SPDX-FileCopyrightText: 2016 Pino Toscano <pino@kde.org>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "xapiancommon.h"
8 
9 #include <QCoreApplication>
10 #include <QCommandLineParser>
11 #include <QLoggingCategory>
12 #include <QVector>
13 
14 #include <iostream>
15 
16 namespace {
17 
18 Q_LOGGING_CATEGORY( LOG, "org.kde.khelpcenter.xapian.search", QtWarningMsg )
19 
20 }
21 
queryFromWordlist(const QString & words,Xapian::Query::op op)22 Xapian::Query queryFromWordlist( const QString& words, Xapian::Query::op op )
23 {
24   QVector<std::string> wordlist;
25   const QStringList splitlist = words.split( QLatin1Char( '+' ) );
26   wordlist.reserve( splitlist.size() );
27   for ( const QString& word : splitlist ) {
28     wordlist.append( word.toStdString() );
29   }
30 
31   return Xapian::Query( op, wordlist.constBegin(), wordlist.constEnd() );
32 }
33 
main(int argc,char * argv[])34 int main( int argc, char *argv[] )
35 {
36   QCoreApplication app( argc, argv );
37 
38   QCommandLineParser parser;
39   const QCommandLineOption indexdirOption( QStringLiteral("indexdir"), QStringLiteral("Index directory"), QStringLiteral("dir") );
40   parser.addOption( indexdirOption );
41   const QCommandLineOption identifierOption( QStringLiteral("identifier"), QStringLiteral("Index identifier"), QStringLiteral("identifier") );
42   parser.addOption( identifierOption );
43   const QCommandLineOption wordsOption( QStringLiteral("words"), QStringLiteral("Words to search"), QStringLiteral("words") );
44   parser.addOption( wordsOption );
45   const QCommandLineOption methodOption( QStringLiteral("method"), QStringLiteral("Method"), QStringLiteral("and|or") );
46   parser.addOption( methodOption );
47   const QCommandLineOption maxnumOption( QStringLiteral("maxnum"), QStringLiteral("Maximum number of results"), QStringLiteral("maxnum") );
48   parser.addOption( maxnumOption );
49   const QCommandLineOption langOption( QStringLiteral("lang"), QStringLiteral("Language"), QStringLiteral("lang") );
50   parser.addOption( langOption );
51 
52   parser.process( app );
53 
54   const QString indexdir = parser.value( indexdirOption );
55   const QString identifier = parser.value( identifierOption );
56   const QString words = parser.value( wordsOption );
57   const QString method = parser.value( methodOption );
58   const QString maxnumString = parser.value( maxnumOption );
59   if ( indexdir.isEmpty() || identifier.isEmpty() ||  words.isEmpty() || method.isEmpty() ) {
60     qCCritical(LOG) << "Missing arguments.";
61     parser.showHelp( 1 );
62   }
63 
64   Xapian::Query::op op;
65   if ( method == QLatin1String( "and" ) ) {
66     op = Xapian::Query::OP_AND;
67   } else if ( method == QLatin1String( "or" ) ) {
68     op = Xapian::Query::OP_OR;
69   } else {
70     qCCritical(LOG) << "Unrecognized method:" << method;
71     parser.showHelp( 1 );
72   }
73 
74   bool ok;
75   const Xapian::doccount maxnum = maxnumString.toUInt( &ok );
76   if ( !ok ) {
77     qCCritical(LOG) << "--maxnum is not a number";
78     parser.showHelp( 1 );
79   }
80 
81   QString lang = parser.value( langOption );
82   if ( lang.isEmpty() || lang == QLatin1String( "C" ) ) {
83     lang = QStringLiteral("en");
84   }
85 
86   Xapian::Database db;
87 
88   try {
89     db = openDb( indexdir + QLatin1Char('/') + identifier );
90   } catch ( const DatabaseVersionMismatch& e ) {
91     qCWarning(LOG) << "Own version mismatch in Xapian DB: found" << e.version << "vs wanted" << e.refVersion;
92     return 1;
93   } catch ( const Xapian::Error& e ) {
94     qCCritical(LOG) << "Xapian error:" << e.get_description();
95     return 1;
96   }
97 
98   try {
99     Xapian::Query query( Xapian::Query::OP_AND, Xapian::Query( "L" + lang.toStdString() ), queryFromWordlist( words, op ) );
100     qCDebug(LOG) << "query:" << query.get_description();
101 
102     Xapian::Enquire enquire( db );
103     enquire.set_query( query );
104 
105     const Xapian::MSet mset = enquire.get_mset( 0, maxnum );
106 
107     std::cout << "<ul>" << std::endl;
108 
109     qCDebug(LOG) << "got" << mset.size() << "results";
110     for ( Xapian::MSetIterator i = mset.begin(); i != mset.end(); ++i ) {
111       const Xapian::Document doc = i.get_document();
112 
113       std::string uid;
114       std::string html;
115       getDocInfo( doc, nullptr, &uid, &html );
116       const std::string title = doc.get_value( VALUE_TITLE );
117 
118       const std::string::size_type slash = uid.find_last_of( '/' );
119       const std::string html_id = slash != std::string::npos
120                                 ? uid.substr( 0, slash + 1 ) + html
121                                 : html;
122       const std::string partial_id = html_id.substr(0, html_id.rfind("/"));
123 
124       std::cout << "<li><a href=\"help:/" << html_id << "\">" << partial_id
125                 << " - " << title << "</a></li>" << std::endl;
126     }
127 
128     std::cout << "</ul>" << std::endl;
129 
130   } catch ( const Xapian::Error& e ) {
131     qCCritical(LOG) << "Xapian error: " << e.get_description();
132     return 1;
133   }
134 
135   return 0;
136 }
137