tranfi 0.0.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/README.md +395 -0
  2. package/app/assets/index-6quYZ5Ap.css +5 -0
  3. package/app/assets/index-pDFMluyz.js +160 -0
  4. package/app/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  5. package/app/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  6. package/app/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  7. package/app/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  8. package/app/index.html +13 -0
  9. package/binding.gyp +69 -0
  10. package/csrc/arena.c +91 -0
  11. package/csrc/batch.c +229 -0
  12. package/csrc/buffer.c +78 -0
  13. package/csrc/cJSON.c +3143 -0
  14. package/csrc/cJSON.h +300 -0
  15. package/csrc/codec_csv.c +1058 -0
  16. package/csrc/codec_jsonl.c +374 -0
  17. package/csrc/codec_table.c +218 -0
  18. package/csrc/codec_text.c +229 -0
  19. package/csrc/compiler.c +102 -0
  20. package/csrc/date_utils.h +94 -0
  21. package/csrc/dsl.c +1180 -0
  22. package/csrc/dsl.h +22 -0
  23. package/csrc/expr.c +1245 -0
  24. package/csrc/expr.h +56 -0
  25. package/csrc/internal.h +250 -0
  26. package/csrc/ir.c +119 -0
  27. package/csrc/ir.h +167 -0
  28. package/csrc/ir_schema.c +60 -0
  29. package/csrc/ir_serialize.c +104 -0
  30. package/csrc/ir_sql.c +1211 -0
  31. package/csrc/ir_validate.c +120 -0
  32. package/csrc/main.c +392 -0
  33. package/csrc/op_acf.c +133 -0
  34. package/csrc/op_anomaly.c +120 -0
  35. package/csrc/op_bin.c +109 -0
  36. package/csrc/op_cast.c +195 -0
  37. package/csrc/op_clip.c +88 -0
  38. package/csrc/op_date_trunc.c +181 -0
  39. package/csrc/op_datetime.c +212 -0
  40. package/csrc/op_derive.c +248 -0
  41. package/csrc/op_diff.c +134 -0
  42. package/csrc/op_ewma.c +103 -0
  43. package/csrc/op_explode.c +108 -0
  44. package/csrc/op_fill_down.c +163 -0
  45. package/csrc/op_fill_null.c +123 -0
  46. package/csrc/op_filter.c +132 -0
  47. package/csrc/op_frequency.c +193 -0
  48. package/csrc/op_grep.c +163 -0
  49. package/csrc/op_group_agg.c +285 -0
  50. package/csrc/op_hash.c +126 -0
  51. package/csrc/op_head.c +149 -0
  52. package/csrc/op_interpolate.c +239 -0
  53. package/csrc/op_join.c +384 -0
  54. package/csrc/op_label_encode.c +144 -0
  55. package/csrc/op_lead.c +190 -0
  56. package/csrc/op_normalize.c +226 -0
  57. package/csrc/op_onehot.c +185 -0
  58. package/csrc/op_pivot.c +370 -0
  59. package/csrc/op_registry.c +1148 -0
  60. package/csrc/op_rename.c +138 -0
  61. package/csrc/op_replace.c +202 -0
  62. package/csrc/op_sample.c +101 -0
  63. package/csrc/op_select.c +140 -0
  64. package/csrc/op_skip.c +152 -0
  65. package/csrc/op_sort.c +273 -0
  66. package/csrc/op_split.c +114 -0
  67. package/csrc/op_split_data.c +87 -0
  68. package/csrc/op_stack.c +315 -0
  69. package/csrc/op_stats.c +779 -0
  70. package/csrc/op_step.c +171 -0
  71. package/csrc/op_tail.c +96 -0
  72. package/csrc/op_top.c +150 -0
  73. package/csrc/op_trim.c +109 -0
  74. package/csrc/op_unique.c +300 -0
  75. package/csrc/op_unpivot.c +159 -0
  76. package/csrc/op_validate.c +71 -0
  77. package/csrc/op_window.c +150 -0
  78. package/csrc/pipeline.c +315 -0
  79. package/csrc/plan.c +206 -0
  80. package/csrc/recipes.c +102 -0
  81. package/csrc/recipes.h +27 -0
  82. package/csrc/report.c +463 -0
  83. package/csrc/report.h +22 -0
  84. package/csrc/tranfi.h +123 -0
  85. package/csrc/wasm_api.c +157 -0
  86. package/napi_api.c +326 -0
  87. package/package.json +46 -57
  88. package/src/cli.js +193 -0
  89. package/src/engines/duckdb.js +109 -0
  90. package/src/index.js +306 -0
  91. package/src/native.js +22 -0
  92. package/src/pipeline.js +286 -0
  93. package/src/server.js +277 -0
  94. package/src/wasm.js +19 -0
  95. package/wasm/index.js +244 -0
  96. package/wasm/package.json +1 -0
  97. package/wasm/tranfi_core.js +0 -0
  98. package/LICENSE +0 -21
  99. package/dist/bundle.js +0 -1
  100. package/index.html +0 -18
  101. package/logo.png +0 -0
  102. package/src/app.css +0 -160
  103. package/src/app.js +0 -203
  104. package/src/app.vue +0 -253
  105. package/src/bulma-input.vue +0 -110
  106. package/src/common-inputs.js +0 -28
  107. package/src/main.js +0 -18
  108. package/src/transforms.js +0 -166
  109. package/webpack.config.js +0 -108
