Vendored sashidown, OpenAI-compatible env (OPENAI_*), and bootstrap scripts. Co-authored-by: Cursor <cursoragent@cursor.com>
138 lines
3.7 KiB
C
138 lines
3.7 KiB
C
#include "table_stats.h"
|
|
#include "line_index.h"
|
|
#include <string.h>
|
|
|
|
int table_is_separator_row(const char *line, size_t len)
|
|
{
|
|
int pipes = 0;
|
|
int dashes = 0;
|
|
for (size_t i = 0; i < len; i++) {
|
|
char c = line[i];
|
|
if (c == '|') pipes++;
|
|
else if (c == '-' || c == ':' || c == ' ') dashes++;
|
|
else if (c != '\r') return 0;
|
|
}
|
|
return pipes >= 1 && dashes > 0;
|
|
}
|
|
|
|
/* GFM: leading/trailing '|' are delimiters, not empty cells. */
|
|
int table_count_cols(const char *line, size_t len, int *has_pipe)
|
|
{
|
|
if (has_pipe)
|
|
*has_pipe = 0;
|
|
|
|
while (len > 0 && line[len - 1] == '\r')
|
|
len--;
|
|
|
|
size_t start = 0;
|
|
size_t end = len;
|
|
while (start < end && (line[start] == ' ' || line[start] == '\t'))
|
|
start++;
|
|
if (start < end && line[start] == '|') {
|
|
start++;
|
|
if (has_pipe)
|
|
*has_pipe = 1;
|
|
}
|
|
while (end > start && (line[end - 1] == ' ' || line[end - 1] == '\t'))
|
|
end--;
|
|
if (end > start && line[end - 1] == '|') {
|
|
end--;
|
|
if (has_pipe)
|
|
*has_pipe = 1;
|
|
}
|
|
|
|
if (start >= end)
|
|
return 1;
|
|
|
|
int cols = 1;
|
|
for (size_t i = start; i < end; i++) {
|
|
if (line[i] == '|') {
|
|
cols++;
|
|
if (has_pipe)
|
|
*has_pipe = 1;
|
|
}
|
|
}
|
|
return cols > SASHIDOWN_MAX_COLS ? SASHIDOWN_MAX_COLS : cols;
|
|
}
|
|
|
|
static size_t te_line_end(sashidown_ctx_t *ctx, const char *input, size_t len, size_t pos)
|
|
{
|
|
if (ctx && ctx->line_ix)
|
|
return line_end_ix((line_index_t *)ctx->line_ix, input, len, pos);
|
|
const char *hit = memchr(input + pos, '\n', len - pos);
|
|
return hit ? (size_t)(hit - input) : len;
|
|
}
|
|
|
|
static size_t te_skip_line(sashidown_ctx_t *ctx, const char *input, size_t len, size_t pos)
|
|
{
|
|
if (ctx && ctx->line_ix)
|
|
return skip_line_ix((line_index_t *)ctx->line_ix, input, len, pos);
|
|
pos = te_line_end(NULL, input, len, pos);
|
|
if (pos < len)
|
|
pos++;
|
|
return pos;
|
|
}
|
|
|
|
static int line_has_pipe_raw(const char *line, size_t len)
|
|
{
|
|
return memchr(line, '|', len) != NULL;
|
|
}
|
|
|
|
static int try_parse_table(sashidown_ctx_t *ctx, const char *input, size_t len,
|
|
size_t line_start, table_entry_t *e)
|
|
{
|
|
size_t le = te_line_end(ctx, input, len, line_start);
|
|
int has_pipe = 0;
|
|
int cols = table_count_cols(input + line_start, le - line_start, &has_pipe);
|
|
if (!has_pipe)
|
|
return 0;
|
|
|
|
size_t sep = te_skip_line(ctx, input, len, line_start);
|
|
if (sep >= len) {
|
|
if (ctx && ctx->line_ix)
|
|
line_index_seek((line_index_t *)ctx->line_ix, line_start);
|
|
return 0;
|
|
}
|
|
size_t sep_le = te_line_end(ctx, input, len, sep);
|
|
if (!table_is_separator_row(input + sep, sep_le - sep)) {
|
|
if (ctx && ctx->line_ix)
|
|
line_index_seek((line_index_t *)ctx->line_ix, line_start);
|
|
return 0;
|
|
}
|
|
|
|
size_t pos = te_skip_line(ctx, input, len, sep);
|
|
|
|
e->start = line_start;
|
|
e->body_start = pos;
|
|
e->cols = cols; /* GFM: header determines column count */
|
|
e->body_rows = 0;
|
|
e->valid = 1;
|
|
|
|
while (pos < len) {
|
|
le = te_line_end(ctx, input, len, pos);
|
|
size_t line_len = le - pos;
|
|
if (!line_has_pipe_raw(input + pos, line_len))
|
|
break;
|
|
if (table_is_separator_row(input + pos, line_len))
|
|
break;
|
|
|
|
e->body_rows++;
|
|
pos = te_skip_line(ctx, input, len, pos);
|
|
}
|
|
|
|
e->end = pos;
|
|
return 1;
|
|
}
|
|
|
|
int table_try_parse_at(sashidown_ctx_t *ctx, const char *input, size_t len,
|
|
size_t pos, table_entry_t *e)
|
|
{
|
|
e->start = 0;
|
|
e->end = 0;
|
|
e->body_start = 0;
|
|
e->cols = 0;
|
|
e->body_rows = 0;
|
|
e->valid = 0;
|
|
return try_parse_table(ctx, input, len, pos, e);
|
|
}
|