Created
September 4, 2025 19:19
-
-
Save wpcarro/a5922ee1692b1bbdf733648b280b07e0 to your computer and use it in GitHub Desktop.
Poor Man's implementation of entr for Windows
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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