Created
July 7, 2023 10:15
-
-
Save cowboyd/d070d208b172aa5b5bcd808f8ba3e5ea to your computer and use it in GitHub Desktop.
Effection v3 joke teller console app
This file contains 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
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