From 37eb90a94f06c12ad51bf7ecc14c1cabb93f6aeb Mon Sep 17 00:00:00 2001 From: ilya-krivtsov <180809461+ilya-krivtsov@users.noreply.github.com> Date: Sat, 5 Oct 2024 14:09:49 +0300 Subject: [PATCH 01/10] Homework 4 task 2 (WIP) --- homework_4/CMakeLists.txt | 2 + homework_4/task_2/CMakeLists.txt | 9 ++ homework_4/task_2/database.c | 228 +++++++++++++++++++++++++++++++ homework_4/task_2/database.h | 58 ++++++++ homework_4/task_2/main.c | 133 ++++++++++++++++++ homework_4/task_2/personEntry.c | 82 +++++++++++ homework_4/task_2/personEntry.h | 40 ++++++ homework_4/task_2/test.c | 7 + 8 files changed, 559 insertions(+) create mode 100644 homework_4/task_2/CMakeLists.txt create mode 100644 homework_4/task_2/database.c create mode 100644 homework_4/task_2/database.h create mode 100644 homework_4/task_2/main.c create mode 100644 homework_4/task_2/personEntry.c create mode 100644 homework_4/task_2/personEntry.h create mode 100644 homework_4/task_2/test.c diff --git a/homework_4/CMakeLists.txt b/homework_4/CMakeLists.txt index 1392ccc..7fc325e 100644 --- a/homework_4/CMakeLists.txt +++ b/homework_4/CMakeLists.txt @@ -1,3 +1,5 @@ project(homework_4) set(homeworkName "${PROJECT_NAME}") + +add_subdirectory(task_2) diff --git a/homework_4/task_2/CMakeLists.txt b/homework_4/task_2/CMakeLists.txt new file mode 100644 index 0000000..8513e27 --- /dev/null +++ b/homework_4/task_2/CMakeLists.txt @@ -0,0 +1,9 @@ +project("${homeworkName}_task_2") + +add_library(phoneBook database.c personEntry.c) + +add_executable(${PROJECT_NAME} main.c) +target_link_libraries(${PROJECT_NAME} phoneBook) + +add_executable(${PROJECT_NAME}_test test.c) +target_link_libraries(${PROJECT_NAME}_test PRIVATE phoneBook) diff --git a/homework_4/task_2/database.c b/homework_4/task_2/database.c new file mode 100644 index 0000000..9ebbffc --- /dev/null +++ b/homework_4/task_2/database.c @@ -0,0 +1,228 @@ +#include "database.h" + +#include +#include +#include +#include + +#define DB_SIGNATURE "#!hw_phonebook" +#define DB_BEGIN "{{" +#define DB_END "}}" +#define DB_ENTRY_BEGIN "[[" +#define DB_ENTRY_END "]]" +#define DB_ENTRY_NULL "__NULL__" + +DBResult createDatabase(Database **database) { + if (database == NULL) { + return DB_NULL_POINTER; + } + + Database *db = malloc(sizeof(Database)); + if (db == NULL) { + *database = NULL; + return DB_ALLOCATION_ERROR; + } + db->entriesCount = 0; + db->entriesCapacity = 4; + db->entries = malloc(db->entriesCapacity * sizeof(PersonEntry *)); + + if (db->entries == NULL) { + *database = NULL; + return DB_ALLOCATION_ERROR; + } + + *database = db; + + return DB_SUCCESS; +} + +DBResult addEntry(Database *database, PersonEntry *entry) { + if (database == NULL) { + return DB_NULL_POINTER; + } + + if (database->entriesCount == database->entriesCapacity) { + database->entriesCapacity *= 2; + PersonEntry **entries = realloc(database->entries, database->entriesCapacity * sizeof(PersonEntry **)); + if (entries == NULL) { + return DB_ALLOCATION_ERROR; + } + database->entries = entries; + } + database->entries[database->entriesCount] = entry; + ++database->entriesCount; + return DB_SUCCESS; +} + +#pragma region Save/Load + +DBResult saveDatabase(const char *path, Database *database) { + if (database == NULL) { + return DB_NULL_POINTER; + } + + FILE *file = fopen(path, "w"); + if (file == NULL) { + return DB_IO_ERROR; + } + + fprintf(file, "%s\n", DB_SIGNATURE); + fprintf(file, "%s\n", DB_BEGIN); + + for (int i = 0; i < database->entriesCount; ++i) { + fprintf(file, "%s\n", DB_ENTRY_BEGIN); + + PersonEntry *entry = database->entries[i]; + + if (entry == NULL) { + fprintf(file, "%s\n", DB_ENTRY_NULL); + } else { + fprintf(file, "%s\n", entry->personName); + + for (int j = 0; j < entry->phoneNumbersCount; ++j) { + fprintf(file, "%s\n", entry->phoneNumbers[j].number); + } + } + + fprintf(file, "%s\n", DB_ENTRY_END); + } + + fprintf(file, "%s\n", DB_END); + + fclose(file); + + return DB_SUCCESS; +} + +// reads line and sets first '\n' to '\0' +bool tryReadLine(char *buffer, int count, FILE *file) { + if (fgets(buffer, count, file) == NULL || feof(file)) { + return false; + } + + for (int i = 0; i < count; ++i) { + if (buffer[i] == '\n') { + buffer[i] = '\0'; + break; + } + } + + return true; +} + +DBResult loadFromFile(Database *database, FILE *file) { + char buffer[256] = { 0 }; + + if (!tryReadLine(buffer, sizeof(buffer), file) && strcmp(buffer, DB_SIGNATURE) != 0) { + return DB_INVALID_FORMAT; + } + + if (!tryReadLine(buffer, sizeof(buffer), file) && strcmp(buffer, DB_BEGIN) != 0) { + return DB_INVALID_FORMAT; + } + + bool finishedScanning = false; + + DBResult result; + + while (true) { + // entry begin anchor or db end anchor + if (!tryReadLine(buffer, sizeof(buffer), file)) { + return DB_INVALID_FORMAT; + } + + if (strcmp(buffer, DB_END) == 0) { + finishedScanning = true; + break; + } + + if (strcmp(buffer, DB_ENTRY_BEGIN) != 0) { + return DB_INVALID_FORMAT; + } + + // name or null keyword + if (!tryReadLine(buffer, sizeof(buffer), file)) { + return DB_INVALID_FORMAT; + } + + if (strcmp(buffer, DB_ENTRY_NULL) == 0) { + result = addEntry(database, NULL); + if (result != DB_SUCCESS) { + return result; + } + if (!tryReadLine(buffer, sizeof(buffer), file) || strcmp(buffer, DB_ENTRY_END) != 0) { + return DB_INVALID_FORMAT; + } + continue; + } + + PersonEntry *entry; + if (!tryCreateEntry(&entry, buffer)) { + return DB_ALLOCATION_ERROR; + } + + while (true) { + // phone number or entry end anchor + if (!tryReadLine(buffer, sizeof(buffer), file)) { + return DB_INVALID_FORMAT; + } + + if (strcmp(buffer, DB_ENTRY_END) == 0) { + break; + } + + PhoneNumber number; + if (!tryParsePhoneNumber(buffer, &number)) { + return DB_INVALID_FORMAT; + } + tryAddPhoneNumber(entry, number); + } + + result = addEntry(database, entry); + if (result != DB_SUCCESS) { + return result; + } + } + + return finishedScanning ? DB_SUCCESS : DB_INVALID_FORMAT; +} + +DBResult loadDatabase(const char *path, Database **database) { + if (database == NULL) { + return DB_NULL_POINTER; + } + + FILE *file = fopen(path, "r"); + + if (file == NULL) { + *database = NULL; + return DB_IO_ERROR; + } + + Database *db; + + DBResult result = createDatabase(&db); + if (result == DB_SUCCESS) { + result = loadFromFile(db, file); + } + + *database = result == DB_SUCCESS ? db : NULL; + + fclose(file); + + return result; +} + +#pragma endregion + +void disposeDatabase(Database *database) { + if (database == NULL) { + return; + } + + for (int i = 0; i < database->entriesCount; ++i) { + disposeEntry(database->entries[i]); + } + free(database->entries); + free(database); +} diff --git a/homework_4/task_2/database.h b/homework_4/task_2/database.h new file mode 100644 index 0000000..bd67359 --- /dev/null +++ b/homework_4/task_2/database.h @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include "personEntry.h" + +typedef struct { + int entriesCount, entriesCapacity; + PersonEntry **entries; +} Database; + +/// @brief Each operation with database (except dispose) yields result +typedef enum { + /// @brief Operation is successful + DB_SUCCESS = 0, + + /// @brief IO error (file does not exist / open in other process / etc) + DB_IO_ERROR = 1, + + /// @brief Specified pointer is null + DB_NULL_POINTER = 2, + + /// @brief Allocation error + DB_ALLOCATION_ERROR = 3, + + /// @brief Invalid file format (when reading) + DB_INVALID_FORMAT = 4, + + /// @brief Unknown error + DB_UNKNOWN_ERROR = -1 +} DBResult; + +/// @brief Creates empty database +/// @param database Pointer to write result to +/// @return `DB_SUCCESS`, `DB_NULL_POINTER`, `DB_ALLOCATION_ERROR` +DBResult createDatabase(Database **database); + +/// @brief Adds a person entry to database +/// @param database Pointer to database +/// @param entry Entry to add (can be `NULL`) +/// @return `DB_SUCCESS`, `DB_NULL_POINTER`, `DB_ALLOCATION_ERROR` +DBResult addEntry(Database *database, PersonEntry *entry); + +/// @brief Saves database to specified file path (will OVERWRITE existing file contents) +/// @param input Path to save database to +/// @param database Pointer to database +/// @return `DB_SUCCESS`, `DB_IO_ERROR`, `DB_NULL_POINTER` +DBResult saveDatabase(const char *path, Database *database); + +/// @brief Loads database from specified file path +/// @param input Path to load database from +/// @param database Pointer to write result to +/// @return `DB_SUCCESS`, `DB_IO_ERROR`, `DB_NULL_POINTER`, `DB_ALLOCATION_ERROR`, `DB_INVALID_FORMAT` +DBResult loadDatabase(const char *path, Database **database); + +/// @brief Disposes database and all of its entries +/// @param database Database to dispose (can be `NULL`) +void disposeDatabase(Database *database); diff --git a/homework_4/task_2/main.c b/homework_4/task_2/main.c new file mode 100644 index 0000000..da96260 --- /dev/null +++ b/homework_4/task_2/main.c @@ -0,0 +1,133 @@ +#include + +#include "database.h" +#include "personEntry.h" + +void printDatabase(Database *database) { + printf("database:\n"); + for (int i = 0; i < database->entriesCount; ++i) { + PersonEntry *entry = database->entries[i]; + + if (entry == NULL) { + printf("- null;\n\n"); + continue; + } + + printf("- %s:\n", entry->personName); + + for (int j = 0; j < entry->phoneNumbersCount; ++j) { + printf(" * %s\n", entry->phoneNumbers[j].number); + } + printf("\n"); + } +} + +bool doStuff(Database *database) { + PersonEntry *entry, *entry2, *entry3; + if (!tryCreateEntry(&entry, "Kris")) { + return false; + } + if (!tryAddRawPhoneNumber(entry, "+7 901 123 45 67")) { + return false; + } + if (!tryAddRawPhoneNumber(entry, "+7 912 345 67 89")) { + return false; + } + if (addEntry(database, entry) != DB_SUCCESS) { + return false; + } + + if (!tryCreateEntry(&entry2, "Susie")) { + return false; + } + if (!tryAddRawPhoneNumber(entry2, "+1 (301) 124-45-75")) { + return false; + } + if (!tryAddRawPhoneNumber(entry2, "+1 (212) 325-38-68")) { + return false; + } + if (addEntry(database, entry2) != DB_SUCCESS) { + return false; + } + + if (addEntry(database, NULL) != DB_SUCCESS) { + return false; + } + + if (!tryCreateEntry(&entry3, "Noelle")) { + return false; + } + if (!tryAddRawPhoneNumber(entry3, "+3 (111) 222-33-44")) { + return false; + } + if (!tryAddRawPhoneNumber(entry3, "+3 (222) 999-22-11")) { + return false; + } + if (!tryAddRawPhoneNumber(entry3, "+3 (355) 532-23-45")) { + return false; + } + if (!tryAddRawPhoneNumber(entry3, "+3 (234) 342-64-41")) { + return false; + } + if (!tryAddRawPhoneNumber(entry3, "+3 (974) 756-33-64")) { + return false; + } + if (!tryAddRawPhoneNumber(entry3, "+3 (535) 463-22-53")) { + return false; + } + if (addEntry(database, entry3) != DB_SUCCESS) { + return false; + } + + printDatabase(database); + + switch (saveDatabase("./db.txt", database)) + { + case DB_IO_ERROR: + printf("write io error\n"); + return false; + + case DB_SUCCESS: + printf("saved\n"); + break; + } + + Database *db2; + + switch (loadDatabase("./db.txt", &db2)) + { + case DB_IO_ERROR: + printf("read io error\n"); + return false; + + case DB_INVALID_FORMAT: + printf("invalid file format\n"); + return false; + + case DB_ALLOCATION_ERROR: + printf("alloc error\n"); + break; + + case DB_SUCCESS: + printf("loaded\n"); + break; + } + + printf("loaded db:\n"); + + printDatabase(db2); + + disposeDatabase(db2); + + return true; +} + +int main(void) { + Database *database = NULL; + + if (createDatabase(&database) != DB_SUCCESS || !doStuff(database)) { + printf("something is very wrong :((\n"); + } + + disposeDatabase(database); +} diff --git a/homework_4/task_2/personEntry.c b/homework_4/task_2/personEntry.c new file mode 100644 index 0000000..9a1ff39 --- /dev/null +++ b/homework_4/task_2/personEntry.c @@ -0,0 +1,82 @@ +#include "personEntry.h" + +#include +#include +#include + +bool tryParsePhoneNumber(const char *input, PhoneNumber *phoneNumber) { + if (phoneNumber == NULL) { + return false; + } + + // TODO: actual parsing + char *result = strdup(input); + + if (result == NULL) { + return false; + } + + phoneNumber->number = result; + + return true; +} + +bool tryCreateEntry(PersonEntry **personEntry, const char *personName) { + PersonEntry *entry = malloc(sizeof(PersonEntry)); + + entry->phoneNumbersCount = 0; + entry->phoneNumbersCapacity = 4; + entry->phoneNumbers = malloc(entry->phoneNumbersCapacity * sizeof(PhoneNumber *)); + if (entry->phoneNumbers == NULL) { + *personEntry = NULL; + return false; + } + + char *name = strdup(personName); + if (name == NULL) { + *personEntry = NULL; + return false; + } + entry->personName = name; + + *personEntry = entry; + + return true; +} + +bool tryAddPhoneNumber(PersonEntry *entry, PhoneNumber number) { + if (entry->phoneNumbersCount == entry->phoneNumbersCapacity) { + entry->phoneNumbersCapacity *= 2; + PhoneNumber *numbers = realloc(entry->phoneNumbers, entry->phoneNumbersCapacity * sizeof(PhoneNumber *)); + if (numbers == NULL) { + return false; + } + entry->phoneNumbers = numbers; + } + entry->phoneNumbers[entry->phoneNumbersCount] = number; + ++entry->phoneNumbersCount; + return true; +} + +bool tryAddRawPhoneNumber(PersonEntry *entry, const char *number) { + PhoneNumber phoneNumber; + if (!tryParsePhoneNumber(number, &phoneNumber)) { + return false; + } + if (!tryAddPhoneNumber(entry, phoneNumber)) { + return false; + } + return true; +} + +void disposeEntry(PersonEntry *entry) { + if (entry == NULL) { + return; + } + + for (int i = 0; i < entry->phoneNumbersCount; ++i) { + free(entry->phoneNumbers[i].number); + } + free(entry->phoneNumbers); + free(entry); +} diff --git a/homework_4/task_2/personEntry.h b/homework_4/task_2/personEntry.h new file mode 100644 index 0000000..0550123 --- /dev/null +++ b/homework_4/task_2/personEntry.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +typedef struct { + char *number; +} PhoneNumber; + +typedef struct { + int phoneNumbersCount, phoneNumbersCapacity; + char *personName; + PhoneNumber *phoneNumbers; +} PersonEntry; + +/// @brief Parses phone number from input string +/// @param input Input string to be parsed +/// @param phoneNumber Pointer to write result to (can't be `NULL`) +/// @return `true` if parsed successfully, `false` otherwise +bool tryParsePhoneNumber(const char *input, PhoneNumber *phoneNumber); + +/// @brief Creates empty person entry +/// @param entry Pointer to write result to +/// @return `true` if successful, `false` otherwise (allocation failed) +bool tryCreateEntry(PersonEntry **entry, const char *personName); + +/// @brief Adds phone number to person entry +/// @param entry Pointer to person entry +/// @param number Phone number to add +/// @return `true` if successful, `false` otherwise (allocation failed) +bool tryAddPhoneNumber(PersonEntry *entry, PhoneNumber number); + +/// @brief Adds raw phone number to person entry +/// @param entry Pointer to person entry +/// @param number Raw phone number to add +/// @return `true` if successful, `false` otherwise (allocation failed) +bool tryAddRawPhoneNumber(PersonEntry *entry, const char *number); + +/// @brief Disposes person entry and all of its phone numbers +/// @param entry Entry to dipose (can be `NULL`) +void disposeEntry(PersonEntry *entry); diff --git a/homework_4/task_2/test.c b/homework_4/task_2/test.c new file mode 100644 index 0000000..f71f08c --- /dev/null +++ b/homework_4/task_2/test.c @@ -0,0 +1,7 @@ +#define CTEST_MAIN +#define CTEST_SEGFAULT +#include "../../ctest/ctest.h" + +int main(int argc, const char *argv[]) { + return ctest_main(argc, argv); +} From 187e8184f27ec3eb493f66d3c4bc6f11649413a4 Mon Sep 17 00:00:00 2001 From: ilya-krivtsov <180809461+ilya-krivtsov@users.noreply.github.com> Date: Sun, 6 Oct 2024 17:24:16 +0300 Subject: [PATCH 02/10] Homework 4 task 2 (WIP 2) --- homework_4/task_2/database.c | 199 +++++++----------------- homework_4/task_2/database.h | 58 +++---- homework_4/task_2/main.c | 268 ++++++++++++++++++++------------ homework_4/task_2/personEntry.c | 57 +------ homework_4/task_2/personEntry.h | 26 +--- 5 files changed, 253 insertions(+), 355 deletions(-) diff --git a/homework_4/task_2/database.c b/homework_4/task_2/database.c index 9ebbffc..3ea68dd 100644 --- a/homework_4/task_2/database.c +++ b/homework_4/task_2/database.c @@ -8,90 +8,55 @@ #define DB_SIGNATURE "#!hw_phonebook" #define DB_BEGIN "{{" #define DB_END "}}" -#define DB_ENTRY_BEGIN "[[" -#define DB_ENTRY_END "]]" -#define DB_ENTRY_NULL "__NULL__" -DBResult createDatabase(Database **database) { +Database *createDatabase() { + Database *database = malloc(sizeof(Database)); if (database == NULL) { - return DB_NULL_POINTER; + return NULL; } + database->entriesCount = 0; - Database *db = malloc(sizeof(Database)); - if (db == NULL) { - *database = NULL; - return DB_ALLOCATION_ERROR; - } - db->entriesCount = 0; - db->entriesCapacity = 4; - db->entries = malloc(db->entriesCapacity * sizeof(PersonEntry *)); - - if (db->entries == NULL) { - *database = NULL; - return DB_ALLOCATION_ERROR; - } - - *database = db; - - return DB_SUCCESS; + return database; } -DBResult addEntry(Database *database, PersonEntry *entry) { +bool addEntry(Database *database, PersonEntry entry) { if (database == NULL) { - return DB_NULL_POINTER; + return false; } - if (database->entriesCount == database->entriesCapacity) { - database->entriesCapacity *= 2; - PersonEntry **entries = realloc(database->entries, database->entriesCapacity * sizeof(PersonEntry **)); - if (entries == NULL) { - return DB_ALLOCATION_ERROR; - } - database->entries = entries; + // entries count cannot be more than DB_MAX_ENTRIES, but check this case too + if (database->entriesCount >= DB_MAX_ENTRIES) { + return false; } + database->entries[database->entriesCount] = entry; ++database->entriesCount; - return DB_SUCCESS; + return true; } #pragma region Save/Load -DBResult saveDatabase(const char *path, Database *database) { - if (database == NULL) { - return DB_NULL_POINTER; +bool saveDatabase(FILE *stream, Database *database) { + if (stream == NULL) { + return false; } - FILE *file = fopen(path, "w"); - if (file == NULL) { - return DB_IO_ERROR; + if (database == NULL) { + return false; } - fprintf(file, "%s\n", DB_SIGNATURE); - fprintf(file, "%s\n", DB_BEGIN); + fprintf(stream, "%s\n", DB_SIGNATURE); + fprintf(stream, "%s\n", DB_BEGIN); for (int i = 0; i < database->entriesCount; ++i) { - fprintf(file, "%s\n", DB_ENTRY_BEGIN); - - PersonEntry *entry = database->entries[i]; - - if (entry == NULL) { - fprintf(file, "%s\n", DB_ENTRY_NULL); - } else { - fprintf(file, "%s\n", entry->personName); - - for (int j = 0; j < entry->phoneNumbersCount; ++j) { - fprintf(file, "%s\n", entry->phoneNumbers[j].number); - } - } - - fprintf(file, "%s\n", DB_ENTRY_END); + PersonEntry entry = database->entries[i]; + fprintf(stream, "%s\n", entry.personName); + fprintf(stream, "%s\n", entry.phoneNumber); } - fprintf(file, "%s\n", DB_END); - - fclose(file); + fprintf(stream, "%s\n", DB_END); - return DB_SUCCESS; + return true; } // reads line and sets first '\n' to '\0' @@ -110,107 +75,64 @@ bool tryReadLine(char *buffer, int count, FILE *file) { return true; } -DBResult loadFromFile(Database *database, FILE *file) { - char buffer[256] = { 0 }; - - if (!tryReadLine(buffer, sizeof(buffer), file) && strcmp(buffer, DB_SIGNATURE) != 0) { - return DB_INVALID_FORMAT; +Database *loadDatabase(FILE *stream) { + if (stream == NULL) { + return NULL; } - if (!tryReadLine(buffer, sizeof(buffer), file) && strcmp(buffer, DB_BEGIN) != 0) { - return DB_INVALID_FORMAT; + Database *database = createDatabase(); + + if (database == NULL) { + return NULL; } - bool finishedScanning = false; + char buffer[256] = { 0 }; + + if (!tryReadLine(buffer, sizeof(buffer), stream) && strcmp(buffer, DB_SIGNATURE) != 0) { + return NULL; + } - DBResult result; + if (!tryReadLine(buffer, sizeof(buffer), stream) && strcmp(buffer, DB_BEGIN) != 0) { + return NULL; + } while (true) { - // entry begin anchor or db end anchor - if (!tryReadLine(buffer, sizeof(buffer), file)) { - return DB_INVALID_FORMAT; + // person name or db end anchor + if (!tryReadLine(buffer, sizeof(buffer), stream)) { + return NULL; } if (strcmp(buffer, DB_END) == 0) { - finishedScanning = true; break; } - if (strcmp(buffer, DB_ENTRY_BEGIN) != 0) { - return DB_INVALID_FORMAT; + char *personName = strdup(buffer); + if (personName == NULL) { + return NULL; } - // name or null keyword - if (!tryReadLine(buffer, sizeof(buffer), file)) { - return DB_INVALID_FORMAT; + // phone number + if (!tryReadLine(buffer, sizeof(buffer), stream)) { + return NULL; } - if (strcmp(buffer, DB_ENTRY_NULL) == 0) { - result = addEntry(database, NULL); - if (result != DB_SUCCESS) { - return result; - } - if (!tryReadLine(buffer, sizeof(buffer), file) || strcmp(buffer, DB_ENTRY_END) != 0) { - return DB_INVALID_FORMAT; - } - continue; + PhoneNumber phoneNumber; + if (!tryParsePhoneNumber(buffer, &phoneNumber)) { + return NULL; } - PersonEntry *entry; - if (!tryCreateEntry(&entry, buffer)) { - return DB_ALLOCATION_ERROR; - } - - while (true) { - // phone number or entry end anchor - if (!tryReadLine(buffer, sizeof(buffer), file)) { - return DB_INVALID_FORMAT; - } - - if (strcmp(buffer, DB_ENTRY_END) == 0) { - break; - } - - PhoneNumber number; - if (!tryParsePhoneNumber(buffer, &number)) { - return DB_INVALID_FORMAT; - } - tryAddPhoneNumber(entry, number); - } + PersonEntry entry = { + .personName = personName, + .phoneNumber = phoneNumber + }; - result = addEntry(database, entry); - if (result != DB_SUCCESS) { - return result; + if (!addEntry(database, entry)) { + // no space left, not an error + break; } } - return finishedScanning ? DB_SUCCESS : DB_INVALID_FORMAT; -} - -DBResult loadDatabase(const char *path, Database **database) { - if (database == NULL) { - return DB_NULL_POINTER; - } - - FILE *file = fopen(path, "r"); - - if (file == NULL) { - *database = NULL; - return DB_IO_ERROR; - } - - Database *db; - - DBResult result = createDatabase(&db); - if (result == DB_SUCCESS) { - result = loadFromFile(db, file); - } - - *database = result == DB_SUCCESS ? db : NULL; - - fclose(file); - - return result; + return database; } #pragma endregion @@ -221,8 +143,7 @@ void disposeDatabase(Database *database) { } for (int i = 0; i < database->entriesCount; ++i) { - disposeEntry(database->entries[i]); + disposeEntry(&database->entries[i]); } - free(database->entries); free(database); } diff --git a/homework_4/task_2/database.h b/homework_4/task_2/database.h index bd67359..ce0efa7 100644 --- a/homework_4/task_2/database.h +++ b/homework_4/task_2/database.h @@ -1,57 +1,37 @@ #pragma once #include +#include #include "personEntry.h" +#define DB_MAX_ENTRIES 100 + typedef struct { - int entriesCount, entriesCapacity; - PersonEntry **entries; + int entriesCount; + PersonEntry entries[DB_MAX_ENTRIES]; } Database; -/// @brief Each operation with database (except dispose) yields result -typedef enum { - /// @brief Operation is successful - DB_SUCCESS = 0, - - /// @brief IO error (file does not exist / open in other process / etc) - DB_IO_ERROR = 1, - - /// @brief Specified pointer is null - DB_NULL_POINTER = 2, - - /// @brief Allocation error - DB_ALLOCATION_ERROR = 3, - - /// @brief Invalid file format (when reading) - DB_INVALID_FORMAT = 4, - - /// @brief Unknown error - DB_UNKNOWN_ERROR = -1 -} DBResult; - /// @brief Creates empty database -/// @param database Pointer to write result to -/// @return `DB_SUCCESS`, `DB_NULL_POINTER`, `DB_ALLOCATION_ERROR` -DBResult createDatabase(Database **database); +/// @return `Database` if created successfully, `NULL` otherwise +Database *createDatabase(); /// @brief Adds a person entry to database /// @param database Pointer to database -/// @param entry Entry to add (can be `NULL`) -/// @return `DB_SUCCESS`, `DB_NULL_POINTER`, `DB_ALLOCATION_ERROR` -DBResult addEntry(Database *database, PersonEntry *entry); +/// @param entry Entry to add +/// @return `true` if added successfully, `false` otherwise (no space in database left) +bool addEntry(Database *database, PersonEntry entry); -/// @brief Saves database to specified file path (will OVERWRITE existing file contents) -/// @param input Path to save database to +/// @brief Saves database to specified stream +/// @param stream Stream to save database to /// @param database Pointer to database -/// @return `DB_SUCCESS`, `DB_IO_ERROR`, `DB_NULL_POINTER` -DBResult saveDatabase(const char *path, Database *database); - -/// @brief Loads database from specified file path -/// @param input Path to load database from -/// @param database Pointer to write result to -/// @return `DB_SUCCESS`, `DB_IO_ERROR`, `DB_NULL_POINTER`, `DB_ALLOCATION_ERROR`, `DB_INVALID_FORMAT` -DBResult loadDatabase(const char *path, Database **database); +/// @return `true` if saved successfully, `false` otherwise +bool saveDatabase(FILE *stream, Database *database); + +/// @brief Loads database from specified file stream +/// @param stream Stream to load database from +/// @return `Database` if loaded successfully, `NULL` otherwise +Database *loadDatabase(FILE *stream); /// @brief Disposes database and all of its entries /// @param database Database to dispose (can be `NULL`) diff --git a/homework_4/task_2/main.c b/homework_4/task_2/main.c index da96260..b5e3f09 100644 --- a/homework_4/task_2/main.c +++ b/homework_4/task_2/main.c @@ -1,133 +1,201 @@ #include +#include +#include #include "database.h" #include "personEntry.h" -void printDatabase(Database *database) { - printf("database:\n"); - for (int i = 0; i < database->entriesCount; ++i) { - PersonEntry *entry = database->entries[i]; - - if (entry == NULL) { - printf("- null;\n\n"); - continue; - } +typedef enum { + Exit, + Error, + Menu, + AddingEntry, + PrintingAllEntries, + FindNumberByName, + FindNameByPhone, + SaveDatabase +} State; + +State readCommand(void) { + int command = -1; + if (scanf("%d", &command) != 1) { + } + while (getchar() != '\n') {} + switch (command) + { + case 0: + return Exit; + case 1: + return AddingEntry; + case 2: + return PrintingAllEntries; + case 3: + return FindNumberByName; + case 4: + return FindNameByPhone; + case 5: + return SaveDatabase; + default: + printf("Error: unknown command\n"); + return Menu; + } +} - printf("- %s:\n", entry->personName); +char *readLine(const char *prompt) { + char buffer[1024] = { 0 }; + printf("%s", prompt); - for (int j = 0; j < entry->phoneNumbersCount; ++j) { - printf(" * %s\n", entry->phoneNumbers[j].number); + bool overflow = true; + for (int i = 0; i < sizeof(buffer) - 1; ++i) { + char c = getchar(); + if (c == '\n' || c == EOF) { + overflow = false; + break; } - printf("\n"); + buffer[i] = c; + } + if (overflow) { + while (getchar() != '\n') {} } + + return strdup(buffer); } -bool doStuff(Database *database) { - PersonEntry *entry, *entry2, *entry3; - if (!tryCreateEntry(&entry, "Kris")) { - return false; - } - if (!tryAddRawPhoneNumber(entry, "+7 901 123 45 67")) { - return false; - } - if (!tryAddRawPhoneNumber(entry, "+7 912 345 67 89")) { - return false; - } - if (addEntry(database, entry) != DB_SUCCESS) { - return false; +State addEntryCommand(Database *database) { + if (database->entriesCount == DB_MAX_ENTRIES) { + printf("Error: entries slots maxed out\n"); + return Menu; } - if (!tryCreateEntry(&entry2, "Susie")) { - return false; - } - if (!tryAddRawPhoneNumber(entry2, "+1 (301) 124-45-75")) { - return false; - } - if (!tryAddRawPhoneNumber(entry2, "+1 (212) 325-38-68")) { - return false; - } - if (addEntry(database, entry2) != DB_SUCCESS) { - return false; + char *name = readLine("Enter name: "); + if (name == NULL) { + printf("Error: cannot read name"); + return Menu; } - if (addEntry(database, NULL) != DB_SUCCESS) { - return false; + char *rawPhoneNumber = readLine("Enter phone number: "); + if (rawPhoneNumber == NULL) { + printf("Error: cannot read phone number"); + return Menu; } - if (!tryCreateEntry(&entry3, "Noelle")) { - return false; - } - if (!tryAddRawPhoneNumber(entry3, "+3 (111) 222-33-44")) { - return false; - } - if (!tryAddRawPhoneNumber(entry3, "+3 (222) 999-22-11")) { - return false; - } - if (!tryAddRawPhoneNumber(entry3, "+3 (355) 532-23-45")) { - return false; - } - if (!tryAddRawPhoneNumber(entry3, "+3 (234) 342-64-41")) { - return false; - } - if (!tryAddRawPhoneNumber(entry3, "+3 (974) 756-33-64")) { - return false; - } - if (!tryAddRawPhoneNumber(entry3, "+3 (535) 463-22-53")) { - return false; - } - if (addEntry(database, entry3) != DB_SUCCESS) { - return false; + PhoneNumber phoneNumber; + if (!tryParsePhoneNumber(rawPhoneNumber, &phoneNumber)) { + printf("Error: phone number is in incorrect format"); + free(rawPhoneNumber); + return Menu; } + free(rawPhoneNumber); - printDatabase(database); + PersonEntry entry = { + .personName = name, + .phoneNumber = phoneNumber + }; - switch (saveDatabase("./db.txt", database)) - { - case DB_IO_ERROR: - printf("write io error\n"); - return false; - - case DB_SUCCESS: - printf("saved\n"); - break; + if (!addEntry(database, entry)) { + printf("Error: cannot add new entry\n"); + return Menu; } - Database *db2; - - switch (loadDatabase("./db.txt", &db2)) - { - case DB_IO_ERROR: - printf("read io error\n"); - return false; - - case DB_INVALID_FORMAT: - printf("invalid file format\n"); - return false; - - case DB_ALLOCATION_ERROR: - printf("alloc error\n"); - break; + printf("Added entry successfully\n"); - case DB_SUCCESS: - printf("loaded\n"); - break; + int slotsLeft = DB_MAX_ENTRIES - database->entriesCount; + if (slotsLeft == 0) { + printf("Warning: no entry slots left in database for new entries\n"); + } else if (slotsLeft <= 10) { + printf("Warning: %d entry slots left in database\n", slotsLeft); } - printf("loaded db:\n"); - - printDatabase(db2); + return Menu; +} - disposeDatabase(db2); +State printDatabaseCommand(Database *database) { + for (int i = 0; i < database->entriesCount; ++i) { + PersonEntry entry = database->entries[i]; + printf("%s : %s\n", entry.personName, entry.phoneNumber); + } - return true; + return Menu; } -int main(void) { - Database *database = NULL; +State saveDatabaseCommand(Database *database, const char *path) { + FILE *file = fopen(path, "w"); + if (file == NULL) { + printf("IO error: cannot save database\n"); + return Menu; + } + if (!saveDatabase(file, database)) { + printf("Error: cannot save database\n"); + } + fclose(file); + printf("Saved successfully\n"); + return Menu; +} - if (createDatabase(&database) != DB_SUCCESS || !doStuff(database)) { - printf("something is very wrong :((\n"); +bool doConversation(void) { + const char *databasePath = "./phoneDatabase"; + Database *database; + FILE *file = fopen(databasePath, "r"); + if (file == NULL) { + database = createDatabase(); + } else { + database = loadDatabase(file); + fclose(file); + } + + printf("Phone database\n"); + printf("Available commands: \n"); + printf(" 0 - exit;\n"); + printf(" 1 - add new entry;\n"); + printf(" 2 - print all entries;\n"); + printf(" 3 - find phone number by name;\n"); + printf(" 4 - find name by phone number;\n"); + printf(" 5 - save database.\n"); + + State state = Menu; + while (true) { + switch (state) + { + case Exit: + printf("Exiting...\n"); + disposeDatabase(database); + return true; + + case Error: + disposeDatabase(database); + return false; + + case Menu: + printf("phonedb> "); + state = readCommand(); + break; + + case AddingEntry: + state = addEntryCommand(database); + break; + + case PrintingAllEntries: + state = printDatabaseCommand(database); + break; + + case FindNumberByName: + case FindNameByPhone: + state = Menu; + break; + + case SaveDatabase: + state = saveDatabaseCommand(database, databasePath); + break; + + default: + printf("Error: Unknown state\n"); + disposeDatabase(database); + return false; + } } +} - disposeDatabase(database); +int main(void) { + bool conversationResult = doConversation(); + return conversationResult ? 0 : 1; } diff --git a/homework_4/task_2/personEntry.c b/homework_4/task_2/personEntry.c index 9a1ff39..1aabce3 100644 --- a/homework_4/task_2/personEntry.c +++ b/homework_4/task_2/personEntry.c @@ -16,67 +16,16 @@ bool tryParsePhoneNumber(const char *input, PhoneNumber *phoneNumber) { return false; } - phoneNumber->number = result; + *phoneNumber = result; return true; } -bool tryCreateEntry(PersonEntry **personEntry, const char *personName) { - PersonEntry *entry = malloc(sizeof(PersonEntry)); - - entry->phoneNumbersCount = 0; - entry->phoneNumbersCapacity = 4; - entry->phoneNumbers = malloc(entry->phoneNumbersCapacity * sizeof(PhoneNumber *)); - if (entry->phoneNumbers == NULL) { - *personEntry = NULL; - return false; - } - - char *name = strdup(personName); - if (name == NULL) { - *personEntry = NULL; - return false; - } - entry->personName = name; - - *personEntry = entry; - - return true; -} - -bool tryAddPhoneNumber(PersonEntry *entry, PhoneNumber number) { - if (entry->phoneNumbersCount == entry->phoneNumbersCapacity) { - entry->phoneNumbersCapacity *= 2; - PhoneNumber *numbers = realloc(entry->phoneNumbers, entry->phoneNumbersCapacity * sizeof(PhoneNumber *)); - if (numbers == NULL) { - return false; - } - entry->phoneNumbers = numbers; - } - entry->phoneNumbers[entry->phoneNumbersCount] = number; - ++entry->phoneNumbersCount; - return true; -} - -bool tryAddRawPhoneNumber(PersonEntry *entry, const char *number) { - PhoneNumber phoneNumber; - if (!tryParsePhoneNumber(number, &phoneNumber)) { - return false; - } - if (!tryAddPhoneNumber(entry, phoneNumber)) { - return false; - } - return true; -} - void disposeEntry(PersonEntry *entry) { if (entry == NULL) { return; } - for (int i = 0; i < entry->phoneNumbersCount; ++i) { - free(entry->phoneNumbers[i].number); - } - free(entry->phoneNumbers); - free(entry); + free(entry->personName); + free(entry->phoneNumber); } diff --git a/homework_4/task_2/personEntry.h b/homework_4/task_2/personEntry.h index 0550123..5a23d69 100644 --- a/homework_4/task_2/personEntry.h +++ b/homework_4/task_2/personEntry.h @@ -2,39 +2,19 @@ #include -typedef struct { - char *number; -} PhoneNumber; +typedef char* PhoneNumber; typedef struct { - int phoneNumbersCount, phoneNumbersCapacity; char *personName; - PhoneNumber *phoneNumbers; + PhoneNumber phoneNumber; } PersonEntry; /// @brief Parses phone number from input string /// @param input Input string to be parsed -/// @param phoneNumber Pointer to write result to (can't be `NULL`) +/// @param phoneNumber Pointer to write result to /// @return `true` if parsed successfully, `false` otherwise bool tryParsePhoneNumber(const char *input, PhoneNumber *phoneNumber); -/// @brief Creates empty person entry -/// @param entry Pointer to write result to -/// @return `true` if successful, `false` otherwise (allocation failed) -bool tryCreateEntry(PersonEntry **entry, const char *personName); - -/// @brief Adds phone number to person entry -/// @param entry Pointer to person entry -/// @param number Phone number to add -/// @return `true` if successful, `false` otherwise (allocation failed) -bool tryAddPhoneNumber(PersonEntry *entry, PhoneNumber number); - -/// @brief Adds raw phone number to person entry -/// @param entry Pointer to person entry -/// @param number Raw phone number to add -/// @return `true` if successful, `false` otherwise (allocation failed) -bool tryAddRawPhoneNumber(PersonEntry *entry, const char *number); - /// @brief Disposes person entry and all of its phone numbers /// @param entry Entry to dipose (can be `NULL`) void disposeEntry(PersonEntry *entry); From 8155e54f9d1226d38cd818bee9732134e25bcdc9 Mon Sep 17 00:00:00 2001 From: ilya-krivtsov <180809461+ilya-krivtsov@users.noreply.github.com> Date: Sun, 6 Oct 2024 19:11:44 +0300 Subject: [PATCH 03/10] Added entry search and phone number parsing --- homework_4/task_2/.gitignore | 1 + homework_4/task_2/main.c | 125 ++++++++++++++++++++++++++------ homework_4/task_2/personEntry.c | 61 +++++++++++++++- 3 files changed, 160 insertions(+), 27 deletions(-) create mode 100644 homework_4/task_2/.gitignore diff --git a/homework_4/task_2/.gitignore b/homework_4/task_2/.gitignore new file mode 100644 index 0000000..407e6bb --- /dev/null +++ b/homework_4/task_2/.gitignore @@ -0,0 +1 @@ +phoneDatabase diff --git a/homework_4/task_2/main.c b/homework_4/task_2/main.c index b5e3f09..458099a 100644 --- a/homework_4/task_2/main.c +++ b/homework_4/task_2/main.c @@ -16,6 +16,39 @@ typedef enum { SaveDatabase } State; +char *readLine(const char *prompt) { + char buffer[1024] = { 0 }; + printf("%s", prompt); + + bool overflow = true; + for (int i = 0; i < sizeof(buffer) - 1; ++i) { + char c = getchar(); + if (c == '\n' || c == EOF) { + overflow = false; + break; + } + buffer[i] = c; + } + if (overflow) { + while (getchar() != '\n') {} + } + + return strdup(buffer); +} + +void printEntry(PersonEntry entry) { + printf("%s : %s\n", entry.personName, entry.phoneNumber); +} + +bool tryReadPhoneNumber(const char *prompt, PhoneNumber *phoneNumber) { + char *rawPhoneNumber = readLine(prompt); + if (!tryParsePhoneNumber(rawPhoneNumber, phoneNumber)) { + free(rawPhoneNumber); + return false; + } + return true; +} + State readCommand(void) { int command = -1; if (scanf("%d", &command) != 1) { @@ -41,26 +74,6 @@ State readCommand(void) { } } -char *readLine(const char *prompt) { - char buffer[1024] = { 0 }; - printf("%s", prompt); - - bool overflow = true; - for (int i = 0; i < sizeof(buffer) - 1; ++i) { - char c = getchar(); - if (c == '\n' || c == EOF) { - overflow = false; - break; - } - buffer[i] = c; - } - if (overflow) { - while (getchar() != '\n') {} - } - - return strdup(buffer); -} - State addEntryCommand(Database *database) { if (database->entriesCount == DB_MAX_ENTRIES) { printf("Error: entries slots maxed out\n"); @@ -110,11 +123,57 @@ State addEntryCommand(Database *database) { } State printDatabaseCommand(Database *database) { + if (database->entriesCount == 0) { + printf("Empty database\n"); + } + for (int i = 0; i < database->entriesCount; ++i) { + PersonEntry entry = database->entries[i]; + printEntry(entry); + } + + return Menu; +} + +State findNameByPhoneCommand(Database *database) { + PhoneNumber phoneNumber; + if (!tryReadPhoneNumber("Input phone number: ", &phoneNumber)) { + printf("Incorrect phone number format"); + return Menu; + } + + bool foundAny = false; + printf("Found entries:\n"); for (int i = 0; i < database->entriesCount; ++i) { PersonEntry entry = database->entries[i]; - printf("%s : %s\n", entry.personName, entry.phoneNumber); + if (strstr(entry.phoneNumber, phoneNumber)) { + foundAny = true; + printEntry(entry); + } } + if (!foundAny) { + printf("None\n"); + } + return Menu; +} + +State findNumberByNameCommand(Database *database) { + char *personName = readLine("Input name: "); + + bool foundAny = false; + printf("Found entries:\n"); + for (int i = 0; i < database->entriesCount; ++i) { + PersonEntry entry = database->entries[i]; + if (strstr(entry.personName, personName)) { + foundAny = true; + printEntry(entry); + } + } + + if (!foundAny) { + printf("None\n"); + } + free(personName); return Menu; } @@ -126,9 +185,10 @@ State saveDatabaseCommand(Database *database, const char *path) { } if (!saveDatabase(file, database)) { printf("Error: cannot save database\n"); + } else { + printf("Saved successfully\n"); } fclose(file); - printf("Saved successfully\n"); return Menu; } @@ -141,6 +201,22 @@ bool doConversation(void) { } else { database = loadDatabase(file); fclose(file); + + if (database == NULL) { + printf("Error: cannot load database; create new? (y/n): "); + char choice; + scanf("%c", &choice); + if (choice == 'y') { + database = createDatabase(); + } else { + return false; + } + } + } + + if (database == NULL) { + printf("Error: cannot create database\n"); + return false; } printf("Phone database\n"); @@ -179,8 +255,11 @@ bool doConversation(void) { break; case FindNumberByName: + state = findNumberByNameCommand(database); + break; + case FindNameByPhone: - state = Menu; + state = findNameByPhoneCommand(database); break; case SaveDatabase: diff --git a/homework_4/task_2/personEntry.c b/homework_4/task_2/personEntry.c index 1aabce3..89518fd 100644 --- a/homework_4/task_2/personEntry.c +++ b/homework_4/task_2/personEntry.c @@ -5,19 +5,72 @@ #include bool tryParsePhoneNumber(const char *input, PhoneNumber *phoneNumber) { + if (input == NULL) { + return false; + } if (phoneNumber == NULL) { return false; } - // TODO: actual parsing - char *result = strdup(input); + char buffer[1024] = { 0 }; - if (result == NULL) { + bool hasOpenParen = false, hyphen = false; + + for (int i = 0, j = 0; i < sizeof(buffer) - 1; ++i) { + char c = input[i]; + if (c == '\0') { + break; + } + + if (c == '-') { + // no two consecutive hyphens + if (hyphen) { + return false; + } + hyphen = true; + continue; + } else { + hyphen = false; + } + + if (c == '(') { + if (hasOpenParen) { + return false; + } + hasOpenParen = true; + continue; + } + + if (c == ')') { + hasOpenParen = false; + continue; + } + + if ((c >= '0' && c <= '9') || c == '+') { + buffer[j] = c; + ++j; + continue; + } + + // all other allowed character(s) + if (c == ' ') { + continue;; + } + + // fallback return false; } - *phoneNumber = result; + if (hyphen || hasOpenParen) { + return false; + } + + char *result = strdup(buffer); + if (result == NULL) { + return false; + } + *phoneNumber = result; return true; } From 575f349bf0683ba17ff2c856ca805778f71ebd66 Mon Sep 17 00:00:00 2001 From: ilya-krivtsov <180809461+ilya-krivtsov@users.noreply.github.com> Date: Sun, 6 Oct 2024 23:28:53 +0300 Subject: [PATCH 04/10] Added tests to hw4 task 2 --- homework_4/task_2/test.c | 105 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/homework_4/task_2/test.c b/homework_4/task_2/test.c index f71f08c..58fdc7a 100644 --- a/homework_4/task_2/test.c +++ b/homework_4/task_2/test.c @@ -2,6 +2,111 @@ #define CTEST_SEGFAULT #include "../../ctest/ctest.h" +#include "database.h" +#include "personEntry.h" + int main(int argc, const char *argv[]) { return ctest_main(argc, argv); } + +#define NAME_1 "Test Name" +#define PHONE_1 "+12345678900" + +#define NAME_2 "Test Name 2" +#define PHONE_2 "+98765432100" + +#define SAVE_PATH "test_tmp_db" + +CTEST_DATA(databaseTests) { + Database *database; +}; + +CTEST_SETUP(databaseTests) { + data->database = createDatabase(); + ASSERT_NOT_NULL(data->database); +} + +CTEST_TEARDOWN(databaseTests) { + disposeDatabase(data->database); +} + +CTEST2(databaseTests, addEntryTest) { + PersonEntry entry = { + .personName = strdup(NAME_1), + .phoneNumber = strdup(PHONE_1) + }; + + ASSERT_TRUE(addEntry(data->database, entry)); + ASSERT_STR(data->database->entries[data->database->entriesCount - 1].personName, NAME_1); + ASSERT_STR(data->database->entries[data->database->entriesCount - 1].phoneNumber, PHONE_1); +} + +CTEST2(databaseTests, addEntryTest2) { + PersonEntry entry = { + .personName = strdup(NAME_2), + .phoneNumber = strdup(PHONE_2) + }; + + ASSERT_TRUE(addEntry(data->database, entry)); + ASSERT_STR(data->database->entries[data->database->entriesCount - 1].personName, NAME_2); + ASSERT_STR(data->database->entries[data->database->entriesCount - 1].phoneNumber, PHONE_2); +} + +CTEST2(databaseTests, saveDatabaseTest) { + FILE *file = fopen(SAVE_PATH, "w"); + ASSERT_NOT_NULL(file); + + ASSERT_TRUE(saveDatabase(file, data->database)); + ASSERT_EQUAL(fclose(file), 0); +} + +CTEST2(databaseTests, loadDatabaseTest) { + FILE *file = fopen(SAVE_PATH, "r"); + ASSERT_NOT_NULL(file); + + Database *database2 = loadDatabase(file); + + ASSERT_NOT_NULL(database2); + ASSERT_EQUAL(fclose(file), 0); + + ASSERT_EQUAL(data->database->entriesCount, database2->entriesCount); + for (int i = 0; i < data->database->entriesCount; ++i) { + PersonEntry entry = data->database->entries[i], + entry2 = database2->entries[i]; + ASSERT_STR(entry.personName, entry2.personName); + ASSERT_STR(entry.phoneNumber, entry2.phoneNumber); + } + + disposeDatabase(database2); + ASSERT_EQUAL(remove(SAVE_PATH), 0); +} + +CTEST(phoneParsingTest, incorrectPhoneTest) { + PhoneNumber phoneNumber; + ASSERT_FALSE(tryParsePhoneNumber("0--0", &phoneNumber)); +} + +CTEST(phoneParsingTest, incorrectPhoneTest2) { + PhoneNumber phoneNumber; + ASSERT_FALSE(tryParsePhoneNumber("+1 ((12))-14", &phoneNumber)); +} + +CTEST(phoneParsingTest, incorrectPhoneTest3) { + PhoneNumber phoneNumber; + ASSERT_FALSE(tryParsePhoneNumber("834-14-", &phoneNumber)); +} + +CTEST(phoneParsingTest, correctPhoneTest) { + PhoneNumber phoneNumber; + ASSERT_TRUE(tryParsePhoneNumber("135-67-54", &phoneNumber)); +} + +CTEST(phoneParsingTest, correctPhoneTest2) { + PhoneNumber phoneNumber; + ASSERT_TRUE(tryParsePhoneNumber("(135) 67 54", &phoneNumber)); +} + +CTEST(phoneParsingTest, correctPhoneTest3) { + PhoneNumber phoneNumber; + ASSERT_TRUE(tryParsePhoneNumber("+5 (432) 234 67-54", &phoneNumber)); +} From 63f3e655d1115a42175c6904b02f6657942a9e94 Mon Sep 17 00:00:00 2001 From: ilya-krivtsov <180809461+ilya-krivtsov@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:53:48 +0300 Subject: [PATCH 05/10] Added cast to int for sizeof() (hw4 task 2) --- homework_4/task_2/main.c | 2 +- homework_4/task_2/personEntry.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homework_4/task_2/main.c b/homework_4/task_2/main.c index 458099a..9da94fa 100644 --- a/homework_4/task_2/main.c +++ b/homework_4/task_2/main.c @@ -21,7 +21,7 @@ char *readLine(const char *prompt) { printf("%s", prompt); bool overflow = true; - for (int i = 0; i < sizeof(buffer) - 1; ++i) { + for (int i = 0; i < (int)sizeof(buffer) - 1; ++i) { char c = getchar(); if (c == '\n' || c == EOF) { overflow = false; diff --git a/homework_4/task_2/personEntry.c b/homework_4/task_2/personEntry.c index 89518fd..d9ce780 100644 --- a/homework_4/task_2/personEntry.c +++ b/homework_4/task_2/personEntry.c @@ -16,7 +16,7 @@ bool tryParsePhoneNumber(const char *input, PhoneNumber *phoneNumber) { bool hasOpenParen = false, hyphen = false; - for (int i = 0, j = 0; i < sizeof(buffer) - 1; ++i) { + for (int i = 0, j = 0; i < (int)sizeof(buffer) - 1; ++i) { char c = input[i]; if (c == '\0') { break; From 2090b0bb18031873d3f83c5de6362404a7ea12cf Mon Sep 17 00:00:00 2001 From: ilya-krivtsov <180809461+ilya-krivtsov@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:55:29 +0300 Subject: [PATCH 06/10] Use scanf return value (hw4 task 2) --- homework_4/task_2/main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homework_4/task_2/main.c b/homework_4/task_2/main.c index 9da94fa..d9cf02d 100644 --- a/homework_4/task_2/main.c +++ b/homework_4/task_2/main.c @@ -204,8 +204,10 @@ bool doConversation(void) { if (database == NULL) { printf("Error: cannot load database; create new? (y/n): "); - char choice; - scanf("%c", &choice); + char choice = 'n'; + if (scanf("%c", &choice) != 1) { + return false; + } if (choice == 'y') { database = createDatabase(); } else { From da6301b650b233b4f43d68d43e04541dba1da98c Mon Sep 17 00:00:00 2001 From: ilya-krivtsov <180809461+ilya-krivtsov@users.noreply.github.com> Date: Sat, 23 Nov 2024 23:08:45 +0300 Subject: [PATCH 07/10] Make tests able to execute in any order (hw4 task 2) --- homework_4/task_2/test.c | 86 +++++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 36 deletions(-) diff --git a/homework_4/task_2/test.c b/homework_4/task_2/test.c index 58fdc7a..92975a2 100644 --- a/homework_4/task_2/test.c +++ b/homework_4/task_2/test.c @@ -17,68 +17,82 @@ int main(int argc, const char *argv[]) { #define SAVE_PATH "test_tmp_db" -CTEST_DATA(databaseTests) { - Database *database; -}; - -CTEST_SETUP(databaseTests) { - data->database = createDatabase(); - ASSERT_NOT_NULL(data->database); -} - -CTEST_TEARDOWN(databaseTests) { - disposeDatabase(data->database); +Database *createNewDatabase(void) { + Database *db = createDatabase(); + ASSERT_NOT_NULL(db); + return db; } -CTEST2(databaseTests, addEntryTest) { +void assertAddEntry(Database *database, const char *personName, const PhoneNumber phoneNumber) { PersonEntry entry = { - .personName = strdup(NAME_1), - .phoneNumber = strdup(PHONE_1) + .personName = strdup(personName), + .phoneNumber = strdup(phoneNumber) }; - ASSERT_TRUE(addEntry(data->database, entry)); - ASSERT_STR(data->database->entries[data->database->entriesCount - 1].personName, NAME_1); - ASSERT_STR(data->database->entries[data->database->entriesCount - 1].phoneNumber, PHONE_1); + ASSERT_TRUE(addEntry(database, entry)); + ASSERT_STR(database->entries[database->entriesCount - 1].personName, personName); + ASSERT_STR(database->entries[database->entriesCount - 1].phoneNumber, phoneNumber); } -CTEST2(databaseTests, addEntryTest2) { - PersonEntry entry = { - .personName = strdup(NAME_2), - .phoneNumber = strdup(PHONE_2) - }; - - ASSERT_TRUE(addEntry(data->database, entry)); - ASSERT_STR(data->database->entries[data->database->entriesCount - 1].personName, NAME_2); - ASSERT_STR(data->database->entries[data->database->entriesCount - 1].phoneNumber, PHONE_2); +void assertAddNamesAndPhones(Database *db) { + assertAddEntry(db, NAME_1, PHONE_1); + assertAddEntry(db, NAME_2, PHONE_2); } -CTEST2(databaseTests, saveDatabaseTest) { +void assertSave(Database *db) { FILE *file = fopen(SAVE_PATH, "w"); ASSERT_NOT_NULL(file); - ASSERT_TRUE(saveDatabase(file, data->database)); + ASSERT_TRUE(saveDatabase(file, db)); ASSERT_EQUAL(fclose(file), 0); } -CTEST2(databaseTests, loadDatabaseTest) { +Database *loadTestDatabase(void) { FILE *file = fopen(SAVE_PATH, "r"); ASSERT_NOT_NULL(file); - Database *database2 = loadDatabase(file); + Database *db = loadDatabase(file); - ASSERT_NOT_NULL(database2); + ASSERT_NOT_NULL(db); ASSERT_EQUAL(fclose(file), 0); + return db; +} + +CTEST(databaseTests, addEntriesTest) { + Database *db = createNewDatabase(); + assertAddNamesAndPhones(db); + disposeDatabase(db); +} - ASSERT_EQUAL(data->database->entriesCount, database2->entriesCount); - for (int i = 0; i < data->database->entriesCount; ++i) { - PersonEntry entry = data->database->entries[i], - entry2 = database2->entries[i]; +CTEST(databaseTests, saveDatabaseTest) { + Database *db = createNewDatabase(); + + assertAddNamesAndPhones(db); + assertSave(db); + + disposeDatabase(db); +} + +CTEST(databaseTests, loadDatabaseTest) { + Database *db = createNewDatabase(); + + assertAddNamesAndPhones(db); + assertSave(db); + + Database *db2 = loadTestDatabase(); + + ASSERT_EQUAL(db->entriesCount, db2->entriesCount); + for (int i = 0; i < db->entriesCount; ++i) { + PersonEntry entry = db->entries[i], + entry2 = db2->entries[i]; ASSERT_STR(entry.personName, entry2.personName); ASSERT_STR(entry.phoneNumber, entry2.phoneNumber); } - disposeDatabase(database2); + disposeDatabase(db2); ASSERT_EQUAL(remove(SAVE_PATH), 0); + + disposeDatabase(db); } CTEST(phoneParsingTest, incorrectPhoneTest) { From 9a1a8e7a2fededcfbd36156722a1f3cf0c7b977d Mon Sep 17 00:00:00 2001 From: ilya-krivtsov <180809461+ilya-krivtsov@users.noreply.github.com> Date: Tue, 10 Dec 2024 03:10:33 +0300 Subject: [PATCH 08/10] Added stdbool.h include (hw4 task 2) --- homework_4/task_2/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/homework_4/task_2/main.c b/homework_4/task_2/main.c index d9cf02d..905f9b9 100644 --- a/homework_4/task_2/main.c +++ b/homework_4/task_2/main.c @@ -1,3 +1,4 @@ +#include #include #include #include From a2c595e99178304427a78f4c3b9ad8f6fe48b7aa Mon Sep 17 00:00:00 2001 From: ilya-krivtsov <180809461+ilya-krivtsov@users.noreply.github.com> Date: Tue, 10 Dec 2024 03:11:02 +0300 Subject: [PATCH 09/10] Removed 'PRIVATE' dependency modifier (hw4 task 2) --- homework_4/task_2/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homework_4/task_2/CMakeLists.txt b/homework_4/task_2/CMakeLists.txt index 8513e27..fdaea50 100644 --- a/homework_4/task_2/CMakeLists.txt +++ b/homework_4/task_2/CMakeLists.txt @@ -6,4 +6,4 @@ add_executable(${PROJECT_NAME} main.c) target_link_libraries(${PROJECT_NAME} phoneBook) add_executable(${PROJECT_NAME}_test test.c) -target_link_libraries(${PROJECT_NAME}_test PRIVATE phoneBook) +target_link_libraries(${PROJECT_NAME}_test phoneBook) From 7bcf79b7d184bcdcd2471cca42bb784c54a553d9 Mon Sep 17 00:00:00 2001 From: ilya-krivtsov <180809461+ilya-krivtsov@users.noreply.github.com> Date: Sun, 22 Dec 2024 22:17:37 +0300 Subject: [PATCH 10/10] Added task name to README (hw4 task 2) --- homework_4/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homework_4/README.md b/homework_4/README.md index ca10cb8..c9d997e 100644 --- a/homework_4/README.md +++ b/homework_4/README.md @@ -1 +1,3 @@ # Homework 4 + +[Task 2. Phone database](/homework_4/task_2)