trainlog/tui/src/catalog.c

279 lines
7.4 KiB
C

/**
* @file catalog.c
* @brief Frozen Trainlog v1 exercise-name normalization.
*/
#include "trainlog/catalog.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utf8proc.h>
#include "trainlog/id.h"
static bool codepoint_is_whitespace(utf8proc_int32_t codepoint)
{
utf8proc_category_t category = utf8proc_category(codepoint);
if (codepoint == '\t' ||
codepoint == '\n' ||
codepoint == '\v' ||
codepoint == '\f' ||
codepoint == '\r') {
return true;
}
return category == UTF8PROC_CATEGORY_ZS ||
category == UTF8PROC_CATEGORY_ZL ||
category == UTF8PROC_CATEGORY_ZP;
}
TrainlogStatus trainlog_catalog_normalize_name(
const char *input,
char *output,
size_t output_size
)
{
utf8proc_uint8_t *mapped = NULL;
utf8proc_ssize_t mapped_length;
utf8proc_ssize_t offset = 0;
size_t output_used = 0U;
bool pending_space = false;
bool wrote_content = false;
if (input == NULL ||
output == NULL ||
output_size == 0U) {
return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
output[0] = '\0';
mapped_length = utf8proc_map(
(const utf8proc_uint8_t *)input,
0,
&mapped,
UTF8PROC_STABLE |
UTF8PROC_COMPOSE |
UTF8PROC_CASEFOLD |
UTF8PROC_NULLTERM
);
if (mapped_length < 0 || mapped == NULL) {
free(mapped);
return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
while (offset < mapped_length) {
utf8proc_int32_t codepoint = 0;
utf8proc_ssize_t consumed;
utf8proc_uint8_t encoded[4];
utf8proc_ssize_t encoded_length;
consumed = utf8proc_iterate(
mapped + offset,
mapped_length - offset,
&codepoint
);
if (consumed <= 0) {
free(mapped);
return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
offset += consumed;
if (codepoint_is_whitespace(codepoint)) {
if (wrote_content) {
pending_space = true;
}
continue;
}
if (pending_space) {
if (output_used + 1U >= output_size) {
free(mapped);
return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
output[output_used++] = ' ';
pending_space = false;
}
encoded_length = utf8proc_encode_char(codepoint, encoded);
if (encoded_length <= 0) {
free(mapped);
return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
if (output_used + (size_t)encoded_length >= output_size) {
free(mapped);
return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
(void)memcpy(
output + output_used,
encoded,
(size_t)encoded_length
);
output_used += (size_t)encoded_length;
wrote_content = true;
}
free(mapped);
if (!wrote_content) {
output[0] = '\0';
return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
output[output_used] = '\0';
return TRAINLOG_STATUS_OK;
}
TrainlogStatus trainlog_catalog_create_exercise_profiled(
TrainlogDatabase *database,
const char *name,
TrainlogTrackingMode tracking_mode,
TrainlogRecordingMode recording_mode,
TrainlogExerciseDataFields data_fields,
TrainlogExercise *output_exercise
)
{
char normalized[(TRAINLOG_NAME_MAX * 4U) + 1U];
char exercise_id[TRAINLOG_GENERATED_ID_CAPACITY];
TrainlogStatus status;
if (database == NULL ||
name == NULL ||
output_exercise == NULL ||
strlen(name) > TRAINLOG_NAME_MAX) {
return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
status = trainlog_catalog_normalize_name(
name,
normalized,
sizeof(normalized)
);
if (status != TRAINLOG_STATUS_OK) {
return status;
}
status = trainlog_id_generate(
"ex",
exercise_id,
sizeof(exercise_id)
);
if (status != TRAINLOG_STATUS_OK) {
return status;
}
status = trainlog_database_insert_exercise_profiled(
database,
exercise_id,
name,
normalized,
tracking_mode,
recording_mode,
data_fields
);
if (status != TRAINLOG_STATUS_OK) {
return status;
}
(void)memset(
output_exercise,
0,
sizeof(*output_exercise)
);
(void)snprintf(
output_exercise->exercise_id,
sizeof(output_exercise->exercise_id),
"%s",
exercise_id
);
(void)snprintf(
output_exercise->name,
sizeof(output_exercise->name),
"%s",
name
);
output_exercise->tracking_mode = tracking_mode;
output_exercise->recording_mode = recording_mode;
output_exercise->data_fields = data_fields;
return TRAINLOG_STATUS_OK;
}
TrainlogStatus trainlog_catalog_create_exercise_profiled_with_zones(
TrainlogDatabase *database,
const char *name,
TrainlogTrackingMode tracking_mode,
TrainlogRecordingMode recording_mode,
TrainlogExerciseDataFields data_fields,
const char *primary_zone_id,
const char *const *secondary_zone_ids,
size_t secondary_count,
TrainlogExercise *output_exercise
)
{
char normalized[(TRAINLOG_NAME_MAX * 4U) + 1U];
char exercise_id[TRAINLOG_GENERATED_ID_CAPACITY];
TrainlogStatus status;
if (database == NULL || name == NULL || output_exercise == NULL ||
strlen(name) > TRAINLOG_NAME_MAX) return TRAINLOG_STATUS_INVALID_ARGUMENT;
status = trainlog_catalog_normalize_name(name, normalized, sizeof(normalized));
if (status != TRAINLOG_STATUS_OK) return status;
status = trainlog_id_generate("ex", exercise_id, sizeof(exercise_id));
if (status != TRAINLOG_STATUS_OK) return status;
status = trainlog_database_begin(database);
if (status != TRAINLOG_STATUS_OK) return status;
status = trainlog_database_insert_exercise_profiled(database, exercise_id,
name, normalized, tracking_mode, recording_mode, data_fields);
if (status == TRAINLOG_STATUS_OK) {
status = trainlog_database_replace_exercise_body_zones(database,
exercise_id, primary_zone_id, secondary_zone_ids, secondary_count);
}
if (status == TRAINLOG_STATUS_OK) {
status = trainlog_database_commit(database);
if (status != TRAINLOG_STATUS_OK) {
(void)trainlog_database_rollback(database);
}
} else {
(void)trainlog_database_rollback(database);
}
if (status != TRAINLOG_STATUS_OK) return status;
(void)memset(output_exercise, 0, sizeof(*output_exercise));
(void)snprintf(output_exercise->exercise_id,
sizeof(output_exercise->exercise_id), "%s", exercise_id);
(void)snprintf(output_exercise->name,
sizeof(output_exercise->name), "%s", name);
output_exercise->tracking_mode = tracking_mode;
output_exercise->recording_mode = recording_mode;
output_exercise->data_fields = data_fields;
return TRAINLOG_STATUS_OK;
}
TrainlogStatus trainlog_catalog_create_exercise(
TrainlogDatabase *database,
const char *name,
TrainlogTrackingMode tracking_mode,
TrainlogExercise *output_exercise
)
{
return trainlog_catalog_create_exercise_profiled(
database,
name,
tracking_mode,
TRAINLOG_RECORDING_SETS,
0U,
output_exercise
);
}