From f88d5bcb4f6505b6bc9f8144cf3d92b5c09a85d1 Mon Sep 17 00:00:00 2001 From: Sebastian Lorenz Date: Sat, 23 Sep 2023 17:51:03 +0200 Subject: [PATCH] simplify --- cli/package.json | 8 +++-- cli/src/index.ts | 38 ----------------------- cli/src/wttr.ts | 79 ++++++++++++++++++++++++++++++------------------ pnpm-lock.yaml | 3 ++ 4 files changed, 58 insertions(+), 70 deletions(-) delete mode 100644 cli/src/index.ts diff --git a/cli/package.json b/cli/package.json index cb7b7b1..87284ae 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,8 +1,8 @@ { "name": "cli", "private": true, - "devDependencies": { - "@types/node": "^18.11.11" + "scripts": { + "dev": "tsx --watch" }, "dependencies": { "@effect/cli": "^0.12.0", @@ -10,5 +10,9 @@ "@effect/platform-node": "^0.17.0", "@effect/schema": "^0.36.5", "effect": "2.0.0-next.34" + }, + "devDependencies": { + "@types/node": "^18.11.11", + "tsx": "^3.12.10" } } diff --git a/cli/src/index.ts b/cli/src/index.ts deleted file mode 100644 index 2124b7b..0000000 --- a/cli/src/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Effect, Option, Cause, Layer } from 'effect' -import * as Http from '@effect/platform-node/HttpClient' -import * as Node from '@effect/platform-node/Runtime' -import * as Command from '@effect/cli/Command' -import * as Cli from '@effect/cli/CliApp' -import * as Options from '@effect/cli/Options' -import * as Args from '@effect/cli/Args' -import * as Console from '@effect/cli/Console' -import * as Wttr from './wttr' - -const cli = Cli.make({ - name: 'Weather', - version: '1.2.3', - command: Command.make('weather', { - args: Args.between(Args.text({ name: 'location' }), 0, 1), - options: Options.all({ - url: Options.withDefault(Options.text('url'), 'https://wttr.in'), - }), - }) -}) - -Node.runMain(Effect.sync(() => process.argv.slice(2)).pipe( - Effect.flatMap((args) => - Cli.run(cli, args, ({ options, args }) => { - const location = Option.fromIterable(args) - const program = Wttr.Wttr.pipe( - Effect.flatMap(wttr => wttr.getWeather(location)), - // TODO: Render the output properly. - Effect.tap(Effect.log), - ) - - const wttr = Layer.provide(Http.client.layer, Wttr.makeLayer(options.url)) - return Effect.provideLayer(program, wttr) - }) - ), - Effect.provideLayer(Console.layer), - Effect.tapErrorCause((_) => Effect.logError(Cause.pretty(_))) -)) diff --git a/cli/src/wttr.ts b/cli/src/wttr.ts index bb822d4..8817cdd 100644 --- a/cli/src/wttr.ts +++ b/cli/src/wttr.ts @@ -1,8 +1,13 @@ -import { Effect, Data, Context, Layer, Option } from 'effect' -import * as Http from '@effect/platform/HttpClient' +import { Effect, Option, Cause } from 'effect' +import * as Http from '@effect/platform-node/HttpClient' +import * as Node from '@effect/platform-node/Runtime' +import * as Command from '@effect/cli/Command' +import * as Cli from '@effect/cli/CliApp' +import * as Options from '@effect/cli/Options' +import * as Args from '@effect/cli/Args' +import * as Console from '@effect/cli/Console' import * as Schema from '@effect/schema/Schema' -type WttrWeather = Schema.Schema.To const WttrWeatherSchema = Schema.struct({ date: Schema.dateFromString(Schema.string), avgtempC: Schema.numberFromString(Schema.string), @@ -19,33 +24,47 @@ const WttrResponseSchema = Schema.struct({ weather: Schema.array(WttrWeatherSchema) }) -class WttrError extends Data.TaggedClass('WttrError')<{ - cause: unknown -}> {} +const cli = Cli.make({ + name: 'Weather', + version: '1.2.3', + command: Command.make('weather', { + // Accept 0 or 1 arguments for the location and convert it to a `Option`. + args: Args.between(Args.text({ name: 'location' }), 0, 1).pipe(Args.map(Option.fromIterable)), + options: Options.all({ + url: Options.withDefault(Options.text('url'), 'https://wttr.in'), + }), + }) +}) -export const Wttr = Context.Tag() -export interface Wttr { - readonly getWeather: (location: Option.Option) => Effect.Effect> -} +Node.runMain(Effect.sync(() => process.argv.slice(2)).pipe( + Effect.flatMap((args) => + Cli.run(cli, args, ({ options: { url }, args: location }) => { + const program = Effect.gen(function* ($) { + const defaultClient = yield* $(Http.client.Client) + const wttrClient = defaultClient.pipe( + // Always prepend the service url to all requests. This makes it more convenient to use the client. + Http.client.mapRequest(Http.request.prependUrl(url)), + // Tell the wttr.in service to return json. + Http.client.mapRequest(Http.request.appendUrlParam('format', 'j1')), + // Only accept status responses with a `2xx` status code. Fail otherwise. + Http.client.filterStatusOk, + // Decode all responses using the `WttrResponseSchema` schema. + Http.client.mapEffect(Http.response.schemaBodyJson(WttrResponseSchema)), + ); -export const makeLayer = (url: string) => Layer.effect(Wttr, Effect.gen(function* ($) { - const defaultClient = yield* $(Http.client.Client) - const wttrClient = defaultClient.pipe( - Http.client.mapRequest(Http.request.prependUrl(url)), - Http.client.mapRequest(Http.request.appendUrlParam('format', 'j1')), - Http.client.filterStatusOk, - Http.client.mapEffect(Http.response.schemaBodyJson(WttrResponseSchema)), - Http.client.catchTags({ - 'RequestError': (cause) => Effect.fail(new WttrError({ cause })), - 'ResponseError': (cause) => Effect.fail(new WttrError({ cause })), - 'ParseError': (cause) => Effect.fail(new WttrError({ cause })), - }), - ) + // Request the weather for the provided location or the current location if none was provided. + const wttrResponse = yield* $(Option.match(location, { + onNone: () => Http.request.get('/'), + onSome: (_) => Http.request.get(`/${encodeURIComponent(_)}`), + }).pipe(wttrClient, Effect.map(_ => _.weather))); - return Wttr.of({ - getWeather: (location) => Option.match(location, { - onNone: () => Http.request.get('/'), - onSome: (_) => Http.request.get(`/${encodeURIComponent(_)}`), - }).pipe(wttrClient, Effect.map(_ => _.weather)), - }) -})) + // TODO: Pretty print the weather. + yield* $(Effect.log(wttrResponse)); + }); + + return Effect.provideLayer(program, Http.client.layer) + }) + ), + Effect.provideLayer(Console.layer), + Effect.tapErrorCause((_) => Effect.logError(Cause.pretty(_))) +)) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4011490..0e8c6c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,6 +33,9 @@ importers: '@types/node': specifier: ^18.11.11 version: 18.16.19 + tsx: + specifier: ^3.12.10 + version: 3.12.10 node-scripts: dependencies: