1111#include < QStringList>
1212#include < QFileInfo>
1313#include < QDir>
14+ #include < QJsonObject>
15+ #include < QJsonDocument>
16+ #include < QJsonArray>
17+ #include < QDateTime>
18+ #include < QTimer>
1419
1520#include < dfm-search/dsearch_global.h>
1621#include < dfm-search/searchengine.h>
@@ -74,6 +79,7 @@ void printUsage()
7479 std::cout << " --file-extensions=<exts> Filter by file extensions, comma separated" << std::endl;
7580 std::cout << " --max-results=<number> Maximum number of results" << std::endl;
7681 std::cout << " --max-preview=<length> Max content preview length (for content search)" << std::endl;
82+ std::cout << " --json, -j Output results in JSON format" << std::endl;
7783 std::cout << " --help Display this help" << std::endl;
7884}
7985
@@ -100,6 +106,133 @@ void printSearchResult(const SearchResult &result, SearchType searchType)
100106 std::cout << std::endl;
101107}
102108
109+ // --------------------------------------------------------------------
110+ // JSON Output Helpers
111+ // --------------------------------------------------------------------
112+
113+ QJsonValue resultToJson (const SearchResult &result, SearchType searchType)
114+ {
115+ if (searchType == SearchType::FileName) {
116+ // 文件名搜索:直接返回路径字符串
117+ return result.path ();
118+ } else if (searchType == SearchType::Content) {
119+ // 内容搜索:返回包含路径和内容匹配的对象
120+ QJsonObject obj;
121+ obj[" path" ] = result.path ();
122+ ContentResultAPI contentResult (const_cast <SearchResult &>(result));
123+ obj[" contentMatch" ] = contentResult.highlightedContent ();
124+ return obj;
125+ }
126+ return result.path ();
127+ }
128+
129+ void printJsonLine (const QJsonObject &obj)
130+ {
131+ QJsonDocument doc (obj);
132+ std::cout << doc.toJson (QJsonDocument::Compact).constData () << std::endl;
133+ }
134+
135+ // 流式模式:输出搜索开始
136+ void printJsonSearchStart (const QString &keyword, const QString &searchPath,
137+ SearchType searchType, SearchMethod searchMethod,
138+ const SearchOptions &options)
139+ {
140+ QJsonObject startObj;
141+ startObj[" type" ] = " search_started" ;
142+
143+ QJsonObject searchInfo;
144+ searchInfo[" keyword" ] = keyword;
145+ searchInfo[" searchPath" ] = searchPath;
146+ searchInfo[" searchType" ] = (searchType == SearchType::FileName ? " filename" : " content" );
147+ searchInfo[" searchMethod" ] = (searchMethod == SearchMethod::Indexed ? " indexed" : " realtime" );
148+ searchInfo[" caseSensitive" ] = options.caseSensitive ();
149+ searchInfo[" includeHidden" ] = options.includeHidden ();
150+
151+ startObj[" search" ] = searchInfo;
152+ startObj[" timestamp" ] = QDateTime::currentDateTime ().toString (Qt::ISODate);
153+
154+ printJsonLine (startObj);
155+ }
156+
157+ // 流式模式:输出单个结果
158+ void printJsonResult (const SearchResult &result, SearchType searchType)
159+ {
160+ QJsonObject resultObj;
161+ resultObj[" type" ] = " result" ;
162+ resultObj[" data" ] = resultToJson (result, searchType);
163+ printJsonLine (resultObj);
164+ }
165+
166+ // 流式模式:输出搜索结束
167+ void printJsonSearchEnd (int totalResults, qint64 elapsedMs)
168+ {
169+ QJsonObject endObj;
170+ endObj[" type" ] = " search_finished" ;
171+
172+ QJsonObject status;
173+ status[" state" ] = " success" ;
174+ status[" totalResults" ] = totalResults;
175+
176+ endObj[" status" ] = status;
177+
178+ QJsonObject timestamps;
179+ timestamps[" finished" ] = QDateTime::currentDateTime ().toString (Qt::ISODate);
180+ timestamps[" duration" ] = elapsedMs;
181+
182+ endObj[" timestamps" ] = timestamps;
183+ printJsonLine (endObj);
184+ }
185+
186+ // 完整 JSON 输出(非流式)
187+ struct JsonOutputContext
188+ {
189+ QString keyword;
190+ QString searchPath;
191+ SearchType searchType;
192+ SearchMethod searchMethod;
193+ SearchOptions options;
194+ QDateTime startTime;
195+ QJsonArray results;
196+ };
197+
198+ void printJsonComplete (const JsonOutputContext &ctx)
199+ {
200+ QJsonObject root;
201+
202+ // 搜索信息
203+ QJsonObject searchInfo;
204+ searchInfo[" keyword" ] = ctx.keyword ;
205+ searchInfo[" searchPath" ] = ctx.searchPath ;
206+ searchInfo[" searchType" ] = (ctx.searchType == SearchType::FileName ? " filename" : " content" );
207+ searchInfo[" searchMethod" ] = (ctx.searchMethod == SearchMethod::Indexed ? " indexed" : " realtime" );
208+ searchInfo[" caseSensitive" ] = ctx.options .caseSensitive ();
209+ searchInfo[" includeHidden" ] = ctx.options .includeHidden ();
210+ root[" search" ] = searchInfo;
211+
212+ // 时间戳
213+ QDateTime endTime = QDateTime::currentDateTime ();
214+ qint64 duration = ctx.startTime .msecsTo (endTime);
215+
216+ QJsonObject timestamps;
217+ timestamps[" started" ] = ctx.startTime .toString (Qt::ISODate);
218+ timestamps[" finished" ] = endTime.toString (Qt::ISODate);
219+ timestamps[" duration" ] = duration;
220+ root[" timestamps" ] = timestamps;
221+
222+ // 状态
223+ QJsonObject status;
224+ status[" state" ] = " success" ;
225+ status[" totalResults" ] = ctx.results .size ();
226+ root[" status" ] = status;
227+
228+ // 结果数组
229+ root[" results" ] = ctx.results ;
230+
231+ // 输出完整 JSON
232+ QJsonDocument doc (root);
233+ std::cout << doc.toJson (QJsonDocument::Indented).constData () << std::endl;
234+ }
235+
103236void testGlobal ()
104237{
105238 std::cout << " ================= test global start =================" << std::endl;
@@ -417,6 +550,9 @@ int main(int argc, char *argv[])
417550 QCommandLineOption maxResultsOption (QStringList () << " max-results" , " Maximum number of results" , " number" , " 100" );
418551 QCommandLineOption maxPreviewOption (QStringList () << " max-preview" , " Max content preview length" , " length" , " 200" );
419552 QCommandLineOption wildcardOption (QStringList () << " wildcard" , " Enable wildcard search with * and ? patterns" );
553+ QCommandLineOption jsonOption (QStringList () << " json"
554+ << " j" ,
555+ " Output results in JSON format" );
420556
421557 parser.addOption (typeOption);
422558 parser.addOption (methodOption);
@@ -430,6 +566,7 @@ int main(int argc, char *argv[])
430566 parser.addOption (maxResultsOption);
431567 parser.addOption (maxPreviewOption);
432568 parser.addOption (wildcardOption);
569+ parser.addOption (jsonOption);
433570
434571 // Setup positional arguments
435572 parser.addPositionalArgument (" keyword" , " Search keyword" );
@@ -576,46 +713,143 @@ int main(int argc, char *argv[])
576713 query.setBooleanOperator (SearchQuery::BooleanOperator::AND);
577714 }
578715
716+ // 检测是否使用 JSON 输出模式
717+ bool useJsonOutput = parser.isSet (jsonOption);
718+
719+ // JSON 模式上下文(用于非流式模式)
720+ JsonOutputContext jsonContext;
721+ if (useJsonOutput && searchMethod == SearchMethod::Indexed) {
722+ // 非流式模式:收集所有结果后统一输出
723+ jsonContext.keyword = keyword;
724+ jsonContext.searchPath = searchPath;
725+ jsonContext.searchType = searchType;
726+ jsonContext.searchMethod = searchMethod;
727+ jsonContext.options = options;
728+ jsonContext.startTime = QDateTime::currentDateTime ();
729+ }
730+
579731 // Connect signals
580- QObject::connect (engine, &SearchEngine::searchStarted, [] {
581- std::cout << " Search started..." << std::endl;
582- });
583-
584- QObject::connect (engine, &SearchEngine::resultsFound, [searchType](const SearchResultList &results) {
585- for (const auto &result : results)
586- printSearchResult (result, searchType);
587- });
588-
589- QObject::connect (engine, &SearchEngine::searchFinished, [options, searchType](const QList<SearchResult> &results) {
590- std::cout << " Search finished. Total results: " << results.size () << std::endl;
591- if (!options.resultFoundEnabled ()) {
592- std::for_each (results.begin (), results.end (), [searchType](const SearchResult &result) {
593- printSearchResult (result, searchType);
732+ if (useJsonOutput) {
733+ // JSON 输出模式
734+ if (searchMethod == SearchMethod::Realtime) {
735+ // 流式 JSON 输出(实时搜索)
736+ QObject::connect (engine, &SearchEngine::searchStarted, [keyword, searchPath, searchType, searchMethod, &options]() {
737+ printJsonSearchStart (keyword, searchPath, searchType, searchMethod, options);
738+ });
739+
740+ QObject::connect (engine, &SearchEngine::resultsFound, [searchType](const SearchResultList &results) {
741+ for (const auto &result : results)
742+ printJsonResult (result, searchType);
743+ });
744+
745+ // 记录搜索开始时间
746+ QDateTime searchStartTime = QDateTime::currentDateTime ();
747+
748+ QObject::connect (engine, &SearchEngine::searchFinished, [searchStartTime](const QList<SearchResult> &results) {
749+ qint64 elapsedMs = searchStartTime.msecsTo (QDateTime::currentDateTime ());
750+ printJsonSearchEnd (results.size (), elapsedMs);
751+ QCoreApplication::quit ();
752+ });
753+
754+ QObject::connect (engine, &SearchEngine::searchCancelled, [] {
755+ QJsonObject cancelObj;
756+ cancelObj[" type" ] = " search_cancelled" ;
757+ cancelObj[" timestamp" ] = QDateTime::currentDateTime ().toString (Qt::ISODate);
758+ printJsonLine (cancelObj);
759+ QCoreApplication::quit ();
760+ });
761+
762+ QObject::connect (engine, &SearchEngine::errorOccurred, [](const DFMSEARCH::SearchError &error) {
763+ QJsonObject errorObj;
764+ errorObj[" type" ] = " error" ;
765+ errorObj[" name" ] = error.name ();
766+ errorObj[" message" ] = error.message ();
767+ errorObj[" timestamp" ] = QDateTime::currentDateTime ().toString (Qt::ISODate);
768+ printJsonLine (errorObj);
769+ });
770+
771+ } else {
772+ // 完整 JSON 输出(索引搜索)
773+ QObject::connect (engine, &SearchEngine::searchStarted, []() {
774+ // 非流式模式:开始时不输出
775+ });
776+
777+ QObject::connect (engine, &SearchEngine::resultsFound, [&jsonContext, searchType](const SearchResultList &results) {
778+ for (const auto &result : results) {
779+ jsonContext.results .append (resultToJson (result, searchType));
780+ }
781+ });
782+
783+ QObject::connect (engine, &SearchEngine::searchFinished, [&jsonContext, searchType](const QList<SearchResult> &results) {
784+ // 如果结果没有被 resultsFound 收集(禁用了 resultFoundEnabled),则在这里转换
785+ if (jsonContext.results .isEmpty () && !results.isEmpty ()) {
786+ for (const auto &result : results) {
787+ jsonContext.results .append (resultToJson (result, searchType));
788+ }
789+ }
790+ printJsonComplete (jsonContext);
791+ QCoreApplication::quit ();
792+ });
793+
794+ QObject::connect (engine, &SearchEngine::searchCancelled, [] {
795+ QJsonObject cancelObj;
796+ cancelObj[" type" ] = " search_cancelled" ;
797+ cancelObj[" timestamp" ] = QDateTime::currentDateTime ().toString (Qt::ISODate);
798+ printJsonLine (cancelObj);
799+ QCoreApplication::quit ();
594800 });
801+
802+ QObject::connect (engine, &SearchEngine::errorOccurred, [](const DFMSEARCH::SearchError &error) {
803+ QJsonObject errorObj;
804+ errorObj[" type" ] = " error" ;
805+ errorObj[" name" ] = error.name ();
806+ errorObj[" message" ] = error.message ();
807+ errorObj[" timestamp" ] = QDateTime::currentDateTime ().toString (Qt::ISODate);
808+ printJsonLine (errorObj);
809+ });
810+ }
811+ } else {
812+ // 文本输出模式(原有逻辑)
813+ QObject::connect (engine, &SearchEngine::searchStarted, [] {
814+ std::cout << " Search started..." << std::endl;
815+ });
816+
817+ QObject::connect (engine, &SearchEngine::resultsFound, [searchType](const SearchResultList &results) {
818+ for (const auto &result : results)
819+ printSearchResult (result, searchType);
820+ });
821+
822+ QObject::connect (engine, &SearchEngine::searchFinished, [options, searchType](const QList<SearchResult> &results) {
823+ std::cout << " Search finished. Total results: " << results.size () << std::endl;
824+ if (!options.resultFoundEnabled ()) {
825+ std::for_each (results.begin (), results.end (), [searchType](const SearchResult &result) {
826+ printSearchResult (result, searchType);
827+ });
828+ }
829+ QCoreApplication::quit ();
830+ });
831+
832+ QObject::connect (engine, &SearchEngine::searchCancelled, [] {
833+ std::cout << " Search cancelled" << std::endl;
834+ QCoreApplication::quit ();
835+ });
836+
837+ QObject::connect (engine, &SearchEngine::errorOccurred, [](const DFMSEARCH::SearchError &error) {
838+ std::cerr << " [Error]: " << error.code ()
839+ << " [Name]: " << error.name ().toStdString ()
840+ << " [Message]:" << error.message ().toStdString () << std::endl;
841+ });
842+
843+ // Start search
844+ std::cout << " Searching for: " << keyword.toStdString () << std::endl;
845+ std::cout << " In path: " << searchPath.toStdString () << std::endl;
846+ std::cout << " Search type: " << (searchType == SearchType::FileName ? " Filename" : " Content" ) << std::endl;
847+ std::cout << " Search method: " << (searchMethod == SearchMethod::Indexed ? " Indexed" : " Realtime" ) << std::endl;
848+
849+ // Print file extensions if set
850+ if (searchType == SearchType::FileName && parser.isSet (fileExtensionsOption)) {
851+ std::cout << " File extensions filter: " << parser.value (fileExtensionsOption).toStdString () << std::endl;
595852 }
596- QCoreApplication::quit ();
597- });
598-
599- QObject::connect (engine, &SearchEngine::searchCancelled, [] {
600- std::cout << " Search cancelled" << std::endl;
601- QCoreApplication::quit ();
602- });
603-
604- QObject::connect (engine, &SearchEngine::errorOccurred, [](const DFMSEARCH::SearchError &error) {
605- std::cerr << " [Error]: " << error.code ()
606- << " [Name]: " << error.name ().toStdString ()
607- << " [Message]:" << error.message ().toStdString () << std::endl;
608- });
609-
610- // Start search
611- std::cout << " Searching for: " << keyword.toStdString () << std::endl;
612- std::cout << " In path: " << searchPath.toStdString () << std::endl;
613- std::cout << " Search type: " << (searchType == SearchType::FileName ? " Filename" : " Content" ) << std::endl;
614- std::cout << " Search method: " << (searchMethod == SearchMethod::Indexed ? " Indexed" : " Realtime" ) << std::endl;
615-
616- // Print file extensions if set
617- if (searchType == SearchType::FileName && parser.isSet (fileExtensionsOption)) {
618- std::cout << " File extensions filter: " << parser.value (fileExtensionsOption).toStdString () << std::endl;
619853 }
620854
621855 engine->search (query);
0 commit comments