@@ -0,0 +1,374 @@
1
+ /*
2
+ * codec_jsonl.c — JSON Lines streaming decoder and encoder.
3
+ *
4
+ * Decoder: splits on newlines, parses each line as JSON object using cJSON.
5
+ * - First line establishes schema (column names and types).
6
+ * - Subsequent lines match schema; missing keys → null.
7
+ * - Emits batch at batch_size.
8
+ *
9
+ * Encoder: batch → one JSON object per row, newline-separated.
10
+ */
11
+
12
+ #include "internal.h"
13
+ #include "date_utils.h"
14
+ #include "cJSON.h"
15
+ #include <stdlib.h>
16
+ #include <string.h>
17
+ #include <stdio.h>
18
+
19
+ #define DEFAULT_BATCH_SIZE 1024
20
+
21
+ /* ================================================================
22
+ * JSONL Decoder
23
+ * ================================================================ */
24
+
25
+ typedef struct {
26
+ size_t batch_size;
27
+
28
+ /* Line accumulator */
29
+ tf_buffer line_buf;
30
+
31
+ /* Schema (discovered from first object) */
32
+ char **col_names;
33
+ tf_type *col_types;
34
+ size_t n_cols;
35
+ int schema_ready;
36
+
37
+ /* Current batch */
38
+ tf_batch *batch;
39
+ size_t rows_buffered;
40
+ } jsonl_decoder_state;
41
+
42
+ static tf_type json_to_type(const cJSON *val) {
43
+ if (cJSON_IsNumber(val)) {
44
+ /* Check if integer */
45
+ double d = val->valuedouble;
46
+ if (d == (double)(int64_t)d && d >= -9007199254740992.0 && d <= 9007199254740992.0)
47
+ return TF_TYPE_INT64;
48
+ return TF_TYPE_FLOAT64;
49
+ }
50
+ if (cJSON_IsString(val)) return TF_TYPE_STRING;
51
+ if (cJSON_IsBool(val)) return TF_TYPE_BOOL;
52
+ return TF_TYPE_NULL;
53
+ }
54
+
55
+ static tf_type widen_type(tf_type current, tf_type incoming) {
56
+ if (current == incoming) return current;
57
+ if (current == TF_TYPE_NULL) return incoming;
58
+ if (incoming == TF_TYPE_NULL) return current;
59
+ if (current == TF_TYPE_INT64 && incoming == TF_TYPE_FLOAT64) return TF_TYPE_FLOAT64;
60
+ if (current == TF_TYPE_FLOAT64 && incoming == TF_TYPE_INT64) return TF_TYPE_FLOAT64;
61
+ return TF_TYPE_STRING;
62
+ }
63
+
64
+ static tf_batch *make_jsonl_batch(jsonl_decoder_state *st) {
65
+ tf_batch *b = tf_batch_create(st->n_cols, st->batch_size);
66
+ if (!b) return NULL;
67
+ for (size_t i = 0; i < st->n_cols; i++) {
68
+ tf_batch_set_schema(b, i, st->col_names[i], st->col_types[i]);
69
+ }
70
+ return b;
71
+ }
72
+
73
+ static int add_json_row(jsonl_decoder_state *st, cJSON *obj) {
74
+ if (!st->batch) {
75
+ st->batch = make_jsonl_batch(st);
76
+ if (!st->batch) return TF_ERROR;
77
+ }
78
+
79
+ size_t row = st->batch->n_rows;
80
+ if (tf_batch_ensure_capacity(st->batch, row + 1) != TF_OK) return TF_ERROR;
81
+
82
+ for (size_t c = 0; c < st->n_cols; c++) {
83
+ cJSON *val = cJSON_GetObjectItemCaseSensitive(obj, st->col_names[c]);
84
+ if (!val || cJSON_IsNull(val)) {
85
+ tf_batch_set_null(st->batch, row, c);
86
+ continue;
87
+ }
88
+ switch (st->col_types[c]) {
89
+ case TF_TYPE_BOOL:
90
+ tf_batch_set_bool(st->batch, row, c, cJSON_IsTrue(val));
91
+ break;
92
+ case TF_TYPE_INT64:
93
+ tf_batch_set_int64(st->batch, row, c, (int64_t)val->valuedouble);
94
+ break;
95
+ case TF_TYPE_FLOAT64:
96
+ tf_batch_set_float64(st->batch, row, c, val->valuedouble);
97
+ break;
98
+ case TF_TYPE_STRING:
99
+ if (cJSON_IsString(val))
100
+ tf_batch_set_string(st->batch, row, c, val->valuestring);
101
+ else {
102
+ char *printed = cJSON_PrintUnformatted(val);
103
+ tf_batch_set_string(st->batch, row, c, printed ? printed : "");
104
+ free(printed);
105
+ }
106
+ break;
107
+ default:
108
+ tf_batch_set_null(st->batch, row, c);
109
+ break;
110
+ }
111
+ }
112
+ st->batch->n_rows = row + 1;
113
+ st->rows_buffered++;
114
+ return TF_OK;
115
+ }
116
+
117
+ static int process_jsonl_line(jsonl_decoder_state *st, const char *line, size_t len,
118
+ tf_batch ***out, size_t *n_out, size_t *out_cap) {
119
+ /* Skip empty lines */
120
+ if (len == 0) return TF_OK;
121
+
122
+ /* Parse JSON */
123
+ cJSON *obj = cJSON_ParseWithLength(line, len);
124
+ if (!obj || !cJSON_IsObject(obj)) {
125
+ cJSON_Delete(obj);
126
+ return TF_OK; /* skip bad lines */
127
+ }
128
+
129
+ if (!st->schema_ready) {
130
+ /* Discover schema from first object */
131
+ int n = cJSON_GetArraySize(obj);
132
+ st->n_cols = (size_t)n;
133
+ st->col_names = malloc(n * sizeof(char *));
134
+ st->col_types = malloc(n * sizeof(tf_type));
135
+
136
+ int i = 0;
137
+ cJSON *item;
138
+ cJSON_ArrayForEach(item, obj) {
139
+ st->col_names[i] = strdup(item->string);
140
+ st->col_types[i] = json_to_type(item);
141
+ i++;
142
+ }
143
+ st->schema_ready = 1;
144
+ } else {
145
+ /* Update types from new row */
146
+ cJSON *item;
147
+ cJSON_ArrayForEach(item, obj) {
148
+ for (size_t c = 0; c < st->n_cols; c++) {
149
+ if (strcmp(st->col_names[c], item->string) == 0) {
150
+ st->col_types[c] = widen_type(st->col_types[c], json_to_type(item));
151
+ break;
152
+ }
153
+ }
154
+ }
155
+ }
156
+
157
+ /* Update batch column types (since they might have widened) */
158
+ if (st->batch) {
159
+ for (size_t c = 0; c < st->n_cols; c++) {
160
+ st->batch->col_types[c] = st->col_types[c];
161
+ }
162
+ }
163
+
164
+ int rc = add_json_row(st, obj);
165
+ cJSON_Delete(obj);
166
+ if (rc != TF_OK) return rc;
167
+
168
+ /* Emit batch if full */
169
+ if (st->rows_buffered >= st->batch_size) {
170
+ if (*n_out >= *out_cap) {
171
+ *out_cap = (*out_cap == 0) ? 4 : *out_cap * 2;
172
+ *out = realloc(*out, *out_cap * sizeof(tf_batch *));
173
+ }
174
+ (*out)[(*n_out)++] = st->batch;
175
+ st->batch = NULL;
176
+ st->rows_buffered = 0;
177
+ }
178
+
179
+ return TF_OK;
180
+ }
181
+
182
+ static int jsonl_decode(tf_decoder *self, const uint8_t *data, size_t len,
183
+ tf_batch ***out, size_t *n_out) {
184
+ jsonl_decoder_state *st = self->state;
185
+ *out = NULL;
186
+ *n_out = 0;
187
+
188
+ if (tf_buffer_write(&st->line_buf, data, len) != TF_OK) return TF_ERROR;
189
+
190
+ size_t out_cap = 0;
191
+ uint8_t *buf = st->line_buf.data + st->line_buf.read_pos;
192
+ size_t buf_len = st->line_buf.len - st->line_buf.read_pos;
193
+
194
+ size_t line_start = 0;
195
+ for (size_t i = 0; i < buf_len; i++) {
196
+ if (buf[i] == '\n' || buf[i] == '\r') {
197
+ size_t line_len = i - line_start;
198
+ if (buf[i] == '\r' && i + 1 < buf_len && buf[i + 1] == '\n') i++;
199
+ if (line_len > 0) {
200
+ if (process_jsonl_line(st, (const char *)buf + line_start, line_len,
201
+ out, n_out, &out_cap) != TF_OK)
202
+ return TF_ERROR;
203
+ }
204
+ line_start = i + 1;
205
+ }
206
+ }
207
+
208
+ st->line_buf.read_pos += line_start;
209
+ tf_buffer_compact(&st->line_buf);
210
+ return TF_OK;
211
+ }
212
+
213
+ static int jsonl_flush(tf_decoder *self, tf_batch ***out, size_t *n_out) {
214
+ jsonl_decoder_state *st = self->state;
215
+ *out = NULL;
216
+ *n_out = 0;
217
+ size_t out_cap = 0;
218
+
219
+ /* Process remaining data */
220
+ size_t remaining = tf_buffer_readable(&st->line_buf);
221
+ if (remaining > 0) {
222
+ uint8_t *buf = st->line_buf.data + st->line_buf.read_pos;
223
+ process_jsonl_line(st, (const char *)buf, remaining, out, n_out, &out_cap);
224
+ st->line_buf.read_pos = st->line_buf.len;
225
+ }
226
+
227
+ /* Emit remaining batch */
228
+ if (st->batch && st->rows_buffered > 0) {
229
+ if (*n_out >= out_cap) {
230
+ out_cap = (*n_out == 0) ? 1 : out_cap * 2;
231
+ *out = realloc(*out, out_cap * sizeof(tf_batch *));
232
+ }
233
+ (*out)[(*n_out)++] = st->batch;
234
+ st->batch = NULL;
235
+ st->rows_buffered = 0;
236
+ }
237
+
238
+ return TF_OK;
239
+ }
240
+
241
+ static void jsonl_decoder_destroy(tf_decoder *self) {
242
+ jsonl_decoder_state *st = self->state;
243
+ if (st) {
244
+ tf_buffer_free(&st->line_buf);
245
+ if (st->batch) tf_batch_free(st->batch);
246
+ for (size_t i = 0; i < st->n_cols; i++) free(st->col_names[i]);
247
+ free(st->col_names);
248
+ free(st->col_types);
249
+ free(st);
250
+ }
251
+ free(self);
252
+ }
253
+
254
+ tf_decoder *tf_jsonl_decoder_create(const cJSON *args) {
255
+ jsonl_decoder_state *st = calloc(1, sizeof(jsonl_decoder_state));
256
+ if (!st) return NULL;
257
+
258
+ st->batch_size = DEFAULT_BATCH_SIZE;
259
+ if (args) {
260
+ cJSON *bs = cJSON_GetObjectItemCaseSensitive(args, "batch_size");
261
+ if (cJSON_IsNumber(bs) && bs->valueint > 0)
262
+ st->batch_size = (size_t)bs->valueint;
263
+ }
264
+
265
+ tf_buffer_init(&st->line_buf);
266
+
267
+ tf_decoder *dec = malloc(sizeof(tf_decoder));
268
+ if (!dec) { free(st); return NULL; }
269
+ dec->decode = jsonl_decode;
270
+ dec->flush = jsonl_flush;
271
+ dec->destroy = jsonl_decoder_destroy;
272
+ dec->state = st;
273
+ return dec;
274
+ }
275
+
276
+ /* ================================================================
277
+ * JSONL Encoder
278
+ * ================================================================ */
279
+
280
+ static int jsonl_encode(tf_encoder *self, tf_batch *in, tf_buffer *out) {
281
+ (void)self;
282
+ char numbuf[64];
283
+
284
+ for (size_t r = 0; r < in->n_rows; r++) {
285
+ tf_buffer_write(out, (const uint8_t *)"{", 1);
286
+ for (size_t c = 0; c < in->n_cols; c++) {
287
+ if (c > 0) tf_buffer_write(out, (const uint8_t *)",", 1);
288
+
289
+ /* Key */
290
+ tf_buffer_write(out, (const uint8_t *)"\"", 1);
291
+ tf_buffer_write_str(out, in->col_names[c]);
292
+ tf_buffer_write(out, (const uint8_t *)"\":", 2);
293
+
294
+ /* Value */
295
+ if (tf_batch_is_null(in, r, c)) {
296
+ tf_buffer_write_str(out, "null");
297
+ continue;
298
+ }
299
+ switch (in->col_types[c]) {
300
+ case TF_TYPE_BOOL:
301
+ tf_buffer_write_str(out, tf_batch_get_bool(in, r, c) ? "true" : "false");
302
+ break;
303
+ case TF_TYPE_INT64:
304
+ snprintf(numbuf, sizeof(numbuf), "%lld",
305
+ (long long)tf_batch_get_int64(in, r, c));
306
+ tf_buffer_write_str(out, numbuf);
307
+ break;
308
+ case TF_TYPE_FLOAT64:
309
+ snprintf(numbuf, sizeof(numbuf), "%g",
310
+ tf_batch_get_float64(in, r, c));
311
+ tf_buffer_write_str(out, numbuf);
312
+ break;
313
+ case TF_TYPE_STRING: {
314
+ const char *s = tf_batch_get_string(in, r, c);
315
+ tf_buffer_write(out, (const uint8_t *)"\"", 1);
316
+ /* Escape special characters */
317
+ for (const char *p = s; *p; p++) {
318
+ switch (*p) {
319
+ case '"': tf_buffer_write_str(out, "\\\""); break;
320
+ case '\\': tf_buffer_write_str(out, "\\\\"); break;
321
+ case '\n': tf_buffer_write_str(out, "\\n"); break;
322
+ case '\r': tf_buffer_write_str(out, "\\r"); break;
323
+ case '\t': tf_buffer_write_str(out, "\\t"); break;
324
+ default: tf_buffer_write(out, (const uint8_t *)p, 1); break;
325
+ }
326
+ }
327
+ tf_buffer_write(out, (const uint8_t *)"\"", 1);
328
+ break;
329
+ }
330
+ case TF_TYPE_DATE: {
331
+ char dbuf[16];
332
+ tf_date_format(tf_batch_get_date(in, r, c), dbuf, sizeof(dbuf));
333
+ tf_buffer_write(out, (const uint8_t *)"\"", 1);
334
+ tf_buffer_write_str(out, dbuf);
335
+ tf_buffer_write(out, (const uint8_t *)"\"", 1);
336
+ break;
337
+ }
338
+ case TF_TYPE_TIMESTAMP: {
339
+ char tsbuf[40];
340
+ tf_timestamp_format(tf_batch_get_timestamp(in, r, c), tsbuf, sizeof(tsbuf));
341
+ tf_buffer_write(out, (const uint8_t *)"\"", 1);
342
+ tf_buffer_write_str(out, tsbuf);
343
+ tf_buffer_write(out, (const uint8_t *)"\"", 1);
344
+ break;
345
+ }
346
+ default:
347
+ tf_buffer_write_str(out, "null");
348
+ break;
349
+ }
350
+ }
351
+ tf_buffer_write(out, (const uint8_t *)"}\n", 2);
352
+ }
353
+ return TF_OK;
354
+ }
355
+
356
+ static int jsonl_encoder_flush(tf_encoder *self, tf_buffer *out) {
357
+ (void)self; (void)out;
358
+ return TF_OK;
359
+ }
360
+
361
+ static void jsonl_encoder_destroy(tf_encoder *self) {
362
+ free(self);
363
+ }
364
+
365
+ tf_encoder *tf_jsonl_encoder_create(const cJSON *args) {
366
+ (void)args;
367
+ tf_encoder *enc = malloc(sizeof(tf_encoder));
368
+ if (!enc) return NULL;
369
+ enc->encode = jsonl_encode;
370
+ enc->flush = jsonl_encoder_flush;
371
+ enc->destroy = jsonl_encoder_destroy;
372
+ enc->state = NULL;
373
+ return enc;
374
+ }
@@ -0,0 +1,218 @@
1
+ /*
2
+ * codec_table.c — Pretty-print Markdown-compatible table encoder.
3
+ *
4
+ * Buffers all rows to compute column widths, then on flush emits
5
+ * a formatted table:
6
+ *
7
+ * | name | age | city |
8
+ * | ------- | --- | ---- |
9
+ * | Alice | 30 | NY |
10
+ * | Bob | 25 | LA |
11
+ *
12
+ * Args:
13
+ * max_width (int, optional) — truncate columns wider than this (default: 40)
14
+ * max_rows (int, optional) — limit output rows (default: 0 = unlimited)
15
+ */
16
+
17
+ #include "internal.h"
18
+ #include "cJSON.h"
19
+ #include "date_utils.h"
20
+ #include <stdlib.h>
21
+ #include <string.h>
22
+ #include <stdio.h>
23
+
24
+ #define TABLE_MAX_COLS 256
25
+ #define TABLE_DEFAULT_WIDTH 40
26
+
27
+ typedef struct {
28
+ char **values; /* flat array: rows × n_cols, each cell is a malloc'd string */
29
+ size_t n_rows;
30
+ size_t n_cols;
31
+ size_t capacity; /* allocated rows */
32
+ char **col_names;
33
+ size_t max_width;
34
+ size_t max_rows;
35
+ } table_encoder_state;
36
+
37
+ /* Format a cell value as a string. Caller must free. */
38
+ static char *cell_to_string(tf_batch *b, size_t row, size_t col) {
39
+ if (tf_batch_is_null(b, row, col)) return strdup("");
40
+
41
+ char buf[128];
42
+ switch (b->col_types[col]) {
43
+ case TF_TYPE_BOOL:
44
+ return strdup(tf_batch_get_bool(b, row, col) ? "true" : "false");
45
+ case TF_TYPE_INT64:
46
+ snprintf(buf, sizeof(buf), "%lld", (long long)tf_batch_get_int64(b, row, col));
47
+ return strdup(buf);
48
+ case TF_TYPE_FLOAT64:
49
+ snprintf(buf, sizeof(buf), "%g", tf_batch_get_float64(b, row, col));
50
+ return strdup(buf);
51
+ case TF_TYPE_STRING:
52
+ return strdup(tf_batch_get_string(b, row, col));
53
+ case TF_TYPE_DATE: {
54
+ char dbuf[16];
55
+ tf_date_format(tf_batch_get_date(b, row, col), dbuf, sizeof(dbuf));
56
+ return strdup(dbuf);
57
+ }
58
+ case TF_TYPE_TIMESTAMP: {
59
+ char tsbuf[40];
60
+ tf_timestamp_format(tf_batch_get_timestamp(b, row, col), tsbuf, sizeof(tsbuf));
61
+ return strdup(tsbuf);
62
+ }
63
+ default:
64
+ return strdup("");
65
+ }
66
+ }
67
+
68
+ static int table_encode(tf_encoder *self, tf_batch *in, tf_buffer *out) {
69
+ table_encoder_state *st = self->state;
70
+ (void)out;
71
+
72
+ /* Capture column names on first batch */
73
+ if (!st->col_names && in->n_cols > 0) {
74
+ st->n_cols = in->n_cols < TABLE_MAX_COLS ? in->n_cols : TABLE_MAX_COLS;
75
+ st->col_names = malloc(st->n_cols * sizeof(char *));
76
+ for (size_t i = 0; i < st->n_cols; i++) {
77
+ st->col_names[i] = strdup(in->col_names[i]);
78
+ }
79
+ }
80
+
81
+ /* Buffer all cell values as strings */
82
+ for (size_t r = 0; r < in->n_rows; r++) {
83
+ if (st->max_rows > 0 && st->n_rows >= st->max_rows) break;
84
+
85
+ if (st->n_rows >= st->capacity) {
86
+ size_t new_cap = st->capacity == 0 ? 64 : st->capacity * 2;
87
+ st->values = realloc(st->values, new_cap * st->n_cols * sizeof(char *));
88
+ if (!st->values) return TF_ERROR;
89
+ st->capacity = new_cap;
90
+ }
91
+
92
+ size_t base = st->n_rows * st->n_cols;
93
+ for (size_t c = 0; c < st->n_cols; c++) {
94
+ st->values[base + c] = cell_to_string(in, r, c);
95
+ }
96
+ st->n_rows++;
97
+ }
98
+
99
+ return TF_OK;
100
+ }
101
+
102
+ static int table_flush(tf_encoder *self, tf_buffer *out) {
103
+ table_encoder_state *st = self->state;
104
+ if (!st->col_names || st->n_cols == 0) return TF_OK;
105
+
106
+ /* Compute column widths */
107
+ size_t *widths = calloc(st->n_cols, sizeof(size_t));
108
+ for (size_t c = 0; c < st->n_cols; c++) {
109
+ widths[c] = strlen(st->col_names[c]);
110
+ }
111
+ for (size_t r = 0; r < st->n_rows; r++) {
112
+ for (size_t c = 0; c < st->n_cols; c++) {
113
+ size_t len = strlen(st->values[r * st->n_cols + c]);
114
+ if (len > widths[c]) widths[c] = len;
115
+ }
116
+ }
117
+ /* Apply max_width */
118
+ for (size_t c = 0; c < st->n_cols; c++) {
119
+ if (st->max_width > 0 && widths[c] > st->max_width)
120
+ widths[c] = st->max_width;
121
+ }
122
+
123
+ char pad[256];
124
+
125
+ /* Header row */
126
+ tf_buffer_write(out, (const uint8_t *)"| ", 2);
127
+ for (size_t c = 0; c < st->n_cols; c++) {
128
+ if (c > 0) tf_buffer_write(out, (const uint8_t *)" | ", 3);
129
+ const char *name = st->col_names[c];
130
+ size_t nlen = strlen(name);
131
+ size_t w = widths[c];
132
+ if (nlen > w) nlen = w;
133
+ tf_buffer_write(out, (const uint8_t *)name, nlen);
134
+ if (nlen < w) {
135
+ size_t plen = w - nlen;
136
+ if (plen > sizeof(pad)) plen = sizeof(pad);
137
+ memset(pad, ' ', plen);
138
+ tf_buffer_write(out, (const uint8_t *)pad, plen);
139
+ }
140
+ }
141
+ tf_buffer_write(out, (const uint8_t *)" |\n", 3);
142
+
143
+ /* Separator row */
144
+ tf_buffer_write(out, (const uint8_t *)"| ", 2);
145
+ for (size_t c = 0; c < st->n_cols; c++) {
146
+ if (c > 0) tf_buffer_write(out, (const uint8_t *)" | ", 3);
147
+ size_t w = widths[c];
148
+ if (w > sizeof(pad)) w = sizeof(pad);
149
+ memset(pad, '-', w);
150
+ tf_buffer_write(out, (const uint8_t *)pad, w);
151
+ }
152
+ tf_buffer_write(out, (const uint8_t *)" |\n", 3);
153
+
154
+ /* Data rows */
155
+ for (size_t r = 0; r < st->n_rows; r++) {
156
+ tf_buffer_write(out, (const uint8_t *)"| ", 2);
157
+ for (size_t c = 0; c < st->n_cols; c++) {
158
+ if (c > 0) tf_buffer_write(out, (const uint8_t *)" | ", 3);
159
+ const char *val = st->values[r * st->n_cols + c];
160
+ size_t vlen = strlen(val);
161
+ size_t w = widths[c];
162
+ if (vlen > w) vlen = w;
163
+ tf_buffer_write(out, (const uint8_t *)val, vlen);
164
+ if (vlen < w) {
165
+ size_t plen = w - vlen;
166
+ if (plen > sizeof(pad)) plen = sizeof(pad);
167
+ memset(pad, ' ', plen);
168
+ tf_buffer_write(out, (const uint8_t *)pad, plen);
169
+ }
170
+ }
171
+ tf_buffer_write(out, (const uint8_t *)" |\n", 3);
172
+ }
173
+
174
+ free(widths);
175
+ return TF_OK;
176
+ }
177
+
178
+ static void table_encoder_destroy(tf_encoder *self) {
179
+ table_encoder_state *st = self->state;
180
+ if (st) {
181
+ if (st->col_names) {
182
+ for (size_t i = 0; i < st->n_cols; i++) free(st->col_names[i]);
183
+ free(st->col_names);
184
+ }
185
+ if (st->values) {
186
+ for (size_t i = 0; i < st->n_rows * st->n_cols; i++) free(st->values[i]);
187
+ free(st->values);
188
+ }
189
+ free(st);
190
+ }
191
+ free(self);
192
+ }
193
+
194
+ tf_encoder *tf_table_encoder_create(const cJSON *args) {
195
+ table_encoder_state *st = calloc(1, sizeof(table_encoder_state));
196
+ if (!st) return NULL;
197
+
198
+ st->max_width = TABLE_DEFAULT_WIDTH;
199
+ st->max_rows = 0;
200
+
201
+ if (args) {
202
+ cJSON *mw = cJSON_GetObjectItemCaseSensitive(args, "max_width");
203
+ if (cJSON_IsNumber(mw) && mw->valueint > 0)
204
+ st->max_width = (size_t)mw->valueint;
205
+
206
+ cJSON *mr = cJSON_GetObjectItemCaseSensitive(args, "max_rows");
207
+ if (cJSON_IsNumber(mr) && mr->valueint > 0)
208
+ st->max_rows = (size_t)mr->valueint;
209
+ }
210
+
211
+ tf_encoder *enc = malloc(sizeof(tf_encoder));
212
+ if (!enc) { free(st); return NULL; }
213
+ enc->encode = table_encode;
214
+ enc->flush = table_flush;
215
+ enc->destroy = table_encoder_destroy;
216
+ enc->state = st;
217
+ return enc;
218
+ }