Skip to content

Instantly share code, notes, and snippets.

@dmohs
Last active July 26, 2022 18:10
Show Gist options
  • Save dmohs/13e2ea044707b77ff5d2af1a1c8585f4 to your computer and use it in GitHub Desktop.
Save dmohs/13e2ea044707b77ff5d2af1a1c8585f4 to your computer and use it in GitHub Desktop.
Executable to run extensionless scripts as a Node module
#!nodem
// vim: set syntax=javascript :
foo // undefined, check reported line number
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int len(char *x[]) {
for(int i = 0; ; ++i) {
if (x[i] == NULL) return i;
}
}
void concat(char *r[], char *a[], char *b[]) {
int ri = 0;
for (int i = 0; i < len(a); ++i, ++ri) {
r[ri] = a[i];
}
for (int i = 0; i < len(b); ++i, ++ri) {
r[ri] = b[i];
}
r[ri] = NULL;
}
char *mallocAndReadFile(char *path) {
FILE *f = fopen(path, "rb");
if (f == NULL) { return NULL; } // perror() for error message
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET); /* same as rewind(f); */
char *contents = malloc(fsize + 1);
fread(contents, fsize, 1, f);
fclose(f);
contents[fsize] = 0;
return contents;
}
int main(int argc, char *argv[]) {
if (len(argv) < 2) {
fprintf(stderr, "usage: %s script [arg ...]\n", argv[0]);
return 1;
}
char *scriptPath = argv[1];
char *contents = mallocAndReadFile(scriptPath);
if (contents == NULL) {
perror(scriptPath);
return 2;
}
// Node chokes on the shebang line, so advance past it. Since we're inserting one line, the
// error reporting should remain accurate.
char *srcStart = contents;
char *firstNl = index(contents, '\n');
if (strncmp(contents, "#!", 2) == 0 && firstNl != NULL) {
srcStart = firstNl + 1;
}
char *srcPrefix = "process.chdir(process.env.PWD);\n";
long srcSize = strlen(srcPrefix) + strlen(srcStart) + 1;
char *src = malloc(srcSize);
snprintf(src, srcSize, "%s%s", srcPrefix, srcStart);
int pwdExprSize = 4 + strlen(getenv("PWD")) + 1;
char *pwdExpr = malloc(pwdExprSize);
snprintf(pwdExpr, pwdExprSize, "PWD=%s", getenv("PWD"));
char *envArgs[] = {
pwdExpr,
"node", "--input-type=module",
"-e", src,
NULL
};
char *combinedArgs[len(envArgs) + len(argv) + 1];
concat(combinedArgs, envArgs, &argv[1]);
if (chdir(dirname(scriptPath)) != 0) {
perror(dirname(scriptPath));
return 3;
}
return execv("/usr/bin/env", combinedArgs);
}
#!nodem
// vim: set syntax=javascript :
// I name the executable `nodem` for "node module" and have it in my path.
import * as fs from 'fs'
import * as path from 'path'
// This works because ./node_modules/$ is a symlink to its parent.
import name from '$/lib/name.mjs'
// This works because ./node_modules/name is a skeleton module with a package.json.
import {default as nmName} from 'name'
export const main = async () => {
// argv has the node executable, then this script, then args
console.log('argv:', process.argv)
console.log('Hello '+name)
console.log('Hello '+nmName)
// These should all refer to the user's working directory, not the script's.
console.log('PWD', process.env.PWD)
console.log('dir:', path.resolve('.'))
console.log('cwd:', process.cwd())
// async/await works.
await fs.promises.access('.', fs.constants.R_OK)
// stdin works.
console.log('Echoing stdin:')
await new Promise(resolve => {
process.stdin.pipe(process.stdout)
process.stdin.on('end', resolve)
})
// Exit codes are reported correctly.
process.exit(5)
}
await main()
@dmohs
Copy link
Author

dmohs commented Jul 26, 2022

To compile:

gcc nodem.c -o nodem

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment