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 0cffde1
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 6 deletions.
12 changes: 11 additions & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
{
"name": "cli",
"private": true
"private": true,
"devDependencies": {
"@types/node": "^18.11.11"
},
"dependencies": {
"@effect/cli": "^0.12.0",
"@effect/platform": "^0.16.1",
"@effect/platform-node": "^0.17.0",
"@effect/schema": "^0.36.5",
"effect": "2.0.0-next.34"
}
}
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"]
}
81 changes: 76 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 0cffde1

Please sign in to comment.