summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAldrik Ramaekers <aldrikboy@gmail.com>2024-03-11 21:47:49 +0100
committerAldrik Ramaekers <aldrikboy@gmail.com>2024-03-11 21:47:49 +0100
commit8808c195069e4dcf44218ebf7ca481a4c695fb12 (patch)
treef0efe5d02af797cc44e516e8b5c2c08346fdeb58
parent08c3990eb2db8c0228160d760cf287f0b6b96915 (diff)
export results to json,csv,xml format
-rw-r--r--src/export.cpp207
-rw-r--r--src/export.h6
-rw-r--r--src/main.cpp17
-rw-r--r--src/search.cpp5
-rw-r--r--src/search.h1
5 files changed, 232 insertions, 4 deletions
diff --git a/src/export.cpp b/src/export.cpp
new file mode 100644
index 0000000..23e7bf6
--- /dev/null
+++ b/src/export.cpp
@@ -0,0 +1,207 @@
+#include "export.h"
+#include "array.h"
+#include "config.h"
+#include <stdio.h>
+
+static bool _str_has_extension(const utf8_int8_t *str, const utf8_int8_t *suffix)
+{
+ if (!str || !suffix)
+ return 0;
+ size_t lenstr = strlen(str);
+ size_t lensuffix = strlen(suffix);
+ if (lensuffix > lenstr)
+ return 0;
+ return strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0;
+}
+
+static utf8_int8_t* _json_escape_str(utf8_int8_t* str, utf8_int8_t* buffer, size_t buffer_size) {
+ memset(buffer, 0, buffer_size);
+
+ utf8_int8_t* buffer_orig = buffer;
+ utf8_int32_t ch;
+ while ((str = utf8codepoint(str, &ch)) && ch)
+ {
+ if (ch == '\\') {
+ utf8cat(buffer, "\\\\");
+ buffer += 2;
+ }
+ else if (ch == '\"') {
+ utf8cat(buffer, "\\\"");
+ buffer += 2;
+ }
+ else {
+ buffer = utf8catcodepoint(buffer, ch, buffer_size - (buffer - buffer_orig) - 1);
+ }
+ }
+ return buffer_orig;
+}
+
+static bool _ts_export_json(ts_search_result* result, const utf8_int8_t* path) {
+ FILE *write_file;
+ fopen_s(&write_file, path, "w");
+ if (write_file == NULL) return false;
+
+ const size_t escape_size = MAX_INPUT_LENGTH*2; // ballpark.
+ utf8_int8_t escape_buffer[escape_size];
+
+ fprintf(write_file, "{\n");
+
+ fprintf(write_file, "\"version\": 1,\n");
+ fprintf(write_file, "\"path\": \"%s\",\n", _json_escape_str(result->directory_to_search, escape_buffer, escape_size));
+ fprintf(write_file, "\"filter\": \"%s\",\n", _json_escape_str(result->file_filter, escape_buffer, escape_size));
+ fprintf(write_file, "\"query\": \"%s\",\n", _json_escape_str(result->search_text, escape_buffer, escape_size));
+ fprintf(write_file, "\"casesensitive\": %d,\n", result->respect_capitalization);
+ fprintf(write_file, "\"match_count\": %d,\n", result->match_count);
+ fprintf(write_file, "\"file_count\": %d,\n", result->file_count);
+ fprintf(write_file, "\"timestamp\": %llu,\n", result->timestamp);
+
+ fprintf(write_file, "\"files\": [\n");
+
+ for (int i = 0; i < result->files.length; i++) {
+ ts_found_file* file = *(ts_found_file **)ts_array_at(&result->files, i);
+
+ fprintf(write_file, "{\n");
+ fprintf(write_file, "\"path\": \"%s\",\n", _json_escape_str(file->path, escape_buffer, escape_size));
+
+ fprintf(write_file, "\"matches\": [\n");
+
+ bool first_match_of_file = true;
+ for (int i = 0; i < result->matches.length; i++) {
+ ts_file_match* match = (ts_file_match*)ts_array_at(&result->matches, i);
+ if (match->file != file) continue;
+
+ if (!first_match_of_file) fprintf(write_file, ",\n");
+ first_match_of_file = false;
+ fprintf(write_file, "{\n");
+ fprintf(write_file, "\"line_nr\": %d,\n", match->line_nr);
+ fprintf(write_file, "\"match_length\": %zu,\n", match->word_match_length);
+ fprintf(write_file, "\"match_offset\": %zu,\n", match->word_match_offset);
+ fprintf(write_file, "\"line\": \"%s\"\n", _json_escape_str(match->line_info, escape_buffer, escape_size));
+ fprintf(write_file, "}\n");
+ }
+
+ fprintf(write_file, "]\n");
+ if (i == result->files.length-1) fprintf(write_file, "}\n"); else fprintf(write_file, "},\n");
+ }
+
+ fprintf(write_file, "]\n");
+
+ fprintf(write_file, "}\n");
+
+ fclose(write_file);
+ return true;
+}
+
+static bool _ts_export_csv(ts_search_result* result, const utf8_int8_t* path) {
+ FILE *write_file;
+ fopen_s(&write_file, path, "w");
+ if (write_file == NULL) return false;
+
+ fprintf(write_file, "VERSION,1\n");
+ fprintf(write_file, "PATH,%s\n", result->directory_to_search);
+ fprintf(write_file, "FILTER,%s\n", result->file_filter);
+ fprintf(write_file, "QUERY,%s\n", result->search_text);
+ fprintf(write_file, "CASESENSITIVE,%d\n", result->respect_capitalization);
+ fprintf(write_file, "MATCH_COUNT,%d\n", result->match_count);
+ fprintf(write_file, "FILE_COUNT,%d\n", result->file_count);
+ fprintf(write_file, "TIMESTAMP,%llu\n", result->timestamp);
+
+ for (int i = 0; i < result->files.length; i++) {
+ ts_found_file* file = *(ts_found_file **)ts_array_at(&result->files, i);
+ fprintf(write_file, "FILE,%s\n", file->path);
+
+ for (int i = 0; i < result->matches.length; i++) {
+ ts_file_match* match = (ts_file_match*)ts_array_at(&result->matches, i);
+ if (match->file != file) continue;
+ fprintf(write_file, "MATCH,%d,%zu,%zu,%s\n", match->line_nr, match->word_match_length, match->word_match_offset, match->line_info);
+ }
+ }
+
+ fclose(write_file);
+ return true;
+}
+
+static utf8_int8_t* _xml_escape_str(utf8_int8_t* str, utf8_int8_t* buffer, size_t buffer_size) {
+ memset(buffer, 0, buffer_size);
+
+ utf8_int8_t* buffer_orig = buffer;
+ utf8_int32_t ch;
+ while ((str = utf8codepoint(str, &ch)) && ch)
+ {
+ switch(ch) {
+ case '<': utf8cat(buffer, "&lt;"); buffer += 4; break;
+ case '>': utf8cat(buffer, "&gt;"); buffer += 4; break;
+ case '&': utf8cat(buffer, "&#38;"); buffer += 5; break;
+ case '\'': utf8cat(buffer, "&#39;"); buffer += 5; break;
+ case '"': utf8cat(buffer, "&#34;"); buffer += 5; break;
+ default: buffer = utf8catcodepoint(buffer, ch, buffer_size - (buffer - buffer_orig) - 1);
+ }
+ }
+ return buffer_orig;
+}
+
+static bool _ts_export_xml(ts_search_result* result, const utf8_int8_t* path) {
+ FILE *write_file;
+ fopen_s(&write_file, path, "w");
+ if (write_file == NULL) return false;
+
+ const size_t escape_size = MAX_INPUT_LENGTH*2; // ballpark.
+ utf8_int8_t escape_buffer[escape_size];
+
+ fprintf(write_file, "<SEARCHRESULT>\n");
+
+ fprintf(write_file, "<VERSION>1</VERSION>\n");
+ fprintf(write_file, "<PATH>%s</PATH>\n", _xml_escape_str(result->directory_to_search, escape_buffer, escape_size));
+ fprintf(write_file, "<FILTER>%s</FILTER>\n", _xml_escape_str(result->file_filter, escape_buffer, escape_size));
+ fprintf(write_file, "<QUERY>%s</QUERY>\n", _xml_escape_str(result->search_text, escape_buffer, escape_size));
+ fprintf(write_file, "<CASESENSITIVE>%d</CASESENSITIVE>\n", result->respect_capitalization);
+ fprintf(write_file, "<MATCH_COUNT>%d</MATCH_COUNT>\n", result->match_count);
+ fprintf(write_file, "<FILE_COUNT>%d</FILE_COUNT>\n", result->file_count);
+ fprintf(write_file, "<TIMESTAMP>%llu</TIMESTAMP>\n", result->timestamp);
+
+ for (int i = 0; i < result->files.length; i++) {
+ ts_found_file* file = *(ts_found_file **)ts_array_at(&result->files, i);
+
+ fprintf(write_file, "<FILE>\n");
+ fprintf(write_file, "<PATH>%s</PATH>\n", _xml_escape_str(file->path, escape_buffer, escape_size));
+
+ for (int i = 0; i < result->matches.length; i++) {
+ ts_file_match* match = (ts_file_match*)ts_array_at(&result->matches, i);
+ if (match->file != file) continue;
+
+ fprintf(write_file, "<MATCH>\n");
+ fprintf(write_file, "<LINENR>%d</LINENR>\n", match->line_nr);
+ fprintf(write_file, "<MATCH_LENGTH>%zu</MATCH_LENGTH>\n", match->word_match_length);
+ fprintf(write_file, "<MATCH_OFFSET>%zu</MATCH_OFFSET>\n", match->word_match_offset);
+ fprintf(write_file, "<LINE>%s</LINE>\n", _xml_escape_str(match->line_info, escape_buffer, escape_size));
+ fprintf(write_file, "</MATCH>\n");
+ }
+
+ fprintf(write_file, "</FILE>\n");
+ }
+
+ fprintf(write_file, "</SEARCHRESULT>\n");
+
+ fclose(write_file);
+ return true;
+}
+
+bool ts_export_result(ts_search_result* result, const utf8_int8_t* path) {
+ if (result == NULL || path == NULL) return false;
+ if (!result->search_completed) return false;
+ result->is_saving = true;
+
+ if (_str_has_extension(path, ".json")) {
+ return _ts_export_json(result, path);
+ }
+ if (_str_has_extension(path, ".csv")) {
+ return _ts_export_csv(result, path);
+ }
+ if (_str_has_extension(path, ".xml")) {
+ return _ts_export_xml(result, path);
+ }
+
+ result->is_saving = false;
+
+ return false;
+} \ No newline at end of file
diff --git a/src/export.h b/src/export.h
new file mode 100644
index 0000000..1e7f824
--- /dev/null
+++ b/src/export.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include "search.h"
+#include "platform.h"
+
+bool ts_export_result(ts_search_result* result, const utf8_int8_t* path); \ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index 55487f2..bcbc4df 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -9,6 +9,7 @@
#include "platform.h"
#include "image.h"
#include "config.h"
+#include "export.h"
#include <stdio.h>
@@ -131,7 +132,7 @@ static void _ts_create_popups() {
}
}
-static int _ts_create_menu() {
+static int _ts_create_menu(int window_w, int window_h) {
int menu_bar_h = 0;
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGui::Spectrum::Color(0xDDDDDD));
ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 1.0f);
@@ -140,7 +141,9 @@ static int _ts_create_menu() {
if (ImGui::BeginMenu("File"))
{
//ImGui::MenuItem("Open", "CTRL+O");
- //ImGui::MenuItem("Save", "CTRL+S");
+ if (ImGui::MenuItem("Save As")) {
+ ifd::FileDialog::Instance().Save("FileSaveAsDialog", "Save results to file", "File (*.csv;*.json;*.xml){.csv,.json,.xml}");
+ }
ImGui::Separator();
if (ImGui::MenuItem("Exit")) {
program_running = false;
@@ -167,6 +170,14 @@ static int _ts_create_menu() {
_ts_create_popups();
+ if (ifd::FileDialog::Instance().IsDone("FileSaveAsDialog", window_w, window_h)) {
+ if (ifd::FileDialog::Instance().HasResult()) {
+ std::string res = ifd::FileDialog::Instance().GetResult().u8string();
+ ts_export_result(current_search_result, (const utf8_int8_t *)res.c_str());
+ }
+ ifd::FileDialog::Instance().Close();
+ }
+
return menu_bar_h;
}
@@ -356,7 +367,7 @@ void ts_create_gui(int window_w, int window_h) {
ImGuiWindowFlags_MenuBar);
ImGui::PopStyleVar();
- int menu_bar_h = _ts_create_menu();
+ int menu_bar_h = _ts_create_menu(window_w, window_h);
float pos_y = 0;
pos_y += menu_bar_h + 15.0f;
diff --git a/src/search.cpp b/src/search.cpp
index 3897cc7..6762779 100644
--- a/src/search.cpp
+++ b/src/search.cpp
@@ -91,6 +91,7 @@ ts_search_result *ts_create_empty_search_result()
new_result_buffer->memory = ts_memory_bucket_init(megabytes(10));
new_result_buffer->prev_result = current_search_result;
new_result_buffer->timestamp = ts_platform_get_time();
+ new_result_buffer->is_saving = false;
new_result_buffer->files = ts_array_create(sizeof(ts_found_file));
new_result_buffer->files.reserve_jump = FILE_RESERVE_COUNT;
@@ -103,6 +104,7 @@ ts_search_result *ts_create_empty_search_result()
// filter buffers
new_result_buffer->directory_to_search = (char *)ts_memory_bucket_reserve(&new_result_buffer->memory, MAX_INPUT_LENGTH);
new_result_buffer->search_text = (char *)ts_memory_bucket_reserve(&new_result_buffer->memory, MAX_INPUT_LENGTH);
+ new_result_buffer->file_filter = (char *)ts_memory_bucket_reserve(&new_result_buffer->memory, MAX_INPUT_LENGTH);
return new_result_buffer;
}
@@ -368,7 +370,7 @@ static void *_ts_list_files_thread(void *args)
// Use this thread to cleanup previous result.
if (info->prev_result) {
- while (!info->prev_result->search_completed) {
+ while (!info->prev_result->search_completed && !info->prev_result->is_saving) {
ts_thread_sleep(10);
}
ts_destroy_result(info->prev_result);
@@ -407,6 +409,7 @@ void ts_start_search(utf8_int8_t *path, utf8_int8_t *filter, utf8_int8_t *query,
ts_search_result *new_result = ts_create_empty_search_result();
snprintf(new_result->directory_to_search, MAX_INPUT_LENGTH, "%s", path);
snprintf(new_result->search_text, MAX_INPUT_LENGTH, "%s", query);
+ snprintf(new_result->file_filter, MAX_INPUT_LENGTH, "%s", filter);
new_result->filters = ts_get_filters(filter);
new_result->max_ts_thread_count = thread_count;
new_result->max_file_size = max_file_size;
diff --git a/src/search.h b/src/search.h
index da11e29..bdfdafa 100644
--- a/src/search.h
+++ b/src/search.h
@@ -32,6 +32,7 @@ typedef struct t_ts_search_result
int file_list_read_cursor;
bool cancel_search;
bool search_completed;
+ bool is_saving;
// search query
utf8_int8_t *directory_to_search;