7#include <ascii-chat/options/schema.h>
8#include <ascii-chat/options/validation.h>
9#include <ascii-chat/options/options.h>
10#include <ascii-chat/options/builder.h>
11#include <ascii-chat/options/presets.h>
12#include <ascii-chat/util/path.h>
13#include <ascii-chat/video/palette.h>
14#include <ascii-chat/platform/terminal.h>
15#include <ascii-chat/platform/system.h>
16#include <ascii-chat/common.h>
32static config_option_metadata_t *g_dynamic_schema = NULL;
33static size_t g_dynamic_schema_count = 0;
34static bool g_schema_built =
false;
35static char **g_dynamic_strings = NULL;
36static size_t g_dynamic_strings_count = 0;
37static size_t g_dynamic_strings_capacity = 0;
46static void str_tolower(
char *str) {
48 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid string for str_tolower");
52 *str = (char)tolower((
unsigned char)*str);
59static void dashes_to_underscores(
char *str) {
61 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid string for dashes_to_underscores");
75static char *generate_toml_key(
const char *group,
const char *field_name,
char *buffer,
size_t buffer_size) {
76 if (!group || !field_name || !buffer ||
buffer_size == 0) {
77 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid arguments for generate_toml_key");
82 size_t group_len = strlen(group);
84 SET_ERRNO(ERROR_CONFIG,
"Group name is too long for buffer");
87 memcpy(buffer, group, group_len + 1);
92 SET_ERRNO(ERROR_CONFIG,
"Group name is too long for buffer");
95 buffer[group_len] =
'.';
98 size_t field_len = strlen(field_name);
100 SET_ERRNO(ERROR_INVALID_STATE,
"Field name is too long for buffer");
103 memcpy(buffer + group_len + 1, field_name, field_len + 1);
104 dashes_to_underscores(buffer + group_len + 1);
105 str_tolower(buffer + group_len + 1);
114static char *generate_cli_flag(
const char *long_name,
char *buffer,
size_t buffer_size) {
116 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid arguments for generate_cli_flag");
122 size_t name_len = strlen(long_name);
124 SET_ERRNO(ERROR_CONFIG,
"Long name is too long for buffer");
127 memcpy(buffer + 2, long_name, name_len + 1);
134static size_t get_field_size(option_type_t type,
size_t offset) {
138 case OPTION_TYPE_BOOL:
140 case OPTION_TYPE_INT:
142 if (offset == offsetof(options_t, webcam_index)) {
143 return sizeof(
unsigned short int);
146 if (offset == offsetof(options_t, color_mode)) {
147 return sizeof(terminal_color_mode_t);
148 }
else if (offset == offsetof(options_t, render_mode)) {
149 return sizeof(render_mode_t);
150 }
else if (offset == offsetof(options_t, palette_type)) {
151 return sizeof(palette_type_t);
154 case OPTION_TYPE_DOUBLE:
156 if (offset == offsetof(options_t, microphone_sensitivity)) {
157 return sizeof(float);
158 }
else if (offset == offsetof(options_t, speakers_volume)) {
159 return sizeof(float);
163 return sizeof(double);
164 case OPTION_TYPE_STRING:
166 return OPTIONS_BUFF_SIZE;
167 case OPTION_TYPE_CALLBACK:
169 if (offset == offsetof(options_t, log_file) || offset == offsetof(options_t, port) ||
170 offset == offsetof(options_t, palette_custom) || offset == offsetof(options_t, yt_dlp_options)) {
171 return OPTIONS_BUFF_SIZE;
173 if (offset == offsetof(options_t, media_seek_timestamp)) {
174 return sizeof(double);
176 if (offset == offsetof(options_t, microphone_sensitivity) || offset == offsetof(options_t, speakers_volume)) {
177 return sizeof(float);
179 if (offset == offsetof(options_t, verbose_level)) {
180 return sizeof(
unsigned short int);
197static bool should_add_descriptor(
const option_descriptor_t *desc,
const option_descriptor_t **existing,
size_t count) {
198 if (!desc || desc->type == OPTION_TYPE_ACTION) {
203 for (
size_t i = 0; i < count; i++) {
204 if (existing[i] && existing[i]->offset == desc->offset) {
214static char *store_dynamic_string(
const char *str) {
220 if (g_dynamic_strings_count >= g_dynamic_strings_capacity) {
221 size_t new_capacity = g_dynamic_strings_capacity == 0 ? 64 : g_dynamic_strings_capacity * 2;
222 char **new_strings = SAFE_REALLOC(g_dynamic_strings, new_capacity *
sizeof(
char *),
char **);
224 SET_ERRNO(ERROR_MEMORY,
"Failed to allocate new strings");
227 g_dynamic_strings = new_strings;
228 g_dynamic_strings_capacity = new_capacity;
234 SET_ERRNO(ERROR_MEMORY,
"Failed to allocate new string");
238 g_dynamic_strings[g_dynamic_strings_count++] = stored;
244 if (g_dynamic_schema) {
245 SAFE_FREE(g_dynamic_schema);
246 g_dynamic_schema = NULL;
247 g_dynamic_schema_count = 0;
251 if (g_dynamic_strings) {
252 for (
size_t i = 0; i < g_dynamic_strings_count; i++) {
253 SAFE_FREE(g_dynamic_strings[i]);
255 SAFE_FREE(g_dynamic_strings);
256 g_dynamic_strings = NULL;
257 g_dynamic_strings_count = 0;
258 g_dynamic_strings_capacity = 0;
263 const option_descriptor_t *all_descriptors[256] = {0};
264 size_t descriptor_count = 0;
267 for (
size_t cfg_idx = 0; cfg_idx < num_configs; cfg_idx++) {
268 const options_config_t *config = configs[cfg_idx];
273 for (
size_t i = 0; i < config->num_descriptors && descriptor_count < 256; i++) {
274 const option_descriptor_t *desc = &config->descriptors[i];
275 if (should_add_descriptor(desc, all_descriptors, descriptor_count)) {
276 all_descriptors[descriptor_count++] = desc;
282 g_dynamic_schema = SAFE_MALLOC(descriptor_count *
sizeof(config_option_metadata_t), config_option_metadata_t *);
283 if (!g_dynamic_schema) {
284 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate dynamic schema");
288 g_dynamic_schema_count = 0;
289 char toml_key_buffer[BUFFER_SIZE_SMALL];
290 char cli_flag_buffer[256];
291 char category_buffer[64];
293 for (
size_t i = 0; i < descriptor_count; i++) {
294 const option_descriptor_t *desc = all_descriptors[i];
295 if (!desc || !desc->long_name || !desc->group) {
299 config_option_metadata_t *meta = &g_dynamic_schema[g_dynamic_schema_count++];
301 memset(meta, 0,
sizeof(*meta));
304 meta->type = desc->type;
307 SAFE_STRNCPY(category_buffer, desc->group,
sizeof(category_buffer) - 1);
308 category_buffer[
sizeof(category_buffer) - 1] =
'\0';
309 str_tolower(category_buffer);
313 if (!generate_toml_key(category_buffer, desc->long_name, toml_key_buffer,
sizeof(toml_key_buffer))) {
314 g_dynamic_schema_count--;
319 meta->toml_key = store_dynamic_string(toml_key_buffer);
320 meta->category = store_dynamic_string(category_buffer);
321 if (!meta->toml_key || !meta->category) {
323 g_dynamic_schema_count--;
328 if (generate_cli_flag(desc->long_name, cli_flag_buffer,
sizeof(cli_flag_buffer))) {
329 meta->cli_flag = store_dynamic_string(cli_flag_buffer);
330 if (!meta->cli_flag) {
334 meta->cli_flag = NULL;
338 meta->context = OPTION_CONTEXT_BOTH;
339 meta->field_offset = desc->offset;
340 meta->field_size = get_field_size(meta->type, desc->offset);
342 meta->validate_fn = desc->validate;
344 meta->parse_fn = desc->parse_fn;
345 meta->description = desc->help_text;
349 meta->mode_bitmask = desc->mode_bitmask;
352 meta->mode_default_getter = desc->mode_default_getter;
356 memset(&meta->constraints, 0,
sizeof(meta->constraints));
358 if (desc && desc->metadata.numeric_range.max > 0) {
359 meta->constraints.int_range.min = desc->metadata.numeric_range.min;
360 meta->constraints.int_range.max = desc->metadata.numeric_range.max;
364 g_schema_built =
true;
374 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid arguments for config_schema_get_by_toml_key");
379 if (!g_schema_built || !g_dynamic_schema) {
380 SET_ERRNO(ERROR_INVALID_STATE,
"Schema not built");
384 for (
size_t i = 0; i < g_dynamic_schema_count; i++) {
385 if (g_dynamic_schema[i].toml_key && strcmp(g_dynamic_schema[i].toml_key, toml_key) == 0) {
386 return &g_dynamic_schema[i];
394 static const config_option_metadata_t *results[64];
395 size_t result_count = 0;
405 if (!g_schema_built || !g_dynamic_schema) {
409 SET_ERRNO(ERROR_INVALID_STATE,
"Schema not built");
413 for (
size_t i = 0; i < g_dynamic_schema_count && result_count < 64; i++) {
414 if (g_dynamic_schema[i].category && strcmp(g_dynamic_schema[i].category, category) == 0) {
415 results[result_count++] = &g_dynamic_schema[i];
420 *count = result_count;
423 return (result_count > 0) ? results : NULL;
428 if (!g_schema_built || !g_dynamic_schema) {
432 SET_ERRNO(ERROR_INVALID_STATE,
"Schema not built");
437 *count = g_dynamic_schema_count;
439 return g_dynamic_schema;
447 if (!g_schema_built) {
452 if (g_dynamic_strings) {
453 for (
size_t i = 0; i < g_dynamic_strings_count; i++) {
454 SAFE_FREE(g_dynamic_strings[i]);
456 SAFE_FREE(g_dynamic_strings);
457 g_dynamic_strings = NULL;
458 g_dynamic_strings_count = 0;
459 g_dynamic_strings_capacity = 0;
463 if (g_dynamic_schema) {
464 SAFE_FREE(g_dynamic_schema);
465 g_dynamic_schema = NULL;
466 g_dynamic_schema_count = 0;
469 g_schema_built =
false;
int buffer_size
Size of circular buffer.
const config_option_metadata_t ** config_schema_get_by_category(const char *category, size_t *count)
void config_schema_destroy(void)
const config_option_metadata_t * config_schema_get_all(size_t *count)
asciichat_error_t config_schema_build_from_configs(const options_config_t **configs, size_t num_configs)
const config_option_metadata_t * config_schema_get_by_toml_key(const char *toml_key)