From b55395802f4c7b929c45013efb572aab786a8b43 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Sat, 26 Sep 2026 10:49:02 +0100 Subject: Add vega check for conventions a spec misses Surfaces read a view that misses a convention as something else without a word: a number label makes a list a table, a text tone disappears, a list never shows its other columns, X_focus without a table becomes a field, a second text input has nowhere to type. vega check MODULE names each such place (the view or input and what the surface does instead) and exits 1; the rules sit next to the code that reads the screen. The checks build a spec that misses eight conventions and expect exactly its nine findings, and none in the examples. --- src/main.c | 21 ++++++++++++++++++--- src/screen.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/vega.h | 5 +++++ 3 files changed, 79 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/main.c b/src/main.c index 1d8cc4b..3fcf105 100644 --- a/src/main.c +++ b/src/main.c @@ -11,6 +11,7 @@ #include /* vega SURFACE MODULE [--store PATH]: one module, one screen, several surfaces. + * check what the module seems to mean but does not meet (exit 1) * tui interactive terminal * tui --dump WxH the terminal's frames as text, driven by a script * cli plain lines for pipes, driven by a script @@ -29,7 +30,7 @@ static char entry[VEGA_TEXT]; static size_t entry_length; static bool typing; -static bool load(const char *path, const char *store) { +static bool load_module(const char *path) { static unsigned char bytes[1 << 17]; FILE *file = fopen(path, "rb"); size_t size = file ? fread(bytes, 1, sizeof(bytes), file) : 0; @@ -41,6 +42,15 @@ static bool load(const char *path, const char *store) { fprintf(stderr, "vega: %s: %s\n", path, error); return false; } + return true; +} + +static void print_finding(void *path, const char *name, const char *message) { + printf("%s: %s: %s\n", (const char *)path, name, message); +} + +static bool load(const char *path, const char *store) { + if (!load_module(path)) return false; if (store && pure_store_init(store) != PURE_OK) { fprintf(stderr, "vega: cannot open store %s\n", store); return false; } PureResult started = pure_app_start(&app, &module, store ? pure_tree_data(store) : (PureData){0}); if (started != PURE_OK) { fprintf(stderr, "vega: cannot start (%d)\n", (int)started); return false; } @@ -376,11 +386,16 @@ int main(int argc, char **argv) { width <= 1000 && height <= 1000) dumped = true; else usage = true; } - if (usage || (dumped && strcmp(argv[1], "tui"))) { + if (usage || (dumped && strcmp(argv[1], "tui")) || (!strcmp(argv[1], "check") && argc != 3)) { fputs("vega tui|cli|say MODULE.pmod [--store PATH]\n" - "vega tui MODULE.pmod [--store PATH] --dump WIDTHxHEIGHT < script\n", stderr); + "vega tui MODULE.pmod [--store PATH] --dump WIDTHxHEIGHT < script\n" + "vega check MODULE.pmod\n", stderr); return 2; } + if (!strcmp(argv[1], "check")) { + if (!load_module(argv[2])) return 2; + return vega_check(&module, print_finding, argv[2]) ? 1 : 0; + } Surface surface = !strcmp(argv[1], "cli") ? SURFACE_CLI : !strcmp(argv[1], "say") ? SURFACE_SAY : !strcmp(argv[1], "tui") ? SURFACE_TUI : (Surface)-1; if ((int)surface < 0) { fprintf(stderr, "vega: unknown surface %s\n", argv[1]); return 2; } diff --git a/src/screen.c b/src/screen.c index 8e584a5..2d4c6dc 100644 --- a/src/screen.c +++ b/src/screen.c @@ -50,6 +50,62 @@ void vega_screen(VegaScreen *screen, const PureModule *m) { } } +static bool is_view(const PureModule *m, int view, uint8_t type) { + return view >= 0 && m->view_type[view] == type; +} + +unsigned vega_check(const PureModule *m, void (*report)(void *, const char *name, const char *message), void *context) { + unsigned found = 0; + char name[2 * PURE_MODULE_NAME + 8], message[256]; +#define FIND(...) do { snprintf(message, sizeof(message), __VA_ARGS__); report(context, name, message); ++found; } while (0) + int title = pure_module_view(m, "title"); + snprintf(name, sizeof(name), "title"); + if (title >= 0 && m->view_type[title] != PURE_VIEW_TEXT) FIND("a title is text; this one shows as a field"); + for (uint32_t i = 0; i < m->views; ++i) { + if (m->view_table[i]) continue; + const char *view = m->view_names[i]; + size_t length = strlen(view); + snprintf(name, sizeof(name), "%s", view); + if (length > 6 && !strcmp(view + length - 6, "_focus")) { + char table[PURE_MODULE_NAME]; + snprintf(table, sizeof(table), "%.*s", (int)(length - 6), view); + int owner = pure_module_view(m, table); + if (!is_view(m, owner, PURE_VIEW_TABLE)) + FIND("names no list or table %s; it shows as a field", table); + else if (m->view_type[i] != PURE_VIEW_NUM) FIND("the focused row is a number; %s shows no focus", table); + } + if (m->view_type[i] != PURE_VIEW_TABLE) continue; + int label = pure_module_column(m, i, "label"), tone = pure_module_column(m, i, "tone"); + bool list = is_view(m, label, PURE_VIEW_TEXT); + unsigned shown = 0; + for (uint32_t k = 0; k < m->views; ++k) { + if (m->view_table[k] != i + 1) continue; + const char *column = m->view_names[k]; + bool text = m->view_type[k] == PURE_VIEW_TEXT; + snprintf(name, sizeof(name), "%s.%s", view, column); + if ((int)k == label && !text) FIND("a list's label is text; %s shows as a table", view); + else if ((int)k == tone && text) FIND("a tone is a number (vega.normal ... vega.danger); %s", list ? + "the list ignores it" : "it shows as a column"); + else if (list && (!strcmp(column, "badge") || !strcmp(column, "detail")) && !text) + FIND("a list's %s is text; this one is not shown", column); + else if (list && (int)k != label && (int)k != tone && strcmp(column, "badge") && strcmp(column, "detail")) + FIND("a list shows label, badge, detail and tone; this column is not shown"); + shown += (int)k != tone || text; + } + snprintf(name, sizeof(name), "%s", view); + if (!list && !shown) FIND("a table with no column to show"); + } + int prompt = -1; + for (uint32_t i = 0; i < m->inputs; ++i) { + if (m->input[i].kind != PURE_KIND_TEXT) continue; + snprintf(name, sizeof(name), "%s", m->input_names[i]); + if (prompt >= 0) FIND("only the first text input (%s) has a place to type into", m->input_names[prompt]); + else prompt = (int)i; + } +#undef FIND + return found; +} + typedef struct { char *out; size_t used; } Collect; static bool collect(void *context, PureSpan span) { diff --git a/src/vega.h b/src/vega.h index 40aecaa..062639b 100644 --- a/src/vega.h +++ b/src/vega.h @@ -34,6 +34,11 @@ typedef struct { } VegaScreen; void vega_screen(VegaScreen *, const PureModule *); +/* What a module seems to mean but does not meet: a list label that is not + * text, a _focus view without its table, a column a list never shows, a + * second text input. Each finding names the view or input it is about; + * returns how many there were. */ +unsigned vega_check(const PureModule *, void (*report)(void *, const char *name, const char *message), void *context); /* A view (row ignored) or a column at a row as text; numbers in Pure's * shortest form. false when it has no value (for example a missing item). */ -- cgit v1.2.3