ludic/tools/ludic-tools/ludic_fmt_main.c
Orkuncakilkaya 985f9ad8f2 Baseline: Ludic compiler + toolchain, Phase 1 syntax fixes complete
Self-hosted compiler (selfhost/*.ludic), runtime, examples, editor tooling,
and docs. Phase 1 of the syntax-redesign cohesion pass has landed:
edge-system fix, signature-query, when-alias, and the documentation truth-pass.
Suite green (14/14), C-free bootstrap fixpoint holds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-27 15:15:35 +03:00

118 lines
4.7 KiB
C

/* ============================================================================
* ludic-fmt — the Ludic formatter, as a plain CLI.
*
* Editors call this through the language server, but CI and pre-commit hooks
* want a binary they can run:
*
* ludic-fmt a.ludic b.ludic print the formatted text
* ludic-fmt -w examples rewrite every .ludic under examples/
* ludic-fmt --check . exit 1 if anything is unformatted
* ludic-fmt LANGUAGE.md -w reformat the ```ludic fences in a document
* cat x.ludic | ludic-fmt - filter mode, for editors without LSP
* ==========================================================================*/
#include "ludic_fmt.h"
#include <dirent.h>
#include <sys/stat.h>
#include <limits.h>
static int g_write = 0, g_check = 0, g_indent = 2, g_quiet = 0;
static int g_changed = 0, g_failed = 0;
static char* slurp(FILE* f){
FSB s = {0};
char buf[65536]; size_t n;
while ((n = fread(buf, 1, sizeof(buf), f)) > 0) fsb_add(&s, buf, n);
if (!s.b){ s.b = malloc(1); s.b[0] = 0; }
return s.b;
}
static int is_md(const char* p){
size_t n = strlen(p);
return (n > 3 && !strcmp(p + n - 3, ".md")) || (n > 9 && !strcmp(p + n - 9, ".markdown"));
}
static int is_ludic(const char* p){
size_t n = strlen(p);
return n > 6 && !strcmp(p + n - 6, ".ludic");
}
static void do_file(const char* path){
FILE* f = fopen(path, "rb");
if (!f){ fprintf(stderr, "ludic-fmt: cannot open %s\n", path); g_failed = 1; return; }
char* src = slurp(f); fclose(f);
char* out = is_md(path) ? ludic_format_markdown(src, g_indent) : ludic_format(src, g_indent);
int same = !strcmp(src, out);
if (g_check){
if (!same){ g_changed = 1; if (!g_quiet) printf("%s\n", path); }
} else if (g_write){
if (!same){
FILE* w = fopen(path, "wb");
if (!w){ fprintf(stderr, "ludic-fmt: cannot write %s\n", path); g_failed = 1; }
else { fwrite(out, 1, strlen(out), w); fclose(w); if (!g_quiet) fprintf(stderr, "formatted %s\n", path); }
g_changed = 1;
}
} else {
fwrite(out, 1, strlen(out), stdout);
}
free(src); free(out);
}
static void do_dir(const char* dir, int depth){
if (depth > 16) return;
DIR* d = opendir(dir);
if (!d){ fprintf(stderr, "ludic-fmt: cannot open %s\n", dir); g_failed = 1; return; }
struct dirent* e;
while ((e = readdir(d))){
if (e->d_name[0] == '.') continue;
if (!strcmp(e->d_name, "build") || !strcmp(e->d_name, "node_modules")) continue;
char p[PATH_MAX]; snprintf(p, sizeof(p), "%s/%s", dir, e->d_name);
struct stat st; if (stat(p, &st)) continue;
if (S_ISDIR(st.st_mode)) do_dir(p, depth + 1);
else if (is_ludic(p)) do_file(p);
}
closedir(d);
}
static const char* USAGE =
"ludic-fmt — format Ludic source\n"
"\n"
"usage: ludic-fmt [options] [file|dir ...]\n"
" ludic-fmt - read stdin, write stdout\n"
"\n"
"options:\n"
" -w, --write rewrite files in place\n"
" --check list unformatted files; exit 1 if any (implies no output)\n"
" --indent N spaces per level (default 2)\n"
" -q, --quiet no per-file chatter\n"
" -h, --help this text\n"
"\n"
"Directories are walked for *.ludic. A .md/.markdown file has the body of every\n"
"```ludic fence formatted and the prose left alone.\n";
int main(int argc, char** argv){
const char* files[4096]; int nf = 0;
for (int i = 1; i < argc; i++){
const char* a = argv[i];
if (!strcmp(a, "-w") || !strcmp(a, "--write")) g_write = 1;
else if (!strcmp(a, "--check") || !strcmp(a, "-l")) g_check = 1;
else if (!strcmp(a, "-q") || !strcmp(a, "--quiet")) g_quiet = 1;
else if (!strcmp(a, "--indent") && i + 1 < argc) g_indent = atoi(argv[++i]);
else if (!strcmp(a, "-h") || !strcmp(a, "--help")){ fputs(USAGE, stdout); return 0; }
else if (a[0] == '-' && a[1] && strcmp(a, "-")){ fprintf(stderr, "ludic-fmt: unknown option %s\n", a); return 2; }
else if (nf < 4096) files[nf++] = a;
}
if (g_indent < 1 || g_indent > 8) g_indent = 2;
if (nf == 0 || (nf == 1 && !strcmp(files[0], "-"))){
char* src = slurp(stdin);
char* out = ludic_format(src, g_indent);
fwrite(out, 1, strlen(out), stdout);
free(src); free(out);
return 0;
}
for (int i = 0; i < nf; i++){
struct stat st;
if (stat(files[i], &st)){ fprintf(stderr, "ludic-fmt: no such file %s\n", files[i]); g_failed = 1; continue; }
if (S_ISDIR(st.st_mode)) do_dir(files[i], 0);
else do_file(files[i]);
}
if (g_failed) return 2;
return (g_check && g_changed) ? 1 : 0;
}