Skip to content

Instantly share code, notes, and snippets.

@cowboyd
Created July 7, 2023 10:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cowboyd/d070d208b172aa5b5bcd808f8ba3e5ea to your computer and use it in GitHub Desktop.
Save cowboyd/d070d208b172aa5b5bcd808f8ba3e5ea to your computer and use it in GitHub Desktop.
Effection v3 joke teller console app
import {
action,
createChannel,
createContext,
exit,
expect,
main,
type Operation,
spawn,
Stream,
suspend,
useAbortSignal,
} from "https://deno.land/x/effection@3.0.0-alpha.9/mod.ts";
const Stdin = createContext<Stream<Uint8Array, void>>("stdin");
await main(function* () {
yield* provideStdin({ raw: true });
while (true) {
// resolving the action takes us immediately back here
// thus causing a nother tick of the loop, which restarts
// everything
yield* action<void>(function* (restart) {
console.log("starting up! (h for help)");
yield* useMatcher("h", function* () {
console.table({
"h": "show this help",
"r": "restart process",
"j": "make a joke",
"q": "quit",
});
});
yield* useMatcher("r", function* () {
restart();
});
yield* useMatcher("q", function* () {
yield* exit(0, "goodbye!");
});
yield* useMatcher("j", function* () {
let status = yield* expect(Deno.permissions.query({ name: "net", host: "v2.jokeapi.dev" }));
if (status.state !== "granted") {
console.warn("you need to have net permissions enabled to get the joke");
return;
}
let signal = yield* useAbortSignal();
console.log('===> fetching a joke');
let response = yield* expect(
fetch(`https://v2.jokeapi.dev/joke/Programming?type=single`, { signal }),
);
if (!response.ok) {
console.error(response.statusText);
} else {
let payload = yield* expect(response.json());
console.log('<joke>');
console.log(payload.joke);
console.log('</joke>');
}
});
yield* suspend();
});
}
});
/**
* use the `Stdin` context to subscribe to stdin, and continuosly check
* for the next character
*/
function* useMatcher(char: string, op: () => Operation<void>) {
yield* spawn(function* () {
let decoder = new TextDecoder();
let stdin = yield* Stdin;
let subscription = yield* stdin;
while (true) {
let next = yield* subscription.next();
if (next.done) {
break;
} else if (decoder.decode(next.value) === char) {
yield* op();
}
}
});
}
/**
* `stdin` is a stateful POSIX stream, and so you can only read bytes off
* of it once, and then they will never be read again. But since we want
* multiple matchers to all be consuming stdin at the same time, we need to
* read form it in a single place, and then broadcast it to anybody that is
* interested.
* This sets up a `Stdin` context, which is the output side of
*/
function* provideStdin({ raw }: { raw: boolean }): Operation<void> {
let { input, output } = createChannel<Uint8Array, void>();
yield* Stdin.set(output);
yield* spawn(function* () {
if (raw) {
Deno.stdin.setRaw(true);
}
let reader = Deno.stdin.readable.getReader();
try {
while (true) {
let result = yield* expect(reader.read());
if (result.done) {
yield* input.close();
break;
} else {
yield* input.send(result.value);
}
}
} finally {
Deno.stdin.setRaw(false);
reader.releaseLock();
}
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment