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

Commit

Permalink
add Prompt.password and Prompt.hidden
Browse files Browse the repository at this point in the history
  • Loading branch information
IMax153 committed Dec 7, 2023
1 parent be2298d commit 863779d
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .changeset/neat-ears-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/cli": patch
---

remove `"type"` option from `Prompt.text` and add `Prompt.password` and `Prompt.hidden` which return `Secret`
5 changes: 2 additions & 3 deletions examples/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@ const numberPrompt = Prompt.float({
validate: (n) => n > 0 ? Effect.succeed(n) : Effect.fail("must be greater than 0")
})

const textPrompt = Prompt.text({
const passwordPrompt = Prompt.password({
message: "Enter your password: ",
type: "password",
validate: (value) =>
value.length === 0
? Effect.fail("Password cannot be empty")
Expand All @@ -52,7 +51,7 @@ const prompt = Prompt.all([
confirmPrompt,
datePrompt,
numberPrompt,
textPrompt,
passwordPrompt,
togglePrompt
])

Expand Down
17 changes: 13 additions & 4 deletions src/Prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { QuitException, Terminal, UserInput } from "@effect/platform/Termin
import type { Effect } from "effect/Effect"
import type { Option } from "effect/Option"
import type { Pipeable } from "effect/Pipeable"
import type { Secret } from "effect/Secret"
import * as InternalPrompt from "./internal/prompt.js"
import * as InternalConfirmPrompt from "./internal/prompt/confirm.js"
import * as InternalDatePrompt from "./internal/prompt/date.js"
Expand Down Expand Up @@ -284,10 +285,6 @@ export declare namespace Prompt {
* The message to display in the prompt.
*/
readonly message: string
/**
* The type of the text option.
*/
readonly type?: "hidden" | "password" | "text"
/**
* The default value of the text option.
*/
Expand Down Expand Up @@ -426,6 +423,12 @@ export const flatMap: {
*/
export const float: (options: Prompt.FloatOptions) => Prompt<number> = InternalNumberPrompt.float

/**
* @since 1.0.0
* @category constructors
*/
export const hidden: (options: Prompt.TextOptions) => Prompt<Secret> = InternalTextPrompt.hidden

/**
* @since 1.0.0
* @category constructors
Expand All @@ -449,6 +452,12 @@ export const map: {
<Output, Output2>(self: Prompt<Output>, f: (output: Output) => Output2): Prompt<Output2>
} = InternalPrompt.map

/**
* @since 1.0.0
* @category constructors
*/
export const password: (options: Prompt.TextOptions) => Prompt<Secret> = InternalTextPrompt.password

/**
* Executes the specified `Prompt`.
*
Expand Down
40 changes: 31 additions & 9 deletions src/internal/prompt/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,19 @@ import * as Effect from "effect/Effect"
import { pipe } from "effect/Function"
import * as Option from "effect/Option"
import * as ReadonlyArray from "effect/ReadonlyArray"
import * as Secret from "effect/Secret"
import type * as Prompt from "../../Prompt.js"
import * as InternalPrompt from "../prompt.js"
import * as InternalPromptAction from "./action.js"
import * as InternalAnsiUtils from "./ansi-utils.js"

interface Options extends Required<Prompt.Prompt.TextOptions> {
/**
* The type of the text option.
*/
readonly type: "hidden" | "password" | "text"
}

interface State {
readonly cursor: number
readonly offset: number
Expand All @@ -22,7 +30,7 @@ const renderBeep = Doc.render(Doc.beep, { style: "pretty" })

const renderClearScreen = (
prevState: Option.Option<State>,
options: Required<Prompt.Prompt.TextOptions>,
options: Options,
columns: number
): Doc.AnsiDoc => {
// Erase the current line and place the cursor in column one
Expand Down Expand Up @@ -54,7 +62,7 @@ const renderClearScreen = (

const renderInput = (
nextState: State,
options: Required<Prompt.Prompt.TextOptions>,
options: Options,
submitted: boolean
): Doc.AnsiDoc => {
const annotation = Option.match(nextState.error, {
Expand Down Expand Up @@ -103,7 +111,7 @@ const renderOutput = (
nextState: State,
leadingSymbol: Doc.AnsiDoc,
trailingSymbol: Doc.AnsiDoc,
options: Required<Prompt.Prompt.TextOptions>,
options: Options,
submitted: boolean = false
): Doc.AnsiDoc => {
const annotateLine = (line: string): Doc.AnsiDoc => pipe(Doc.text(line), Doc.annotate(Ansi.bold))
Expand All @@ -126,7 +134,7 @@ const renderOutput = (
const renderNextFrame = (
prevState: Option.Option<State>,
nextState: State,
options: Required<Prompt.Prompt.TextOptions>
options: Options
): Effect.Effect<Terminal.Terminal, never, string> =>
Effect.gen(function*(_) {
const terminal = yield* _(Terminal.Terminal)
Expand All @@ -148,7 +156,7 @@ const renderNextFrame = (

const renderSubmission = (
nextState: State,
options: Required<Prompt.Prompt.TextOptions>
options: Options
) =>
Effect.gen(function*(_) {
const terminal = yield* _(Terminal.Terminal)
Expand Down Expand Up @@ -230,11 +238,13 @@ const initialState: State = {
error: Option.none()
}

/** @internal */
export const text = (options: Prompt.Prompt.TextOptions): Prompt.Prompt<string> => {
const opts: Required<Prompt.Prompt.TextOptions> = {
const basePrompt = (
options: Prompt.Prompt.TextOptions,
type: Options["type"]
): Prompt.Prompt<string> => {
const opts: Options = {
default: "",
type: "text",
type,
validate: Effect.succeed,
...options
}
Expand Down Expand Up @@ -283,3 +293,15 @@ export const text = (options: Prompt.Prompt.TextOptions): Prompt.Prompt<string>
}
)
}

/** @internal */
export const hidden = (options: Prompt.Prompt.TextOptions): Prompt.Prompt<Secret.Secret> =>
basePrompt(options, "hidden").pipe(InternalPrompt.map(Secret.fromString))

/** @internal */
export const password = (options: Prompt.Prompt.TextOptions): Prompt.Prompt<Secret.Secret> =>
basePrompt(options, "password").pipe(InternalPrompt.map(Secret.fromString))

/** @internal */
export const text = (options: Prompt.Prompt.TextOptions): Prompt.Prompt<string> =>
basePrompt(options, "text")

0 comments on commit 863779d

Please sign in to comment.