Skip to content
This repository has been archived by the owner on Jul 16, 2024. It is now read-only.

Commit

Permalink
Fix the example CLI scripts (#366)
Browse files Browse the repository at this point in the history
  • Loading branch information
IMax153 authored Nov 11, 2023
1 parent cb01813 commit dbc0bf7
Show file tree
Hide file tree
Showing 10 changed files with 54 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-undef */
module.exports = {
ignorePatterns: ["dist", "build", "docs", "*.md"],
ignorePatterns: ["dist", "build", "*.md"],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 2018,
Expand Down
5 changes: 3 additions & 2 deletions examples/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as Command from "@effect/cli/Command"
import * as HelpDoc from "@effect/cli/HelpDoc"
import * as Span from "@effect/cli/HelpDoc/Span"
import * as Options from "@effect/cli/Options"
import * as NodeContext from "@effect/platform-node/NodeContext"
import * as Data from "effect/Data"
import * as Effect from "effect/Effect"
import { pipe } from "effect/Function"
Expand Down Expand Up @@ -131,8 +132,7 @@ const cli = CliApp.make({
footer: HelpDoc.p("Copyright 2023")
})

pipe(
Effect.sync(() => process.argv.slice(2)),
Effect.sync(() => process.argv.slice(2)).pipe(
Effect.flatMap((args) =>
CliApp.run(cli, args, (command) =>
Option.match(command.subcommand, {
Expand All @@ -143,5 +143,6 @@ pipe(
onSome: handleGitSubcommand
}))
),
Effect.provide(NodeContext.layer),
Effect.runFork
)
4 changes: 3 additions & 1 deletion examples/prompt.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as CliApp from "@effect/cli/CliApp"
import * as Command from "@effect/cli/Command"
import * as Prompt from "@effect/cli/Prompt"
import { Effect } from "effect"
import * as NodeContext from "@effect/platform-node/NodeContext"
import * as Effect from "effect/Effect"

const colorPrompt = Prompt.select({
message: "Pick your favorite color!",
Expand All @@ -26,5 +27,6 @@ const cli = CliApp.make({

Effect.sync(() => process.argv.slice(2)).pipe(
Effect.flatMap((args) => CliApp.run(cli, args, (input) => Effect.log(input))),
Effect.provide(NodeContext.layer),
Effect.runFork
)
4 changes: 2 additions & 2 deletions src/Options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ export const date: (name: string) => Options<globalThis.Date> = InternalOptions.
* @since 1.0.0
* @category constructors
*/
export const directory: (name: string, config: Options.PathOptionsConfig) => Options<string> =
export const directory: (name: string, config?: Options.PathOptionsConfig) => Options<string> =
InternalOptions.directory

/**
Expand All @@ -230,7 +230,7 @@ export const directory: (name: string, config: Options.PathOptionsConfig) => Opt
* @since 1.0.0
* @category constructors
*/
export const file: (name: string, config: Options.PathOptionsConfig) => Options<string> =
export const file: (name: string, config?: Options.PathOptionsConfig) => Options<string> =
InternalOptions.file

/**
Expand Down
26 changes: 14 additions & 12 deletions src/internal/builtInOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import type * as HelpDoc from "../HelpDoc.js"
import type * as Options from "../Options.js"
import type * as ShellType from "../ShellType.js"
import type * as Usage from "../Usage.js"
import * as options from "./options.js"
import * as _shellType from "./shellType.js"
import * as InternalOptions from "./options.js"
import * as InternalShellType from "./shellType.js"

/** @internal */
export const showCompletions = (
Expand Down Expand Up @@ -69,21 +69,23 @@ export const builtInOptions = <A>(
usage: Usage.Usage,
helpDoc: HelpDoc.HelpDoc
): Options.Options<Option.Option<BuiltInOptions.BuiltInOptions>> => {
const help = options.boolean("help").pipe(options.withAlias("h"))
// TODO: after path/file primitives added
// const completionScriptPath = options.optional(options.file("shell-completion-script"))
const shellCompletionScriptPath = options.optional(options.text("shell-completion-script"))
const shellType = options.optional(_shellType.shellOption)
const shellCompletionIndex = options.optional(options.integer("shell-completion-index"))
const wizardOption = options.boolean("wizard")
const option = options.all({
const help = InternalOptions.boolean("help").pipe(InternalOptions.withAlias("h"))
const shellCompletionScriptPath = InternalOptions.optional(
InternalOptions.file("shell-completion-script")
)
const shellType = InternalOptions.optional(InternalShellType.shellOption)
const shellCompletionIndex = InternalOptions.optional(
InternalOptions.integer("shell-completion-index")
)
const wizard = InternalOptions.boolean("wizard")
const option = InternalOptions.all({
shellCompletionScriptPath,
shellType,
shellCompletionIndex,
help,
wizard: wizardOption
wizard
})
return options.map(option, (builtIn) => {
return InternalOptions.map(option, (builtIn) => {
if (builtIn.help) {
return Option.some(showHelp(usage, helpDoc))
}
Expand Down
10 changes: 3 additions & 7 deletions src/internal/cliApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,21 +70,17 @@ export const run = dual<
args: ReadonlyArray<string>,
execute: (a: A) => Effect.Effect<R, E, void>
): Effect.Effect<R | CliApp.CliApp.Environment, E | ValidationError.ValidationError, void> =>
Effect.gen(function*(_) {
const context = yield* _(Effect.context<CliApp.CliApp.Environment>())

Effect.contextWithEffect((context: Context.Context<CliApp.CliApp.Environment>) => {
// Attempt to parse the CliConfig from the environment, falling back to the
// default CliConfig if none was provided
const config = Option.getOrElse(
Context.getOption(context, InternalCliConfig.Tag),
() => InternalCliConfig.defaultConfig
)

// Prefix the command name to the command line arguments
const prefixedArgs = ReadonlyArray.appendAll(prefixCommand(self.command), args)

// Handle the command
return yield* _(Effect.matchEffect(self.command.parse(prefixedArgs, config), {
return Effect.matchEffect(self.command.parse(prefixedArgs, config), {
onFailure: (e) => Effect.zipRight(printDocs(e.error), Effect.fail(e)),
onSuccess: unify((directive) => {
switch (directive._tag) {
Expand All @@ -102,7 +98,7 @@ export const run = dual<
}
}
})
}))
})
}).pipe(Effect.provide(MainLive)))

// =============================================================================
Expand Down
42 changes: 23 additions & 19 deletions src/internal/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,12 @@ export class Standard<Name extends string, OptionsType, ArgsType>
Effect.when(() => normalizedArgv0 === normalizedCommandName),
Effect.flatten,
Effect.catchTag("NoSuchElementException", () => {
const error = InternalHelpDoc.p(`Missing command name: ${this.name}`)
const error = InternalHelpDoc.p(`Missing command name: '${this.name}'`)
return Effect.fail(InternalValidationError.commandMismatch(error))
})
)
}
const error = InternalHelpDoc.p(`Missing command name: ${this.name}`)
const error = InternalHelpDoc.p(`Missing command name: '${this.name}'`)
return Effect.fail(InternalValidationError.commandMismatch(error))
}
const parseBuiltInArgs = (
Expand All @@ -127,20 +127,23 @@ export class Standard<Name extends string, OptionsType, ArgsType>
ValidationError.ValidationError,
CommandDirective.CommandDirective<never>
> => {
const normalizedArgv0 = InternalCliConfig.normalizeCase(config, args[0])
const normalizedCommandName = InternalCliConfig.normalizeCase(config, this.name)
if (normalizedArgv0 === normalizedCommandName) {
const options = InternalBuiltInOptions.builtInOptions(this, this.usage, this.help)
return InternalOptions.validate(options, ReadonlyArray.drop(args, 1), config).pipe(
Effect.flatMap((tuple) => tuple[2]),
Effect.catchTag("NoSuchElementException", () => {
const error = InternalHelpDoc.p("No built-in option was matched")
return Effect.fail(InternalValidationError.noBuiltInMatch(error))
}),
Effect.map(InternalCommandDirective.builtIn)
)
if (ReadonlyArray.isNonEmptyReadonlyArray(args)) {
const argv0 = ReadonlyArray.headNonEmpty(args)
const normalizedArgv0 = InternalCliConfig.normalizeCase(config, argv0)
const normalizedCommandName = InternalCliConfig.normalizeCase(config, this.name)
if (normalizedArgv0 === normalizedCommandName) {
const options = InternalBuiltInOptions.builtInOptions(this, this.usage, this.help)
return InternalOptions.validate(options, ReadonlyArray.drop(args, 1), config).pipe(
Effect.flatMap((tuple) => tuple[2]),
Effect.catchTag("NoSuchElementException", () => {
const error = InternalHelpDoc.p("No built-in option was matched")
return Effect.fail(InternalValidationError.noBuiltInMatch(error))
}),
Effect.map(InternalCommandDirective.builtIn)
)
}
}
const error = InternalHelpDoc.p(`Missing command name: ${this.name}`)
const error = InternalHelpDoc.p(`Missing command name: '${this.name}'`)
return Effect.fail(InternalValidationError.commandMismatch(error))
}
const parseUserDefinedArgs = (
Expand Down Expand Up @@ -188,7 +191,7 @@ export class Standard<Name extends string, OptionsType, ArgsType>
if (ReadonlyArray.contains(args, "--wizard") || ReadonlyArray.contains(args, "-w")) {
return parseBuiltInArgs(ReadonlyArray.make(this.name, "--wizard"))
}
const error = InternalHelpDoc.p(`Missing command name: ${this.name}`)
const error = InternalHelpDoc.p(`Missing command name: '${this.name}'`)
return Effect.fail(InternalValidationError.commandMismatch(error))
}
return parseBuiltInArgs(args).pipe(
Expand Down Expand Up @@ -218,6 +221,7 @@ export class Standard<Name extends string, OptionsType, ArgsType>
}
}

/** @internal */
export class GetUserInput<Name extends string, ValueType>
implements Command.Command<Command.Command.ParsedUserInputCommand<Name, ValueType>>
{
Expand Down Expand Up @@ -361,11 +365,11 @@ export class OrElse<A, B> implements Command.Command<A | B> {
CommandDirective.CommandDirective<A | B>
> {
return this.left.parse(args, config).pipe(
Effect.catchSome((e) =>
InternalValidationError.isValidationError(e)
Effect.catchSome((e) => {
return InternalValidationError.isCommandMismatch(e)
? Option.some(this.right.parse(args, config))
: Option.none()
)
})
)
}

Expand Down
4 changes: 2 additions & 2 deletions src/internal/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,7 @@ export const date = (name: string): Options.Options<Date> =>
/** @internal */
export const directory = (
name: string,
config: Options.Options.PathOptionsConfig
config: Options.Options.PathOptionsConfig = {}
): Options.Options<string> =>
new Single(
name,
Expand All @@ -806,7 +806,7 @@ export const directory = (
/** @internal */
export const file = (
name: string,
config: Options.Options.PathOptionsConfig
config: Options.Options.PathOptionsConfig = {}
): Options.Options<string> =>
new Single(
name,
Expand Down
1 change: 0 additions & 1 deletion test/Options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ describe("Options", () => {
)
const args = ReadonlyArray.make("-a", "a", "-b", "b")
const result = yield* _(Effect.flip(validation(options, args, CliConfig.defaultConfig)))
console.log(result)
expect(result).toEqual(ValidationError.invalidValue(HelpDoc.p(
"Collision between two options detected - you can only " +
"specify one of either: ['-a', '-b']"
Expand Down
3 changes: 3 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
},
{
"path": "./tsconfig.test.json"
},
{
"path": "./tsconfig.examples.json"
}
]
}

0 comments on commit dbc0bf7

Please sign in to comment.