Skip to content

Instantly share code, notes, and snippets.

@wpcarro
Created September 4, 2025 19:19
Show Gist options
  • Select an option

  • Save wpcarro/a5922ee1692b1bbdf733648b280b07e0 to your computer and use it in GitHub Desktop.

Select an option

Save wpcarro/a5922ee1692b1bbdf733648b280b07e0 to your computer and use it in GitHub Desktop.
Poor Man's implementation of entr for Windows
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
#include <windows.h>
#include <assert.h>
#include "fixed_buffer_allocator.h"
////////////////////////////////////////////////////////////////////////////////
// Types
////////////////////////////////////////////////////////////////////////////////
typedef struct { const char *path; uint64_t writetime; } FileMeta;
typedef struct { bool help; const char *command; } ParsedArgs;
////////////////////////////////////////////////////////////////////////////////
// Constants
////////////////////////////////////////////////////////////////////////////////
static const unsigned int KiB = 1024u;
static const unsigned int MiB = 1024u * 1024u;
////////////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////////////
static bool get_write_time(uint64_t *out, const char *path) {
WIN32_FILE_ATTRIBUTE_DATA data = {0};
if (!GetFileAttributesExA(path, GetFileExInfoStandard, &data)) { return false; }
*out = (data.ftLastWriteTime.dwHighDateTime << 8) | data.ftLastWriteTime.dwLowDateTime;
return true;
}
static void print_file(FileMeta *file) {
printf("File { .path = \"%s\", .writetime = %llu }\n", file->path, file->writetime);
}
bool startswith(const char *x, const char *prefix) {
size_t prefix_len = strlen(prefix);
return strncmp(x, prefix, prefix_len) == 0;
}
static bool parse_args(FixedBufferAllocator *fba, ParsedArgs *args, int argc, const char *argv[]) {
for (int i = 1; i < argc; i += 1) {
const char *arg = argv[i];
if (strcmp(arg, "--help") == 0) {
args->help = true;
continue;
}
else if (startswith(arg, "--cmd=")) {
char *command = (char*)fba_alloc(fba, 128);
args->command = strcpy(command, arg + 6);
continue;
}
else {
return false;
}
}
if (!args->help && args->command == NULL) {
return false;
}
return true;
}
void print_usage() {
fprintf(stderr, "Usage: entr.exe [-h] --cmd=CMD\n");
fprintf(stderr, "\n");
fprintf(stderr, "Example Usage:");
fprintf(stderr, " dir /s /b | entr.exe onchange.bat\n");
fprintf(stderr, "\n");
fprintf(stderr, "Watch files and react to the changes\n");
fprintf(stderr, "\n");
fprintf(stderr, "Options:\n");
fprintf(stderr, "-h, --help: Show this help message and exit\n");
fprintf(stderr, "--cmd: Path to executable we call when files change\n");
}
////////////////////////////////////////////////////////////////////////////////
// Main
////////////////////////////////////////////////////////////////////////////////
int main(int argc, char *argv[]) {
FixedBufferAllocator fba = {0};
fba_new(&fba, 512 * MiB);
ParsedArgs args = {0};
bool ok = parse_args(&fba, &args, argc, argv);
if (!ok) { printf("NOT OK\n"); print_usage(); return 1; }
if (args.help) { print_usage(); return 0; }
const char *cmd = args.command;
FileMeta files[256] = {0};
size_t num_files = 0;
char line[512] = {0};
while (fgets(line, sizeof(line), stdin)) {
size_t len = strlen(line);
assert(line[len - 1] == '\n');
line[len - 1] = '\0';
uint64_t lwt = {0};
bool ok = get_write_time(&lwt, line);
if (!ok) {
fprintf(stderr, "ERROR: Issue getting metadata for %s. Exiting...\n", line);
return 1;
}
char *path = (char*)fba_alloc(&fba, len);
strncpy(path, line, len);
files[num_files].path = path;
files[num_files].writetime = lwt;
num_files += 1;
}
for (;;) {
for (size_t i = 0; i < num_files; i += 1) {
uint64_t latest = {0};
bool ok = get_write_time(&latest, files[i].path);
if (!ok) {
fprintf(stderr, "WARNING: Issue getting metadata for %s. Ignoring...\n", files[i].path);
continue;
}
if (latest != files[i].writetime) {
// NOTE: We should run the command only once per session for changed
// files.
//
// Maybe this should be some kind of queue system?
system(cmd);
}
files[i].writetime = latest;
}
Sleep(250);
}
// TODO(wpcarro): Capture Ctrl-C and handle it
printf("Exiting...\n");
fba_free(&fba);
return 0;
}
#include <stdlib.h>
#include <assert.h>
typedef struct {
unsigned char *buf;
size_t offset;
size_t cap;
} FixedBufferAllocator;
void fba_new(FixedBufferAllocator *a, size_t bytes) {
unsigned char *buf = (unsigned char*)malloc(bytes);
assert(buf);
a->buf = buf;
a->offset = 0;
a->cap = bytes;
}
unsigned char *fba_alloc(FixedBufferAllocator *a, size_t bytes) {
assert(a->offset + bytes < a->cap);
unsigned char *result = a->buf + a->offset;
a->offset += bytes;
return result;
}
void fba_free(FixedBufferAllocator *a) {
free(a->buf);
a->buf = NULL;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment