syndicate-js/packages/fs/src/index.ts

92 lines
2.9 KiB
TypeScript

import { Bytes, Observe, Ref, Turn } from "@syndicate-lang/core";
import { QuasiValue as Q } from "@syndicate-lang/core";
import { service } from '@syndicate-lang/service';
import { Contents, File, asConfig, Config, Encoding, toEncoding } from './gen/fs.js';
import chokidar from 'chokidar';
import path from 'path';
import fs from 'fs';
export function main(_argv: string[]) {
service(args => {
const config = asConfig(args);
at config.dataspace {
during Observe({
"pattern": :pattern File({
"label": config.label,
"path": \Q.lit($relativePath: string),
"encoding": \Q.lit($encoding0),
"contents": \_,
}),
}) => {
const encoding = toEncoding(encoding0);
if (encoding !== void 0 && !path.isAbsolute(relativePath)) {
trackFile(config, relativePath, encoding);
}
}
}
});
}
function trackFile(config: Config<Ref>, relativePath: string, encoding: Encoding) {
const facet = Turn.activeFacet;
const absolutePath = path.resolve(config.path, relativePath);
field contents: Contents | undefined = void 0;
at config.dataspace {
assert File({
"label": config.label,
"path": relativePath,
"encoding": encoding,
"contents": contents.value!,
}) when (contents.value !== void 0);
}
let watcher: chokidar.FSWatcher | undefined = void 0;
on stop {
if (watcher !== void 0) {
watcher.close();
watcher = void 0;
}
}
watcher = chokidar.watch(absolutePath, {
cwd: config.path,
disableGlobbing: true,
depth: 0,
});
function readContents() {
const bytes = fs.readFileSync(absolutePath);
switch (encoding._variant) {
case 'utf8':
try {
const text = new TextDecoder('utf8', { fatal: true }).decode(bytes);
contents.value = Contents.text(text);
} catch {
contents.value = Contents.invalidText(Bytes.from(bytes));
}
break;
case 'binary':
contents.value = Contents.binary(Bytes.from(bytes));
break;
}
}
function markAbsent() {
contents.value = Contents.absent();
}
watcher
.on('ready', () => facet.turn(() => {
if (contents.value === void 0) {
markAbsent();
}
}))
.on('add', () => facet.turn(readContents))
.on('change', () => facet.turn(readContents))
.on('unlink', () => facet.turn(markAbsent))
.on('addDir', () => facet.turn(() => contents.value = Contents.directory()))
.on('unlinkDir', () => facet.turn(markAbsent));
}