sorm/sorm.h

553 lines
15 KiB
C
Raw Normal View History

2024-11-22 13:45:03 +00:00
#ifndef SORM_H
#define SORM_H
2024-11-22 14:51:47 +00:00
#include "str.h"
#include <ctype.h>
2024-11-22 13:45:03 +00:00
#include <rlib.h>
#include <sqlite3.h>
2024-11-22 14:51:47 +00:00
#include <stdarg.h>
#include <stdbool.h>
2024-11-22 13:45:03 +00:00
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
sqlite3 *db;
sqlite3_stmt *stmt;
2024-11-22 14:51:47 +00:00
char *sorm_last_query = NULL;
char *sorm_last_query_expanded = NULL;
2024-11-22 13:45:03 +00:00
nsecs_t _sorm_query_start = 0;
nsecs_t _sorm_query_end = 0;
nsecs_t _sorm_query_duration = 0;
nsecs_t _sorm_result_format_start = 0;
nsecs_t _sorm_result_format_end = 0;
nsecs_t _sorm_result_format_duration = 0;
int64_t sorm_row_count = 0;
typedef struct sorm_t {
2024-11-22 14:51:47 +00:00
sqlite3 *conn;
2024-11-22 13:45:03 +00:00
int current_row;
int current_column;
2024-11-22 14:51:47 +00:00
char *csv;
2024-11-22 13:45:03 +00:00
nsecs_t time_query_start;
nsecs_t time_query_end;
nsecs_t time_query_duration;
nsecs_t time_result_format_start;
nsecs_t time_result_format_end;
nsecs_t time_result_format_duration;
} sorm_t;
2024-11-22 14:51:47 +00:00
typedef char *sorm_pk;
typedef char *sorm_int;
typedef char *sorm_ptr;
typedef unsigned char *sorm_str;
2024-11-22 13:45:03 +00:00
typedef double sorm_double;
typedef double sorm_float;
typedef bool sorm_bool;
2024-11-22 14:51:47 +00:00
int sormc(char *path);
2024-11-22 13:45:03 +00:00
void sormd(int db);
2024-11-22 14:51:47 +00:00
char *sormpt(char *sql, int number);
unsigned int sormcq(char *sql, char *out);
unsigned int sormpc(char *sql);
sqlite3_stmt *sormb(sorm_t *db, char *sql, ...);
sorm_ptr sormq(int db, char *sql, ...);
char *sorm_csvc(int db, sqlite3_stmt *stmt);
char *sorm_csvd(int db, sqlite3_stmt *stmt);
char *sorm_csv(int db, sqlite3_stmt *stmt);
2024-11-22 13:45:03 +00:00
typedef enum sorm_query_t {
SORM_UNKNOWN = 0,
SORM_SELECT = 1,
SORM_INSERT = 2,
SORM_UPDATE = 3,
SORM_DELETE = 4,
SORM_CREATE = 5
} sorm_query_t;
const int sorm_null = -1337;
2024-11-22 14:51:47 +00:00
sorm_t **sorm_instances = NULL;
2024-11-22 13:45:03 +00:00
int sorm_instance_count = 0;
2024-11-22 14:51:47 +00:00
int sormc(char *path) {
2024-11-22 13:45:03 +00:00
// sorm connect
printf("HIERR\n");
sorm_instance_count++;
sorm_instance_count++;
2024-11-22 14:51:47 +00:00
sorm_instances = realloc(sorm_instances, sorm_instance_count * sizeof(sorm_t *) + sorm_instance_count * sizeof(sorm_t));
2024-11-22 13:45:03 +00:00
printf("HIERR\n");
2024-11-22 14:51:47 +00:00
sorm_t *db = &sorm_instances[sorm_instance_count - 1];
2024-11-22 13:45:03 +00:00
printf("HIERR\n");
db->conn = NULL;
printf("HIERR\n");
db->csv = NULL;
db->current_column = 0;
db->current_row = 0;
db->time_query_duration = 0;
db->time_query_end = 0;
db->time_query_start = 0;
db->time_result_format_duration = 0;
db->time_result_format_end = 0;
db->time_result_format_start = 0;
2024-11-22 14:51:47 +00:00
if (sqlite3_open(path, &db->conn)) {
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db->conn));
return 0;
2024-11-22 13:45:03 +00:00
}
printf("DONE!\n");
return sorm_instance_count;
}
2024-11-22 14:51:47 +00:00
sorm_t *sormg(int ptr) { return &sorm_instances[ptr - 1]; }
2024-11-22 13:45:03 +00:00
2024-11-22 14:51:47 +00:00
char *sormgcsv(int ptr) {
2024-11-22 13:45:03 +00:00
/* sorm get csv*/
2024-11-22 14:51:47 +00:00
sorm_t *db = sormg(ptr);
2024-11-22 13:45:03 +00:00
return db->csv;
}
2024-11-22 14:51:47 +00:00
void sormd(int sorm) {
sorm_t *db = sormg(sorm);
if (sqlite3_close(db->conn)) {
2024-11-22 13:45:03 +00:00
fprintf(stderr, "Error closing database: %s\n", sqlite3_errmsg(db->conn));
}
2024-11-22 14:51:47 +00:00
if (sorm_last_query) {
2024-11-22 13:45:03 +00:00
free(sorm_last_query);
sorm_last_query = NULL;
}
2024-11-22 14:51:47 +00:00
if (sorm_last_query_expanded) {
2024-11-22 13:45:03 +00:00
free(sorm_last_query_expanded);
sorm_last_query_expanded = NULL;
}
}
2024-11-22 14:51:47 +00:00
char *sormpt(char *sql, int number) {
2024-11-22 13:45:03 +00:00
// param type
2024-11-22 14:51:47 +00:00
char *sqlp = sql;
char *result = NULL;
2024-11-22 13:45:03 +00:00
int index = 0;
2024-11-22 14:51:47 +00:00
while (*sqlp) {
if (*sqlp == '%' || *sqlp == '?') {
2024-11-22 13:45:03 +00:00
sqlp++;
2024-11-22 14:51:47 +00:00
switch (*sqlp) {
case 'f':
result = "double";
break;
case 's':
result = "text";
break;
case 'd':
result = "int";
break;
case 'b':
result = "blob";
break;
default:
result = "?";
break;
2024-11-22 13:45:03 +00:00
}
sqlp++;
index++;
}
2024-11-22 14:51:47 +00:00
if (index == number) {
2024-11-22 13:45:03 +00:00
return result;
}
2024-11-22 14:51:47 +00:00
if (*sqlp)
2024-11-22 13:45:03 +00:00
sqlp++;
}
2024-11-22 14:51:47 +00:00
if (number > index) {
2024-11-22 13:45:03 +00:00
printf("RETURNED\n");
return NULL;
}
return NULL;
}
2024-11-22 14:51:47 +00:00
unsigned int sormcq(char *sql, char *out) {
2024-11-22 13:45:03 +00:00
// clean query
// converts %s %i parameters to ?
2024-11-22 14:51:47 +00:00
2024-11-22 13:45:03 +00:00
unsigned int count = 0;
2024-11-22 14:51:47 +00:00
while (*sql) {
if (*sql != '%' && *sql != '?')
*out = *sql;
else {
2024-11-22 13:45:03 +00:00
count++;
sql++;
*out = '?';
}
out++;
sql++;
}
*out = 0;
return count;
}
2024-11-22 14:51:47 +00:00
unsigned int sormpc(char *sql) {
2024-11-22 13:45:03 +00:00
int number = 0;
2024-11-22 14:51:47 +00:00
while (sormpt(sql, number) != NULL)
2024-11-22 13:45:03 +00:00
number++;
2024-11-22 14:51:47 +00:00
printf("FOUND: %d\n", number);
2024-11-22 13:45:03 +00:00
return number;
}
2024-11-22 14:51:47 +00:00
char *sormcts(int column_type) {
if (column_type == SQLITE_INTEGER)
2024-11-22 13:45:03 +00:00
return "integer";
2024-11-22 14:51:47 +00:00
else if (column_type == SQLITE_TEXT)
2024-11-22 13:45:03 +00:00
return "text";
2024-11-22 14:51:47 +00:00
else if (column_type == SQLITE_FLOAT)
2024-11-22 13:45:03 +00:00
return "float";
2024-11-22 14:51:47 +00:00
else if (column_type == SQLITE_NULL)
2024-11-22 13:45:03 +00:00
return "null";
2024-11-22 14:51:47 +00:00
else if (column_type == SQLITE_BLOB)
2024-11-22 13:45:03 +00:00
return "blob";
return "?";
}
/*
Execute 3.35s, Format: 36.77s
Memory usage: 537 GB, 96.026 allocated, 96.024 freed, 2 in use.
*/
2024-11-22 14:51:47 +00:00
char *sorm_csvc(int db, sqlite3_stmt *stmt) {
sormstr_t *str = sormstrn(512);
unsigned int column_count = sqlite3_column_count(stmt);
for (int i = 0; i < column_count; i++) {
const char *column_name = sqlite3_column_name(stmt, i);
sormstra(str, column_name);
sormstra(str, "(");
char column_type[1000] = "";
sprintf(column_type, "%s", sormcts(sqlite3_column_type(stmt, i)));
sormstra(str, column_type);
sormstra(str, ")");
// if(i != column_count - 1)
sormstra(str, ";");
}
2024-11-22 13:45:03 +00:00
return sormstrc(str);
}
2024-11-22 14:51:47 +00:00
char *sorm_csvd(int sorm, sqlite3_stmt *stmt) {
sorm_t *db = sormg(sorm);
2024-11-22 13:45:03 +00:00
int rc = SQLITE_ROW;
int column_count = sqlite3_column_count(stmt);
/*
sormstrn(1)
Execute 3.41s, Format: 36.77s
Memory usage: 5 MB, 96.061 (re)allocated, 96.024 unqiue freed, 2 in use.
sormstrn(512)
Execute 3.68s, Format: 36.83s
Memory usage: 537 GB, 96.026 allocated, 96.024 freed, 2 in use.
sormstrn(256)
xecute 3.42s, Format: 37.33s
Memory usage: 6 MB, 96.052 (re)allocated, 96.024 unqiue freed, 2 in use.
*/
2024-11-22 14:51:47 +00:00
sormstr_t *str = sormstrn(512);
while (rc == SQLITE_ROW) {
sorm_row_count++;
for (int field_index = 0; field_index < column_count; field_index++) {
if (sqlite3_column_type(stmt, field_index) == SQLITE_INTEGER) {
char temp[1000] = "";
sprintf(temp, "%lld", sqlite3_column_int64(stmt, field_index));
sormstra(str, temp);
} else if (sqlite3_column_type(stmt, field_index) == SQLITE_FLOAT) {
char temp[1000] = "";
sprintf(temp, "%f", sqlite3_column_double(stmt, field_index));
sormstra(str, temp);
} else if (sqlite3_column_type(stmt, field_index) == SQLITE3_TEXT) {
const char *temp = sqlite3_column_text(stmt, field_index);
sormstra(str, temp);
} else {
// exit(1);
2024-11-22 13:45:03 +00:00
}
2024-11-22 14:51:47 +00:00
// if(field_index != column_count - 1)
sormstra(str, ";");
2024-11-22 13:45:03 +00:00
}
2024-11-22 14:51:47 +00:00
sormstra(str, "\n");
rc = sqlite3_step(stmt);
}
char *text = sormstrc(str);
if (*text)
if (text[strlen(text) - 1] == '\n')
text[strlen(text) - 1] = 0;
return strdup(text);
2024-11-22 13:45:03 +00:00
}
2024-11-22 14:51:47 +00:00
char *sorm_csv(int sorm, sqlite3_stmt *stmt) {
sorm_t *db = sormg(sorm);
2024-11-22 13:45:03 +00:00
sorm_row_count = 0;
2024-11-22 14:51:47 +00:00
char *column_names = sorm_csvc(sorm, stmt);
char *data = sorm_csvd(sorm, stmt);
char *result = (char *)malloc(strlen(column_names) + strlen(data) + 2);
2024-11-22 13:45:03 +00:00
result[0] = 0;
2024-11-22 14:51:47 +00:00
strcat(result, column_names);
if (*column_names)
strcat(result, "\n");
2024-11-22 13:45:03 +00:00
free(column_names);
2024-11-22 14:51:47 +00:00
strcat(result, data);
2024-11-22 13:45:03 +00:00
free(data);
return result;
}
2024-11-22 14:51:47 +00:00
sqlite3_stmt *sormb(sorm_t *db, char *sql, ...) {
2024-11-22 13:45:03 +00:00
// Bind parameters to statement and return amount of parameters
int rc = 0;
2024-11-22 14:51:47 +00:00
sqlite3_stmt *stmt;
2024-11-22 13:45:03 +00:00
va_list args;
2024-11-22 14:51:47 +00:00
va_start(args, sql);
2024-11-22 13:45:03 +00:00
unsigned int number = 0;
2024-11-22 14:51:47 +00:00
char *clean_query = (char *)malloc(strlen(sql) + 1);
unsigned int parameter_count = sormcq(sql, clean_query);
2024-11-22 13:45:03 +00:00
free(clean_query);
2024-11-22 14:51:47 +00:00
2024-11-22 13:45:03 +00:00
return stmt;
}
2024-11-22 14:51:47 +00:00
char *sormm(sorm_t *db) {
2024-11-22 13:45:03 +00:00
/* sormmemory */
return rmalloc_stats();
}
2024-11-22 14:51:47 +00:00
sorm_ptr sormq(int sorm, char *sql, ...) {
sorm_t *db = sormg(sorm);
if (db->csv) {
// free(db->csv);
// db->csv = NULL;
2024-11-22 13:45:03 +00:00
}
_sorm_query_start = nsecs();
db->time_query_start = nsecs();
va_list args;
2024-11-22 14:51:47 +00:00
va_start(args, sql);
sqlite3_stmt *stmt;
2024-11-22 13:45:03 +00:00
sorm_ptr result = NULL;
2024-11-22 14:51:47 +00:00
char *clean_query = malloc(strlen(sql) + 1);
unsigned int parameter_count = sormcq(sql, clean_query);
2024-11-22 13:45:03 +00:00
int rc = sqlite3_prepare_v2(db->conn, clean_query, -1, &stmt, 0);
if (rc != SQLITE_OK) {
fprintf(stderr, "%s\n", sqlite3_errmsg(db->conn));
}
free(clean_query);
int number = 0;
2024-11-22 14:51:47 +00:00
for (int i = 0; i < parameter_count; i++) {
2024-11-22 13:45:03 +00:00
number++;
2024-11-22 14:51:47 +00:00
char *column_type = sormpt(sql, number);
2024-11-22 13:45:03 +00:00
int arg_index = number - 1;
2024-11-22 14:51:47 +00:00
if (!strcmp(column_type, "int") || !strcmp(column_type, "integer") || !strcmp(column_type, "number")) {
rc = sqlite3_bind_int(stmt, number, va_arg(args, int));
} else if (!strcmp(column_type, "int64")) {
rc = sqlite3_bind_int64(stmt, number, va_arg(args, __uint64_t));
} else if (!strcmp(column_type, "double") || !strcmp(column_type, "dec") || !strcmp(column_type, "decimal") ||
!strcmp(column_type, "float")) {
rc = sqlite3_bind_double(stmt, number, va_arg(args, double));
} else if (!strcmp(column_type, "blob")) {
size_t size = (size_t)va_arg(args, size_t);
unsigned char *data = va_arg(args, unsigned char *);
2024-11-22 13:45:03 +00:00
rc = sqlite3_bind_blob(stmt, number, data, size, SQLITE_STATIC);
2024-11-22 14:51:47 +00:00
} else if (!strcmp(column_type, "text") || !strcmp(column_type, "str") || !strcmp(column_type, "string")) {
unsigned char *data = va_arg(args, unsigned char *);
2024-11-22 13:45:03 +00:00
rc = sqlite3_bind_text(stmt, number, data, -1, SQLITE_STATIC);
}
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to bind parameters: %s\n", sqlite3_errmsg(db->conn));
result = NULL;
}
}
2024-11-22 14:51:47 +00:00
rc = sqlite3_step(stmt);
2024-11-22 13:45:03 +00:00
if (rc != SQLITE_DONE && rc != SQLITE_ROW) {
fprintf(stderr, "Execution failed: %s\n", sqlite3_errmsg(db->conn));
2024-11-22 14:51:47 +00:00
} else if (rc == SQLITE_DONE) {
if (!sqlite3_strnicmp(sql, "SELECT", 6)) {
2024-11-22 13:45:03 +00:00
result = 0;
2024-11-22 14:51:47 +00:00
} else {
result = (sorm_ptr)sqlite3_last_insert_rowid(db->conn);
2024-11-22 13:45:03 +00:00
}
} else if (rc == SQLITE_ROW) {
2024-11-22 14:51:47 +00:00
result = sorm_csv(sorm, stmt);
2024-11-22 13:45:03 +00:00
}
2024-11-22 14:51:47 +00:00
else {
2024-11-22 13:45:03 +00:00
fprintf(stderr, "Execution failed: %s\n", sqlite3_errmsg(db->conn));
}
2024-11-22 14:51:47 +00:00
if (sorm_last_query) {
2024-11-22 13:45:03 +00:00
free(sorm_last_query);
}
2024-11-22 14:51:47 +00:00
if (sorm_last_query) {
2024-11-22 13:45:03 +00:00
free(sorm_last_query_expanded);
}
sorm_last_query = strdup(sqlite3_sql(stmt));
sorm_last_query_expanded = strdup(sqlite3_expanded_sql(stmt));
sqlite3_finalize(stmt);
_sorm_query_end = nsecs();
_sorm_query_duration = _sorm_query_end - _sorm_query_start;
db->time_query_end = nsecs();
db->time_query_duration = db->time_query_end - db->time_query_start;
db->csv = result;
return result;
}
2024-11-22 14:51:47 +00:00
char sormlc(char *sql) {
2024-11-22 13:45:03 +00:00
// returns last char
char last_char = 0;
2024-11-22 14:51:47 +00:00
while (*sql) {
if (*sql == ' ' || *sql == '\t' || *sql == '\n')
continue;
2024-11-22 13:45:03 +00:00
2024-11-22 14:51:47 +00:00
// printf("%c\n",*sql);
2024-11-22 13:45:03 +00:00
last_char = *sql;
sql++;
}
return last_char;
}
2024-11-22 14:51:47 +00:00
int sormlv(char *csv) {
2024-11-22 13:45:03 +00:00
size_t longest = 0;
2024-11-22 14:51:47 +00:00
while (*csv) {
char *found = strstr(csv, ";");
if (found) {
if (found - csv > longest)
longest = found - csv;
2024-11-22 13:45:03 +00:00
csv = csv + (found - csv) + 1;
2024-11-22 14:51:47 +00:00
} else {
2024-11-22 13:45:03 +00:00
break;
}
}
return longest;
}
2024-11-22 14:51:47 +00:00
sorm_query_t sormqt(char *sql) {
while (*sql && isspace(*sql))
2024-11-22 13:45:03 +00:00
sql++;
2024-11-22 14:51:47 +00:00
if (!sqlite3_strnicmp(sql, "select", 6))
2024-11-22 13:45:03 +00:00
return SORM_SELECT;
2024-11-22 14:51:47 +00:00
else if (!sqlite3_strnicmp(sql, "update", 6))
2024-11-22 13:45:03 +00:00
return SORM_UPDATE;
2024-11-22 14:51:47 +00:00
else if (!sqlite3_strnicmp(sql, "delete", 6))
2024-11-22 13:45:03 +00:00
return SORM_DELETE;
2024-11-22 14:51:47 +00:00
else if (!sqlite3_strnicmp(sql, "create", 6)) {
2024-11-22 13:45:03 +00:00
return SORM_CREATE;
2024-11-22 14:51:47 +00:00
} else {
2024-11-22 13:45:03 +00:00
return SORM_UNKNOWN;
}
}
2024-11-22 14:51:47 +00:00
char *sormrq(FILE *f) {
2024-11-22 13:45:03 +00:00
static char buffer[4097];
buffer[0] = 0;
2024-11-22 14:51:47 +00:00
char *bufferp = buffer;
2024-11-22 13:45:03 +00:00
char c;
bool in_string = false;
2024-11-22 14:51:47 +00:00
while ((c = fgetc(f)) != EOF && strlen(buffer) != sizeof(buffer) - 2) {
2024-11-22 13:45:03 +00:00
*bufferp = c;
2024-11-22 14:51:47 +00:00
if (c == '"') {
2024-11-22 13:45:03 +00:00
in_string = !in_string;
}
2024-11-22 14:51:47 +00:00
if (!in_string && c == ';') {
2024-11-22 13:45:03 +00:00
break;
}
bufferp++;
*bufferp = 0;
}
return strdup(buffer);
}
2024-11-22 14:51:47 +00:00
char *sormcsvn(char *csv) {
if (!csv || !*csv)
2024-11-22 13:45:03 +00:00
return NULL;
2024-11-22 14:51:47 +00:00
char *pos = strstr(csv, ";");
char *pos2 = strstr(csv, "\n");
if (pos2) {
if (pos > pos2) {
2024-11-22 13:45:03 +00:00
pos = pos2;
}
2024-11-22 14:51:47 +00:00
// pos = pos > pos2 ? pos2 : pos;
2024-11-22 13:45:03 +00:00
}
2024-11-22 14:51:47 +00:00
if (!pos || !*pos)
2024-11-22 13:45:03 +00:00
return strdup(csv);
int length = pos - csv;
2024-11-22 14:51:47 +00:00
char *result = malloc(length + 2);
strncpy(result, csv, length);
2024-11-22 13:45:03 +00:00
result[length] = 0;
2024-11-22 14:51:47 +00:00
// csv += strlen(result);
2024-11-22 13:45:03 +00:00
return result;
}
2024-11-22 14:51:47 +00:00
char *sormfmt(char *csv) {
2024-11-22 13:45:03 +00:00
_sorm_result_format_start = nsecs();
size_t longest = sormlv(csv);
2024-11-22 14:51:47 +00:00
char *field;
2024-11-22 13:45:03 +00:00
/*
sormstrn(1)
Execute 3.77s, Format: 36.40s
Memory usage: 6 MB, 96.055 (re)allocated, 96.024 unqiue freed, 2 in use.
sormstrn(longest);
Execute 3.27s, Format: 36.61s
Memory usage: 6 MB, 96.053 (re)allocated, 96.024 unqiue freed, 2 in use.
sormstrn(longest * 2);
xecute 3.42s, Format: 37.33s
Memory usage: 6 MB, 96.052 (re)allocated, 96.024 unqiue freed, 2 in use.
sormstrn(512);
Execute 3.11s, Format: 36.45s
Memory usage: 6 MB, 96.048 (re)allocated, 96.024 unqiue freed, 2 in use.
*/
2024-11-22 14:51:47 +00:00
sormstr_t *str = sormstrn(longest + 2);
while (*csv && (field = sormcsvn(csv))) {
sormstra(str, field);
for (int i = 0; i < longest - strlen(field); i++)
sormstra(str, " ");
2024-11-22 13:45:03 +00:00
csv += strlen(field);
2024-11-22 14:51:47 +00:00
while (*csv == ';' || *csv == '\n') {
if (*csv == '\n')
2024-11-22 13:45:03 +00:00
sormstra(str, "\n");
csv++;
}
free(field);
}
_sorm_result_format_end = nsecs();
_sorm_result_format_duration = _sorm_result_format_end - _sorm_result_format_start;
return sormstrc(str);
}
2024-11-22 14:51:47 +00:00
void apply_colors(char *csv) {
char *end;
2024-11-22 13:45:03 +00:00
bool even = false;
2024-11-22 14:51:47 +00:00
while (*csv) {
printf("%s\n", csv);
end = strstr(csv, "\n");
char *line;
if (end) {
line = (char *)malloc(end - csv + 1024);
strncpy(line, csv, end - csv);
} else {
2024-11-22 13:45:03 +00:00
line = (char *)malloc(strlen(csv) + 1024);
strcpy(line, csv);
}
2024-11-22 14:51:47 +00:00
if (even) {
printf("%s", "\033[37m");
2024-11-22 13:45:03 +00:00
}
2024-11-22 14:51:47 +00:00
printf("%s\n", line);
2024-11-22 13:45:03 +00:00
free(line);
2024-11-22 14:51:47 +00:00
if (even) {
printf("%s", "\033[0m");
2024-11-22 13:45:03 +00:00
}
even = !even;
2024-11-22 14:51:47 +00:00
csv += end - csv;
if (*csv && *(csv + 1))
2024-11-22 13:45:03 +00:00
csv++;
}
}
2024-11-22 14:51:47 +00:00
void sormfmtd(char *csv) {
char *formatted = sormfmt(csv);
printf("%s\n", formatted);
2024-11-22 13:45:03 +00:00
free(formatted);
}
2024-11-22 14:51:47 +00:00
#endif