Skip to content

Commit

Permalink
add weather cli example
Browse files Browse the repository at this point in the history
  • Loading branch information
fubhy committed Sep 23, 2023
1 parent e17b2f5 commit 24871b0
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 0 deletions.
38 changes: 38 additions & 0 deletions cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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(_)))
))
51 changes: 51 additions & 0 deletions cli/src/wttr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Effect, Data, Context, Layer, Option } from 'effect'
import * as Http from '@effect/platform/HttpClient'
import * as Schema from '@effect/schema/Schema'

type WttrWeather = Schema.Schema.To<typeof WttrWeatherSchema>
const WttrWeatherSchema = Schema.struct({
date: Schema.dateFromString(Schema.string),
avgtempC: Schema.numberFromString(Schema.string),
avgtempF: Schema.numberFromString(Schema.string),
maxtempC: Schema.numberFromString(Schema.string),
maxtempF: Schema.numberFromString(Schema.string),
mintempC: Schema.numberFromString(Schema.string),
mintempF: Schema.numberFromString(Schema.string),
sunHour: Schema.numberFromString(Schema.string),
uvIndex: Schema.numberFromString(Schema.string)
})

const WttrResponseSchema = Schema.struct({
weather: Schema.array(WttrWeatherSchema)
})

class WttrError extends Data.TaggedClass('WttrError')<{
cause: unknown
}> {}

export const Wttr = Context.Tag<Wttr>()
export interface Wttr {
readonly getWeather: (location: Option.Option<string>) => Effect.Effect<never, WttrError, ReadonlyArray<WttrWeather>>
}

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 })),
}),
)

return Wttr.of({
getWeather: (location) => Option.match(location, {
onNone: () => Http.request.get('/'),
onSome: (_) => Http.request.get(`/${encodeURIComponent(_)}`),
}).pipe(wttrClient, Effect.map(_ => _.weather)),
})
}))
10 changes: 10 additions & 0 deletions cli/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"noEmit": true,
"module": "Node16",
"moduleResolution": "Node16",
"strict": true,
"plugins": [{ "name": "@effect/language-service" }],
},
"include": ["src/**/*.ts"]
}

0 comments on commit 24871b0

Please sign in to comment.