diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 9fc29c8..b9528b1 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -4,20 +4,25 @@ FIND_PACKAGE(PkgConfig) PKG_CHECK_MODULES(JANSSON REQUIRED jansson) PKG_CHECK_MODULES(GLIB2 REQUIRED glib-2.0) +PKG_CHECK_MODULES(GOBJECT2 REQUIRED gobject-2.0) SET(SOURCES - "src/journal/file.c" + "include/edapi/journal/entry.h" "include/edapi/journal/file.h" + "src/journal/entry.c" + "src/journal/file.c" ) INCLUDE_DIRECTORIES( "include" ${GLIB2_INCLUDE_DIRS} + ${GOBJECT2_INCLUDE_DIRS} ${JANSSON_INCLUDE_DIRS} ) LINK_DIRECTORIES( ${GLIB2_LIBRARY_DIRS} + ${GOBJECT2_LIBRARY_DIRS} ${JANSSON_LIBRARY_DIRS} ) @@ -25,5 +30,9 @@ ADD_LIBRARY("edapi" SHARED ${SOURCES}) TARGET_LINK_LIBRARIES( "edapi" ${GLIB2_LIBRARIES} + ${GOBJECT2_LIBRARIES} ${JANSSON_LIBRARIES} ) + +ENABLE_TESTING() +ADD_SUBDIRECTORY("tests") diff --git a/lib/include/edapi/error.h b/lib/include/edapi/error.h new file mode 100644 index 0000000..b235020 --- /dev/null +++ b/lib/include/edapi/error.h @@ -0,0 +1,25 @@ +#ifndef EDAPI_ERROR_H +#define EDAPI_ERROR_H + +typedef enum { + ed_error_success = 0, + ed_error_args, + ed_error_invalid, + ed_error_internal, + ed_error_invalid_json, +} EDErrorCode; + +#define ED_SUCCESS(r) ((r) == ed_error_success) +#define ED_ERROR(r) ((r) != ed_error_success) + +/** + * Goes to label l if b is true. + */ +#define goto_if_true(b, l) do { if (b) goto l; } while(0) + +/** + * Goes to label `l` if r is an error code. + */ +#define goto_if_error(r, l) do { if (ED_ERROR(r)) goto l; } while(0) + +#endif diff --git a/lib/include/edapi/journal/entry.h b/lib/include/edapi/journal/entry.h new file mode 100644 index 0000000..e671941 --- /dev/null +++ b/lib/include/edapi/journal/entry.h @@ -0,0 +1,30 @@ +#ifndef EDAPI_JOURNAL_ENTRY_H +#define EDAPI_JOURNAL_ENTRY_H + +#include + +#include +#include + +G_BEGIN_DECLS + +struct _EDJournalEntryClass { + GObjectClass parent_class; + + gpointer padding[42]; +}; + +G_DECLARE_DERIVABLE_TYPE( + EDJournalEntry, ed_journal_entry, ED, JOURNALENTRY, GObject); + +#define ED_TYPE_JOURNALENTRY ed_journal_entry_get_type() + +EDJournalEntry *ed_journal_entry_new(void); + +EDErrorCode ed_journal_entry_parse(EDJournalEntry *self, + gchar const *line, + GError **error); + +G_END_DECLS + +#endif diff --git a/lib/include/edapi/journal/file.h b/lib/include/edapi/journal/file.h index fb5b002..3df15ba 100644 --- a/lib/include/edapi/journal/file.h +++ b/lib/include/edapi/journal/file.h @@ -1,6 +1,8 @@ #ifndef EDAPI_JOURNAL_FILE_H #define EDAPI_JOURNAL_FILE_H +#include + #include #include @@ -12,6 +14,10 @@ G_DECLARE_FINAL_TYPE(EDJournalFile, ed_journal_file, ED, JOURNALFILE, GObject); EDJournalFile *ed_journal_file_new(void); +EDErrorCode ed_journal_file_parse(EDJournalFile *file, char const *filename); + +EDErrorCode ed_journal_file_open(EDJournalFile *file, char const *filename); + G_END_DECLS #endif diff --git a/lib/include/edapi/util.h b/lib/include/edapi/util.h new file mode 100644 index 0000000..64c9589 --- /dev/null +++ b/lib/include/edapi/util.h @@ -0,0 +1,9 @@ +#ifndef EDAPI_UTIL_H +#define EDAPI_UTIL_H + +/** + * Checks if a string pointer is NULL or empty + */ +#define S_EMPTY(s) ((s == NULL) || strlen(s) <= 0) + +#endif diff --git a/lib/src/journal/entry.c b/lib/src/journal/entry.c new file mode 100644 index 0000000..131e0c4 --- /dev/null +++ b/lib/src/journal/entry.c @@ -0,0 +1,67 @@ +#include +#include + +#include + +typedef struct { + json_t *entry; +} EDJournalEntryPrivate; + +G_DEFINE_QUARK("ed-json-error", ed_json_error); + +G_DEFINE_TYPE_EXTENDED( + EDJournalEntry, + ed_journal_entry, + G_TYPE_OBJECT, + 0, + G_ADD_PRIVATE(EDJournalEntry) + ); + +static void ed_journal_entry_class_init(EDJournalEntryClass *klass) +{ +} + +static void ed_journal_entry_init(EDJournalEntry *self) +{ +} + +EDJournalEntry *ed_journal_entry_new(void) +{ + return g_object_new( + ED_TYPE_JOURNALENTRY, + NULL + ); +} + +EDErrorCode ed_journal_entry_parse(EDJournalEntry *self, + gchar const *line, + GError **error) +{ + EDJournalEntryPrivate *p = ed_journal_entry_get_instance_private(self); + EDErrorCode ret = ed_error_invalid; + json_error_t e; + + json_decref(p->entry); + p->entry = NULL; + + memset(&e, 0, sizeof(e)); + + p->entry = json_loads(line, 0, &e); + if (p->entry == NULL) { + g_set_error( + error, + ed_json_error_quark(), + ed_error_invalid_json, + "%d: %d: %d: %s", + e.line, + e.column, + e.position, + e.text + ); + ret = ed_error_invalid_json; + } else { + ret = ed_error_success; + } + + return ret; +} diff --git a/lib/src/journal/file.c b/lib/src/journal/file.c index 0d57ba1..9438de9 100644 --- a/lib/src/journal/file.c +++ b/lib/src/journal/file.c @@ -1,8 +1,10 @@ #include +#include typedef struct { - char *filename; - char *timestamp; + gchar *filename; + gchar *timestamp; + int index; } EDJournalFilePrivate; struct _EDJournalFile { @@ -13,11 +15,12 @@ struct _EDJournalFileClass { GObjectClass parent_class; }; -G_DEFINE_TYPE_EXTENDED(EDJournalFile, - ed_journal_file, - G_TYPE_OBJECT, - 0, - G_ADD_PRIVATE(EDJournalFile) +G_DEFINE_TYPE_EXTENDED( + EDJournalFile, + ed_journal_file, + G_TYPE_OBJECT, + 0, + G_ADD_PRIVATE(EDJournalFile) ); static void ed_journal_file_finalize(GObject *obj) @@ -47,3 +50,67 @@ EDJournalFile *ed_journal_file_new(void) { return g_object_new(ED_TYPE_JOURNALFILE, NULL); } + +EDErrorCode ed_journal_file_parse(EDJournalFile *file, char const *filename) +{ + EDJournalFilePrivate *p = ed_journal_file_get_instance_private(file); + gchar *basename = NULL; + EDErrorCode ret = ed_error_internal; + + GRegex *new_style = NULL; + GMatchInfo *matches = NULL; + + gchar *timestamp = NULL; + gchar *index = NULL; + + new_style = g_regex_new( + "Journal\\.([\\dT\\-]+)\\.(\\d+)\\.log", + 0, 0, NULL + ); + goto_if_true(new_style == NULL, done); + + basename = g_path_get_basename(filename); + goto_if_true(S_EMPTY(basename), done); + + if (g_regex_match(new_style, basename, 0, &matches)) { + timestamp = g_match_info_fetch(matches, 1); + index = g_match_info_fetch(matches, 2); + } + + g_match_info_unref(matches); + matches = NULL; + + goto_if_true(S_EMPTY(timestamp), done); + goto_if_true(S_EMPTY(index), done); + + g_free(p->timestamp); + p->timestamp = g_strdup(timestamp); + + p->index = strtol(index, NULL, 0); + + g_free(p->filename); + p->filename = g_strdup(filename); + + ret = ed_error_success; + +done: + + g_free(timestamp); + g_free(index); + g_free(basename); + g_regex_unref(new_style); + + return ret; +} + +EDErrorCode ed_journal_file_open(EDJournalFile *file, char const *filename) +{ + EDErrorCode r = ed_error_success; + + r = ed_journal_file_parse(file, filename); + if (ED_ERROR(r)) { + return r; + } + + return r; +} diff --git a/lib/tests/CMakeLists.txt b/lib/tests/CMakeLists.txt new file mode 100644 index 0000000..733f9d7 --- /dev/null +++ b/lib/tests/CMakeLists.txt @@ -0,0 +1,22 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 3.24) + +PKG_CHECK_MODULES(CMOCKA REQUIRED cmocka) + +SET(TESTS + "test-journal-file-parse" +) + +INCLUDE_DIRECTORIES( + "${CMAKE_CURRENT_SOURCE_DIR}/../include" + "${CMOCKA_INCLUDE_DIRS}" +) + +FOREACH(TEST ${TESTS}) + ADD_EXECUTABLE(${TEST} "${TEST}.c") + TARGET_LINK_LIBRARIES( + "${TEST}" + "edapi" + ${CMOCKA_LIBRARIES} + ) + ADD_TEST(NAME ${TEST} COMMAND ${TEST}) +ENDFOREACH() diff --git a/lib/tests/test-journal-file-parse.c b/lib/tests/test-journal-file-parse.c new file mode 100644 index 0000000..ffeae88 --- /dev/null +++ b/lib/tests/test-journal-file-parse.c @@ -0,0 +1,30 @@ +#include +#include +#include +#include + +#include + +static void test_new_filename(void **state) +{ + char const *filename = "Journal.2023-04-18T061507.01.log"; + + EDJournalFile *file = ed_journal_file_new(); + EDErrorCode ret = 0; + + assert_non_null(file); + + ret = ed_journal_file_parse(file, filename); + assert_int_equal(ret, ed_error_success); + + g_clear_object(&file); +} + +int main(int ac, char **av) +{ + static const struct CMUnitTest tests[] = { + cmocka_unit_test(test_new_filename), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +}