OverviewFilesCommitsIssuesDocsReleasesPeers

basal-agent / src / sensor.c

122 lines · c ref:
/* sensor.c — see sensor.h. Pure POSIX/libc. */
#define _POSIX_C_SOURCE 200809L   /* strdup, lstat under -std=c11 */
#include "sensor.h"

#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

/* ---- feature interning ---- */

static int intern(FeatureTable *t, const char *s) {
	for (size_t i = 0; i < t->count; i++)
		if (strcmp(t->names[i], s) == 0) return (int)i;
	if (t->count == t->cap) {
		t->cap = t->cap ? t->cap * 2 : 32;
		t->names = realloc(t->names, t->cap * sizeof *t->names);
	}
	t->names[t->count] = strdup(s);
	return (int)t->count++;
}

const char *ext_name(const Corpus *c, int f) {
	return (f < 0 || (size_t)f >= c->ext.count) ? "?" : c->ext.names[f];
}
const char *dir_name(const Corpus *c, int f) {
	return (f < 0 || (size_t)f >= c->dir.count) ? "?" : c->dir.names[f];
}

/* lowercase extension after last '.' in basename; "<none>" if absent. */
static void ext_of(const char *path, char *out, size_t n) {
	const char *base = strrchr(path, '/');
	base = base ? base + 1 : path;
	const char *dot = strrchr(base, '.');
	if (!dot || dot == base) { snprintf(out, n, "<none>"); return; }
	size_t k = 0;
	for (const char *p = dot + 1; *p && k + 1 < n; p++) {
		char ch = *p;
		out[k++] = (ch >= 'A' && ch <= 'Z') ? ch + 32 : ch;
	}
	out[k] = '\0';
}

/* basename of the parent directory. */
static void dir_of(const char *path, char *out, size_t n) {
	const char *slash = strrchr(path, '/');
	if (!slash || slash == path) { snprintf(out, n, "<root>"); return; }
	const char *p = slash - 1;
	while (p > path && *p != '/') p--;
	if (*p == '/') p++;
	size_t k = 0;
	for (const char *q = p; q < slash && k + 1 < n; q++) out[k++] = *q;
	out[k] = '\0';
}

/* ---- corpus collection ---- */

static void push(Corpus *c, const char *path, int fe, int fd) {
	if (c->count == c->cap) {
		c->cap = c->cap ? c->cap * 2 : 256;
		c->files = realloc(c->files, c->cap * sizeof *c->files);
	}
	c->files[c->count++] = (File){ .path = strdup(path), .f_ext = fe, .f_dir = fd, .klass = -1 };
}

static void walk(Corpus *c, const char *dir) {
	DIR *d = opendir(dir);
	if (!d) return;
	struct dirent *e;
	char path[4096], ext[64], dname[256];
	while ((e = readdir(d))) {
		if (e->d_name[0] == '.') continue;
		snprintf(path, sizeof path, "%s/%s", dir, e->d_name);
		struct stat st;
		if (lstat(path, &st) != 0) continue;
		if (S_ISDIR(st.st_mode)) {
			walk(c, path);
		} else if (S_ISREG(st.st_mode)) {
			ext_of(path, ext, sizeof ext);
			dir_of(path, dname, sizeof dname);
			push(c, path, intern(&c->ext, ext), intern(&c->dir, dname));
		}
	}
	closedir(d);
}

int corpus_collect(Corpus *c, const char *root) {
	memset(c, 0, sizeof *c);
	walk(c, root);
	return c->count > 0 ? 0 : -1;
}

void corpus_free(Corpus *c) {
	for (size_t i = 0; i < c->count; i++) free(c->files[i].path);
	for (size_t i = 0; i < c->ext.count; i++) free(c->ext.names[i]);
	for (size_t i = 0; i < c->dir.count; i++) free(c->dir.names[i]);
	free(c->files); free(c->ext.names); free(c->dir.names);
	memset(c, 0, sizeof *c);
}

/* ---- the observation: is any ancestor dir a build/artifact marker? ---- */

static const char *MARKERS[] = {
	"/node_modules/", "/dist/", "/build/", "/__pycache__/", "/.git/",
	"/target/", "/vendor/", "/.cache/", "/out/", "/obj/", "/coverage/",
	"/.next/", "/.tox/", "/.venv/", "/generated/", "/bin/", 0
};

static int path_is_generated(const char *path) {
	for (int i = 0; MARKERS[i]; i++)
		if (strstr(path, MARKERS[i])) return 1;
	return 0;
}

int sensor_open(Corpus *c, size_t i) {
	File *f = &c->files[i];
	if (f->klass >= 0) return f->klass;
	f->klass = path_is_generated(f->path) ? CLASS_GENERATED : CLASS_SOURCE;
	return f->klass;
}