mirror of
https://github.com/marcusDenslow/FERRUM.git
synced 2026-10-25 20:28:51 +01:00
479 lines
16 KiB
C
479 lines
16 KiB
C
/**
|
|
* structured_data.c
|
|
* Implementation of structured data operations
|
|
*/
|
|
|
|
#include "structured_data.h"
|
|
#include "builtins.h" // For set_color and reset_color functions
|
|
#include <strings.h>
|
|
|
|
/**
|
|
* Create a new table with the given headers
|
|
*/
|
|
TableData* create_table(char **headers, int header_count) {
|
|
TableData *table = (TableData*)malloc(sizeof(TableData));
|
|
if (!table) {
|
|
fprintf(stderr, "lsh: allocation error in create_table\n");
|
|
return NULL;
|
|
}
|
|
|
|
// Copy headers
|
|
table->headers = (char**)malloc(header_count * sizeof(char*));
|
|
if (!table->headers) {
|
|
fprintf(stderr, "lsh: allocation error in create_table (headers)\n");
|
|
free(table);
|
|
return NULL;
|
|
}
|
|
|
|
for (int i = 0; i < header_count; i++) {
|
|
table->headers[i] = strdup(headers[i]);
|
|
}
|
|
|
|
table->header_count = header_count;
|
|
table->row_count = 0;
|
|
table->row_capacity = 10; // Initial capacity for 10 rows
|
|
|
|
// Allocate memory for rows
|
|
table->rows = (DataValue**)malloc(table->row_capacity * sizeof(DataValue*));
|
|
if (!table->rows) {
|
|
fprintf(stderr, "lsh: allocation error in create_table (rows)\n");
|
|
for (int i = 0; i < header_count; i++) {
|
|
free(table->headers[i]);
|
|
}
|
|
free(table->headers);
|
|
free(table);
|
|
return NULL;
|
|
}
|
|
|
|
return table;
|
|
}
|
|
|
|
/**
|
|
* Add a row to a table
|
|
*/
|
|
void add_table_row(TableData *table, DataValue *row) {
|
|
if (!table || !row) return;
|
|
|
|
// Resize if needed
|
|
if (table->row_count >= table->row_capacity) {
|
|
table->row_capacity *= 2;
|
|
table->rows = (DataValue**)realloc(table->rows, table->row_capacity * sizeof(DataValue*));
|
|
if (!table->rows) {
|
|
fprintf(stderr, "lsh: allocation error in add_table_row\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Add the row
|
|
table->rows[table->row_count] = row;
|
|
table->row_count++;
|
|
}
|
|
|
|
/**
|
|
* Free all memory associated with a table
|
|
*/
|
|
void free_table(TableData *table) {
|
|
if (!table) return;
|
|
|
|
// Free headers
|
|
for (int i = 0; i < table->header_count; i++) {
|
|
free(table->headers[i]);
|
|
}
|
|
free(table->headers);
|
|
|
|
// Free rows
|
|
for (int i = 0; i < table->row_count; i++) {
|
|
for (int j = 0; j < table->header_count; j++) {
|
|
free_data_value(&table->rows[i][j]);
|
|
}
|
|
free(table->rows[i]);
|
|
}
|
|
free(table->rows);
|
|
|
|
// Free table structure
|
|
free(table);
|
|
}
|
|
|
|
/**
|
|
* Create a copy of a DataValue
|
|
*/
|
|
DataValue copy_data_value(const DataValue *src) {
|
|
DataValue dest;
|
|
dest.type = src->type;
|
|
dest.is_highlighted = src->is_highlighted; // Make sure we preserve the highlighting flag
|
|
|
|
switch (src->type) {
|
|
case TYPE_STRING:
|
|
case TYPE_SIZE:
|
|
dest.value.str_val = strdup(src->value.str_val);
|
|
break;
|
|
case TYPE_INT:
|
|
dest.value.int_val = src->value.int_val;
|
|
break;
|
|
case TYPE_FLOAT:
|
|
dest.value.float_val = src->value.float_val;
|
|
break;
|
|
}
|
|
|
|
return dest;
|
|
}
|
|
|
|
/**
|
|
* Free a DataValue
|
|
*/
|
|
void free_data_value(DataValue *value) {
|
|
if (!value) return;
|
|
|
|
if ((value->type == TYPE_STRING || value->type == TYPE_SIZE) && value->value.str_val) {
|
|
free(value->value.str_val);
|
|
value->value.str_val = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse human-readable sizes (e.g., "10kb", "2.5MB") into bytes
|
|
*/
|
|
long parse_size(const char *size_str) {
|
|
char *unit;
|
|
double size = strtod(size_str, &unit);
|
|
|
|
// Skip whitespace between number and unit
|
|
while (*unit && isspace(*unit)) unit++;
|
|
|
|
// Handle case insensitive units
|
|
if (strcasecmp(unit, "kb") == 0 || strcasecmp(unit, "k") == 0) {
|
|
return (long)(size * 1024);
|
|
} else if (strcasecmp(unit, "mb") == 0 || strcasecmp(unit, "m") == 0) {
|
|
return (long)(size * 1024 * 1024);
|
|
} else if (strcasecmp(unit, "gb") == 0 || strcasecmp(unit, "g") == 0) {
|
|
return (long)(size * 1024 * 1024 * 1024);
|
|
} else if (strcasecmp(unit, "b") == 0 || *unit == '\0') {
|
|
return (long)size;
|
|
}
|
|
|
|
// Try to handle cases like "2.5 KB" with a space
|
|
char num_str[32];
|
|
char unit_str[8];
|
|
if (sscanf(size_str, "%31s %7s", num_str, unit_str) == 2) {
|
|
double num = atof(num_str);
|
|
if (strcasecmp(unit_str, "kb") == 0 || strcasecmp(unit_str, "k") == 0) {
|
|
return (long)(num * 1024);
|
|
} else if (strcasecmp(unit_str, "mb") == 0 || strcasecmp(unit_str, "m") == 0) {
|
|
return (long)(num * 1024 * 1024);
|
|
} else if (strcasecmp(unit_str, "gb") == 0 || strcasecmp(unit_str, "g") == 0) {
|
|
return (long)(num * 1024 * 1024 * 1024);
|
|
} else if (strcasecmp(unit_str, "b") == 0) {
|
|
return (long)num;
|
|
}
|
|
}
|
|
|
|
return (long)size; // Default to just the number if no unit matches
|
|
}
|
|
|
|
/**
|
|
* Extract size in bytes from a formatted size string (e.g., "10.5 KB")
|
|
*/
|
|
long extract_size_bytes(const char *size_str) {
|
|
double size_val = 0;
|
|
char unit[8] = "";
|
|
|
|
// Try the format with float + space + unit (e.g., "10.5 MB")
|
|
if (sscanf(size_str, "%lf %7s", &size_val, unit) == 2) {
|
|
if (strcasecmp(unit, "B") == 0) {
|
|
return (long)size_val;
|
|
} else if (strcasecmp(unit, "KB") == 0) {
|
|
return (long)(size_val * 1024);
|
|
} else if (strcasecmp(unit, "MB") == 0) {
|
|
return (long)(size_val * 1024 * 1024);
|
|
} else if (strcasecmp(unit, "GB") == 0) {
|
|
return (long)(size_val * 1024 * 1024 * 1024);
|
|
}
|
|
}
|
|
|
|
// Fallback to direct parsing if the format doesn't match
|
|
return parse_size(size_str);
|
|
}
|
|
|
|
/**
|
|
* Filter a table based on a condition
|
|
*/
|
|
TableData* filter_table(TableData *input, char *field, char *op, char *value) {
|
|
if (!input || !field || !op || !value) {
|
|
return NULL;
|
|
}
|
|
|
|
// Create a new table with the same headers
|
|
TableData *result = create_table(input->headers, input->header_count);
|
|
if (!result) {
|
|
return NULL;
|
|
}
|
|
|
|
// Find field index
|
|
int field_idx = -1;
|
|
for (int i = 0; i < input->header_count; i++) {
|
|
if (strcasecmp(input->headers[i], field) == 0) {
|
|
field_idx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (field_idx == -1) {
|
|
fprintf(stderr, "filter_table: unknown field '%s'\n", field);
|
|
free_table(result);
|
|
return NULL;
|
|
}
|
|
|
|
// Special handling for size field - parse human-readable sizes
|
|
// Modified to handle both "size" and "Memory" columns
|
|
int is_size_field = (strcasecmp(field, "size") == 0 || strcasecmp(field, "Memory") == 0);
|
|
long value_size = is_size_field ? parse_size(value) : 0;
|
|
|
|
// Filter rows based on condition
|
|
for (int i = 0; i < input->row_count; i++) {
|
|
int include_row = 0;
|
|
|
|
// Handle based on data type
|
|
if (input->rows[i][field_idx].type == TYPE_STRING ||
|
|
input->rows[i][field_idx].type == TYPE_SIZE) {
|
|
char *row_value = input->rows[i][field_idx].value.str_val;
|
|
|
|
// Special handling for size field with values like "10.5 KB"
|
|
if (is_size_field) {
|
|
long row_size = extract_size_bytes(row_value);
|
|
|
|
// Compare sizes
|
|
if (strcmp(op, ">") == 0) {
|
|
include_row = (row_size > value_size);
|
|
} else if (strcmp(op, "<") == 0) {
|
|
include_row = (row_size < value_size);
|
|
} else if (strcmp(op, ">=") == 0) {
|
|
include_row = (row_size >= value_size);
|
|
} else if (strcmp(op, "<=") == 0) {
|
|
include_row = (row_size <= value_size);
|
|
} else if (strcmp(op, "==") == 0) {
|
|
include_row = (row_size == value_size);
|
|
}
|
|
} else {
|
|
// String comparison
|
|
int cmp = strcasecmp(row_value, value);
|
|
if (strcmp(op, ">") == 0) {
|
|
include_row = (cmp > 0);
|
|
} else if (strcmp(op, "<") == 0) {
|
|
include_row = (cmp < 0);
|
|
} else if (strcmp(op, "==") == 0) {
|
|
include_row = (cmp == 0);
|
|
} else if (strcmp(op, ">=") == 0) {
|
|
include_row = (cmp >= 0);
|
|
} else if (strcmp(op, "<=") == 0) {
|
|
include_row = (cmp <= 0);
|
|
}
|
|
}
|
|
} else if (input->rows[i][field_idx].type == TYPE_INT) {
|
|
int row_value = input->rows[i][field_idx].value.int_val;
|
|
int val = atoi(value);
|
|
|
|
if (strcmp(op, ">") == 0) {
|
|
include_row = (row_value > val);
|
|
} else if (strcmp(op, "<") == 0) {
|
|
include_row = (row_value < val);
|
|
} else if (strcmp(op, ">=") == 0) {
|
|
include_row = (row_value >= val);
|
|
} else if (strcmp(op, "<=") == 0) {
|
|
include_row = (row_value <= val);
|
|
} else if (strcmp(op, "==") == 0) {
|
|
include_row = (row_value == val);
|
|
}
|
|
} else if (input->rows[i][field_idx].type == TYPE_FLOAT) {
|
|
float row_value = input->rows[i][field_idx].value.float_val;
|
|
float val = (float)atof(value);
|
|
|
|
if (strcmp(op, ">") == 0) {
|
|
include_row = (row_value > val);
|
|
} else if (strcmp(op, "<") == 0) {
|
|
include_row = (row_value < val);
|
|
} else if (strcmp(op, ">=") == 0) {
|
|
include_row = (row_value >= val);
|
|
} else if (strcmp(op, "<=") == 0) {
|
|
include_row = (row_value <= val);
|
|
} else if (strcmp(op, "==") == 0) {
|
|
include_row = (row_value == val);
|
|
}
|
|
}
|
|
|
|
if (include_row) {
|
|
// Create a copy of the row for the filtered table
|
|
DataValue *row_copy = (DataValue*)malloc(input->header_count * sizeof(DataValue));
|
|
if (!row_copy) {
|
|
fprintf(stderr, "lsh: allocation error in filter_table\n");
|
|
free_table(result);
|
|
return NULL;
|
|
}
|
|
|
|
for (int j = 0; j < input->header_count; j++) {
|
|
row_copy[j] = copy_data_value(&input->rows[i][j]);
|
|
}
|
|
|
|
add_table_row(result, row_copy);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Print a table to the console with nice formatting using Unicode box characters
|
|
* Linux version using ANSI escape sequences for colors
|
|
*/
|
|
void print_table(TableData *table) {
|
|
if (!table || table->row_count == 0) {
|
|
printf("(empty table)\n");
|
|
return;
|
|
}
|
|
|
|
// Calculate column widths
|
|
int *col_widths = (int*)malloc(table->header_count * sizeof(int));
|
|
if (!col_widths) {
|
|
fprintf(stderr, "lsh: allocation error in print_table\n");
|
|
return;
|
|
}
|
|
|
|
// Initialize with header widths
|
|
for (int i = 0; i < table->header_count; i++) {
|
|
col_widths[i] = strlen(table->headers[i]);
|
|
}
|
|
|
|
// Check cell widths
|
|
for (int i = 0; i < table->row_count; i++) {
|
|
for (int j = 0; j < table->header_count; j++) {
|
|
if (table->rows[i][j].type == TYPE_STRING || table->rows[i][j].type == TYPE_SIZE) {
|
|
int len = strlen(table->rows[i][j].value.str_val);
|
|
if (len > col_widths[j]) {
|
|
col_widths[j] = len;
|
|
}
|
|
} else if (table->rows[i][j].type == TYPE_INT) {
|
|
char buf[32];
|
|
int len = snprintf(buf, sizeof(buf), "%d", table->rows[i][j].value.int_val);
|
|
if (len > col_widths[j]) {
|
|
col_widths[j] = len;
|
|
}
|
|
} else if (table->rows[i][j].type == TYPE_FLOAT) {
|
|
char buf[32];
|
|
int len = snprintf(buf, sizeof(buf), "%.2f", table->rows[i][j].value.float_val);
|
|
if (len > col_widths[j]) {
|
|
col_widths[j] = len;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add padding to column widths
|
|
for (int i = 0; i < table->header_count; i++) {
|
|
col_widths[i] += 4; // 2 spaces on each side
|
|
}
|
|
|
|
// Calculate total table width
|
|
int totalWidth = 1; // Start with 1 for the left border
|
|
for (int i = 0; i < table->header_count; i++) {
|
|
totalWidth += col_widths[i] + 1; // Add column width and right border
|
|
}
|
|
|
|
// Create a buffer big enough for one line of the table
|
|
char *lineBuffer = (char*)malloc(totalWidth * 4 + 1); // Unicode chars can be up to 4 bytes
|
|
if (!lineBuffer) {
|
|
fprintf(stderr, "lsh: allocation error in print_table\n");
|
|
free(col_widths);
|
|
return;
|
|
}
|
|
|
|
// Begin output with a newline
|
|
printf("\n");
|
|
|
|
// ---- Print top border ----
|
|
lineBuffer[0] = '\0';
|
|
strcat(lineBuffer, "┌"); // Top-left corner
|
|
for (int i = 0; i < table->header_count; i++) {
|
|
for (int j = 0; j < col_widths[i]; j++) {
|
|
strcat(lineBuffer, "─"); // Horizontal line
|
|
}
|
|
if (i < table->header_count - 1) {
|
|
strcat(lineBuffer, "┬"); // Top T-junction
|
|
}
|
|
}
|
|
strcat(lineBuffer, "┐"); // Top-right corner
|
|
printf("%s\n", lineBuffer);
|
|
|
|
// ---- Print header row ----
|
|
lineBuffer[0] = '\0';
|
|
strcat(lineBuffer, "│"); // Vertical line
|
|
for (int i = 0; i < table->header_count; i++) {
|
|
char cellBuffer[256];
|
|
snprintf(cellBuffer, sizeof(cellBuffer), " %-*s ", col_widths[i] - 2, table->headers[i]);
|
|
strcat(lineBuffer, cellBuffer);
|
|
strcat(lineBuffer, "│"); // Vertical line
|
|
}
|
|
printf("%s\n", lineBuffer);
|
|
|
|
// ---- Print header/data separator ----
|
|
lineBuffer[0] = '\0';
|
|
strcat(lineBuffer, "├"); // Left T-junction
|
|
for (int i = 0; i < table->header_count; i++) {
|
|
for (int j = 0; j < col_widths[i]; j++) {
|
|
strcat(lineBuffer, "─"); // Horizontal line
|
|
}
|
|
if (i < table->header_count - 1) {
|
|
strcat(lineBuffer, "┼"); // Cross junction
|
|
}
|
|
}
|
|
strcat(lineBuffer, "┤"); // Right T-junction
|
|
printf("%s\n", lineBuffer);
|
|
|
|
// Print data rows
|
|
for (int i = 0; i < table->row_count; i++) {
|
|
lineBuffer[0] = '\0';
|
|
strcat(lineBuffer, "│"); // Left border
|
|
|
|
for (int j = 0; j < table->header_count; j++) {
|
|
// Format cell content based on data type
|
|
char cellBuffer[256];
|
|
|
|
if (table->rows[i][j].type == TYPE_STRING || table->rows[i][j].type == TYPE_SIZE) {
|
|
snprintf(cellBuffer, sizeof(cellBuffer), " %-*s ", col_widths[j] - 2, table->rows[i][j].value.str_val);
|
|
} else if (table->rows[i][j].type == TYPE_INT) {
|
|
snprintf(cellBuffer, sizeof(cellBuffer), " %-*d ", col_widths[j] - 2, table->rows[i][j].value.int_val);
|
|
} else if (table->rows[i][j].type == TYPE_FLOAT) {
|
|
snprintf(cellBuffer, sizeof(cellBuffer), " %-*.2f ", col_widths[j] - 2, table->rows[i][j].value.float_val);
|
|
}
|
|
|
|
// Apply color if the cell is highlighted
|
|
if (table->rows[i][j].is_highlighted) {
|
|
strcat(lineBuffer, ANSI_COLOR_GREEN); // Green text for highlighted cells
|
|
strcat(lineBuffer, cellBuffer);
|
|
strcat(lineBuffer, ANSI_COLOR_RESET); // Reset color
|
|
} else {
|
|
strcat(lineBuffer, cellBuffer);
|
|
}
|
|
|
|
strcat(lineBuffer, "│"); // Right border of cell
|
|
}
|
|
|
|
printf("%s\n", lineBuffer);
|
|
}
|
|
|
|
// ---- Print bottom border ----
|
|
lineBuffer[0] = '\0';
|
|
strcat(lineBuffer, "└"); // Bottom-left corner
|
|
for (int i = 0; i < table->header_count; i++) {
|
|
for (int j = 0; j < col_widths[i]; j++) {
|
|
strcat(lineBuffer, "─"); // Horizontal line
|
|
}
|
|
if (i < table->header_count - 1) {
|
|
strcat(lineBuffer, "┴"); // Bottom T-junction
|
|
}
|
|
}
|
|
strcat(lineBuffer, "┘"); // Bottom-right corner
|
|
printf("%s\n\n", lineBuffer);
|
|
|
|
// Clean up
|
|
free(lineBuffer);
|
|
free(col_widths);
|
|
} |